dsh-plugin 1.2.0 → 1.3.0

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.
Files changed (96) hide show
  1. package/README.en.md +20 -15
  2. package/README.md +21 -16
  3. package/client/client.js +684 -446
  4. package/client/client.js.map +1 -1
  5. package/cordis.patch.yml +1 -1
  6. package/lib/http/routes.js +47 -3
  7. package/lib/index.js +1 -1
  8. package/lib/services/install/install-types.js +1 -1
  9. package/lib/services/install/npm-check.js +1 -1
  10. package/lib/services/install/npm-resolve.js +1 -1
  11. package/lib/services/install/preflight.js +1 -1
  12. package/lib/services/install/task-queue.js +1 -1
  13. package/lib/services/loader.js +1 -1
  14. package/lib/services/log.js +1 -1
  15. package/lib/services/probe.js +1 -1
  16. package/lib/services/profile/installed-versions.js +1 -1
  17. package/lib/services/profile/profile.js +6 -5
  18. package/lib/services/profile/progress.js +1 -1
  19. package/lib/services/settings.js +1 -1
  20. package/lib/types/index.d.ts +1 -1
  21. package/lib/types/services/install/install-types.d.ts +1 -1
  22. package/lib/types/services/install/install.d.ts +1 -1
  23. package/lib/types/services/profile/pending-restart.d.ts +1 -1
  24. package/lib/types/services/profile/progress.d.ts +1 -1
  25. package/package.json +2 -2
  26. package/src/client/components/PluginHubSection.tsx +83 -13
  27. package/src/client/components/catalog/CatalogList.tsx +1 -1
  28. package/src/client/components/catalog/PluginCard.tsx +1 -1
  29. package/src/client/components/layout/CatalogControls.tsx +1 -1
  30. package/src/client/components/layout/CatalogHeader.tsx +2 -2
  31. package/src/client/components/layout/CategoryTabs.tsx +1 -1
  32. package/src/client/components/layout/SectionTabs.tsx +1 -1
  33. package/src/client/components/modals/AboutModal.tsx +2 -2
  34. package/src/client/components/modals/HubUpdateModal.tsx +1 -1
  35. package/src/client/components/modals/InstalledDetailModal.tsx +1 -1
  36. package/src/client/components/modals/LogsModal.tsx +1 -1
  37. package/src/client/components/modals/LogsPathModal.tsx +1 -1
  38. package/src/client/components/modals/NotificationsModal.tsx +11 -2
  39. package/src/client/components/modals/ProgressView.tsx +1 -1
  40. package/src/client/components/modals/modals.tsx +1 -1
  41. package/src/client/components/ui/Dropdown.tsx +1 -1
  42. package/src/client/components/ui/Toggle.tsx +1 -1
  43. package/src/client/components/ui/icons.tsx +22 -1
  44. package/src/client/components/views/CustomInstallView.tsx +133 -44
  45. package/src/client/components/views/DiagnosticsView.tsx +2 -2
  46. package/src/client/components/views/InstalledView.tsx +1 -1
  47. package/src/client/components/views/LogsView.tsx +1 -1
  48. package/src/client/components/views/MarketView.tsx +1 -1
  49. package/src/client/components/views/SettingsView.tsx +1 -1
  50. package/src/client/data/catalog.ts +1 -1
  51. package/src/client/data/host.ts +1 -1
  52. package/src/client/data/hub.ts +12 -3
  53. package/src/client/hooks/useCatalog.ts +19 -6
  54. package/src/client/hooks/useIncrementalList.ts +1 -1
  55. package/src/client/hooks/useLanguage.ts +1 -1
  56. package/src/client/hooks/useSettings.ts +1 -1
  57. package/src/client/hooks/useTaskQueue.ts +1 -1
  58. package/src/client/index.tsx +1 -1
  59. package/src/client/locales.ts +50 -30
  60. package/src/client/logic/constants.ts +8 -9
  61. package/src/client/logic/failures.ts +48 -2
  62. package/src/client/logic/format.ts +1 -1
  63. package/src/client/logic/install-command.ts +1 -1
  64. package/src/client/logic/installed.ts +1 -1
  65. package/src/client/logic/normalize.ts +1 -1
  66. package/src/client/logic/renderMarkdown.ts +1 -1
  67. package/src/client/logic/urls.ts +2 -2
  68. package/src/client/styles/CustomInstallView.module.css +59 -1
  69. package/src/client/styles/DiagnosticsView.module.css +1 -1
  70. package/src/client/styles/Dropdown.module.css +1 -1
  71. package/src/client/styles/Header.module.css +1 -1
  72. package/src/client/styles/InstalledView.module.css +19 -6
  73. package/src/client/styles/List.module.css +11 -1
  74. package/src/client/styles/LogsView.module.css +1 -1
  75. package/src/client/styles/Modal.module.css +32 -1
  76. package/src/client/styles/SectionTabs.module.css +1 -1
  77. package/src/client/styles/SettingsView.module.css +1 -1
  78. package/src/client/styles/Toggle.module.css +1 -1
  79. package/src/client/styles/tokens.module.css +32 -5
  80. package/src/client/types.ts +1 -1
  81. package/src/server/http/routes.ts +54 -3
  82. package/src/server/index.ts +1 -1
  83. package/src/server/services/install/install-types.ts +1 -1
  84. package/src/server/services/install/install.ts +1 -1
  85. package/src/server/services/install/npm-check.ts +1 -1
  86. package/src/server/services/install/npm-resolve.ts +1 -1
  87. package/src/server/services/install/preflight.ts +1 -1
  88. package/src/server/services/install/task-queue.ts +1 -1
  89. package/src/server/services/loader.ts +1 -1
  90. package/src/server/services/log.ts +1 -1
  91. package/src/server/services/probe.ts +1 -1
  92. package/src/server/services/profile/installed-versions.ts +1 -1
  93. package/src/server/services/profile/pending-restart.ts +1 -1
  94. package/src/server/services/profile/profile.ts +6 -5
  95. package/src/server/services/profile/progress.ts +1 -1
  96. package/src/server/services/settings.ts +1 -1
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
- # dsh bundle patch: inserts the DSH-Plugin section into a profile's layer stack.
1
+ # dsh bundle patch: inserts the DSH Plugin section into a profile's layer stack.
2
2
  - insert:
