dsh-my-guardian 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,57 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+
9
+ ## [0.4.5] - 2026-10-17
10
+
11
+ ### 变更
12
+
13
+ - chore(guardian): 还原 dsh-my-guardian 并修复 #481 依赖预检误报 (#484)
14
+ - chore(plugins)!: 删除 5 个插件及其全仓引用 (#464)
15
+ - fix(compat): 插件适配 DSH 0.2.0-rc.2 宿主(peer 声明 + 取证通道 + fixture)
16
+ - fix(guardian): #438 teardown 期间 entry dispose 诊断不再丢失 (#441)
17
+ - fix(guardian): #429 移除永不派发的死监听并修 entry-init 记录标识 (#434)
18
+ - fix(guardian): #423 roster 行自身回退 profiles 根 + #424 scoped 行纳入预检 (#425)
19
+ - fix(guardian): #410 依赖预检出口分离「版本不满足」与「缺失」 (#420)
20
+ - fix(verify): #402 门禁假红根因——等待预算与真实墙钟解耦(用例鲁棒化 + flaky 复测分类缺口) (#417)
21
+
22
+ ## [0.4.4] - 2026-09-25
23
+
24
+ ### 变更
25
+
26
+ - fix(guardian): #407 依赖预检解析 profiles 根宿主包与子路径导出 (#412)
27
+ - fix(dsh-my-guardian): 适配 0.1.7 HMR 重构,取证不绑包名并登记退役事件
28
+ - fix(test): 补测试临时目录配对清理,加有界兜底清扫防陈旧残留
29
+
30
+ ## [Unreleased]
31
+
32
+ ### 变更
33
+
34
+ - fix(guardian): #481 依赖预检识别桌面版宿主内置包——读 `app.asar/dsh/desktop-runtime.json` 的
35
+ `sharedPackages`(287 个包带版本)与 `files`(覆盖面更广、含第三方包)建索引;裸名
36
+ `cordis` / `schemastery` 回退 `@deepseek-ai/<name>`;客户端插件(`dsh.client`)把宿主前端注入的
37
+ 9 个静态模块(`react` / `react-dom` / client UI 包)视为已满足。宿主内置包版本不满足降级为警告
38
+ (提示升级 DSH,不再产出 `dsh plugin add`);名册非包名行(`cordis:group` / YAML 折叠标量 `>-`)
39
+ 不再报 `unresolvable`。真实桌面名册实测:依赖告警 1 → 0、非包名行 0 误报;纯 Node 环境降级不抛错。
40
+ - fix(guardian): #429 移除宿主从不派发的死监听 `hmr/config-update-failed`。0.1.7-rc.2 的参考源与已装宿主
41
+ 双向 0 命中(`@deepseek-ai/dsh-hmr` 只剩 `hmr/change` / `hmr/reload`),注册它既不触发也不报错;原功能的
42
+ 诊断**并未失效**——宿主在同一 catch 里先打 `config reload at %C failed` warn 序列,由结构化日志通道单独
43
+ 承担(旧宿主 cordis-plugin-hmr 也是同一顺序,事件通道对两个版本都是冗余的)
44
+ - fix(guardian): #429 `loader/entry-init` 诊断记录不再退化成 `entry ? initialized`——loader 在 Entry 构造函数里
45
+ emit,此刻 `entry.options` 还是 `{}`(vendor/loader/src/config/entry.ts:50/58),改为推迟一个 microtask 再落笔;
46
+ 隔离实例实测热挂载记录带真实 entry id
47
+ - test(guardian): #429 新增 test/host-event-live.mjs——用**真实** cordis loader(已装宿主)证明 `loader/entry-init` /
48
+ `loader/partial-dispose` 真会触发;契约测试新增「源码不得再订阅不存在的事件」判据
49
+ - fix(guardian): #438 teardown 收尾期同批并发 entry 的 dispose 诊断不再丢失(收尾写入窗口 + 排空落盘)
50
+ - fix(guardian): #438 第二种形态——整树卸载期 `loader/partial-dispose` 监听器与 teardown disposer 同批被
51
+ `Promise.all` 摘除,事件根本到不了 guardian(隔离实例 + SIGTERM 实测:修前 `state.json.events` 恒 1 条、
52
+ entry-dispose 0 条)。改为收尾起手同步抓 loader 树 entry 快照、unmount 后取差集记录「收尾释放了哪些 entry」,
53
+ 不再依赖事件通道;实测 183 个活条目全部记到(state.json 按 20 条环形上限保留末 20 条)
54
+ - test(guardian): #438 新增第二种形态防复发——fake tree 三例(事件通道静默时全量记录 / 存活条目不误记 /
55
+ teardown 返回后不得覆盖快照)+ test/teardown-releases-live.mjs(真实 cordis 整树卸载取证快照前提与差集)
56
+ - fix(guardian): 依赖预检解析 profiles 根 node_modules 的宿主包与子路径导出,消除「缺少依赖」误报
57
+ - fix(guardian): #410 依赖预检出口把「版本不满足」与「缺失」分成独立文案/字段/徽标(`dependency-missing` / `dependency-mismatch`),宿主提供的包不再给 `dsh plugin add` 建议,消除含空格/管道的畸形安装命令
58
+
8
59
  ## [0.4.3] - 2026-09-25
