dsh-life-pack 0.3.66 → 0.3.67

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/dist/client.js CHANGED
@@ -471,7 +471,7 @@ window.__ModuleLoader__.load({
471
471
  path: "/api/ilife-manager"
472
472
  };
473
473
  //#endregion
474
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/config.js
474
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/config.js
475
475
  const DEFAULT_PANEL_POLL_MS = 1e3;
476
476
  function assertPrefix(value, role) {
477
477
  if (typeof value !== "string" || value.length === 0) throw new Error("[dsh-plugin-update] " + role + " 非法:须为非空字符串(收到 " + JSON.stringify(value) + ")");
@@ -493,71 +493,7 @@ window.__ModuleLoader__.load({
493
493
  return buildPhoneNames(prefix).logAppend;
494
494
  }
495
495
  //#endregion
496
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/redaction.js
497
- const COPY_BUDGET_CHARS = 1500;
498
- const REDACTED_PATH = "<路径>";
499
- const REDACTED_SECRET = "<脱敏>";
500
- const ABSOLUTE_PATH_RE = /[A-Za-z]:\\[^\s"']*|\\\\[^\s"'()\[\];]+|(^|[\s"'(\[=,])\/\/[^\s"'()\[\];]+|(^|[\s"'(\[=:,])\/(?!\/)[^\s"'()\[\];]+/g;
501
- const URL_USERINFO_RE = /[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/]*@/;
502
- const NPM_TOKEN_RE = /\bnpm_[A-Za-z0-9_-]{8,}/g;
503
- const GITHUB_TOKEN_RE = /\b(?:gh[pousr]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{10,})/g;
504
- const SK_TOKEN_RE = /\bsk-[A-Za-z0-9_-]{6,}/g;
505
- const BEARER_TOKEN_RE = /(\bbearer\s+)[A-Za-z0-9._~+/-=]{6,}/gi;
506
- const CRED_PAIR_RE = /(\b(?:token|password|passwd|pwd|api[_-]?key|access[_-]?key|auth[_-]?token|secret|cookie)\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s'";,)\]]+)/gi;
507
- const EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
508
- function flattenWhitespace(text) {
509
- return String(text ?? "").replace(/\s+/g, " ").trim();
510
- }
511
- function applyPathRule(text) {
512
- ABSOLUTE_PATH_RE.lastIndex = 0;
513
- return text.replace(ABSOLUTE_PATH_RE, (_whole, g1, g2) => {
514
- return `${g1 ?? g2 ?? ""}${REDACTED_PATH}`;
515
- });
516
- }
517
- function applyTokenRule(text) {
518
- NPM_TOKEN_RE.lastIndex = 0;
519
- GITHUB_TOKEN_RE.lastIndex = 0;
520
- SK_TOKEN_RE.lastIndex = 0;
521
- BEARER_TOKEN_RE.lastIndex = 0;
522
- return text.replace(NPM_TOKEN_RE, REDACTED_SECRET).replace(GITHUB_TOKEN_RE, REDACTED_SECRET).replace(SK_TOKEN_RE, REDACTED_SECRET).replace(BEARER_TOKEN_RE, (_whole, prefix) => `${prefix}${REDACTED_SECRET}`);
523
- }
524
- function applyCredPairRule(text) {
525
- CRED_PAIR_RE.lastIndex = 0;
526
- return text.replace(CRED_PAIR_RE, (_whole, head) => `${head}${REDACTED_SECRET}`);
527
- }
528
- function applyEmailRule(text) {
529
- EMAIL_RE.lastIndex = 0;
530
- return text.replace(EMAIL_RE, REDACTED_SECRET);
531
- }
532
- function hasUserinfo(text) {
533
- URL_USERINFO_RE.lastIndex = 0;
534
- return URL_USERINFO_RE.test(text);
535
- }
536
- function truncateToWordBoundary(text, max) {
537
- const input = String(text ?? "");
538
- if (input.length <= max) return input;
539
- const slice = input.slice(0, max);
540
- const lastSpace = slice.lastIndexOf(" ");
541
- let cut = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
542
- const lastOpen = cut.lastIndexOf("<");
543
- if (lastOpen > cut.lastIndexOf(">")) cut = cut.slice(0, lastOpen).trimEnd();
544
- if (!cut) cut = slice;
545
- return `${cut}\u2026`;
546
- }
547
- function truncateForCopy(text) {
548
- return truncateToWordBoundary(text, COPY_BUDGET_CHARS);
549
- }
550
- function scrubWithoutBudget(text) {
551
- return applyEmailRule(applyCredPairRule(applyTokenRule(applyPathRule(text))));
552
- }
553
- function sanitizeForCopy(text) {
554
- const flat = flattenWhitespace(String(text ?? ""));
555
- if (!flat) return "";
556
- if (hasUserinfo(flat)) return "";
557
- return truncateForCopy(scrubWithoutBudget(flat));
558
- }
559
- //#endregion
560
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/service.js
496
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/version-judgment.js
561
497
  function updateError(code) {
562
498
  return Object.assign(new Error(code), { code });
563
499
  }
@@ -644,7 +580,7 @@ window.__ModuleLoader__.load({
644
580
  return 0;
645
581
  }
646
582
  //#endregion
647
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/lang.js
583
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/lang.js
648
584
  function normalizeLangTag(tag) {
649
585
  if (typeof tag !== "string") return "zh";
650
586
  const s = tag.trim().toLowerCase().replace(/_/g, "-");
@@ -786,7 +722,7 @@ window.__ModuleLoader__.load({
786
722
  };
787
723
  }
788
724
  //#endregion
789
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/bilingual.js
725
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/bilingual.js
790
726
  const BILINGUAL_STRINGS = {
791
727
  "entry.label.idle": {
792
728
  en: "Check for updates",
@@ -2088,6 +2024,41 @@ window.__ModuleLoader__.load({
2088
2024
  zh: "请重试一次;若持续,请提交诊断信息至插件作者。",
2089
2025
  draft: true
2090
2026
  },
2027
+ "batch.banner.age-gated": {
2028
+ en: "{n} plugin(s) are blocked by the 24-hour age gate.",
2029
+ zh: "{n} 个插件的最新版被 24 小时年龄门挡住。",
2030
+ draft: true
2031
+ },
2032
+ "batch.banner.gated-wait": {
2033
+ en: "{n} plugin(s) are blocked by the 24-hour age gate and nothing is installable right now; installable in about {time}.",
2034
+ zh: "{n} 个插件的最新版被 24 小时年龄门挡住,现在还没有可装的版本;约 {time} 后可装。",
2035
+ draft: true
2036
+ },
2037
+ "batch.banner.compliant-done-title": {
2038
+ en: "Actual install result for these {n} plugin(s):",
2039
+ zh: "这 {n} 个插件的实际安装结果:",
2040
+ draft: true
2041
+ },
2042
+ "batch.banner.compliant-done": {
2043
+ en: "{title}: installed {version}, not the latest {target} (the latest did not go through).",
2044
+ zh: "{title}:已装 {version},不是最新 {target}(最新版没装成)。",
2045
+ draft: true
2046
+ },
2047
+ "batch.banner.compliant-done-gated": {
2048
+ en: "{title}: installed {version}; the latest {target} is still blocked by the 24-hour age gate (installable in about {time}).",
2049
+ zh: "{title}:已装 {version};最新 {target} 仍被 24 小时年龄门挡着,约 {time} 后可装。",
2050
+ draft: true
2051
+ },
2052
+ "batch.banner.compliant-confirm": {
2053
+ en: "Install these compliant builds: {list}?",
2054
+ zh: "确认安装以下合规版:{list}?",
2055
+ draft: true
2056
+ },
2057
+ "batch.action.install-compliant-confirm": {
2058
+ en: "Confirm and install these {n}",
2059
+ zh: "确认装这 {n} 个",
2060
+ draft: true
2061
+ },
2091
2062
  "batch.banner.failed-title": {
2092
2063
  en: "{n} failed to install.",
2093
2064
  zh: "{n} 个插件安装失败。",
@@ -2253,6 +2224,11 @@ window.__ModuleLoader__.load({
2253
2224
  zh: "失败 {code}:{detail}",
2254
2225
  draft: true
2255
2226
  },
2227
+ "batch.row.compliant-done": {
2228
+ en: "Installed {version}, not the latest {target} (the latest did not go through)",
2229
+ zh: "已装 {version},不是最新 {target}(最新版这次没装上)",
2230
+ draft: true
2231
+ },
2256
2232
  "batch.diag.source-job": {
2257
2233
  en: "(Source: background job record)",
2258
2234
  zh: "(来源:后台任务执行记录)",
@@ -2288,6 +2264,11 @@ window.__ModuleLoader__.load({
2288
2264
  zh: "此行缺少取消排队所需的信息,暂无法取消;请待下次刷新后重试。",
2289
2265
  draft: true
2290
2266
  },
2267
+ "batch.toast.compliant-installed": {
2268
+ en: "Requested {a} compliant build(s) (not the latest: it is still age-gated); {b} did not go through.",
2269
+ zh: "已请求安装 {a} 个合规版(不是最新版:最新版仍被年龄门挡着);{b} 个没装成。",
2270
+ draft: true
2271
+ },
2291
2272
  "batch.toast.cancel-ok": {
2292
2273
  en: "Queue cancelled: this plugin will not wait.",
2293
2274
  zh: "已取消排队:此插件将不再等待。",
@@ -2518,7 +2499,7 @@ window.__ModuleLoader__.load({
2518
2499
  }
2519
2500
  const BILINGUAL_CSS = [".dsh-upd-bi{display:inline;overflow-wrap:anywhere}", ".dsh-upd-bi [lang]{overflow-wrap:anywhere}"].join("\n");
2520
2501
  //#endregion
2521
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/changelog.js
2502
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/changelog.js
2522
2503
  const CHANGELOG_MAX_CHARS = 65536;
2523
2504
  const CHANGELOG_ALL_CATEGORIES = [
2524
2505
  "Added",
@@ -3626,7 +3607,7 @@ ${note}${blocks.join("\n")}
3626
3607
  }
3627
3608
  }
3628
3609
  //#endregion
3629
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/log-events.js
3610
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/log-events.js
3630
3611
  const LOG_EVENT_CALL = "host.call";
3631
3612
  const LOG_EVENT_CALL_FAIL = "host.call.fail";
3632
3613
  const LOG_EVENT_INSTALL_EXEC = "update.install.exec";
@@ -3877,7 +3858,7 @@ ${note}${blocks.join("\n")}
3877
3858
  };
3878
3859
  }
3879
3860
  //#endregion
3880
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/codes.js
3861
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/codes.js
3881
3862
  const BLOCKED_REASONS = [
3882
3863
  "unknown-profile",
3883
3864
  "channel-mismatch",
@@ -3912,8 +3893,25 @@ ${note}${blocks.join("\n")}
3912
3893
  return typeof code === "string" && isOneOf(CHECK_SIDE_FAILURE_CODES, code.trim());
3913
3894
  }
3914
3895
  //#endregion
3915
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/install-cause.js
3896
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/install-cause.js
3916
3897
  const CAUSE_KINDS = ["minimum-release-age"];
3898
+ const CAUSE_SIGNATURES = [{
3899
+ kind: "minimum-release-age",
3900
+ codes: ["install-failed"],
3901
+ stages: ["exec"],
3902
+ token: "[ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION]",
3903
+ pattern: /(?<spec>@?[a-z0-9][a-z0-9._/-]*@[0-9][A-Za-z0-9._-]*)[\s\S]*?published[\s\S]{0,120}?(?<published>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?Z?)[\s\S]*?cutoff[\s\S]{0,120}?(?<cutoff>[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?Z?)/
3904
+ }];
3905
+ function toMs(value) {
3906
+ try {
3907
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
3908
+ if (typeof value === "string" && value.trim()) {
3909
+ const ms = Date.parse(value.trim());
3910
+ if (Number.isFinite(ms) && ms > 0) return ms;
3911
+ }
3912
+ } catch {}
3913
+ return null;
3914
+ }
3917
3915
  function causePackageNameOf(spec) {
3918
3916
  try {
3919
3917
  const s = String(spec ?? "").trim();
@@ -3953,118 +3951,133 @@ ${note}${blocks.join("\n")}
3953
3951
  return null;
3954
3952
  }
3955
3953
  }
3954
+ const WINDOW_PATTERN = /less than (?<windowMinutes>[0-9]{1,7}) minutes? ago/;
3955
+ const WINDOW_MINUTES_MAX = 5256e3;
3956
+ function parseWindowMinutes(detail) {
3957
+ try {
3958
+ const matched = WINDOW_PATTERN.exec(detail);
3959
+ if (!matched?.groups) return null;
3960
+ const n = Number(matched.groups["windowMinutes"]);
3961
+ if (!Number.isInteger(n) || n <= 0 || n > WINDOW_MINUTES_MAX) return null;
3962
+ return n;
3963
+ } catch {
3964
+ return null;
3965
+ }
3966
+ }
3967
+ function interpretInstallCause(input) {
3968
+ try {
3969
+ const errorCode = String(input?.errorCode ?? "");
3970
+ const detail = input?.detail;
3971
+ if (!errorCode || typeof detail !== "string" || !detail.trim()) return null;
3972
+ const nowMs = toMs(input?.nowMs) ?? Date.now();
3973
+ for (const sig of CAUSE_SIGNATURES) {
3974
+ const codes = sig.codes;
3975
+ const stages = sig.stages;
3976
+ if (!codes.includes(errorCode)) continue;
3977
+ if (stages.length > 0 && typeof input?.stage === "string" && input.stage) {
3978
+ if (!stages.includes(input.stage)) continue;
3979
+ }
3980
+ if (!detail.includes(sig.token)) continue;
3981
+ const matched = sig.pattern.exec(detail);
3982
+ if (!matched || !matched.groups) continue;
3983
+ const spec = String(matched.groups["spec"] ?? "").trim();
3984
+ if (!spec) continue;
3985
+ const publishedAtMs = matched.groups["published"] ? toMs(matched.groups["published"]) : null;
3986
+ const cutoffMs = matched.groups["cutoff"] ? toMs(matched.groups["cutoff"]) : null;
3987
+ const windowMinutes = parseWindowMinutes(detail);
3988
+ let unlockAtMs;
3989
+ if (publishedAtMs !== null && windowMinutes !== null) {
3990
+ const unlock = publishedAtMs + windowMinutes * 6e4;
3991
+ if (unlock > nowMs) unlockAtMs = unlock;
3992
+ } else if (publishedAtMs !== null && cutoffMs !== null) {
3993
+ const windowMs = nowMs - cutoffMs;
3994
+ const unlock = publishedAtMs + windowMs;
3995
+ if (windowMs > 0 && unlock > nowMs) unlockAtMs = unlock;
3996
+ }
3997
+ const at = spec.lastIndexOf("@");
3998
+ return {
3999
+ kind: sig.kind,
4000
+ spec,
4001
+ packageName: causePackageNameOf(spec),
4002
+ packageVersion: at > 0 ? spec.slice(at + 1) || null : null,
4003
+ publishedAtMs,
4004
+ cutoffMs,
4005
+ windowMinutes,
4006
+ ...unlockAtMs !== void 0 ? { unlockAtMs } : {},
4007
+ blame: blameCausePackage(spec, input?.targetPackageName)
4008
+ };
4009
+ }
4010
+ return null;
4011
+ } catch {
4012
+ return null;
4013
+ }
4014
+ }
3956
4015
  //#endregion
3957
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/panel.js
3958
- function normalizePanelTheme(value) {
3959
- return value === "archive" ? "archive" : "default";
4016
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/redaction.js
4017
+ const COPY_BUDGET_CHARS = 1500;
4018
+ const REDACTED_PATH = "<路径>";
4019
+ const REDACTED_SECRET = "<脱敏>";
4020
+ const ABSOLUTE_PATH_RE = /[A-Za-z]:\\[^\s"']*|\\\\[^\s"'()\[\];]+|(^|[\s"'(\[=,])\/\/[^\s"'()\[\];]+|(^|[\s"'(\[=:,])\/(?!\/)[^\s"'()\[\];]+/g;
4021
+ const URL_USERINFO_RE = /[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/]*@/;
4022
+ const NPM_TOKEN_RE = /\bnpm_[A-Za-z0-9_-]{8,}/g;
4023
+ const GITHUB_TOKEN_RE = /\b(?:gh[pousr]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{10,})/g;
4024
+ const SK_TOKEN_RE = /\bsk-[A-Za-z0-9_-]{6,}/g;
4025
+ const BEARER_TOKEN_RE = /(\bbearer\s+)[A-Za-z0-9._~+/-=]{6,}/gi;
4026
+ const CRED_PAIR_RE = /(\b(?:token|password|passwd|pwd|api[_-]?key|access[_-]?key|auth[_-]?token|secret|cookie)\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s'";,)\]]+)/gi;
4027
+ const EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
4028
+ function flattenWhitespace(text) {
4029
+ return String(text ?? "").replace(/\s+/g, " ").trim();
3960
4030
  }
3961
- const THEME_TOKEN_VARS = {
3962
- text: "--dsh-update-text",
3963
- textMuted: "--dsh-update-text-muted",
3964
- bg: "--dsh-update-bg",
3965
- bgSoft: "--dsh-update-bg-soft",
3966
- border: "--dsh-update-border",
3967
- borderStrong: "--dsh-update-border-strong",
3968
- buttonBg: "--dsh-update-button-bg",
3969
- primary: "--dsh-update-primary",
3970
- primaryDeep: "--dsh-update-primary-deep",
3971
- focus: "--dsh-update-focus",
3972
- okBg: "--dsh-update-ok-bg",
3973
- okBorder: "--dsh-update-ok-border",
3974
- okText: "--dsh-update-ok-text",
3975
- warnBg: "--dsh-update-warn-bg",
3976
- warnBorder: "--dsh-update-warn-border",
3977
- warnText: "--dsh-update-warn-text",
3978
- badBg: "--dsh-update-bad-bg",
3979
- badBorder: "--dsh-update-bad-border",
3980
- badText: "--dsh-update-bad-text",
3981
- busyBg: "--dsh-update-busy-bg",
3982
- busyBorder: "--dsh-update-busy-border",
3983
- busyText: "--dsh-update-busy-text",
3984
- newText: "--dsh-update-new-text",
3985
- newOkText: "--dsh-update-new-ok-text",
3986
- fontSans: "--dsh-update-font-sans",
3987
- fontSerif: "--dsh-update-font-serif",
3988
- fontMono: "--dsh-update-font-mono",
3989
- shadow: "--dsh-update-shadow",
3990
- radiusPanel: "--dsh-update-radius-panel",
3991
- radiusButton: "--dsh-update-radius-button",
3992
- radiusBadge: "--dsh-update-radius-badge",
3993
- entryFontSize: "--dsh-update-entry-font-size",
3994
- entryPadding: "--dsh-update-entry-padding",
3995
- entryBorderRadius: "--dsh-update-entry-border-radius",
3996
- entryScale: "--dsh-update-entry-scale",
3997
- panelFontSize: "--dsh-update-panel-font-size",
3998
- panelChapterFontSize: "--dsh-update-panel-chapter-font-size",
3999
- panelBannerTitleFontSize: "--dsh-update-panel-banner-title-font-size",
4000
- panelLogFontSize: "--dsh-update-panel-log-font-size",
4001
- panelSmallFontSize: "--dsh-update-panel-small-font-size"
4002
- };
4003
- const THEME_TOKEN_COLOR_KEYS = /* @__PURE__ */ new Set([
4004
- "text",
4005
- "textMuted",
4006
- "bg",
4007
- "bgSoft",
4008
- "border",
4009
- "borderStrong",
4010
- "buttonBg",
4011
- "primary",
4012
- "primaryDeep",
4013
- "focus",
4014
- "okBg",
4015
- "okBorder",
4016
- "okText",
4017
- "warnBg",
4018
- "warnBorder",
4019
- "warnText",
4020
- "badBg",
4021
- "badBorder",
4022
- "badText",
4023
- "busyBg",
4024
- "busyBorder",
4025
- "busyText",
4026
- "newText",
4027
- "newOkText"
4028
- ]);
4029
- function themeTokensError(raw) {
4030
- return /* @__PURE__ */ new Error(`[dsh-plugin-update] \u4E3B\u9898\u53C2\u6570 themeTokens \u975E\u6CD5\uFF1A\u53EA\u6536\u5DF2\u77E5 token \u952E\uFF08\u989C\u8272\u7528 hex \u6216\u82F1\u6587\u540D\uFF0C\u5B57\u4F53/\u5706\u89D2/\u9634\u5F71/\u5C3A\u5BF8\u4E3A\u5B89\u5168 CSS \u503C\uFF0CentryScale \u4E3A\u5927\u4E8E 0 \u7684\u6709\u9650\u6570\uFF09\uFF08\u6536\u5230 ${JSON.stringify(raw ?? null)})`);
4031
+ function applyPathRule(text) {
4032
+ ABSOLUTE_PATH_RE.lastIndex = 0;
4033
+ return text.replace(ABSOLUTE_PATH_RE, (_whole, g1, g2) => {
4034
+ return `${g1 ?? g2 ?? ""}${REDACTED_PATH}`;
4035
+ });
4031
4036
  }
4032
- function isSafeThemeCssValue(value) {
4033
- const v = value.trim();
4034
- if (!v || v.length > 200) return false;
4035
- if (/[;"'<>\`{}!&]/.test(v)) return false;
4036
- if (/url\s*\(/i.test(v)) return false;
4037
- if (/expression\s*\(/i.test(v)) return false;
4038
- if (/javascript\s*:/i.test(v)) return false;
4039
- return true;
4037
+ function applyTokenRule(text) {
4038
+ NPM_TOKEN_RE.lastIndex = 0;
4039
+ GITHUB_TOKEN_RE.lastIndex = 0;
4040
+ SK_TOKEN_RE.lastIndex = 0;
4041
+ BEARER_TOKEN_RE.lastIndex = 0;
4042
+ return text.replace(NPM_TOKEN_RE, REDACTED_SECRET).replace(GITHUB_TOKEN_RE, REDACTED_SECRET).replace(SK_TOKEN_RE, REDACTED_SECRET).replace(BEARER_TOKEN_RE, (_whole, prefix) => `${prefix}${REDACTED_SECRET}`);
4040
4043
  }
4041
- function isThemeColorValue(value) {
4042
- const v = value.trim();
4043
- if (!v || v.length > 100) return false;
4044
- return /^(?:#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|[a-zA-Z]+)$/.test(v);
4044
+ function applyCredPairRule(text) {
4045
+ CRED_PAIR_RE.lastIndex = 0;
4046
+ return text.replace(CRED_PAIR_RE, (_whole, head) => `${head}${REDACTED_SECRET}`);
4045
4047
  }
4046
- function themeTokensStyleFor(tokens) {
4047
- if (tokens === void 0 || tokens === null) return "";
4048
- if (typeof tokens !== "object" || Array.isArray(tokens)) throw themeTokensError(tokens);
4049
- for (const key of Object.keys(tokens)) if (!Object.prototype.hasOwnProperty.call(THEME_TOKEN_VARS, key)) throw themeTokensError(tokens);
4050
- const parts = [];
4051
- for (const key of Object.keys(THEME_TOKEN_VARS)) {
4052
- const value = tokens[key];
4053
- if (value === void 0) continue;
4054
- if (key === "entryScale") {
4055
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw themeTokensError(tokens);
4056
- parts.push(THEME_TOKEN_VARS[key] + ":" + String(value));
4057
- continue;
4058
- }
4059
- if (typeof value !== "string") throw themeTokensError(tokens);
4060
- const text = value.trim();
4061
- if (THEME_TOKEN_COLOR_KEYS.has(key)) {
4062
- if (!isThemeColorValue(text)) throw themeTokensError(tokens);
4063
- } else if (!isSafeThemeCssValue(text)) throw themeTokensError(tokens);
4064
- parts.push(THEME_TOKEN_VARS[key] + ":" + text);
4065
- }
4066
- return parts.join(";");
4048
+ function applyEmailRule(text) {
4049
+ EMAIL_RE.lastIndex = 0;
4050
+ return text.replace(EMAIL_RE, REDACTED_SECRET);
4051
+ }
4052
+ function hasUserinfo(text) {
4053
+ URL_USERINFO_RE.lastIndex = 0;
4054
+ return URL_USERINFO_RE.test(text);
4067
4055
  }
4056
+ function truncateToWordBoundary(text, max) {
4057
+ const input = String(text ?? "");
4058
+ if (input.length <= max) return input;
4059
+ const slice = input.slice(0, max);
4060
+ const lastSpace = slice.lastIndexOf(" ");
4061
+ let cut = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
4062
+ const lastOpen = cut.lastIndexOf("<");
4063
+ if (lastOpen > cut.lastIndexOf(">")) cut = cut.slice(0, lastOpen).trimEnd();
4064
+ if (!cut) cut = slice;
4065
+ return `${cut}\u2026`;
4066
+ }
4067
+ function truncateForCopy(text) {
4068
+ return truncateToWordBoundary(text, COPY_BUDGET_CHARS);
4069
+ }
4070
+ function scrubWithoutBudget(text) {
4071
+ return applyEmailRule(applyCredPairRule(applyTokenRule(applyPathRule(text))));
4072
+ }
4073
+ function sanitizeForCopy(text) {
4074
+ const flat = flattenWhitespace(String(text ?? ""));
4075
+ if (!flat) return "";
4076
+ if (hasUserinfo(flat)) return "";
4077
+ return truncateForCopy(scrubWithoutBudget(flat));
4078
+ }
4079
+ //#endregion
4080
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/panel-copy.js
4068
4081
  const BLOCKED_KEYS = {
4069
4082
  "unknown-profile": {
4070
4083
  title: "panel.blocked.unknown-profile.title",
@@ -4374,6 +4387,118 @@ ${note}${blocks.join("\n")}
4374
4387
  ` ${copyText("panel.diag.copy.block.remedy", l, { remedy })}`
4375
4388
  ].join("\n");
4376
4389
  }
4390
+ //#endregion
4391
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/panel.js
4392
+ function normalizePanelTheme(value) {
4393
+ return value === "archive" ? "archive" : "default";
4394
+ }
4395
+ const THEME_TOKEN_VARS = {
4396
+ text: "--dsh-update-text",
4397
+ textMuted: "--dsh-update-text-muted",
4398
+ bg: "--dsh-update-bg",
4399
+ bgSoft: "--dsh-update-bg-soft",
4400
+ border: "--dsh-update-border",
4401
+ borderStrong: "--dsh-update-border-strong",
4402
+ buttonBg: "--dsh-update-button-bg",
4403
+ primary: "--dsh-update-primary",
4404
+ primaryDeep: "--dsh-update-primary-deep",
4405
+ focus: "--dsh-update-focus",
4406
+ okBg: "--dsh-update-ok-bg",
4407
+ okBorder: "--dsh-update-ok-border",
4408
+ okText: "--dsh-update-ok-text",
4409
+ warnBg: "--dsh-update-warn-bg",
4410
+ warnBorder: "--dsh-update-warn-border",
4411
+ warnText: "--dsh-update-warn-text",
4412
+ badBg: "--dsh-update-bad-bg",
4413
+ badBorder: "--dsh-update-bad-border",
4414
+ badText: "--dsh-update-bad-text",
4415
+ busyBg: "--dsh-update-busy-bg",
4416
+ busyBorder: "--dsh-update-busy-border",
4417
+ busyText: "--dsh-update-busy-text",
4418
+ newText: "--dsh-update-new-text",
4419
+ newOkText: "--dsh-update-new-ok-text",
4420
+ fontSans: "--dsh-update-font-sans",
4421
+ fontSerif: "--dsh-update-font-serif",
4422
+ fontMono: "--dsh-update-font-mono",
4423
+ shadow: "--dsh-update-shadow",
4424
+ radiusPanel: "--dsh-update-radius-panel",
4425
+ radiusButton: "--dsh-update-radius-button",
4426
+ radiusBadge: "--dsh-update-radius-badge",
4427
+ entryFontSize: "--dsh-update-entry-font-size",
4428
+ entryPadding: "--dsh-update-entry-padding",
4429
+ entryBorderRadius: "--dsh-update-entry-border-radius",
4430
+ entryScale: "--dsh-update-entry-scale",
4431
+ panelFontSize: "--dsh-update-panel-font-size",
4432
+ panelChapterFontSize: "--dsh-update-panel-chapter-font-size",
4433
+ panelBannerTitleFontSize: "--dsh-update-panel-banner-title-font-size",
4434
+ panelLogFontSize: "--dsh-update-panel-log-font-size",
4435
+ panelSmallFontSize: "--dsh-update-panel-small-font-size"
4436
+ };
4437
+ const THEME_TOKEN_COLOR_KEYS = /* @__PURE__ */ new Set([
4438
+ "text",
4439
+ "textMuted",
4440
+ "bg",
4441
+ "bgSoft",
4442
+ "border",
4443
+ "borderStrong",
4444
+ "buttonBg",
4445
+ "primary",
4446
+ "primaryDeep",
4447
+ "focus",
4448
+ "okBg",
4449
+ "okBorder",
4450
+ "okText",
4451
+ "warnBg",
4452
+ "warnBorder",
4453
+ "warnText",
4454
+ "badBg",
4455
+ "badBorder",
4456
+ "badText",
4457
+ "busyBg",
4458
+ "busyBorder",
4459
+ "busyText",
4460
+ "newText",
4461
+ "newOkText"
4462
+ ]);
4463
+ function themeTokensError(raw) {
4464
+ return /* @__PURE__ */ new Error(`[dsh-plugin-update] \u4E3B\u9898\u53C2\u6570 themeTokens \u975E\u6CD5\uFF1A\u53EA\u6536\u5DF2\u77E5 token \u952E\uFF08\u989C\u8272\u7528 hex \u6216\u82F1\u6587\u540D\uFF0C\u5B57\u4F53/\u5706\u89D2/\u9634\u5F71/\u5C3A\u5BF8\u4E3A\u5B89\u5168 CSS \u503C\uFF0CentryScale \u4E3A\u5927\u4E8E 0 \u7684\u6709\u9650\u6570\uFF09\uFF08\u6536\u5230 ${JSON.stringify(raw ?? null)})`);
4465
+ }
4466
+ function isSafeThemeCssValue(value) {
4467
+ const v = value.trim();
4468
+ if (!v || v.length > 200) return false;
4469
+ if (/[;"'<>\`{}!&]/.test(v)) return false;
4470
+ if (/url\s*\(/i.test(v)) return false;
4471
+ if (/expression\s*\(/i.test(v)) return false;
4472
+ if (/javascript\s*:/i.test(v)) return false;
4473
+ return true;
4474
+ }
4475
+ function isThemeColorValue(value) {
4476
+ const v = value.trim();
4477
+ if (!v || v.length > 100) return false;
4478
+ return /^(?:#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|[a-zA-Z]+)$/.test(v);
4479
+ }
4480
+ function themeTokensStyleFor(tokens) {
4481
+ if (tokens === void 0 || tokens === null) return "";
4482
+ if (typeof tokens !== "object" || Array.isArray(tokens)) throw themeTokensError(tokens);
4483
+ for (const key of Object.keys(tokens)) if (!Object.prototype.hasOwnProperty.call(THEME_TOKEN_VARS, key)) throw themeTokensError(tokens);
4484
+ const parts = [];
4485
+ for (const key of Object.keys(THEME_TOKEN_VARS)) {
4486
+ const value = tokens[key];
4487
+ if (value === void 0) continue;
4488
+ if (key === "entryScale") {
4489
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw themeTokensError(tokens);
4490
+ parts.push(THEME_TOKEN_VARS[key] + ":" + String(value));
4491
+ continue;
4492
+ }
4493
+ if (typeof value !== "string") throw themeTokensError(tokens);
4494
+ const text = value.trim();
4495
+ if (THEME_TOKEN_COLOR_KEYS.has(key)) {
4496
+ if (!isThemeColorValue(text)) throw themeTokensError(tokens);
4497
+ } else if (!isSafeThemeCssValue(text)) throw themeTokensError(tokens);
4498
+ parts.push(THEME_TOKEN_VARS[key] + ":" + text);
4499
+ }
4500
+ return parts.join(";");
4501
+ }
4377
4502
  const PANEL_SKIPPED_MAX = 50;
4378
4503
  function panelSkipKey(pluginId) {
4379
4504
  return `dsh-upd-skipped/${String(pluginId)}`;
@@ -5067,6 +5192,63 @@ ${note}${blocks.join("\n")}
5067
5192
  const at = spec.lastIndexOf("@");
5068
5193
  return (at > 0 ? spec.slice(at + 1).trim() : "") || null;
5069
5194
  }
5195
+ function fallbackCauseFromDetail(detail, errorCode, pluginId) {
5196
+ try {
5197
+ const facts = interpretInstallCause({
5198
+ errorCode: typeof errorCode === "string" && errorCode ? errorCode : "install-failed",
5199
+ detail,
5200
+ targetPackageName: typeof pluginId === "string" ? pluginId : void 0
5201
+ });
5202
+ if (!facts || facts.kind !== "minimum-release-age" || facts.blame !== "target") return null;
5203
+ const targetVersion = typeof facts.packageVersion === "string" && facts.packageVersion.trim() ? facts.packageVersion.trim() : null;
5204
+ if (!targetVersion) return null;
5205
+ let windowMs = null;
5206
+ let windowSource = "unknown";
5207
+ if (typeof facts.windowMinutes === "number" && Number.isFinite(facts.windowMinutes) && facts.windowMinutes > 0) {
5208
+ windowMs = facts.windowMinutes * 6e4;
5209
+ windowSource = "message";
5210
+ } else if (typeof facts.cutoffMs === "number" && typeof facts.publishedAtMs === "number" && facts.cutoffMs > facts.publishedAtMs) windowMs = facts.cutoffMs - facts.publishedAtMs;
5211
+ return {
5212
+ kind: facts.kind,
5213
+ packageName: facts.packageName,
5214
+ targetVersion,
5215
+ windowMs,
5216
+ windowSource,
5217
+ publishedAtMs: typeof facts.publishedAtMs === "number" ? facts.publishedAtMs : null
5218
+ };
5219
+ } catch {
5220
+ return null;
5221
+ }
5222
+ }
5223
+ function fallbackCauseFromDiag(diag, detail, errorCode, preferredTargetVersion) {
5224
+ try {
5225
+ const d = diag && typeof diag === "object" && !Array.isArray(diag) ? diag : null;
5226
+ if (!d) return null;
5227
+ const dKind = typeof d["causeKind"] === "string" ? d["causeKind"].trim() : "";
5228
+ const dSpec = typeof d["causePackage"] === "string" ? d["causePackage"].trim() : "";
5229
+ const dTarget = typeof d["targetPackageName"] === "string" ? d["targetPackageName"].trim() : "";
5230
+ if (!dKind || !CAUSE_KINDS.includes(dKind) || !dSpec || !dTarget) return null;
5231
+ if (blameCausePackage(dSpec, dTarget) !== "target") return null;
5232
+ const timed = fallbackCauseFromDetail(detail, errorCode, dTarget);
5233
+ const preferred = typeof preferredTargetVersion === "string" ? preferredTargetVersion.trim() : "";
5234
+ const at = dSpec.lastIndexOf("@");
5235
+ const specVer = at > 0 ? dSpec.slice(at + 1).trim() : "";
5236
+ const targetVersion = preferred || timed?.targetVersion || specVer;
5237
+ if (!targetVersion) return null;
5238
+ return {
5239
+ kind: dKind,
5240
+ spec: dSpec,
5241
+ target: dTarget,
5242
+ packageName: at > 0 && dSpec.slice(0, at).trim() ? dSpec.slice(0, at).trim() : dTarget,
5243
+ targetVersion,
5244
+ windowMs: timed?.windowMs ?? null,
5245
+ windowSource: timed?.windowSource ?? "unknown",
5246
+ publishedAtMs: timed?.publishedAtMs ?? null
5247
+ };
5248
+ } catch {
5249
+ return null;
5250
+ }
5251
+ }
5070
5252
  function formatLatchTime(atMs, lang) {
5071
5253
  if (typeof atMs !== "number" || !Number.isFinite(atMs) || atMs <= 0) return null;
5072
5254
  const raw = typeof lang === "string" ? lang.trim().replace(/_/g, "-") : "";
@@ -5296,7 +5478,7 @@ ${UPDATE_PANEL_ARCHIVE_CSS}` : UPDATE_PANEL_CSS}</style>
5296
5478
  ${body}`;
5297
5479
  }
5298
5480
  //#endregion
5299
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/entry.js
5481
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/entry.js
5300
5482
  const UPDATE_ENTRY_CSS = [
5301
5483
  ".dsh-upd-entry{display:inline-flex;align-items:center;gap:8px;font:13px/1.6 var(--dsh-update-font-sans,system-ui,\"Microsoft YaHei\",sans-serif);font-size:var(--dsh-update-entry-font-size,13px);color:var(--dsh-update-text,#1f2937)}",
5302
5484
  ".dsh-upd-entry-btn{font:inherit;border:1px solid var(--dsh-update-border,#d1d5db);border-radius:var(--dsh-update-entry-border-radius,6px);",
@@ -5410,7 +5592,7 @@ ${body}`;
5410
5592
  }
5411
5593
  }
5412
5594
  //#endregion
5413
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/panel-batch.js
5595
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/panel-batch.js
5414
5596
  const BATCH_PANEL_POLL = {
5415
5597
  defaultMs: 1500,
5416
5598
  minMs: 250
@@ -5427,7 +5609,7 @@ ${body}`;
5427
5609
  prefsSave: p + ".batchPrefsSave"
5428
5610
  };
5429
5611
  }
5430
- function batchLedgerCounts(rows, inventory, session) {
5612
+ function batchLedgerCounts(rows, inventory, session, compliantDone) {
5431
5613
  const counts = {
5432
5614
  updatable: 0,
5433
5615
  installing: 0,
@@ -5439,9 +5621,15 @@ ${body}`;
5439
5621
  };
5440
5622
  for (const row of rows) {
5441
5623
  const phase = asBatchPhase(row.phase);
5442
- if (isQueueBusyRow(row)) counts.updatable += 1;
5443
- else if (phase === "failed") counts.failed += 1;
5444
- else if (phase === "installing") counts.installing += 1;
5624
+ const doneRestart = phase === "failed" ? compliantDone?.get(row.key) : void 0;
5625
+ if (doneRestart !== void 0) {
5626
+ if (doneRestart) counts.restart += 1;
5627
+ else counts.settled += 1;
5628
+ } else if (isQueueBusyRow(row)) counts.updatable += 1;
5629
+ else if (phase === "failed") {
5630
+ if (row.error === "pending-restart") counts.restart += 1;
5631
+ else counts.failed += 1;
5632
+ } else if (phase === "installing") counts.installing += 1;
5445
5633
  else if (phase === "ready") counts.updatable += 1;
5446
5634
  else if (phase === "pending") counts[isInUnfinishedRound(session, row.key) ? "pending" : ledgerKnowledgeBucket(knowledgeEntryOf(inventory, row.key))] += 1;
5447
5635
  else if (phase === "checking") counts.pending += 1;
@@ -5663,7 +5851,14 @@ ${body}`;
5663
5851
  const expandedKey = typeof input.expandedKey === "string" && input.expandedKey ? input.expandedKey : null;
5664
5852
  const lastError = typeof input.lastError === "string" && input.lastError ? input.lastError : null;
5665
5853
  const loaded = input.loaded === void 0 ? rows.length > 0 : input.loaded === true;
5666
- const counts = batchLedgerCounts(rows, input.inventory, input.session);
5854
+ const compliantFacts = /* @__PURE__ */ new Map();
5855
+ for (const row of rows) {
5856
+ const fact = compliantInstalledOf(row);
5857
+ if (fact) compliantFacts.set(row.key, fact);
5858
+ }
5859
+ const compliantDone = /* @__PURE__ */ new Map();
5860
+ for (const [key, fact] of compliantFacts) compliantDone.set(key, fact.needsRestart);
5861
+ const counts = batchLedgerCounts(rows, input.inventory, input.session, compliantDone);
5667
5862
  const installing = counts.installing > 0;
5668
5863
  const macroBusy = input.inFlight === true || installing;
5669
5864
  const total = rows.length;
@@ -5686,8 +5881,32 @@ ${body}`;
5686
5881
  lang,
5687
5882
  knowledge: null,
5688
5883
  inRound: false,
5689
- worldLatest: null
5884
+ worldLatest: null,
5885
+ fallbackCandidate: null,
5886
+ fallbackBusy: false,
5887
+ fallbackWaiting: false
5690
5888
  };
5889
+ const fallbackOffers = input.fallbackOffers && typeof input.fallbackOffers === "object" ? input.fallbackOffers : {};
5890
+ const fallbackBusyKey = typeof input.fallbackBusyKey === "string" && input.fallbackBusyKey ? input.fallbackBusyKey : null;
5891
+ const compliantInput = isObject$2(input.compliant) ? input.compliant : null;
5892
+ const compliantWaitInput = compliantInput && isObject$2(compliantInput["wait"]) ? compliantInput["wait"] : null;
5893
+ const waitKeys = [];
5894
+ if (compliantWaitInput && Array.isArray(compliantWaitInput["keys"])) {
5895
+ for (const key of compliantWaitInput["keys"]) if (typeof key === "string" && key) waitKeys.push(key);
5896
+ }
5897
+ const waitUnlockAtMs = compliantWaitInput && typeof compliantWaitInput["unlockAtMs"] === "number" ? compliantWaitInput["unlockAtMs"] : null;
5898
+ const compliantUnlocksInput = compliantInput && isObject$2(compliantInput["unlocks"]) ? compliantInput["unlocks"] : null;
5899
+ const compliant = compliantInput && typeof compliantInput["gated"] === "number" ? {
5900
+ gated: compliantInput["gated"],
5901
+ confirmList: typeof compliantInput["confirmList"] === "string" && compliantInput["confirmList"] ? compliantInput["confirmList"] : null,
5902
+ confirmCount: typeof compliantInput["confirmCount"] === "number" ? compliantInput["confirmCount"] : 0,
5903
+ busy: compliantInput["busy"] === true,
5904
+ wait: waitKeys.length > 0 ? {
5905
+ keys: waitKeys,
5906
+ unlockAtMs: waitUnlockAtMs
5907
+ } : null,
5908
+ unlocks: compliantUnlocksInput ? compliantUnlocksInput : null
5909
+ } : null;
5691
5910
  const parts = [];
5692
5911
  parts.push("<div class=\"dsh-upd-batch-head\">");
5693
5912
  parts.push("<div class=\"dsh-upd-batch-title\">" + escapeHtml$2(batchText("batch.header.title", lang)) + " · <i>" + escapeHtml$2(batchText("batch.seal.ledger", lang)) + "</i></div>");
@@ -5699,7 +5918,7 @@ ${body}`;
5699
5918
  parts.push("</div>");
5700
5919
  const summary = loaded ? batchSummaryText(counts, rows, total, installing, lastError !== null, lang) : batchText("batch.banner.loading", lang);
5701
5920
  parts.push("<div class=\"dsh-upd-batch-sum\" role=\"status\" aria-live=\"polite\">" + escapeHtml$2(summary) + "</div>");
5702
- parts.push(bannersHTML(rows, titles, lastError, lang));
5921
+ parts.push(bannersHTML(rows, titles, lastError, lang, compliant, compliantFacts));
5703
5922
  const session = isObject$2(input.session) ? input.session : null;
5704
5923
  const unfinished = unfinishedCount(rows, session ?? void 0);
5705
5924
  const showControls = total > 0 && hasUnfinishedRows(rows, session ?? void 0);
@@ -5712,6 +5931,10 @@ ${body}`;
5712
5931
  const expanded = row.key === expandedKey;
5713
5932
  const inRound = isInUnfinishedRound(input.session, row.key);
5714
5933
  const knowledge = !inRound ? knowledgeEntryOf(input.inventory, row.key) : null;
5934
+ const offerRaw = fallbackOffers[row.key];
5935
+ const offerCand = isObject$2(offerRaw) && typeof offerRaw["candidate"] === "string" && offerRaw["candidate"].trim() ? offerRaw["candidate"].trim() : null;
5936
+ const offerTarget = isObject$2(offerRaw) && typeof offerRaw["targetVersion"] === "string" ? offerRaw["targetVersion"] : null;
5937
+ const latest = latestVersionOf(row);
5715
5938
  parts.push(rowSetHTML(row, {
5716
5939
  ...ctx,
5717
5940
  expanded,
@@ -5721,7 +5944,10 @@ ${body}`;
5721
5944
  lang,
5722
5945
  knowledge,
5723
5946
  inRound,
5724
- worldLatest: worldUpdateVersion(row, knowledge, sessionUpdatedAtOf(input.session, row.key))
5947
+ worldLatest: worldUpdateVersion(row, knowledge, sessionUpdatedAtOf(input.session, row.key)),
5948
+ fallbackCandidate: offerCand && offerTarget === latest ? offerCand : null,
5949
+ fallbackBusy: fallbackBusyKey === row.key,
5950
+ fallbackWaiting: waitKeys.includes(row.key)
5725
5951
  }));
5726
5952
  }
5727
5953
  parts.push("</div>");
@@ -5752,12 +5978,18 @@ ${body}`;
5752
5978
  if (target !== null) {
5753
5979
  const rawSkipped = skippedVersions[target.key];
5754
5980
  const skippedForDialog = typeof rawSkipped === "string" && rawSkipped ? rawSkipped : null;
5981
+ const offerRaw = fallbackOffers[target.key];
5982
+ const offerCand = isObject$2(offerRaw) && typeof offerRaw["candidate"] === "string" && offerRaw["candidate"].trim() ? offerRaw["candidate"].trim() : null;
5983
+ const offerTarget = isObject$2(offerRaw) && typeof offerRaw["targetVersion"] === "string" ? offerRaw["targetVersion"] : null;
5755
5984
  detailDialog = detailDialogHTML(target, {
5756
5985
  ...ctx,
5757
5986
  expanded: true,
5758
5987
  skipped: skippedForDialog,
5759
5988
  notice: target.key === detailNoticeKey ? notice : null,
5760
- changelogMarkdown: typeof changelogs[target.key] === "string" ? changelogs[target.key] : null
5989
+ changelogMarkdown: typeof changelogs[target.key] === "string" ? changelogs[target.key] : null,
5990
+ fallbackCandidate: offerCand && offerTarget === latestVersionOf(target) ? offerCand : null,
5991
+ fallbackBusy: fallbackBusyKey === target.key,
5992
+ fallbackWaiting: waitKeys.includes(target.key)
5761
5993
  });
5762
5994
  }
5763
5995
  }
@@ -5804,8 +6036,33 @@ ${body}`;
5804
6036
  tone: "ink"
5805
6037
  };
5806
6038
  }
5807
- function bannersHTML(rows, titles, lastError, lang) {
6039
+ function bannersHTML(rows, titles, lastError, lang, compliant = null, facts) {
5808
6040
  const l = langOf(lang);
6041
+ const unlocks = compliant?.unlocks ?? null;
6042
+ const done = new Map(facts ?? []);
6043
+ if (!facts) for (const row of rows) {
6044
+ const fact = compliantInstalledOf(row);
6045
+ if (fact) done.set(row.key, fact);
6046
+ }
6047
+ const doneLineOf = (row, fact) => {
6048
+ const raw = unlocks && typeof unlocks[row.key] === "number" ? unlocks[row.key] : null;
6049
+ const time = raw !== null ? formatUnlockAt(raw, l) : null;
6050
+ const vars = {
6051
+ title: titleOf(row, titles),
6052
+ version: fact.version,
6053
+ target: fact.target
6054
+ };
6055
+ return time ? escapeHtml$2(batchText("batch.banner.compliant-done-gated", l, {
6056
+ ...vars,
6057
+ time
6058
+ })) : escapeHtml$2(batchText("batch.banner.compliant-done", l, vars));
6059
+ };
6060
+ const gatedCandidates = rows.filter((row) => batchFallbackCauseOf(row) !== null && singleFallbackPhoneOf(row) !== null && !done.has(row.key));
6061
+ const gated = typeof compliant?.gated === "number" ? compliant.gated : gatedCandidates.length;
6062
+ const confirmList = typeof compliant?.confirmList === "string" && compliant.confirmList ? compliant.confirmList : null;
6063
+ const confirmCount = typeof compliant?.confirmCount === "number" ? compliant.confirmCount : 0;
6064
+ const compliantBusy = compliant?.busy === true;
6065
+ const waitKeys = compliant?.wait && Array.isArray(compliant.wait.keys) ? compliant.wait.keys : [];
5809
6066
  const parts = [];
5810
6067
  if (lastError) {
5811
6068
  const copy = failureCopy(lastError, l);
@@ -5817,7 +6074,8 @@ ${body}`;
5817
6074
  })) + "</strong></div></div>");
5818
6075
  if (act) parts.push("<div class=\"dsh-upd-banner-sub\">" + escapeHtml$2(act) + "</div>");
5819
6076
  }
5820
- const failed = rows.filter((row) => asBatchPhase(row.phase) === "failed" && !isQueueBusyRow(row));
6077
+ const pendingRestartRow = (row) => asBatchPhase(row.phase) === "failed" && row.error === "pending-restart";
6078
+ const failed = rows.filter((row) => asBatchPhase(row.phase) === "failed" && !isQueueBusyRow(row) && !pendingRestartRow(row) && !done.has(row.key));
5821
6079
  if (failed.length > 0) {
5822
6080
  const lines = failed.map((row) => {
5823
6081
  const code = row.error || "install-failed";
@@ -5828,7 +6086,22 @@ ${body}`;
5828
6086
  parts.push("<div class=\"dsh-upd-banner\" data-kind=\"failed\" data-mini=\"阻\" role=\"status\"><div><span class=\"dsh-upd-sealmini\" aria-hidden=\"true\">阻</span><strong>" + escapeHtml$2(batchText("batch.banner.failed-title", l, { n: String(failed.length) })) + "</strong></div><div class=\"dsh-upd-blist\">" + lines + "</div></div>");
5829
6087
  if (failedAct) parts.push("<div class=\"dsh-upd-banner-sub\">" + escapeHtml$2(failedAct) + "</div>");
5830
6088
  }
5831
- const restart = rows.filter((row) => row.restartRequired === true);
6089
+ if (confirmList) parts.push("<div class=\"dsh-upd-banner\" data-kind=\"update\" role=\"status\"><div><strong>" + escapeHtml$2(batchText("batch.banner.compliant-confirm", l, { list: confirmList })) + "</strong></div><div class=\"dsh-upd-browline\"><span class=\"dsh-upd-blist\">" + escapeHtml$2(confirmList) + "</span><button type=\"button\" data-act=\"batch-fallback-confirm\" data-primary=\"1\"" + (compliantBusy ? " disabled" : "") + ">" + escapeHtml$2(batchText("batch.action.install-compliant-confirm", l, { n: String(confirmCount) })) + "</button></div></div>");
6090
+ if (waitKeys.length > 0) {
6091
+ const rawUnlock = compliant?.wait ? compliant.wait.unlockAtMs : null;
6092
+ const unlockAtMs = typeof rawUnlock === "number" ? rawUnlock : null;
6093
+ const time = unlockAtMs !== null ? formatUnlockAt(unlockAtMs, l) : null;
6094
+ const waitTitles = rows.filter((row) => waitKeys.includes(row.key)).map((row) => titleOf(row, titles)).join("、");
6095
+ parts.push("<div class=\"dsh-upd-banner\" data-kind=\"update\" role=\"status\"><div><strong>" + escapeHtml$2(time ? batchText("batch.banner.gated-wait", l, {
6096
+ n: String(waitKeys.length),
6097
+ time
6098
+ }) : batchText("batch.banner.age-gated", l, { n: String(waitKeys.length) })) + "</strong></div><div class=\"dsh-upd-browline\"><span class=\"dsh-upd-blist\">" + escapeHtml$2(waitTitles) + "</span></div></div>");
6099
+ } else if (gated > 0 && confirmList === null) parts.push("<div class=\"dsh-upd-banner\" data-kind=\"update\" role=\"status\"><div><strong>" + escapeHtml$2(batchText("batch.banner.age-gated", l, { n: String(gated) })) + "</strong></div><div class=\"dsh-upd-browline\"><span class=\"dsh-upd-blist\">" + escapeHtml$2(gatedCandidates.map((row) => titleOf(row, titles)).join("、")) + "</span><button type=\"button\" data-act=\"batch-fallback-scan\"" + (compliantBusy ? " disabled" : "") + " title=\"" + escapeHtml$2(copyText("panel.action.install-fallback-title", l)) + "\">" + escapeHtml$2(copyText("panel.action.install-fallback", l)) + "</button></div></div>");
6100
+ if (done.size > 0) {
6101
+ const lines = rows.filter((row) => done.has(row.key)).map((row) => "<div>" + doneLineOf(row, done.get(row.key)) + "</div>").join("");
6102
+ parts.push("<div class=\"dsh-upd-banner\" data-kind=\"update\" role=\"status\"><div><strong>" + escapeHtml$2(batchText("batch.banner.compliant-done-title", l, { n: String(done.size) })) + "</strong></div><div class=\"dsh-upd-blist\">" + lines + "</div></div>");
6103
+ }
6104
+ const restart = rows.filter((row) => row.restartRequired === true || pendingRestartRow(row) || done.get(row.key)?.needsRestart === true);
5832
6105
  if (restart.length > 0) {
5833
6106
  const restartAct = batchText("batch.banner.restart-action", l);
5834
6107
  parts.push("<div class=\"dsh-upd-banner\" data-kind=\"restart\" role=\"status\"><div><strong>" + escapeHtml$2(batchText("batch.banner.restart-title", l, { n: String(restart.length) })) + "</strong></div><div class=\"dsh-upd-browline\"><span class=\"dsh-upd-blist\">" + restart.map((row) => escapeHtml$2(titleOf(row, titles))).join("、") + "</span><button type=\"button\" data-act=\"restart\">" + escapeHtml$2(batchText("batch.banner.restart-button", l)) + "</button></div></div>");
@@ -5836,14 +6109,29 @@ ${body}`;
5836
6109
  }
5837
6110
  return parts.join("");
5838
6111
  }
6112
+ function rowFallbackButtonHTML(row, ctx) {
6113
+ try {
6114
+ if (ctx.fallbackWaiting) return "";
6115
+ const keyAttr = escapeHtml$2(row.key);
6116
+ const busy = ctx.fallbackBusy || ctx.inFlight ? " disabled aria-busy=\"true\"" : "";
6117
+ const off = ctx.inFlight && !ctx.fallbackBusy ? " disabled" : "";
6118
+ if (typeof ctx.fallbackCandidate === "string" && ctx.fallbackCandidate) return "<button type=\"button\" data-act=\"row-fallback-confirm\" data-key=\"" + keyAttr + "\" data-primary=\"1\" title=\"" + escapeHtml$2(copyText("panel.action.install-fallback-confirm-title", ctx.lang)) + "\"" + busy + ">" + escapeHtml$2(copyText("panel.action.install-fallback-confirm", ctx.lang, { version: ctx.fallbackCandidate })) + "</button>";
6119
+ if (singleFallbackPhoneOf(row) === null) return "";
6120
+ if (!batchFallbackCauseOf(row)) return "";
6121
+ return "<button type=\"button\" data-act=\"row-fallback\" data-key=\"" + keyAttr + "\" data-primary=\"1\" title=\"" + escapeHtml$2(copyText("panel.action.install-fallback-title", ctx.lang)) + "\"" + (ctx.fallbackBusy ? busy : off) + ">" + escapeHtml$2(copyText("panel.action.install-fallback", ctx.lang)) + "</button>";
6122
+ } catch {
6123
+ return "";
6124
+ }
6125
+ }
5839
6126
  function rowSetHTML(row, ctx) {
5840
6127
  const phase = asBatchPhase(row.phase);
5841
6128
  const keyAttr = escapeHtml$2(row.key);
5842
6129
  const skipped = ctx.skipped;
5843
6130
  const queued = isQueuedRow(row);
5844
6131
  const position = queued ? queuePositionOf(row) : null;
6132
+ const rowDone = phase === "failed" && !queued ? compliantInstalledOf(row) : null;
5845
6133
  const versionHTML = batchRowVersionHTML(row, ctx.knowledge, ctx.lang);
5846
- const rowStatusText = queued ? batchQueuedStatus(position, ctx.lang) : ctx.worldLatest !== null ? batchText("batch.row.update", ctx.lang, { version: ctx.worldLatest }) : ctx.inRound ? batchRowKnowledgeText(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang) : batchRowPendingStatus(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang);
6134
+ const rowStatusText = rowDone !== null ? rowDone.needsRestart ? batchText("batch.row.done-restart", ctx.lang) : batchText("batch.row.done", ctx.lang) : queued ? batchQueuedStatus(position, ctx.lang) : ctx.worldLatest !== null ? batchText("batch.row.update", ctx.lang, { version: ctx.worldLatest }) : ctx.inRound ? batchRowKnowledgeText(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang) : batchRowPendingStatus(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang);
5847
6135
  const actions = [];
5848
6136
  const off = ctx.inFlight ? " disabled" : "";
5849
6137
  if (phase === "installing") actions.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\" disabled>" + escapeHtml$2(batchText("batch.row-action.installing", ctx.lang)) + "</button>");
@@ -5851,10 +6139,19 @@ ${body}`;
5851
6139
  if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) actions.push("<button type=\"button\" data-act=\"row-cancel-queue\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(batchText("batch.row-action.cancel-queue", ctx.lang)) + "</button>");
5852
6140
  } else if (phase === "ready" && skipped === null) actions.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\" data-primary=\"1\"" + off + ">" + escapeHtml$2(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.install-row", ctx.lang)) + "</button>");
5853
6141
  else if (phase === "ready" && skipped !== null) actions.push("<button type=\"button\" data-act=\"row-resume-skip\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(batchText("batch.row-action.unskip", ctx.lang, { version: skipped })) + "</button>");
5854
- else if (phase === "failed") actions.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.retry", ctx.lang)) + "</button>");
6142
+ else if (phase === "failed") {
6143
+ actions.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.retry", ctx.lang)) + "</button>");
6144
+ if (!queued && rowDone === null) actions.push(rowFallbackButtonHTML(row, ctx));
6145
+ }
5855
6146
  if (row.restartRequired === true) actions.push("<button type=\"button\" data-act=\"restart\" data-key=\"" + keyAttr + "\">" + escapeHtml$2(batchText("batch.row-action.restart", ctx.lang)) + "</button>");
5856
6147
  actions.push("<button type=\"button\" data-act=\"toggle-details\" data-key=\"" + keyAttr + "\">" + escapeHtml$2(ctx.expanded ? batchText("batch.row-action.hide-detail", ctx.lang) : batchText("batch.row-action.show-detail", ctx.lang)) + "</button>");
5857
- const failLine = phase === "failed" && !queued ? (() => {
6148
+ const failLine = phase === "failed" && !queued ? rowDone !== null ? (() => {
6149
+ const vars = {
6150
+ version: rowDone.version,
6151
+ target: rowDone.target
6152
+ };
6153
+ return "<span class=\"dsh-upd-bfail dsh-upd-bdone\">" + escapeHtml$2(batchText("batch.row.compliant-done", ctx.lang, vars)) + "</span>";
6154
+ })() : (() => {
5858
6155
  const failCode = row.error || "install-failed";
5859
6156
  const failDetail = isCheckSideFailureCode(jobFailureOf(row)?.code ?? (typeof row.error === "string" && row.error ? row.error : null)) ? batchText("batch.row.failed", ctx.lang) : failureCopy(failCode, ctx.lang)?.zh ?? failureCopy("unknown", ctx.lang)?.zh ?? "";
5860
6157
  const escaped = escapeHtml$2(batchText("batch.row.error-label", ctx.lang, {
@@ -5864,7 +6161,7 @@ ${body}`;
5864
6161
  const codeEsc = escapeHtml$2(failCode);
5865
6162
  return "<span class=\"dsh-upd-bfail\">" + (escaped.includes(codeEsc) ? escaped.replace(codeEsc, "<code>" + codeEsc + "</code>") : escaped) + "</span>";
5866
6163
  })() : "";
5867
- const main = "<button type=\"button\" class=\"dsh-upd-brow-main\" data-act=\"toggle-details\" data-key=\"" + keyAttr + "\" aria-expanded=\"" + (ctx.expanded ? "true" : "false") + "\"><span class=\"dsh-upd-updot\" data-tone=\"" + dotToneOf(row, phase, skipped, queued, ctx.worldLatest) + "\"></span><span class=\"dsh-upd-bname\">" + escapeHtml$2(titleOf(row, ctx.titles)) + "</span>" + versionHTML + "<span class=\"dsh-upd-bstat\">" + escapeHtml$2(rowStatusText) + (ctx.inRound && !queued ? "(" + escapeHtml$2(batchText("batch.row.unfinished-tag", ctx.lang)) + ")" : "") + "</span>" + failLine + "</button>";
6164
+ const main = "<button type=\"button\" class=\"dsh-upd-brow-main\" data-act=\"toggle-details\" data-key=\"" + keyAttr + "\" aria-expanded=\"" + (ctx.expanded ? "true" : "false") + "\"><span class=\"dsh-upd-updot\" data-tone=\"" + dotToneOf(row, phase, skipped, queued, ctx.worldLatest, rowDone === null ? null : rowDone.needsRestart) + "\"></span><span class=\"dsh-upd-bname\">" + escapeHtml$2(titleOf(row, ctx.titles)) + "</span>" + versionHTML + "<span class=\"dsh-upd-bstat\">" + escapeHtml$2(rowStatusText) + (ctx.inRound && !queued ? "(" + escapeHtml$2(batchText("batch.row.unfinished-tag", ctx.lang)) + ")" : "") + "</span>" + failLine + "</button>";
5868
6165
  return "<div class=\"dsh-upd-brow-set\" data-key=\"" + keyAttr + "\"><div class=\"dsh-upd-brow\" data-phase=\"" + escapeHtml$2(phase) + "\" data-key=\"" + keyAttr + "\"" + (queued ? " data-queued=\"1\"" : "") + ">" + main + "<span class=\"dsh-upd-brow-actions\">" + actions.join("") + "</span></div></div>";
5869
6166
  }
5870
6167
  function detailActionsHTML(row, ctx) {
@@ -5877,8 +6174,13 @@ ${body}`;
5877
6174
  if (phase === "installing") buttons.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\" disabled>" + escapeHtml$2(batchText("batch.row-action.installing", ctx.lang)) + "</button>");
5878
6175
  else if (queued) {
5879
6176
  if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) buttons.push("<button type=\"button\" data-act=\"row-cancel-queue\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(batchText("batch.row-action.cancel-queue", ctx.lang)) + "</button>");
5880
- } else if (phase === "failed") buttons.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.retry", ctx.lang)) + "</button>");
5881
- else if (phase === "ready" && ctx.skipped === null) buttons.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\" data-primary=\"1\"" + off + ">" + escapeHtml$2(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : version !== null ? batchText("batch.row-action.install-version", ctx.lang, { version }) : batchText("batch.row-action.install-generic", ctx.lang)) + "</button>");
6177
+ } else if (phase === "failed") {
6178
+ buttons.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.retry", ctx.lang)) + "</button>");
6179
+ if (!queued) {
6180
+ const fallback = rowFallbackButtonHTML(row, ctx);
6181
+ if (fallback) buttons.push(fallback);
6182
+ }
6183
+ } else if (phase === "ready" && ctx.skipped === null) buttons.push("<button type=\"button\" data-act=\"row-install\" data-key=\"" + keyAttr + "\" data-primary=\"1\"" + off + ">" + escapeHtml$2(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : version !== null ? batchText("batch.row-action.install-version", ctx.lang, { version }) : batchText("batch.row-action.install-generic", ctx.lang)) + "</button>");
5882
6184
  if (phase !== "installing" && singleCheckPhoneOf(row) !== null) buttons.push("<button type=\"button\" data-act=\"row-check\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(batchText("batch.action.check", ctx.lang)) + "</button>");
5883
6185
  if (!queued && phase === "ready" && ctx.skipped === null && version !== null) buttons.push("<button type=\"button\" data-act=\"row-skip\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(batchText("batch.row-action.skip", ctx.lang)) + "</button>");
5884
6186
  if (ctx.skipped !== null) buttons.push("<button type=\"button\" data-act=\"row-resume-skip\" data-key=\"" + keyAttr + "\"" + off + ">" + escapeHtml$2(batchText("batch.row-action.unskip", ctx.lang, { version: ctx.skipped })) + "</button>");
@@ -5920,7 +6222,9 @@ ${body}`;
5920
6222
  if (at < 0) return withBar + footer;
5921
6223
  return withBar.slice(0, at) + footer + withBar.slice(at);
5922
6224
  }
5923
- function dotToneOf(row, phase, skipped, queued, worldLatest = null) {
6225
+ function dotToneOf(row, phase, skipped, queued, worldLatest = null, compliantRestart = null) {
6226
+ if (compliantRestart === true) return "warn";
6227
+ if (compliantRestart === false) return "ok";
5924
6228
  if (phase === "failed" && !queued) return "bad";
5925
6229
  if (phase === "installing" || phase === "checking") return "busy";
5926
6230
  if (queued) return "warn";
@@ -5960,6 +6264,8 @@ ${body}`;
5960
6264
  ".dsh-upd-bver-new-ok{color:var(--dsh-update-new-ok-text,#047857);font-weight:700}",
5961
6265
  ".dsh-upd-bstat{font-size:var(--dsh-update-panel-small-font-size,12.5px);opacity:.9;min-width:0;overflow-wrap:anywhere}",
5962
6266
  ".dsh-upd-bfail{grid-column:1/-1;display:block;font-size:var(--dsh-update-panel-small-font-size,12.5px);color:var(--dsh-update-bad-border,#dc2626)}",
6267
+ "/* #156 finishing fact: ledger phase failed but the newest installable build did get installed — green, not red. */",
6268
+ ".dsh-upd-bdone{color:var(--dsh-update-ok-border,#059669)}",
5963
6269
  ".dsh-upd-batch{--dsh-update-new-text:#dc2626}",
5964
6270
  ".dsh-upd-batch{--dsh-update-new-ok-text:#047857}",
5965
6271
  "@media (prefers-color-scheme: dark){.dsh-upd-batch{--dsh-update-new-text:#f87171}}",
@@ -6072,6 +6378,78 @@ ${body}`;
6072
6378
  const install = phones["updateInstall"];
6073
6379
  return typeof install === "string" && install ? install : null;
6074
6380
  }
6381
+ function singleFallbackPhoneOf(row) {
6382
+ const phones = isObject$2(row.phoneNames) ? row.phoneNames : null;
6383
+ if (!phones) return null;
6384
+ const name = phones["updateCheckFallback"];
6385
+ return typeof name === "string" && name ? name : null;
6386
+ }
6387
+ function singleInstallPhoneOf(row) {
6388
+ const phones = isObject$2(row.phoneNames) ? row.phoneNames : null;
6389
+ if (!phones) return null;
6390
+ const name = phones["updateInstall"];
6391
+ return typeof name === "string" && name ? name : null;
6392
+ }
6393
+ function fallbackOutcomeOf(reply) {
6394
+ if (!isObject$2(reply) || reply["ok"] === false) return {
6395
+ kind: "",
6396
+ candidate: "",
6397
+ checkId: "",
6398
+ unlockAtMs: null
6399
+ };
6400
+ const snap = isObject$2(reply["snapshot"]) ? reply["snapshot"] : null;
6401
+ const verdict = snap && isObject$2(snap["verdict"]) ? snap["verdict"] : null;
6402
+ const kind = verdict && typeof verdict["kind"] === "string" ? verdict["kind"] : "";
6403
+ const version = verdict && typeof verdict["version"] === "string" ? verdict["version"].trim() : "";
6404
+ const snapCandidate = snap && typeof snap["candidate"] === "string" ? snap["candidate"].trim() : "";
6405
+ const rec = isObject$2(reply["receipt"]) ? reply["receipt"] : null;
6406
+ const checkId = rec && typeof rec["checkId"] === "string" ? rec["checkId"] : "";
6407
+ const unlockAtMs = verdict && typeof verdict["unlockAtMs"] === "number" ? verdict["unlockAtMs"] : null;
6408
+ const candidate = snapCandidate || (kind === "ok-now" ? version : "");
6409
+ const usable = (kind === "downgrade-available" || kind === "ok-now") && candidate !== "" && checkId !== "";
6410
+ return {
6411
+ kind,
6412
+ candidate: usable ? candidate : "",
6413
+ checkId: usable ? checkId : "",
6414
+ unlockAtMs
6415
+ };
6416
+ }
6417
+ function batchFallbackCauseOf(row) {
6418
+ try {
6419
+ if (asBatchPhase(row.phase) !== "failed") return null;
6420
+ const fail = jobFailureOf(row);
6421
+ if (!fail) return null;
6422
+ const rowDiag = row.diag;
6423
+ const cause = fallbackCauseFromDiag(rowDiag, fail.detail, fail.code, latestVersionOf(row)) ?? fallbackCauseFromDetail(fail.detail, fail.code, pluginIdOf(row));
6424
+ if (!cause) return null;
6425
+ if (cause.targetVersion !== latestVersionOf(row)) return null;
6426
+ return cause;
6427
+ } catch {
6428
+ return null;
6429
+ }
6430
+ }
6431
+ function compliantInstalledOf(row) {
6432
+ try {
6433
+ if (!isObject$2(row.snapshot)) return null;
6434
+ const snapshot = row.snapshot;
6435
+ if (!isObject$2(snapshot["job"])) return null;
6436
+ const job = snapshot["job"];
6437
+ const state = typeof job["state"] === "string" ? job["state"] : "";
6438
+ if (state !== "restart-required" && state !== "completed") return null;
6439
+ const version = typeof job["targetVersion"] === "string" ? job["targetVersion"].trim() : "";
6440
+ if (!version) return null;
6441
+ if ((typeof snapshot["installedVersion"] === "string" ? snapshot["installedVersion"].trim() : "") !== version) return null;
6442
+ const target = latestVersionOf(row);
6443
+ if (!target || target === version) return null;
6444
+ return {
6445
+ version,
6446
+ target,
6447
+ needsRestart: state === "restart-required"
6448
+ };
6449
+ } catch {
6450
+ return null;
6451
+ }
6452
+ }
6075
6453
  function asBatchPhase(value) {
6076
6454
  return typeof value === "string" && BATCH_PHASES.includes(value) ? value : "pending";
6077
6455
  }
@@ -6264,9 +6642,10 @@ ${body}`;
6264
6642
  const titles = options.titles && typeof options.titles === "object" ? options.titles : {};
6265
6643
  const onRestartRequested = options.onRestartRequested;
6266
6644
  const onCloseRequested = typeof options.onCloseRequested === "function" ? options.onCloseRequested : null;
6267
- const copyText = options.copyText ?? defaultCopyText$1;
6645
+ const copyText$1 = options.copyText ?? defaultCopyText$1;
6268
6646
  const skipStores = /* @__PURE__ */ new Map();
6269
6647
  const autoChangelogEnabled = options.autoChangelog !== false;
6648
+ const autoPlanEnabled = options.autoCompliant !== false;
6270
6649
  const rowChangelog = /* @__PURE__ */ new Map();
6271
6650
  const rowInflight = /* @__PURE__ */ new Set();
6272
6651
  const rowFailedAt = /* @__PURE__ */ new Map();
@@ -6294,6 +6673,9 @@ ${body}`;
6294
6673
  let lastCheckedAt = 0;
6295
6674
  let autoCheckDone = false;
6296
6675
  let autoResumeDone = false;
6676
+ const rowFallbackOffers = /* @__PURE__ */ new Map();
6677
+ let fallbackBusyKey = null;
6678
+ let batchCompliantKeys = [];
6297
6679
  function changelogView() {
6298
6680
  const out = {};
6299
6681
  for (const row of rows) {
@@ -6435,6 +6817,13 @@ ${body}`;
6435
6817
  let lastHTML = "";
6436
6818
  function render() {
6437
6819
  if (!mounted) return;
6820
+ const offers = {};
6821
+ for (const [k, v] of rowFallbackOffers) offers[k] = {
6822
+ candidate: v.candidate,
6823
+ targetVersion: v.targetVersion
6824
+ };
6825
+ const confirmKeys = batchCompliantKeys.filter((k) => rowFallbackOffers.has(k));
6826
+ const wait = waitingOf();
6438
6827
  const nextHTML = renderBatchPanelHTML({
6439
6828
  rows,
6440
6829
  theme,
@@ -6453,13 +6842,163 @@ ${body}`;
6453
6842
  session: lastSession,
6454
6843
  inventory: lastInventory,
6455
6844
  prefs: lastPrefs,
6456
- lang: currentLang()
6845
+ lang: currentLang(),
6846
+ fallbackOffers: offers,
6847
+ fallbackBusyKey,
6848
+ compliant: {
6849
+ gated: rows.filter((row) => needsAutoResolve(row)).length,
6850
+ confirmList: confirmKeys.length > 0 ? compliantListText(confirmKeys) : null,
6851
+ confirmCount: confirmKeys.length,
6852
+ busy: inFlight,
6853
+ wait: wait.keys.length > 0 ? wait : null,
6854
+ unlocks: unlockTimesOf()
6855
+ }
6457
6856
  });
6458
6857
  if (nextHTML !== lastHTML) {
6459
6858
  lastHTML = nextHTML;
6460
6859
  setStableHTML(container, nextHTML);
6461
6860
  }
6462
6861
  }
6862
+ function pruneFallbackOffers() {
6863
+ try {
6864
+ for (const [k, v] of [...rowFallbackOffers]) {
6865
+ const row = rowOfKey(k);
6866
+ if (!row || asBatchPhase(row.phase) !== "failed" || v.targetVersion !== latestVersionOf(row)) rowFallbackOffers.delete(k);
6867
+ }
6868
+ if (batchCompliantKeys.length > 0) batchCompliantKeys = batchCompliantKeys.filter((k) => rowFallbackOffers.has(k));
6869
+ } catch {}
6870
+ }
6871
+ function compliantListText(keys) {
6872
+ const sep = currentLang() === "en" ? ", " : "、";
6873
+ return keys.map((k) => {
6874
+ const row = rowOfKey(k);
6875
+ const offer = rowFallbackOffers.get(k);
6876
+ return row && offer ? titleOf(row, titles) + " " + offer.candidate : "";
6877
+ }).filter((text) => text !== "").join(sep);
6878
+ }
6879
+ const gatedOutcomes = /* @__PURE__ */ new Map();
6880
+ let autoPlanSignature = "";
6881
+ let autoPlanRunning = false;
6882
+ function needsAutoResolve(row) {
6883
+ try {
6884
+ if (batchFallbackCauseOf(row) === null || singleFallbackPhoneOf(row) === null) return false;
6885
+ if (compliantInstalledOf(row) !== null) return false;
6886
+ if (rowFallbackOffers.has(row.key)) return false;
6887
+ const seen = gatedOutcomes.get(row.key);
6888
+ if (seen) {
6889
+ if (seen.kind === "installed") return false;
6890
+ if (seen.kind === "wait-until") {
6891
+ if (seen.unlockAtMs === null) return false;
6892
+ if (nowMs() < seen.unlockAtMs) return false;
6893
+ }
6894
+ }
6895
+ return true;
6896
+ } catch {
6897
+ return false;
6898
+ }
6899
+ }
6900
+ async function resolveGated(row) {
6901
+ const cause = batchFallbackCauseOf(row);
6902
+ const phoneName = singleFallbackPhoneOf(row);
6903
+ if (!cause || phoneName === null) return {
6904
+ kind: "",
6905
+ candidate: "",
6906
+ checkId: "",
6907
+ unlockAtMs: null
6908
+ };
6909
+ let reply = null;
6910
+ try {
6911
+ reply = await call(phoneName, {
6912
+ kind: cause.kind,
6913
+ blame: "target",
6914
+ targetVersion: cause.targetVersion,
6915
+ ...typeof cause.windowMs === "number" ? { windowMs: cause.windowMs } : {},
6916
+ windowSource: cause.windowSource,
6917
+ ...typeof cause.publishedAtMs === "number" ? { publishedAtMs: cause.publishedAtMs } : {}
6918
+ });
6919
+ } catch {
6920
+ reply = null;
6921
+ }
6922
+ const outcome = fallbackOutcomeOf(reply);
6923
+ gatedOutcomes.set(row.key, {
6924
+ kind: outcome.kind,
6925
+ unlockAtMs: outcome.unlockAtMs
6926
+ });
6927
+ if (outcome.candidate) rowFallbackOffers.set(row.key, {
6928
+ candidate: outcome.candidate,
6929
+ checkId: outcome.checkId,
6930
+ targetVersion: cause.targetVersion
6931
+ });
6932
+ return outcome;
6933
+ }
6934
+ function maybeAutoPlanCompliant() {
6935
+ if (!autoPlanEnabled || !mounted || inFlight || autoPlanRunning) return;
6936
+ let signature = "";
6937
+ const targets = [];
6938
+ try {
6939
+ for (const row of rows) {
6940
+ if (!needsAutoResolve(row)) continue;
6941
+ targets.push(row);
6942
+ signature += row.key + "@" + (latestVersionOf(row) ?? "") + ";";
6943
+ }
6944
+ } catch {
6945
+ return;
6946
+ }
6947
+ if (targets.length === 0 || signature === autoPlanSignature) return;
6948
+ autoPlanSignature = signature;
6949
+ autoPlanRunning = true;
6950
+ inFlight = true;
6951
+ render();
6952
+ (async () => {
6953
+ try {
6954
+ for (const row of targets) {
6955
+ if (!mounted) return;
6956
+ if (!needsAutoResolve(row)) continue;
6957
+ await resolveGated(row);
6958
+ }
6959
+ } finally {
6960
+ autoPlanRunning = false;
6961
+ inFlight = false;
6962
+ }
6963
+ if (!mounted) return;
6964
+ const merged = [...batchCompliantKeys];
6965
+ for (const row of targets) if (rowFallbackOffers.has(row.key) && !merged.includes(row.key)) merged.push(row.key);
6966
+ batchCompliantKeys = merged.filter((k) => rowFallbackOffers.has(k));
6967
+ if (batchCompliantKeys.length > 0) clearNotice();
6968
+ render();
6969
+ maybeAutoRowChangelog(expandedKey);
6970
+ })();
6971
+ }
6972
+ function waitingOf() {
6973
+ const keys = [];
6974
+ let earliest = null;
6975
+ try {
6976
+ for (const row of rows) {
6977
+ const seen = gatedOutcomes.get(row.key);
6978
+ if (!seen || seen.kind !== "wait-until") continue;
6979
+ if (compliantInstalledOf(row) !== null) continue;
6980
+ if (batchFallbackCauseOf(row) === null || singleFallbackPhoneOf(row) === null) continue;
6981
+ keys.push(row.key);
6982
+ if (typeof seen.unlockAtMs === "number" && (earliest === null || seen.unlockAtMs < earliest)) earliest = seen.unlockAtMs;
6983
+ }
6984
+ } catch {
6985
+ return {
6986
+ keys: [],
6987
+ unlockAtMs: null
6988
+ };
6989
+ }
6990
+ return {
6991
+ keys,
6992
+ unlockAtMs: earliest
6993
+ };
6994
+ }
6995
+ function unlockTimesOf() {
6996
+ const out = {};
6997
+ try {
6998
+ for (const [k, v] of gatedOutcomes) out[k] = v.unlockAtMs;
6999
+ } catch {}
7000
+ return out;
7001
+ }
6463
7002
  function rowOfKey(key) {
6464
7003
  return rows.find((row) => row.key === key) ?? null;
6465
7004
  }
@@ -6496,7 +7035,7 @@ ${body}`;
6496
7035
  }
6497
7036
  async function copyWith(text, okNotice, key) {
6498
7037
  try {
6499
- await copyText(text);
7038
+ await copyText$1(text);
6500
7039
  say(okNotice, key);
6501
7040
  } catch {
6502
7041
  say(batchText("batch.toast.copy-fail", currentLang()), key);
@@ -6535,6 +7074,7 @@ ${body}`;
6535
7074
  loaded = true;
6536
7075
  lastError = null;
6537
7076
  if (expandedKey !== null && !rows.some((row) => row.key === expandedKey)) expandedKey = null;
7077
+ pruneFallbackOffers();
6538
7078
  return;
6539
7079
  }
6540
7080
  const err = typeof reply["error"] === "string" ? reply["error"].trim() : "";
@@ -6555,6 +7095,7 @@ ${body}`;
6555
7095
  }
6556
7096
  render();
6557
7097
  maybeAutoRowChangelog(expandedKey);
7098
+ maybeAutoPlanCompliant();
6558
7099
  }
6559
7100
  async function phone(name, args, busyRowKey = null) {
6560
7101
  if (!mounted || inFlight) return null;
@@ -6583,6 +7124,7 @@ ${body}`;
6583
7124
  if (mounted) render();
6584
7125
  }
6585
7126
  maybeAutoRowChangelog(expandedKey);
7127
+ maybeAutoPlanCompliant();
6586
7128
  return out;
6587
7129
  }
6588
7130
  function remainingCount() {
@@ -6757,6 +7299,212 @@ ${body}`;
6757
7299
  await copyWith(diagTextOf(row, currentLang()), batchText("batch.toast.copy-diag-ok", currentLang()), row.key);
6758
7300
  return;
6759
7301
  }
7302
+ case "row-fallback": {
7303
+ const row = typeof key === "string" && key ? rowOfKey(key) : null;
7304
+ if (!row) return;
7305
+ const cause = batchFallbackCauseOf(row);
7306
+ const phoneName = singleFallbackPhoneOf(row);
7307
+ if (!cause || phoneName === null) {
7308
+ say(copyText("panel.toast.fallback-unavailable", currentLang()), row.key);
7309
+ render();
7310
+ return;
7311
+ }
7312
+ if (inFlight) return;
7313
+ inFlight = true;
7314
+ fallbackBusyKey = row.key;
7315
+ clearNotice();
7316
+ render();
7317
+ try {
7318
+ let reply;
7319
+ try {
7320
+ reply = await call(phoneName, {
7321
+ kind: cause.kind,
7322
+ blame: "target",
7323
+ targetVersion: cause.targetVersion,
7324
+ ...typeof cause.windowMs === "number" ? { windowMs: cause.windowMs } : {},
7325
+ windowSource: cause.windowSource,
7326
+ ...typeof cause.publishedAtMs === "number" ? { publishedAtMs: cause.publishedAtMs } : {}
7327
+ });
7328
+ } catch {
7329
+ reply = null;
7330
+ }
7331
+ if (!mounted) return;
7332
+ if (!isObject$2(reply) || reply["ok"] === false) {
7333
+ say(copyText("panel.toast.fallback-unavailable", currentLang()), row.key);
7334
+ render();
7335
+ return;
7336
+ }
7337
+ const outcome = fallbackOutcomeOf(reply);
7338
+ if (outcome.candidate) {
7339
+ rowFallbackOffers.set(row.key, {
7340
+ candidate: outcome.candidate,
7341
+ checkId: outcome.checkId,
7342
+ targetVersion: cause.targetVersion
7343
+ });
7344
+ clearNotice();
7345
+ } else if (outcome.kind === "wait-until") {
7346
+ const time = typeof outcome.unlockAtMs === "number" ? formatUnlockAt(outcome.unlockAtMs, currentLang()) ?? null : null;
7347
+ say(time ? copyText("panel.toast.fallback-wait", currentLang(), { time }) : copyText("panel.toast.fallback-unavailable", currentLang()), row.key);
7348
+ } else say(copyText("panel.toast.fallback-unavailable", currentLang()), row.key);
7349
+ } finally {
7350
+ inFlight = false;
7351
+ fallbackBusyKey = null;
7352
+ }
7353
+ render();
7354
+ return;
7355
+ }
7356
+ case "batch-fallback-scan": {
7357
+ const targets = rows.filter((row) => batchFallbackCauseOf(row) !== null && singleFallbackPhoneOf(row) !== null);
7358
+ if (targets.length === 0) {
7359
+ say(copyText("panel.toast.fallback-unavailable", currentLang()));
7360
+ render();
7361
+ return;
7362
+ }
7363
+ if (inFlight) return;
7364
+ inFlight = true;
7365
+ clearNotice();
7366
+ render();
7367
+ const got = [];
7368
+ let waitTime = null;
7369
+ try {
7370
+ for (const row of targets) {
7371
+ if (!batchFallbackCauseOf(row) || singleFallbackPhoneOf(row) === null) continue;
7372
+ const outcome = await resolveGated(row);
7373
+ if (!mounted) return;
7374
+ if (outcome.candidate) got.push(row.key);
7375
+ else if (outcome.kind === "wait-until" && waitTime === null) waitTime = typeof outcome.unlockAtMs === "number" ? formatUnlockAt(outcome.unlockAtMs, currentLang()) ?? null : null;
7376
+ }
7377
+ } finally {
7378
+ inFlight = false;
7379
+ }
7380
+ if (got.length > 0) {
7381
+ const merged = [...batchCompliantKeys];
7382
+ for (const k of got) if (!merged.includes(k)) merged.push(k);
7383
+ batchCompliantKeys = merged.filter((k) => rowFallbackOffers.has(k));
7384
+ clearNotice();
7385
+ } else if (waitTime) say(copyText("panel.toast.fallback-wait", currentLang(), { time: waitTime }));
7386
+ else say(copyText("panel.toast.fallback-unavailable", currentLang()));
7387
+ render();
7388
+ return;
7389
+ }
7390
+ case "batch-fallback-confirm": {
7391
+ const keys = batchCompliantKeys.filter((k) => rowFallbackOffers.has(k));
7392
+ if (keys.length === 0) {
7393
+ batchCompliantKeys = [];
7394
+ say(copyText("panel.toast.fallback-unavailable", currentLang()));
7395
+ render();
7396
+ return;
7397
+ }
7398
+ if (inFlight) return;
7399
+ inFlight = true;
7400
+ clearNotice();
7401
+ render();
7402
+ let done = 0;
7403
+ let bad = 0;
7404
+ let busyCount = 0;
7405
+ try {
7406
+ for (const k of keys) {
7407
+ const row = rowOfKey(k);
7408
+ const offer = rowFallbackOffers.get(k) ?? null;
7409
+ const phoneName = row ? singleInstallPhoneOf(row) : null;
7410
+ if (!row || !offer || phoneName === null || offer.targetVersion !== latestVersionOf(row)) {
7411
+ rowFallbackOffers.delete(k);
7412
+ bad++;
7413
+ continue;
7414
+ }
7415
+ const freshRequestId = `r${Date.now().toString(36)}${Math.floor(Math.random() * 16777215).toString(36)}`.slice(0, 64);
7416
+ let reply = null;
7417
+ try {
7418
+ reply = await call(phoneName, {
7419
+ checkId: offer.checkId,
7420
+ requestId: freshRequestId,
7421
+ version: offer.candidate,
7422
+ from: "batch",
7423
+ includeEnv: true,
7424
+ includeQueue: true
7425
+ });
7426
+ } catch {
7427
+ reply = null;
7428
+ }
7429
+ const busy = isObject$2(reply) && reply["ok"] === false && (reply["error"] === "update-busy" || reply["errorKind"] === "update-busy");
7430
+ if (!busy) rowFallbackOffers.delete(k);
7431
+ if (!mounted) return;
7432
+ if (isObject$2(reply) && reply["ok"] !== false) {
7433
+ done++;
7434
+ const prev = gatedOutcomes.get(k);
7435
+ gatedOutcomes.set(k, {
7436
+ kind: "installed",
7437
+ unlockAtMs: prev ? prev.unlockAtMs : null
7438
+ });
7439
+ } else if (busy) busyCount++;
7440
+ else bad++;
7441
+ }
7442
+ } finally {
7443
+ inFlight = false;
7444
+ }
7445
+ batchCompliantKeys = keys.filter((k) => rowFallbackOffers.has(k));
7446
+ pruneFallbackOffers();
7447
+ await refresh();
7448
+ const parts = [];
7449
+ if (done > 0 || bad > 0) parts.push(batchText("batch.toast.compliant-installed", currentLang(), {
7450
+ a: String(done),
7451
+ b: String(bad)
7452
+ }));
7453
+ if (busyCount > 0) parts.push(copyText("batch.toast.queue-missed", currentLang()));
7454
+ if (parts.length > 0) say(parts.join(" "));
7455
+ render();
7456
+ return;
7457
+ }
7458
+ case "row-fallback-confirm": {
7459
+ const row = typeof key === "string" && key ? rowOfKey(key) : null;
7460
+ if (!row) return;
7461
+ const offer = rowFallbackOffers.get(row.key) ?? null;
7462
+ const phoneName = singleInstallPhoneOf(row);
7463
+ if (!offer || phoneName === null || offer.targetVersion !== latestVersionOf(row)) {
7464
+ rowFallbackOffers.delete(row.key);
7465
+ say(copyText("panel.toast.fallback-unavailable", currentLang()), row.key);
7466
+ render();
7467
+ return;
7468
+ }
7469
+ if (inFlight) return;
7470
+ inFlight = true;
7471
+ clearNotice();
7472
+ render();
7473
+ try {
7474
+ const freshRequestId = `r${Date.now().toString(36)}${Math.floor(Math.random() * 16777215).toString(36)}`.slice(0, 64);
7475
+ let reply;
7476
+ try {
7477
+ reply = await call(phoneName, {
7478
+ checkId: offer.checkId,
7479
+ requestId: freshRequestId,
7480
+ version: offer.candidate,
7481
+ from: "batch",
7482
+ includeEnv: true,
7483
+ includeQueue: true
7484
+ });
7485
+ } catch {
7486
+ reply = null;
7487
+ }
7488
+ if (!mounted) return;
7489
+ if (isObject$2(reply) && reply["ok"] === false && (reply["error"] === "update-busy" || reply["errorKind"] === "update-busy")) say(copyText("batch.toast.queue-missed", currentLang()), row.key);
7490
+ else {
7491
+ rowFallbackOffers.delete(row.key);
7492
+ if (!isObject$2(reply) || reply["ok"] === false) say(copyText("panel.toast.fallback-unavailable", currentLang()), row.key);
7493
+ else {
7494
+ const prev = gatedOutcomes.get(row.key);
7495
+ gatedOutcomes.set(row.key, {
7496
+ kind: "installed",
7497
+ unlockAtMs: prev ? prev.unlockAtMs : null
7498
+ });
7499
+ }
7500
+ }
7501
+ } finally {
7502
+ inFlight = false;
7503
+ }
7504
+ pruneFallbackOffers();
7505
+ await refresh();
7506
+ return;
7507
+ }
6760
7508
  case "toggle-details":
6761
7509
  if (typeof key !== "string" || !key) return;
6762
7510
  expandedKey = expandedKey === key ? null : key;
@@ -6936,7 +7684,7 @@ ${body}`;
6936
7684
  };
6937
7685
  }
6938
7686
  //#endregion
6939
- //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.6/node_modules/dsh-plugin-update/dist/entry-batch.js
7687
+ //#region ../../node_modules/.pnpm/dsh-plugin-update@0.18.7/node_modules/dsh-plugin-update/dist/entry-batch.js
6940
7688
  function isObject$1(value) {
6941
7689
  return !!value && typeof value === "object" && !Array.isArray(value);
6942
7690
  }
@@ -6947,11 +7695,20 @@ ${body}`;
6947
7695
  function escapeHtml$1(value) {
6948
7696
  return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6949
7697
  }
7698
+ function compliantEntryDone(rows) {
7699
+ const done = /* @__PURE__ */ new Map();
7700
+ if (!Array.isArray(rows)) return done;
7701
+ for (const row of rows) try {
7702
+ const fact = compliantInstalledOf(row);
7703
+ if (fact) done.set(row.key, fact.needsRestart);
7704
+ } catch {}
7705
+ return done;
7706
+ }
6950
7707
  function batchEntrySummary(state, lang) {
6951
7708
  const rows = state?.rows ?? null;
6952
7709
  const inventory = state?.inventory ?? void 0;
6953
7710
  const session = state?.session;
6954
- const counts = batchLedgerCounts(Array.isArray(rows) ? rows : [], inventory, session);
7711
+ const counts = batchLedgerCounts(Array.isArray(rows) ? rows : [], inventory, session, compliantEntryDone(rows));
6955
7712
  const total = Array.isArray(rows) ? rows.length : 0;
6956
7713
  let kind;
6957
7714
  if (!Array.isArray(rows) || rows.length === 0) kind = state?.error ? "failed" : "idle";
@@ -7372,6 +8129,29 @@ ${body}`;
7372
8129
  }
7373
8130
  } catch {}
7374
8131
  }
8132
+ function installButtonFallbackOf(input, bannerKind) {
8133
+ try {
8134
+ if (bannerKind !== "failed") return null;
8135
+ if (input.busyAct === "check" || input.busyAct === "install") return null;
8136
+ const code = typeof input.lastError === "string" && input.lastError ? input.lastError : "install-failed";
8137
+ const cause = fallbackCauseFromDiag(input.errorDiag, input.errorDetail ?? null, code, input.snapshot?.job?.targetVersion) ?? fallbackCauseFromDetail(input.errorDetail ?? null, code, input.pluginId ?? null);
8138
+ if (!cause) return null;
8139
+ const latest = input.snapshot?.latestVersion ?? null;
8140
+ if (typeof latest !== "string" || !latest || cause.targetVersion !== latest) return null;
8141
+ const offered = typeof input.fallbackOffer?.candidate === "string" ? input.fallbackOffer.candidate.trim() : "";
8142
+ const offerTarget = typeof input.fallbackOffer?.targetVersion === "string" ? input.fallbackOffer.targetVersion.trim() : "";
8143
+ if (offered && offerTarget === cause.targetVersion) return {
8144
+ stage: "confirm",
8145
+ candidate: offered
8146
+ };
8147
+ return {
8148
+ stage: "offer",
8149
+ candidate: null
8150
+ };
8151
+ } catch {
8152
+ return null;
8153
+ }
8154
+ }
7375
8155
  function installButtonViewModel(input) {
7376
8156
  const l = normalizeLangTag(input.lang ?? "zh");
7377
8157
  const view = panelViewModel({
@@ -7384,6 +8164,8 @@ ${body}`;
7384
8164
  }, l);
7385
8165
  const jobState = (input.snapshot?.job ?? null)?.state ?? null;
7386
8166
  const jobActive = jobState === "installing" || jobState === "verifying";
8167
+ const fallback = installButtonFallbackOf(input, view.banner.kind);
8168
+ const fallbackBusy = input.busyAct === "fallback";
7387
8169
  if (input.pollCapped === true && jobActive && !input.busyAct) return {
7388
8170
  role: "check",
7389
8171
  buttonLabel: copyText("panel.action.check", l),
@@ -7394,9 +8176,12 @@ ${body}`;
7394
8176
  statusAction: view.banner.action,
7395
8177
  showManual: view.showManual,
7396
8178
  queueNote: view.queueNote,
7397
- bannerKind: view.banner.kind
8179
+ bannerKind: view.banner.kind,
8180
+ fallbackStage: null,
8181
+ fallbackCandidate: null,
8182
+ fallbackBusy: false
7398
8183
  };
7399
- if (input.busyAct === "check" || input.busyAct === "install" || jobActive) return {
8184
+ if (input.busyAct === "check" || input.busyAct === "install" || input.busyAct === "fallback" || jobActive) return {
7400
8185
  role: "none",
7401
8186
  buttonLabel: input.busyAct === "check" ? copyText("panel.action.checking-busy", l) : input.busyAct === "install" ? copyText("panel.action.installing-busy", l) : view.installLabel,
7402
8187
  buttonEnabled: false,
@@ -7406,7 +8191,10 @@ ${body}`;
7406
8191
  statusAction: view.banner.action,
7407
8192
  showManual: view.showManual,
7408
8193
  queueNote: view.queueNote,
7409
- bannerKind: view.banner.kind
8194
+ bannerKind: view.banner.kind,
8195
+ fallbackStage: fallback?.stage ?? null,
8196
+ fallbackCandidate: fallback?.candidate ?? null,
8197
+ fallbackBusy
7410
8198
  };
7411
8199
  if (view.installEnabled) return {
7412
8200
  role: "install",
@@ -7418,7 +8206,10 @@ ${body}`;
7418
8206
  statusAction: view.banner.action,
7419
8207
  showManual: view.showManual,
7420
8208
  queueNote: view.queueNote,
7421
- bannerKind: view.banner.kind
8209
+ bannerKind: view.banner.kind,
8210
+ fallbackStage: fallback?.stage ?? null,
8211
+ fallbackCandidate: fallback?.candidate ?? null,
8212
+ fallbackBusy
7422
8213
  };
7423
8214
  if (!input.snapshot || !input.snapshot.latestVersion) return {
7424
8215
  role: "check",
@@ -7430,7 +8221,10 @@ ${body}`;
7430
8221
  statusAction: view.banner.action,
7431
8222
  showManual: view.showManual,
7432
8223
  queueNote: view.queueNote,
7433
- bannerKind: view.banner.kind
8224
+ bannerKind: view.banner.kind,
8225
+ fallbackStage: null,
8226
+ fallbackCandidate: null,
8227
+ fallbackBusy: false
7434
8228
  };
7435
8229
  if (input.lastError && isCheckSideFailureCode(input.lastError)) return {
7436
8230
  role: "check",
@@ -7442,7 +8236,10 @@ ${body}`;
7442
8236
  statusAction: view.banner.action,
7443
8237
  showManual: view.showManual,
7444
8238
  queueNote: view.queueNote,
7445
- bannerKind: view.banner.kind
8239
+ bannerKind: view.banner.kind,
8240
+ fallbackStage: null,
8241
+ fallbackCandidate: null,
8242
+ fallbackBusy: false
7446
8243
  };
7447
8244
  const done = view.banner.kind === "done";
7448
8245
  return {
@@ -7455,7 +8252,10 @@ ${body}`;
7455
8252
  statusAction: view.banner.action,
7456
8253
  showManual: view.showManual,
7457
8254
  queueNote: view.queueNote,
7458
- bannerKind: view.banner.kind
8255
+ bannerKind: view.banner.kind,
8256
+ fallbackStage: fallback?.stage ?? null,
8257
+ fallbackCandidate: fallback?.candidate ?? null,
8258
+ fallbackBusy
7459
8259
  };
7460
8260
  }
7461
8261
  function renderInstallButtonHTML(view, opts) {
@@ -7472,6 +8272,13 @@ ${body}`;
7472
8272
  const busy = view.buttonBusy ? " aria-busy=\"true\"" : "";
7473
8273
  parts.push("<div class=\"dsh-upd-ib-row\"><button type=\"button\"" + actionAttr + " data-primary=\"1\"" + disabled + busy + ">" + escapeHtml(view.buttonLabel) + "</button></div>");
7474
8274
  }
8275
+ if (view.fallbackStage === "confirm" && view.fallbackCandidate) {
8276
+ const busy = view.fallbackBusy || view.buttonBusy ? " disabled aria-busy=\"true\"" : "";
8277
+ parts.push("<div class=\"dsh-upd-ib-row\"><button type=\"button\" data-action=\"install-fallback-confirm\" data-primary=\"1\" title=\"" + escapeHtml(copyText("panel.action.install-fallback-confirm-title", l)) + "\"" + busy + ">" + escapeHtml(copyText("panel.action.install-fallback-confirm", l, { version: view.fallbackCandidate })) + "</button><button type=\"button\" data-action=\"wait-latest\" title=\"" + escapeHtml(copyText("panel.action.wait-latest-title", l)) + "\"" + (view.fallbackBusy || view.buttonBusy ? " disabled" : "") + ">" + escapeHtml(copyText("panel.action.wait-latest", l)) + "</button></div>");
8278
+ } else if (view.fallbackStage === "offer") {
8279
+ const busy = view.fallbackBusy || view.buttonBusy ? " disabled aria-busy=\"true\"" : "";
8280
+ parts.push("<div class=\"dsh-upd-ib-row\"><button type=\"button\" data-action=\"install-fallback\" data-primary=\"1\" title=\"" + escapeHtml(copyText("panel.action.install-fallback-title", l)) + "\"" + busy + ">" + escapeHtml(copyText("panel.action.install-fallback", l)) + "</button><button type=\"button\" data-action=\"wait-latest\" title=\"" + escapeHtml(copyText("panel.action.wait-latest-title", l)) + "\"" + (view.fallbackBusy || view.buttonBusy ? " disabled" : "") + ">" + escapeHtml(copyText("panel.action.wait-latest", l)) + "</button></div>");
8281
+ }
7475
8282
  if (view.bannerKind === "failed") parts.push("<div class=\"dsh-upd-ib-row\"><button type=\"button\" data-action=\"copy-diag\" title=\"" + escapeHtml(copyText("panel.action.copy-diag-title", l)) + "\">" + escapeHtml(copyText("panel.action.copy-diag", l)) + "</button></div>");
7476
8283
  if (view.showManual && opts.manual) parts.push("<details class=\"dsh-upd-ib-manual\"><summary>" + escapeHtml(copyText("panel.chapter.manual", l)) + "</summary><code>" + escapeHtml(opts.manual) + "</code><button type=\"button\" data-action=\"copy-manual\" title=\"" + escapeHtml(copyText("panel.action.copy-manual-title", l)) + "\">" + escapeHtml(copyText("panel.action.copy-manual", l)) + "</button></details>");
7477
8284
  if (opts.notice) parts.push("<div class=\"dsh-upd-ib-notice\" role=\"status\">" + escapeHtml(opts.notice) + "</div>");
@@ -7577,6 +8384,7 @@ ${body}`;
7577
8384
  let requestId = null;
7578
8385
  let latch = null;
7579
8386
  let busyAct = null;
8387
+ let fallbackOffer = null;
7580
8388
  let ensuring = false;
7581
8389
  let pollCount = 0;
7582
8390
  let pollCapped = false;
@@ -7626,9 +8434,11 @@ ${body}`;
7626
8434
  }
7627
8435
  function setLatch(next) {
7628
8436
  latch = next;
8437
+ fallbackOffer = null;
7629
8438
  }
7630
8439
  function clearLatch() {
7631
8440
  latch = null;
8441
+ fallbackOffer = null;
7632
8442
  }
7633
8443
  function latchFromReply(reply, source) {
7634
8444
  const env = currentEnvPair();
@@ -7653,7 +8463,7 @@ ${body}`;
7653
8463
  atMs: latchNow()
7654
8464
  };
7655
8465
  }
7656
- function latchFromJob(job, snap) {
8466
+ function latchFromJob(job, snap, replyDiag) {
7657
8467
  const env = currentEnvPair();
7658
8468
  const message = typeof job.message === "string" ? job.message : null;
7659
8469
  const detail = message && message.includes(":") ? message.slice(message.indexOf(":") + 1).trim() || null : message;
@@ -7661,7 +8471,7 @@ ${body}`;
7661
8471
  code: messageCodeOf(message) || "install-failed",
7662
8472
  kind: null,
7663
8473
  detail,
7664
- diag: latch?.diag ?? null,
8474
+ diag: latch?.diag ?? replyDiag ?? null,
7665
8475
  requestId: job.requestId ?? latch?.requestId ?? requestId,
7666
8476
  checkId: receipt?.checkId ?? latch?.checkId ?? null,
7667
8477
  runningVersion: snap?.runningVersion ?? snapshot?.runningVersion ?? null,
@@ -7715,7 +8525,7 @@ ${body}`;
7715
8525
  if (jobState === "installing" || jobState === "verifying") clearLatch();
7716
8526
  else if (jobState === "completed" || jobState === "restart-required") clearLatch();
7717
8527
  else if ((jobState === "failed" || jobState === "interrupted") && snapshot?.job) {
7718
- const next = latchFromJob(snapshot.job, snapshot);
8528
+ const next = latchFromJob(snapshot.job, snapshot, isObject(reply["diag"]) ? reply["diag"] : null);
7719
8529
  if (!latch || latch.volatile || latch.code !== next.code || latch.targetVersion !== next.targetVersion || latch.latestVersion !== next.latestVersion) setLatch(next);
7720
8530
  } else if (via === "install") clearLatch();
7721
8531
  else if (via === "check" && latch?.source === "check") clearLatch();
@@ -7754,6 +8564,13 @@ ${body}`;
7754
8564
  queue,
7755
8565
  lastError: latch?.code ?? null,
7756
8566
  errorKind: latch?.kind ?? null,
8567
+ errorDetail: latch?.detail ?? null,
8568
+ errorDiag: latch?.diag ?? null,
8569
+ pluginId,
8570
+ fallbackOffer: fallbackOffer ? {
8571
+ candidate: fallbackOffer.candidate,
8572
+ targetVersion: fallbackOffer.targetVersion
8573
+ } : null,
7757
8574
  busyAct,
7758
8575
  pollCapped,
7759
8576
  lang: currentLang()
@@ -7780,9 +8597,9 @@ ${body}`;
7780
8597
  logClient("error", "update.panel.render.fail", { kind: "unknown" });
7781
8598
  }
7782
8599
  }
7783
- function sayCopyKey(key) {
8600
+ function sayCopyKey(key, params) {
7784
8601
  try {
7785
- copyNotice = copyText(key, currentLang());
8602
+ copyNotice = copyText(key, currentLang(), params);
7786
8603
  noticeExpiresAt = Date.now() + 5e3;
7787
8604
  } catch {
7788
8605
  copyNotice = null;
@@ -7980,6 +8797,124 @@ ${body}`;
7980
8797
  });
7981
8798
  }
7982
8799
  }
8800
+ async function doFallbackResolve() {
8801
+ if (!latch) {
8802
+ sayCopyKey("panel.toast.fallback-unavailable");
8803
+ paint();
8804
+ return;
8805
+ }
8806
+ const cause = fallbackCauseFromDetail(latch.detail, latch.code, pluginId);
8807
+ if (!cause || cause.targetVersion !== (latch.latestVersion ?? snapshot?.latestVersion ?? null)) {
8808
+ sayCopyKey("panel.toast.fallback-unavailable");
8809
+ paint();
8810
+ return;
8811
+ }
8812
+ busyAct = "fallback";
8813
+ paint();
8814
+ try {
8815
+ let reply;
8816
+ try {
8817
+ reply = await call(phoneNames.updateCheckFallback, {
8818
+ kind: cause.kind,
8819
+ blame: "target",
8820
+ targetVersion: cause.targetVersion,
8821
+ ...typeof cause.windowMs === "number" ? { windowMs: cause.windowMs } : {},
8822
+ windowSource: cause.windowSource,
8823
+ ...typeof cause.publishedAtMs === "number" ? { publishedAtMs: cause.publishedAtMs } : {}
8824
+ });
8825
+ } catch {
8826
+ reply = null;
8827
+ }
8828
+ if (!mounted) return;
8829
+ if (!isObject(reply) || reply["ok"] === false) {
8830
+ sayCopyKey("panel.toast.fallback-unavailable");
8831
+ paint();
8832
+ return;
8833
+ }
8834
+ const snap = isObject(reply["snapshot"]) ? reply["snapshot"] : null;
8835
+ const verdict = snap && isObject(snap["verdict"]) ? snap["verdict"] : null;
8836
+ const vKind = verdict && typeof verdict["kind"] === "string" ? verdict["kind"] : "";
8837
+ const vVersion = verdict && typeof verdict["version"] === "string" ? verdict["version"].trim() : "";
8838
+ const snapCandidate = snap && typeof snap["candidate"] === "string" ? snap["candidate"].trim() : "";
8839
+ const rec = isObject(reply["receipt"]) ? reply["receipt"] : null;
8840
+ const gotCheckId = rec && typeof rec["checkId"] === "string" ? rec["checkId"] : "";
8841
+ const candidate = snapCandidate || (vKind === "ok-now" ? vVersion : "");
8842
+ if ((vKind === "downgrade-available" || vKind === "ok-now") && candidate && gotCheckId) fallbackOffer = {
8843
+ candidate,
8844
+ checkId: gotCheckId,
8845
+ targetVersion: cause.targetVersion
8846
+ };
8847
+ else if (vKind === "wait-until") {
8848
+ const unlockAt = verdict && typeof verdict["unlockAtMs"] === "number" ? verdict["unlockAtMs"] : null;
8849
+ const time = typeof unlockAt === "number" ? formatUnlockAt(unlockAt, currentLang()) ?? null : null;
8850
+ if (time) sayCopyKey("panel.toast.fallback-wait", { time });
8851
+ else sayCopyKey("panel.toast.fallback-unavailable");
8852
+ } else sayCopyKey("panel.toast.fallback-unavailable");
8853
+ } finally {
8854
+ busyAct = null;
8855
+ }
8856
+ paint();
8857
+ }
8858
+ async function doFallbackConfirm() {
8859
+ if (!fallbackOffer) return;
8860
+ const offer = fallbackOffer;
8861
+ busyAct = "install";
8862
+ copyNotice = copyText("panel.toast.installing", currentLang());
8863
+ paint();
8864
+ try {
8865
+ requestId = randomRequestId();
8866
+ pollCount = 0;
8867
+ pollCapped = false;
8868
+ let reply;
8869
+ try {
8870
+ reply = await call(phoneNames.updateInstall, {
8871
+ checkId: offer.checkId,
8872
+ requestId,
8873
+ version: offer.candidate,
8874
+ ...queueArgs()
8875
+ });
8876
+ } catch (err) {
8877
+ if (!mounted) return;
8878
+ fallbackOffer = null;
8879
+ if (thrownIsCheckExpired(err)) {
8880
+ sayCopyKey("panel.toast.fallback-unavailable");
8881
+ paint();
8882
+ return;
8883
+ }
8884
+ const env = currentEnvPair();
8885
+ setLatch({
8886
+ code: "install-failed",
8887
+ kind: null,
8888
+ detail: thrownDetail(err),
8889
+ diag: null,
8890
+ requestId,
8891
+ checkId: offer.checkId,
8892
+ runningVersion: snapshot?.runningVersion ?? null,
8893
+ installedVersion: snapshot?.installedVersion ?? null,
8894
+ latestVersion: snapshot?.latestVersion ?? null,
8895
+ targetVersion: null,
8896
+ hostKind: env.hostKind,
8897
+ profileName: env.profileName,
8898
+ source: "install",
8899
+ volatile: false,
8900
+ atMs: latchNow()
8901
+ });
8902
+ paint();
8903
+ return;
8904
+ }
8905
+ if (!mounted) return;
8906
+ fallbackOffer = null;
8907
+ if (replyIsCheckExpired(reply)) {
8908
+ sayCopyKey("panel.toast.fallback-unavailable");
8909
+ paint();
8910
+ return;
8911
+ }
8912
+ applyStatusReply(reply, "install");
8913
+ } finally {
8914
+ busyAct = null;
8915
+ }
8916
+ paint();
8917
+ }
7983
8918
  async function runAction(kind) {
7984
8919
  copyNotice = null;
7985
8920
  switch (kind) {
@@ -8031,6 +8966,20 @@ ${body}`;
8031
8966
  paint();
8032
8967
  return;
8033
8968
  }
8969
+ case "install-fallback":
8970
+ if (busyAct) return;
8971
+ await doFallbackResolve();
8972
+ return;
8973
+ case "install-fallback-confirm":
8974
+ if (busyAct || !fallbackOffer) return;
8975
+ await doFallbackConfirm();
8976
+ return;
8977
+ case "wait-latest":
8978
+ fallbackOffer = null;
8979
+ clearLatch();
8980
+ sayCopyKey("panel.toast.failure-dismissed");
8981
+ paint();
8982
+ return;
8034
8983
  }
8035
8984
  }
8036
8985
  function onClick(ev) {
@@ -8038,7 +8987,7 @@ ${body}`;
8038
8987
  const t = ev;
8039
8988
  const btn = t?.target && typeof t.target.closest === "function" ? t.target.closest("[data-action]") : null;
8040
8989
  const kind = btn?.getAttribute ? btn.getAttribute("data-action") : null;
8041
- if (kind !== "install" && kind !== "check" && kind !== "copy-manual" && kind !== "copy-diag") return;
8990
+ if (kind !== "install" && kind !== "check" && kind !== "copy-manual" && kind !== "copy-diag" && kind !== "install-fallback" && kind !== "install-fallback-confirm" && kind !== "wait-latest") return;
8042
8991
  act(kind);
8043
8992
  } catch {}
8044
8993
  }
@@ -10895,7 +11844,7 @@ ${body}`;
10895
11844
  }, react.createElement("span", { style: S.morePkg }, row.pkg), react.createElement("span", { style: S.moreDesc }, row.desc), react.createElement("span", { style: S.moreIcon }, react.createElement(ExternalIcon, null)))));
10896
11845
  }
10897
11846
  /** 版本胶囊那一行字:只在这里拼一次,屏上与用例读的是同一串。 */
10898
- const MANAGER_VERSION_TEXT = "dsh-life-pack · 0.3.66";
11847
+ const MANAGER_VERSION_TEXT = "dsh-life-pack · 0.3.67";
10899
11848
  /** 右下角那本书离面板两沿的距离:用户口径「一直处于面板右下角、不随列表滑动消失」
10900
11849
  * ⇒ sticky 吸附(面板相对,视口多大都落在面板角上);INSET=右沿,BOTTOM=底沿。 */
10901
11850
  const MANUAL_ENTRY_INSET = 16;
@@ -11096,7 +12045,40 @@ ${body}`;
11096
12045
  });
11097
12046
  } catch {}
11098
12047
  }, [active]);
11099
- return react.createElement("div", { style: { position: "relative" } }, react.createElement("div", { style: S.headRow }, react.createElement("div", { style: S.head }, "爱生活"), react.createElement("span", {
12048
+ /** 面板宽度不许跟着宿主滚动条变:宿主承载本面板的滚动体一出现滚动条,
12049
+ * 内容盒就窄 5px(主题里 `--dsh-scrollbar-width:5px`),标题行右对齐的按钮组便跟着左右挪;
12050
+ * 而滚动条出不出,取决于**当前页签**的内容高——装了的那家有配置卡,必然要滚,缺席页签不必。
12051
+ *
12052
+ * 这里不动面板高度、也不改宿主样式表,只给**最近的可滚动祖先**钉一条内联
12053
+ * `scrollbar-gutter: stable`:槽位恒定预留,滚动条本身仍按需出现/消失,内容盒宽度因此恒定。
12054
+ * 卸载时还原(宿主本来就有 stable 时压根不写),改动不留在宿主上。
12055
+ * 替身渲染台(test/helpers/panel-render.mjs)里 ref 拿不到元素 ⇒ 这里天然空过,不碰 window。 */
12056
+ const rootRef = react.useRef(null);
12057
+ react.useEffect(() => {
12058
+ const root = rootRef.current;
12059
+ if (root === null || typeof window === "undefined") return;
12060
+ let node = root.parentElement;
12061
+ while (node !== null) {
12062
+ const overflowY = window.getComputedStyle(node).overflowY;
12063
+ if (overflowY === "auto" || overflowY === "scroll") break;
12064
+ node = node.parentElement;
12065
+ }
12066
+ const scroller = node;
12067
+ if (scroller === null) return;
12068
+ if (window.getComputedStyle(scroller).getPropertyValue("scrollbar-gutter").trim() === "stable") return;
12069
+ const previous = scroller.style.getPropertyValue("scrollbar-gutter");
12070
+ scroller.style.setProperty("scrollbar-gutter", "stable");
12071
+ return () => {
12072
+ if (previous.length > 0) scroller.style.setProperty("scrollbar-gutter", previous);
12073
+ else scroller.style.removeProperty("scrollbar-gutter");
12074
+ };
12075
+ }, []);
12076
+ return react.createElement("div", {
12077
+ style: { position: "relative" },
12078
+ ref: (element) => {
12079
+ rootRef.current = element;
12080
+ }
12081
+ }, react.createElement("div", { style: S.headRow }, react.createElement("div", { style: S.head }, "爱生活"), react.createElement("span", {
11100
12082
  style: S.versionCapsule,
11101
12083
  "data-ilife-version": "capsule"
11102
12084
  }, MANAGER_VERSION_TEXT), react.createElement("div", { style: S.headActions }, react.createElement("button", {