@max-null/dsh-plugin-center 0.2.3 → 0.2.5

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
@@ -39,35 +39,35 @@ var CSS = `
39
39
  .pc-head { display: flex; align-items: center; gap: 10px; }
40
40
  .pc-head .pc-sub { margin-top: 0; }
41
41
  .pc-head .pc-btn { flex: none; }
42
- .pc-count { display: inline-block; min-width: 16px; padding: 0 5px; margin-left: 6px; border-radius: 8px; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); font-size: 12px; text-align: center; }
42
+ .pc-count { display: inline-block; min-width: 16px; padding: 0 5px; margin-left: 6px; border-radius: 999px; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 500; line-height: 17px; text-align: center; }
43
43
  .pc-tab.active .pc-count { background: var(--dsw-alias-state-business-tertiary); color: var(--dsw-alias-state-business-primary); }
44
- .pc-tabs { display: flex; gap: 4px; margin: 16px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l2); }
45
- .pc-tab { padding: 8px 14px; font-size: 14px; cursor: pointer; background: none; border: none; font-family: inherit; color: var(--dsw-alias-label-tertiary); border-bottom: 2px solid transparent; margin-bottom: -1px; }
46
- .pc-tab:hover { color: var(--dsw-alias-label-secondary); }
47
- .pc-tab.active { color: var(--dsw-alias-label-primary); border-bottom-color: var(--dsw-alias-state-business-primary); }
44
+ .pc-tabs { display: flex; gap: 22px; align-items: flex-end; margin: 16px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l2); }
45
+ .pc-tab { padding: 7px 1px 9px; font-size: 13px; line-height: 20px; cursor: pointer; background: none; border: none; font-family: inherit; color: var(--dsw-alias-label-tertiary); position: relative; }
46
+ .pc-tab:hover { color: var(--dsw-alias-label-primary); }
47
+ .pc-tab.active { color: var(--dsw-alias-label-primary); }
48
+ .pc-tab.active::after { content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; border-radius: 2px 2px 0 0; background: var(--dsw-alias-label-primary); }
49
+ .pc-tab:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; border-radius: 2px; }
48
50
 
49
- .pc-card { border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; padding: 14px 16px; margin-bottom: 10px; background: var(--dsw-alias-bg-layer-1); min-width: 0; transition: border-color .15s; display: flex; flex-direction: column; }
50
- .pc-card:hover { border-color: var(--dsw-alias-border-l3); }
51
- .pc-name { font-weight: 500; color: var(--dsw-alias-label-primary); }
51
+ .pc-card { border: 1px solid var(--dsw-alias-border-l2); border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; background: var(--dsw-alias-bg-layer-3); min-width: 0; transition: border-color .16s, background .16s; display: flex; flex-direction: column; }
52
+ .pc-card:hover { border-color: var(--dsw-alias-label-dimmed); }
53
+ .pc-name { font-size: 15px; font-weight: 600; line-height: 1.4; color: var(--dsw-alias-label-primary); }
52
54
  .pc-ver { color: var(--dsw-alias-label-caption); font-size: 12px; }
53
- .pc-desc { color: var(--dsw-alias-label-secondary); font-size: 13px; margin-top: 6px; word-break: break-word; overflow-wrap: break-word; }
55
+ .pc-desc { color: var(--dsw-alias-label-tertiary); font-size: 13px; margin-top: 6px; word-break: break-word; overflow-wrap: break-word; }
54
56
  .pc-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
55
57
  .pc-spacer { flex: 1; }
56
58
  .pc-meta { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: auto; padding-top: 10px; }
57
59
 
58
- .pc-badge { display: inline-flex; align-items: center; height: 20px; padding: 0 8px; border-radius: 6px; font-size: 12px; line-height: 1; }
60
+ .pc-badge { display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 500; line-height: 17px; white-space: nowrap; }
59
61
  .pc-badge.official { background: var(--dsw-alias-state-business-tertiary); color: var(--dsw-alias-state-business-primary); }
60
62
  .pc-badge.installed { background: var(--dsw-alias-state-warn-tertiary); color: var(--dsw-alias-state-warn-primary); }
61
63
  .pc-badge.local, .pc-badge.builtin { background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-tertiary); }
62
- .pc-tag { display: inline-flex; align-items: center; height: 20px; padding: 0 8px; border-radius: 6px; font-size: 12px; line-height: 1; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); }
64
+ .pc-tag { display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 500; line-height: 17px; white-space: nowrap; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-secondary); }
63
65
  .pc-tag.danger { background: var(--dsw-alias-interactive-bg-hover-danger); color: var(--dsw-alias-state-error-primary); }
64
- .pc-switch { position: relative; flex: none; width: 36px; height: 20px; border-radius: 10px; border: none; background: rgba(0,0,0,.22); cursor: pointer; transition: background .2s ease; padding: 0; }
65
- .pc-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transition: left .2s ease; }
66
- .pc-switch.on { background: var(--dsw-alias-state-success-primary); }
67
- .pc-switch.on::after { left: 18px; }
66
+ .pc-switch { position: relative; flex: none; width: 40px; height: 22px; border-radius: 11px; border: none; background: var(--dsw-alias-border-l4, rgba(0,0,0,.16)); cursor: pointer; transition: background .15s ease; padding: 0; }
67
+ .pc-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left .15s ease; }
68
+ .pc-switch.on { background: var(--dsw-alias-state-business-primary, #4FC3F7); }
69
+ .pc-switch.on::after { left: 21px; }
68
70
  .pc-switch:disabled { opacity: .6; cursor: default; }
69
- body[data-ds-dark-theme] .pc-switch { background: rgba(255,255,255,.28); }
70
- body[data-ds-dark-theme] .pc-switch.on { background: var(--dsw-alias-state-success-primary); }
71
71
 
72
72
  .pc-toolbar { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 4px 0; }
73
73
  /* \u7B5B\u9009\u533A\u7EB5\u5411\u5BB9\u5668\uFF1AAI \u63A8\u8350\u884C / \u4E3B\u7B5B\u9009\u884C / \u5206\u7C7B\u884C\u4E09\u6BB5\uFF0C\u95F4\u8DDD\u7EDF\u4E00\u3002 */
@@ -81,27 +81,27 @@ body[data-ds-dark-theme] .pc-switch.on { background: var(--dsw-alias-state-succe
81
81
  .pc-limit { margin: 2px 0 8px; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-caption); }
82
82
  .pc-ai-row { display: flex; gap: 8px; align-items: center; }
83
83
  .pc-ai-row .pc-btn { height: 28px; }
84
- .pc-chip { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; transition: background .15s ease, color .15s ease, border-color .15s ease; }
84
+ .pc-chip { height: 28px; padding: 0 12px; border-radius: 14px; border: 1px solid var(--dsw-alias-border-l2); background: transparent; color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; cursor: pointer; font-family: inherit; transition: background .15s ease, color .15s ease, border-color .15s ease; }
85
85
  .pc-chip:hover { background: var(--dsw-alias-interactive-bg-hover); }
86
- .pc-chip.active { background: var(--dsw-alias-state-business-primary); border-color: var(--dsw-alias-state-business-primary); color: var(--dsw-alias-bg-base); }
86
+ .pc-chip.active { background: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3); }
87
87
 
88
- .pc-btn { height: 26px; padding: 0 10px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-primary); font-size: 12px; cursor: pointer; font-family: inherit; }
88
+ .pc-btn { padding: 5px 14px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; cursor: pointer; font-family: inherit; }
89
89
  .pc-btn:hover { background: var(--dsw-alias-interactive-bg-hover); }
90
- .pc-btn.primary { border: none; background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-bg-base); }
90
+ .pc-btn.primary { border: none; background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }
91
91
  .pc-btn.primary:hover { background: var(--dsw-alias-button-primary-hover); }
92
92
  .pc-btn:disabled { opacity: 0.5; cursor: default; }
93
93
 
94
- .pc-search { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font-size: 13px; outline: none; width: 200px; font-family: inherit; }
95
- .pc-search:focus { border-color: var(--dsw-alias-state-business-primary); }
96
- .pc-search::placeholder { color: var(--dsw-alias-label-caption); }
94
+ .pc-search { height: 34px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; outline: none; width: 200px; font-family: inherit; }
95
+ .pc-search:focus { border-color: var(--dsw-alias-brand-primary); }
96
+ .pc-search::placeholder { color: var(--dsw-alias-label-dimmed); }
97
97
  /* \u5B9A\u5236 select\uFF1A\u5265\u79BB\u6D4F\u89C8\u5668\u539F\u751F\u5916\u89C2\uFF08\u9AD8\u5EA6/\u5185\u8FB9\u8DDD/padding \u5DEE\u5F02\u662F\u8DE8\u63A7\u4EF6\u4E0D\u9F50\u7684
98
- \u4E3B\u56E0\uFF09\uFF0C\u7528\u80CC\u666F chevron \u66FF\u4EE3\u7CFB\u7EDF\u7BAD\u5934\uFF0C\u4E0E chip/\u641C\u7D22\u6846\u540C\u9AD8\u540C\u5706\u89D2\u3002 */
99
- .pc-select { height: 28px; padding: 0 26px 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background-color: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); font-size: 13px; cursor: pointer; font-family: inherit; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23777777' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-size: 10px 10px; }
98
+ \u4E3B\u56E0\uFF09\uFF0C\u7528\u80CC\u666F chevron \u66FF\u4EE3\u7CFB\u7EDF\u7BAD\u5934\uFF0C\u4E0E\u8F93\u5165\u6846\u540C\u9AD8\u540C\u5706\u89D2\u3002 */
99
+ .pc-select { height: 34px; padding: 0 26px 0 12px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background-color: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-secondary); font-size: 13px; line-height: 1.5; cursor: pointer; font-family: inherit; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23777777' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-size: 10px 10px; }
100
100
  .pc-select:hover { background-color: var(--dsw-alias-interactive-bg-hover); }
101
- .pc-select:focus-visible { outline: none; border-color: var(--dsw-alias-state-business-primary); }
101
+ .pc-select:focus-visible { outline: none; border-color: var(--dsw-alias-brand-primary); }
102
102
  body[data-ds-dark-theme] .pc-select { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10' fill='none' stroke='%23b0b0b0' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 3.5l3 3 3-3'/%3E%3C/svg%3E"); }
103
- .pc-iconbtn { width: 28px; height: 28px; padding: 0; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-secondary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
104
- .pc-iconbtn:hover { background: var(--dsw-alias-interactive-bg-hover); }
103
+ .pc-iconbtn { width: 28px; height: 28px; padding: 6px; border-radius: 7px; border: none; background: transparent; color: var(--dsw-alias-label-tertiary); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
104
+ .pc-iconbtn:hover { background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); }
105
105
 
106
106
  .pc-grid { display: grid; gap: 12px; }
107
107
  .pc-grid.double { grid-template-columns: 1fr 1fr; }
@@ -111,6 +111,7 @@ body[data-ds-dark-theme] .pc-select { background-image: url("data:image/svg+xml,
111
111
  .pc-panel { width: 760px; max-width: 94vw; max-height: 86vh; background: var(--dsw-alias-bg-base); border-radius: 12px; box-shadow: 0 24px 64px rgba(0,0,0,.24); display: flex; flex-direction: column; overflow: hidden; }
112
112
  .pc-panel-head { flex: none; display: flex; align-items: center; padding: 20px 28px 0; }
113
113
  .pc-panel-body { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 8px 28px 20px; }
114
+ .pc-panel-footer { flex: none; display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 14px 28px; border-top: 1px solid var(--dsw-alias-border-l2); }
114
115
  .pc-scroll { flex: 1; min-height: 0; overflow: auto; }
115
116
  .pc-close { background: none; border: none; color: var(--dsw-alias-label-tertiary); font-size: 20px; cursor: pointer; padding: 4px 8px; border-radius: 6px; font-family: inherit; }
116
117
  .pc-close:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
@@ -261,6 +262,28 @@ function usePendingVersion() {
261
262
  }, []);
262
263
  return v;
263
264
  }
265
+ var doneUpdatesStore = [];
266
+ var doneUpdatesVersion = 0;
267
+ var doneUpdatesListeners = /* @__PURE__ */ new Set();
268
+ function markDoneUpdate(entry) {
269
+ if (doneUpdatesStore.some((d) => d.name === entry.name)) return;
270
+ doneUpdatesStore.push(entry);
271
+ doneUpdatesVersion++;
272
+ doneUpdatesListeners.forEach((l) => l());
273
+ }
274
+ function useDoneUpdatesVersion() {
275
+ const [v, setV] = (0, import_react.useState)(0);
276
+ (0, import_react.useEffect)(() => {
277
+ const l = () => {
278
+ setV((x) => x + 1);
279
+ };
280
+ doneUpdatesListeners.add(l);
281
+ return () => {
282
+ doneUpdatesListeners.delete(l);
283
+ };
284
+ }, []);
285
+ return v;
286
+ }
264
287
  var installedListeners = /* @__PURE__ */ new Set();
265
288
  function useInstalledVersion() {
266
289
  const [v, setV] = (0, import_react.useState)(0);
@@ -384,10 +407,21 @@ var STRINGS = {
384
407
  installQueued: "\u5DF2\u53D1\u8D77\u5B89\u88C5 {n}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
385
408
  installFailed: "\u5B89\u88C5\u5931\u8D25\uFF1A{e}",
386
409
  installNotApplied: "\u5B89\u88C5\u672A\u751F\u6548",
387
- updatedOne: "\u5DF2\u66F4\u65B0 {n}\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
410
+ updatedOne: "\u5DF2\u66F4\u65B0 {n}\uFF0C\u91CD\u542F\u540E\u751F\u6548",
388
411
  updateFailed: "\u66F4\u65B0\u5931\u8D25\uFF1A{e}",
389
412
  updateNotApplied: "\u66F4\u65B0\u672A\u751F\u6548",
390
- updatedMany: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F dsh web \u540E\u751F\u6548",
413
+ updatedMany: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F\u540E\u751F\u6548",
414
+ commandTitle: "\u5728\u7EC8\u7AEF\u6267\u884C\u4EE5\u4E0B\u547D\u4EE4",
415
+ commandHint: "\u5F53\u524D\u5E94\u7528\u6B63\u5728\u8FD0\u884C\uFF0C\u88AB\u9501\u5B9A\u7684\u6587\u4EF6\u65E0\u6CD5\u5728\u5E94\u7528\u5185\u66FF\u6362\u3002\u8BF7\u5148\u5173\u95ED\u5E94\u7528\uFF0C\u518D\u5728\u7EC8\u7AEF\u6267\u884C\uFF1A",
416
+ commandCopy: "\u590D\u5236\u547D\u4EE4",
417
+ commandCopied: "\u5DF2\u590D\u5236",
418
+ updateRestartNow: "\u5DF2\u4E0B\u8F7D {n} \u4E2A\u66F4\u65B0\uFF0C\u91CD\u542F\u601D\u7075\u540E\u81EA\u52A8\u5B89\u88C5\u3002\u7ACB\u5373\u91CD\u542F\uFF1F",
419
+ updateRestartBusy: "\u6709 {n} \u4E2A\u4F1A\u8BDD\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u672A\u6267\u884C\u91CD\u542F\uFF1B\u66F4\u65B0\u5DF2\u51C6\u5907\u597D\uFF0C\u7A0D\u540E\u624B\u52A8\u91CD\u542F\u5373\u53EF",
420
+ updatedPendingTag: "\u5DF2\u66F4\u65B0\u5F85\u91CD\u542F",
421
+ restartUnavailable: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u91CD\u542F\uFF0C\u8BF7\u624B\u52A8\u91CD\u542F\u5E94\u7528",
422
+ restartAskTitle: "\u9700\u8981\u91CD\u542F\u751F\u6548",
423
+ restartAskBody: "\u5DF2\u66F4\u65B0 {n} \u4E2A\u63D2\u4EF6\uFF0C\u91CD\u542F\u601D\u7075\u540E\u751F\u6548\uFF08\u6709\u8FDB\u884C\u4E2D\u7684\u4F1A\u8BDD\u65F6\u4F1A\u5148\u68C0\u67E5\uFF09",
424
+ restartNowBtn: "\u7ACB\u5373\u91CD\u542F",
391
425
  updateSummary: "\u66F4\u65B0\u5B8C\u6210\uFF1A\u6210\u529F {a}\uFF0C\u5931\u8D25 {b}\uFF08{c}\uFF09",
392
426
  whatsNewTitle: "\u63D2\u4EF6\u66F4\u65B0",
393
427
  whatsNewSub: "{n} \u4E2A\u63D2\u4EF6\u6709\u65B0\u7248\u672C",
@@ -458,10 +492,21 @@ var STRINGS = {
458
492
  installQueued: "Install of {n} started; restart dsh web to take effect",
459
493
  installFailed: "Install failed: {e}",
460
494
  installNotApplied: "Install did not take effect",
461
- updatedOne: "Updated {n}; restart dsh web to take effect",
495
+ updatedOne: "Updated {n}; restart to take effect",
462
496
  updateFailed: "Update failed: {e}",
463
497
  updateNotApplied: "Update did not take effect",
464
- updatedMany: "Updated {n} plugins; restart dsh web to take effect",
498
+ updatedMany: "Updated {n} plugin(s); restart to take effect",
499
+ commandTitle: "Run this command in a terminal",
500
+ commandHint: "The app is running and locked files cannot be replaced in-place. Close the app first, then run:",
501
+ commandCopy: "Copy command",
502
+ commandCopied: "Copied",
503
+ updateRestartNow: "{n} update(s) downloaded; auto-installs after restarting SSiD. Restart now?",
504
+ updateRestartBusy: "{n} session(s) still in progress \u2014 no restart; updates ready, restart manually later",
505
+ updatedPendingTag: "Updated \u2014 restart pending",
506
+ restartUnavailable: "Auto-restart unavailable here; please restart manually",
507
+ restartAskTitle: "Restart required",
508
+ restartAskBody: "{n} plugin(s) updated; takes effect after restarting SSiD (active sessions are checked first)",
509
+ restartNowBtn: "Restart now",
465
510
  updateSummary: "Update done: {a} succeeded, {b} failed ({c})",
466
511
  whatsNewTitle: "Plugin updates",
467
512
  whatsNewSub: "{n} plugins have new versions",
@@ -759,28 +804,45 @@ function MarketView({ category, single, source, search, onCount }) {
759
804
  ] }) })
760
805
  ] });
761
806
  }
762
- function UpdatesView({ updates, refresh, updateOne, busy }) {
807
+ function UpdatesView({ updates, refresh, updateOne, busy, doneUpdates, onDoneClick }) {
763
808
  const t = useT();
764
809
  useUpdatingVersion();
765
810
  if (updates === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("checkingUpdates") });
766
- if (updates.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
811
+ const doneOnly = doneUpdates.filter((d) => !(updates ?? []).some((u) => u.name === d.name));
812
+ if (updates.length === 0 && doneOnly.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
767
813
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "pc-sub", children: t("noUpdates") }),
768
814
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: refresh, children: t("recheck") })
769
815
  ] });
770
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: updates.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
771
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
772
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: u.name }),
773
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: u.fromVersion }),
774
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" }),
775
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: u.toVersion }),
776
- u.compat === "incompatible" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag danger", children: t("incompat") }),
816
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
817
+ updates.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-card", children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: u.name }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: u.fromVersion }),
821
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" }),
822
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: u.toVersion }),
823
+ u.compat === "incompatible" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag danger", children: t("incompat") }),
824
+ pendingInstall.has(u.name) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("pendingRestart") }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
826
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null || pendingInstall.has(u.name), onClick: () => {
827
+ updateOne(u.name, u.toVersion);
828
+ }, children: busy === u.name || busy === "__all__" || updatingPlugins.has(u.name) ? t("updating") : t("update") })
829
+ ] }),
830
+ u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
831
+ ] }, u.name)),
832
+ doneOnly.map((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
833
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: d.name }),
834
+ d.fromVersion !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
835
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: d.fromVersion }),
836
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" })
837
+ ] }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: d.toVersion }),
839
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-tag", children: t("updatedPendingTag") }),
777
840
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
778
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy !== null, onClick: () => {
779
- updateOne(u.name, u.toVersion);
780
- }, children: busy === u.name || busy === "__all__" || updatingPlugins.has(u.name) ? t("updating") : t("update") })
781
- ] }),
782
- u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
783
- ] }, u.name)) });
841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", onClick: () => {
842
+ onDoneClick(d.name);
843
+ }, children: t("updatedPendingTag") })
844
+ ] }) }, d.name))
845
+ ] });
784
846
  }
785
847
  function DiagnoseView() {
786
848
  const t = useT();
@@ -872,6 +934,63 @@ function DiagnoseView() {
872
934
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "pc-desc", style: { whiteSpace: "pre-wrap", fontSize: 11 }, children: report.pnpmLogTail === "" ? "\u2014" : report.pnpmLogTail })
873
935
  ] });
874
936
  }
937
+ function CommandDialog({ command, copied, onCopy, onClose }) {
938
+ const t = useT();
939
+ return (0, import_react_dom.createPortal)(
940
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "fixed", inset: 0, background: "rgba(0,0,0,.55)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { width: "min(560px, 92vw)", background: "var(--dsw-alias-bg-layer-3)", border: "1px solid var(--dsw-alias-border-l2)", borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
941
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #d8e0ea)" }, children: t("commandTitle") }),
942
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #67748a)", lineHeight: 1.5 }, children: t("commandHint") }),
943
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
944
+ "textarea",
945
+ {
946
+ readOnly: true,
947
+ value: command,
948
+ rows: Math.min(8, command.split("\n").length + 1),
949
+ style: { width: "100%", boxSizing: "border-box", background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.12))", color: "var(--dsw-alias-label-primary, #d8e0ea)", border: "1px solid var(--dsw-alias-border-l2, #1e2836)", borderRadius: 6, padding: "8px 10px", fontSize: 12, fontFamily: "monospace", resize: "vertical" }
950
+ }
951
+ ),
952
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
953
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-btn", onClick: () => {
954
+ void navigator.clipboard.writeText(command).then(onCopy).catch(() => {
955
+ });
956
+ }, children: copied ? t("commandCopied") : t("commandCopy") }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-btn primary", onClick: onClose, children: t("close") })
958
+ ] })
959
+ ] }) }),
960
+ document.body
961
+ );
962
+ }
963
+ function RestartDialog({ count, onRestart, onClose }) {
964
+ const t = useT();
965
+ return (0, import_react_dom.createPortal)(
966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "fixed", inset: 0, background: "rgba(0,0,0,.55)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 9999 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { width: "min(420px, 92vw)", background: "var(--dsw-alias-bg-layer-3)", border: "1px solid var(--dsw-alias-border-l2)", borderRadius: 12, padding: 14, display: "flex", flexDirection: "column", gap: 10 }, children: [
967
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 13, fontWeight: 600, color: "var(--dsw-alias-label-primary, #d8e0ea)" }, children: t("restartAskTitle") }),
968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, color: "var(--dsw-alias-label-secondary, #67748a)", lineHeight: 1.5 }, children: t("restartAskBody", { n: count }) }),
969
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
970
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-btn", onClick: onClose, children: t("later") }),
971
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
972
+ "button",
973
+ {
974
+ type: "button",
975
+ style: {
976
+ padding: "3px 12px",
977
+ fontSize: 11.5,
978
+ border: "none",
979
+ borderRadius: 6,
980
+ cursor: "pointer",
981
+ fontWeight: 600,
982
+ background: "var(--dsw-alias-button-primary-fill)",
983
+ color: "var(--dsw-alias-label-primary-foreground)"
984
+ },
985
+ onClick: onRestart,
986
+ children: t("restartNowBtn")
987
+ }
988
+ )
989
+ ] })
990
+ ] }) }),
991
+ document.body
992
+ );
993
+ }
875
994
  function CenterPanel({ variant = "section" }) {
876
995
  const t = useT();
877
996
  const [view, setView] = (0, import_react.useState)("installed");
@@ -887,6 +1006,28 @@ function CenterPanel({ variant = "section" }) {
887
1006
  const [busyUpdate, setBusyUpdate] = (0, import_react.useState)(null);
888
1007
  const [checking, setChecking] = (0, import_react.useState)(false);
889
1008
  const [togglingId, setTogglingId] = (0, import_react.useState)(null);
1009
+ const [commandDialog, setCommandDialog] = (0, import_react.useState)(null);
1010
+ const [copied, setCopied] = (0, import_react.useState)(false);
1011
+ const readyPending = (0, import_react.useRef)([]);
1012
+ const inUpdateAll = (0, import_react.useRef)(false);
1013
+ useDoneUpdatesVersion();
1014
+ const [restartAsk, setRestartAsk] = (0, import_react.useState)(0);
1015
+ const runRestartNow = (n) => {
1016
+ void fetch("/ssid/api/sessionRoot.restart", { method: "POST", headers: { "content-type": "application/json" }, body: "{}" }).then((res) => res.json()).then((body2) => {
1017
+ if (body2.value?.code === "busy") {
1018
+ showToast(t("updateRestartBusy", { n: body2.value.activeSessions ?? 0 }), "error", 8e3);
1019
+ }
1020
+ }).catch(() => showToast(t("restartUnavailable"), "error", 8e3));
1021
+ };
1022
+ const askRestart = (n) => {
1023
+ setRestartAsk(n);
1024
+ };
1025
+ const flushReady = () => {
1026
+ const n = readyPending.current.length;
1027
+ if (n === 0) return;
1028
+ readyPending.current = [];
1029
+ askRestart(n);
1030
+ };
890
1031
  const handleToggle = (p) => {
891
1032
  if (togglingId !== null) return;
892
1033
  const id = p.entryId;
@@ -989,11 +1130,29 @@ function CenterPanel({ variant = "section" }) {
989
1130
  setUpdating(name, true);
990
1131
  void rpc("update", { name, version }).then(
991
1132
  (v) => {
992
- const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
993
- if (v !== true && durationMs === void 0) throw new Error(t("updateNotApplied"));
1133
+ const value = typeof v === "object" && v !== null ? v : null;
1134
+ if (value === null || value.durationMs === void 0 && value.command === void 0) throw new Error(t("updateNotApplied"));
994
1135
  setUpdating(name, false);
995
1136
  setBusyUpdate(null);
996
- showToast(t("updatedOne", { n: name }) + (durationMs !== void 0 ? `\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : ""), "ok", 5e3);
1137
+ if (value.command !== void 0 && value.command !== "") {
1138
+ setCommandDialog(value.command);
1139
+ setCopied(false);
1140
+ return;
1141
+ }
1142
+ if (value.pending === true) {
1143
+ pendingInstall.add(name);
1144
+ readyPending.current.push(name);
1145
+ if (!inUpdateAll.current) flushReady();
1146
+ refreshUpdates(true);
1147
+ return;
1148
+ }
1149
+ pendingInstall.add(name);
1150
+ markDoneUpdate({
1151
+ name,
1152
+ fromVersion: updates?.find((u) => u.name === name)?.fromVersion ?? "",
1153
+ toVersion: version
1154
+ });
1155
+ showToast(t("updatedOne", { n: name }) + (value.durationMs !== void 0 ? `\uFF08${(value.durationMs / 1e3).toFixed(1)}s\uFF09` : ""), "ok", 5e3);
997
1156
  refreshUpdates(true);