9
60
 
10
61
  ### 变更
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  - **两段式加载**:新插件先进候选区,坏插件不再拖垮 `dsh web` 启动(DSH 启动名册是 all-or-nothing,任一插件失败即整个进程起不来)。
14
14
  - **失败自动隔离**:挂载失败记录尝试次数 + 失败类型 + 错误摘要,连续失败 **3 次冻结**,需手动重试。
15
- - **挂载前依赖预检**:检查候选插件 `peerDependencies` 是否安装、版本是否满足,不满足标记「依赖缺失」并给安装建议,不进入挂载。
15
+ - **挂载前依赖预检**:检查候选插件 `peerDependencies` 是否安装、版本是否满足;**硬缺失**与**版本不满足**分开成句、分开字段、分开徽标,不进入挂载。宿主提供的包(`@deepseek-ai/*`、`react`/`react-dom`)不给 `dsh plugin add` 建议(按提示执行会把宿主自有包装进 profile)。桌面版宿主的 287 个 `@deepseek-ai/*` 包归档在 `app.asar` 内,预检会先读 `desktop-runtime.json` 建宿主清单再判定,不再把它们误报为缺失;客户端插件(有 `dsh.client`)的 9 个前端种子模块(`react` / `react-dom` 等)视为已满足(#481)。
16
16
  - **成功自动转正**:挂载成功的插件进入持久化清单,后续启动自动恢复。
17
17
  - **运行中热挂载**:运行期间往候选区加条目即自动挂载,无需重启。
18
18
  - **安全模式**:一键跳过全部候选/已转正插件,快速救回被插件搞坏的环境。
@@ -37,9 +37,19 @@ dsh plugin --profile web add link:<仓库路径>/plugins/dsh-my-guardian
37
37
  [{ "id": "my-plugin", "name": "dsh-my-plugin", "config": { "option": 1 } }]
38
38
  ```
39
39
 
40
- 写入后自动挂载:成功则该条从候选文件移除(转正),失败则保留。面板状态:运行中(可移除)/ 待加载(安全模式等)/ 失败 ×N(重试或移除)/ 冻结(连续失败 3 次,重试解除冻结)。失败条目带失败类型徽标(依赖缺失 / 代码错误 / 其他),依赖缺失时附安装建议命令。
40
+ 写入后自动挂载:成功则该条从候选文件移除(转正),失败则保留。面板状态:运行中(可移除)/ 待加载(安全模式等)/ 失败 ×N(重试或移除)/ 冻结(连续失败 3 次,重试解除冻结)。失败条目带失败类型徽标(依赖缺失 / 版本不满足 / 代码错误 / 其他),并在存在**可执行**的修复命令时附安装建议(宿主提供的包、含空格/管道的版本范围不给命令)。
41
41
 
42
- **启动名册静态预检**:直接写进 `cordis.patch.yml` / profile / bundles 的插件仍由 DSH 启动时 all-or-nothing 加载,守护每次启动对名册做静态预检(包可解析、`peerDependencies` 满足、无重复 entry id),问题写入 `$DSH_HOME/guardian/startup-issues.json` 并在面板「最近事件」置顶展示;**预检只记录告警,不阻断启动**。
42
+ **启动名册静态预检**:直接写进 `cordis.patch.yml` / profile / bundles 的插件仍由 DSH 启动时 all-or-nothing 加载,守护每次启动对名册做静态预检,问题写入 `$DSH_HOME/guardian/startup-issues.json`(`type` 区分 `unresolvable` / `dependency-missing` / `dependency-mismatch` / `duplicate-id`,依赖字段分 `missingDeps` / `mismatchedDeps`)并在面板「最近事件」置顶展示;**预检只记录告警,不阻断启动**。
43
+
44
+ 检查范围与解析顺序(逐行显式,不留静默免检):
45
+
46
+ | 检查 | 覆盖范围 | 解析顺序 |
47
+ | --- | --- | --- |
48
+ | 包可解析(`unresolvable`) | **所有行**(含 scoped `@scope/pkg` 与子路径 `pkg/sub`,按 `basePackage()` 归一) | `<profileDir>/node_modules` → `<profileDir>/../node_modules`(`$DSH_HOME/profiles/node_modules`),两处都无才报 |
49
+ | `peerDependencies` | **仅非 scoped 行**(保持既有范围,不因 scoped 行放开而扩大) | 插件自身 nested → profile → profiles 根 |
50
+ | 重复 entry id | 全名册 | — |
51
+
52
+ **宿主供给行**(`@deepseek-ai/*`、`react` / `react-dom`)跳过可解析性检查:它们由宿主安装目录供给,不在上述两段根内,硬查只会误报。跳过**不是静默的**——报告里有 `skippedHostRows` 计数与 `notes` 说明(例如「已跳过 8 个宿主供给行…」),快照同步携带,用来区分「都查过且没问题」与「有 N 行没查」。
43
53
 
44
54
  ## 配置
45
55
 
package/lib/api.js CHANGED
@@ -170,6 +170,7 @@ function entrySnapshot(shared, id, record, isStaged) {
170
170
  lastFailedAt: record.lastFailedAt,
171
171
  failureType: record.failureType ?? null,
172
172
  missingDeps: record.missingDeps ?? [],
173
+ mismatchedDeps: record.mismatchedDeps ?? [],
173
174
  installHint: record.installHint ?? null,
174
175
  status,
175
176
  };
@@ -189,5 +190,8 @@ function snapshot(shared) {
189
190
  // startup-roster pre-check report (issue #144): empty array = healthy
190
191
  startupIssues: Array.isArray(shared.startupIssues) ? shared.startupIssues : [],
191
192
  startupCheckedAt: typeof shared.startupCheckedAt === 'number' ? shared.startupCheckedAt : null,
193
+ // #424 A′: 宿主供给行被跳过,但跳过本身必须可见(计数 + 说明)。
194
+ startupSkippedHostRows: typeof shared.startupSkippedHostRows === 'number' ? shared.startupSkippedHostRows : 0,
195
+ startupNotes: Array.isArray(shared.startupNotes) ? shared.startupNotes : [],
192
196
  };
193
197
  }
package/lib/client.js CHANGED
@@ -117,12 +117,22 @@ const STYLES = `
117
117
  .dsh-my-guardian-badge-pending { color:var(--dsw-alias-accent); background:color-mix(in srgb, var(--dsw-alias-accent) 12%, transparent); }
118
118
  .dsh-my-guardian-badge-failed { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); }
119
119
  .dsh-my-guardian-badge-frozen { color:var(--dsw-alias-state-warn-primary); background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, transparent); }
120
- /* failure-classification badge chips (issue #86): dependency / code / other */
120
+ /* failure-classification badge chips (issue #86, split by #410):
121
+ dependency-missing / dependency-mismatch (+ legacy dependency) / code / other */
121
122
  .dsh-my-guardian-category { flex:none; display:inline-flex; align-items:center; justify-content:center; height:17px; padding:0 5px; border-radius:4px;
122
123
  font:var(--dsw-font-xxxs-strong-11); }
124
+ .dsh-my-guardian-category-dependency-missing,
123
125
  .dsh-my-guardian-category-dependency { color:var(--dsw-alias-state-warn-primary); background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, transparent); }