3
3
  - id: dsh-plugin
4
4
  name: 'dsh-plugin'
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -8,7 +8,7 @@
8
8
  * install handler validates the target, then spawns the official dsh CLI
9
9
  * (see services/install/install.ts) and reports the captured result back.
10
10
  */
11
- import { mkdirSync, readFileSync } from 'node:fs';
11
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
12
12
  import { homedir, release } from 'node:os';
13
13
  import { dirname, join, resolve } from 'node:path';
14
14
  import { spawn } from 'node:child_process';
@@ -23,6 +23,36 @@ import { appendLog, readLog, logFilePath, defaultLogFilePath, customLogFile } fr
23
23
  const PROFILE_RE = /^[A-Za-z0-9_-]+$/;
24
24
  const BODY_LIMIT_BYTES = 4 * 1024;
25
25
  const COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
26
+ /** 目录/统计数据代理的本地缓存时长:1 小时内重复打开插件中心直接读盘,
27
+ * 不重复走 curl 拉远程 —— 重启宿主后首次打开同样秒开(缓存跨重启存活)。 */
28
+ const CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
29
+ function catalogCacheFile(profile, key) {
30
+ return join(profileDirectory(profile), 'cache', `catalog-${key}.json`);
31
+ }
32
+ /** 读缓存:文件存在、JSON 合法、未过期 → 返回原始响应字符串;否则 null(视为 miss)。 */
33
+ function readCatalogCache(file) {
34
+ try {
35
+ const entry = JSON.parse(readFileSync(file, 'utf8'));
36
+ if (entry && typeof entry.at === 'number' && typeof entry.body === 'string'
37
+ && Date.now() - entry.at < CATALOG_CACHE_TTL_MS) {
38
+ return entry.body;
39
+ }
40
+ }
41
+ catch {
42
+ // 文件缺失 / 解析失败 / 字段不完整 → miss,重新拉取覆盖写盘
43
+ }
44
+ return null;
45
+ }
46
+ /** 写缓存:目录不存在则创建;写失败静默忽略(缓存只是加速,不影响功能)。 */
47
+ function writeCatalogCache(file, body) {
48
+ try {
49
+ mkdirSync(dirname(file), { recursive: true });
50
+ writeFileSync(file, JSON.stringify({ at: Date.now(), body }));
51
+ }
52
+ catch {
53
+ // 磁盘只读/无权限等极端情况:忽略,继续走直连
54
+ }
55
+ }
26
56
  function profileDirectory(profile) {
27
57
  return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', profile);
28
58
  }
