@mars-sea/dsh-commandcode-provider 0.12.1 → 0.12.2

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
@@ -36,6 +36,38 @@ window.__ModuleLoader__.load({
36
36
  };
37
37
  }
38
38
  //#endregion
39
+ //#region src/client/error-text.ts
40
+ /**
41
+ * Turn a caught value into readable text for the settings page's error UI.
42
+ *
43
+ * @module dsh-commandcode-provider/client/error-text
44
+ */
45
+ /**
46
+ * `error instanceof Error ? error.message : String(error)` degrades to the
47
+ * literal string `[object Object]` for a plain object with no `Error`
48
+ * prototype — which is exactly what a Remote call across the webview/host
49
+ * boundary can hand back: `dsh-typert-protocol` serializes a rejection for
50
+ * the wire, and a structurally-cloned value loses its prototype chain even
51
+ * when the host threw a real `Error` (issue #74). Reading a `message` string
52
+ * off the plain object first recovers the original text in that case; only a
53
+ * value with neither an `Error` prototype nor a usable `message` field falls
54
+ * through to a JSON dump.
55
+ */
56
+ function readableErrorText(error) {
57
+ if (error instanceof Error) return error.message;
58
+ if (typeof error === "string") return error;
59
+ if (error !== null && typeof error === "object") {
60
+ const message = error.message;
61
+ if (typeof message === "string" && message !== "") return message;
62
+ try {
63
+ return JSON.stringify(error);
64
+ } catch {
65
+ return Object.prototype.toString.call(error);
66
+ }
67
+ }
68
+ return String(error);
69
+ }
70
+ //#endregion
39
71
  //#region src/client/settings-scope.ts
40
72
  /**
41
73
  * Backoff before re-reading a directory whose FIRST read failed, in millis.
@@ -169,7 +201,7 @@ window.__ModuleLoader__.load({
169
201
  outcome = typeof value === "object" && value !== null ? { view: value } : { failure: "settings describe answered no view" };
170
202
  } else outcome = { failure: response.error?.message ?? "settings describe failed" };
171
203
  } catch (error) {
172
- outcome = { failure: error instanceof Error ? error.message : String(error) };
204
+ outcome = { failure: readableErrorText(error) };
173
205
  }
174
206
  if (generation !== this.generation) continue;
175
207
  if ("view" in outcome) {
@@ -310,7 +342,7 @@ window.__ModuleLoader__.load({
310
342
  } catch (error) {
311
343
  return {
312
344
  ok: false,
313
- error: { message: error instanceof Error ? error.message : String(error) }
345
+ error: { message: readableErrorText(error) }
314
346
  };
315
347
  }
316
348
  }
@@ -417,6 +449,7 @@ window.__ModuleLoader__.load({
417
449
  subscribe: (listener) => scope.subscribe(listener),
418
450
  set: (field, value) => scope.set(field, value),
419
451
  unset: (field) => scope.unset(field),
452
+ mutate: (ops, revision) => scope.mutate(ops, revision),
420
453
  refresh: () => {
421
454
  mirror.load();
422
455
  },
@@ -428,6 +461,23 @@ window.__ModuleLoader__.load({
428
461
  };
429
462
  }
430
463
  //#endregion
464
+ //#region src/timeout-limits.ts
465
+ /** 两端共用可编辑超时范围,存储仍是整数毫秒;提示不能借用重试退避上限。 */
466
+ const MIN_TIMEOUT_SECONDS = .001;
467
+ const MAX_TIMEOUT_SECONDS = 3600;
468
+ //#endregion
469
+ //#region src/model-visibility.ts
470
+ /** 网页、终端与模型目录共用的选择规则;显式开关优先于白名单。 */
471
+ function modelIsVisible(id, allowlist, overrides) {
472
+ const flag = overrides !== null && typeof overrides === "object" && !Array.isArray(overrides) ? overrides[id] : void 0;
473
+ return typeof flag === "boolean" ? flag : allowlist.length === 0 || allowlist.includes(id);
474
+ }
475
+ /** 只接收布尔事实,手工配置中的其他值不能成为可见性开关。 */
476
+ function readVisibility(raw) {
477
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
478
+ return Object.fromEntries(Object.entries(raw).filter(([id, value]) => id !== "" && typeof value === "boolean"));
479
+ }
480
+ //#endregion
431
481
  //#region src/client/settings.ts
432
482
  /**
433
483
  * Browser controller for the "Command Code" settings page (a `settings.section`
@@ -526,28 +576,25 @@ window.__ModuleLoader__.load({
526
576
  }
527
577
  };
528
578
  }
529
- const MAX_TIMEOUT_SECONDS = 3600;
530
579
  /**
531
- * A duration field the user edits in WHOLE SECONDS.
580
+ * 秒制输入保留毫秒精度,兼容已有 1500ms、1ms 等配置。
532
581
  *
533
582
  * Milliseconds are the wrong unit to show: the defaults are 300000 and the
534
583
  * ceiling is 2147483647, so the page asked users to do arithmetic to answer
535
584
  * "how long is a reasonable wait". `format` divides, `parse` multiplies, and
536
585
  * the stored value never leaves milliseconds.
537
586
  *
538
- * Decimals are refused rather than rounded. "1.5" is a format the control does
539
- * not accept, and silently storing 1000 or 2000 for it would make the saved
540
- * profile disagree with the box it was typed in.
587
+ * 最多三位小数;不接受更多位数后悄悄取整,避免页面与存储不一致。
541
588
  */