126
+ .dsh-my-guardian-category-dependency-mismatch { color:var(--dsw-alias-state-accent-primary); background:color-mix(in srgb, var(--dsw-alias-state-accent-primary) 14%, transparent); }
124
127
  .dsh-my-guardian-category-code { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); }
125
128
  .dsh-my-guardian-category-other { color:var(--dsw-alias-label-tertiary); background:var(--dsw-alias-interactive-bg-hover); }
129
+ /* version-mismatch detail: 声明范围 vs 实装版本 (issue #410) */
130
+ .dsh-my-guardian-mismatch { display:flex; align-items:center; flex-wrap:wrap; gap:5px; padding:3px 6px; border-radius:6px;
131
+ background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 6%, transparent); font:var(--dsw-font-xxxs-11);
132
+ color:var(--dsw-alias-label-tertiary); }
133
+ .dsh-my-guardian-mismatch-label { flex:none; }
134
+ .dsh-my-guardian-mismatch code { font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:var(--dsw-font-xxxs-11);
135
+ color:var(--dsw-alias-label-secondary); word-break:break-all; }
126
136
  /* install-suggestion line for dependency failures */
127
137
  .dsh-my-guardian-install-hint { display:flex; align-items:center; gap:5px; padding:3px 6px; border-radius:6px;
128
138
  background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 6%, transparent);