@@ -439,9 +469,22 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
439
469
  ? settings.proxy
440
470
  : (systemProxy() ?? process.env.HTTPS_PROXY ?? process.env.https_proxy ?? '');
441
471
  const isStats = url.searchParams.get('stats') === '1';
472
+ const lang = url.searchParams.get('lang') === 'en' ? 'en' : 'zh';
442
473
  const target = isStats
443
474
  ? 'https://api.dsh-plugin.org/stats.json'
444
- : `https://api.dsh-plugin.org/plugins.${url.searchParams.get('lang') === 'en' ? 'en' : 'zh'}.json`;
475
+ : `https://api.dsh-plugin.org/plugins.${lang}.json`;
476
+ // 1 小时本地缓存:命中直接返回(跨重启生效,插件中心秒开),未命中才走 curl 拉远程
477
+ const cacheFile = catalogCacheFile(profile, isStats ? 'stats' : `plugins-${lang}`);
478
+ const cached = readCatalogCache(cacheFile);
479
+ if (cached !== null) {
480
+ try {
481
+ sendJson(response, 200, JSON.parse(cached));
482
+ return;
483
+ }
484
+ catch {
485
+ // 缓存正文损坏:忽略,走重新拉取并覆盖写盘
486
+ }
487
+ }
445
488
  const r = await fetchViaCurl(target, proxy, 20000);