998
1157
  },
999
1158
  (e) => {
@@ -1006,6 +1165,9 @@ function CenterPanel({ variant = "section" }) {
1006
1165
  const updateAll = async () => {
1007
1166
  if (updates === null || updates.length === 0) return;
1008
1167
  setBusyUpdate("__all__");
1168
+ inUpdateAll.current = true;
1169
+ readyPending.current = [];
1170
+ const commands = [];
1009
1171
  let okCount = 0;
1010
1172
  const okNames = [];
1011
1173
  const failures = [];
@@ -1013,21 +1175,43 @@ function CenterPanel({ variant = "section" }) {
1013
1175
  setUpdating(u.name, true);
1014
1176
  try {
1015
1177
  const v = await rpc("update", { name: u.name, version: u.toVersion });
1016
- const durationMs = typeof v === "object" && v !== null ? v.durationMs : void 0;
1017
- if (v !== true && durationMs === void 0) throw new Error(t("updateNotApplied"));
1178
+ const value = typeof v === "object" && v !== null ? v : null;
1179
+ if (value === null || value.durationMs === void 0 && value.command === void 0) throw new Error(t("updateNotApplied"));
1018
1180
  okCount++;
1019
- okNames.push(durationMs !== void 0 ? `${u.name}\uFF08${(durationMs / 1e3).toFixed(1)}s\uFF09` : u.name);
1181
+ if (value.command !== void 0 && value.command !== "") {
1182
+ commands.push(value.command);
1183
+ continue;
1184
+ }
1185
+ if (value.pending === true) {
1186
+ readyPending.current.push(u.name);
1187
+ okNames.push(`${u.name}\uFF08\u5F85\u91CD\u542F\uFF09`);
1188
+ continue;
1189
+ }
1190
+ pendingInstall.add(u.name);
1191
+ markDoneUpdate({
1192
+ name: u.name,
1193
+ fromVersion: u.fromVersion,
1194
+ toVersion: u.toVersion
1195
+ });
1196
+ okNames.push(value.durationMs !== void 0 ? `${u.name}\uFF08${(value.durationMs / 1e3).toFixed(1)}s\uFF09` : u.name);
1020
1197
  } catch (e) {
1021
1198
  failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
1022
1199
  } finally {
1023
1200
  setUpdating(u.name, false);
1024
1201
  }
1025
1202
  }
1203
+ inUpdateAll.current = false;
1026
1204
  setBusyUpdate(null);
1027
- if (failures.length === 0) {
1028
- showToast(t("updatedMany", { n: okCount }) + `\uFF08${okNames.join("\u3001")}\uFF09`, "ok", 6e3);
1029
- } else {
1205
+ if (commands.length > 0) {
1206
+ setCommandDialog(commands.join("\n"));
1207
+ setCopied(false);
1208
+ } else if (readyPending.current.length > 0) {
1209
+ flushReady();
1210
+ }
1211
+ if (failures.length > 0) {
1030
1212
  showToast(t("updateSummary", { a: okCount, b: failures.length, c: failures.join("\uFF1B") }), "error", 15e3);
1213
+ } else if (commands.length === 0 && readyPending.current.length === 0) {
1214
+ showToast(t("updatedMany", { n: okCount }) + `\uFF08${okNames.join("\u3001")}\uFF09`, "ok", 6e3);
1031
1215
  }
1032
1216
  refreshUpdates();
1033
1217
  };
@@ -1117,28 +1301,58 @@ function CenterPanel({ variant = "section" }) {
1117
1301
  const body = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1118
1302
  view === "installed" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InstalledView, { search, category: installedCategory, source: installedSource, onToggle: handleToggle, togglingId }),
1119
1303
  view === "market" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarketView, { category, single, source, search: marketSearch, onCount: handleMarketCount }),
1120
- view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate }),
1304
+ view === "updates" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UpdatesView, { updates, refresh: refreshUpdates, updateOne, busy: busyUpdate, doneUpdates: doneUpdatesStore, onDoneClick: () => {
1305
+ askRestart(1);
1306
+ } }),
1121
1307
  view === "diagnose" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DiagnoseView, {})
