dsh-plugin-t-expert 0.2.9 → 0.2.10

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/README.md CHANGED
@@ -33,6 +33,18 @@ dsh plugin --profile web add dsh-plugin-t-expert
33
33
 
34
34
  装完重启 DSH,然后在 **设置 → T专家** 里启用专家——**默认全部未启用**,未启用的专家不能被召唤。
35
35
 
36
+ ### 兼容性与两个已知依赖
37
+
38
+ 团队引擎里有两处**贴着宿主实现细节**的适配,升级 DSH 前值得知道:
39
+
40
+ | 依赖 | 说明 | 失效时的表现 |
41
+ | --- | --- | --- |
42
+ | 子代理投递契约(`ctx.subagents` 的 `followup` / `sendMessage` 与两个 `Symbol.for` 内部接口) | 插件要在"成员已退役"时拦住投递,而宿主没有公开的 retire/forget 扩展点,只能按**实测过的版本**包装这几个方法;已实测版本写在 `lib/teams/harness-compat.js` 的 `TESTED_HARNESS_VERSIONS`(当前 `0.1.5-rc.1` / `rc.2`) | 引擎**降级**(团队功能不可用,名册与召唤不受影响),日志与 `/t` 会给出原因。若上游把服务冻结或改成 accessor,会明确报「属性不可写」而不是一个裸 `TypeError` |
43
+ | 活动面板的客户端物化用 `new Function` 求值内联产物 | 因此需要宿主允许 `eval`。当前 DSH 壳**没有**设置 CSP,本机实测可用 | 壳将来若加严格 CSP,面板模块会加载失败——失败会记 `console.error` 并跳过,不影响名册与团队引擎 |
44
+
45
+ 这两条都不是"插件写坏了",而是宿主还没提供文档化扩展点。要根治第一条,需要宿主提供一个
46
+ 文档化的退役/投递拦截扩展点,插件改为挂在那里。
47
+
36
48
  ---
37
49
 
38
50
  ## 二、数据目录
@@ -103,6 +115,14 @@ dsh plugin --profile web add dsh-plugin-t-expert
103
115
  `t-expert-manager` 正文里的 `tz.sh` 路径是注册时按本机解出来的,不是写死的。
104
116
  后两个是 `dsh-project-expert`(项目专家模式)这个 agent preset 里同名 skill 的随包副本。
105
117
 
118
+ ### 本地活动的可见范围
119
+
120
+ 设置页「团队」标签读的是插件在本机注册的只读接口(`/plugins/t-team/…`):每个请求都先过宿主的
121
+ Host/Origin 校验与浏览器认证,静态资源另有白名单,非白名单一律 404。其中团队活动接口会**遍历
122
+ 当前机器的所有工作区**,所以任何已认证的本机浏览器上下文都能看到这些工作区里的团队名、成员名
123
+ 与任务标题(这是内置引擎的既有行为,不是 T专家 自己引入的)。DSH 是单用户本地应用,风险有限;
124
+ 只在你的机器上有不信任的浏览器扩展或共享浏览器配置时才需要留意。
125
+
106
126
  ---
107
127
 
108
128
  ## 五、小队
@@ -110,8 +130,12 @@ dsh plugin --profile web add dsh-plugin-t-expert
110
130
  设置 → 队伍 里搜索、修改成员 / 别名 / 启停,然后保存:改动写回 `~/.t-team/teams.json`,
111
131
  再调用小队编译器生成引擎配置;**编译失败会自动回滚**,不会把坏定义留在盘上。
112
132
 
113
- > ⚠️ **保存后要重启 DSH 才对建队生效**:`/t` 列表会立刻显示新小队,
114
- > 但引擎配置在插件启动时就已载入,不重启用新小队建不了队。
133
+ **保存后立即生效,无需重启 DSH**:编译成功后运行中的插件会就地重载引擎配置,
134
+ `/t` 列表与新建队(含队长提示段)随即看到新小队;已经跑起来的团队不受影响,继续用启动时的成员表。
135
+
136
+ - 编译失败并**回滚**时不会重载:盘上还是旧配置,建队照旧按旧小队走,设置页会直接报错。
137
+ - 引擎重载本身失败时会留下可诊断的信号(日志里有 `[t-team]` 的 error/warn),
138
+ 并且报错时会带着原因说清引擎当前是否可用,不会静默假装已生效。
115
139
 
