dsh-session-manager 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -181,8 +181,11 @@ function isTopmostDialog(z) {
181
181
  "panel.update.title": "检查更新",
182
182
  "panel.update.badge": "有新版本可用",
183
183
  "update.title": "检查更新",
184
+ "update.done.title": "升级成功",
184
185
  "update.newVersion": "发现新版本",
185
186
  "update.currentVersion": "当前版本",
187
+ "update.runningVersion": "当前运行版本",
188
+ "update.installedVersion": "已安装版本",
186
189
  "update.latestVersion": "最新版本",
187
190
  "update.isLatest": "当前已是最新版本",
188
191
  "update.checking": "正在检查更新…",
@@ -192,16 +195,26 @@ function isTopmostDialog(z) {
192
195
  "update.btn.updating": "更新中…",
193
196
  "update.btn.later": "稍后",
194
197
  "update.btn.done": "好的",
195
- "update.btn.retry": "重试",
196
198
  "update.btn.close": "关闭",
197
- "update.done.restart": "更新已安装(v{from} → v{to}),重启 DSH 后生效。",
198
- "update.done.applied": "更新已应用!",
199
+ "update.btn.retry": "重试",
200
+ "update.done.restart": "升级成功!已安装 v{to}(当前运行 v{from})。请重启 DSH 使新版本生效。",
201
+ "update.done.refresh": "升级成功!已安装 v{to}。请刷新界面使新版本生效。",
202
+ "update.done.applied": "升级成功!新版本已立即生效。",
203
+ "update.rolledBack.title": "已安全回滚",
204
+ "update.rolledBack": "升级未成功,已安全回滚至旧版本 v{version}。",
205
+ "update.rolledBack.reason": "原因:{reason}",
206
+ "update.source.linked": "开发模式(本地链接),请手动修改或拉取代码。",
207
+ "update.source.github": "通过 Git/GitHub 安装,请通过 DSH 插件市场或 git 更新。",
208
+ "update.source.installation": "系统内置插件,无法独立更新。",
209
+ "update.source.unknown": "无法确认插件安装来源,为保证安全已禁止自动更新。",
210
+ "update.hmrUnsupported": "当前处于 HMR 开发环境,无法在运行中直接自我更新。请使用 DSH 插件市场或命令行执行更新。",
199
211
  "update.error": "更新遇到问题",
200
212
  "update.autoCheck": "自动检查更新",
201
213
  "update.autoCheck.enable": "开启",
202
214
  "settings.registry": "安装源",
203
215
  "settings.registry.npm": "npm 官方源 (npmjs.org)",
204
216
  "settings.registry.china": "国内镜像源 (npmmirror.com)",
217
+ "settings.registry.local": "本地测试源 (127.0.0.1:4873)",
205
218
  "settings.title": "会话管理",
206
219
  "settings.pluginName": "dsh-session-manager",
207
220
  "settings.versionSection": "插件版本",
@@ -452,8 +465,11 @@ function isTopmostDialog(z) {
452
465
  "panel.update.title": "Check for updates",
453
466
  "panel.update.badge": "New version available",
454
467
  "update.title": "Check for Updates",
468
+ "update.done.title": "Upgrade Succeeded",
455
469
  "update.newVersion": "New Version Available",
456
470
  "update.currentVersion": "Current Version",
471
+ "update.runningVersion": "Running Version",
472
+ "update.installedVersion": "Installed Version",
457
473
  "update.latestVersion": "Latest Version",
458
474
  "update.isLatest": "Up to date",
459
475
  "update.checking": "Checking for updates…",
@@ -463,16 +479,26 @@ function isTopmostDialog(z) {
463
479
  "update.btn.updating": "Updating…",
464
480
  "update.btn.later": "Later",
465
481
  "update.btn.done": "OK",
466
- "update.btn.retry": "Retry",
467
482
  "update.btn.close": "Close",
468
- "update.done.restart": "Update installed (v{from} → v{to}). Please restart DSH.",
469
- "update.done.applied": "Update applied successfully!",
483
+ "update.btn.retry": "Retry",
484
+ "update.done.restart": "Upgrade succeeded! Installed v{to} (running v{from}). Please restart DSH to apply.",
485
+ "update.done.refresh": "Upgrade succeeded! Installed v{to}. Please refresh the UI to apply.",
486
+ "update.done.applied": "Upgrade succeeded! Update applied and active now.",
487
+ "update.rolledBack.title": "Safely Rolled Back",
488
+ "update.rolledBack": "Upgrade was not completed. Safely rolled back to previous version v{version}.",
489
+ "update.rolledBack.reason": "Reason: {reason}",
490
+ "update.source.linked": "Development mode (linked package). Please update local code directly.",
491
+ "update.source.github": "Installed via Git/GitHub. Please update via DSH Market or git.",
492
+ "update.source.installation": "Built-in package. Cannot be updated independently.",
493
+ "update.source.unknown": "Unknown installation source. Self-update disabled for safety.",
494
+ "update.hmrUnsupported": "Running under HMR development mode; self-update cannot proceed while active. Please update via DSH Market or CLI.",
470
495
  "update.error": "Update Error",
471
496
  "update.autoCheck": "Automatically check for updates",
472
497
  "update.autoCheck.enable": "Enable",
473
498
  "settings.registry": "Install Source",
474
499
  "settings.registry.npm": "Official npm (npmjs.org)",
475
500
  "settings.registry.china": "China mirror (npmmirror.com)",
501
+ "settings.registry.local": "Local test registry (127.0.0.1:4873)",
476
502
  "settings.title": "Session Manager",
477
503
  "settings.pluginName": "dsh-session-manager",
478
504
  "settings.versionSection": "Plugin Version",
@@ -732,7 +758,7 @@ function isTopmostDialog(z) {
732
758
  // applied). Tooltip visibility is governed by the layering contract
733
759
  // above, not by this rule; it is kept as the scoped replacement for the
734
760
  // unscoped selector Issue #21 removed.
735
- ".sm-panelHeaderClose{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;min-height:26px;padding:0;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#555);cursor:pointer;flex:none;box-sizing:border-box;outline:none}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose svg{display:block;width:14px;height:14px;margin:auto}.sm-panelHeaderGithub{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:6px;color:var(--dsw-alias-label-secondary,#555);text-decoration:none;flex:none;transition:background .15s,color .15s;cursor:pointer}.sm-panelHeaderGithub:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderGithub svg{display:block;width:16px;height:16px;fill:currentColor}.sm-panelHeaderLeft{display:flex;align-items:center;gap:4px;flex:none;position:relative;z-index:1}.sm-panelHeaderWhale{display:inline-flex;align-items:center;justify-content:center;position:relative;width:26px;height:26px;border-radius:6px;color:var(--dsw-alias-label-secondary,#555);text-decoration:none;flex:none;transition:background .15s,color .15s;cursor:pointer}.sm-panelHeaderWhale:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderWhale svg{display:block;width:16px;height:16px;margin:auto}.sm-panelHeaderWhale .sm-updateDot{position:absolute;top:2px;right:2px;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-error-primary,#e53e3e);border:1.5px solid var(--dsw-alias-bg-layer-1,#fff)}.sm-panelHeaderWhale .sm-updateSpinner{position:absolute;inset:2px;border:2px solid transparent;border-top-color:var(--dsw-alias-state-business-primary,#356ae6);border-radius:50%;animation:sm-spin .8s linear infinite}@keyframes sm-spin{to{transform:rotate(360deg)}}.sm-updateDialogLayer{display:flex!important;align-items:center!important;justify-content:center!important;position:fixed;inset:0;padding:16px;box-sizing:border-box;background:transparent;border:0;box-shadow:none;z-index:10010;pointer-events:none}.sm-updateDialogLayer .sm-nativeDialog{position:relative;width:380px;max-width:calc(100vw - 32px);box-sizing:border-box;background:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);overflow:hidden;animation:sm-confirmPop .14s ease-out;pointer-events:auto}.sm-updateDialog .sm-nativeDialogHeader{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;min-height:46px;box-sizing:border-box}.sm-updateDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:15px;line-height:20px;font-weight:600;text-align:center;width:auto;max-width:calc(100% - 80px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:0}.sm-updateDialog .sm-nativeDialogHeader .sm-panelHeaderClose{position:relative;z-index:1;margin-left:auto}.sm-updateDialog .sm-nativeDialogBody{padding:14px 16px;display:flex;flex-direction:column;gap:10px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}.sm-updateDialog .sm-nativeDialogFooter{display:flex;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;flex-wrap:wrap;justify-content:space-between;gap:8px;margin-top:0}.sm-updateDialog .sm-nativeDialogFooter .sm-nativeDialogConfirm:only-child{margin-left:auto}.sm-updateDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center}.sm-updateDialog .sm-nativeDialogButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-updateDialog .sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-updateDialog .sm-nativeDialogConfirm:hover:not(:disabled){filter:brightness(.94)}.sm-updateVersionGrid{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;padding:12px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px;font-size:13px;line-height:20px}.sm-updateVersionLabel{color:var(--dsw-alias-label-secondary,#666);font-weight:500}.sm-updateVersionValue{color:var(--dsw-alias-label-primary,#111);font-family:monospace;font-size:13px}.sm-updateStatusBox{margin-top:10px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111)}.sm-updateStatusBox.sm-warn{color:var(--dsw-alias-state-warn-label,#dd8629);background:var(--dsw-alias-state-warn-tertiary,#fef5e7);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 25%,transparent)}.sm-updateStatusBox.sm-error{color:var(--dsw-alias-state-error-primary,#ec1313);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 8%,transparent);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 22%,transparent)}.sm-updateStatusBox.sm-success{color:var(--dsw-alias-state-success-primary,#22c55e);background:var(--dsw-alias-state-success-tertiary,#e6faed);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 25%,transparent)}.sm-settingsCard{background:var(--sm-dialog-surface);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;padding:16px 20px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111);box-sizing:border-box;width:100%}.sm-settingsHeader{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee)}.sm-settingsTitle{margin:0;font-size:16px;line-height:22px;font-weight:600;display:flex;align-items:center;gap:8px}.sm-settingsTag{font-size:12px;padding:2px 8px;border-radius:999px;background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#666);font-family:monospace}.sm-settingsSection{margin-top:14px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}.sm-settingsSectionTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#666);margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px}.sm-settingsRow{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:36px;margin-top:8px}.sm-settingsRow:first-of-type{margin-top:0}.sm-settingsRow>span:first-child{white-space:nowrap;flex-shrink:0}.sm-settingsActions{display:flex;align-items:center;gap:8px;margin-top:10px}.sm-settingsLink{color:var(--dsw-alias-state-business-primary,#356ae6);text-decoration:none}.sm-settingsLink:hover{text-decoration:underline}.sm-settingsCheckbox{display:inline-flex;align-items:center;gap:6px;cursor:pointer}.sm-settingsSelect{min-height:30px;height:30px;width:280px;min-width:280px;max-width:280px;flex:0 0 280px;box-sizing:border-box;font-size:12px;line-height:20px;padding:4px 26px 4px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);cursor:pointer}.sm-settingsRowLeft{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:2px;min-width:0;flex:1 1 auto}.sm-settingsRowLeft>span:first-child{white-space:nowrap;flex-shrink:0}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose:disabled{opacity:.5;cursor:not-allowed}.sm-migrateCurrent{padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#111)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer{width:360px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer .sm-migrateDialog{position:relative;left:auto;top:auto;bottom:auto;transform:none;width:100%;max-width:calc(100vw - 32px);max-height:min(70vh,calc(100vh - 32px));z-index:auto;display:flex;flex-direction:column;overflow:hidden}.sm-migrateConfirmLayer .sm-migratePanel{min-width:0;max-height:calc(100vh - 120px);overflow-y:auto}.sm-migrateConfirmLayer .sm-nativeDialogButton.sm-nativeDialogConfirm:hover:not(:disabled){background-color:var(--dsw-alias-button-info-hover,#4576f0);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-button-info-hover,#4576f0);filter:none}.sm-settingsSectionHeader{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.sm-settingsSectionHeader .sm-settingsSectionTitle{margin-bottom:0}.sm-settingsVersionActions{display:flex;align-items:center;gap:8px}.sm-settingsCheckBtn{min-height:26px;padding:2px 12px;font-size:12px;line-height:18px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,#ddd);background:transparent;color:var(--dsw-alias-label-primary,#111);cursor:pointer;white-space:nowrap}.sm-settingsCheckBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-settingsRowInline{justify-content:flex-start;flex-wrap:wrap;gap:8px;align-items:center}.sm-settingsProjectLeft{display:flex;align-items:center;gap:8px;min-width:0;flex-wrap:nowrap}.sm-settingsProjectLabel{color:var(--dsw-alias-label-primary,#111);white-space:nowrap;flex-shrink:0}.sm-settingsBadgeActions{display:flex;align-items:center;gap:8px;margin-left:auto;flex:0 0 280px;width:280px;min-width:280px;max-width:280px;box-sizing:border-box;justify-content:flex-end}.sm-settingsBadgeActions>*{display:inline-flex;flex:1 1 0;min-width:0}.sm-settingsBadgeLink{display:inline-flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:4px 10px;min-height:28px;height:28px;box-sizing:border-box;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,#ddd);background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#555);font-size:12px;line-height:18px;text-decoration:none;cursor:pointer;white-space:nowrap;transition:all .15s}.sm-settingsBadgeLink:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-state-business-primary,#356ae6);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-settingsBadgeLink svg{flex:none}.sm-tooltip{z-index:1100!important;box-shadow:none!important;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.15))!important;filter:none!important}",
761
+ ".sm-panelHeaderClose{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;min-height:26px;padding:0;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#555);cursor:pointer;flex:none;box-sizing:border-box;outline:none}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose svg{display:block;width:14px;height:14px;margin:auto}.sm-panelHeaderGithub{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:6px;color:var(--dsw-alias-label-secondary,#555);text-decoration:none;flex:none;transition:background .15s,color .15s;cursor:pointer}.sm-panelHeaderGithub:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderGithub svg{display:block;width:16px;height:16px;fill:currentColor}.sm-panelHeaderLeft{display:flex;align-items:center;gap:4px;flex:none;position:relative;z-index:1}.sm-panelHeaderWhale{display:inline-flex;align-items:center;justify-content:center;position:relative;width:26px;height:26px;border-radius:6px;color:var(--dsw-alias-label-secondary,#555);text-decoration:none;flex:none;transition:background .15s,color .15s;cursor:pointer}.sm-panelHeaderWhale:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderWhale svg{display:block;width:16px;height:16px;margin:auto}.sm-panelHeaderWhale .sm-updateDot{position:absolute;top:2px;right:2px;width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-state-error-primary,#e53e3e);border:1.5px solid var(--dsw-alias-bg-layer-1,#fff)}.sm-panelHeaderWhale .sm-updateSpinner{position:absolute;inset:2px;border:2px solid transparent;border-top-color:var(--dsw-alias-state-business-primary,#356ae6);border-radius:50%;animation:sm-spin .8s linear infinite}@keyframes sm-spin{to{transform:rotate(360deg)}}.sm-updateDialogLayer{display:flex!important;align-items:center!important;justify-content:center!important;position:fixed;inset:0;padding:16px;box-sizing:border-box;background:transparent;border:0;box-shadow:none;z-index:10010;pointer-events:none}.sm-updateDialogLayer .sm-nativeDialog{position:relative;width:380px;max-width:calc(100vw - 32px);height:248px!important;min-height:248px!important;max-height:248px!important;box-sizing:border-box;background:var(--sm-dialog-fill);color:var(--dsw-alias-label-primary,#111);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,.25);overflow:hidden;animation:none!important;pointer-events:auto}.sm-updateDialog .sm-nativeDialogHeader{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee);flex:none;height:46px;min-height:46px;max-height:46px;box-sizing:border-box}.sm-updateDialog .sm-nativeDialogTitle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0;font-size:15px;line-height:20px;font-weight:600;text-align:center;width:auto;max-width:calc(100% - 80px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:0}.sm-updateDialog .sm-nativeDialogHeader .sm-panelHeaderClose{position:relative;z-index:1;margin-left:auto}.sm-updateDialog .sm-nativeDialogBody{padding:14px 16px;display:flex;flex-direction:column;gap:10px;height:152px!important;min-height:152px!important;max-height:152px!important;box-sizing:border-box;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111);overflow:hidden}.sm-updateDialog .sm-nativeDialogFooter{display:flex;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2,#eee);flex:none;height:50px;min-height:50px;max-height:50px;box-sizing:border-box;flex-wrap:nowrap;justify-content:space-between;align-items:center;gap:8px;margin-top:0}.sm-updateDialog .sm-nativeDialogFooter .sm-nativeDialogConfirm:only-child{margin-left:auto}.sm-updateDialog .sm-nativeDialogButton{min-height:28px;padding:3px 14px;font-size:12px;line-height:20px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;cursor:pointer;background:transparent;color:var(--dsw-alias-label-primary,#111);text-align:center;white-space:nowrap}.sm-nativeDialogButton:disabled{opacity:.5!important;cursor:not-allowed!important}.sm-updateDialog .sm-nativeDialogButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-updateDialog .sm-nativeDialogConfirm,.sm-updateDialog .sm-nativeDialogButton.sm-nativeDialogConfirm,.sm-settingsCard .sm-nativeDialogConfirm,.sm-settingsCard .sm-nativeDialogButton.sm-nativeDialogConfirm{background:var(--dsw-alias-state-business-primary,#356ae6)!important;color:var(--dsw-alias-label-primary-foreground,#fff)!important;border-color:var(--dsw-alias-state-business-primary,#356ae6)!important}.sm-updateDialog .sm-nativeDialogCancel,.sm-updateDialog .sm-nativeDialogButton.sm-nativeDialogCancel{background:transparent!important;color:var(--dsw-alias-label-primary,#111)!important;border-color:var(--dsw-alias-border-l2,#ddd)!important}.sm-updateDialog .sm-nativeDialogConfirm:hover:not(:disabled),.sm-updateDialog .sm-nativeDialogButton.sm-nativeDialogConfirm:hover:not(:disabled),.sm-settingsCard .sm-nativeDialogConfirm:hover:not(:disabled),.sm-settingsCard .sm-nativeDialogButton.sm-nativeDialogConfirm:hover:not(:disabled){background:var(--dsw-alias-state-business-primary,#356ae6)!important;color:var(--dsw-alias-label-primary-foreground,#fff)!important;border-color:var(--dsw-alias-state-business-primary,#356ae6)!important;filter:brightness(.94)!important}.sm-updateDialog .sm-nativeDialogCancel:hover:not(:disabled),.sm-updateDialog .sm-nativeDialogButton.sm-nativeDialogCancel:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)!important;color:var(--dsw-alias-label-primary,#111)!important}.sm-updateVersionGrid{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;padding:12px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l2,#eee);border-radius:8px;font-size:13px;line-height:20px;box-sizing:border-box;flex:none}.sm-updateVersionLabel{color:var(--dsw-alias-label-secondary,#666);font-weight:500}.sm-updateVersionValue{color:var(--dsw-alias-label-primary,#111);font-family:monospace;font-size:13px}.sm-updateStatusBox{margin-top:10px;box-sizing:border-box;min-height:38px;height:38px;padding:8px 10px;border-radius:6px;border:1px solid transparent;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111);display:flex;align-items:center;flex:none;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm-updateDialog .sm-updateStatusBox{margin-top:0}.sm-updateStatusBox.sm-checking{color:var(--dsw-alias-label-secondary,#666);background:var(--dsw-alias-bg-layer-2,#f5f5f5);border:1px solid var(--dsw-alias-border-l2,#eee)}.sm-updateStatusBox.sm-warn{color:var(--dsw-alias-state-warn-label,#dd8629);background:var(--dsw-alias-state-warn-tertiary,#fef5e7);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 25%,transparent)}.sm-updateStatusBox.sm-error{color:var(--dsw-alias-state-error-primary,#ec1313);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 8%,transparent);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#ec1313) 22%,transparent)}.sm-updateStatusBox.sm-success{color:var(--dsw-alias-state-success-primary,#22c55e);background:var(--dsw-alias-state-success-tertiary,#e6faed);padding:8px 10px;border-radius:6px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 25%,transparent)}.sm-settingsCard{background:var(--sm-dialog-surface);border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:12px;padding:16px 20px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary,#111);box-sizing:border-box;width:100%}.sm-settingsHeader{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--dsw-alias-border-l2,#eee)}.sm-settingsTitle{margin:0;font-size:16px;line-height:22px;font-weight:600;display:flex;align-items:center;gap:8px}.sm-settingsTag{font-size:12px;padding:2px 8px;border-radius:999px;background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#666);font-family:monospace}.sm-settingsSection{margin-top:14px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2,#eee)}.sm-settingsSectionTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#666);margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px}.sm-settingsRow{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:36px;margin-top:8px}.sm-settingsRow:first-of-type{margin-top:0}.sm-settingsRow>span:first-child{white-space:nowrap;flex-shrink:0}.sm-settingsActions{display:flex;align-items:center;gap:8px;margin-top:10px}.sm-settingsLink{color:var(--dsw-alias-state-business-primary,#356ae6);text-decoration:none}.sm-settingsLink:hover{text-decoration:underline}.sm-settingsCheckbox{display:inline-flex;align-items:center;gap:6px;cursor:pointer}.sm-settingsSelect{min-height:30px;height:30px;width:280px;min-width:280px;max-width:280px;flex:0 0 280px;box-sizing:border-box;font-size:12px;line-height:20px;padding:4px 26px 4px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#111);cursor:pointer}.sm-settingsRowLeft{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:2px;min-width:0;flex:1 1 auto}.sm-settingsRowLeft>span:first-child{white-space:nowrap;flex-shrink:0}.sm-panelHeaderClose:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-label-primary,#111)}.sm-panelHeaderClose:disabled{opacity:.5;cursor:not-allowed}.sm-migrateCurrent{padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#ddd);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#fff);font-size:13px;line-height:18px;color:var(--dsw-alias-label-primary,#111)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer{width:360px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);inset:calc(50vh - 90px) auto auto calc(50vw + 308px)}.sm-migrateConfirmLayer.sm-confirmDialog.sm-nativeDialogLayer .sm-migrateDialog{position:relative;left:auto;top:auto;bottom:auto;transform:none;width:100%;max-width:calc(100vw - 32px);max-height:min(70vh,calc(100vh - 32px));z-index:auto;display:flex;flex-direction:column;overflow:hidden}.sm-migrateConfirmLayer .sm-migratePanel{min-width:0;max-height:calc(100vh - 120px);overflow-y:auto}.sm-migrateConfirmLayer .sm-nativeDialogButton.sm-nativeDialogConfirm:hover:not(:disabled){background-color:var(--dsw-alias-state-business-primary,#356ae6);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:var(--dsw-alias-state-business-primary,#356ae6);filter:brightness(.94)}.sm-settingsSectionHeader{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px}.sm-settingsSectionHeader .sm-settingsSectionTitle{margin-bottom:0}.sm-settingsVersionActions{display:flex;align-items:center;gap:8px}.sm-settingsCheckBtn{min-height:26px;padding:2px 12px;font-size:12px;line-height:18px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,#ddd);background:transparent;color:var(--dsw-alias-label-primary,#111);cursor:pointer;white-space:nowrap}.sm-settingsCheckBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.sm-settingsRowInline{justify-content:flex-start;flex-wrap:wrap;gap:8px;align-items:center}.sm-settingsProjectLeft{display:flex;align-items:center;gap:8px;min-width:0;flex-wrap:nowrap}.sm-settingsProjectLabel{color:var(--dsw-alias-label-primary,#111);white-space:nowrap;flex-shrink:0}.sm-settingsBadgeActions{display:flex;align-items:center;gap:8px;margin-left:auto;flex:0 0 280px;width:280px;min-width:280px;max-width:280px;box-sizing:border-box;justify-content:flex-end}.sm-settingsBadgeActions>*{display:inline-flex;flex:1 1 0;min-width:0}.sm-settingsBadgeLink{display:inline-flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:4px 10px;min-height:28px;height:28px;box-sizing:border-box;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,#ddd);background:var(--dsw-alias-markdown-tag,#f1f3f5);color:var(--dsw-alias-label-secondary,#555);font-size:12px;line-height:18px;text-decoration:none;cursor:pointer;white-space:nowrap;transition:all .15s}.sm-settingsBadgeLink:hover{background:var(--dsw-alias-interactive-bg-hover,#eee);color:var(--dsw-alias-state-business-primary,#356ae6);border-color:var(--dsw-alias-state-business-primary,#356ae6)}.sm-settingsBadgeLink svg{flex:none}.sm-tooltip{z-index:1100!important;box-shadow:none!important;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.15))!important;filter:none!important}",
736
762
 
737
763
  // Issue #23 — dialog opacity.
738
764
  //
@@ -3747,7 +3773,7 @@ function isTopmostDialog(z) {
3747
3773
  ));
3748
3774
  }
3749
3775
 
3750
- const CURRENT_VERSION = "0.7.2";
3776
+ const CURRENT_VERSION = "0.7.3";
3751
3777
 
3752
3778
  function compareSemver(v1, v2) {
3753
3779
  if (typeof v1 !== "string" || typeof v2 !== "string") return 0;
@@ -3803,17 +3829,25 @@ function isTopmostDialog(z) {
3803
3829
  state: {
3804
3830
  status: "idle",
3805
3831
  currentVersion: CURRENT_VERSION,
3832
+ runningVersion: CURRENT_VERSION,
3833
+ installedVersion: null,
3806
3834
  latestVersion: null,
3807
3835
  error: null,
3808
3836
  application: null,
3809
3837
  dialogOpen: false,
3810
3838
  checkedAt: null,
3811
3839
  lastAttemptAt: null,
3840
+ source: null,
3841
+ notes: null,
3842
+ channelSwitch: null,
3843
+ rollbackInfo: null,
3844
+ taskId: null,
3812
3845
  },
3813
3846
  CHECK_INTERVAL_MS: 6 * 60 * 60 * 1000,
3814
3847
  RETRY_COOLDOWN_MS: 30 * 60 * 1000,
3815
3848
  lastFailedAt: null,
3816
3849
  listeners: new Set(),
3850
+ _statusPromise: null,
3817
3851
  getSnapshot() { return this.state; },
3818
3852
  subscribe(cb) {
3819
3853
  this.listeners.add(cb);
@@ -3828,9 +3862,92 @@ function isTopmostDialog(z) {
3828
3862
  this._notify();
3829
3863
  },
3830
3864
  closeDialog() {
3865
+ try {
3866
+ if (typeof localStorage !== "undefined") {
3867
+ const currentTaskId = this.state.taskId || this.state.rollbackInfo?.taskId;
3868
+ if (currentTaskId && this.state.status !== "updating") {
3869
+ localStorage.setItem("dsh-sm-ack-task", currentTaskId);
3870
+ }
3871
+ }
3872
+ } catch (_) {}
3831
3873
  this.state.dialogOpen = false;
3874
+ if (this.state.status !== "updating") {
3875
+ this.state.status = "idle";
3876
+ }
3832
3877
  this._notify();
3833
3878
  },
3879
+ async checkRecentStatus() {
3880
+ if (this._statusPromise) return this._statusPromise;
3881
+ if (this.state.status === "updating") return;
3882
+ this._statusPromise = (async () => {
3883
+ try {
3884
+ const statusData = await fetchApi("/update/status");
3885
+ if (statusData && statusData.ok && statusData.result) {
3886
+ const r = statusData.result;
3887
+ const ackTaskId = (typeof localStorage !== "undefined") ? localStorage.getItem("dsh-sm-ack-task") : null;
3888
+ if (r.taskId && r.taskId === ackTaskId) return;
3889
+ if (this.state.taskId === r.taskId && (this.state.status === "done" || this.state.status === "error")) return;
3890
+
3891
+ const runningV = r.runningVersion || r.currentVersion || this.state.runningVersion || this.state.currentVersion || CURRENT_VERSION;
3892
+ const installedV = r.installedVersion || (r.status === "completed" ? (r.targetVersion || runningV) : runningV);
3893
+ this.state.runningVersion = runningV;
3894
+ this.state.currentVersion = runningV;
3895
+ if (compareSemver(installedV, runningV) > 0) {
3896
+ this.state.installedVersion = installedV;
3897
+ }
3898
+
3899
+ if (r.status === "rolled-back" || (r.status === "failed" && r.rolledBack)) {
3900
+ this.state.status = "error";
3901
+ this.state.taskId = r.taskId;
3902
+ this.state.rollbackInfo = {
3903
+ rolledBack: true,
3904
+ currentVersion: r.currentVersion || runningV,
3905
+ reason: r.error || "未知原因",
3906
+ taskId: r.taskId
3907
+ };
3908
+ this.state.error = (r.error || "升级未成功") + "(已安全回滚至旧版本 v" + (r.currentVersion || runningV) + ")";
3909
+ this.state.dialogOpen = true;
3910
+ this._notify();
3911
+ } else if (r.status === "completed") {
3912
+ const targetV = r.targetVersion || this.state.latestVersion || installedV;
3913
+ this.state.taskId = r.taskId;
3914
+ this.state.latestVersion = targetV;
3915
+ this.state.installedVersion = targetV;
3916
+
3917
+ // If DSH was restarted and already running targetV or higher, do not popup again!
3918
+ if (compareSemver(runningV, targetV) >= 0) {
3919
+ try {
3920
+ if (typeof localStorage !== "undefined" && r.taskId) {
3921
+ localStorage.setItem("dsh-sm-ack-task", r.taskId);
3922
+ }
3923
+ } catch (_) {}
3924
+ return;
3925
+ }
3926
+
3927
+ if (r.application === "applied") {
3928
+ this.state.runningVersion = targetV;
3929
+ this.state.currentVersion = targetV;
3930
+ }
3931
+
3932
+ this.state.status = "done";
3933
+ this.state.application = r.application || "restart-required";
3934
+ this.state.dialogOpen = true;
3935
+ this._notify();
3936
+ } else if (r.status === "recovery-required") {
3937
+ this.state.status = "error";
3938
+ this.state.taskId = r.taskId;
3939
+ this.state.error = "更新失败且自动恢复旧版本失败: " + (r.rollbackError || r.error) + "。请手动重新安装插件。";
3940
+ this.state.dialogOpen = true;
3941
+ this._notify();
3942
+ }
3943
+ }
3944
+ } catch (_) {}
3945
+ finally {
3946
+ this._statusPromise = null;
3947
+ }
3948
+ })();
3949
+ return this._statusPromise;
3950
+ },
3834
3951
  getAutoCheck() {
3835
3952
  try {
3836
3953
  if (typeof localStorage !== "undefined") {
@@ -3851,6 +3968,9 @@ function isTopmostDialog(z) {
3851
3968
  if (!this.getAutoCheck()) return false;
3852
3969
  if (this.state.status === "checking" || this.state.status === "updating") return false;
3853
3970
  if (this.state.status === "available") return false;
3971
+ if (this.state.installedVersion && compareSemver(this.state.installedVersion, this.state.currentVersion) > 0) {
3972
+ return false;
3973
+ }
3854
3974
  if (this.lastFailedAt && (now - this.lastFailedAt < this.RETRY_COOLDOWN_MS)) {
3855
3975
  return false;
3856
3976
  }
@@ -3894,13 +4014,14 @@ function isTopmostDialog(z) {
3894
4014
  REGISTRY_OPTIONS: {
3895
4015
  "registry.npmjs.org": "https://registry.npmjs.org",
3896
4016
  "registry.npmmirror.com": "https://registry.npmmirror.com",
4017
+ "127.0.0.1:4873": "http://127.0.0.1:4873",
3897
4018
  },
3898
4019
  DEFAULT_REGISTRY: "registry.npmjs.org",
3899
4020
  getRegistry() {
3900
4021
  try {
3901
4022
  if (typeof localStorage !== "undefined") {
3902
4023
  const v = localStorage.getItem("dsh-session-manager-registry");
3903
- if (v && this.REGISTRY_OPTIONS[v]) return v;
4024
+ if (v && (this.REGISTRY_OPTIONS[v] || /^https?:\/\//i.test(v))) return v;
3904
4025
  }
3905
4026
  } catch (_) {}
3906
4027
  return this.DEFAULT_REGISTRY;
@@ -3908,7 +4029,7 @@ function isTopmostDialog(z) {
3908
4029
  setRegistry(key) {
3909
4030
  try {
3910
4031
  if (typeof localStorage !== "undefined") {
3911
- if (this.REGISTRY_OPTIONS[key]) {
4032
+ if (this.REGISTRY_OPTIONS[key] || (key && /^https?:\/\//i.test(key))) {
3912
4033
  localStorage.setItem("dsh-session-manager-registry", key);
3913
4034
  } else {
3914
4035
  localStorage.setItem("dsh-session-manager-registry", this.DEFAULT_REGISTRY);
@@ -3918,7 +4039,9 @@ function isTopmostDialog(z) {
3918
4039
  this._notify();
3919
4040
  },
3920
4041
  getRegistryUrl() {
3921
- return this.REGISTRY_OPTIONS[this.getRegistry()] || this.REGISTRY_OPTIONS[this.DEFAULT_REGISTRY];
4042
+ const reg = this.getRegistry();
4043
+ if (/^https?:\/\//i.test(reg)) return reg;
4044
+ return this.REGISTRY_OPTIONS[reg] || this.REGISTRY_OPTIONS[this.DEFAULT_REGISTRY];
3922
4045
  },
3923
4046
  async checkForUpdate(options = {}) {
3924
4047
  const { silent = false, openOnFound = true, manual = false } = options;
@@ -3927,6 +4050,7 @@ function isTopmostDialog(z) {
3927
4050
 
3928
4051
  this.state.status = "checking";
3929
4052
  this.state.error = null;
4053
+ this.state.notes = null;
3930
4054
  if (shouldOpenDialog) {
3931
4055
  this.state.dialogOpen = true;
3932
4056
  }
@@ -3951,12 +4075,15 @@ function isTopmostDialog(z) {
3951
4075
  }
3952
4076
  const cur = testOverride.currentVersion || this.state.currentVersion;
3953
4077
  const lat = testOverride.latestVersion || "9.9.9";
3954
- const hasUpdate = compareSemver(lat, cur) > 0;
3955
- res = { ok: true, result: { currentVersion: cur, latestVersion: lat, hasUpdate } };
4078
+ const src = testOverride.source || "npm";
4079
+ const hasUpdate = typeof testOverride.hasUpdate === "boolean"
4080
+ ? testOverride.hasUpdate
4081
+ : (src === "npm" && compareSemver(lat, cur) > 0);
4082
+ res = { ok: true, result: { currentVersion: cur, latestVersion: lat, hasUpdate, source: src } };
3956
4083
  } else {
4084
+ const fetchOpts = abortController ? { signal: abortController.signal } : undefined;
3957
4085
  const registryUrl = this.getRegistryUrl();
3958
4086
  const registryParam = encodeURIComponent(registryUrl);
3959
- const fetchOpts = abortController ? { signal: abortController.signal } : undefined;
3960
4087
  try {
3961
4088
  res = await fetchApi("/update/check?registry=" + registryParam, fetchOpts);
3962
4089
  } catch (fetchErr) {
@@ -3972,7 +4099,7 @@ function isTopmostDialog(z) {
3972
4099
  const cur = this.state.currentVersion;
3973
4100
  const lat = npmData.version;
3974
4101
  const hasUpdate = compareSemver(lat, cur) > 0;
3975
- res = { ok: true, result: { currentVersion: cur, latestVersion: lat, hasUpdate } };
4102
+ res = { ok: true, result: { currentVersion: cur, latestVersion: lat, hasUpdate, source: "npm" } };
3976
4103
  }
3977
4104
  }
3978
4105
  } catch (_) {
@@ -3989,24 +4116,51 @@ function isTopmostDialog(z) {
3989
4116
  }
3990
4117
 
3991
4118
  const result = res.result || {};
3992
- const currentVersion = result.currentVersion || this.state.currentVersion;
3993
- const latestVersion = result.latestVersion || currentVersion;
3994
- const hasUpdate = !!result.hasUpdate || compareSemver(latestVersion, currentVersion) > 0;
3995
-
3996
- this.state.currentVersion = currentVersion;
4119
+ const runningVersion = result.runningVersion || result.currentVersion || this.state.runningVersion || CURRENT_VERSION;
4120
+ const installedVersion = result.installedVersion || (this.state.installedVersion && compareSemver(this.state.installedVersion, runningVersion) > 0 ? this.state.installedVersion : runningVersion);
4121
+ const latestVersion = result.latestVersion || installedVersion;
4122
+ const source = result.source || "npm";
4123
+ const hasUpdate = typeof result.hasUpdate === "boolean" ? result.hasUpdate : compareSemver(latestVersion, installedVersion) > 0;
4124
+
4125
+ this.state.runningVersion = runningVersion;
4126
+ this.state.currentVersion = runningVersion;
4127
+ this.state.installedVersion = installedVersion;
3997
4128
  this.state.latestVersion = latestVersion;
4129
+ this.state.source = source;
4130
+ this.state.channelSwitch = result.channelSwitch || null;
3998
4131
  const now = Date.now();
3999
4132
  this.state.checkedAt = now;
4000
4133
  this.state.lastAttemptAt = now;
4001
4134
  this.lastFailedAt = null;
4002
4135
 
4003
- if (hasUpdate) {
4136
+ if (source === "linked") {
4137
+ this.state.notes = "update.source.linked";
4138
+ this.state.status = "idle";
4139
+ } else if (source === "github" && !hasUpdate) {
4140
+ this.state.notes = "update.source.github";
4141
+ this.state.status = "idle";
4142
+ } else if (source === "installation") {
4143
+ this.state.notes = "update.source.installation";
4144
+ this.state.status = "idle";
4145
+ } else if (source === "unknown") {
4146
+ this.state.notes = "update.source.unknown";
4147
+ this.state.status = "idle";
4148
+ } else if (compareSemver(installedVersion, runningVersion) > 0) {
4149
+ this.state.status = "done";
4150
+ this.state.application = "restart-required";
4151
+ this.state.notes = null;
4152
+ if (shouldOpenDialog) {
4153
+ this.state.dialogOpen = true;
4154
+ }
4155
+ } else if (hasUpdate) {
4004
4156
  this.state.status = "available";
4157
+ this.state.notes = null;
4005
4158
  if (openOnFound && !silent) {
4006
4159
  this.state.dialogOpen = true;
4007
4160
  }
4008
4161
  } else {
4009
4162
  this.state.status = "idle";
4163
+ this.state.notes = null;
4010
4164
  if (shouldOpenDialog) {
4011
4165
  this.state.dialogOpen = true;
4012
4166
  }
@@ -4029,7 +4183,7 @@ function isTopmostDialog(z) {
4029
4183
  }
4030
4184
  },
4031
4185
  async startUpdate(options = {}) {
4032
- const { openDialog = false } = options;
4186
+ const { openDialog = true } = options;
4033
4187
  if (this.state.status === "updating") return this.state;
4034
4188
  this.state.status = "updating";
4035
4189
  this.state.error = null;
@@ -4040,6 +4194,10 @@ function isTopmostDialog(z) {
4040
4194
 
4041
4195
  const targetVersion = this.state.latestVersion || "latest";
4042
4196
  const spec = "dsh-session-manager@" + targetVersion;
4197
+ const registryUrl = this.getRegistryUrl();
4198
+ const clientTaskId = "update-" + Date.now() + "-" + Math.random().toString(36).slice(2, 8);
4199
+ let activeTaskId = clientTaskId;
4200
+ this.state.taskId = activeTaskId;
4043
4201
 
4044
4202
  try {
4045
4203
  let outcome = null;
@@ -4055,31 +4213,127 @@ function isTopmostDialog(z) {
4055
4213
  outcome = { application: testOverride.application || "restart-required" };
4056
4214
  }
4057
4215
  } else {
4058
- const remotePm = activeClientCtx?.remote?.pluginManager || activeClientCtx?.pluginManager;
4059
- if (remotePm && typeof remotePm.installBundle === "function") {
4060
- outcome = await remotePm.installBundle(spec);
4061
- } else {
4062
- const res = await callApi("/update/install", { version: targetVersion, registry: this.getRegistryUrl() });
4063
- if (!res || !res.ok) {
4064
- throw new Error(res?.error || "安装更新失败");
4065
- }
4216
+ let res = null;
4217
+ let shouldPoll = false;
4218
+ let callError = null;
4219
+ try {
4220
+ res = await callApi("/update/install", { version: targetVersion, registry: registryUrl, taskId: clientTaskId });
4221
+ } catch (err) {
4222
+ shouldPoll = true;
4223
+ callError = err;
4224
+ }
4225
+
4226
+ if (res && res.ok) {
4066
4227
  outcome = res.result;
4228
+ } else if (res && !res.ok) {
4229
+ if (res.code === "update-in-progress" && res.taskId) {
4230
+ // Another update task is already running: adopt its taskId and poll
4231
+ activeTaskId = res.taskId;
4232
+ this.state.taskId = activeTaskId;
4233
+ shouldPoll = true;
4234
+ } else {
4235
+ const errObj = new Error(res.error || "安装更新失败");
4236
+ if (res.rolledBack) {
4237
+ errObj.rolledBack = true;
4238
+ errObj.reason = res.error;
4239
+ errObj.currentVersion = this.state.currentVersion;
4240
+ }
4241
+ throw errObj;
4242
+ }
4243
+ }
4244
+
4245
+ if (shouldPoll) {
4246
+ const pollStart = Date.now();
4247
+ const maxPollMs = (typeof window !== "undefined" && typeof window.__DSH_SM_POLL_TIMEOUT_MS__ === "number")
4248
+ ? window.__DSH_SM_POLL_TIMEOUT_MS__
4249
+ : 90000;
4250
+ let reconciled = false;
4251
+ while (Date.now() - pollStart < maxPollMs) {
4252
+ await new Promise((resolve) => {
4253
+ const timer = setTimeout(resolve, 1500);
4254
+ if (typeof timer === "number" && timer === 1) {
4255
+ Promise.resolve().then(resolve);
4256
+ }
4257
+ });
4258
+
4259
+ let statusData = null;
4260
+ try {
4261
+ statusData = await fetchApi("/update/status?taskId=" + encodeURIComponent(activeTaskId));
4262
+ } catch (netErr) {
4263
+ // Only network or HTTP transport errors during status fetching allow polling retry
4264
+ continue;
4265
+ }
4266
+
4267
+ if (statusData && statusData.ok && statusData.result) {
4268
+ const r = statusData.result;
4269
+ if (r.status === "completed") {
4270
+ outcome = {
4271
+ application: r.application || "restart-required",
4272
+ spec,
4273
+ version: r.targetVersion || targetVersion,
4274
+ taskId: activeTaskId,
4275
+ alreadyInstalled: !!r.alreadyInstalled
4276
+ };
4277
+ reconciled = true;
4278
+ break;
4279
+ } else if (r.status === "rolled-back" || (r.status === "failed" && r.rolledBack)) {
4280
+ const rollErr = new Error((r.error || "安装更新失败") + "(已自动恢复旧版本 v" + (r.currentVersion || "") + ")");
4281
+ rollErr.rolledBack = true;
4282
+ rollErr.currentVersion = r.currentVersion || this.state.currentVersion;
4283
+ rollErr.reason = r.error;
4284
+ throw rollErr;
4285
+ } else if (r.status === "recovery-required") {
4286
+ throw new Error("更新失败且自动恢复旧版本失败: " + (r.rollbackError || r.error || "未知原因") + "。请手动重新安装插件。");
4287
+ } else if (r.status === "failed") {
4288
+ throw new Error(r.error || "安装更新失败");
4289
+ } else if (r.status === "superseded") {
4290
+ throw new Error("更新任务已被其他操作覆盖,请重新检查更新");
4291
+ }
4292
+ }
4293
+ }
4294
+
4295
+ if (!reconciled) {
4296
+ throw new Error("更新结果尚未确认,请刷新页面或重启 DSH 查看状态");
4297
+ }
4067
4298
  }
4068
4299
  }
4069
4300
 
4070
4301
  const application = outcome?.application || "restart-required";
4071
4302
  this.state.status = "done";
4072
4303
  this.state.application = application;
4304
+ this.state.taskId = activeTaskId;
4305
+ const installedV = outcome?.version || targetVersion;
4306
+ this.state.installedVersion = installedV;
4307
+ this.state.latestVersion = installedV;
4308
+ // Running version is NOT modified before restart/reload!
4309
+ this.state.runningVersion = CURRENT_VERSION;
4310
+ this.state.currentVersion = CURRENT_VERSION;
4311
+ this.state.rollbackInfo = null;
4312
+ this.state.error = null;
4313
+ if (openDialog || this.state.dialogOpen) {
4314
+ this.state.dialogOpen = true;
4315
+ }
4073
4316
  this._notify();
4074
4317
  return outcome;
4075
4318
  } catch (err) {
4076
4319
  this.state.status = "error";
4077
- this.state.error = err.message || "更新安装失败";
4078
- if (openDialog) {
4320
+ if (err.rolledBack) {
4321
+ this.state.rollbackInfo = {
4322
+ rolledBack: true,
4323
+ currentVersion: err.currentVersion || this.state.currentVersion,
4324
+ reason: err.reason || err.message,
4325
+ taskId: activeTaskId
4326
+ };
4327
+ this.state.error = err.message || ("安装更新失败(已自动恢复旧版本 v" + (err.currentVersion || this.state.currentVersion) + ")");
4328
+ } else {
4329
+ this.state.rollbackInfo = null;
4330
+ this.state.error = err.message || "更新安装失败";
4331
+ }
4332
+ if (openDialog || this.state.dialogOpen) {
4079
4333
  this.state.dialogOpen = true;
4080
4334
  }
4081
4335
  this._notify();
4082
- return { ok: false, error: this.state.error };
4336
+ return { ok: false, error: this.state.error, rolledBack: !!err.rolledBack };
4083
4337
  }
4084
4338
  }
4085
4339
  };
@@ -6140,6 +6394,9 @@ function isTopmostDialog(z) {
6140
6394
  () => updateStore.getSnapshot(),
6141
6395
  () => updateStore.getSnapshot()
6142
6396
  );
6397
+ useEffect(() => {
6398
+ try { updateStore.checkRecentStatus().catch(() => {}); } catch (_) {}
6399
+ }, []);
6143
6400
  const btnClass = "sm-footerBtn" + (wide ? " sm-footerBtn-wide" : " sm-footerBtn-rail");
6144
6401
  const btnLabel = t("footer.aria");
6145
6402
  return h(React.Fragment, null,
@@ -6253,8 +6510,8 @@ function isTopmostDialog(z) {
6253
6510
  let dialogTitle = t("update.title");
6254
6511
  if (isAvailable) dialogTitle = t("update.newVersion");
6255
6512
  else if (isUpdating) dialogTitle = t("update.installing");
6256
- else if (isDone) dialogTitle = t("update.title");
6257
- else if (isError) dialogTitle = t("update.error");
6513
+ else if (isDone) dialogTitle = t("update.done.title");
6514
+ else if (isError) dialogTitle = updateState.rollbackInfo ? t("update.rolledBack.title") : t("update.error");
6258
6515
 
6259
6516
  const handleClose = () => {
6260
6517
  if (!isUpdating) {
@@ -6286,22 +6543,64 @@ function isTopmostDialog(z) {
6286
6543
  }, h(CloseIcon, { size: 14 }))) : null
6287
6544
  ),
6288
6545
  h("div", { className: "sm-nativeDialogBody" },
6289
- h("div", { className: "sm-updateVersionGrid" },
6290
- h("span", { className: "sm-updateVersionLabel" }, t("update.currentVersion")),
6291
- h("span", { className: "sm-updateVersionValue" }, "v" + updateState.currentVersion),
6292
- h("span", { className: "sm-updateVersionLabel" }, t("update.latestVersion")),
6293
- h("span", { className: "sm-updateVersionValue" }, updateState.latestVersion ? "v" + updateState.latestVersion : (isChecking ? t("update.checking") : "-"))
6294
- ),
6295
- isChecking ? h("div", { className: "sm-updateStatusBox" }, t("update.checking")) : null,
6296
- isAvailable ? h("div", { className: "sm-updateStatusBox sm-warn" }, t("update.newVersion") + " (v" + updateState.latestVersion + ")") : null,
6297
- isUpdating ? h("div", { className: "sm-updateStatusBox sm-warn" }, t("update.installing")) : null,
6298
- isDone ? h("div", { className: "sm-updateStatusBox sm-success" },
6299
- updateState.application === "applied"
6300
- ? t("update.done.applied")
6301
- : t("update.done.restart", { from: updateState.currentVersion, to: updateState.latestVersion || updateState.currentVersion })
6302
- ) : null,
6303
- isError ? h("div", { className: "sm-updateStatusBox sm-error" }, (updateState.error || t("update.error"))) : null,
6304
- (isIdle && updateState.latestVersion && !isAvailable) ? h("div", { className: "sm-updateStatusBox sm-success" }, t("update.isLatest")) : null
6546
+ (() => {
6547
+ const runningV = updateState.currentVersion || updateState.runningVersion || CURRENT_VERSION;
6548
+ const hasInstalledDiff = !!(updateState.installedVersion && compareSemver(updateState.installedVersion, runningV) > 0);
6549
+ const targetV = hasInstalledDiff ? updateState.installedVersion : (updateState.latestVersion || runningV);
6550
+ return h("div", { className: "sm-updateVersionGrid" },
6551
+ h("span", { className: "sm-updateVersionLabel" }, hasInstalledDiff ? t("update.runningVersion") : t("update.currentVersion")),
6552
+ h("span", { className: "sm-updateVersionValue" }, "v" + runningV),
6553
+ h("span", { className: "sm-updateVersionLabel" }, hasInstalledDiff ? t("update.installedVersion") : t("update.latestVersion")),
6554
+ h("span", { className: "sm-updateVersionValue" }, hasInstalledDiff
6555
+ ? ("v" + updateState.installedVersion)
6556
+ : (updateState.latestVersion ? "v" + updateState.latestVersion : (isChecking ? t("update.checking") : "-")))
6557
+ );
6558
+ })(),
6559
+ (() => {
6560
+ const runningV = updateState.currentVersion || updateState.runningVersion || CURRENT_VERSION;
6561
+ const hasInstalledDiff = !!(updateState.installedVersion && compareSemver(updateState.installedVersion, runningV) > 0);
6562
+ const targetV = hasInstalledDiff ? updateState.installedVersion : (updateState.latestVersion || runningV);
6563
+ let statusBox = null;
6564
+ if (isChecking) {
6565
+ statusBox = h("div", { className: "sm-updateStatusBox sm-checking" }, t("update.checking"));
6566
+ } else if (isUpdating) {
6567
+ statusBox = h("div", { className: "sm-updateStatusBox sm-warn" }, t("update.installing"));
6568
+ } else if (isDone) {
6569
+ statusBox = h("div", { className: "sm-updateStatusBox sm-success", style: { textAlign: "left", whiteSpace: "pre-wrap" } },
6570
+ updateState.application === "applied" && runningV === targetV
6571
+ ? t("update.done.applied", { to: targetV, version: targetV })
6572
+ : (updateState.application === "refresh-required"
6573
+ ? t("update.done.refresh", { from: runningV, to: targetV })
6574
+ : t("update.done.restart", { from: runningV, to: targetV })
6575
+ )
6576
+ );
6577
+ } else if (isError) {
6578
+ if (updateState.rollbackInfo) {
6579
+ statusBox = h("div", { className: "sm-updateStatusBox sm-warn", style: { textAlign: "left", whiteSpace: "pre-wrap" } },
6580
+ h("div", { style: { fontWeight: "bold", marginBottom: "4px" } },
6581
+ t("update.rolledBack.title")
6582
+ ),
6583
+ h("div", null,
6584
+ t("update.rolledBack", { version: updateState.rollbackInfo.currentVersion })
6585
+ ),
6586
+ h("div", { style: { marginTop: "4px", opacity: 0.9, fontSize: "12px" } },
6587
+ t("update.rolledBack.reason", { reason: updateState.rollbackInfo.reason })
6588
+ )
6589
+ );
6590
+ } else {
6591
+ statusBox = h("div", { className: "sm-updateStatusBox sm-error", style: { textAlign: "left", whiteSpace: "pre-wrap" } },
6592
+ (updateState.error || t("update.error"))
6593
+ );
6594
+ }
6595
+ } else if (isAvailable) {
6596
+ statusBox = h("div", { className: "sm-updateStatusBox sm-warn" }, t("update.newVersion") + " (v" + updateState.latestVersion + ")");
6597
+ } else if (updateState.notes) {
6598
+ statusBox = h("div", { className: "sm-updateStatusBox sm-warn" }, t(updateState.notes));
6599
+ } else if (isIdle && updateState.latestVersion) {
6600
+ statusBox = h("div", { className: "sm-updateStatusBox sm-success" }, t("update.isLatest"));
6601
+ }
6602
+ return statusBox || h("div", { className: "sm-updateStatusBox sm-placeholder", style: { visibility: "hidden" } }, "-");
6603
+ })()
6305
6604
  ),
6306
6605
  h("div", { className: "sm-nativeDialogFooter" },
6307
6606
  isAvailable ? h(React.Fragment, null,
@@ -6313,7 +6612,7 @@ function isTopmostDialog(z) {
6313
6612
  h("button", {
6314
6613
  type: "button",
6315
6614
  className: "sm-nativeDialogButton sm-nativeDialogConfirm",
6316
- onClick: () => updateStore.startUpdate()
6615
+ onClick: () => updateStore.startUpdate({ openDialog: true })
6317
6616
  }, t("update.btn.update", { version: updateState.latestVersion }))
6318
6617
  ) : null,
6319
6618
  isUpdating ? h("button", {
@@ -6337,7 +6636,7 @@ function isTopmostDialog(z) {
6337
6636
  className: "sm-nativeDialogButton sm-nativeDialogConfirm",
6338
6637
  onClick: () => {
6339
6638
  if (updateState.latestVersion && compareSemver(updateState.latestVersion, updateState.currentVersion) > 0) {
6340
- updateStore.startUpdate();
6639
+ updateStore.startUpdate({ openDialog: true });
6341
6640
  } else {
6342
6641
  updateStore.checkForUpdate({ manual: true, openDialog: true });
6343
6642
  }
@@ -6350,11 +6649,15 @@ function isTopmostDialog(z) {
6350
6649
  className: "sm-nativeDialogButton sm-nativeDialogCancel",
6351
6650
  onClick: handleClose
6352
6651
  }, t("update.btn.close")),
6353
- !isChecking ? h("button", {
6652
+ h("button", {
6354
6653
  type: "button",
6355
- className: "sm-nativeDialogButton",
6356
- onClick: () => updateStore.checkForUpdate({ manual: true, openDialog: true })
6357
- }, t("update.btn.check")) : null
6654
+ className: "sm-nativeDialogButton sm-nativeDialogConfirm",
6655
+ disabled: isChecking,
6656
+ style: { minWidth: "96px", whiteSpace: "nowrap" },
6657
+ onClick: () => {
6658
+ if (!isChecking) updateStore.checkForUpdate({ manual: true, openDialog: true });
6659
+ }
6660
+ }, isChecking ? t("update.checking") : t("update.btn.check"))
6358
6661
  ) : null
6359
6662
  )
6360
6663
  )
@@ -6391,12 +6694,20 @@ function isTopmostDialog(z) {
6391
6694
  updateStore.setDialogOpacity(e.target.value);
6392
6695
  };
6393
6696
 
6697
+ useEffect(() => {
6698
+ try { updateStore.checkRecentStatus().catch(() => {}); } catch (_) {}
6699
+ }, []);
6700
+
6701
+ const runningV = updateState.currentVersion || updateState.runningVersion || CURRENT_VERSION;
6702
+ const hasInstalledDiff = !!(updateState.installedVersion && compareSemver(updateState.installedVersion, runningV) > 0);
6703
+ const targetV = hasInstalledDiff ? updateState.installedVersion : (updateState.latestVersion || runningV);
6704
+
6394
6705
  return h("div", { className: "sm-settingsCard" },
6395
6706
  h("div", { className: "sm-settingsHeader" },
6396
6707
  h("h3", { className: "sm-settingsTitle" },
6397
6708
  h(NpmIcon, { size: 18 }),
6398
6709
  h("span", null, t("settings.title")),
6399
- h("span", { className: "sm-settingsTag" }, "v" + updateState.currentVersion)
6710
+ h("span", { className: "sm-settingsTag" }, "v" + runningV)
6400
6711
  )
6401
6712
  ),
6402
6713
  h("div", { className: "sm-settingsSection" },
@@ -6413,31 +6724,48 @@ function isTopmostDialog(z) {
6413
6724
  type: "button",
6414
6725
  className: "sm-nativeDialogButton sm-nativeDialogConfirm sm-settingsCheckBtn",
6415
6726
  disabled: isUpdating,
6416
- onClick: () => updateStore.startUpdate({ openDialog: false })
6727
+ onClick: () => updateStore.startUpdate({ openDialog: true })
6417
6728
  }, isUpdating ? t("update.btn.updating") : t("update.btn.update", { version: updateState.latestVersion })) : null
6418
6729
  )
6419
6730
  ),
6420
6731
  h("div", { className: "sm-settingsRow" },
6421
- h("span", null, t("update.currentVersion")),
6422
- h("span", { style: { fontFamily: "monospace" } }, "v" + updateState.currentVersion)
6732
+ h("span", null, hasInstalledDiff ? t("update.runningVersion") : t("update.currentVersion")),
6733
+ h("span", { style: { fontFamily: "monospace" } }, "v" + runningV)
6423
6734
  ),
6735
+ hasInstalledDiff ? h("div", { className: "sm-settingsRow" },
6736
+ h("span", null, t("update.installedVersion")),
6737
+ h("span", { style: { fontFamily: "monospace" } }, "v" + updateState.installedVersion)
6738
+ ) : null,
6424
6739
  h("div", { className: "sm-settingsRow" },
6425
6740
  h("span", null, t("update.latestVersion")),
6426
6741
  h("span", { style: { fontFamily: "monospace" } },
6427
6742
  updateState.latestVersion ? "v" + updateState.latestVersion : (isChecking ? t("update.checking") : "-")
6428
6743
  )
6429
6744
  ),
6745
+ updateState.notes ? h("div", { className: "sm-updateStatusBox sm-warn" },
6746
+ t(updateState.notes)
6747
+ ) : null,
6430
6748
  updateState.status === "available" ? h("div", { className: "sm-updateStatusBox sm-warn" },
6431
6749
  t("update.newVersion") + " (v" + updateState.latestVersion + ")"
6432
6750
  ) : null,
6433
6751
  updateState.status === "updating" ? h("div", { className: "sm-updateStatusBox sm-warn" },
6434
6752
  t("update.installing")
6435
6753
  ) : null,
6436
- updateState.status === "done" ? h("div", { className: "sm-updateStatusBox sm-success" },
6437
- updateState.application === "applied" ? t("update.done.applied") : t("update.done.restart", { from: updateState.currentVersion, to: updateState.latestVersion || updateState.currentVersion })
6754
+ updateState.status === "done" ? h("div", { className: "sm-updateStatusBox sm-success", style: { whiteSpace: "pre-wrap" } },
6755
+ updateState.application === "applied" && runningV === targetV
6756
+ ? t("update.done.applied", { to: targetV, version: targetV })
6757
+ : (updateState.application === "refresh-required"
6758
+ ? t("update.done.refresh", { from: runningV, to: targetV })
6759
+ : t("update.done.restart", { from: runningV, to: targetV })
6760
+ )
6438
6761
  ) : null,
6439
- updateState.status === "error" ? h("div", { className: "sm-updateStatusBox sm-error" },
6440
- updateState.error || t("update.error")
6762
+ updateState.status === "error" ? h("div", {
6763
+ className: "sm-updateStatusBox " + (updateState.rollbackInfo ? "sm-warn" : "sm-error"),
6764
+ style: { whiteSpace: "pre-wrap" }
6765
+ },
6766
+ updateState.rollbackInfo
6767
+ ? (t("update.rolledBack", { version: updateState.rollbackInfo.currentVersion }) + "\n" + t("update.rolledBack.reason", { reason: updateState.rollbackInfo.reason }))
6768
+ : (updateState.error || t("update.error"))
6441
6769
  ) : null
6442
6770
  ),
6443
6771
  h("div", { className: "sm-settingsSection" },
@@ -6463,7 +6791,8 @@ function isTopmostDialog(z) {
6463
6791
  "aria-label": t("settings.registry")
6464
6792
  },
6465
6793
  h("option", { value: "registry.npmjs.org" }, t("settings.registry.npm")),
6466
- h("option", { value: "registry.npmmirror.com" }, t("settings.registry.china"))
6794
+ h("option", { value: "registry.npmmirror.com" }, t("settings.registry.china")),
6795
+ h("option", { value: "127.0.0.1:4873" }, t("settings.registry.local"))
6467
6796
  )
6468
6797
  ),
6469
6798
  h("div", { className: "sm-settingsRow" },
@@ -6765,6 +7094,12 @@ function isTopmostDialog(z) {
6765
7094
  inject: () => actions()
6766
7095
  }, SessionManagerSettingsCard));
6767
7096
 
7097
+ ctx.effect(() => {
7098
+ try {
7099
+ updateStore.checkRecentStatus().catch(() => {});
7100
+ } catch (_) {}
7101
+ }, "session-manager: check update status on boot");
7102
+
6768
7103
  ctx.effect(() => {
6769
7104
  if (!updateStore.getAutoCheck()) return;
6770
7105
  const timer = setTimeout(() => {