1122
1308
  ] });
1123
1309
  if (variant === "overlay") {
1124
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }, children: [
1125
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "none" }, children: [
1126
- head(false),
1310
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1311
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0 }, children: [
1312
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "none" }, children: [
1313
+ head(false),
1314
+ tabs,
1315
+ installedToolbar,
1316
+ marketToolbar
1317
+ ] }),
1318
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-scroll", children: body })
1319
+ ] }),
1320
+ commandDialog !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandDialog, { command: commandDialog, copied, onCopy: () => {
1321
+ setCopied(true);
1322
+ }, onClose: () => {
1323
+ setCommandDialog(null);
1324
+ } }),
1325
+ restartAsk > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RestartDialog, { count: restartAsk, onRestart: () => {
1326
+ const n = restartAsk;
1327
+ setRestartAsk(0);
1328
+ runRestartNow(n);
1329
+ }, onClose: () => {
1330
+ setRestartAsk(0);
1331
+ } })
1332
+ ] });
1333
+ }
1334
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
1336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "none", paddingBottom: "4px" }, children: [
1337
+ head(true),
1127
1338
  tabs,
1128
1339
  installedToolbar,
1129
1340
  marketToolbar
1130
1341
  ] }),
1131
1342
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-scroll", children: body })
1132
- ] });
1133
- }
1134
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
1135
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: "none", paddingBottom: "4px" }, children: [
1136
- head(true),
1137
- tabs,
1138
- installedToolbar,
1139
- marketToolbar
1140
1343
  ] }),