542
589
  function secondsField(field, bounds) {
543
590
  return {
544
591
  field,
545
- format: (value) => typeof value === "number" ? String(Math.round(value / 1e3)) : "",
592
+ format: (value) => typeof value === "number" ? String(value / 1e3) : "",
546
593
  parse: (text) => {
547
594
  const trimmed = text.trim();
548
595
  if (trimmed === "") return { kind: "clear" };
549
596
  const parsed = Number(trimmed);
550
- if (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return {
597
+ if (!Number.isFinite(parsed) || !/^-?(?:\d+(?:\.\d{1,3})?|\.\d{1,3})$/.test(trimmed)) return {
551
598
  kind: "invalid",
552
599
  reason: "format"
553
600
  };
@@ -561,7 +608,7 @@ window.__ModuleLoader__.load({
561
608
  };
562
609
  return {
563
610
  kind: "set",
564
- value: parsed * 1e3
611
+ value: Math.round(parsed * 1e3)
565
612
  };
566
613
  }
567
614
  };
@@ -570,11 +617,11 @@ window.__ModuleLoader__.load({
570
617
  const SECTION_FIELDS = [
571
618
  textField("apiBase"),
572
619
  secondsField("requestTimeoutMs", {
573
- min: 1,
620
+ min: MIN_TIMEOUT_SECONDS,
574
621
  max: MAX_TIMEOUT_SECONDS
575
622
  }),
576
623
  secondsField("streamIdleTimeoutMs", {
577
- min: 1,
624
+ min: MIN_TIMEOUT_SECONDS,
578
625
  max: MAX_TIMEOUT_SECONDS
579
626
  }),
580
627
  numberField$1("transportMaxRetries", {
@@ -726,7 +773,9 @@ window.__ModuleLoader__.load({
726
773
  accountModels: Object.fromEntries(accountModelMap(this.storedRules())),
727
774
  accountBusy: this.accountPending > 0,
728
775
  accountFailed: this.accountFailed,
776
+ pendingCredentialCleanup: this.cleanupRefs(),
729
777
  visibleModels: this.effectiveVisibleModels(),
778
+ visibleModelsAll: this.visibleModelsDraft === void 0 ? this.storedShowsAll() : this.visibleModelsDraft.length === 0,
730
779
  catalogModels: this.catalogModels,
731
780
  catalogFailed: this.catalogFailed,
732
781
  dirty: plan.length > 0 || this.visibleModelsDirty(),
@@ -780,6 +829,8 @@ window.__ModuleLoader__.load({
780
829
  }
781
830
  /** Write every staged edit, then re-read the Host's accepted state. */
782
831
  async save() {
832
+ const submitted = new Map(this.staged);
833
+ const submittedVisible = this.visibleModelsDraft;
783
834
  const plan = this.plan();
784
835
  const visibleRuns = this.visibleModelsPlan();
785
836
  if (plan.length === 0 && visibleRuns.length === 0 || this.saving) return;
@@ -808,8 +859,8 @@ window.__ModuleLoader__.load({
808
859
  this.failed = !landed;
809
860
  if (landed) {
810
861
  this.savedCount += 1;
811
- this.staged.clear();
812
- this.visibleModelsDraft = void 0;
862
+ for (const [field, draft] of submitted) if (this.staged.get(field) === draft) this.staged.delete(field);
863
+ if (this.visibleModelsDraft === submittedVisible) this.visibleModelsDraft = void 0;
813
864
  } else this.reconcileStaging();
814
865
  this.publish();
815
866
  }
@@ -869,23 +920,74 @@ window.__ModuleLoader__.load({
869
920
  });
870
921
  }
871
922
  /**
872
- * Remove one stored extra account now: its stored key, its dedicated
873
- * models and a pin naming it go with it, so nothing orphaned remains.
923
+ * 账户、路由、固定账户与清理队列一起提交;随后才删密钥。
924
+ * 跨服务不能构造事务,持久化队列让中断或凭据失败后仍可重试。
874
925
  */
875
926
  removeAccount(ref) {
876
927
  return this.runAccountOp("remove", async () => {
877
- if (this.credentialStates.get(ref)?.configured === true && !await this.unsetKey(ref)) return false;
928
+ if (this.scope.mutate === void 0) return false;
929
+ if (!this.rawStoredAccounts().some((entry) => entry.apiKeyEnv === ref) && !this.cleanupRefs().includes(ref)) return false;
930
+ const snapshot = this.scope.getSnapshot();
878
931
  const list = this.rawStoredAccounts().filter((entry) => entry.apiKeyEnv !== ref).map((entry) => ({ ...entry }));
879
- if (!await this.writeAccountList(list)) return false;
880
- const map = accountModelMap(this.storedRules());
881
- if (map.has(ref)) {
882
- map.delete(ref);
883
- if (!await this.writeRules(rulesFromMap(map))) return false;
884
- }
885
- if (this.sectionValue("activeAccount") === ref) return this.clear("activeAccount");
886
- return true;
932
+ const rules = this.storedRules().filter((rule) => rule.account !== ref);
933
+ const cleanup = [.../* @__PURE__ */ new Set([...this.cleanupRefs(), ref])];
934
+ const ops = [
935
+ {
936
+ op: "set",
937
+ path: ["accounts"],
938
+ value: list
939
+ },
940
+ {
941
+ op: "set",
942
+ path: ["modelAccountRules"],
943
+ value: rules
944
+ },
945
+ {
946
+ op: "set",
947
+ path: ["credentialCleanupRefs"],
948
+ value: cleanup
949
+ }
950
+ ];
951
+ const pinned = this.sectionValue("activeAccount") === ref;
952
+ if (pinned) ops.push(this.baseValue("activeAccount") === ref ? {
953
+ op: "set",
954
+ path: ["activeAccount"],
955
+ value: ""
956
+ } : {
957
+ op: "unset",
958
+ path: ["activeAccount"]
959
+ });
960
+ await this.scope.mutate(ops, snapshot.revision);
961
+ if (this.rawStoredAccounts().some((entry) => entry.apiKeyEnv === ref) || this.storedRules().some((rule) => rule.account === ref) || pinned && this.sectionValue("activeAccount") === ref || !this.cleanupRefs().includes(ref)) return false;
962
+ return this.cleanRemovedCredential(ref);
887
963
  });
888
964
  }
965
+ /** 清理重试不能再次删除同名新账户,配置移除与凭据清理分开调用。 */
966
+ retryCredentialCleanup(ref) {
967
+ return this.runAccountOp("remove", () => this.cleanRemovedCredential(ref));
968
+ }
969
+ async cleanRemovedCredential(ref) {
970
+ if (!this.cleanupRefs().includes(ref) || this.scope.mutate === void 0) return false;
971
+ if (!await this.describeAll([ref])) return false;
972
+ const baseAccounts = this.baseValue("accounts");
973
+ const inherited = Array.isArray(baseAccounts) && baseAccounts.some((entry) => typeof entry === "object" && entry !== null && entry.apiKeyEnv === ref);
974
+ if (!(ref === this.credentialRef || inherited || this.rawStoredAccounts().some((entry) => entry.apiKeyEnv === ref) || this.storedRules().some((rule) => rule.account === ref) || this.sectionValue("activeAccount") === ref) && this.credentialStates.get(ref)?.configured === true && !await this.unsetKey(ref)) return false;
975
+ return this.finishCleanup(ref);
976
+ }
977
+ cleanupRefs() {
978
+ const raw = this.sectionValue("credentialCleanupRefs");
979
+ return Array.isArray(raw) ? [...new Set(raw.filter((ref) => typeof ref === "string" && ref !== ""))] : [];
980
+ }
981
+ async finishCleanup(ref) {
982
+ const snapshot = this.scope.getSnapshot();
983
+ const refs = this.cleanupRefs().filter((entry) => entry !== ref);
984
+ await this.scope.mutate?.([{
985
+ op: "set",
986
+ path: ["credentialCleanupRefs"],
987
+ value: refs
988
+ }], snapshot.revision);
989
+ return !this.cleanupRefs().includes(ref);
990
+ }
889
991
  /** Store a replacement key now; `target` is `'default'` or an extra account's reference. */
890
992
  setAccountKey(target, key) {
891
993
  const value = key.trim();
@@ -1037,7 +1139,7 @@ window.__ModuleLoader__.load({
1037
1139
  if (field === "apiKey" || staged.clear) continue;
1038
1140
  if (staged.text === this.spec(field).format(this.sectionValue(field))) this.staged.delete(field);
1039
1141
  }
1040
- if (this.visibleModelsDraft !== void 0 && sameModels(this.visibleModelsDraft, this.storedVisibleModels())) this.visibleModelsDraft = void 0;
1142
+ if (this.visibleModelsDraft !== void 0 && !this.visibleModelsDirty()) this.visibleModelsDraft = void 0;
1041
1143
  }
1042
1144
  async clear(field) {
1043
1145
  if (!this.stored(field)) return true;
@@ -1082,9 +1184,10 @@ window.__ModuleLoader__.load({
1082
1184
  try {
1083
1185
  response = await this.api.credentials.describe(refs);
1084
1186
  } catch {
1085
- return;
1187
+ return false;
1086
1188
  }
1087
- if (!response.ok) return;
1189
+ if (!response.ok) return false;
1190
+ if (extra.some((ref) => typeof response.value?.[ref]?.configured !== "boolean")) return false;
1088
1191
  let changed = false;
1089
1192
  for (const ref of refs) {
1090
1193
  const view = response.value?.[ref];
@@ -1099,6 +1202,7 @@ window.__ModuleLoader__.load({
1099
1202
  }
1100
1203
  }
1101
1204
  if (changed) this.publish();
1205
+ return true;
1102
1206
  }
1103
1207
  /**
1104
1208
  * Fetch the model catalog through the Host Remote. Runs once at construction;
@@ -1200,24 +1304,61 @@ window.__ModuleLoader__.load({
1200
1304
  const after = this.storedRules();
1201
1305
  return after.length === list.length && list.every((item, index) => after[index] !== void 0 && sameModels(after[index].models, item.models) && after[index].account === item.account);
1202
1306
  }
1203
- /** The stored visible-model allowlist (`visibleModels`); empty = show all. */
1204
- storedVisibleModels() {
1307
+ /** 原始白名单;空列表延续现有的显示全部语义。 */
1308
+ storedAllowlist() {
1205
1309
  const raw = this.scope.getSnapshot().value?.visibleModels;
1206
1310
  if (!Array.isArray(raw)) return [];
1207
1311
  return raw.filter((m) => typeof m === "string" && m !== "");
1208
1312
  }
1313
+ storedShowsAll() {
1314
+ return this.storedAllowlist().length === 0 && !Object.values(readVisibility(this.scope.getSnapshot().value?.modelVisibility)).includes(false);
1315
+ }
1316
+ /** 目录、白名单与显式显示的旧模型一起投影,目录失败不删除旧选择。 */
1317
+ storedVisibleModels() {
1318
+ const list = this.storedAllowlist();
1319
+ const flags = readVisibility(this.scope.getSnapshot().value?.modelVisibility);
1320
+ return [.../* @__PURE__ */ new Set([
1321
+ ...list,
1322
+ ...this.catalogModels.map((model) => model.id),
1323
+ ...Object.keys(flags)
1324
+ ])].filter((id) => modelIsVisible(id, list, flags));
1325
+ }
1209
1326
  effectiveVisibleModels() {
1210
- return this.visibleModelsDraft ?? this.storedVisibleModels();
1327
+ if (this.visibleModelsDraft === void 0) return this.storedVisibleModels();
1328
+ return this.visibleModelsDraft.length === 0 ? this.catalogModels.map((model) => model.id) : this.visibleModelsDraft;
1211
1329
  }
1212
1330
  visibleModelsDirty() {
1213
- return this.visibleModelsDraft !== void 0 && !sameModels(this.visibleModelsDraft, this.storedVisibleModels());
1331
+ if (this.visibleModelsDraft === void 0) return false;
1332
+ if (this.visibleModelsDraft.length === 0) return !this.storedShowsAll();
1333
+ return this.storedShowsAll() || !sameModels(this.visibleModelsDraft, this.storedVisibleModels());
1214
1334
  }
1215
1335
  visibleModelsPlan() {
1216
1336
  if (!this.visibleModelsDirty()) return [];
1337
+ const list = [...this.visibleModelsDraft ?? []];
1217
1338
  return [async () => {
1218
- const list = this.visibleModelsDraft ?? [];
1219
- await this.scope.set("visibleModels", list);
1220
- return sameModels(this.storedVisibleModels(), list);
1339
+ if (this.scope.mutate === void 0) return false;
1340
+ const snapshot = this.scope.getSnapshot();
1341
+ const layers = [
1342
+ snapshot.base,
1343
+ snapshot.user,
1344
+ snapshot.value
1345
+ ];
1346
+ const keys = /* @__PURE__ */ new Set();
1347
+ for (const layer of layers) {
1348
+ if (layer === null || typeof layer !== "object") continue;
1349
+ for (const id of Object.keys(readVisibility(layer.modelVisibility))) keys.add(id);
1350
+ }
1351
+ const flags = Object.fromEntries([...keys].map((id) => [id, list.length === 0 || list.includes(id)]));
1352
+ await this.scope.mutate([{
1353
+ op: "set",
1354
+ path: ["visibleModels"],
1355
+ value: list
1356
+ }, {
1357
+ op: "set",
1358
+ path: ["modelVisibility"],
1359
+ value: flags
1360
+ }], snapshot.revision);
1361
+ return list.length === 0 ? this.storedShowsAll() : sameModels(this.storedVisibleModels(), list);
1221
1362
  }];
1222
1363
  }
1223
1364
  publish() {
@@ -1324,7 +1465,7 @@ window.__ModuleLoader__.load({
1324
1465
  this.current = {
1325
1466
  ...this.current,
1326
1467
  status: "error",
1327
- error: error instanceof Error ? error.message : String(error)
1468
+ error: readableErrorText(error)
1328
1469
  };
1329
1470
  } finally {
1330
1471
  if (generation === this.generation) this.inFlight = false;
@@ -1458,7 +1599,7 @@ window.__ModuleLoader__.load({
1458
1599
  this.publish({
1459
1600
  status: "error",
1460
1601
  table: void 0,
1461
- error: error instanceof Error ? error.message : String(error)
1602
+ error: readableErrorText(error)
1462
1603
  });
1463
1604
  this.scheduleRetry();
1464
1605
  });
@@ -1578,7 +1719,7 @@ window.__ModuleLoader__.load({
1578
1719
  } catch (error) {
1579
1720
  result = {
1580
1721
  ok: false,
1581
- error: { message: error instanceof Error ? error.message : String(error) }
1722
+ error: { message: readableErrorText(error) }
1582
1723
  };
1583
1724
  }
1584
1725
  if (this.superseded(generation)) return;
@@ -2262,7 +2403,7 @@ window.__ModuleLoader__.load({
2262
2403
  }
2263
2404
  //#endregion
2264
2405
  //#region package.json
2265
- var version = "0.12.1";
2406
+ var version = "0.12.2";
2266
2407
  var repository = {
2267
2408
  "type": "git",
2268
2409
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -2520,7 +2661,7 @@ window.__ModuleLoader__.load({
2520
2661
  }) : null;
2521
2662
  }
2522
2663
  /** One text/number field row. */
2523
- function Field({ id, label, hint, state, disabled, numeric, wide, placeholder, onEdit, onReset, t }) {
2664
+ function Field({ id, label, hint, state, disabled, numeric, decimal, wide, placeholder, onEdit, onReset, t }) {
2524
2665
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingRow, {
2525
2666
  title: label,
2526
2667
  titleFor: id,
@@ -2529,7 +2670,7 @@ window.__ModuleLoader__.load({
2529
2670
  t
2530
2671
  }),
2531
2672
  description: hint,
2532
- error: state.invalid ? invalidCopy(state.invalidReason, t) : void 0,
2673
+ error: state.invalid ? invalidCopy(state.invalidReason, t, decimal) : void 0,
2533
2674
  control: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldOverride, {
2534
2675
  label,
2535
2676
  state,
@@ -2540,7 +2681,7 @@ window.__ModuleLoader__.load({
2540
2681
  id,
2541
2682
  className: `cc-input ${wide ? "cc-rowInputWide" : "cc-rowInput"}${state.invalid ? " cc-inputInvalid" : ""}`,
2542
2683
  type: "text",
2543
- inputMode: numeric ? "numeric" : void 0,
2684
+ inputMode: decimal ? "decimal" : numeric ? "numeric" : void 0,
2544
2685
  "aria-invalid": state.invalid || void 0,
2545
2686
  value: state.text,
2546
2687
  placeholder,
@@ -2550,7 +2691,12 @@ window.__ModuleLoader__.load({
2550
2691
  });
2551
2692
  }
2552
2693
  /** The per-field error copy for a staged draft's failure reason. */
2553
- function invalidCopy(reason, t) {
2694
+ function invalidCopy(reason, t, duration = false) {
2695
+ if (duration) {
2696
+ if (reason === "tooSmall") return t("durationTooSmall");
2697
+ if (reason === "tooLarge") return t("durationTooLarge");
2698
+ return t("durationInvalid");
2699
+ }
2554
2700
  if (reason === "tooSmall") return t("numberTooSmall");
2555
2701
  if (reason === "tooLarge") return t("numberTooLarge");
2556
2702
  return t("invalidNumber");
@@ -2923,8 +3069,7 @@ window.__ModuleLoader__.load({
2923
3069
  function VisibleModelsRow({ t, state, disabled, onSelect, onClear }) {
2924
3070
  const count = state.visibleModels.length;
2925
3071
  const pickT = (key, params) => {
2926
- if (key === "modelPick") return t("visibleModelsPick");
2927
- if (key === "modelCount") return t("visibleModelsCount", params);
3072
+ if (key === "modelPick" || key === "modelCount") return state.visibleModelsAll ? t("visibleModelsShowAll") : t("visibleModelsCount", { count });
2928
3073
  return t(key, params);
2929
3074
  };
2930
3075
  const readiness = {
@@ -2946,7 +3091,7 @@ window.__ModuleLoader__.load({
2946
3091
  onClick: () => onSelect(state.visibleModels.filter((id) => !staleIds.includes(id))),
2947
3092
  children: t("visibleModelsCleanStale", { count: staleIds.length })
2948
3093
  }) : null,
2949
- count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3094
+ !state.visibleModelsAll ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2950
3095
  type: "button",
2951
3096
  className: "cc-linkButton",
2952
3097
  disabled,
@@ -3127,6 +3272,7 @@ window.__ModuleLoader__.load({
3127
3272
  state: state.requestTimeoutMs,
3128
3273
  disabled,
3129
3274
  numeric: true,
3275
+ decimal: true,
3130
3276
  onEdit: (text) => onEdit("requestTimeoutMs", text),
3131
3277
  onReset: () => onReset("requestTimeoutMs"),
3132
3278
  t
@@ -3138,6 +3284,7 @@ window.__ModuleLoader__.load({
3138
3284
  state: state.streamIdleTimeoutMs,
3139
3285
  disabled,
3140
3286
  numeric: true,
3287
+ decimal: true,
3141
3288
  onEdit: (text) => onEdit("streamIdleTimeoutMs", text),
3142
3289
  onReset: () => onReset("streamIdleTimeoutMs"),
3143
3290
  t
@@ -3876,6 +4023,19 @@ window.__ModuleLoader__.load({
3876
4023
  role: "status",
3877
4024
  children: t("accountOpFailed")
3878
4025
  }) : null,
4026
+ state.pendingCredentialCleanup.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4027
+ className: "cc-rowError",
4028
+ role: "status",
4029
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("accountCleanupPending") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4030
+ type: "button",
4031
+ className: "cc-btn cc-btnSm",
4032
+ disabled: disabled || state.accountBusy,
4033
+ onClick: () => {
4034
+ for (const ref of state.pendingCredentialCleanup) actions.cleanup(ref);
4035
+ },
4036
+ children: t("accountCleanupRetry")
4037
+ })]
4038
+ }) : null,
3879
4039
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3880
4040
  className: "cc-accountList",
3881
4041
  children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountItem, {
@@ -4071,11 +4231,12 @@ window.__ModuleLoader__.load({
4071
4231
  const disabled = !state.writable;
4072
4232
  const savedVisible = useSavedFlash(state.savedCount);
4073
4233
  const updateVersion = usePluginUpdate();
4074
- const { createAccount, renameAccount, removeAccount, setAccountKey, clearAccountKey, setActiveAccount, setAccountModels, beginLogin, cancelLogin } = props;
4234
+ const { createAccount, renameAccount, removeAccount, retryCredentialCleanup, setAccountKey, clearAccountKey, setActiveAccount, setAccountModels, beginLogin, cancelLogin } = props;
4075
4235
  const actions = (0, react.useMemo)(() => ({
4076
4236
  create: createAccount,
4077
4237
  rename: renameAccount,
4078
4238
  remove: removeAccount,
4239
+ cleanup: retryCredentialCleanup,
4079
4240
  setKey: setAccountKey,
4080
4241
  clearKey: clearAccountKey,
4081
4242
  setActive: setActiveAccount,
@@ -4086,6 +4247,7 @@ window.__ModuleLoader__.load({
4086
4247
  createAccount,
4087
4248
  renameAccount,
4088
4249
  removeAccount,
4250
+ retryCredentialCleanup,
4089
4251
  setAccountKey,
4090
4252
  clearAccountKey,
4091
4253
  setActiveAccount,
@@ -6362,7 +6524,7 @@ window.__ModuleLoader__.load({
6362
6524
  .cc-usageHead{align-items:center;gap:8px;display:flex}
6363
6525
  .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:14px;font-weight:500;line-height:22px}
6364
6526
  .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}
6365
- .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:11px;font-weight:500;line-height:17px}
6527
+ .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}
6366
6528
  .cc-usagePlanStatus{flex:none;margin:0;white-space:nowrap;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
6367
6529
  .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
6368
6530
  /* Two fixed grids, not one auto-fit track: auto-fit wrapped the four activity
@@ -6494,8 +6656,8 @@ window.__ModuleLoader__.load({
6494
6656
  bar into what is left beside them. Mirrors the dashboard's window block. */
6495
6657
  .ccp-footRow{flex-direction:column;gap:4px;min-width:0;display:flex}
6496
6658
  .ccp-footHead{align-items:baseline;gap:8px;min-width:0;display:flex}
6497
- .ccp-footLabel{flex:1;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
6498
- .ccp-footAmount{flex:none;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums;white-space:nowrap}
6659
+ .ccp-footLabel{flex:1;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}
6660
+ .ccp-footAmount{flex:none;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:16px;font-variant-numeric:tabular-nums;white-space:nowrap}
6499
6661
  /* The card's markup must stay PHRASING content — it renders inside the shell's
6500
6662
  own button — so these bars are spans, which makes display:block load-bearing:
6501
6663
  an inline box ignores width outright, so the fill would collapse to 0x0 and
@@ -6503,7 +6665,7 @@ window.__ModuleLoader__.load({
6503
6665
  .ccp-footBar{display:block;background:var(--dsw-alias-bg-layer-2);border-radius:999px;height:5px;overflow:hidden}
6504
6666
  .ccp-footFill{display:block;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
6505
6667
  .ccp-footFillWarn{background:var(--dsw-alias-state-error-primary)}
6506
- .ccp-footPct{flex:none;width:34px;color:var(--dsw-alias-label-secondary);text-align:right;font-size:11px;line-height:16px;font-variant-numeric:tabular-nums}
6668
+ .ccp-footPct{flex:none;width:34px;color:var(--dsw-alias-label-secondary);text-align:right;font-size:12px;line-height:16px;font-variant-numeric:tabular-nums}
6507
6669
 
6508
6670
  /* The 56px rail: one icon button on the shell's own 36px rail cell, so the
6509
6671
  collapsed column keeps a single 18px glyph like its siblings. */
@@ -6523,7 +6685,7 @@ window.__ModuleLoader__.load({
6523
6685
  .ccp-title{margin:0;font-size:18px;font-weight:600;line-height:1.4}
6524
6686
  .ccp-subtitle{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
6525
6687
  .ccp-spacer{flex:1}
6526
- .ccp-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-variant-numeric:tabular-nums}
6688
+ .ccp-meta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;font-variant-numeric:tabular-nums}
6527
6689
  /* The dashboard's exit: the primitive's own size="sm" geometry is kept; only the
6528
6690
  glyph is enlarged and optically centred, so it reads as a dismiss control. */
6529
6691
  .ccp-close{min-width:28px;justify-content:center;padding-left:0;padding-right:0}
@@ -6535,16 +6697,16 @@ window.__ModuleLoader__.load({
6535
6697
  .ccp-noticeTitle{margin:0;font-size:13px;font-weight:600;line-height:1.5}
6536
6698
  .ccp-noticeError .ccp-noticeTitle{color:var(--dsw-alias-state-error-primary)}
6537
6699
  .ccp-noticeHint{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55}
6538
- .ccp-noticeDetail{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;word-break:break-word}
6700
+ .ccp-noticeDetail{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;word-break:break-word}
6539
6701
 
6540
6702
  .ccp-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:14px;padding:16px 18px;flex-direction:column;gap:16px;display:flex}
6541
6703
  .ccp-cardHead{align-items:center;gap:10px;display:flex;flex-wrap:wrap}
6542
6704
  .ccp-avatar{flex:none;width:28px;height:28px;color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-module-platform);border-radius:50%;justify-content:center;align-items:center;font-size:12px;font-weight:600;line-height:1;display:inline-flex}
6543
6705
  .ccp-cardIdentity{flex-direction:column;gap:1px;min-width:0;display:flex}
6544
6706
  .ccp-cardTitle{font-size:13px;font-weight:600;line-height:1.4}
6545
- .ccp-cardOwner{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px}
6707
+ .ccp-cardOwner{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:220px}
6546
6708
  .ccp-block{flex-direction:column;gap:8px;display:flex}
6547
- .ccp-blockTitle{margin:0;color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:600;line-height:1.5;text-transform:uppercase;letter-spacing:.04em}
6709
+ .ccp-blockTitle{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:600;line-height:1.5;text-transform:uppercase;letter-spacing:.04em}
6548
6710
  .ccp-planRow{align-items:center;gap:8px;display:flex;flex-wrap:wrap}
6549
6711
  .ccp-fieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
6550
6712
  .ccp-planName{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:1.5}
@@ -6552,9 +6714,9 @@ window.__ModuleLoader__.load({
6552
6714
  /* The monthly credits and the usage totals share one grid. */
6553
6715
  .ccp-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px}
6554
6716
  .ccp-tile{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:8px 10px;flex-direction:column;gap:2px;display:flex;min-width:0}
6555
- .ccp-tileLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
6717
+ .ccp-tileLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
6556
6718
  .ccp-tileValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4;font-variant-numeric:tabular-nums}
6557
- .ccp-tileSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6719
+ .ccp-tileSub{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6558
6720
 
6559
6721
  /* Quota bars: the monthly bar and the two windows. */
6560
6722
  .ccp-windows{flex-direction:column;gap:14px;display:flex}
@@ -6563,16 +6725,16 @@ window.__ModuleLoader__.load({
6563
6725
  .ccp-windowLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:1.5}
6564
6726
  .ccp-windowValue{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;font-variant-numeric:tabular-nums;white-space:nowrap}
6565
6727
  .ccp-windowPct{color:var(--dsw-alias-label-primary);min-width:38px;text-align:right;font-size:12px;font-weight:600;line-height:1.5;font-variant-numeric:tabular-nums}
6566
- .ccp-warnTag{white-space:nowrap;background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-secondary));border-radius:999px;padding:0 8px;font-size:11px;font-weight:600;line-height:17px}
6728
+ .ccp-warnTag{white-space:nowrap;background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-secondary));border-radius:999px;padding:0 8px;font-size:12px;font-weight:600;line-height:17px}
6567
6729
  .ccp-bar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:8px}
6568
6730
  .ccp-barFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
6569
6731
  .ccp-barFillWarn{background:var(--dsw-alias-state-error-primary)}
6570
- .ccp-windowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
6732
+ .ccp-windowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6571
6733
 
6572
- .ccp-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-brand-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;line-height:17px}
6734
+ .ccp-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-brand-primary);border-radius:999px;padding:1px 8px;font-size:12px;font-weight:600;line-height:17px}
6573
6735
  .ccp-badgeError{background:transparent;color:var(--dsw-alias-state-error-primary)}
6574
6736
  .ccp-badgeWarn{background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-secondary))}
6575
- .ccp-badgeMuted{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;max-width:220px;overflow:hidden;text-overflow:ellipsis}
6737
+ .ccp-badgeMuted{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:12px;font-weight:500;line-height:17px;max-width:220px;overflow:hidden;text-overflow:ellipsis}
6576
6738
 
6577
6739
  /* Account switch: plain buttons, like the settings page's usage carousel. */
6578
6740
  .ccp-tabs{flex-wrap:wrap;gap:6px;display:flex}
@@ -6626,7 +6788,7 @@ window.__ModuleLoader__.load({
6626
6788
  modelAllowance: "${amount}/月",
6627
6789
  modelAllowanceTitle: "该模型每月可用的额度(美元)。套餐内所有模型共用同一个月度额度池,这不是额外赠送的额度。",
6628
6790
  visibleModelsTitle: "模型白名单",
6629
- visibleModelsHint: "勾选要保留的模型,模型选择器就只列出这些;一个都不勾选时则显示全部模型。保存后,下次打开模型选择器生效。",
6791
+ visibleModelsHint: "勾选要保留的模型,模型选择器就只列出这些;一个都不勾选时则显示全部模型。这里显示网页与终端合并后的有效选择;保存后两端一致,套餐权限仍独立过滤。",
6630
6792
  visibleModelsPick: "选择要保留的模型…",
6631
6793
  visibleModelsCount: "已选 {count} 个模型",
6632
6794
  visibleModelsShowAll: "显示全部",
@@ -6635,8 +6797,11 @@ window.__ModuleLoader__.load({
6635
6797
  overridden: "已覆盖",
6636
6798
  reset: "重置",
6637
6799
  invalidNumber: "无效数字",
6638
- numberTooSmall: "不能小于 1(毫秒)",
6639
- numberTooLarge: "超出允许上限(2147483647 毫秒)",
6800
+ numberTooSmall: "重试次数不能小于 0",
6801
+ numberTooLarge: "重试次数不能大于 50",
6802
+ durationTooSmall: "不能小于 0.001 秒(1 毫秒)",
6803
+ durationTooLarge: "不能大于 3600 秒",
6804
+ durationInvalid: "请输入秒数,最多保留三位小数",
6640
6805
  readOnly: "当前配置为只读。",
6641
6806
  save: "保存",
6642
6807
  saving: "保存中",
@@ -6730,6 +6895,8 @@ window.__ModuleLoader__.load({
6730
6895
  accountNameN: "账户 {n}",
6731
6896
  accountKeyPlaceholder: "粘贴 API 密钥",
6732
6897
  accountOpFailed: "账户操作未能完成,请重试。",
6898
+ accountCleanupPending: "账户已移除,但有密钥尚未清理。可重试,不影响其他账户。",
6899
+ accountCleanupRetry: "重试清理密钥",
6733
6900
  accountModels: "专用模型",
6734
6901
  accountModelsHint: "选中的模型优先由此账户处理,此账户不可用时仍会自动切换。一个模型只属于一个账户,选中会把它从其他账户移过来。",
6735
6902
  accountModelOwner: "属于 {name}",
@@ -6782,7 +6949,7 @@ window.__ModuleLoader__.load({
6782
6949
  modelAllowance: "${amount}/mo",
6783
6950
  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.",
6784
6951
  visibleModelsTitle: "Model allowlist",
6785
- 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 the next time you open a model picker.",
6952
+ 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.",
6786
6953
  visibleModelsPick: "Select models to keep…",
6787
6954
  visibleModelsCount: "{count} model(s) selected",
6788
6955
  visibleModelsShowAll: "Show all",
@@ -6791,8 +6958,11 @@ window.__ModuleLoader__.load({
6791
6958
  overridden: "Overridden",
6792
6959
  reset: "Reset",
6793
6960
  invalidNumber: "Invalid number",
6794
- numberTooSmall: "Must be at least 1 (ms)",
6795
- numberTooLarge: "Above the allowed maximum (2147483647 ms)",
6961
+ numberTooSmall: "Retries must be at least 0",
6962
+ numberTooLarge: "Retries must not exceed 50",
6963
+ durationTooSmall: "Must be at least 0.001 seconds (1 ms)",
6964
+ durationTooLarge: "Must not exceed 3600 seconds",
6965
+ durationInvalid: "Enter seconds with at most three decimal places",
6796
6966
  readOnly: "Settings are read-only.",
6797
6967
  save: "Save",
6798
6968
  saving: "Saving",
@@ -6886,6 +7056,8 @@ window.__ModuleLoader__.load({
6886
7056
  accountNameN: "Account {n}",
6887
7057
  accountKeyPlaceholder: "Paste an API key",
6888
7058
  accountOpFailed: "The account change did not complete. Try again.",
7059
+ accountCleanupPending: "The account was removed, but credential cleanup is still pending. Retry without affecting other accounts.",
7060
+ accountCleanupRetry: "Retry credential cleanup",
6889
7061
  accountModels: "Dedicated models",
6890
7062
  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.",
6891
7063
  accountModelOwner: "on {name}",
@@ -7017,7 +7189,7 @@ window.__ModuleLoader__.load({
7017
7189
  }, "dsh-commandcode-provider: usage namespace");
7018
7190
  });
7019
7191
  }, (error) => {
7020
- usageMountError = error instanceof Error ? error.message : String(error);
7192
+ usageMountError = readableErrorText(error);
7021
7193
  });
7022
7194
  return () => {
7023
7195
  cancelled = true;
@@ -7130,7 +7302,11 @@ window.__ModuleLoader__.load({
7130
7302
  return ref;
7131
7303
  }),
7132
7304
  renameAccount: (ref, label) => controller.renameAccount(ref, label),
7133
- removeAccount: (ref) => controller.removeAccount(ref).then(refreshUsageOn),
7305
+ removeAccount: (ref) => controller.removeAccount(ref).then((ok) => {
7306
+ if (ok || !controller.state().accounts.some((account) => account.ref === ref)) usageController.refresh();
7307
+ return ok;
7308
+ }),
7309
+ retryCredentialCleanup: (ref) => controller.retryCredentialCleanup(ref),
7134
7310
  setAccountKey: (target, key) => controller.setAccountKey(target, key).then(refreshUsageOn),
7135
7311
  clearAccountKey: (target) => controller.clearAccountKey(target).then(refreshUsageOn),
7136
7312
  setActiveAccount: (id) => controller.setActiveAccount(id).then(refreshUsageOn),