446
489
  if (!r.ok || r.body === '') {
447
490
  sendJson(response, 502, { error: 'catalog fetch failed' });
@@ -449,6 +492,7 @@ export function mountPluginHubRoutes(webServer, profile, loader) {
449
492
  }
450
493
  try {
451
494
  sendJson(response, 200, JSON.parse(r.body));
495
+ writeCatalogCache(cacheFile, r.body);
452
496
  }
453
497
  catch {
454
498
  sendJson(response, 502, { error: 'catalog fetch returned invalid JSON' });
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -27,11 +27,12 @@ export function githubTarget(repo) {
27
27
  return `git+https://github.com/${repo}.git`;
28
28
  }
29
29
  /**
30
- * DSH 命令行前缀(`dsh plugin [--profile|-p <p>] add <target>`)—— 提取其中的安装目标;
31
- * 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 段可选,
32
- * 支持 `-p` 简写与 `--profile=web` 等号形式,命令大小写不敏感。
30
+ * DSH 命令行前缀(官方唯一形式 `dsh plugin --profile <name> add <target>`)—— 提取其中的安装目标;
31
+ * 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 必填且只有长选项
32
+ * (官方 CLI 无 -p 简写,省略即报错),支持 `--profile=<name>` 等号形式,命令大小写不敏感。
33
+ * 与客户端 CustomInstallView 口径一致。
33
34
  */
34
- const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+(?:(?:--profile|-p)(?:=|\s+)\S+\s*)?add\s+(.+)$/i;
35
+ const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+--profile(?:\s+|=)\S+\s+add\s+(.+)$/i;
35
36
  export function installTargetOf(value) {
36
37
  const input = typeof value === 'string' ? value.trim() : '';
37
38
  const match = DSH_PLUGIN_CMD_RE.exec(input);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 4000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):4000+ 人工精选社区插件,每日更新。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,7 +14,7 @@
14
14
  "bugs": {
15
15
  "url": "https://github.com/dshplugin/dsh-plugin-hub/issues"
16
16
  },
17
- "author": "DSH-Plugin Hub contributors",
17
+ "author": "DSH Plugin Hub contributors",
18
18
  "funding": {
19
19
  "type": "custom",
20
20
  "url": "https://dsh-plugin.org"
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -9,7 +9,9 @@
9
9
  * folder; it hosts the dialogs/toast and the section-level copy actions.
10
10
  */
11
11
  import { createElement as h, useEffect, useRef, useState } from 'react'
12
+ import type { MouseEvent } from 'react'
12
13
  import styles from '../styles/Header.module.css'
14
+ import modalStyles from '../styles/Modal.module.css'
13
15
  import type { EnvInfo, HubPlugin, SectionProps, ToastState } from '../types.ts'
14
16
  import { installCommandOf, repoFromInstallTarget } from '../logic/install-command.ts'
15
17
  import { PLUGIN_VERSION } from '../logic/constants.ts'
@@ -23,7 +25,7 @@ import { AboutModal } from './modals/AboutModal.tsx'
23
25
  import { HubUpdateModal } from './modals/HubUpdateModal.tsx'
24
26
  import { InstalledDetailModal } from './modals/InstalledDetailModal.tsx'
25
27
  import { NotificationsModal } from './modals/NotificationsModal.tsx'
26
- import { addFailure, addSuccess, addUpdateNotice, clearNotifications, loadNotifications, removeNotification } from '../logic/failures.ts'
28
+ import { addFailure, addSuccess, addUpdateNotice, clearNotifications, ignoreUpdate, loadIgnoredUpdates, loadNotifications, removeNotification, removeUpdateNotice } from '../logic/failures.ts'
27
29
  import type { NotificationRecord } from '../logic/failures.ts'
28
30
  import { CatalogHeader } from './layout/CatalogHeader.tsx'
29
31
  import { MarketView } from './views/MarketView.tsx'
@@ -32,6 +34,7 @@ import type { SectionView } from './layout/SectionTabs.tsx'
32
34
  import { InstalledView } from './views/InstalledView.tsx'
33
35
  import { CustomInstallView } from './views/CustomInstallView.tsx'
34
36
  import { SettingsView } from './views/SettingsView.tsx'
37
+ import { CloseIcon } from './ui/icons.tsx'
35
38
  import type { InstalledItem } from '../logic/installed.ts'
36
39
  import { pluginOfItem } from '../logic/installed.ts'
37
40
 
@@ -86,6 +89,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
86
89
  const [notifications, setNotifications] = useState<NotificationRecord[]>(() => loadNotifications())
87
90
  /** 通知中心弹窗:一级导航「设置」tab 后边的铃铛入口按钮打开 */
88
91
  const [showNotifications, setShowNotifications] = useState(false)
92
+ /** 「忽略本次更新」确认弹窗:{ repo, version };null = 关闭 */
93
+ const [ignoreTarget, setIgnoreTarget] = useState<{ repo: string; version?: string } | null>(null)
89
94
  /** 宿主机器环境快照:提交 bug 的 issue 正文附带;取不到为 null(链接少环境段,不阻塞) */
90
95
  const [env, setEnv] = useState<EnvInfo | null>(null)
91
96
  useEffect(() => {
@@ -128,6 +133,28 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
128
133
  },
129
134
  })
130
135
 
136
+ /** 补齐更新提醒:把「当前有更新但通知中心还没有」的插件补写进通知(repo+version 去重)。
137
+ * 返回本次实际新增的条数(0 = 无可更新或全部已提示过)。启动检查与打开通知中心共用,
138
+ * 保证通知中心始终反映可更新状态:同一版本只一条、清空/删除后重开恢复、更新完成后消失。 */
139
+ const syncUpdateNotices = (): number => {
140
+ const updatable = catalog.installedItems.filter((i) => i.hasUpdate && i.plugin)
141
+ if (updatable.length === 0) return 0
142
+ const known = new Set(loadIgnoredUpdates())
143
+ // 已忽略(repo+version 持久化)与通知中心已有的更新提醒都不再补写:
144
+ // 忽略本次版本后不再提醒,直到下一个新版本(version 变化)才重新提示
145
+ for (const r of loadNotifications()) {
146
+ if (r.kind === 'update') known.add(`${r.repo}@${r.version ?? ''}`)
147
+ }
148
+ const fresh = updatable.filter((item) =>
149
+ item.repo !== null && !known.has(`${item.repo}@${item.catalogVersion ?? ''}`))
150
+ if (fresh.length === 0) return 0
151
+ for (const item of fresh) {
152
+ if (item.repo) addUpdateNotice({ kind: 'update', repo: item.repo, version: item.catalogVersion ?? undefined })
153
+ }
154
+ setNotifications(loadNotifications())
155
+ return fresh.length
156
+ }
157
+
131
158
  // 启动更新策略:目录数据与设置都就绪后触发一次 —— 开启「启动时检查更新」→ 发现可更新插件
132
159
  // 即写入通知中心(每条一条,点击可去更新),toast 同步提示已入通知。
133
160
  // 绝不自动安装:是否更新完全由用户决定。
@@ -138,18 +165,13 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
138
165
  if (!settingsReady || catalog.plugins === null || catalog.failed) return
139
166
  startupCheckedRef.current = true
140
167
  if (!hubSettings.checkUpdatesOnStart) return
141
- const updatable = catalog.installedItems.filter((i) => i.hasUpdate && i.plugin)
142
- if (updatable.length === 0) return
143
- for (const item of updatable) {
144
- if (item.repo) addUpdateNotice({ kind: 'update', repo: item.repo, version: item.catalogVersion ?? undefined })
145
- }
146
- setNotifications(loadNotifications())
147
- setToast({ id: Date.now(), kind: 'updates', n: updatable.length })
168
+ const added = syncUpdateNotices()
169
+ if (added > 0) setToast({ id: Date.now(), kind: 'updates', n: added })
148
170
  }, [settingsReady, hubSettings, catalog.plugins, catalog.failed, catalog.installedItems, queue])