1141
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-scroll", children: body })
1344
+ commandDialog !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommandDialog, { command: commandDialog, copied, onCopy: () => {
1345
+ setCopied(true);
1346
+ }, onClose: () => {
1347
+ setCommandDialog(null);
1348
+ } }),
1349
+ restartAsk > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RestartDialog, { count: restartAsk, onRestart: () => {
1350
+ const n = restartAsk;
1351
+ setRestartAsk(0);
1352
+ runRestartNow(n);
1353
+ }, onClose: () => {
1354
+ setRestartAsk(0);
1355
+ } })
1142
1356
  ] });
1143
1357
  }
1144
1358
  function HeaderButton() {
@@ -1186,8 +1400,17 @@ function WhatsNewDialog() {
1186
1400
  for (const u of whatsNewDigests) {
1187
1401
  try {
1188
1402
  const v = await rpc("update", { name: u.name, version: u.toVersion });
1189
- if (v !== true) throw new Error(t("updateNotApplied"));
1403
+ const value = typeof v === "object" && v !== null ? v : null;
1404
+ if (value === null || value.durationMs === void 0 && value.command === void 0) throw new Error(t("updateNotApplied"));
1405
+ if (value.command !== void 0 && value.command !== "") {
1406
+ failures.push(`${u.name}\uFF1A${t("commandHint")}`);
1407
+ continue;
1408
+ }
1190
1409
  okCount++;
1410
+ if (value.pending === true || value.direct === true) {
1411
+ pendingInstall.add(u.name);
1412
+ markDoneUpdate({ name: u.name, fromVersion: u.fromVersion, toVersion: u.toVersion });
1413
+ }
1191
1414
  } catch (e) {
1192
1415
  failures.push(`${u.name}\uFF1A${e instanceof Error ? e.message : String(e)}`);
1193
1416
  }
@@ -1207,23 +1430,21 @@ function WhatsNewDialog() {
1207
1430
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-spacer" }),
1208
1431
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "pc-close", onClick: closeWhatsNew, "aria-label": t("close"), children: "\u2715" })
1209
1432
  ] }),
