dsh-plugin-discovery 1.1.1 → 1.2.4

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/client.js CHANGED
@@ -49,6 +49,20 @@ window.__ModuleLoader__.load({
49
49
  installLog: "安装输出:{text}",
50
50
  installFailed: "安装失败",
51
51
  retry: "重试",
52
+ copy: "复制",
53
+ copied: "已复制",
54
+ copyFailed: "复制失败",
55
+ riskInstall: "承担风险安装",
56
+ riskTitle: "版本不兼容,确认风险后安装?",
57
+ riskDesc:
58
+ "插件 {package} 与当前 DSH {runtime} 声明的 peer 不匹配。强行安装可能导致崩溃或数据丢失。仅在你明确愿意承担该风险时继续。",
59
+ riskAck: "我已了解风险,仍要为这对精确版本授予豁免并重试安装",
60
+ riskCancel: "取消",
61
+ riskConfirm: "确认豁免并安装",
62
+ riskWorking: "正在授权并重试…",
63
+ riskAltTitle: "推荐改用兼容插件",
64
+ riskAltDesc: "以下精选包更可能匹配当前 DSH 版本,可直接安装代替强行豁免:",
65
+ riskAltInstall: "安装推荐",
52
66
  browseRecommend: "为你推荐",
53
67
  browseCategory: "分类浏览",
54
68
  browseSearch: "在线搜索",
@@ -64,6 +78,17 @@ window.__ModuleLoader__.load({
64
78
  catalogOriginGithub: "GitHub",
65
79
  catalogOriginRemote: "远程",
66
80
  refreshCatalog: "刷新目录",
81
+ selfUpdateTitle: "发现插件有新版本",
82
+ selfUpdateDesc: "当前 {current} → 最新 {latest}(来源 {origin})",
83
+ selfUpdateNow: "立即更新",
84
+ selfUpdateChecking: "正在检查更新…",
85
+ selfUpdateUpdating: "正在更新…",
86
+ selfUpdateLatest: "发现插件已是最新版 {version}",
87
+ selfUpdateDone: "已更新到 {version},请刷新页面",
88
+ selfUpdateFileSkip: "本地开发安装,已跳过自动更新",
89
+ selfUpdateOriginNpm: "npm",
90
+ selfUpdateOriginGitee: "Gitee",
91
+ selfUpdateOriginGithub: "GitHub",
67
92
  reasonFeatured: "精选",
68
93
  reasonRelated: "与已装相关",
69
94
  reasonPopular: "热门",
@@ -118,6 +143,20 @@ window.__ModuleLoader__.load({
118
143
  installLog: "Output: {text}",
119
144
  installFailed: "Install failed",
120
145
  retry: "Retry",
146
+ copy: "Copy",
147
+ copied: "Copied",
148
+ copyFailed: "Copy failed",
149
+ riskInstall: "Install anyway",
150
+ riskTitle: "Incompatible version — install at your own risk?",
151
+ riskDesc:
152
+ "Plugin {package} does not match peers for DSH {runtime}. Forcing install may crash or lose data. Continue only if you accept that risk.",
153
+ riskAck: "I understand the risk and grant an exact-version exemption for this pair",
154
+ riskCancel: "Cancel",
155
+ riskConfirm: "Grant exemption and install",
156
+ riskWorking: "Granting and retrying…",
157
+ riskAltTitle: "Try a compatible plugin instead",
158
+ riskAltDesc: "These curated packages are more likely to match this DSH version:",
159
+ riskAltInstall: "Install recommended",
121
160
  browseRecommend: "For you",
122
161
  browseCategory: "Categories",
123
162
  browseSearch: "Online search",
@@ -133,6 +172,17 @@ window.__ModuleLoader__.load({
133
172
  catalogOriginGithub: "GitHub",
134
173
  catalogOriginRemote: "remote",
135
174
  refreshCatalog: "Refresh catalog",
175
+ selfUpdateTitle: "Discovery plugin update available",
176
+ selfUpdateDesc: "Current {current} → latest {latest} (via {origin})",
177
+ selfUpdateNow: "Update now",
178
+ selfUpdateChecking: "Checking for updates…",
179
+ selfUpdateUpdating: "Updating…",
180
+ selfUpdateLatest: "Discovery plugin is up to date ({version})",
181
+ selfUpdateDone: "Updated to {version}. Please refresh the page.",
182
+ selfUpdateFileSkip: "Local file: install — auto-update skipped",
183
+ selfUpdateOriginNpm: "npm",
184
+ selfUpdateOriginGitee: "Gitee",
185
+ selfUpdateOriginGithub: "GitHub",
136
186
  reasonFeatured: "Featured",
137
187
  reasonRelated: "Related to installed",
138
188
  reasonPopular: "Popular",
@@ -571,7 +621,7 @@ window.__ModuleLoader__.load({
571
621
  },
572
622
  errorBox: {
573
623
  display: "flex",
574
- alignItems: "center",
624
+ alignItems: "flex-start",
575
625
  justifyContent: "space-between",
576
626
  gap: "12px",
577
627
  padding: "12px 14px",
@@ -580,6 +630,136 @@ window.__ModuleLoader__.load({
580
630
  background: "var(--dsw-alias-settings-card-fill, #ffffff)",
581
631
  color: "var(--dsw-alias-state-warn-primary, var(--dsw-alias-label-secondary))",
582
632
  fontSize: "13px",
633
+ lineHeight: "20px",
634
+ },
635
+ errorText: {
636
+ flex: 1,
637
+ minWidth: 0,
638
+ whiteSpace: "pre-wrap",
639
+ wordBreak: "break-word",
640
+ overflowWrap: "anywhere",
641
+ },
642
+ errorActions: {
643
+ display: "flex",
644
+ alignItems: "center",
645
+ gap: "8px",
646
+ flexShrink: 0,
647
+ flexWrap: "wrap",
648
+ justifyContent: "flex-end",
649
+ },
650
+ riskOverlay: {
651
+ position: "fixed",
652
+ inset: 0,
653
+ zIndex: 10000,
654
+ display: "flex",
655
+ alignItems: "center",
656
+ justifyContent: "center",
657
+ padding: "24px",
658
+ background: "rgba(15, 23, 42, 0.45)",
659
+ },
660
+ riskDialog: {
661
+ width: "min(520px, 100%)",
662
+ maxHeight: "90vh",
663
+ overflow: "auto",
664
+ borderRadius: "var(--dsw-radius-md, 12px)",
665
+ border: "1px solid var(--dsw-alias-border-l2, #d0d5dd)",
666
+ background: "var(--dsw-alias-settings-card-fill, #ffffff)",
667
+ color: "var(--dsw-alias-label-primary)",
668
+ padding: "20px",
669
+ boxShadow: "0 16px 40px rgba(15, 23, 42, 0.18)",
670
+ },
671
+ riskTitle: {
672
+ margin: "0 0 10px",
673
+ fontSize: "16px",
674
+ fontWeight: 600,
675
+ lineHeight: "24px",
676
+ },
677
+ riskDesc: {
678
+ margin: "0 0 14px",
679
+ fontSize: "13px",
680
+ lineHeight: "20px",
681
+ color: "var(--dsw-alias-label-secondary)",
682
+ whiteSpace: "pre-wrap",
683
+ },
684
+ riskAckRow: {
685
+ display: "flex",
686
+ alignItems: "flex-start",
687
+ gap: "10px",
688
+ margin: "0 0 16px",
689
+ fontSize: "13px",
690
+ lineHeight: "20px",
691
+ },
692
+ riskAltBox: {
693
+ margin: "0 0 16px",
694
+ padding: "12px",
695
+ borderRadius: "var(--dsw-radius-sm, 8px)",
696
+ border: "1px solid var(--dsw-alias-border-l2, #d0d5dd)",
697
+ background: "var(--dsw-alias-settings-card-fill-secondary, #f8fafc)",
698
+ },
699
+ riskAltTitle: {
700
+ margin: "0 0 6px",
701
+ fontSize: "13px",
702
+ fontWeight: 600,
703
+ lineHeight: "20px",
704
+ },
705
+ riskAltDesc: {
706
+ margin: "0 0 10px",
707
+ fontSize: "12px",
708
+ lineHeight: "18px",
709
+ color: "var(--dsw-alias-label-secondary)",
710
+ },
711
+ riskAltList: {
712
+ display: "flex",
713
+ flexDirection: "column",
714
+ gap: "8px",
715
+ },
716
+ riskAltRow: {
717
+ display: "flex",
718
+ alignItems: "center",
719
+ justifyContent: "space-between",
720
+ gap: "10px",
721
+ },
722
+ riskAltMeta: {
723
+ minWidth: 0,
724
+ flex: 1,
725
+ },
726
+ riskAltSpec: {
727
+ fontSize: "13px",
728
+ fontWeight: 500,
729
+ lineHeight: "18px",
730
+ wordBreak: "break-all",
731
+ },
732
+ riskAltHint: {
733
+ fontSize: "12px",
734
+ lineHeight: "16px",
735
+ color: "var(--dsw-alias-label-secondary)",
736
+ },
737
+ riskFooter: {
738
+ display: "flex",
739
+ justifyContent: "flex-end",
740
+ gap: "8px",
741
+ flexWrap: "wrap",
742
+ },
743
+ updateBox: {
744
+ display: "flex",
745
+ alignItems: "center",
746
+ justifyContent: "space-between",
747
+ gap: "12px",
748
+ flexWrap: "wrap",
749
+ padding: "12px 14px",
750
+ borderRadius: "var(--dsw-radius-sm, 8px)",
751
+ border: "1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary, #4176e6) 35%, transparent)",
752
+ background: "color-mix(in srgb, var(--dsw-alias-state-business-primary, #4176e6) 8%, transparent)",
753
+ color: "var(--dsw-alias-label-primary)",
754
+ fontSize: "13px",
755
+ lineHeight: "20px",
756
+ },
757
+ updateText: {
758
+ display: "flex",
759
+ flexDirection: "column",
760
+ gap: "2px",
761
+ margin: 0,
762
+ flex: "1 1 220px",
583
763
  },
584
764
  pager: {
585
765
  display: "flex",
@@ -708,12 +888,25 @@ window.__ModuleLoader__.load({
708
888
  const err = result.error;
709
889
  return {
710
890
  ok: false,
711
- message: (err && (err.message || err.code)) || fallbackMessage,
891
+ message: (err && (err.message || err.diagnostic || err.code)) || fallbackMessage,
712
892
  };
713
893
  }
714
894
  return result.value;
715
895
  }
716
896
 
897
+ function pickInstallError(res, fallback, lastDetail) {
898
+ const primary = String((res && (res.detail || res.message)) || "").trim();
899
+ const weak =
900
+ !primary ||
901
+ /^install failed$/i.test(primary) ||
902
+ /^安装失败([((]failed[))])?$/i.test(primary);
903
+ if (!weak) return primary;
904
+ const extra = String(lastDetail || "").trim();
905
+ const title = fallback || "安装失败";
906
+ if (extra && extra !== primary) return `${title}\n${extra}`;
907
+ return primary || title;
908
+ }
909
+
717
910
  function Panel(props) {
718
911
  const api = props.api;
719
912
  const t = props.t;
@@ -725,11 +918,18 @@ window.__ModuleLoader__.load({
725
918
  const [category, setCategory] = React.useState("beginner");
726
919
  const [categories, setCategories] = React.useState([]);
727
920
  const [catalogInfo, setCatalogInfo] = React.useState({ version: 0, origin: "bundled" });
921
+ const [selfUpdate, setSelfUpdate] = React.useState(null);
922
+ const [selfUpdateBusy, setSelfUpdateBusy] = React.useState(false);
728
923
  const [query, setQuery] = React.useState(DEFAULT_QUERY);
729
924
  const [source, setSource] = React.useState("all");
730
925
  const [results, setResults] = React.useState([]);
731
926
  const [message, setMessage] = React.useState("");
732
927
  const [error, setError] = React.useState("");
928
+ const [copyHint, setCopyHint] = React.useState("");
929
+ const [riskPrompt, setRiskPrompt] = React.useState(null);
930
+ const [riskOpen, setRiskOpen] = React.useState(false);
931
+ const [riskAck, setRiskAck] = React.useState(false);
932
+ const [riskBusy, setRiskBusy] = React.useState(false);
733
933
  const [busy, setBusy] = React.useState(false);
734
934
  const [resolveInput, setResolveInput] = React.useState("");
735
935
  const [installDetail, setInstallDetail] = React.useState("");
@@ -738,6 +938,7 @@ window.__ModuleLoader__.load({
738
938
  const [page, setPage] = React.useState(1);
739
939
  const jobsRef = React.useRef(jobs);
740
940
  const installedNamesRef = React.useRef(installedNames);
941
+ const installDetailRef = React.useRef("");
741
942
  React.useEffect(() => {
742
943
  jobsRef.current = jobs;
743
944
  writeJobCache(jobs);
@@ -745,6 +946,37 @@ window.__ModuleLoader__.load({
745
946
  React.useEffect(() => {
746
947
  installedNamesRef.current = installedNames;
747
948
  }, [installedNames]);
949
+ React.useEffect(() => {
950
+ installDetailRef.current = installDetail;
951
+ }, [installDetail]);
952
+ React.useEffect(() => {
953
+ setCopyHint("");
954
+ }, [error]);
955
+
956
+ const openRiskPrompt = React.useCallback((res, item) => {
957
+ const rows = Array.isArray(res && res.incompatible) ? res.incompatible : [];
958
+ const first = rows[0];
959
+ if (!(res && res.needsRiskExemption) || !first) return;
960
+ const packageVersion = `${first.name}@${first.version}`;
961
+ const runtimeVersion = first.runtimeVersion || "unknown";
962
+ const recommendations = Array.isArray(res && res.recommendations) ? res.recommendations : [];
963
+ setRiskAck(false);
964
+ setRiskPrompt({
965
+ spec: (res && res.installSpec) || (item && (item.installSpec || item.spec)) || first.name,
966
+ item: item || null,
967
+ packageVersion,
968
+ runtimeVersion,
969
+ incompatible: rows,
970
+ recommendations,
971
+ });
972
+ setRiskOpen(true);
973
+ }, []);
974
+
975
+ const closeRiskPrompt = React.useCallback(() => {
976
+ if (riskBusy) return;
977
+ setRiskOpen(false);
978
+ setRiskAck(false);
979
+ }, [riskBusy]);
748
980
 
749
981
  const refreshInstalled = React.useCallback(async () => {
750
982
  if (!api || typeof api.listInstalled !== "function") return;
@@ -866,6 +1098,10 @@ window.__ModuleLoader__.load({
866
1098
  version: res.catalogVersion || 0,
867
1099
  origin: res.catalogOrigin || "bundled",
868
1100
  });
1101
+ if (res.selfUpdate) setSelfUpdate(res.selfUpdate);
1102
+ if (res.selfUpdated && res.selfUpdate?.latestVersion) {
1103
+ setMessage(format(t("selfUpdateDone"), { version: res.selfUpdate.latestVersion }));
1104
+ }
869
1105
  const list = res.results || [];
870
1106
  setResults(list);
871
1107
  setPage(1);
@@ -996,18 +1232,36 @@ window.__ModuleLoader__.load({
996
1232
  setMessage(res.message || t("installed"));
997
1233
  await refreshInstalled();
998
1234
  } else {
1235
+ const lastDetail =
1236
+ (jobsRef.current[job.requestId] && jobsRef.current[job.requestId].detail) ||
1237
+ installDetailRef.current;
1238
+ const errText = pickInstallError(res, t("installFailed"), lastDetail);
999
1239
  finishJob(job.requestId, {
1000
1240
  status: "failed",
1001
- detail: (res && res.message) || t("installFailed"),
1241
+ detail: errText,
1242
+ });
1243
+ setError(errText);
1244
+ openRiskPrompt(res, {
1245
+ spec: job.spec,
1246
+ installSpec: job.spec,
1247
+ title: job.packageName || job.spec,
1002
1248
  });
1003
- setError((res && res.message) || t("installFailed"));
1004
1249
  }
1005
1250
  } catch (e) {
1006
1251
  if (cancelled) return;
1252
+ const lastDetail =
1253
+ (jobsRef.current[job.requestId] && jobsRef.current[job.requestId].detail) ||
1254
+ installDetailRef.current;
1255
+ const errText = pickInstallError(
1256
+ { message: (e && e.message) || String(e) },
1257
+ t("installFailed"),
1258
+ lastDetail,
1259
+ );
1007
1260
  finishJob(job.requestId, {
1008
1261
  status: "failed",
1009
- detail: (e && e.message) || String(e),
1262
+ detail: errText,
1010
1263
  });
1264
+ setError(errText);
1011
1265
  } finally {
1012
1266
  if (!cancelled) setInstallDetail("");
1013
1267
  }
@@ -1102,11 +1356,20 @@ window.__ModuleLoader__.load({
1102
1356
  setMessage(installed.message || t("installed"));
1103
1357
  await refreshInstalled();
1104
1358
  } else {
1359
+ const lastDetail =
1360
+ (jobsRef.current[requestId] && jobsRef.current[requestId].detail) ||
1361
+ installDetailRef.current;
1362
+ const errText = pickInstallError(installed, t("installFailed"), lastDetail);
1105
1363
  finishJob(requestId, {
1106
1364
  status: "failed",
1107
- detail: (installed && installed.message) || t("inspectFailed"),
1365
+ detail: errText,
1366
+ });
1367
+ setError(errText);
1368
+ openRiskPrompt(installed, {
1369
+ spec: res.spec,
1370
+ installSpec: res.spec,
1371
+ title: (res.inspection && res.inspection.name) || res.spec,
1108
1372
  });
1109
- setError((installed && installed.message) || t("inspectFailed"));
1110
1373
  }
1111
1374
  } else {
1112
1375
  setResults([]);
@@ -1155,23 +1418,79 @@ window.__ModuleLoader__.load({
1155
1418
  await refreshInstalled();
1156
1419
  setMessage(res.message || t("installed"));
1157
1420
  } else {
1421
+ const lastDetail =
1422
+ (jobsRef.current[requestId] && jobsRef.current[requestId].detail) || installDetailRef.current;
1423
+ const errText = pickInstallError(res, t("installFailed"), lastDetail);
1158
1424
  finishJob(requestId, {
1159
1425
  status: "failed",
1160
- detail: (res && res.message) || t("installFailed"),
1426
+ detail: errText,
1161
1427
  });
1162
- setError((res && res.message) || t("installFailed"));
1428
+ setError(errText);
1429
+ openRiskPrompt(res, item);
1163
1430
  }
1164
1431
  } catch (e) {
1432
+ const lastDetail =
1433
+ (jobsRef.current[requestId] && jobsRef.current[requestId].detail) || installDetailRef.current;
1434
+ const errText = pickInstallError(
1435
+ { message: (e && e.message) || String(e) },
1436
+ t("installFailed"),
1437
+ lastDetail,
1438
+ );
1165
1439
  finishJob(requestId, {
1166
1440
  status: "failed",
1167
- detail: (e && e.message) || String(e),
1441
+ detail: errText,
1168
1442
  });
1169
- setError((e && e.message) || String(e));
1443
+ setError(errText);
1170
1444
  } finally {
1171
1445
  setInstallDetail("");
1172
1446
  }
1173
1447
  };
1174
1448
 
1449
+ const confirmRiskInstall = async () => {
1450
+ if (!riskPrompt || !riskAck || !api || typeof api.grantVersionExemption !== "function") return;
1451
+ setRiskBusy(true);
1452
+ setMessage("");
1453
+ try {
1454
+ const granted = await api.grantVersionExemption(
1455
+ riskPrompt.packageVersion,
1456
+ riskPrompt.runtimeVersion,
1457
+ true,
1458
+ );
1459
+ if (!granted || !granted.ok) {
1460
+ setError((granted && granted.message) || t("installFailed"));
1461
+ return;
1462
+ }
1463
+ const item = riskPrompt.item || {
1464
+ spec: riskPrompt.spec,
1465
+ installSpec: riskPrompt.spec,
1466
+ title: riskPrompt.packageVersion,
1467
+ };
1468
+ setRiskOpen(false);
1469
+ setRiskAck(false);
1470
+ setError("");
1471
+ await onInstall(item);
1472
+ } catch (e) {
1473
+ setError((e && e.message) || String(e));
1474
+ } finally {
1475
+ setRiskBusy(false);
1476
+ }
1477
+ };
1478
+
1479
+ const installRecommendedAlt = async (alt) => {
1480
+ if (!alt || riskBusy) return;
1481
+ const item = {
1482
+ spec: alt.installSpec || alt.spec,
1483
+ installSpec: alt.installSpec || alt.spec,
1484
+ title: alt.title || alt.spec,
1485
+ source: alt.source || "catalog",
1486
+ catalogId: alt.catalogId,
1487
+ };
1488
+ setRiskOpen(false);
1489
+ setRiskAck(false);
1490
+ setError("");
1491
+ await onInstall(item);
1492
+ };
1493
+
1175
1494
  const sourceOptions = [
1176
1495
  { value: "all", label: t("sourceAll") },
1177
1496
  { value: "npm", label: t("sourceNpm") },
@@ -1228,6 +1547,10 @@ window.__ModuleLoader__.load({
1228
1547
  version: res.catalogVersion || 0,
1229
1548
  origin: res.catalogOrigin || "bundled",
1230
1549
  });
1550
+ if (res.selfUpdate) setSelfUpdate(res.selfUpdate);
1551
+ if (res.selfUpdated && res.selfUpdate?.latestVersion) {
1552
+ setMessage(format(t("selfUpdateDone"), { version: res.selfUpdate.latestVersion }));
1553
+ }
1231
1554
  setResults(res.results || []);
1232
1555
  setPage(1);
1233
1556
  }
@@ -1238,7 +1561,52 @@ window.__ModuleLoader__.load({
1238
1561
  })();
1239
1562
  };
1240
1563
 
1564
+ const onUpdateSelf = () => {
1565
+ void (async () => {
1566
+ if (!api || typeof api.updateSelf !== "function") return;
1567
+ setSelfUpdateBusy(true);
1568
+ setError("");
1569
+ try {
1570
+ const res = await api.updateSelf({
1571
+ latestVersion: selfUpdate && selfUpdate.latestVersion,
1572
+ });
1573
+ if (res && res.update) setSelfUpdate(res.update);
1574
+ if (res && res.ok && res.updated) {
1575
+ setMessage(format(t("selfUpdateDone"), { version: res.version || res.update?.latestVersion }));
1576
+ setSelfUpdate((prev) =>
1577
+ prev
1578
+ ? {
1579
+ ...prev,
1580
+ updateAvailable: false,
1581
+ currentVersion: res.version || prev.latestVersion,
1582
+ latestVersion: res.version || prev.latestVersion,
1583
+ }
1584
+ : prev,
1585
+ );
1586
+ } else if (res && res.skipped) {
1587
+ setMessage(res.message || t("selfUpdateFileSkip"));
1588
+ } else {
1589
+ setError((res && res.message) || t("installFailed"));
1590
+ }
1591
+ } catch (e) {
1592
+ setError((e && e.message) || String(e));
1593
+ } finally {
1594
+ setSelfUpdateBusy(false);
1595
+ }
1596
+ })();
1597
+ };
1598
+
1599
+ const selfUpdateOriginLabel =
1600
+ selfUpdate && selfUpdate.origin === "gitee"
1601
+ ? t("selfUpdateOriginGitee")
1602
+ : selfUpdate && selfUpdate.origin === "github"
1603
+ ? t("selfUpdateOriginGithub")
1604
+ : t("selfUpdateOriginNpm");
1605
+
1241
1606
  return h(
1607
+ React.Fragment,
1608
+ null,
1609
+ h(
1242
1610
  "div",
1243
1611
  { style: styles.page },
1244
1612
  h(
@@ -1250,6 +1618,36 @@ window.__ModuleLoader__.load({
1250
1618
  h("h1", { style: styles.title }, t("panel")),
1251
1619
  h("p", { style: styles.intro }, t("intro")),
1252
1620
  ),
1621
+ selfUpdate && selfUpdate.updateAvailable
1622
+ ? h(
1623
+ "div",
1624
+ { style: styles.updateBox },
1625
+ h(
1626
+ "div",
1627
+ { style: styles.updateText },
1628
+ h("strong", null, t("selfUpdateTitle")),
1629
+ h(
1630
+ "span",
1631
+ null,
1632
+ format(t("selfUpdateDesc"), {
1633
+ current: selfUpdate.currentVersion || "?",
1634
+ latest: selfUpdate.latestVersion || "?",
1635
+ origin: selfUpdateOriginLabel,
1636
+ }),
1637
+ ),
1638
+ selfUpdate.isFileInstall ? h("span", null, t("selfUpdateFileSkip")) : null,
1639
+ ),
1640
+ h(
1641
+ Btn,
1642
+ {
1643
+ size: "sm",
1644
+ disabled: selfUpdateBusy || busy || !!selfUpdate.isFileInstall,
1645
+ onClick: onUpdateSelf,
1646
+ },
1647
+ selfUpdateBusy ? t("selfUpdateUpdating") : t("selfUpdateNow"),
1648
+ ),
1649
+ )
1650
+ : null,
1253
1651
  h(
1254
1652
  "div",
1255
1653
  { style: styles.card },
@@ -1376,16 +1774,67 @@ window.__ModuleLoader__.load({
1376
1774
  ? h(
1377
1775
  "div",
1378
1776
  { style: styles.errorBox },
1379
- h("span", null, error),
1777
+ h("div", { style: styles.errorText }, error),
1380
1778
  h(
1381
- Btn,
1382
- {
1383
- size: "sm",
1384
- variant: "outline",
1385
- disabled: busy,
1386
- onClick: onSearch,
1387
- },
1388
- t("retry"),
1779
+ "div",
1780
+ { style: styles.errorActions },
1781
+ h(
1782
+ Btn,
1783
+ {
1784
+ size: "sm",
1785
+ variant: "outline",
1786
+ disabled: busy || riskBusy,
1787
+ onClick: async () => {
1788
+ const text = String(error || "");
1789
+ try {
1790
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1791
+ await navigator.clipboard.writeText(text);
1792
+ } else {
1793
+ const ta = document.createElement("textarea");
1794
+ ta.value = text;
1795
+ ta.setAttribute("readonly", "");
1796
+ ta.style.position = "fixed";
1797
+ ta.style.left = "-9999px";
1798
+ document.body.appendChild(ta);
1799
+ ta.select();
1800
+ document.execCommand("copy");
1801
+ document.body.removeChild(ta);
1802
+ }
1803
+ setCopyHint(t("copied"));
1804
+ window.setTimeout(() => setCopyHint(""), 1600);
1805
+ } catch {
1806
+ setCopyHint(t("copyFailed"));
1807
+ window.setTimeout(() => setCopyHint(""), 1600);
1808
+ }
1809
+ },
1810
+ },
1811
+ copyHint || t("copy"),
1812
+ ),
1813
+ riskPrompt
1814
+ ? h(
1815
+ Btn,
1816
+ {
1817
+ size: "sm",
1818
+ variant: "outline",
1819
+ disabled: busy || riskBusy,
1820
+ onClick: () => {
1821
+ setRiskAck(false);
1822
+ setRiskOpen(true);
1823
+ },
1824
+ },
1825
+ t("riskInstall"),
1826
+ )
1827
+ : null,
1828
+ h(
1829
+ Btn,
1830
+ {
1831
+ size: "sm",
1832
+ variant: "outline",
1833
+ disabled: busy || riskBusy,
1834
+ onClick: onSearch,
1835
+ },
1836
+ t("retry"),
1837
+ ),
1389
1838
  ),
1390
1839
  )
1391
1840
  : null,
@@ -1509,6 +1958,110 @@ window.__ModuleLoader__.load({
1509
1958
  )
1510
1959
  : null,
1511
1960
  ),
1961
+ ),
1962
+ riskOpen && riskPrompt
1963
+ ? h(
1964
+ "div",
1965
+ {
1966
+ style: styles.riskOverlay,
1967
+ onClick: (e) => {
1968
+ if (e.target === e.currentTarget) closeRiskPrompt();
1969
+ },
1970
+ },
1971
+ h(
1972
+ "div",
1973
+ {
1974
+ style: styles.riskDialog,
1975
+ role: "dialog",
1976
+ "aria-modal": "true",
1977
+ onClick: (e) => e.stopPropagation(),
1978
+ },
1979
+ h("h2", { style: styles.riskTitle }, t("riskTitle")),
1980
+ h(
1981
+ "p",
1982
+ { style: styles.riskDesc },
1983
+ format(t("riskDesc"), {
1984
+ package: riskPrompt.packageVersion,
1985
+ runtime: riskPrompt.runtimeVersion,
1986
+ }),
1987
+ ),
1988
+ Array.isArray(riskPrompt.recommendations) && riskPrompt.recommendations.length
1989
+ ? h(
1990
+ "div",
1991
+ { style: styles.riskAltBox },
1992
+ h("div", { style: styles.riskAltTitle }, t("riskAltTitle")),
1993
+ h("p", { style: styles.riskAltDesc }, t("riskAltDesc")),
1994
+ h(
1995
+ "div",
1996
+ { style: styles.riskAltList },
1997
+ ...riskPrompt.recommendations.map((alt) =>
1998
+ h(
1999
+ "div",
2000
+ { key: alt.spec, style: styles.riskAltRow },
2001
+ h(
2002
+ "div",
2003
+ { style: styles.riskAltMeta },
2004
+ h("div", { style: styles.riskAltSpec }, alt.title || alt.spec),
2005
+ h(
2006
+ "div",
2007
+ { style: styles.riskAltHint },
2008
+ alt.spec === (alt.title || "")
2009
+ ? alt.reason || ""
2010
+ : alt.spec,
2011
+ ),
2012
+ ),
2013
+ h(
2014
+ Btn,
2015
+ {
2016
+ size: "sm",
2017
+ variant: "outline",
2018
+ disabled: riskBusy,
2019
+ onClick: () => void installRecommendedAlt(alt),
2020
+ },
2021
+ t("riskAltInstall"),
2022
+ ),
2023
+ ),
2024
+ ),
2025
+ ),
2026
+ )
2027
+ : null,
2028
+ h(
2029
+ "label",
2030
+ { style: styles.riskAckRow },
2031
+ h("input", {
2032
+ type: "checkbox",
2033
+ checked: riskAck,
2034
+ disabled: riskBusy,
2035
+ onChange: (e) => setRiskAck(!!e.target.checked),
2036
+ }),
2037
+ h("span", null, t("riskAck")),
2038
+ ),
2039
+ h(
2040
+ "div",
2041
+ { style: styles.riskFooter },
2042
+ h(
2043
+ Btn,
2044
+ {
2045
+ size: "sm",
2046
+ variant: "outline",
2047
+ disabled: riskBusy,
2048
+ onClick: closeRiskPrompt,
2049
+ },
2050
+ t("riskCancel"),
2051
+ ),
2052
+ h(
2053
+ Btn,
2054
+ {
2055
+ size: "sm",
2056
+ disabled: riskBusy || !riskAck,
2057
+ onClick: () => void confirmRiskInstall(),
2058
+ },
2059
+ riskBusy ? t("riskWorking") : t("riskConfirm"),
2060
+ ),
2061
+ ),
2062
+ ),
2063
+ )
2064
+ : null,
1512
2065
  );
1513
2066
  }
1514
2067
 
@@ -1517,11 +2070,19 @@ window.__ModuleLoader__.load({
1517
2070
  const SEARCH_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/search:result");
1518
2071
  const CATALOG_OPTIONS_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/listCatalog:options");
1519
2072
  const CATALOG_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/listCatalog:result");
2073
+ const SELF_UPDATE_OPTIONS = strictJson("dsh-plugin-discovery#pluginDiscovery/checkSelfUpdate:options");
2074
+ const SELF_UPDATE_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/checkSelfUpdate:result");
2075
+ const SELF_UPDATE_APPLY_OPTIONS = strictJson("dsh-plugin-discovery#pluginDiscovery/updateSelf:options");
2076
+ const SELF_UPDATE_APPLY_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/updateSelf:result");
1520
2077
  const INPUT_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/resolve:input");
1521
2078
  const RESOLVE_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/resolve:result");
1522
2079
  const SPEC_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/installPlugin:spec");
1523
2080
  const INSTALL_OPTIONS_CODEC = strictJson("dsh-plugin-discovery#pluginDiscovery/installPlugin:options");
1524
2081
  const INSTALL_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/installPlugin:result");
2082
+ const GRANT_PACKAGE = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:packageVersion");
2083
+ const GRANT_RUNTIME = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:runtimeVersion");
2084
+ const GRANT_ACCEPT = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:acceptRisk");
2085
+ const GRANT_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/grantVersionExemption:result");
1525
2086
  const LIST_INSTALLED_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/listInstalled:result");
1526
2087
  const WAIT_REQUEST_ID = strictJson("dsh-plugin-discovery#pluginDiscovery/waitInstall:requestId");
1527
2088
  const WAIT_RESULT = strictJson("dsh-plugin-discovery#pluginDiscovery/waitInstall:result");
@@ -1564,6 +2125,40 @@ window.__ModuleLoader__.load({
1564
2125
  ],
1565
2126
  result: CATALOG_RESULT,
1566
2127
  },
2128
+ {
2129
+ id: "dsh-plugin-discovery#pluginDiscovery/checkSelfUpdate",
2130
+ service: "pluginDiscovery",
2131
+ namespace: "pluginDiscovery",
2132
+ method: "checkSelfUpdate",
2133
+ invocation: { kind: "direct" },
2134
+ parameters: [
2135
+ {
2136
+ name: "options",
2137
+ wire: "options",
2138
+ source: "json",
2139
+ acceptsUndefined: true,
2140
+ codec: SELF_UPDATE_OPTIONS,
2141
+ },
2142
+ ],
2143
+ result: SELF_UPDATE_RESULT,
2144
+ },
2145
+ {
2146
+ id: "dsh-plugin-discovery#pluginDiscovery/updateSelf",
2147
+ service: "pluginDiscovery",
2148
+ namespace: "pluginDiscovery",
2149
+ method: "updateSelf",
2150
+ invocation: { kind: "direct" },
2151
+ parameters: [
2152
+ {
2153
+ name: "options",
2154
+ wire: "options",
2155
+ source: "json",
2156
+ acceptsUndefined: true,
2157
+ codec: SELF_UPDATE_APPLY_OPTIONS,
2158
+ },
2159
+ ],
2160
+ result: SELF_UPDATE_APPLY_RESULT,
2161
+ },
1567
2162
  {
1568
2163
  id: "dsh-plugin-discovery#pluginDiscovery/resolve",
1569
2164
  service: "pluginDiscovery",
@@ -1591,6 +2186,19 @@ window.__ModuleLoader__.load({
1591
2186
  ],
1592
2187
  result: INSTALL_RESULT,
1593
2188
  },
2189
+ {
2190
+ id: "dsh-plugin-discovery#pluginDiscovery/grantVersionExemption",
2191
+ service: "pluginDiscovery",
2192
+ namespace: "pluginDiscovery",
2193
+ method: "grantVersionExemption",
2194
+ invocation: { kind: "direct" },
2195
+ parameters: [
2196
+ { name: "packageVersion", wire: "packageVersion", source: "json", codec: GRANT_PACKAGE },
2197
+ { name: "runtimeVersion", wire: "runtimeVersion", source: "json", codec: GRANT_RUNTIME },
2198
+ { name: "acceptRisk", wire: "acceptRisk", source: "json", codec: GRANT_ACCEPT },
2199
+ ],
2200
+ result: GRANT_RESULT,
2201
+ },
1594
2202
  {
1595
2203
  id: "dsh-plugin-discovery#pluginDiscovery/listInstalled",
1596
2204
  service: "pluginDiscovery",
@@ -1657,6 +2265,35 @@ window.__ModuleLoader__.load({
1657
2265
  }
1658
2266
  return value;
1659
2267
  },
2268
+ checkSelfUpdate: async (opts) => {
2269
+ if (mountError) return { ok: false, message: mountError };
2270
+ const remote = remoteNs();
2271
+ if (!remote || typeof remote.checkSelfUpdate !== "function") {
2272
+ return { ok: false, message: "remote.pluginDiscovery.checkSelfUpdate unavailable" };
2273
+ }
2274
+ const wire = await remote.checkSelfUpdate(opts || {});
2275
+ const value = unwrapRemote(wire, "checkSelfUpdate failed");
2276
+ if (wire && wire.ok === false) return { ok: false, message: value.message, update: value.update };
2277
+ return value;
2278
+ },
2279
+ updateSelf: async (opts) => {
2280
+ if (mountError) return { ok: false, message: mountError };
2281
+ const remote = remoteNs();
2282
+ if (!remote || typeof remote.updateSelf !== "function") {
2283
+ return { ok: false, message: "remote.pluginDiscovery.updateSelf unavailable" };
2284
+ }
2285
+ const wire = await remote.updateSelf(opts || {});
2286
+ const value = unwrapRemote(wire, "updateSelf failed");
2287
+ if (wire && wire.ok === false) {
2288
+ return {
2289
+ ok: false,
2290
+ message: value.message,
2291
+ skipped: !!(value && value.skipped),
2292
+ update: value.update,
2293
+ };
2294
+ }
2295
+ return value;
2296
+ },
1660
2297
  resolve: async (input) => {
1661
2298
  if (mountError) return { ok: false, message: mountError };
1662
2299
  const remote = remoteNs();
@@ -1683,10 +2320,27 @@ window.__ModuleLoader__.load({
1683
2320
  packageName: value && value.packageName,
1684
2321
  requestId: value && value.requestId,
1685
2322
  message: value && value.message,
2323
+ detail: value && value.detail,
2324
+ code: value && value.code,
2325
+ incompatible: value && value.incompatible,
2326
+ needsRiskExemption: !!(value && value.needsRiskExemption),
2327
+ recommendations: value && value.recommendations,
2328
+ installSpec: (value && value.installSpec) || spec,
1686
2329
  };
1687
2330
  }
1688
2331
  return value;
1689
2332
  },
2333
+ grantVersionExemption: async (packageVersion, runtimeVersion, acceptRisk) => {
2334
+ if (mountError) return { ok: false, message: mountError };
2335
+ const remote = remoteNs();
2336
+ if (!remote || typeof remote.grantVersionExemption !== "function") {
2337
+ return { ok: false, message: "remote.pluginDiscovery.grantVersionExemption unavailable" };
2338
+ }
2339
+ const wire = await remote.grantVersionExemption(packageVersion, runtimeVersion, acceptRisk);
2340
+ const value = unwrapRemote(wire, "grantVersionExemption failed");
2341
+ if (wire && wire.ok === false) return { ok: false, message: value.message };
2342
+ return value;
2343
+ },
1690
2344
  listInstalled: async () => {
1691
2345
  if (mountError) return { ok: false, names: [] };
1692
2346
  const remote = remoteNs();
@@ -1706,7 +2360,20 @@ window.__ModuleLoader__.load({
1706
2360
  }
1707
2361
  const wire = await remote.waitInstall(requestId);
1708
2362
  const value = unwrapRemote(wire, "waitInstall failed");
1709
- if (wire && wire.ok === false) return { ok: false, message: value.message || "waitInstall failed" };
2363
+ if (wire && wire.ok === false) {
2364
+ return {
2365
+ ok: false,
2366
+ message: (value && value.message) || "waitInstall failed",
2367
+ detail: value && value.detail,
2368
+ code: value && value.code,
2369
+ incompatible: value && value.incompatible,
2370
+ needsRiskExemption: !!(value && value.needsRiskExemption),
2371
+ recommendations: value && value.recommendations,
2372
+ installSpec: value && value.installSpec,
2373
+ packageName: value && value.packageName,
2374
+ pending: !!(value && value.pending),
2375
+ };
2376
+ }
1710
2377
  return value;
1711
2378
  },
1712
2379
  subscribePluginChanged: (listener) => {