149
171
 
150
172
  // 信任/卸载/更新说明弹窗打开时按 Esc 关闭;任务进入后台队列后可随时关闭(任务继续)
151
173
  useEffect(() => {
152
- if (!confirmPlugin && !confirmCustomTarget && !confirmGlobalNpm && !uninstallPlugin && !uninstallItem && !detailItem && !showHubUpdate && !showAbout && !showNotifications) return
174
+ if (!confirmPlugin && !confirmCustomTarget && !confirmGlobalNpm && !uninstallPlugin && !uninstallItem && !detailItem && !showHubUpdate && !showAbout && !showNotifications && !ignoreTarget) return
153
175
  const onKey = (e: KeyboardEvent) => {
154
176
  if (e.key === 'Escape') {
155
177
  setConfirmPlugin(null)
@@ -162,11 +184,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
162
184
  setShowHubUpdate(false)
163
185
  setShowAbout(false)
164
186
  setShowNotifications(false)
187
+ setIgnoreTarget(null)
165
188
  }
166
189
  }
167
190
  window.addEventListener('keydown', onKey)
168
191
  return () => window.removeEventListener('keydown', onKey)
169
- }, [confirmPlugin, confirmCustomTarget, uninstallPlugin, uninstallItem, detailItem, showHubUpdate, showAbout, showNotifications])
192
+ }, [confirmPlugin, confirmCustomTarget, uninstallPlugin, uninstallItem, detailItem, showHubUpdate, showAbout, showNotifications, ignoreTarget])
170
193
 
171
194
  // Toast 统一自动消失
