dsh-m 0.9.32 → 0.9.33

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
@@ -8,6 +8,14 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
8
8
 
9
9
  ## 中文
10
10
 
11
+ ### 0.9.33 变更:自研条目元数据 v1.1——`audience`/`decoupled` 两字段与三端标注
12
+
13
+ - **schema**:registry 条目增补可选 `decoupled?: true`(解耦条目,实操口径判定;与 `verified` 互斥——同条目共存报 error)与 `audience?: 'public' | 'internal'`(缺省 public 不产生键;`'team'` 值集预留未开放)。决策共识与五项裁决记录见 DESIGN §2.7,术语见 GLOSSARY「解耦条目 / 自用条目」。
14
+ - **三端标注**:GUI 卡片与 Modal 头新增「作者自用」「版本无关」徽章+Modal 受众/适配详情行;`dshm_search` 输出投影携带 `audience`/`decoupled`、条目行加 `[作者自用]` 标,工具描述写明推荐纪律(internal 勿向普通用户主动推荐,点名或内部推广场景除外);CLI `dshm search` 行内同标(新增 `tests/cli-search.test.mjs`)。已装视图与收藏页不打标——推荐发现链路才是纪律靶面。
15
+ - **CI**:validate-registry 新增软警告——decoupled 条目 description 含「已适配」句式即 warn(copy-guide §4 第四句式「版本无关,详见仓库」,空间不足用紧凑形「版本无关。」)。
16
+ - **registry 数据**:八个自研条目终态——surf / obmc-web / onetree-log 打 `decoupled`+`audience: internal`,quota-watch 打 `decoupled`;四条 `verified` 数组删除(历史归 git 与各仓库文档),兼容句换第四句式;四条 coupled 条目(dsh-m / dsh-skins / skip-browser-auth / copilot-auth)verified 原样保留。
17
+ - **测试**:净增 9 例(registry 字段校验 5、tools-search 标注 2、cli-search 2);全量 1133 tests,本机 1108 pass / 15 fail——15 项全部为 Windows 平台既有 symlink 语义用例(与基线集合逐项一致,零回归);typecheck 零错误。
18
+
11
19
  ### 0.9.32 变更:npm registry 路由自适应——元数据预取换源/镜像自愈(ADR-0012)
12
20
 
13
21
  - **动机**:2026-10-04 上海 Windows 桌面机实证——元数据预取写死直连 registry.npmjs.org,间歇性超时使升级在委派 pnpm 之前夭折(`fetch failed` / `The operation was aborted due to timeout`,且 `.plugin-manager/logs` 无对应操作日志),而 npmmirror 镜像早已同步目标版本;同期首尔腾讯云机 npmjs 直连良好。两台机器需要零配置各自可用。
@@ -317,6 +325,14 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
317
325
 
318
326
  ## English
319
327
 