116
140
  约束:小队 key 只能 ASCII `a-z0-9-`(中文放 `description` / `aliases`);每队 ≤ `maxMembers`(默认 8,见「六、配置项」);
117
141
  小队总数 ≤ 48;别名全局唯一;成员必须是名册里真实存在的专家。
@@ -133,7 +157,7 @@ dsh plugin --profile web add dsh-plugin-t-expert
133
157
  | `provider` | `"spawn"` | 召唤专家用的子代理 provider |
134
158
  | `divisions` | `[]` | 留空=自动扫描 `root` 下所有含 `.md` 的分类 |
135
159
  | `maxSummonBatch` / `summonConcurrency` | `8` / `4` | 批量召唤上限与并发 |
136
- | `stateDir` | `.agent-teams` | 团队状态目录(**工作区内的相对路径**),团队状态落在 `<工作区>/<stateDir>/<teamId>/`。内置团队引擎、设置页「队伍」标签与只读预检工具 `t_team_plan_check` 都按它定位团队。⚠️ 三者当前**不都读这个字段**:设置页与 `t_team_plan_check` 走的仍是引擎配置文件里的同名键,两者不一致时会「看不到团队且不报错」——改这个字段前请确认三处取值一致 |
160
+ | `stateDir` | `.agent-teams` | 团队状态目录(**工作区内的相对路径**),团队状态落在 `<工作区>/<stateDir>/<teamId>/`。内置团队引擎、设置页「队伍」标签与只读预检工具 `t_team_plan_check` 都按它定位团队——**三处都读这一个字段**,不存在第二来源(自检 8f 有静态 + 行为双断言兜着)。要挪团队状态目录只改这里 |
137
161
  | `memberProvider` / `memberModel` / `maxMembers` | — | 转给内置团队引擎;`maxMembers` 还由插件透传给小队编译器(见「五、小队」) |
138
162
 
139
163
  ---
@@ -149,14 +173,31 @@ dsh plugin --profile web add dsh-plugin-t-expert
149
173
  ## 八、开发
150
174
 
151
175
  ```bash
152
- npm run build # 构建客户端产物(lib/client.js)
153
- npm run verify # 自检套件:插件契约、工具、remote、客户端产物、播种与快照,
176
+ npm install # 装开发期依赖(.npmrc 里开了 legacy-peer-deps,原因见该文件)
177
+ npm run typecheck # 类型检查(tsconfig.json 只覆盖自研 Host 文件,不碰并入的引擎)
178
+ npm run verify # 自检套件(现 468 项断言):插件契约、工具、remote、客户端产物、播种与快照,
154
179
  # 外加「发布包自洽」(真打一份 tgz、解开、再用插件解析器读一遍)
180
+ npm run build # 构建客户端产物(lib/client.js)
155
181
  npm run sync-data # 把运行时数据同步进包内 data/
156
182
  ```
157
183
 