172
195
  useEffect(() => {
@@ -299,7 +322,10 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
299
322
  // 有可用更新时版本号后紧跟红色「可更新」徽标(同一入口),点击都打开同一个更新说明弹窗。
300
323
  // 有更新弹窗显示新版本 + Markdown 变更记录,点「直接更新」进安装弹窗覆盖重装。
301
324
  hubUpdate: catalog.hubHasUpdate,
302
- onVersionClick: () => setShowHubUpdate(true),
325
+ // 点击版本号/徽标:先强制刷新拉取最新版本信息(发版后点击即见新版本),再打开弹窗
326
+ onVersionClick: () => {
327
+ void catalog.refreshHub().then(() => setShowHubUpdate(true))
328
+ },
303
329
  onAboutClick: () => setShowAbout(true),
304
330
  }),
305
331
  // 一级导航行:tab 占满左侧,通知入口靠右(「设置」tab 后边,右对齐)
@@ -312,7 +338,12 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
312
338
  t,
313
339
  // 通知中心入口:红圈徽标 = 记录数 + 进行中任务 + 待重启(有动静才醒目)
314
340
  noticeCount: notifications.length + queue.queue.length + queue.pendingRestarts.length,
315
- onOpenNotifications: () => setShowNotifications(true),
341
+ onOpenNotifications: () => {
342
+ // 打开弹窗时补齐更新提醒(受「启动时检查更新」开关约束):同一版本只一条,
343
+ // 清空/删除后重开仍恢复,直到插件更新完成才消失
344
+ if (hubSettings.checkUpdatesOnStart) syncUpdateNotices()
345
+ setShowNotifications(true)
346
+ },
316
347
  }),
317
348
  ),
318
349
  view === 'market'
@@ -466,10 +497,49 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
466
497
  queue.clearModalTask()
467
498
  setConfirmPlugin(plugin)
468
499
  },
500
+ // 更新提醒「忽略本次更新」:先弹确认弹窗,确认后该版本不再提醒(持久化),直到下一个新版本
501
+ onIgnoreUpdate: (repo: string, version?: string) => setIgnoreTarget({ repo, version }),
469
502
  cancelTask: queue.cancelTask,
470
503
  restarting,
471
504
  onRestart: () => { void requestRestart() },
472
505
  }),
506
+ // 「忽略本次更新」确认弹窗:确认后该版本写入忽略集(持久化)并从通知中心移除,
507
+ // 本次版本不再提醒,直到插件发布下一个新版本
508
+ ignoreTarget && h('div', {
509
+ className: modalStyles.overlay,
510
+ onClick: (e: MouseEvent<HTMLDivElement>) => { if (e.target === e.currentTarget) setIgnoreTarget(null) },
511
+ },
512
+ h('div', { className: modalStyles.modal, role: 'dialog', 'aria-modal': 'true' },
513
+ h('div', { className: modalStyles.modalHead },
514
+ h('div', { className: modalStyles.modalTitle }, t('ignoreUpdateConfirmTitle')),
515
+ h('button', {
516
+ className: modalStyles.modalClose,
517
+ type: 'button',
518
+ 'aria-label': t('errorClose'),
519
+ onClick: () => setIgnoreTarget(null),
520
+ }, h(CloseIcon)),
521
+ ),
522
+ h('div', { className: modalStyles.modalBody },
523
+ h('div', { className: modalStyles.failPrepareHint }, t('ignoreUpdateConfirmDetail')),
524
+ h('div', { className: modalStyles.modalActions },
525
+ h('button', {
526
+ className: modalStyles.restartLater,
527
+ type: 'button',
528
+ onClick: () => setIgnoreTarget(null),
529
+ }, t('confirmCancel')),
530
+ h('button', {
531
+ className: modalStyles.restartNow,
532
+ type: 'button',
533
+ onClick: () => {
534
+ ignoreUpdate(ignoreTarget.repo, ignoreTarget.version)
535
+ setNotifications(removeUpdateNotice(ignoreTarget.repo, ignoreTarget.version))
536
+ setIgnoreTarget(null)
537
+ },
538
+ }, t('ignoreUpdateConfirm')),
539
+ ),
540
+ ),
541
+ ),
542
+ ),
473
543
  // 安装确认弹窗:锁定/进度/结果视图逻辑收敛在 modals.tsx;更新与安装同入口,仅标记 update
