dsh-plugin 1.6.4 → 1.6.6

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 (51) hide show
  1. package/README.en.md +11 -11
  2. package/README.md +11 -11
  3. package/client/client.js +450 -405
  4. package/client/client.js.map +1 -1
  5. package/lib/http/catalog.js +132 -0
  6. package/lib/http/diagnostics.js +201 -0
  7. package/lib/http/install.js +399 -0
  8. package/lib/http/logs.js +164 -0
  9. package/lib/http/routes.js +19 -1261
  10. package/lib/http/settings.js +77 -0
  11. package/lib/http/shared.js +169 -0
  12. package/lib/http/task.js +208 -0
  13. package/lib/services/install/install.js +1 -1
  14. package/lib/services/install/npm-resolve.js +44 -15
  15. package/lib/services/install/task-queue.js +45 -16
  16. package/lib/services/probe.js +18 -1
  17. package/lib/types/http/catalog.d.ts +10 -0
  18. package/lib/types/http/diagnostics.d.ts +4 -0
  19. package/lib/types/http/install.d.ts +11 -0
  20. package/lib/types/http/logs.d.ts +2 -0
  21. package/lib/types/http/routes.d.ts +15 -20
  22. package/lib/types/http/settings.d.ts +2 -0
  23. package/lib/types/http/shared.d.ts +44 -0
  24. package/lib/types/http/task.d.ts +3 -0
  25. package/lib/types/services/install/install.d.ts +1 -1
  26. package/lib/types/services/install/npm-resolve.d.ts +3 -2
  27. package/lib/types/services/probe.d.ts +8 -0
  28. package/package.json +1 -1
  29. package/src/client/components/PluginHubSection.tsx +7 -2
  30. package/src/client/components/modals/HubUpdateModal.tsx +19 -10
  31. package/src/client/components/modals/NotificationsModal.tsx +34 -18
  32. package/src/client/components/modals/modals.tsx +38 -28
  33. package/src/client/components/views/InstalledView.tsx +23 -11
  34. package/src/client/hooks/useCatalog.ts +13 -9
  35. package/src/client/locales.ts +28 -8
  36. package/src/client/logic/failures.ts +36 -157
  37. package/src/client/logic/urls.ts +72 -1
  38. package/src/client/types.ts +11 -0
  39. package/src/server/http/catalog.ts +143 -0
  40. package/src/server/http/diagnostics.ts +193 -0
  41. package/src/server/http/install.ts +391 -0
  42. package/src/server/http/logs.ts +149 -0
  43. package/src/server/http/routes.ts +22 -1220
  44. package/src/server/http/settings.ts +73 -0
  45. package/src/server/http/shared.ts +191 -0
  46. package/src/server/http/task.ts +194 -0
  47. package/src/server/services/install/install-types.ts +9 -1
  48. package/src/server/services/install/install.ts +11 -1
  49. package/src/server/services/install/npm-resolve.ts +41 -13
  50. package/src/server/services/install/task-queue.ts +45 -16
  51. package/src/server/services/probe.ts +19 -1
@@ -16,7 +16,7 @@ import type { EnvInfo, HubPlugin, TaskState, ToastState, Translate } from '../..
16
16
  import { CloseIcon, ConfirmIcon, CopyIcon, LinkIcon } from '../ui/icons.tsx'
17
17
  import { ProgressView } from './ProgressView.tsx'
18
18
  import { installCommandOf } from '../../logic/install-command.ts'
19
- import { pluginDetailUrl, pluginIssueUrl, pluginSiteUrl } from '../../logic/urls.ts'
19
+ import { pluginDetailUrl, pluginIssueUrl, pluginSiteUrl, hubIssueUrl } from '../../logic/urls.ts'
20
20
  import { classifyFailure, missingRegistryPackageOf, npmTooLowVersion, pnpmPolicyHintOf, pnpmPolicyHintParams, unreachableTargetOf, registryHostOf } from '../../logic/failures.ts'
21
21
 