158
- > 这些 script 的实现不在本仓库:维护者的构建 / 自检脚本放在仓库同级的运维目录,
159
- > 由 `package.json` 以 `node ../<file>.mjs` 引用。只 clone 本仓库跑不了构建与自检。
184
+ `tools/` 里的构建 / 自检 / 同步脚本**随仓库走**,所以干净 clone 就能跑 `typecheck` 与 `verify`;
185
+ 需要运维台(`tz.sh`、`add-expert.py`)的那几节在本机没有运维目录时会**显式打印 skip**,不会
186
+ 静默少跑。推送与 PR 由 `.github/workflows/ci.yml` 跑同一套门禁。
187
+
188
+ 四条容易踩的开发约定:
189
+
190
+ - **线格式(remote)的 schema 只有一份**:`lib/remote-schemas.js`,host 的 `lib/remote.js`
191
+ 与客户端 `src/client.jsx` 都从它取;两端各自只保留**信封**(`descriptor()` / `direct()`)。
192
+ 改字段只改 schema 文件;改方法签名要同时改两端信封——自检会机械比对每个方法的**参数名与
193
+ typeSymbol 是否两端一致**,漏改一处就会被拦下(不用等调用期)。
194
+ - `lib/client.js` 是**入库的构建产物**(≈1 MB)。改客户端只改 `src/client.jsx`,
195
+ 然后必须重跑 `npm run build` —— 自检里有一条「产物新鲜度」门禁,忘了重跑会拦下来。
196
+ - `package-lock.json` 管的是**本插件自己的 devDependencies**;用户装插件用的 `pnpm`
197
+ 是宿主 `dsh plugin` 在 profile 目录里跑的,两者互不冲突,不要为了方便删掉 lock。
198
+ - `lib/squads.js` 这类数据层**不写 `console`**:诊断一律走注入的宿主 logger
199
+ (`ctx.logger`),桌面与 Web 里 stderr 用户看不见。编译团队配置是**异步**的
200
+ (`execFile`,不是 `execFileSync`)—— 同步跑会把整个 Host 事件循环卡住。
160
201
 
161
202
  内置团队引擎(`lib/teams/`)是**手工维护的源码**,改引擎直接改这里;
162
203
  名册(`data/experts/`)与中文侧车(`data/zh/`)就是发布源,两者都不再从任何上游同步。
package/lib/bootstrap.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /**
2
3
  * 首次启动播种:把包内自带的名册快照落到可写数据目录,让插件对任何机器都自包含。
3
4
  *
package/lib/catalog.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /**
2
3
  * T专家 花名册:扫描固定专家目录,解析 frontmatter,按 slug 建立索引。
3
4
  *
@@ -9,6 +10,24 @@ import { createHash } from "node:crypto";
9
10
  import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
10
11
  import { dirname, join, relative } from "node:path";
11
12
 
13
+ /**
14
+ * 名册本体:`Map<slug, Expert>`,另外挂上扫描诊断与分类表。
15
+ *
16
+ * 之所以是 Map 加属性而不是 `{bySlug, ...}`:调用方(`lib/index.js`、测试)大量直接
17
+ * `catalog.get(slug)` / `catalog.size` / 迭代,保持 Map 形态的改动面最小。
18
+ * @typedef {Map<string, any> & {
19
+ * divisions: string[],
20
+ * rosterDivisions: string[],
21
+ * customDivisions: string[],
22
+ * customRoot: string | undefined,
23
+ * customLabels: Record<string, string>,
24
+ * sidecarPresent: boolean,
25
+ * skippedFiles: number,
26
+ * unreadableDivisions: string[],
27
+ * labels: Record<string, string>,
28
+ * }} CatalogMap
29
+ */
30
+
12
31
  /** 内置的分区目录名(= divisions.json 的键)。 */
13
32
  export const DEFAULT_DIVISIONS = [
14
33
  "academic",
@@ -248,7 +267,8 @@ export async function loadCatalog(root, divisions, options = {}) {
248
267
  .filter(isValidDivision);
249
268
  /** 读不动的分区(供 host 侧 snapshot 暴露,诊断用)。 */
250
269
  const unreadableDivisions = (discovery?.unreadable ?? []).map((item) => item.division);
251
- const catalog = new Map();
270
+ /** @type {CatalogMap} 名册本体 + 扫描诊断(见 CatalogMap 的各字段说明)。 */
271
+ const catalog = /** @type {CatalogMap} */ (new Map());
252
272
 
253
273
  /** 解析一个 persona 文件并放进名册;自定义根的文件额外带 custom 标记与写回路径。 */
254
274
  async function ingest(fromRoot, division, filePath, fileName, isCustom) {