1210
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-body", style: { overflow: "auto" }, children: [
1211
- whatsNewDigests.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-wn-item", children: [
1212
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
1213
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: u.name }),
1214
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: u.fromVersion }),
1215
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" }),
1216
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: u.toVersion })
1217
- ] }),
1218
- u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
1219
- ] }, u.name)),
1220
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "16px" }, children: [
1221
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("later") }),
1222
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("markAllRead") }),
1223
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy, onClick: () => {
1224
- void updateNow();
1225
- }, children: busy ? t("updating") : t("updateNow") })
1226
- ] })
1433
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pc-panel-body", style: { overflow: "auto" }, children: whatsNewDigests.map((u) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-wn-item", children: [
1434
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-row", children: [
1435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-name", children: u.name }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: u.fromVersion }),
1437
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pc-ver", children: "\u2192" }),
1438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500 }, children: u.toVersion })
1439
+ ] }),
1440
+ u.changelog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "pc-wn-list", children: u.changelog.slice(0, 5).map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: line }, i)) })
1441
+ ] }, u.name)) }),
1442
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "pc-panel-footer", children: [
1443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("later") }),
1444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn", onClick: closeWhatsNew, children: t("markAllRead") }),
1445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "pc-btn primary", disabled: busy, onClick: () => {
1446
+ void updateNow();
1447
+ }, children: busy ? t("updating") : t("updateNow") })
1227
1448
  ] })