328
+ ### Added in 0.9.33 — self-dev entry metadata v1.1: `audience`/`decoupled` fields with three-surface marking
329
+
330
+ - **Schema**: registry entries gain optional `decoupled?: true` (decoupled entries, operational criterion; mutually exclusive with `verified` — co-occurrence is an error) and `audience?: 'public' | 'internal'` (defaults to public, no key emitted; `'team'` reserved, not accepted this release). The decision record and five rulings live in DESIGN §2.7; terms in GLOSSARY (Decoupled Entry / Internal Entry).
331
+ - **Three-surface marking**: GUI cards and the Modal head gain 「作者自用」 (Author's own) and 「版本无关」 (Version-independent) badges plus Modal audience/compat rows; `dshm_search` output projects `audience`/`decoupled` with an inline [作者自用] line marker, and the tool description states the recommendation discipline (never proactively recommend internal entries to general users unless named or for internal rollout); CLI `dshm search` marks lines identically (new `tests/cli-search.test.mjs`). Installed views and favorites stay unmarked — the discovery path is the discipline's target.
332
+ - **CI**: validate-registry gains a soft warning — a decoupled entry whose description contains the 「已适配」 pattern (copy-guide §4 fourth pattern 「版本无关,详见仓库」, compact form 「版本无关。」 when space is tight).
333
+ - **Registry data**: final state for the eight self-dev entries — surf / obmc-web / onetree-log gain `decoupled`+`audience: internal`, quota-watch gains `decoupled`; the four `verified` arrays are removed (history lives in git and per-repo docs) and compatibility sentences switch to the fourth pattern; the four coupled entries (dsh-m / dsh-skins / skip-browser-auth / copilot-auth) keep verified as-is.
334
+ - **Tests**: 9 net-new cases (registry validation 5, tools-search marking 2, cli-search 2); full suite 1133 tests, locally 1108 pass / 15 fail — all 15 are the pre-existing Windows symlink-semantics baseline (item-for-item identical, zero regression); typecheck clean.
335
+
320
336
  ### Added in 0.9.32 — npm registry route adaptation: metadata prefetch source switching / mirror self-heal (ADR-0012)
321
337
 
322
338
  - **Motivation**: on 2026-10-04 a Shanghai Windows desktop machine proved the metadata prefetch is hardcoded to registry.npmjs.org; intermittent timeouts aborted upgrades before pnpm was ever invoked (`fetch failed` / `The operation was aborted due to timeout`, with no operation log under `.plugin-manager/logs`), while npmmirror had already synced the target version. A Seoul (Tencent Cloud) box enjoys fast direct npmjs. Both machines must work with zero configuration.
package/docs/DESIGN.md CHANGED
@@ -18,7 +18,7 @@
18
18
  - **默认源**获取顺序:raw.githubusercontent `@main` → jsDelivr `@main` → 默认 TTL 缓存 → npm 包内快照兜底。jsDelivr 是 GitHub 内容的免费 CDN 镜像,仅作 raw 拉取失败时的**备用线路**(覆盖大陆可达性与 GitHub 故障;CDN 缓存可能滞后数小时,可用 purge.jsdelivr.net 手动清理)。收录更新与插件发版**解耦**。
19
19
  - **自定义覆盖源(单一地址,整体覆盖,不合并)**:`registryUrl` 为空 = 官方默认清单;非空 = 一个 HTTPS URL(或 loopback HTTP,仅本机管理员信任边界,不承诺 DNS rebinding 防护)或 DSH Web 主机上的本地普通文件(绝对路径 / `file://`,`realpath` + `O_NOFOLLOW` 同 fd 读取与复核,严格 UTF-8,2 MiB 原始字节上限)。自定义源失败只回退**该源自己的缓存**,绝不静默改用官方清单;无可缓存数据时返回空清单 + 不可用状态。
20
20
  - **「新增插件」流程**:设置页下载默认 `registry.json` → 用户自行编辑副本 → 填入副本地址「校验并应用」。副本是独立快照,不自动同步官方新条目。
21
- - **严格 v1 schema**:顶层只允许 `version/plugins`,条目只允许 `id/name/description/category/tags/source/npm/github/homepage/icon`;未知字段、非法 ID/npm/GitHub/URL、重复 ID/tag、字段超限、`plugins` 超过 1,000 条均拒绝整份清单(不截断、不部分加载);超过 200 条提示性能边界。
21
+ - **严格 v1 schema**:顶层只允许 `version/plugins`,条目只允许 `id/name/description/category/tags/source/npm/github/homepage/icon`(v1.1 增补可选 `verified`、`alsoCategories`、`decoupled`、`audience`,见 §2.7);未知字段、非法 ID/npm/GitHub/URL、重复 ID/tag、字段超限、`plugins` 超过 1,000 条均拒绝整份清单(不截断、不部分加载);超过 200 条提示性能边界。
22
22
  - **缓存模型**:cache 按 namespace 分目录(Host 与 Agent tools 用 `host/`,独立 CLI 用 `cli/`,互不删除),按带算法版本的 cacheKey 分文件(`CacheFile v2`:原子临时文件 + fsync + rename,0700/0600,symlink 拒写,同 key 进程内写锁)。**候选验证只写候选 cache,绝不 prune 旧源**;settings 写入成功后才 `commitActiveSource`:先原子写 accepted-source metadata(`host/active-source.json`,仅 Host),再清理非当前 custom cache;metadata 失败不 prune、prune 失败保留旧 cache,均只降级为 warning,不撤销已生效的配置。切换后只保留默认缓存与当前 custom 缓存。
23
23
  - **统一安全 HTTP**:所有 JSON/text/HEAD 请求共享同一 primitive——手动重定向(每跳校验协议、最多 3 跳、循环检测、signal 传播、返回最终 URL)+ 响应大小上限 + 超时。
24
24
  - **秒开缓存语义(0.9.14 SWR)**:`loadRegistry` 与社区获取链的 TTL 过期不再同步等网络——磁盘缓存存在即**先回 stale**(registry `source='default-cache'`、社区 `status='stale'` 既有契约;stale 状态由设置页社区卡承接,市场页不提示临时缓存状态——0.9.15 修订),后台**单飞**刷新写回 cache(同 key 进行中复用;不接调用者 signal,后台自愈不被请求 abort 腰斩;`force`/candidate/显式下载三路径不经 SWR)。默认双线路**粘性**:`loadDefaultChain` 读 cache 记录的上次成功 `source` 排序线路(稳定排序,cache 缺失 = 原序,force 同样生效)。社区 bg 自愈是 force flight——**不走同版本短路**(`if (!opts.force)` 守卫),每 TTL 至多全量重拉一次正文(同步路径的短路优化保留)。页条目 latest 探测缓存为 `latest-cache.ts` 的**纯内存 Map + TTL**(0.9.20 ADR-0006,推翻 0.9.14 的「磁盘信封 write-through + 懒 seed、跨服务重启存活」——2026-10-03 事故实证发版窗口内重启/重开面板全吃陈旧值,重启即失效回归为特性;install/upgrade/uninstall 成功点由 market.ts 按 itemId 定向作废该条目全部 registryKey 变体,只失效不回写;0.9.14 遗留 `<cacheRoot>/latest/<ns>.json` 惰性文件由探测段一次性 best-effort 清扫,`latest/` 在 nsDir 之外、不受 `pruneCaches` 清扫的不变量不变)。客户端默认首页响应存 localStorage(10min TTL、双 zone 分键、读侧再过 normalize 收敛)——面板打开先渲染快照后 background 换新。已装面板第二段探测为 TTL=0 例外(ADR-0008)。
@@ -37,11 +37,13 @@
37
37
  "version": 1,
38
38
  "plugins": [
39
39
  {
40
- "id": "dsh-skins", // slug,唯一
41
- "name": "DSH Skins",
40
+ "id": "dsh-example", // slug,唯一(schema 示例载体,非真实条目)
41
+ "name": "Example Plugin",
42
42
  "description": "中文描述", // v1 只有中文
43
43
  "category": "essentials", // 策展五桶:essentials|cui-picks|self-dev|tencent-lighthouse|watchlist 五选一(0.9.16 策展分类法;旧功能五分类退役)
44
44
  "tags": ["主题"],
45
+ "decoupled": true, // 可选;解耦条目(实操口径),与 verified 互斥
46
+ "audience": "internal", // 可选 public|internal;缺省 public;'team' 预留未开放
45
47
  "source": "github", // npm|github
46
48
  "npm": "可选;source=npm 时必填",
47
49
  "github": "owner/repo", // source=github 时必填;npm 条目也可附
@@ -96,6 +98,15 @@
96
98
  - **技术默认件**:图片三层懒加载(IntersectionObserver + `loading=lazy` + `fetchPriority=low`;缩略图本机直连原图,不引第三方代理服务);移除客户端重复排序(服务端单一排序源);`total>200` 性能提示随分区退役;host-api limit 上限对齐新页大小;空状态逐分区定制;错误态带具体原因 + 重试。
97
99
  - **Backlog(明确不做)**:UI 完整双语字典、时间窗过滤、浏览层宿主兼容徽章/过滤、组管理、个人备注、giscus 评论、静态官网。
98
100
 
101
+ ### 2.7 自研条目元数据:受众与解耦(grilling 定稿 2026-10-05)
102
+
103
+ registry 条目增补两个可选字段(schema v1.1,同一次变更上线;发布纪律:**先发 dsh-m 新版、再推 registry @main**,旧客户端拒收新清单回落缓存属「例行过渡」,0.9.17 alsoCategories 先例):
104
+
105
+ - `decoupled?: true`——**解耦条目**,实操口径判定(DSH 升级后大概率无需跟着发适配新版,价值源多在 DSH 之外);与 `verified` **互斥**(同条目共存报 error),不携带实测数组,展示层以「版本无关」徽章呈现,兼容声明用「版本无关,详见仓库」句式(copy-guide §4 第四句式)。
106
+ - `audience?: 'public' | 'internal'`——**受众标记**;缺省 `public` 不产生键;`internal` = 作者自用(GLOSSARY「自用条目」);`'team'` 值集预留未开放。
107
+
108
+ 五项裁决:①双具名字段(否决 `hostCoupling` 单值枚举——主观评级违背实测声明文化,与 `flags` 旗帜数组——弱类型难校验);②受众两档起步(否决三档,扩档是兼容变更);③载体为字段、策展五桶不动(否决拆 self-dev 桶与移出主清单——`alsoCategories` 先例:多维归属用字段不建桶;撤出主清单反致社区清单浮升);④三端照常返回与展示、**只标注不过滤**,推荐纪律走 `dshm_search` 工具描述约束(否决 agent 通道机械过滤——内部推广也依赖搜索);⑤decoupled 条目**删除** `verified` 数组、渲染「版本无关」徽章(否决保留数组改渲染——缺号不再构成负面信号才是病根)。标注落点:GUI 卡片/Modal 头「作者自用」「版本无关」徽章+Modal 详情行;agent 工具与 CLI 搜索输出 `[作者自用]` 行标;已装视图与收藏页不打标(推荐发现链路才是纪律靶面)。registry 数据侧:surf / obmc-web / onetree-log / quota-watch 四条 decoupled(前三者兼 internal),verified 删除、兼容句换第四句式。
109
+
99
110
  ## 3. 安装 / 卸载 / 升级 / 重启
100
111
 
101
112
  底层原语(本机实证):`dsh plugin --profile web add|remove|update`(转发 pnpm,作用于 `$DSH_HOME/profiles/web`)。**profile 的 `package.json` 就是唯一事实源**——不引入任何额外状态文件。
package/lib/cli.js CHANGED
@@ -234,7 +234,7 @@ async function runCliDispatch(argv, deps, io) {
234
234
  for (const it of result.items) {
235
235
  const inst = it.installed ? ` [已安装 v${it.installedVersion || '?'}]` : '';
236
236
  const zone = it.community === true ? '[社区] ' : '';
237
- out(`• ${it.name} (${it.id})${zone}· ${categoryLabelOf(it.category)} · ${it.source}${inst}`);
237
+ out(`• ${it.name} (${it.id})${zone}${it.audience === 'internal' ? '[作者自用] ' : ''}· ${categoryLabelOf(it.category)} · ${it.source}${inst}`);
238
238
  out(` ${it.description}`);
239
239
  }
240
240
  const shown = result.items.length;
package/lib/client.js CHANGED
@@ -1279,6 +1279,9 @@ var ZH = {
1279
1279
  "badge.deprecated": "\u5DF2\u5F03\u7528",
1280
1280
  "sub.snapshot": "v{v}\uFF08\u76EE\u5F55\u5FEB\u7167\uFF09",
1281
1281
  "badge.verified": "\u5DF2\u5B9E\u6D4B",
1282
+ "badge.internal": "\u4F5C\u8005\u81EA\u7528",
1283
+ "badge.decoupled": "\u7248\u672C\u65E0\u5173",
1284
+ "badge.decoupled.tip": "DSH \u5347\u7EA7\u65E0\u9700\u8DDF\u968F\u9002\u914D",
1282
1285
  "modal.category": "\u5206\u7C7B",
1283
1286
  "modal.added": "\u6536\u5F55\u65E5\u671F",
1284
1287
  "modal.dlwindow": "\u4E0B\u8F7D\u91CF\uFF0830 \u5929\u7A97\u53E3\uFF09",
@@ -1290,6 +1293,10 @@ var ZH = {
1290
1293
  "modal.installcmd": "\u5B89\u88C5\u547D\u4EE4",
1291
1294
  "modal.copy": "\u590D\u5236",
1292
1295
  "modal.copied": "\u5DF2\u590D\u5236",
1296
+ "modal.audience.label": "\u53D7\u4F17",
1297
+ "modal.audience.value": "\u4F5C\u8005\u81EA\u7528\uFF08\u4E0D\u9762\u5411\u5927\u4F17\u63A8\u5E7F\uFF09",
1298
+ "modal.decoupled.label": "\u9002\u914D",
1299
+ "modal.decoupled.value": "\u7248\u672C\u65E0\u5173\uFF08DSH \u5347\u7EA7\u65E0\u9700\u8DDF\u968F\u9002\u914D\uFF09",
1293
1300
  "op.clear": "\u6E05\u9664\u5DF2\u7ED3\u675F",
1294
1301
  "op.clear.none": "\u6CA1\u6709\u53EF\u6E05\u9664\u7684\u5DF2\u7ED3\u675F\u8BB0\u5F55",
1295
1302
  "op.superseded.note": "{target} \u5DF2\u8DF3\u8FC7\uFF08\u524D\u63D0\u5DF2\u4E0D\u6210\u7ACB\u6216\u5DF2\u624B\u52A8\u5904\u7406\uFF09",
@@ -1542,6 +1549,9 @@ var EN = {
1542
1549
  "badge.deprecated": "Deprecated",
1543
1550
  "sub.snapshot": "v{v} (catalog snapshot)",
1544
1551
  "badge.verified": "Verified",
1552
+ "badge.internal": "Author's own",
1553
+ "badge.decoupled": "Version-independent",
1554
+ "badge.decoupled.tip": "Survives DSH upgrades without per-release adaptation",
1545
1555
  "modal.category": "Category",
1546
1556
  "modal.added": "Added",
1547
1557
  "modal.dlwindow": "Downloads (30-day window)",
@@ -1553,6 +1563,10 @@ var EN = {
1553
1563
  "modal.installcmd": "Install command",
1554
1564
  "modal.copy": "Copy",
1555
1565
  "modal.copied": "Copied",
1566
+ "modal.audience.label": "Audience",
1567
+ "modal.audience.value": "Author's own (not for general promotion)",
1568
+ "modal.decoupled.label": "Compat",
1569
+ "modal.decoupled.value": "Version-independent (no per-release adaptation)",
1556
1570
  "op.clear": "Clear ended",
1557
1571
  "op.clear.none": "Nothing finished to clear",
1558
1572
  "op.superseded.note": "{target} skipped (precondition gone or already handled)",
@@ -2541,6 +2555,8 @@ function DetailModal({ it, labels, busy, onClose, onInstall, profileKind, instal
2541
2555
  h("span", { className: "dshm-name" }, it.name),
2542
2556
  it.deprecated === true ? h("span", { className: "dshm-badge warn" }, lookup("badge.deprecated")) : null,
2543
2557
  it.community !== true && Array.isArray(it.verified) && it.verified.length ? h("span", { className: "dshm-badge", title: it.verified.join("\u3001") }, lookup("badge.verified")) : null,
2558
+ it.audience === "internal" ? h("span", { className: "dshm-badge" }, lookup("badge.internal")) : null,
2559
+ it.decoupled === true ? h("span", { className: "dshm-badge", title: lookup("badge.decoupled.tip") }, lookup("badge.decoupled")) : null,
2544
2560
  it.installed ? h("span", { className: "dshm-badge" }, lookup("badge.installed")) : null,
2545
2561
  it.community === true ? h("span", { className: "dshm-badge info" }, lookup("badge.community")) : null,
2546
2562
  h("span", { className: "dshm-badge info" }, it.source === "npm" ? "npm" : "github"),
@@ -2567,6 +2583,8 @@ function DetailModal({ it, labels, busy, onClose, onInstall, profileKind, instal
2567
2583
  kv(lookup("detail.latest"), it.latestVersion ? `v${it.latestVersion}` : it.latestTag ? it.latestTag : it.latestSha ? it.latestSha : it.latestError ? it.version ? lookup("sub.snapshot", { v: it.version }) : it.latestError : "\u2014"),
2568
2584
  kv(lookup("detail.installed"), it.installedPkg ? `${it.installedPkg} v${it.installedVersion || "?"}` : lookup("installed.none")),
2569
2585
  it.community !== true && Array.isArray(it.verified) && it.verified.length ? kv(lookup("modal.verified"), it.verified.join("\u3001")) : null,
2586
+ it.audience === "internal" ? kv(lookup("modal.audience.label"), lookup("modal.audience.value")) : null,
2587
+ it.decoupled === true ? kv(lookup("modal.decoupled.label"), lookup("modal.decoupled.value")) : null,
2570
2588
  (it.tags || []).length ? kv(lookup("modal.tags"), it.tags.join(", ")) : null,
2571
2589
  it.deprecated === true && it.replacement ? kv(lookup("modal.replacement"), it.replacement) : null
2572
2590
  ),
@@ -2885,6 +2903,8 @@ function MarketTab({ notify, markets, onMutation, ops, favorites, profileKind })
2885
2903
  ) : null,
2886
2904
  it.deprecated === true ? h("span", { className: "dshm-badge warn", key: "dep" }, lookup("badge.deprecated")) : null,
2887
2905
  it.community !== true && Array.isArray(it.verified) && it.verified.length ? h("span", { className: "dshm-badge", key: "v", title: it.verified.join("\u3001") }, lookup("badge.verified")) : null,
2906
+ it.audience === "internal" ? h("span", { className: "dshm-badge dshm-badge-internal", key: "internal" }, lookup("badge.internal")) : null,
2907
+ it.decoupled === true ? h("span", { className: "dshm-badge", key: "decoupled", title: lookup("badge.decoupled.tip") }, lookup("badge.decoupled")) : null,
2888
2908
  it.outdated ? h("span", { className: "dshm-badge warn", key: "u" }, lookup("badge.update")) : null,
2889
2909
  it.installed ? h("span", { className: "dshm-badge", key: "i" }, lookup("badge.installed")) : null,
2890
2910
  it.community === true ? h("span", { className: "dshm-badge info", key: "c" }, lookup("badge.community")) : null,
@@ -124,7 +124,7 @@ export function parseRegistryAddress(raw) {
124
124
  }
125
125
  // ---------- 严格 v1 校验 ----------
126
126
  const TOP_LEVEL_KEYS = new Set(['version', 'plugins']);
127
- const ENTRY_KEYS = new Set(['id', 'name', 'description', 'category', 'tags', 'source', 'npm', 'github', 'homepage', 'icon', 'verified', 'alsoCategories']);
127
+ const ENTRY_KEYS = new Set(['id', 'name', 'description', 'category', 'tags', 'source', 'npm', 'github', 'homepage', 'icon', 'verified', 'alsoCategories', 'decoupled', 'audience']);
128
128
  /** 精确 semver 判定(与 versions.ts EXACT_VERSION_RE 同语义;接受 prerelease/build,拒绝 range/前缀)。 */
129
129
  const EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
130
130
  function semverParse(value) {
@@ -338,6 +338,25 @@ export function validateRegistry(raw) {
338
338
  alsoCategories = list;
339
339
  }
340
340
  }
341
+ // decoupled(自研元数据 v1.1):只声明 true;与 verified 互斥(GLOSSARY「解耦条目」)
342
+ let entryDecoupled;
343
+ if (e.decoupled !== undefined) {
344
+ if (e.decoupled !== true)
345
+ errors.push(`${where}.decoupled: 只允许 true 或缺省`);
346
+ else
347
+ entryDecoupled = true;
348
+ }
349
+ // audience:'public' 为缺省不产生键;'team' 值集预留但本期不接受
350
+ let entryAudience;
351
+ if (e.audience !== undefined) {
352
+ if (e.audience === 'internal')
353
+ entryAudience = 'internal';
354
+ else if (e.audience !== 'public')
355
+ errors.push(`${where}.audience: 只允许 public/internal`);
356
+ }
357
+ if (entryDecoupled === true && verified !== undefined) {
358
+ errors.push(`${where}.decoupled 与 verified 互斥:解耦条目不携带实测版本数组(GLOSSARY「解耦条目」)`);
359
+ }
341
360
  plugins.push({
342
361
  id,
343
362
  name,
@@ -351,6 +370,8 @@ export function validateRegistry(raw) {
351
370
  ...(entryIcon !== undefined ? { icon: entryIcon } : {}),
352
371
  ...(verified !== undefined ? { verified } : {}),
353
372
  ...(alsoCategories !== undefined ? { alsoCategories } : {}),
373
+ ...(entryDecoupled === true ? { decoupled: true } : {}),
374
+ ...(entryAudience !== undefined ? { audience: entryAudience } : {}),
354
375
  });
355
376
  });
356
377
  return { ok: errors.length === 0, errors, registry: errors.length === 0 ? { version: 1, plugins } : null };
package/lib/tools.js CHANGED
@@ -78,7 +78,7 @@ export function registerTools(ctx, cfg, deps = {}, profile = resolveActiveProfil
78
78
  const ensureService = deps.ensureService;
79
79
  ctx.tools.register(defineTool({
80
80
  name: 'dshm_search',
81
- description: 'Search the DSH plugin marketplace (dsh-m) and show clickable plugin cards. ALWAYS call this instead of web_search or bash when the user wants to find/recommend/browse DSH plugins (插件). Call EXACTLY ONCE per user message; extract a real keyword (主题, 搜索) rather than pasting the whole sentence. Dual catalog: ~22 hand-curated 精选 entries plus a 4,000+ community catalog — default searches all; pass source="community" or "primary" to zone in. Returns 10 cards by default; use offset (take nextOffset from the result) for more batches. After cards appear, reply with AT MOST one short sentence. Do not print install commands.',
81
+ description: 'Search the DSH plugin marketplace (dsh-m) and show clickable plugin cards. ALWAYS call this instead of web_search or bash when the user wants to find/recommend/browse DSH plugins (插件). Call EXACTLY ONCE per user message; extract a real keyword (主题, 搜索) rather than pasting the whole sentence. Dual catalog: ~22 hand-curated 精选 entries plus a 4,000+ community catalog — default searches all; pass source="community" or "primary" to zone in. Returns 10 cards by default; use offset (take nextOffset from the result) for more batches. Cards may carry audience="internal" (作者自用; do not proactively recommend to general users unless the user names one or asks about internal rollout) and decoupled=true (版本无关, survives DSH upgrades without per-release adaptation). After cards appear, reply with AT MOST one short sentence. Do not print install commands.',
82
82
  parameters: {
83
83
  query: { type: 'string', description: 'Main keyword, e.g. 主题 or 搜索. Optional; omit to browse.' },
84
84
  category: {
@@ -175,6 +175,8 @@ export function registerTools(ctx, cfg, deps = {}, profile = resolveActiveProfil
175
175
  installedPkg: e.installedPkg,
176
176
  installedVersion: e.installedVersion,
177
177
  community: e.community === true,
178
+ ...(e.audience === 'internal' ? { audience: 'internal' } : {}),
179
+ ...(e.decoupled === true ? { decoupled: true } : {}),
178
180
  downloads: e.downloads ?? null,
179
181
  stars: e.stars ?? null,
180
182
  })),
@@ -496,7 +498,8 @@ function renderSearch(out) {
496
498
  const lines = out.items.map((it, i) => {
497
499
  const inst = it.installed ? `(已安装 v${it.installedVersion || '?'})` : '';
498
500
  const zone = it.community === true ? '[社区]' : '';
499
- return `${i + 1}. ${it.name} · ${it.id}${zone}${inst} · ${it.categoryLabel || it.category}`;
501
+ const own = it.audience === 'internal' ? '[作者自用]' : '';
502
+ return `${i + 1}. ${it.name} · ${it.id}${zone}${own}${inst} · ${it.categoryLabel || it.category}`;
500
503
  });
501
504
  // 翻页尾行(0.7.0 Task 5):nextOffset 为空 = 已到末尾
502
505
  const pager = typeof out.nextOffset === 'number'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.9.32",
3
+ "version": "0.9.33",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/registry.json CHANGED
@@ -111,10 +111,10 @@
111
111
  {
112
112
  "id": "dsh-quota-watch",
113
113
  "name": "DSH Quota Watch",
114
- "description": "GLM 与 Copilot 额度悬浮球:胶囊、详情面板与侧栏卡片,宿主解析凭据,适配 web 与 desktop。",
114
+ "description": "GLM 与 Copilot 额度悬浮球:胶囊、详情面板与侧栏卡片,宿主解析凭据,适配 web 与 desktop。版本无关,详见仓库。",
115
115
  "category": "self-dev",
116
116
  "tags": ["配额", "悬浮球", "GLM", "Copilot"],
117
- "verified": ["0.1.7-rc.2", "0.2.0-rc.2"],
117
+ "decoupled": true,
118
118
  "source": "npm",
119
119
  "npm": "@iasiv5/dsh-quota-watch",
120
120
  "github": "iasiv5/dsh-quota-watch",
@@ -123,10 +123,11 @@
123
123
  {
124
124
  "id": "dsh-surf",
125
125
  "name": "DSH Surf",
126
- "description": "网络冲浪入口:设置一级菜单一键打开自托管 Selkies/Chrome 远程桌面,视频流直连;需自建容器并挂认证反代。",
126
+ "description": "网络冲浪入口:设置一级菜单一键打开 Selkies/Chrome 远程桌面,视频流直连;需自建容器与认证反代。版本无关,详见仓库。",
127
127
  "category": "self-dev",
128
128
  "tags": ["浏览器", "远程桌面", "Selkies", "网络冲浪"],
129
- "verified": ["0.1.7-rc.2"],
129
+ "decoupled": true,
130
+ "audience": "internal",
130
131
  "source": "npm",
131
132
  "npm": "@iasiv5/dsh-surf",
132
133
  "github": "iasiv5/dsh-surf",
@@ -135,10 +136,11 @@
135
136
  {
136
137
  "id": "dsh-obmc-web",
137
138
  "name": "DSH OBMC Web",
138
- "description": "在 DSH 里打开 OpenBMC 控制台:同源代理走 SSH 隧道,设置面板一键开启,支持 IP 探测与隧道重定向;需 SSH 跳板可达 BMC。",
139
+ "description": "DSH 内打开 OpenBMC 控制台:同源 SSH 隧道代理,设置页一键开启,IP 探测与隧道重定向;需 SSH 跳板可达 BMC。版本无关。",
139
140
  "category": "self-dev",
140
141
  "tags": ["OpenBMC", "远程管理", "SSH 隧道", "KVM"],
141
- "verified": ["0.1.7-rc.1", "0.1.7-rc.2"],
142
+ "decoupled": true,
143
+ "audience": "internal",
142
144
  "source": "npm",
143
145
  "npm": "@iasiv5/dsh-obmc-web",
144
146
  "github": "iasiv5/dsh-obmc-web",
@@ -147,10 +149,11 @@
147
149
  {
148
150
  "id": "dsh-onetree-log",
149
151
  "name": "OneTree Log",
150
- "description": "OneTree/dreport 日志包上传解包,14 域清单与预览;可登记服务器本地路径;已适配 0.1.7-rc.2,详见仓库。",
152
+ "description": "OneTree/dreport 日志包上传解包,14 域清单与预览;可登记服务器本地路径;版本无关,详见仓库。",
151
153
  "category": "self-dev",
152
154
  "tags": ["日志", "OneTree", "诊断", "工具"],
153
- "verified": ["0.1.7-rc.2"],
155
+ "decoupled": true,
156
+ "audience": "internal",
154
157
  "source": "npm",
155
158
  "npm": "@inventec/dsh-onetree-log",
156
159
  "github": "iasiv5/dsh-onetree-log",