@@ -211,7 +221,9 @@ const STYLES = `
211
221
  font:var(--dsw-font-s-strong-14); color:var(--dsw-alias-label-primary); }
212
222
  .dsh-my-guardian-startup-issue-badge-unresolvable { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); }
213
223
  .dsh-my-guardian-startup-issue-badge-duplicate-id { color:var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent); }
224
+ .dsh-my-guardian-startup-issue-badge-dependency-missing,
214
225
  .dsh-my-guardian-startup-issue-badge-dependency { color:var(--dsw-alias-state-warn-primary); background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 16%, transparent); }
226
+ .dsh-my-guardian-startup-issue-badge-dependency-mismatch { color:var(--dsw-alias-state-accent-primary); background:color-mix(in srgb, var(--dsw-alias-state-accent-primary) 14%, transparent); }
215
227
  .dsh-my-guardian-startup-issue-message { font:var(--dsw-font-xxs-12); color:var(--dsw-alias-label-secondary); line-height:1.6; }
216
228
  .dsh-my-guardian-startup-issue-line { display:flex; align-items:flex-start; gap:5px; padding:2px 0;
217
229
  font:var(--dsw-font-xxxs-11); color:var(--dsw-alias-label-tertiary); line-height:1.6; min-width:0; }
@@ -271,16 +283,25 @@ const strings = {
271
283
  isZh()
272
284
  ? '已冻结:连续失败停止自动重试——点击刷新按钮手动重试,或移除该条目'
273
285
  : 'Frozen: auto-retry stopped after repeated failures — retry manually or remove the entry',
274
- failureDependency: () => (isZh() ? '依赖缺失' : 'Dependency'),
286
+ // #410: 「依赖缺失」与「版本不满足」是两种结论,徽标文案必须能区分;
287
+ // 'dependency' 是 pre-#410 的旧分类(含运行期 module 解析失败),按「依赖错误」渲染。
288
+ failureDependencyMissing: () => (isZh() ? '依赖缺失' : 'Missing dependency'),
289
+ failureDependencyMismatch: () => (isZh() ? '版本不满足' : 'Version mismatch'),
290
+ failureDependencyUnknown: () => (isZh() ? '依赖错误' : 'Dependency error'),
275
291
  failureCode: () => (isZh() ? '代码错误' : 'Code error'),
276
292
  failureOther: () => (isZh() ? '其他' : 'Other'),
277
293
  installHint: () => (isZh() ? '安装建议' : 'Install'),
294
+ /** 版本不满足的明细行:声明范围 vs 实装版本(#410)。 */
295
+ mismatchLine: (item) =>
296
+ isZh() ? `声明 ${item.expected},当前 ${item.found}` : `declared ${item.expected}, installed ${item.found}`,
278
297
  // ── startup-roster issues (issue #144) ─────────────────────────────────
279
298
  startupIssues: () => (isZh() ? '启动区问题' : 'Startup roster issues'),
280
299
  startupIssueFix: () => (isZh() ? '修复' : 'Fix'),
281
300
  startupIssueRemove: () => (isZh() ? '移除' : 'Remove'),
282
301
  startupIssueUnresolvable: () => (isZh() ? '包不可解析' : 'Unresolvable'),
283
- startupIssueDependency: () => (isZh() ? '依赖缺失' : 'Dependency'),
302
+ startupIssueDependencyMissing: () => (isZh() ? '依赖缺失' : 'Missing dependency'),
303
+ startupIssueDependencyMismatch: () => (isZh() ? '版本不满足' : 'Version mismatch'),
304
+ startupIssueDependencyUnknown: () => (isZh() ? '依赖错误' : 'Dependency error'),
284
305
  startupIssueDuplicate: () => (isZh() ? '重复 id' : 'Duplicate id'),
285
306
  }
286
307
  // ── api ───────────────────────────────────────────────────────────────
@@ -323,11 +344,17 @@ function statusLabel(status) {
323
344
  return status
324
345
  }
325
346
  }
326
- /** Failure-classification badge label (issue #86): dependency / code / other. */
347
+ /** Failure-classification badge label (issue #86, split by #410):
348
+ * dependency-missing / dependency-mismatch / legacy dependency / code / other. */
327
349
  function failureTypeLabel(type) {
328
350
  switch (type) {
351
+ case 'dependency-missing':
352
+ return strings.failureDependencyMissing()
353
+ case 'dependency-mismatch':
354
+ return strings.failureDependencyMismatch()
329
355
  case 'dependency':
330
- return strings.failureDependency()
356
+ // pre-#410 的记录(也含 mount 期 module 解析失败)仍渲染出徽章
357
+ return strings.failureDependencyUnknown()
331
358
  case 'code':
332
359
  return strings.failureCode()
333
360
  case 'other':
@@ -336,6 +363,13 @@ function failureTypeLabel(type) {
336
363
  return type
337
364
  }
338
365
  }
366
+ /** 版本不满足时的明细文本(声明 vs 当前);无明细时返回空数组(不渲染该行)。 */
367
+ function mismatchLabels(entry) {
368
+ const items = Array.isArray(entry.mismatchedDeps) ? entry.mismatchedDeps : []
369
+ return items
370
+ .filter((item) => item !== null && typeof item === 'object')
371
+ .map((item) => `${item.name}:${strings.mismatchLine(item)}`)
372
+ }
339
373
  // ── event log ─────────────────────────────────────────────────────────
340
374
  // Event type → badge label + color variant (mirrors the dfa-op chip style).
341
375
  const EVENT_LABELS = {
@@ -370,13 +404,18 @@ function eventVariant(type) {
370
404
  return 'neutral'
371
405
  }
372
406
  }
373
- /** Startup-issue badge label (issue #144): unresolvable / dependency / dup. */
407
+ /** Startup-issue badge label (issue #144, split by #410): unresolvable /
408
+ * dependency-missing / dependency-mismatch / legacy dependency / dup. */
374
409
  function startupIssueLabel(type) {
375
410
  switch (type) {
376
411
  case 'unresolvable':
377
412
  return strings.startupIssueUnresolvable()
413
+ case 'dependency-missing':
414
+ return strings.startupIssueDependencyMissing()
415
+ case 'dependency-mismatch':
416
+ return strings.startupIssueDependencyMismatch()
378
417
  case 'dependency':
379
- return strings.startupIssueDependency()
418
+ return strings.startupIssueDependencyUnknown()
380
419
  case 'duplicate-id':
381
420
  return strings.startupIssueDuplicate()
382
421
  default:
@@ -527,7 +566,7 @@ const icon = {
527
566
  size,
528
567
  ),
529
568
  // 代码(issue #54 阶段 1 新增):尖括号 `</>`,预览/代码切换的代码视图
530
- // 图标(dsh-mermaid-render 卡片),stroke=currentColor 风格与其余图标一致。
569
+ // 代码图标(预览 / 代码切换的代码视图),stroke=currentColor 风格与其余图标一致。
531
570
  code: (size = 16) =>
532
571
  iconSvg(
533
572
  [
@@ -536,8 +575,8 @@ const icon = {
536
575
  ],
537
576
  size,
538
577
  ),
539
- // 下载(issue #85 新增):箭头入托盘,图表导出按钮(dsh-mermaid-render
540
- // 卡片下载 PNG/SVG),stroke=currentColor 风格与其余图标一致。
578
+ // 下载(issue #85 新增):箭头入托盘,mermaid 卡片导出 PNG/SVG 用,
579
+ // stroke=currentColor 风格与其余图标一致。
541
580
  download: (size = 16) =>
542
581
  iconSvg(
543
582
  [
@@ -547,8 +586,8 @@ const icon = {
547
586
  ],
548
587
  size,
549
588
  ),
550
- // 复制(issue #85 新增):双层矩形,复制源码按钮(dsh-mermaid-render
551
- // 卡片复制代码),stroke=currentColor 风格与其余图标一致。
589
+ // 复制(issue #85 新增):双层矩形,mermaid 卡片复制源码用,
590
+ // stroke=currentColor 风格与其余图标一致。
552
591
  copy: (size = 16) =>
553
592
  iconSvg(
554
593
  [
@@ -557,6 +596,92 @@ const icon = {
557
596
  ],
558
597
  size,
559
598
  ),
599
+ // ── 控件图标化(issue #486):卡片工具条「文字收起、只留图标」需要每个按钮都有图标 ──
600
+ // 全屏浮窗(四角向外),mermaid 缩放条用。
601
+ maximize: (size = 16) =>
602
+ iconSvg(
603
+ [
604
+ createElement('path', { d: 'M8 3H5a2 2 0 0 0-2 2v3' }),
605
+ createElement('path', { d: 'M16 3h3a2 2 0 0 1 2 2v3' }),
606
+ createElement('path', { d: 'M21 16v3a2 2 0 0 1-2 2h-3' }),
607
+ createElement('path', { d: 'M3 16v3a2 2 0 0 0 2 2h3' }),
608
+ ],
609
+ size,
610
+ ),
611
+ // 适应容器(「完整显示整张图」):外框套内框,表达「内容装进可用宽度」。
612
+ fit: (size = 16) =>
613
+ iconSvg(
614
+ [
615
+ createElement('rect', { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
616
+ createElement('rect', { x: 8, y: 8, width: 8, height: 8, rx: 1 }),
617
+ ],
618
+ size,
619
+ ),
620
+ // 适应宽度 / 铺满:两侧容器边界 + 水平双向箭头。
621
+ fill: (size = 16) =>
622
+ iconSvg(
623
+ [
624
+ createElement('path', { d: 'M3 5v14' }),
625
+ createElement('path', { d: 'M21 5v14' }),
626
+ createElement('path', { d: 'M7 12h10' }),
627
+ createElement('path', { d: 'm9 9-3 3 3 3' }),
628
+ createElement('path', { d: 'm15 9 3 3-3 3' }),
629
+ ],
630
+ size,
631
+ ),
632
+ // 缩小:与既有 plus 同粗细的单横线。
633
+ minus: (size = 16) => iconSvg([createElement('line', { x1: 5, y1: 12, x2: 19, y2: 12 })], size),
634
+ // 自动换行:折行箭头(lucide wrap-text 同形),代码块换行切换用。
635
+ wrap: (size = 16) =>
636
+ iconSvg(
637
+ [
638
+ createElement('line', { x1: 3, y1: 6, x2: 21, y2: 6 }),
639
+ createElement('path', { d: 'M3 12h15a3 3 0 1 1 0 6h-4' }),
640
+ createElement('polyline', { points: '16 16 14 18 16 20' }),
641
+ createElement('line', { x1: 3, y1: 18, x2: 10, y2: 18 }),
642
+ ],
643
+ size,
644
+ ),
645
+ // markdown 文本(框内 M↓):「渲染为 Markdown」用。
646
+ markdown: (size = 16) =>
647
+ iconSvg(
648
+ [
649
+ createElement('rect', { x: 3, y: 4, width: 18, height: 16, rx: 2 }),
650
+ createElement('path', { d: 'M7 15V9l3 3 3-3v6' }),
651
+ createElement('path', { d: 'M17 9v6' }),
652
+ createElement('path', { d: 'm15 13 2 2 2-2' }),
653
+ ],
654
+ size,
655
+ ),
656
+ // 更多(三点):卡片工具条的「更多」菜单触发器 —— 低频按钮收进菜单,不长期霸占头部。
657
+ more: (size = 16) =>
658
+ iconSvg(
659
+ [
660
+ createElement('circle', { cx: 5, cy: 12, r: 1.6, fill: 'currentColor', stroke: 'none' }),
661
+ createElement('circle', { cx: 12, cy: 12, r: 1.6, fill: 'currentColor', stroke: 'none' }),
662
+ createElement('circle', { cx: 19, cy: 12, r: 1.6, fill: 'currentColor', stroke: 'none' }),
663
+ ],
664
+ size,
665
+ ),
666
+ // 图表(柱状):mermaid 图表卡片标题与「渲染为图表」选项用。
667
+ chart: (size = 16) =>
668
+ iconSvg(
669
+ [
670
+ createElement('line', { x1: 6, y1: 20, x2: 6, y2: 10 }),
671
+ createElement('line', { x1: 12, y1: 20, x2: 12, y2: 4 }),
672
+ createElement('line', { x1: 18, y1: 20, x2: 18, y2: 14 }),
673
+ ],
674
+ size,
675
+ ),
676
+ // 预览(眼睛):视图切换的「预览」用 —— 比 file 更贴语义。
677
+ eye: (size = 16) =>
678
+ iconSvg(
679
+ [
680
+ createElement('path', { d: 'M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z' }),
681
+ createElement('circle', { cx: 12, cy: 12, r: 3 }),
682
+ ],
683
+ size,
684
+ ),
560
685
  // 箭头向上(更新图标):向上的箭头,表示更新操作
561
686
  arrowUp: (size = 16) =>
562
687
  iconSvg(
@@ -870,6 +995,26 @@ function RowActions({ entry, busy, onRetry, onRemove }) {
870
995
  ),
871
996
  )
872
997
  }
998
+ /** 依赖类失败(#410 分两类;'dependency' 是 pre-#410 的旧分类,含 mount 期 module 解析失败)。 */
999
+ function isDependencyFailure(type) {
1000
+ return type === 'dependency' || type === 'dependency-missing' || type === 'dependency-mismatch'
1001
+ }
1002
+ /** 依赖失败行展示的安装命令;其他失败类型、空命令、宿主提供的包(无命令)都不展示(#410)。 */
1003
+ function installCommandOf(entry) {
1004
+ if (!isDependencyFailure(entry.failureType)) return null
1005
+ return typeof entry.installHint === 'string' && entry.installHint !== '' ? entry.installHint : null
1006
+ }
1007
+ /** 版本不满足明细行(#410):声明范围 vs 实装版本;无明细时不渲染。 */
1008
+ function MismatchHint({ entry }) {
1009
+ const labels = mismatchLabels(entry)
1010
+ if (labels.length === 0) return null
1011
+ return createElement(
1012
+ 'div',
1013
+ { className: 'dsh-my-guardian-mismatch' },
1014
+ createElement('span', { className: 'dsh-my-guardian-mismatch-label' }, strings.failureDependencyMismatch()),
1015
+ labels.map((label, index) => createElement('code', { key: index }, label)),
1016
+ )
1017
+ }
873
1018
  /** 冻结行提示(连败停止自动重试,需手动操作)。 */
874
1019
  function FrozenHint({ status }) {
875
1020
  if (status !== 'frozen') return null
@@ -886,9 +1031,7 @@ function EntryRow({ entry, source, onAction }) {
886
1031
  const [confirming, setConfirming] = useState(false)
887
1032
  const [busy, setBusy] = useState(false)
888
1033
  const hasError = typeof entry.lastError === 'string' && entry.lastError !== ''
889
- const isDepFailure = entry.failureType === 'dependency'
890
- const installHint =
891
- isDepFailure && typeof entry.installHint === 'string' && entry.installHint !== '' ? entry.installHint : null
1034
+ const installHint = installCommandOf(entry)
892
1035
  const run = (kind) => {
893
1036
  setBusy(true)
894
1037
  Promise.resolve(onAction(kind, entry)).finally(() => setBusy(false))
@@ -899,6 +1042,7 @@ function EntryRow({ entry, source, onAction }) {
899
1042
  createElement(RowHead, { entry, source }),
900
1043
  createElement(RowMeta, { entry }),
901
1044
  createElement(FrozenHint, { status: entry.status }),
1045
+ createElement(MismatchHint, { entry }),
902
1046
  installHint
903
1047
  ? createElement(
904
1048
  'div',