1228
1449
  ] }) });
1229
1450
  }
package/dist/engine.d.ts CHANGED
@@ -96,7 +96,14 @@ export declare class PluginCenterEngine extends Service {
96
96
  /** Detect updates for every installed third-party/local plugin, TTL-cached. */
97
97
  checkUpdates(sinceIso: string): Promise<UpdateDigest[]>;
98
98
  install(spec: string): Promise<PnpmResult>;
99
- /** Update one installed plugin to the detected target version (exact — see update.ts). */
99
+ /** Update one installed plugin to the detected target version (exact — see update.ts).
100
+ * 三段式(2026-08-22):
101
+ * 1. 先尝试直装(绝大多数成功:纯 JS 包、或原生模块未被宿主加载——无锁);
102
+ * 2. 直装失败且是文件锁(EPERM/rename)→ 特殊路径:
103
+ * - SSiD(kernel 声明 SSID_PENDING_CONSUMER=1)→ 转两段式:预下载到
104
+ * ~/.ssid/pending-plugin-updates/,重启时由 kernel 在 boot DSH 前安装;
105
+ * - 官方 dsh web(无消费方)→ 仿社区市场返回可复制 CLI 指令;
106
+ * 3. 非锁失败(网络/版本)→ 原样报错。 */
100
107
  update(name: string, version: string): Promise<PnpmResult>;
101
108
  /** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
102
109
  private enqueuePnpm;
package/dist/engine.js CHANGED
@@ -7,12 +7,12 @@
7
7
  */
8
8
  import { Service } from '@deepseek-ai/cordis';
9
9
  import { fileURLToPath } from 'node:url';
10
- import { dirname, join } from 'node:path';
10
+ import { basename, dirname, join } from 'node:path';
11
11
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
12
12
  import { existsSync, readFileSync } from 'node:fs';
13
13
  import { buildInstalledPlugin, clearPackageCache, resolvePackage } from "./meta.js";
14
14
  import { fetchAwesomePluginsJson, fetchDshMarketPlugins, fetchOhMyDshOverrides, fetchOhMyDshPlugins, mapConcurrent, mergePlugins, } from "./market.js";
15
- import { detectUpdate, installPlugin, updatePlugin } from "./update.js";
15
+ import { detectUpdate, installPlugin, preparePluginUpdate, updatePlugin } from "./update.js";
16
16
  import { reconcileInstalled, readDependencyKeys } from "./reconcile.js";
17
17
  import { readDisabledState, setDisabled } from "./toggle.js";
18
18
  /** Runtime mirror of cordis FiberState (a cross-package const enum). */
@@ -335,9 +335,27 @@ export class PluginCenterEngine extends Service {
335
335
  return result;
336
336
  });
