dsh-plugin-t-expert 0.3.92 → 0.4.9

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 ADDED
@@ -0,0 +1,267 @@
1
+ # 更新日志
2
+
3
+ 本项目遵循[语义化版本](https://semver.org/lang/zh-CN/)与 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
4
+
5
+ > **本文件是 2026-09-28 才建的**(0.4.5 起)—— 此前这个仓库一直没有 CHANGELOG,
6
+ > 变更历史只存在于 git 提交标题里。所以:
7
+ >
8
+ > - **0.4.5 起**的条目是当时手写的,细节完整;
9
+ > - **0.3.93 – 0.4.4** 的条目由 `git log` 的提交标题**回溯生成**(每条附提交号与日期),
10
+ > 只有一句话摘要,细节以对应提交为准;
11
+ > - **0.3.92 及更早**(共 51 个版本提交)不再逐条回溯:npm 上发布的最后一个版本是
12
+ > **v0.3.92**,`git tag` 与 `git log` 是唯一可信来源。
13
+ >
14
+ > 版本号规矩(与 dsh-helper-patch 同一套,用户 2026-09-26 定):**每次 git 提交都升一级版本号** ——
15
+ > 改 `package.json` 的 `version` 与本文件顶部段落,两者连同提交一起走;
16
+ > 发布时**不要再跑 `npm version`**(那会把已提交好的版本又升一级,发出去的号与 CHANGELOG、tag 就对不上),
17
+ > 只补一个 annotated tag(`git tag -a`,因为 `git push --follow-tags` 只推带注释的 tag)。
18
+
19
+ ## [0.4.9] — 未发布
20
+
21
+ 对宿主版本的 peer 范围收敛为**只保留下限**:内核以后升级,这一行不用再改。
22
+
23
+ ### 变更
24
+
25
+ - **18 个 `@deepseek-ai/dsh-*` 的 peer 范围由四段 caret 改为 `>=0.1.5-rc.1`**。
26
+ 原文是 `^0.1.5-rc.1 || ^0.1.6-alpha.2 || ^0.1.7-rc.1 || ^0.2.0-rc.1` —— 每来一个内核
27
+ 版本就补一段,是一份只增不减的清单。实测(直接跑 `dshmarket` 的
28
+ `deriveHostCompatibility`)表明**这些分段并没有起到门禁作用**:判定链是
29
+ `rangeResult`(裸 semver)→ 不满足时再走 `classifyPeer`,而后者只在「低于下限」
30
+ 或「超出**显式**上界」时给 `risk`;超出 caret 的**隐式**上界只算 `warning` 并被
31
+ `declarationResult` 放行。所以三段写法在宿主 `0.5.0` 上照样判 `compatible`,
32
+ 第四段纯属表意。
33
+ 改成 `>=0.1.5-rc.1` 之后:
34
+ - 下限仍是真正验证过的 `0.1.5-rc.1` —— 更老的宿主照旧判 `incompatible` 被拦;
35
+ - 上限彻底不存在,`0.2.3-rc.4` / `0.3.0` / `0.5.0` / `1.0.0` 实测全部 `compatible`,
36
+ 不必再为装机判定改这一行。
37
+ ⚠️ 这只解决**装机门禁**。内核若有破坏性 API 变更(改槽位名、改 codec 协议、改配置
38
+ 字段机制、改包结构),插件仍要改代码并重新发布 —— peer 范围管不到运行时。
39
+
40
+ ## [0.4.8] — 未发布
41
+
42
+ ### 修复
43
+
44
+ - **`dsh.client.inject` 补上 `@deepseek-ai/dsh-client-ui-slots`**。客户端源码 `src/client/index.jsx`
45
+ 的静态 `inject` 列出了 `"slots"` 服务(`ctx.slots.inject` / `ctx.slots.register` 都依赖它),
46
+ 但 `package.json` 的 `dsh.client.inject` 没有映射它的提供者(`@deepseek-ai/dsh-client-ui-slots`)。
47
+ slots 是 shell 级服务,在大多数启动顺序下别的插件或宿主 shell 会先加载它,所以此前没报错;
48
+ 但在极端启动顺序下 `ctx.slots` 会是 `undefined`,设置卡片与 @ 召唤菜单一同消失且不报错。
49
+ 补上后,插件本身的依赖链才完整,不再依赖"别人先加载了它"这个巧合。同一修复在
50
+ `dsh-helper-patch` 建仓时就已经做了([#b1f2c8b](https://github.com/jiuaiwo/dsh-helper-patch/commit/b1f2c8b)),
51
+ 当时 T专家 那份被漏掉了。
52
+
53
+ ## [0.4.7] — 未发布
54
+
55
+ 这一批是 2026-09-28 审查的尾巴——杂项清理与"模型可见的事实不能写死"那条原则的延伸。
56
+ 共同主题是**让长期沉默的代码开口说话**:要么删掉,要么留一句可归因的日志。
57
+
58
+ ### 修复
59
+
60
+ - **`TTeamRemote` 的 `static inject` 去掉从未读取的 `settings`**。cordis 要等 inject 里
61
+ **每一个**服务就绪才实例化这个类(见 cordis 源码的 inject 实现);宿主 settings 一旦
62
+ 晚到或出错,T专家 的整个面板就跟着报"服务暂时不可用",而排查的人却被引去查 typert
63
+ 与调度引擎——因为这才是表面报错处。删掉之后 T专家 的激活就不再依赖宿主 settings 的
64
+ 加载顺序。同一类问题在 dsh-helper-patch 0.13.0 那批也修过一次(schedule-remote 的死
65
+ 依赖),模式相同。
66
+ - **死代码清理**:`lib/index.js` 里的 `renderPlanReport`(预检报告的中文渲染器,
67
+ 0.4.7 之前已**全仓零调用**——预检功能本身已随定时任务迁出)、dangling 的 `/**`
68
+ JSDoc 起点(148 行,一行空 `/**` 后另起一行真注释)、空的 `if (typeof ctx.on === "function") {}`
69
+ 占位(361 行,预留但从未填过)。三者都是"代码看着在跑、其实没在跑"。
70
+ - **`readJson`(`lib/catalog.js`)与 `parseStore`(`lib/skill-gate/store.js`)不再把错误
71
+ 吞了**。0.4.7 之前是空 `catch`:JSON 损坏、读权限问题等都被无声地当成"文件不存在"。
72
+ UI 表现是"中文名/简介静默变全英文"或"技能开关回到默认状态",用户无一可查的线索。
73
+ 现在 ENOENT 仍当空对象(侧车目录可缺省——这是正常路径);其它错误 `console.warn` 出
74
+ 一句 `[t-team] 读 <path> 失败,按空对象继续:…`,保留原行为(仍当空对象),但下次
75
+ 写盘会用原子 rename 把坏文件替换掉。
76
+
77
+ ### 变更
78
+
79
+ - **名册规模不再以字面量形式出现在模型可见的文本里**。`src/client/i18n.js` 的
80
+ `settings.summary` 原来写"专家名册(323 位 · 22 个分区)"——这是**模型可见的事实**
81
+ (提示段会进 prompt),一旦名册增删或改分区就立刻过期,且没有任何东西会红。
82
+ `lib/index.js` 的 `rosterScale` 本来就是从目录现算的(并按目录缓存),数字应当
83
+ 永远从真源来。这一版把字面量从文案里清掉,模型可见的事实只能来自运行时——规模
84
+ 已经在面板的 `tab.count` 与 `hint` 里动态显示了,summary 本身不需要再含数字。
85
+ - **`lib/index.js` 里两处解释性注释的字面量也清掉**("a 316-expert, 22-division roster"
86
+ 与"一层 301 / 递归 316")。同样是"上一次见到的数字"被写死成注释:写注释的那天是对的,
87
+ 之后就是错的;而且它解释了**机制**(为什么要递归),不是**当下**值。
88
+ - **README 的"浮层标签"表述**:原文是"专家 → 技能 → 任务"——「任务」标签在 0.3.x 已
89
+ 随定时任务功能一起迁出(T专家 不再做定时任务,由 `dsh-helper-patch` 接管)。
90
+ 改回"专家 / 技能"两个标签,并在备注里说明「任务」的去向。`verify` 里钉这条的正则
91
+ 同步更新(旧的 `/浮层标签:\*\*专家 → 技能 → 任务\*\*/u` 与实际不一致,修了之后
92
+ 会红)。
93
+
94
+ ### 自检
95
+
96
+ - 474 → **483 项**,新增 [21b] 一节(9 条)专门钉死"代码看起来在跑、其实没在跑"与
97
+ "文档与实现不符":remote inject 不含未读 settings、`renderPlanReport` 不在
98
+ `lib/index.js`、没有连续 `/**` + `/**` 的孤立注释、i18n 不含字面量数字、README
99
+ 没有"专家 → 技能 → 任务"、没有单行 if + 紧邻空块的占位、`parseStore` 与 `readJson`
100
+ 的 JSON 损坏会 `console.warn`。**这一节是反向回归**:任何一条后来又把 0.4.7 清掉的
101
+ 问题长回来都会立刻红。
102
+ - ci.yml 里两处写死的断言数同步到 483。
103
+ - typecheck / check-client(13 文件 0 处未定义标识符)/ render-smoke(9/9)/
104
+ verify(483/483)/ npm pack --dry-run(698 个文件)/ sync-data --check(656 个文件)
105
+ 全绿;lib/client.js 已重建(内嵌的 CLIENT_BUILD_VERSION 由 build 自动对齐到 0.4.7,
106
+ [6c] 的产物新鲜度与版本一致性两道门禁都盯着它)。
107
+
108
+ ## [0.4.6] — 未发布
109
+
110
+ 四个"端到端消息没通"的修复 + 一组工程化配套。共同点是:单看每段都很小,串起来才让
111
+ host 早就在说的话**真的被面板听到**。
112
+
113
+ ### 修复
114
+
115
+ - **技能开关的 `skills` 服务换实例时不再静默失效**。`apply` 里那一个"只装一次"的守卫
116
+ 是必要的:cordis 的 `reflect.provide` 对同名服务是**硬冲突**(`service "X" has been
117
+ registered at <fiber>`,见 cordis 源码的 `if (this.store[key]) throw`),第二次装配
118
+ 必然抛;但"只装一次"不等于"之后什么都不说"——宿主真把 `ctx.skills` 换成新实例时(另一个
119
+ 插件重载并重新 provide 了它),停用名单仍作用在旧实例上,新实例的技能不受约束,那是
120
+ 静默失效。现在改为记下已包装的 skills 对象,新回调给的是**同一个对象**就幂等返回,
121
+ **不是同一个**就 `logger.warn` 出声(说明重载 T专家 即可恢复),专家名册与召唤不受影响。
122
+ - **名册健康状态 `sidecar` 字段终于穿过 schema**。host 早就在快照里返回它(lib/index.js 的
123
+ `getCatalog`,把中文侧车缺失/跳过的文件/读不出的分类目录都报上),注释也写着
124
+ "面板可以据此提示"。但 `catalogSnapshotSchema` 从 0.3.x 一直**没声明**它,zod 会把
125
+ schema 里没有的键**直接剥掉**——上面 `customDivision` 那两个字段的注释已经警告过
126
+ "漏声明会被剥掉",这次踩在一个嵌套对象上,更难发现。0.4.6 把它写进 schema,
127
+ 面板新增 `roster.sidecarMissing` / `skippedFiles` / `unreadableDivisions` 三条警告
128
+ (中英,i18n 已同步),新增 `.t-team-warn` CSS 类——比 `t-team-error` 轻(功能仍在)、
129
+ 比 `t-team-note` 醒目(**名册正在静默缺东西**,不容忽视)。
130
+ [20c] 把这条**端到端**钉住:单独每条都好做,加起来才证明"消息通了"。
131
+
132
+ ### 工程化
133
+
134
+ - **`tsconfig.json` 把 `lib/skill-gate/` 下 6 个文件纳入 include**。它们此前**全部**标了
135
+ `// @ts-check` 但又**全部**不在 include 里——和 dsh-helper-patch 0.13.0 修的那个坑
136
+ 一模一样,只是踩在子目录里,常规 `readdirSync(join(HERE, "lib"))` 看不见。
137
+ `[20]` 同步改成**递归**扫(对照辅助补丁那边的写法),并把硬编码阈值从 7 提到 13
138
+ (7 个老 + 6 个 skill-gate)。补上之后第一次跑 typecheck:**全部通过**——说明
139
+ lib/skill-gate/*.js 写得干净;这也是"标记死了"和"标记下其实有真问题"两种情况中
140
+ 比较幸运的那一种,但意义在于此后的任何修改都被 typecheck 盯着。
141
+ - 新增 `[20c] sidecar 端到端`(7 条):import `catalogSnapshotSchema` 真跑一遍解析,
142
+ 确认 sidecar 字段被保留;读 host 源码确认 `sidecar: { zhRoot, skippedFiles,
143
+ unreadableDivisions }` 真在快照里被拼出(不是只在 schema 里写);读 settings.jsx 确认
144
+ 引用了 `snapshot.sidecar` 与 `healthNotice`;读 css.js 确认 `.t-team-warn` 类存在;
145
+ 在 zh/en 两份字典里各查一次 i18n 三个新键(缺一个就红——中文少一个、英文少一个、
146
+ 多打一遍都会被抓)。
147
+ - 总数 467 → **474 项**,ci.yml 里两处写死的断言数同步到 474。
148
+
149
+ ### 自检
150
+
151
+ - typecheck / check-client(13 文件 0 处未定义标识符)/ render-smoke(9/9)/
152
+ verify(474/474)/ npm pack --dry-run(698 个文件)/ sync-data --check(656 个文件)全绿。
153
+
154
+ ## [0.4.5] — 未发布
155
+
156
+ 一次代码审查(2026-09-28,与 dsh-helper-patch 一起做的只读复查)查出的问题,这一版修的是
157
+ **客户端与 host 之间错误码对不上**的那一组。这类缺陷不会报错,只会静默走错分支,
158
+ 所以两处都补了自检断言把两端的字符串钉在一起。
159
+
160
+ ### 修复
161
+
162
+ - **技能开关的乐观锁冲突码对不上**:`skills-panel.jsx` 比的是 `skillGate/conflict`,
163
+ 而 host 的 `setDisabled` 抛的是 `tTeam/conflict` —— `skillGate` 那个命名空间在 0.4.0
164
+ 已整体并进 `tTeam`(见 `lib/remote.js` 顶部的说明),这处是合并时漏改的残留。
165
+ 后果不是报错,而是**静默走 else 分支**:冲突时面板不会自动重拉快照,用户只看到一句
166
+ "保存失败",再点一次仍然失败(手里的修订号还是旧的),非得手动刷新页面才解得开 ——
167
+ 而这条路径偏偏只在多窗口同时改设置时才走到,于是它既难复现又难归因。
168
+ - **自建专家的冲突判断在本地化文案里搜错误码**:`settings.jsx` 写的是
169
+ `message.includes("custom-slug-taken")`,而 host 的 `customError` 把那串铸在
170
+ **`error.code`** 上(`tTeam/custom-slug-taken`),`message` 是给人看的中/英文句子
171
+ —— 里面根本不含这串码,于是那个 `if` **永远为 false**。后果:slug 或名称撞车时
172
+ 面板不重拉名册,用户看到"已被占用"却不知道该跟谁比,本地快照里也始终没有那个占用者,
173
+ 改完再存还是撞。改为判 `cause.code`,并把 `tTeam/custom-conflict`(编辑时指纹不符 =
174
+ 别的窗口改过同一个文件)一并纳入重拉条件。
175
+ - **`businessError` 的 `error` 参数缺类型标注**:它是 `any`,于是 `candidate` 也跟着是
176
+ `any`,`new RemoteError(...)` 的类型检查整个失效(TS 对 any 一律放行)。表现是
177
+ 这里**本该有**的那道「上游类型缺口」抑制指令根本不需要存在 —— 也就是说
178
+ `RemoteErrorDetailsMap` 是闭合集合、`tTeam/*` 业务码不在其中这件事,此前没有任何东西在检查。
179
+ 补上 `{unknown}` 与显式取值后,抑制指令才名副其实(临时删掉它会立刻报
180
+ `TS2345: Argument of type 'string' is not assignable to parameter of type 'keyof RemoteErrorDetailsMap'`)。
181
+ dsh-helper-patch 同一个方法踩过同一个坑(那边 0.13.0 修)。
182
+
183
+ ### 自检
184
+
185
+ - 459 → **467 项**,新增「错误码两端对齐」一组:复现 host 侧 `customError` 的铸码规则
186
+ (并先断言那行规则还在,否则比对就是自说自话)、逐个核对
187
+ `error.customSlugTaken` / `customNameTaken` / `customConflict` 铸出的码在客户端源码里
188
+ 确实被用来分支、客户端不再出现 `if (message.includes("custom-…")` 这种写法、
189
+ `skillGate/conflict` 无残留、host 的 `setDisabled` 抛的确实是 `tTeam/conflict`、
190
+ `businessError` 的 `error` 有类型标注。
191
+ - `.github/workflows/ci.yml` 里两处写死的断言数同步到 467(`[21] 文档计数一致性` 会拿它
192
+ 与实测总数比,不同步就红)。
193
+ - 那条「不再在文案里搜错误码」的断言只匹配**真实代码形式**:`readClientSource()` 读的是
194
+ `src/client` 源码,而修复处的注释必然会引用旧写法来说明"原先错在哪",
195
+ 用裸 `includes` 会被自己的注释触发(第一版就这么红过一次)。
196
+
197
+ ## [0.4.4] — 未发布
198
+
199
+ - 细节清理(ChoiceGroup 的唯一 id、输入框类名语义、死导出)(`d851aaf`,2026-09-28)
200
+
201
+ ## [0.4.3] — 未发布
202
+
203
+ - 技能开关的装配加一次性守卫(动态 inject 会重复触发)(`aaff105`,2026-09-28)
204
+
205
+ ## [0.4.2] — 未发布
206
+
207
+ - 去掉中文字典里重复的 tab.skills(`776b576`,2026-09-28)
208
+
209
+ ## [0.4.1] — 未发布
210
+
211
+ - 技能开关改挂到已有的 tTeam remote(第二份 remote 入口在实机上挂不上)(`7a9c696`,2026-09-28)
212
+
213
+ ## [0.4.0] — 未发布
214
+
215
+ - 技能开关(dsh-plugin-skill-gate)整体并入(`f03a50b`,2026-09-28)
216
+
217
+ ## [0.3.103] — 未发布
218
+
219
+ - 分类页小节与列表之间补上间距(`775010d`,2026-09-28)
220
+
221
+ ## [0.3.102] — 未发布
222
+
223
+ - 分类列表恢复成一张有边框的卡片(内容是留白不够,不是不要框)(`9ff9182`,2026-09-28)
224
+
225
+ ## [0.3.101] — 未发布
226
+
227
+ - 分类页去掉两处多余的横线(`714123b`,2026-09-28)
228
+
229
+ ## [0.3.100] — 未发布
230
+
231
+ - 分类页的列表边框贴住了行内按钮(`6853892`,2026-09-28)
232
+
233
+ ## [0.3.99] — 未发布
234
+
235
+ - 分类折叠头改成条状卡片(`a04007c`,2026-09-28)
236
+
237
+ ## [0.3.98] — 未发布
238
+
239
+ - 专家名册按分类折叠(`c66d236`,2026-09-28)
240
+
241
+ ## [0.3.97] — 未发布
242
+
243
+ - 面板标签页被拉满整行(官方 SegmentedTabs 的类名撞了自绘那套)(`c6903ee`,2026-09-28)
244
+
245
+ ## [0.3.96] — 未发布
246
+
247
+ - 设置面板搬到「插件列表 → T专家」并换官方 primitives(`5ab56be`,2026-09-28)
248
+
249
+ ## [0.3.94] — 未发布
250
+
251
+ - 输入框尺寸修复:分类新建栏与专家页搜索框自己钉死尺寸(`3af1e51`,2026-09-28)
252
+
253
+ ## [0.3.93] — 未发布
254
+
255
+ - 插件列表的名称/简介改为中英双语(`cb95d0c`,2026-09-28)
256
+
257
+ ---
258
+
259
+ ## 0.3.92 及更早
260
+
261
+ npm 上发布的最后一个版本是 **v0.3.92**(`git tag` 里最新的那个)。0.3.92 及更早共有 51 个
262
+ 带版本号的提交,不在这里逐条回溯 —— 需要时用:
263
+
264
+ ```bash
265
+ git log --oneline --reverse | grep -E '升版本到 |^0\.[0-9]+\.[0-9]+'
266
+ git tag --sort=v:refname
267
+ ```
package/README.md CHANGED
@@ -7,7 +7,8 @@ dsh-plugin-t-expert 插件:包含 **22 个分类 / 323 位专家**的名册(
7
7
  | 做什么 | 怎么做 |
8
8
  | --- | --- |
9
9
  | 用专家 | 输入框打 `@` → 按分类分组的菜单 → 选中的专家作为引用 chip 进本轮对话;模型也可自行 `summon_t_expert` |
10
- | 管名册 | 设置 → T专家 → **专家**(启停 / 看提示词 / 新建自建专家) |
10
+ | 管名册 | 插件列表 → T专家 → **专家** 标签(看提示词 / 新建自建专家 / 分类管理) |
11
+ | 管技能 | 插件列表 → T专家 → **技能** 标签(关掉的技能不进模型目录,少占上下文) |
11
12
 
12
13
  ## 安装
13
14
 
@@ -36,7 +37,7 @@ CLI 会被拒绝 —— 任何插件都一样)。
36
37
 
37
38
  ## 快速上手
38
39
 
39
- 1. **启用专家**:设置 → T专家 → **专家**,把要用的打开 —— **默认全部未启用**,未启用的不会被召唤。
40
+ 1. **看名册**:插件列表 → T专家 的详情页里就是管理面板(**专家** / **分类** 两个标签)。
40
41
  2. **用一位专家**:输入框打 `@`,按分类挑一位,它以引用 chip 进入本轮对话;也可以直接说"让 XX 专家看看",
41
42
  由模型调 `summon_t_expert`。
42
43
 
@@ -44,9 +45,9 @@ CLI 会被拒绝 —— 任何插件都一样)。
44
45
 
45
46
  | 位置 | 是什么 |
46
47
  | --- | --- |
47
- | 设置 → T专家 | 管理页,两个标签:**专家**(启停 / 提示词 / 新建自建专家)、**分类**(自建分类增删改) |
48
+ | 插件列表 → T专家 | 管理面板,两个标签:**专家**(提示词 / 新建自建专家)、**分类**(自建分类增删改)。2026-09-28 起不再占「设置」里的分区 |
48
49
  | 输入框 `@` | 按分类分组的专家菜单,选中即插入引用 chip |
49
- | 输入区「T专家」按钮 | 浮层标签:**专家 → 技能 → 任务**,默认停在**专家**;专家 / 技能两个标签带计数(该标签里的总条数,不随搜索框变化) |
50
+ | 输入区「T专家」按钮 | 浮层标签:**专家 / 技能**(默认停在**专家**);专家 / 技能两个标签带计数(该标签里的总条数,不随搜索框变化)。原「任务」标签已在 0.3.x 迁出(T专家 不再做定时任务,由 `dsh-helper-patch` 接管) |
50
51
  | 会话标题栏徽章 | 后台任务只读面板(当前会话及其子代理的任务:状态、退出码、耗时);只在真有任务在跑时亮起并给出数量,**面板绝不自动展开**,可拖动、位置记在本机 |
51
52
 
52
53
  ## 专家
@@ -62,6 +63,26 @@ CLI 会被拒绝 —— 任何插件都一样)。
62
63
  | `summon_t_expert` | 召唤 1 位专家:以它的完整人格跑一个子代理并返回结果 |
63
64
  | `summon_t_experts` | 并行召唤多位(最多 8 位、并发 4;部分失败仍返回成功的结果) |
64
65
 
66
+ ## 技能开关(2026-09-28 从 dsh-plugin-skill-gate 并入)
67
+
68
+ 面板第三个标签页:**关掉的技能不再进入每次请求的 `<available_skills>` 清单**,从而少占上下文。
69
+ 不改任何 `SKILL.md`,也不把文件移出扫描目录 —— 斜杠菜单里仍可手动点(与官方
70
+ `disable-model-invocation` 同一语义),重新打开立刻恢复。
71
+
72
+ - **视角**:列表默认是**全部来源的并集**(宿主全局层 / 默认 preset / 各活跃会话 / 磁盘扫描)。
73
+ 模型实际读到的是它自己那条 scope 链,两者并不相等 —— 顶部「视角」下拉切到具体来源,
74
+ 计数就变成那个视角的真实值。
75
+ - **批量**:对当前筛选结果一键启用 / 停用。
76
+ - 已带 `disable-model-invocation` 的技能显示为「原生关闭」,不会被强行打开。
77
+ - 名单在 `~/.dsh/skill-gate.json`(**沿用原插件的路径**:并入时没改,所以已有开关状态直接沿用)。
78
+ 改完下一轮模型请求即生效,不必重启。
79
+
80
+ 实现:宿主启动时包装 `ctx.skills.snapshot / list / get`,把名单里技能的 `modelInvocable` 置 false
81
+ (`dsh-tool-skill` 生成目录时本来就会 `filter(isModelInvocable)`)。装配代码在 `lib/skill-gate/`,
82
+ remote 入口 `lib/skill-gate-remote.js`(wire 名仍是 `skillGate`,客户端信封同步)。
83
+ ⚠️ 用**动态** `ctx.inject(["skills"])` 而不是静态 inject:静态依赖会让「宿主缺 skills 服务」变成
84
+ 整个插件不激活 —— 连专家名册与召唤一起没。
85
+
65
86
  ## 数据与目录
66
87
 
67
88
  **全部数据都在你本机**:不连任何网络服务、不做遥测;名册与中文译文都随包分发,运行时不向上游拉取。
@@ -85,8 +106,8 @@ CLI 会被拒绝 —— 任何插件都一样)。
85
106
 
86
107
  | 现象 | 原因与处理 |
87
108
  | --- | --- |
88
- | 召唤不了某位专家 | 它没被启用(设置 → T专家 → 专家);名册默认是"全部未启用" |
89
- | 改完插件看不到变化 | 客户端与宿主代码都要重启 DSH 才生效;设置 → T专家 标题行有当前版本号(`v0.3.x`)可核对 |
109
+ | 召唤不了某位专家 | 它没在名册里、或已被停用:插件列表 → T专家 的**专家**标签里核对 |
110
+ | 改完插件看不到变化 | 客户端与宿主代码都要重启 DSH 才生效;插件列表 → T专家 的卡片副标题有当前版本号(`· v0.3.x`)可核对 |
90
111
 
91
112
  ## 卸载与反馈
92
113
 
package/lib/catalog.js CHANGED
@@ -365,12 +365,19 @@ export async function loadCatalog(root, divisions, options = {}) {
365
365
  return catalog;
366
366
  }
367
367
 
368
- /** 读 JSON,失败就当空对象(侧车目录可缺省)。 */
368
+ /**
369
+ * 读 JSON:文件不存在/侧车目录缺省当空对象(正常路径,0.4.7 之前是空 catch 静默吃);
370
+ * 其他错误(JSON 损坏、读权限、EISDIR 等)出声 + 返回空对象——调用方拿到的 {} 行为不变,
371
+ * 但日志里有了线索,否则用户侧只会表现为"中文名/简介静默变全英文",无法归因。
372
+ */
369
373
  async function readJson(path) {
370
374
  try {
371
375
  const parsed = JSON.parse(await readFile(path, "utf8"));
372
376
  return parsed !== null && typeof parsed === "object" ? parsed : {};
373
- } catch {
377
+ } catch (error) {
378
+ if (error?.code !== "ENOENT") {
379
+ console.warn(`[t-team] 读 ${path} 失败,按空对象继续:`, error?.message ?? error);
380
+ }
374
381
  return {};
375
382
  }
376
383
  }