474
544
  confirmPlugin && h(InstallModal, {
475
545
  plugin: confirmPlugin,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -40,7 +40,7 @@ export function CatalogHeader({ t, langPath, statsTotal, statsVerified, onToggle
40
40
  h(LogoIcon),
41
41
  h('h1', { className: styles.title }, t('title')),
42
42
  ),
43
- // 版本号:紧跟标题「DSH-Plugin Hub」之后,常驻可点入口(<a> 内不能嵌套按钮,故从官网链接里移出放在旁边)。
43
+ // 版本号:紧跟标题「DSH Plugin Hub」之后,常驻可点入口(<a> 内不能嵌套按钮,故从官网链接里移出放在旁边)。
44
44
  // 点击打开更新记录弹窗 —— 有更新看新版本内容,无更新看当前版本内容。
45
45
  // 有更新时版本号后直接跟一个红色「可更新」徽标(同一按钮内,不分开成两个元素),静态显示不闪烁。
46
46
  h('button', {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,9 +1,9 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
6
- * 头部「关注我们」弹窗:介绍 DSH-Plugin Hub 的功能定位 + 用户反馈群二维码。
6
+ * 头部「关注我们」弹窗:介绍 DSH Plugin Hub 的功能定位 + 用户反馈群二维码。
7
7
  * 内容以 Markdown 形式由 dsh-update Worker 的 /about 接口下发(key = hub:about),
8
8
  * 客户端按接口返回渲染,不内置固定文案。
9
9
  * 图片(反馈群二维码)经 renderMarkdown 的 ![](url) 语法嵌入,居中、最大高度受控。
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -55,7 +55,7 @@ function BadgeGlyph({ ok }: { ok: boolean }) {
55
55
  }))
56
56
  }
57
57
 
58
- export function NotificationsModal({ records, tasks, pendingRestarts, t, env, onClose, onCopy, onClear, onRemove, onUpdate, cancelTask, restarting, onRestart }: {
58
+ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, onClose, onCopy, onClear, onRemove, onUpdate, onIgnoreUpdate, cancelTask, restarting, onRestart }: {
59
59
  records: NotificationRecord[]
60
60
  /** 进行中的安装/卸载任务(实时进度,与队列弹窗同一数据源) */
61
61
  tasks: QueueTask[]
@@ -70,6 +70,8 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
70
70
  onRemove: (id: number) => void
71
71
  /** 更新提醒通知点击:跳去该插件的更新确认弹窗 */
72
72
  onUpdate: (repo: string) => void
73
+ /** 更新提醒「忽略本次更新」:先弹确认,确认后本次版本不再提醒,下一个新版本再通知 */
74
+ onIgnoreUpdate: (repo: string, version?: string) => void
73
75
  cancelTask: (id: number) => void
74
76
  restarting: boolean
75
77
  onRestart: () => void
@@ -217,6 +219,13 @@ export function NotificationsModal({ records, tasks, pendingRestarts, t, env, on
217
219
  onClick: (e: MouseEvent<HTMLButtonElement>) => { e.stopPropagation(); onUpdate(r.repo) },
218
220
  }, t('updateNoticeGo'))
219
221
  : null,
222
+ // 更新提醒:「忽略本次更新」按钮 —— 先弹确认,确认后本次版本不再提醒
223
+ isUpdate
224
+ ? h('button', {
225
+ className: styles.noticeIgnore,
226
+ onClick: (e: MouseEvent<HTMLButtonElement>) => { e.stopPropagation(); onIgnoreUpdate(r.repo, r.version) },
227
+ }, t('ignoreUpdateRun'))
228
+ : null,
220
229
  // 每条通知右侧的删除按钮:单独移除这一条
221
230
  h('button', {
222
231
  className: styles.noticeRemove,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
2
+ * DSH Plugin Hub — the community plugin marketplace for DeepSeek Harness.
3
3
  * Website: https://dsh-plugin.org
4
4
  * GitHub: https://github.com/dshplugin/dsh-plugin-hub
5
5
  *