337
337
  }
338
- /** Update one installed plugin to the detected target version (exact — see update.ts). */
338
+ /** Update one installed plugin to the detected target version (exact — see update.ts).
339
+ * 三段式(2026-08-22):
340
+ * 1. 先尝试直装(绝大多数成功:纯 JS 包、或原生模块未被宿主加载——无锁);
341
+ * 2. 直装失败且是文件锁(EPERM/rename)→ 特殊路径:
342
+ * - SSiD(kernel 声明 SSID_PENDING_CONSUMER=1)→ 转两段式:预下载到
343
+ * ~/.ssid/pending-plugin-updates/,重启时由 kernel 在 boot DSH 前安装;
344
+ * - 官方 dsh web(无消费方)→ 仿社区市场返回可复制 CLI 指令;
345
+ * 3. 非锁失败(网络/版本)→ 原样报错。 */
339
346
  async update(name, version) {
340
- return this.enqueuePnpm(() => updatePlugin(name, version, this.baseUrl));
347
+ // 1) 直装:成败都在这一步见分晓(锁只发生在最后「替换已加载原生模块」)。
348
+ const direct = await this.enqueuePnpm(() => updatePlugin(name, version, this.baseUrl));
349
+ if (direct.ok)
350
+ return { ...direct, direct: true };
351
+ // 2) 锁类失败 → 特殊路径;其余失败原样返回。
352
+ if (!/EPERM|operation not permitted|EBUSY|rename/i.test(direct.detail))
353
+ return direct;
354
+ if (process.env.SSID_PENDING_CONSUMER === '1') {
355
+ return this.enqueuePnpm(() => preparePluginUpdate(name, version, this.baseUrl));
356
+ }
357
+ const profileName = basename(this.baseUrl) || 'web';
358
+ return { ok: true, detail: '', durationMs: direct.durationMs, command: `dsh plugin --profile ${profileName} add ${name}@${version}` };
341
359
  }
342
360
  /** 串行执行一次 pnpm 操作并失效缓存(无论成败都放行链条后续任务)。 */