22
22
  /** 完成结果视图:绿色对勾 + 标题/描述;
@@ -205,7 +205,9 @@ export function InstallModal(props: InstallModalProps) {
205
205
  disabled: busy,
206
206
  onClick: onCopy,
207
207
  }, t('copyInstallCommand')),
208
- h('button', {
208
+ // 命令行插件(webInstallable=false):网页端无法可靠代执行(如 npx 安装器),
209
+ // 只给复制命令,不提供一键安装,避免装成无效插件
210
+ cliOnly ? null : h('button', {
209
211
  className: styles.modalInstall,
210
212
  disabled: busy,
211
213
  onClick: onInstall,
@@ -402,20 +404,16 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
402
404
  ? h('div', { className: styles.failPrepareHint },
403
405
  t(npmVersion ? 'failNpmTooLowV' : 'failNpmTooLow', npmVersion ? { v: npmVersion } : undefined))
404
406
  : failureKind === 'dshMissing'
405
- // 找不到 dsh 命令(Win「不是内部或外部命令」/ POSIX「command not found」/ spawn ENOENT):
406
- // 本机 DeepSeek Harness 未正确安装或 dsh 不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
407
+ // 本机缺 dsh,不是插件问题
407
408
  ? h('div', { className: styles.failPrepareHint }, t('failDshMissingHint'))
408
409
  : failureKind === 'gitMissing'
409
- // 找不到 git 命令(Win「'git' is not recognized」/ POSIX「git: command not found」/ spawn ENOENT):
410
- // 本机 Git 未安装或不在 PATH,不是插件问题 —— 直接给环境修复指引,不引导提 Issue
410
+ // 本机缺 git,不是插件问题
411
411
  ? h('div', { className: styles.failPrepareHint }, t('failGitMissingHint'))
412
412
  : failureKind === 'pnpmMissing'
413
- // 找到了 dsh 但缺 pnpm(dsh 用它管理 profile 插件;dsh 报 pnpm not found on PATH):
414
- // 本机缺 pnpm,不是插件问题 —— 给安装指引,不引导提 Issue
413
+ // 本机缺 pnpm,不是插件问题
415
414
  ? h('div', { className: styles.failPrepareHint }, t('failPnpmMissingHint'))
416
415
  : failureKind === 'npmMissing'
417
- // 全局 npm 安装通道 spawn 的 npm 找不到(Win「'npm' is not recognized」/ POSIX
418
- // 「npm: command not found」/ spawn ENOENT):本机 npm 未安装或不在 PATH —— 给安装指引,不引导提 Issue
416
+ // 本机缺 npm,不是插件问题
419
417
  ? h('div', { className: styles.failPrepareHint }, t('failNpmMissingHint'))
420
418
  : failureKind === 'pnpmWorkspace'
421
419
  // pnpm 拒绝把依赖装到 workspace 根目录(ERR_PNPM_ADDING_TO_ROOT):宿主在 profile 目录
@@ -446,17 +444,25 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
446
444
  })(),
447
445
  h('div', { className: styles.failPrepareHint }, t('failPnpmMissingDepHint')),
448
446
  ])
447
+ : failureKind === 'registryVersionStale'
448
+ // 要装的版本在「实际执行安装的那个源」里查不到(ERR_PNPM_NO_MATCHING_VERSION / ETARGET):
449
+ // 常见于镜像滞后 —— 反查包名走设置里的 npmRegistry,实际安装沿用本机 ~/.npmrc 的源,
450
+ // 官方源刚发布而镜像还没同步;也可能只是版本号写错。与插件作者无关 ——
451
+ // 给「对齐源 / 那一次显式走官方源 / 用离线包」的指引,不引导提 Issue
452
+ ? h('div', { className: styles.failPrepareHint }, t('failRegistryVersionStaleHint'))
453
+ : failureKind === 'pnpmLinkedDirMissing'
454
+ // profile 里留着指向不存在目录的本地依赖(ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND):常见于
455
+ // link:/本地路径装过开发中的插件、之后目录被删;pnpm 每次解析 profile 依赖都先卡在它上面,
456
+ // 与本次要装的插件无关 —— 给删除悬空条目的指引,不引导提 Issue
457
+ ? h('div', { className: styles.failPrepareHint }, t('failPnpmLinkedDirMissingHint'))
449
458
  : failureKind === 'fileLocked'
450
- // profile 里的文件被其他进程占用(Windows os error 32):pnpm 无法替换被占用的文件,
451
- // 通常是宿主或杀毒软件持有句柄 —— 给退出宿主的指引,不引导提 Issue
459
+ // profile 文件被其他进程占用
452
460
  ? h('div', { className: styles.failPrepareHint }, t('failFileLockedHint'))
453
461
  : failureKind === 'accessDenied'
454
- // profile 里的文件被系统拒绝写入(Windows os error 5「拒绝访问」/ EPERM / EACCES):
455
- // 占用、只读属性、目录 ACL 或杀软拦截 —— 同样给退出宿主/检查权限的指引,不引导提 Issue
462
+ // 系统拒绝写 profile
456
463
  ? h('div', { className: styles.failPrepareHint }, t('failAccessDeniedHint'))
457
464
  : failureKind === 'fsUnavailable'
458
- // 本机磁盘写不进去(ENOSPC / EROFS / EMFILE 及 Windows os error 112、19):
459
- // 空间不足、盘只读、句柄耗尽,任何插件都装不进来 —— 按子场景给指引,不引导提 Issue
465
+ // 磁盘写不进去
460
466
  ? h('div', { className: styles.failPrepareHint }, t('failFsUnavailableHint'))
461
467
  : failureKind === 'originRejected'
462
468
  // 请求来源没通过本地 hub 服务的校验(服务端 403 untrusted origin):请求根本没进
@@ -478,13 +484,11 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
478
484
  onClick: onRunDiagnostics,
479
485
  }, t('failNetworkRunDiag')) : null,
480
486
  ])
481
- : failureKind === 'pluginPrepare' || failureKind === 'pnpmIgnoredBuild'
487
+ : failureKind === 'pluginPrepare'
482
488
  ? h('div', null, [
483
489
  // [packaging](预检/装后校验拦截:git 分发缺产物)与
484
- // prepare 构建脚本实际执行失败 / 原生依赖构建被 pnpm 拦截:都是插件打包分发问题 —— 先说明原因,按钮在下方提交
485
- h('div', { className: styles.failPrepareHint }, failureKind === 'pnpmIgnoredBuild'
486
- ? t('failIgnoredBuild')
487
- : /\[packaging\]/i.test(message) ? t('failPackagingHint') : t('failPrepareHint')),
490
+ // prepare 构建脚本实际执行失败:唯一确有证据的插件打包分发问题 —— 引导到插件仓库提 Issue
491
+ h('div', { className: styles.failPrepareHint }, /\[packaging\]/i.test(message) ? t('failPackagingHint') : t('failPrepareHint')),
488
492
  repo ? h('a', {
489
493
  className: styles.failBigIssue,
490
494
  href: pluginIssueUrl(repo, message, env, command, attempts),
@@ -493,13 +497,19 @@ export function ErrorModal({ message, repo, kind, command, attempts, t, env, onC
493
497
  title: t('failIssueHint'),
494
498
  }, t('failIssueBig')) : null,
495
499
  ])
496
- : repo ? h('a', {
497
- className: styles.failBigIssue,
498
- href: pluginIssueUrl(repo, message, env, command, attempts),
499
- target: '_blank',
500
- rel: 'noopener noreferrer',
501
- title: t('failIssueHint'),
502
- }, t('failIssueBig')) : null,
500
+ : h('div', null, [
501
+ // pnpm allowBuilds 拦截与未知失败:不归到插件作者,改为引导到插件中心仓库排查
502
+ failureKind === 'pnpmIgnoredBuild'
503
+ ? h('div', { className: styles.failPrepareHint }, t('failIgnoredBuild'))
504
+ : h('div', { className: styles.failPrepareHint }, t('failUnclassifiedHint')),
505
+ h('a', {
506
+ className: styles.failBigIssue,
507
+ href: hubIssueUrl(repo, message, env, command, attempts),
508
+ target: '_blank',
509
+ rel: 'noopener noreferrer',
510
+ title: t('failHubIssueHint'),
511
+ }, t('failHubIssueBig')),
512
+ ]),
503
513
  ),
504
514
  h('div', { className: styles.modalActions },
505
515
  h('button', { className: styles.restartLater, onClick: onClose }, t('errorClose')),
@@ -51,7 +51,7 @@ function SegBtn({ active, onClick, label, icon }: {
51
51
  }, label, icon)
52
52
  }
53
53
 
54
- function InstalledRow({ item, t, langKey, canReveal, revealLabel, onOpenDetail, onReveal, onUpdate, onUninstall, onRestart }: {
54
+ function InstalledRow({ item, t, langKey, canReveal, revealLabel, pendingRestart, onOpenDetail, onReveal, onUpdate, onUninstall, onRestart }: {
55
55
  item: InstalledItem
56
56
  t: Translate
57
57
  langKey: LocaleId
@@ -59,6 +59,8 @@ function InstalledRow({ item, t, langKey, canReveal, revealLabel, onOpenDetail,
59
59
  canReveal: boolean
60
60
  /** 打开目录按钮文案(macOS = 在 Finder 中显示,Linux = 在文件夹中显示) */