343
361
  enqueuePnpm(op) {
package/dist/rpc.js CHANGED
@@ -43,7 +43,15 @@ export class PluginCenterRpc extends Service {
43
43
  const result = await ctx.pluginCenter.update(name, version);
44
44
  if (!result.ok)
45
45
  return internal(`update ${name} 失败:${result.detail}`);
46
- return { ok: true, value: { durationMs: result.durationMs } };
46
+ return {
47
+ ok: true,
48
+ value: {
49
+ durationMs: result.durationMs,
50
+ direct: result.direct === true,
51
+ pending: result.pending === true,
52
+ command: result.command,
53
+ },
54
+ };
47
55
  }
48
56
  case 'toggle': {
49
57
  const id = payload?.id;
package/dist/update.d.ts CHANGED
@@ -23,7 +23,31 @@ export interface PnpmResult {
23
23
  detail: string;
24
24
  /** Wall-clock duration of the pnpm process (slow-but-successful is common). */
25
25
  durationMs: number;
26
+ /** True when the update was installed DIRECTLY (files on disk now; a
27
+ * restart makes the running DSH pick them up), no locks were hit. */
28
+ direct?: boolean;
29
+ /** True when the update was only PREPARED (downloaded to the pending dir)
30
+ * and the real install is queued for the next SSiD/DSH startup. */
31
+ pending?: boolean;
32
+ /** Non-SSiD locked-update fallback: the CLI command the user should run
33
+ * after closing DSH (community-market style). */
34
+ command?: string;
26
35
  }
36
+ /** One queued update: prepared tarball + install spec for the next boot. */
37
+ export interface PendingUpdate {
38
+ name: string;
39
+ version: string;
40
+ tgz: string;
41
+ at: number;
42
+ }
43
+ /** npm pack 生成的 tgz 文件名(scope 包的 @ 与 / 均转 -)。 */
44
+ export declare function tarballNameOf(name: string, version: string): string;
45
+ export declare function pendingUpdateDir(): string;
46
+ export declare function readPendingUpdates(): PendingUpdate[];
47
+ export declare function writePendingUpdates(entries: PendingUpdate[]): void;
48
+ /** Run `npm pack <spec>` into the pending dir and record the queued update.
49
+ * @returns the pnpm-style result with `pending: true` on success. */
50
+ export declare function preparePluginUpdate(name: string, version: string, profileDir: string): Promise<PnpmResult>;
27
51
  /**
28
52
  * Append one pnpm operation to `<profileDir>/plugin-center-pnpm.log`: time,
29
53
  * command, cwd, exit, duration, and the captured output tail. Every install
package/dist/update.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * first (many community repos ship no release/tag/CHANGELOG — verified §7.2).
5
5
  */
6
6
  import { spawn } from 'node:child_process';
7
- import { appendFileSync, existsSync } from 'node:fs';
7
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
8
8
  import { homedir } from 'node:os';
9
9
  import { join } from 'node:path';
10
10
  import { compareVersions, satisfies } from "./semver.js";
@@ -70,6 +70,85 @@ export async function detectUpdate(name, localVersion, repoUrl, compatRange, loc
70
70
  compatRange,
71
71
  };
72
72
  }
73
+ // ── 两段式更新(2026-08-22):预下载 + 重启时安装 ────────────────────────
74
+ // SSiD/DSH 运行中替换 node_modules 里的原生模块(node-pty 的 conpty.node
75
+ // 等)会被 Windows 锁死(EPERM:rename tmp -> 目标 失败)。与「禁用插件
76
+ // 写 patch + 重启生效」同款两段式:
77
+ // 1. 现在:npm pack 把 <name>@<version> 下载到 ~/.ssid/pending-plugin-updates/
78
+ // (不动 node_modules,无锁;网络/版本问题此刻暴露);
79
+ // 2. 重启(SSiD 启动、boot DSH 之前):shell/kernel.ts 消费清单,用
80
+ // `pnpm add -w <name>@<version>` 安装(无锁窗口、store 已缓存)。
81
+ // 注意:pnpm pack 只能打当前项目,远程 registry 包必须用 npm pack
82
+ // (2026-08-22 实测两者行为差异)。tgz 命名(npm pack 实测):
83
+ // 普通包 <name>-<version>.tgz;scope 包 <scope>-<name>-<version>.tgz。
84
+ const PENDING_DIR = join(homedir(), '.ssid', 'pending-plugin-updates');
85
+ const PENDING_INDEX = join(PENDING_DIR, 'index.json');
86
+ /** npm pack 生成的 tgz 文件名(scope 包的 @ 与 / 均转 -)。 */
87
+ export function tarballNameOf(name, version) {
88
+ return `${name.startsWith('@') ? name.slice(1).replace('/', '-') : name}-${version}.tgz`;
89
+ }
90
+ export function pendingUpdateDir() {
91
+ return PENDING_DIR;
92
+ }
93
+ export function readPendingUpdates() {
94
+ try {
95
+ const parsed = JSON.parse(readFileSync(PENDING_INDEX, 'utf8'));
96
+ if (!Array.isArray(parsed))
97
+ return [];
98
+ return parsed.filter((entry) => {
99
+ const e = entry;
100
+ return e !== null && typeof e.name === 'string' && typeof e.version === 'string' && typeof e.tgz === 'string';
101
+ });
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ }
107
+ export function writePendingUpdates(entries) {
108
+ mkdirSync(PENDING_DIR, { recursive: true });
109
+ writeFileSync(PENDING_INDEX, JSON.stringify(entries, null, 2) + '\n');
110
+ }
111
+ /** npm 候选命令:优先 PATH,回退用户级 npm 全局目录(同 pnpmCandidates 策略)。 */
112
+ function npmCandidates() {
113
+ const commands = ['npm', 'npm.cmd'];
114
+ const userNpm = join(homedir(), 'AppData', 'Roaming', 'npm');
115
+ for (const name of ['npm.cmd', 'npm.exe', 'npm']) {
116
+ const candidate = join(userNpm, name);
117
+ if (existsSync(candidate))
118
+ commands.push(candidate);
119
+ }
120
+ return commands;
121
+ }
122
+ /** Run `npm pack <spec>` into the pending dir and record the queued update.
123
+ * @returns the pnpm-style result with `pending: true` on success. */
124
+ export async function preparePluginUpdate(name, version, profileDir) {
125
+ const spec = `${name}@${version}`;
126
+ const tgz = join(PENDING_DIR, tarballNameOf(name, version));
127
+ // 清理同包其他版本的残留 tgz(保留同 name 最新一次准备)
128
+ try {
129
+ for (const file of readdirSync(PENDING_DIR)) {
130
+ if (file.endsWith('.tgz') && file !== tarballNameOf(name, version)) {
131
+ rmSync(join(PENDING_DIR, file), { force: true });
132
+ }
133
+ }
134
+ }
135
+ catch { /* pending dir absent is fine */ }
136
+ let last = { ok: false, detail: 'no npm candidate found', durationMs: 0 };
137
+ for (const command of npmCandidates()) {
138
+ last = await runOne(command, ['pack', spec, '--pack-destination', PENDING_DIR], profileDir);
139
+ if (last.ok)
140
+ break;
141
+ }
142
+ logPnpm(profileDir, ['npm', 'pack', spec, '--pack-destination', PENDING_DIR], last);
143
+ if (last.ok && existsSync(tgz)) {
144
+ const entries = readPendingUpdates().filter(entry => !(entry.name === name && entry.version === version));
145
+ entries.push({ name, version, tgz, at: Date.now() });
146
+ writePendingUpdates(entries);
147
+ return { ...last, pending: true };
148
+ }
149
+ // 失败:detail 里已带 npm 输出(网络/版本问题此时暴露,无需重启才发现)
150
+ return last;
151
+ }
73
152
  /**
74
153
  * Append one pnpm operation to `<profileDir>/plugin-center-pnpm.log`: time,
75
154
  * command, cwd, exit, duration, and the captured output tail. Every install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-plugin-center",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Plugin center for DeepSeek Harness 閳?installed metadata, community market, update detection, and What\u0027s New",
5
5
  "license": "MIT",
6
6
  "publishConfig": {