61
61
  revealLabel: string
62
+ /** 是否在服务端待重启列表里(装完未重启);区分「待重启」与「已重启仍加载失败」 */
63
+ pendingRestart: boolean
62
64
  onOpenDetail: (item: InstalledItem) => void
63
65
  /** 在系统文件管理器里定位安装目录(macOS Finder / Linux 文件管理器) */
64
66
  onReveal: (item: InstalledItem) => void
@@ -89,17 +91,19 @@ function InstalledRow({ item, t, langKey, canReveal, revealLabel, onOpenDetail,
89
91
  // 第一行:状态点 + 名称/版本/更新徽标 + 行尾来源标签列 + 右侧固定操作按钮组
90
92
  h('div', { className: styles.rowTitleLine },
91
93
  // 运行状态点:绿 = 已加载进 loader;橙 = 待重启(装完未挂载的 dsh 插件);
92
- // 灰 = 非 dsh 插件(如官方示例项目,宿主不加载,无需重启)
94
+ // 灰 = 未激活(声明是 dsh 插件但宿主加载失败,重启无效)或非 dsh 插件(示例项目)
93
95
  h('span', {
94
96
  className: item.loaded ? styles.statusDot
95
- : item.dshCapable ? `${styles.statusDot} ${styles.statusPending}`
97
+ : item.dshCapable && pendingRestart ? `${styles.statusDot} ${styles.statusPending}`
96
98
  : `${styles.statusDot} ${styles.statusInactive}`,
97
99
  title: item.loaded ? t('statusRunning')
98
- : item.dshCapable ? t('statusPending')
99
- : t('exampleHint'),
100
+ : item.dshCapable && pendingRestart ? t('statusPending')
101
+ : item.dshCapable ? t('statusInactive')
102
+ : t('exampleHint'),
100
103
  'aria-label': item.loaded ? t('statusRunning')
101
- : item.dshCapable ? t('statusPending')
102
- : t('exampleHint'),
104
+ : item.dshCapable && pendingRestart ? t('statusPending')
105
+ : item.dshCapable ? t('statusInactive')
106
+ : t('exampleHint'),
103
107
  }),
104
108
  // 主信息:名称 + 已装版本 + 有更新徽标(flex 撑满,超长省略)
105
109
  h('div', { className: styles.rowMain },
@@ -121,8 +125,9 @@ function InstalledRow({ item, t, langKey, canReveal, revealLabel, onOpenDetail,
121
125
  }, isCustom ? t('manualInstall') : t('hubInstall')),
122
126
  // 行操作:重启(dsh 插件待重启时)/ 详情 / 更新(有更新时)/ 卸载 —— 按钮点击不触发行打开详情
123
127
  h('div', { className: styles.rowActions },
124
- // 装完未挂载的 dsh 插件:给「重启」入口,点了宿主重启后插件即生效;非 dsh 插件不提示(重启无用)
125
- !item.loaded && item.dshCapable
128
+ // 装完未挂载、且在待重启列表里的 dsh 插件:给「重启」入口,点了宿主重启后插件即生效;
129
+ // 已重启但仍加载失败(不在待重启列表)不显示「重启」,改为灰点「未激活」
130
+ !item.loaded && item.dshCapable && pendingRestart
126
131
  ? h('button', {
127
132
  className: styles.rowRestart,
128
133
  type: 'button',
@@ -174,18 +179,21 @@ function InstalledRow({ item, t, langKey, canReveal, revealLabel, onOpenDetail,
174
179
  }
175
180
 
176
181
  /** 行列表:单个已安装列表(自定义安装与目录插件混排,靠徽标区分)。 */
177
- function RowList({ items, t, langKey, canReveal, revealLabel, onOpenDetail, onReveal, onUpdate, onUninstall, onRestart }: {
182
+ function RowList({ items, t, langKey, canReveal, revealLabel, pendingRestarts, onOpenDetail, onReveal, onUpdate, onUninstall, onRestart }: {
178
183
  items: InstalledItem[]
179
184
  t: Translate
180
185
  langKey: LocaleId
181
186
  canReveal: boolean
182
187
  revealLabel: string
188
+ /** 待重启的 owner/repo 集合;用于区分「待重启」与「已重启仍加载失败」 */
189
+ pendingRestarts: string[]
183
190
  onOpenDetail: (item: InstalledItem) => void
184
191
  onReveal: (item: InstalledItem) => void
185
192
  onUpdate: (item: InstalledItem) => void
186
193
  onUninstall: (item: InstalledItem) => void
187
194
  onRestart: () => void
188
195
  }) {
196
+ const pendingSet = new Set(pendingRestarts.map((r) => r.toLowerCase()))
189
197
  return h('ul', { className: styles.list },
190
198
  items.map((item) => h(InstalledRow, {
191
199
  key: item.name,
@@ -194,6 +202,7 @@ function RowList({ items, t, langKey, canReveal, revealLabel, onOpenDetail, onRe
194
202
  langKey,
195
203
  canReveal,
196
204
  revealLabel,
205
+ pendingRestart: item.repo !== null && pendingSet.has(item.repo.toLowerCase()),
197
206
  onOpenDetail,
198
207
  onReveal,
199
208
  onUpdate,
@@ -203,12 +212,14 @@ function RowList({ items, t, langKey, canReveal, revealLabel, onOpenDetail, onRe
203
212
  )
204
213
  }
205
214
 
206
- export function InstalledView({ items, t, langKey, platform, onOpenDetail, onReveal, onUpdate, onUninstall, onRestart }: {
215
+ export function InstalledView({ items, t, langKey, platform, pendingRestarts, onOpenDetail, onReveal, onUpdate, onUninstall, onRestart }: {
207
216
  items: InstalledItem[]
208
217
  t: Translate
209
218
  langKey: LocaleId
210
219
  /** 宿主系统平台(node process.platform):支持文件管理器定位(macOS / Linux)时行内显示打开目录按钮 */
211
220
  platform: string
221
+ /** 待重启的 owner/repo 集合;用于区分「待重启」与「已重启仍加载失败」 */
222
+ pendingRestarts: string[]
212
223
  onOpenDetail: (item: InstalledItem) => void
213
224
  onReveal: (item: InstalledItem) => void
214
225
  /** 更新:仅目录插件且有更新时触发(自定义安装无目录信号可比) */
@@ -275,6 +286,7 @@ export function InstalledView({ items, t, langKey, platform, onOpenDetail, onRev
275
286
  t, langKey,
276
287
  canReveal: platform === 'darwin' || platform === 'win32' || platform === 'linux',
277
288
  revealLabel: platform === 'darwin' ? t('revealFolder') : t('openFolder'),
289
+ pendingRestarts,
278
290
  onOpenDetail, onReveal, onUpdate, onUninstall, onRestart,
279
291
  }
280
292
 
@@ -12,7 +12,7 @@
12
12
  import { useEffect, useMemo, useRef, useState } from 'react'
13
13
  import type { HubPlugin, LocaleId } from '../types.ts'
14
14
  import { HUB_REPO, PLUGIN_VERSION } from '../logic/constants.ts'
15
- import type { HubAboutInfo, HubUpdateInfo } from '../types.ts'
15
+ import type { HubAboutInfo, HubUpdateInfo, HubUpdateState } from '../types.ts'
16
16
  import type { SortKey } from '../logic/constants.ts'
17
17
  import { fetchCatalog, fetchStats } from '../data/catalog.ts'
18
18
  import { fetchHubAbout, fetchHubUpdate } from '../data/hub.ts'
@@ -196,30 +196,32 @@ export function useCatalog(lang: LocaleId) {
196
196
  )
197
197
 
198
198
  /**
199
- * Hub 自身是否有可用更新:Hub 就是当前运行的应用(始终已安装,无需 installedName 命中)。
199
+ * Hub 自身更新状态:Hub 就是当前运行的应用(始终已安装,无需 installedName 命中)。
200
200
  * 以 Worker 版本控制中心返回的「最新版本」为准,与当前运行的版本号比对——
201
201
  * 构建时注入的 PLUGIN_VERSION 是运行 bundle 的真实版本;测试/异常场景缺失时
202
202
  * 回退到安装时记录的版本。版本号不等即新版(版本号只在发版时变更,绝无降级场景)。
203
203
  *
204
204
  * 另加 pnpm 供应链安全门槛:新版本发布还不满 24 小时(+30 分钟缓冲)时,pnpm 会静默
205
205
  * 回退到旧版本,此时若提示「可更新」用户点了也更新不到。publishedAt 由接口中心按 npm
206
- * registry 真实发布时间下发(同源),据此判断最准。
206
+ * registry 真实发布时间下发(同源),据此判断最准。门槛命中要单列成 `pending` 而不是
207
+ * 落回「无更新」——回落会让弹窗在本机版本落后时谎称「已是最新」。
207
208
  */
208
- const hubHasUpdate = (() => {
209
- if (!hubUpdateInfo) return false
210
- const current = PLUGIN_VERSION || versions[HUB_REPO]?.version || null
211
- if (current === null || hubUpdateInfo.version === current) return false
209
+ const hubCurrentVersion = PLUGIN_VERSION || versions[HUB_REPO]?.version || ''
210
+ const hubUpdateState: HubUpdateState = (() => {
211
+ if (!hubUpdateInfo) return 'none'
212
+ if (hubCurrentVersion === '' || hubUpdateInfo.version === hubCurrentVersion) return 'latest'
212
213
  const publishedAt = hubUpdateInfo.publishedAt
213
214
  if (publishedAt) {
214
215
  const publishedMs = Date.parse(publishedAt)
215
216
  if (!Number.isNaN(publishedMs)) {
216
217
  // 24 小时 + 30 分钟缓冲(防网络/CDN 边缘时间偏差)
217
218
  const MIN_RELEASE_AGE_MS = 24 * 60 * 60 * 1000 + 30 * 60 * 1000
218
- if (Date.now() - publishedMs < MIN_RELEASE_AGE_MS) return false
219
+ if (Date.now() - publishedMs < MIN_RELEASE_AGE_MS) return 'pending'
219
220
  }
220
221
  }
221
- return true
222
+ return 'available'
222
223
  })()
224
+ const hubHasUpdate = hubUpdateState === 'available'
223
225
 
224
226
  /** 当前分类下的插件(「全部」时为整个目录)。 */
225
227
  const categoryPlugins = useMemo(() => {
@@ -271,6 +273,8 @@ export function useCatalog(lang: LocaleId) {
271
273
  installedVersion,
272
274
  hasUpdate,
273
275
  hubHasUpdate,
276
+ hubUpdateState,
277
+ hubCurrentVersion,
274
278
  hubUpdateInfo,
275
279
  hubAboutInfo,
276
280
  refreshHub,
@@ -31,7 +31,7 @@ export const zh = {
31
31
  retry: '重试',
32
32
  install: '安装',
33
33
  // AI 识别可能需要命令行辅助的插件(webInstallable=false):仍可尝试一键安装,失败时复制命令去 dsh 终端
34
- cliOnlyHint: '此插件可能需命令行辅助,失败时复制上方命令到 dsh 终端',
34
+ cliOnlyHint: '此插件需在命令行安装(网页端无法代执行),请复制上方命令到终端运行',
35
35
  failedCopyHint: '安装失败。请复制上方安装命令到 dsh 终端手动执行(原生依赖构建脚本可能需要人工放行,如 pnpm approve-builds)',
36
36
  toastCopied: '安装命令已复制,去 dsh 终端粘贴即可安装',
37
37
  installed: '已安装',
@@ -92,6 +92,7 @@ export const zh = {
92
92
  statusLabel: '安装状态',
93
93
  statusRunning: '运行中',
94
94
  statusPending: '待重启',
95
+ statusInactive: '未激活(宿主加载失败,可能不是有效的 dsh 插件)',
95
96
  exampleLabel: 'DeepSeek Harness 内置示例项目',
96
97
  exampleHint: 'DeepSeek Harness 内置示例项目:不是插件市场的 dsh 插件,宿主不会加载它,无需重启',
97
98
  restart: '重启',
@@ -309,12 +310,17 @@ export const zh = {
309
310
  failCopy: '复制完整错误信息',
310
311
  failIssueHint: '带着错误日志去作者仓库一键提交 Issue(正文含官网收录链接)',
311
312
  failIssueBig: '一键提交 BUG 到 GitHub Issue 为开源作贡献',
313
+ // 非插件侧失败(未知原因 / pnpm allowBuilds 拦截)改为引导到插件中心仓库排查
314
+ failHubIssueHint: '此失败已被判定为环境或插件中心自身问题(非插件作者问题),带着错误日志到插件中心仓库一键提交 Issue',
315
+ failHubIssueBig: '反馈给插件中心排查',
316
+ // 未知失败兜底:不归到插件作者,引导到插件中心仓库排查
317
+ failUnclassifiedHint: '安装失败,原因未能自动识别。这通常不是插件自身的问题(可能与本机环境或插件中心有关),请将错误信息反馈给插件中心,由维护者排查。',
312
318
  // prepare 构建脚本实际执行失败(git 分发缺子模块/构建产物):插件打包分发问题,应提 Issue
313
319
  failPrepareHint: '该插件安装时的构建脚本执行失败(git 分发常缺失子模块或构建产物),属插件打包分发问题。请向作者仓库提交 Issue 反馈。',
314
320
  // 安装前预检 / 装后校验拦截([packaging]):git 分发缺构建产物、入口文件不存在 = 插件未适配官方默认安装方式
315
321
  failPackagingHint: '该插件不支持官方默认安装方式:其 git 分发缺少构建产物(package.json 声明的入口文件在仓库中不存在),说明作者未适配官方安装流程。请到作者仓库提交 Issue 反馈,请其提交构建产物、改用预构建 release `.tgz` 直链(`releases/download/<tag>/<asset>.tgz`),或发布完整的 npm 版。',
316
- // 原生依赖构建脚本被 pnpm 默认拦截(如 node-pty 的 ERR_PNPM_IGNORED_BUILDS):只影响带原生模块的插件,属插件依赖打包问题,应提 Issue
317
- failIgnoredBuild: '该插件或其依赖需要执行构建脚本,被 pnpm 的安全白名单(allowBuilds)默认拦截导致安装失败(如原生模块 node-pty、或 git 安装时插件的 prepare 脚本)—— 其他插件不受影响,属该插件的依赖/打包问题。建议向作者仓库一键反馈,请作者改用预编译版本(如 node-pty-prebuilt-multiarch)或移除安装期构建。',
322
+ // 原生依赖构建脚本被 pnpm 默认拦截(如 node-pty 的 ERR_PNPM_IGNORED_BUILDS):不单独算插件问题,引导到插件中心仓库排查
323
+ failIgnoredBuild: '该插件或其依赖需要执行构建脚本,被 pnpm 的安全白名单(allowBuilds)默认拦截导致安装失败(如原生模块 node-pty、或 git 安装时插件的 prepare 脚本)。这不一定是插件自身的问题(也可能是插件中心反查失败后回退到 git 安装所致),请先反馈给插件中心排查;若确认为插件依赖问题,可建议作者改用预编译版本(如 node-pty-prebuilt-multiarch)或移除安装期构建。',
318
324
  // npm arborist 内部崩溃(edgesOut)+ 本机 npm 版本过低:npm 自身已知缺陷,不是插件问题,升级 npm 即可
319
325
  failNpmTooLow: '怀疑本机 npm 版本过低:npm 解析插件依赖时内部崩溃(这是 npm 自身的已知缺陷,不是插件问题)。请升级 npm 后重试:npm install -g npm@latest',
320
326
  failNpmTooLowV: '本机 npm 版本过低(当前 v{v}):npm 解析插件依赖时内部崩溃(这是 npm 自身的已知缺陷,不是插件问题)。请升级 npm 后重试:npm install -g npm@latest',
@@ -337,6 +343,8 @@ export const zh = {
337
343
  // 404 依赖:点名 registry 上不存在的那个包(取不到包名时跳过这行),再给删除条目的指引
338
344
  failPnpmMissingDepTarget: '{pkg} 在 npm registry 上不存在(404)',
339
345
  failPnpmMissingDepHint: '安装器调用 pnpm 时报「包不存在」(ERR_PNPM_FETCH_404):你的 profile 里留着一条指向 registry 上未发布包的依赖,pnpm 每次解析依赖都会先卡在它上面,因此任何插件都装不进来。这不是插件本身的问题——请在 profile 目录(~/.dsh/profiles/<profile>)的 package.json 里删除该依赖条目(同时删掉对应的 pnpm-lock.yaml 条目或整个锁文件)后重试。',
346
+ failRegistryVersionStaleHint: '安装器调用 pnpm 时报「找不到匹配的版本」(ERR_PNPM_NO_MATCHING_VERSION):要装的这个版本在实际执行安装的 npm 源里查不到。常见原因是你设置的 npmRegistry 与本机 ~/.npmrc 用的不是同一个源——官方源刚发布、镜像还没同步上来(快则几分钟、慢则几小时),也可能只是版本号写错了。这不是插件本身的问题。可在插件市场设置里把 npmRegistry 改成与本机 ~/.npmrc 一致(或反过来),或那一次显式走官方源(https://registry.npmjs.org)、改用离线包后重试;若该版本刚发布不久,等镜像同步后重试即可。',
347
+ failPnpmLinkedDirMissingHint: '安装器调用 pnpm 时报「要安装的目录不存在」(ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND):你的 profile 里留着一条指向本机不存在目录的本地依赖(link: / file: 形式,例如用 dsh plugin add <本地路径> 装过开发中的插件,之后那个目录被删除或移动了)。pnpm 每次解析 profile 依赖都会先卡在它上面,因此任何插件都装不进来。这不是插件本身的问题——请在 profile 目录(~/.dsh/profiles/<profile>)的 package.json 里删除该本地依赖条目(同时删掉对应的 pnpm-lock.yaml 条目或整个锁文件),或把该目录恢复回原路径后重试。',
340
348
  failFileLockedHint: '安装过程中 pnpm 无法替换 profile 里的文件:有程序正占用着它(Windows 常见报错 os error 32「另一个程序正在使用此文件」)。通常是 DeepSeek Harness 自身或杀毒软件的实时扫描持有文件句柄。这不是插件本身的问题——请完全退出 DeepSeek Harness(Windows 可在任务管理器确认进程已结束,必要时临时关闭杀毒软件实时防护)后重试。',
341
349
  failAccessDeniedHint: '安装过程中 pnpm 写入 profile 里的文件被系统拒绝访问(Windows 常见报错 os error 5「拒绝访问」,英文为 Access is denied;Node 下的 EPERM / EACCES 同理)。常见原因是该文件或目录正被其他进程占用、文件带只读属性、目录权限受限,或杀毒软件实时防护拦截了写入——这与「文件正被占用」(os error 32)同属本机文件系统问题,任何插件装进该目录都会一样失败,不是插件本身的问题。请完全退出 DeepSeek Harness(Windows 可在任务管理器确认进程已结束,必要时临时关闭杀毒软件实时防护)后重试;若仍然失败,请检查 profile 目录及其文件是否被设为只读,并以管理员身份重新运行。',
342
350
  failFsUnavailableHint: '安装过程中 pnpm 无法把文件写进本机磁盘(这类报错与装哪个插件无关,任何插件装在同一目录都会失败,不是插件本身的问题)。请按报错对号入座:① 磁盘空间不足(ENOSPC / no space left on device,Windows 为 os error 112)——请清理 profile 所在磁盘的剩余空间后重试;② 磁盘或挂载点只读、带写保护(EROFS / read-only file system,Windows 为 os error 19)——请确认 profile 目录所在磁盘可写(外接盘/U 盘、带写保护的移动盘或只读挂载的分区都会触发),必要时把插件目录换到系统盘的可写位置;③ 进程可用的文件句柄被耗尽(EMFILE / ENFILE / too many open files)——请关闭占用大量文件的程序并重启 DeepSeek Harness 后重试。',
@@ -344,7 +352,7 @@ export const zh = {
344
352
  failPnpmTarballIntegrityHint: 'pnpm 报 ERR_PNPM_MISSING_TARBALL_INTEGRITY:锁文件中的 tarball 条目没有校验值,无法验证下载的包。先查看完整日志,完全退出 DeepSeek Harness,并检查、备份当前 profile 的 package.json、pnpm-lock.yaml 和 pnpm-workspace.yaml。确认依赖来源后,将 pnpm-lock.yaml 改名保留,再通过同一 DSH 安装器安装适配当前宿主版本的插件,重新生成锁文件。重建会重新解析 profile 依赖,请核对新旧版本;保持完整性校验开启。',
345
353
  failPnpmPolicyHint: '安装器调用 pnpm 时被其供应链安全策略拦截——装任何插件都会撞墙,不是插件本身的问题。分两种情况处理:① 提示「Minimum release age」(ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION):包发布还不满 24 小时,pnpm 11 出于安全会拒收这种刚发布的新包。可在 pnpm 配置(pnpm-workspace.yaml)里加 minimumReleaseAge: 0 豁免,或等满 24 小时后重试。② 提示「untrusted origin」(ERR_PNPM_UNTRUSTED_ORIGIN):依赖来源不被本机 pnpm 信任,通常是该 profile 里之前用 github: 或非官方 npm 源装过插件,锁文件记录了不受信任的来源。请删除该 profile 目录下的 node_modules 与 pnpm-lock.yaml 后重新安装(可先到「系统日志」或 ~/.dsh/profiles/<profile>/hub.log 查看完整报错)。',
346
354
  // minimumReleaseAge 专属:从报错里算出确切的可重试时刻({time}),让用户知道「何时能再装」,
347
- // 而不是笼统地让等 24 小时(见 issue #121)
355
+ // 而不是笼统地让等 24 小时
348
356
  failPnpmReleaseAgeHint: '安装器调用 pnpm 时被其供应链安全策略拦住了,任何插件装进这个 profile 都会先撞上它——与插件本身无关。原因是这个包发布还不满 24 小时,pnpm 11 出于安全会拒收刚发布的新包,这段时间内该 profile 的安装与卸载都会被拦下。可重试时间:{time}(即该包发布满 24 小时后)。如确需立刻安装,可在 profile 目录(~/.dsh/profiles/<profile>)的 pnpm-workspace.yaml 里加 minimumReleaseAge: 0 豁免,但这会放宽整个 profile 的供应链保护。',
349
357
  // 自定义安装三卡片入口的渠道名(报错/日志溯源:本次安装从哪个入口发起)
350
358
  installChannelNpm: 'NPM 包',
@@ -380,6 +388,8 @@ export const zh = {
380
388
  versionHint: '查看版本信息与更新记录',
381
389
  hubCurrentTitle: '当前版本',
382
390
  hubCurrentDesc: '你正在使用 v{version},已是最新版本。',
391
+ // 暂缓提示:远端确有新版本,但发布未满 24 小时(pnpm 供应链安全门槛),与「已是最新」严格区分
392
+ hubPendingDesc: '最新版 v{version} 已发布,但发布尚未满 24 小时:此时更新会被 pnpm 静默回退到旧版本,因此暂不提示。稍后这里会自动出现更新入口。',
383
393
  hubUpToDate: '已是最新版本',
384
394
  // 头部「关注我们」弹窗:平台介绍 + 用户反馈群二维码(内容由 Worker /about Markdown 推送,非写死)
385
395
  followUs: '关注我们',
@@ -418,7 +428,7 @@ export const en = {
418
428
  install: 'Install',
419
429
  // AI-flagged plugins that may need command-line help (webInstallable=false): one-click install still allowed,
420
430
  // on failure the user is guided to copy the command into the dsh terminal
421
- cliOnlyHint: 'This plugin may need command-line help; if it fails, copy the command above and run it in your dsh terminal',
431
+ cliOnlyHint: 'This plugin must be installed from the command line (it cannot be run from the web UI) — copy the command above and run it in your terminal',
422
432
  failedCopyHint: 'Install failed. Copy the command above and run it manually in your dsh terminal (native build scripts may need manual approval, e.g. pnpm approve-builds)',
423
433
  toastCopied: 'Install command copied — paste it in your dsh terminal',
424
434
  installed: 'Installed',
@@ -477,6 +487,7 @@ export const en = {
477
487
  statusLabel: 'Status',
478
488
  statusRunning: 'Running',
479
489
  statusPending: 'Restart pending',
490
+ statusInactive: 'Not active (the host failed to load it — possibly not a valid dsh plugin)',
480
491
  exampleLabel: 'DeepSeek Harness built-in example',
481
492
  exampleHint: 'DeepSeek Harness built-in example — not a marketplace dsh plugin, the host does not load it, no restart needed',
482
493
  restart: 'Restart',
@@ -697,12 +708,17 @@ export const en = {
697
708
  failCopy: 'Copy full log',
698
709
  failIssueHint: 'Open a pre-filled issue on the author repo with this error log (includes catalog links)',
699
710
  failIssueBig: 'Report this bug to GitHub — contribute to open source',
711
+ // Non-plugin-side failures (unclassified / pnpm allowBuilds block) now route to the hub repo for triage
712
+ failHubIssueHint: 'This failure was classified as an environment or Plugin Hub issue (not a plugin-author issue). Open a pre-filled issue on the Plugin Hub repo with this error log.',
713
+ failHubIssueBig: 'Report to the Plugin Hub for triage',
714
+ // Unknown-failure fallback: not routed to the plugin author, but to the hub repo for triage
715
+ failUnclassifiedHint: 'The install failed and the cause could not be identified automatically. This is usually not the plugin itself (it may relate to your local environment or the Plugin Hub). Please report the error to the Plugin Hub so the maintainers can triage it.',
700
716
  // prepare script actually failed (git tarball misses submodules/build output): a packaging issue of the plugin itself — file an issue
701
717
  failPrepareHint: 'This plugin failed while running its build scripts during install (git tarballs often miss submodules or build output). This is a packaging issue of the plugin itself — please report it to the author repository.',
702
718
  // Pre-install / post-install guard hit ([packaging]): git distribution lacks build output, entry file missing = not adapted to the official default install method
703
719
  failPackagingHint: 'This plugin does not support the official default install method: its git distribution lacks the build output (the entry file declared in package.json is not in the repository), so it has not been adapted to the official install flow. Please file an Issue on the author\'s repo asking for committed build output, a prebuilt release `.tgz` (`releases/download/<tag>/<asset>.tgz`), or a complete npm release.',
704
- // native-module build scripts ignored by pnpm by default (e.g. node-pty, ERR_PNPM_IGNORED_BUILDS): only affects plugins with native deps — a dependency/packaging issue of the plugin itself, file an issue
705
- failIgnoredBuild: 'This plugin (or its dependencies) needs to run build scripts, which pnpm blocks by default via its allowBuilds allowlist, so the install fails (e.g. native modules like node-pty, or the plugin\'s own prepare script on git installs) — other plugins are unaffected. This is a dependency/packaging issue of the plugin itself. Please report it to the author repository and ask the author to ship a prebuilt variant (e.g. node-pty-prebuilt-multiarch) or drop install-time builds.',
720
+ // native-module build scripts ignored by pnpm by default (e.g. node-pty, ERR_PNPM_IGNORED_BUILDS): not a plugin-side issue on its own, route to the hub repo for triage
721
+ failIgnoredBuild: 'This plugin (or its dependencies) needs to run build scripts, which pnpm blocks by default via its allowBuilds allowlist, so the install fails (e.g. native modules like node-pty, or the plugin\'s own prepare script on git installs). This is not necessarily the plugin itself (it may be caused by the hub falling back to a git install after npm lookup failed). Please report it to the Plugin Hub first; if it turns out to be a plugin dependency issue, the author can ship a prebuilt variant (e.g. node-pty-prebuilt-multiarch) or drop install-time builds.',
706
722
  failNpmTooLow: 'Your local npm may be too old: npm crashed internally while resolving the plugin\'s dependencies (a known npm bug — not a plugin issue). Please upgrade npm and retry: npm install -g npm@latest',
707
723
  failNpmTooLowV: 'Your local npm is too old (v{v}): npm crashed internally while resolving the plugin\'s dependencies (a known npm bug — not a plugin issue). Please upgrade npm and retry: npm install -g npm@latest',
708
724
  // Pre-install connectivity probe / connection failure in the install log ([network] / git fetch timeout / DNS / TLS): a local network or proxy issue, not a plugin issue — no issue link
@@ -726,6 +742,8 @@ export const en = {
726
742
  // then give the removal instructions
727
743
  failPnpmMissingDepTarget: '{pkg} was not found on the npm registry (404)',
728
744
  failPnpmMissingDepHint: 'pnpm reported that a package does not exist on the npm registry (ERR_PNPM_FETCH_404): your profile keeps a dependency on a package that was never published, and pnpm hits it every time it resolves dependencies — no plugin can be installed into that profile. This is not a problem with the plugin itself. Remove that dependency from package.json inside the profile directory (~/.dsh/profiles/<profile>) — along with its entry in pnpm-lock.yaml, or the lockfile itself — then retry.',
745
+ failRegistryVersionStaleHint: 'pnpm reported that no matching version was found (ERR_PNPM_NO_MATCHING_VERSION): the version being installed does not exist in the npm registry that actually performs the install. This usually means the npmRegistry configured in the hub differs from the source in your local ~/.npmrc — the official registry has published it but the mirror has not synced yet (it can take minutes to hours) — or the version number was simply mistyped. This is not a problem with the plugin itself. Align the hub\'s npmRegistry with your local ~/.npmrc (or the other way round), or explicitly use the official registry (https://registry.npmjs.org) for that one install, or use an offline package, then retry; if the version was published only recently, waiting for the mirror to sync is enough.',
746
+ failPnpmLinkedDirMissingHint: 'pnpm reported that a directory it needs to install from does not exist (ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND): your profile keeps a local dependency (a link: / file: entry, e.g. from `dsh plugin add <local path>`) whose target directory has since been deleted or moved. pnpm hits it every time it resolves profile dependencies, so no plugin can be installed into that profile. This is not a problem with the plugin itself. Remove that local dependency from package.json inside the profile directory (~/.dsh/profiles/<profile>) — along with its entry in pnpm-lock.yaml, or the lockfile itself — or restore the directory to its original path, then retry.',
729
747
  failFileLockedHint: 'During install, pnpm could not replace a file inside the profile because another program is holding it (on Windows this shows as os error 32, "the process cannot access the file because it is being used by another process"). Usually DeepSeek Harness itself or a real-time antivirus scan holds the handle. This is not a problem with the plugin itself — fully quit DeepSeek Harness (on Windows, confirm in Task Manager that the process has ended, and temporarily disable real-time antivirus protection if needed), then retry.',
730
748
  failAccessDeniedHint: 'During install, Windows denied pnpm access to a file inside the profile (this shows as os error 5, "Access is denied"; on Node the equivalent codes are EPERM / EACCES). Typical causes: the file or directory is held by another process, the file is marked read-only, the directory permissions are restricted, or a real-time antivirus scan blocked the write. This is the same class of local file-system problem as a locked file (os error 32) — no plugin can be installed into that directory, so it is not a problem with the plugin itself. Fully quit DeepSeek Harness (on Windows, confirm in Task Manager that the process has ended, and temporarily disable real-time antivirus protection if needed), then retry. If it still fails, check whether the profile directory or its files are read-only and run as administrator.',
731
749
  failFsUnavailableHint: 'During install, pnpm could not write files to the local disk — this has nothing to do with which plugin you are installing: any plugin installed into the same directory would fail the same way, so it is not a problem with the plugin itself. Match the error you see: ① Out of space (ENOSPC / no space left on device, os error 112 on Windows) — free up space on the drive that holds the profile and retry. ② Read-only or write-protected volume (EROFS / read-only file system, os error 19 on Windows) — make sure the drive holding the profile directory is writable (external or USB drives, write-protected media, and read-only mounts all trigger this); move the plugin directory to a writable location on the system drive if needed. ③ File handles exhausted (EMFILE / ENFILE / too many open files) — close programs that hold many files open, restart DeepSeek Harness, then retry.',
@@ -733,7 +751,7 @@ export const en = {
733
751
  failPnpmTarballIntegrityHint: 'pnpm reported ERR_PNPM_MISSING_TARBALL_INTEGRITY: a tarball entry in the lockfile has no checksum to verify the downloaded package. Read the full log, fully quit DeepSeek Harness, and inspect and back up package.json, pnpm-lock.yaml and pnpm-workspace.yaml in the affected profile. Once the dependency sources are verified, rename pnpm-lock.yaml to preserve it, then use the same DSH installer to install a plugin version compatible with the host and regenerate the lockfile. This resolves profile dependencies again: compare the old and new versions, and keep integrity checks enabled.',
734
752
  failPnpmPolicyHint: 'The installer was blocked by pnpm supply-chain security policy — every plugin install would hit the same wall, so this is not a problem with the plugin itself. Two cases: ① "Minimum release age" (ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION): the package was published less than 24 hours ago, and pnpm 11 refuses freshly-published packages for safety. Add minimumReleaseAge: 0 to your pnpm config (pnpm-workspace.yaml) to opt out, or wait until the 24 hours have passed and retry. ② "untrusted origin" (ERR_PNPM_UNTRUSTED_ORIGIN): a dependency source is not trusted by this machine, usually because plugins were previously installed via github: or a non-official npm registry, so the lockfile records an untrusted source. Delete node_modules and pnpm-lock.yaml under the profile directory (see System Logs or ~/.dsh/profiles/<profile>/hub.log for the full output), then reinstall.',
735
753
  // minimumReleaseAge only: compute the exact retry time ({time}) from the error so the user knows
736
- // when installs will work again, instead of the vague "wait 24 hours" (see issue #121)
754
+ // when installs will work again, instead of the vague "wait 24 hours"
737
755
  failPnpmReleaseAgeHint: 'The installer was blocked by pnpm supply-chain security policy, and every plugin installed into this profile would hit the same wall — this is not a problem with the plugin itself. The package was published less than 24 hours ago, and pnpm 11 refuses freshly-published packages for safety, so both installs and removals are blocked for this profile in the meantime. Retry after: {time} (i.e. 24 hours after the package was published). If you must install right now, add minimumReleaseAge: 0 to pnpm-workspace.yaml inside the profile directory (~/.dsh/profiles/<profile>) to opt out, but that loosens supply-chain protection for the whole profile.',
738
756
  // Custom install entry channel names (the three cards) — used in error/log traceability
739
757
  installChannelNpm: 'NPM package',
@@ -769,6 +787,8 @@ export const en = {
769
787
  versionHint: 'View version info and changelog',
770
788
  hubCurrentTitle: 'Current version',
771
789
  hubCurrentDesc: 'You are on v{version} — this is the latest version.',
790
+ // Deferred notice: a newer release exists but is still inside pnpm's 24h supply-chain window
791
+ hubPendingDesc: 'v{version} has been released, but it is still within the 24-hour window where pnpm silently falls back to the older version, so the update is not offered yet. The update entry will appear here shortly.',
772
792
  hubUpToDate: 'Up to date',
773
793
  // Header "Follow us" modal: platform intro + user feedback group QR (content pushed via Worker /about Markdown)
774
794
  followUs: 'Follow us',