dsh-plugin-yanyan-toolkit 1.2.0 → 1.4.1

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 (34) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.en.md +82 -0
  3. package/README.i18n.yaml +7 -6
  4. package/README.md +53 -37
  5. package/README.zh.md +24 -8
  6. package/cordis.patch.yml +10 -2
  7. package/icon.svg +5 -8
  8. package/lib/client.js +179 -4
  9. package/lib/index.js +120 -15
  10. package/locale/en.json +1 -1
  11. package/locale/zh.json +1 -1
  12. package/package.json +5 -2
  13. package/packages/dsh-plugin-yanyan-icon/README.md +33 -0
  14. package/packages/dsh-plugin-yanyan-icon/README.zh.md +33 -0
  15. package/packages/dsh-plugin-yanyan-icon/cordis.patch.yml +7 -0
  16. package/packages/dsh-plugin-yanyan-icon/icon.svg +7 -0
  17. package/packages/dsh-plugin-yanyan-icon/lib/client.js +388 -0
  18. package/packages/dsh-plugin-yanyan-icon/lib/index.js +239 -0
  19. package/packages/dsh-plugin-yanyan-icon/locale/en.json +6 -0
  20. package/packages/dsh-plugin-yanyan-icon/locale/zh.json +6 -0
  21. package/packages/dsh-plugin-yanyan-icon/package.json +32 -0
  22. package/packages/dsh-plugin-yanyan-subagent/README.md +3 -2
  23. package/packages/dsh-plugin-yanyan-subagent/README.zh.md +3 -2
  24. package/packages/dsh-plugin-yanyan-subagent/lib/client.js +157 -33
  25. package/packages/dsh-plugin-yanyan-subagent/lib/index.js +45 -4
  26. package/packages/dsh-plugin-yanyan-subagent/lib/legacy-hidden-ids.json +29 -9
  27. package/packages/dsh-plugin-yanyan-subagent/lib/log-rewrite.js +29 -0
  28. package/packages/dsh-plugin-yanyan-subagent/locale/en.json +1 -1
  29. package/packages/dsh-plugin-yanyan-subagent/locale/zh.json +1 -1
  30. package/packages/dsh-plugin-yanyan-subagent/package.json +2 -2
  31. package/packages/dsh-plugin-yanyan-update/README.md +1 -0
  32. package/packages/dsh-plugin-yanyan-update/README.zh.md +1 -0
  33. package/packages/dsh-plugin-yanyan-update/lib/index.js +86 -2
  34. package/packages/dsh-plugin-yanyan-update/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,73 @@
2
2
 
3
3
  All notable changes to this project are documented here.
4
4
 
5
+ ## 1.4.1 — 2026-11-15
6
+
7
+ ### 图标生成:修复生成图标偏小 + 版本号迭代
8
+
9
+ - **现象**(用户反馈):插件页「已安装插件」列表里研研工具包的哈士奇图标明显比 dsh-ops / dsh-pet 等邻居插件小。
10
+ - **根因**:`packages/dsh-plugin-yanyan-icon/lib/index.js` 的 `buildIconSvg` 默认布局为 `outer=6 / frameSize=52 / padding=4 / radius=14`,位图仅占 `44/64≈69%`;浅紫背景 `#eef2ff` 描边 `#c7d2fe` 在白底上几乎不可见,视觉上只剩中间一小块。用 Edge 无头浏览器按 DSH 真实 30px / 36px 渲染做了五方案对比图确认。
11
+ - **修复**:改为用户选定的「方案 A · 适度放大」——`outer=3 / frameSize=58 / padding=3 / radius=13`,位图占 `52/64≈81%`,保留圆角框呼吸感且 30px 下明显更清楚。同步 `normalizeOptions` 默认值、根与子包两处 `cordis.patch.yml` 的 config、单测断言与双语文档。
12
+ - **图标重生成**:用更新后的 `buildIconSvg` 重新生成聚合根包 `icon.svg` 与桌面三张哈士奇 `icon.svg` / `icon-2.svg` / `icon-3.svg`,结构校验 3/3 PASS 并经真实 Edge `<img>` 渲染确认。
13
+ - **版本**:聚合根包 → `1.4.1`;图标生成组件 `dsh-plugin-yanyan-icon` → `1.0.1`(随生成器修复一并迭代);其余组件版本不变。
14
+ - **测试**:`npm test` 全绿;新增「默认布局方案 A」单测锁定 `52/64` 占比,防止回退。
15
+ - **生效**:涉及 bundle 行与 `cordis.patch.yml`,需重新 `npm pack` 并在插件页更新安装(或重建 bundle 后刷新页面)。
16
+
17
+ ## 1.4.0 — 2026-11-15
18
+
19
+ ### 新增「图标生成」组件包
20
+
21
+ - **需求**:根据上传的图片生成适配插件列表图标位(`PackageArtwork`,列表行 30px / 详情卡片 36px)的 `icon.svg`。
22
+ - **规范探查**:`/dsh/node_modules/@deepseek-ai/dsh-plugin-manager/lib/typert.host.js` 的 `icon` schema 描述为 `Base64 image data URL from a package root's manifest icon or an exported <specifier>/icon; render as an image, not inline markup.`;`dsh-client-ui-plugin-manager` 的 `PackageArtwork` 用 `<img src={row.meta?.icon}>` 渲染,故生成物必须纯静态、自带背景与显式颜色、无脚本 / 外链 / `foreignObject`;市场索引 `MAX_INLINE_ICON_BYTES = 32 * 1024` 作为体积上限。
23
+ - **实现**:新增自写子包 `dsh-plugin-yanyan-icon`(纯 JS ESM、零第三方依赖)。client 半区照抄 `dsh-plugin-yanyan-update` 的 `__ModuleLoader__` + `MutationObserver` + `ctx.effect` 注入骨架,用原生 Canvas 居中裁切正方形并缩放成 48px PNG dataURL;host 半区照抄其 `/health` / `/generate` 路由骨架,纯函数 `buildIconSvg` 包装成 64×64 viewBox、圆角背景、内嵌 `data:image/png;base64,...` 的自包含静态 SVG(单一真源、可单测)。提供 30px / 36px 双尺寸预览与「下载 icon.svg」。
24
+ - **接线**:根 `cordis.patch.yml` 加第 5 条 insert 行、`dependencies` 加第 4 项、`lib/index.js` 的 `SUBPACKAGES` 加新包名,三份 README 与双语 `locale` 同步。
25
+ - **测试**:新包 12 个单测;聚合根包三份结构测试同步到第 5 条行 / 第 4 个组件 / 5 个组件版本。
26
+ - **版本**:聚合根包 → `1.4.0`;新组件 `dsh-plugin-yanyan-icon` → `1.0.0`;其余组件版本不变。
27
+ - **图标**:聚合根包 `icon.svg` 改用本组件由用户上传的哈士奇照片生成的图标(48px 居中裁切、64×64 圆角背景、内嵌 `data:image/png;base64,...`,7426 B),形成「用图标生成插件产出自身图标」的闭环;`package.json` 的 `icon` 与 `exports["./icon"]` 声明保持不变。
28
+ - **生效**:涉及 bundle 行与 `cordis.patch.yml`,需重新 `npm pack` 并在插件页更新安装(或重建 bundle 后刷新页面)才能看到新组件。
29
+
30
+ ## 1.3.1 — 2026-11-15
31
+
32
+ ### 子智能体管理:修复浮层「直接退出 / 清理无反应」
33
+
34
+ - **现象**(用户反馈):点击「清理全部空闲」没反应;点击「设置」「重命名标签」输入框浮层直接退出、没有任何反应。
35
+ - **根因 1 — React Portal 事件冒泡**:`packages/dsh-plugin-yanyan-subagent/lib/client.js` 的 `makeActionButton`(原 :951-979)把 `ReactDOM.createPortal(...)` 当作 `<button>` 的 children(`kids.push(...)`)。React 合成事件沿 fiber 树(非 DOM 树)冒泡:浮层内任意点击(按钮 / 输入框都是原生 `addEventListener`)从 portal-host 沿 fiber return 链上溯到 `<button>`,触发 `onClick` → `setOpen(v=>!v)` → 浮层被 toggle 卸载。纯 DOM 降级路径不受影响,故只在 slots + createPortal 路径出现。
36
+ - **修复 1**:`makeActionButton` 用 `React.Fragment` 让 `<button>` 与 portal 成为兄弟节点(`return React.createElement(React.Fragment, null, tabBtn, portal)`),portal 的 fiber 父级是 Fragment、不再经过 button;`PortalHost` 的 div 增加 `onClick/onMouseDown/onPointerDown/onTouchStart` 的 `stopPropagation` 双保险(只 stopPropagation、不 preventDefault,输入框聚焦不受影响)。DOM 位置不变,仍 createPortal 到 `document.body`。
37
+ - **根因 2 — 清理 0 命中**:`cleanAllIdle`(原 :638-663)复用 `idleFilterLocal`,要求 `activity==='inactive'` 且空闲 ≥ `shared.idleMinutes`(默认 30 分钟);但官方 catalog 行无 `idleSince/lastActiveAt` 字段,客户端 `firstSeen`(:348)又是打开浮层后才起步,几乎永远达不到阈值;且 `renderRow`(:897)用「非 running 即空闲」渲染、清理却要求 `==='inactive'`,无 activity 字段的行显示空闲却清不掉,对应截图「已清理 0 个空闲子智能体」。
38
+ - **修复 2**:新增纯函数 `manualCleanTargets(rows, options)`;手动「清理全部空闲」改为清掉所有 `activity !== 'running'`、非 `diagnostic`、非 root、有 id、不在隐藏清单(`isDeleted`)的条目,**不套 30 分钟阈值**,与 `renderRow`、host `/cleanup`(`isRunningSubagent` 用 `agents.get(v).status==='running'` 判定,即清理所有非 running,`lib/index.js:794-801`)完全对齐;0 命中给出 `cleanNoIdle` 提示。定时**自动清理** `autoCleanTick`(:667-690)仍保留 `idleFilterLocal` + 阈值语义(阈值只约束自动清理)。host 半区 `lib/index.js` 未改,避免破坏 `test/idle-filter.test.mjs` 与已发布语义。
39
+ - **字典**:`UI_zh` / `UI_en` 同步新增 `cleanNoIdle`(zh 为 key 源、en 键集完整契约)。
40
+ - **测试**:新增 `packages/dsh-plugin-yanyan-subagent/test/portal-interaction.test.mjs`,用「花括号配对提取函数 + `vm` 真实执行」验证 `manualCleanTargets` 筛选边界,用源码契约断言锁定 Fragment 兄弟结构、stopPropagation 双保险、`cleanAllIdle` 不再套阈值、zh/en 字典键集一致。
41
+ - **版本**:`dsh-plugin-yanyan-subagent` → `1.2.2`;聚合根包 → `1.3.1`(仅承载并发布子包修复)。`dsh-plugin-yanyan-update` `1.2.1` / `@linxin666/dsh-session-archive` `0.4.18` 不变;三份 README 的当前版本与组件版本列表、测试注释同步对齐。子包 host 半区 `lib/index.js` 的 `VERSION` 常量改为像根包一样从自身 `package.json` 动态读取,根治 bump 时硬编码常量与包声明失同步(仅元数据,路由逻辑零改动)。
42
+ - **生效**:bug 修复全部落在 client(浏览器)半区,host 路由逻辑未改,**修好交互无需重启宿主进程**;但插件 bundle 在启动 / 安装时加载,需重新 `npm pack` 并在插件页更新安装(或 `pnpm run dev:web` 重建 bundle 后刷新页面)才能在 Web GUI 看到新交互。若顺带更新了子包 host 的 `VERSION` 常量,重载插件 / 重启宿主后 `/health` 才会报 `1.2.2`,未重启时仍报旧值属正常、不影响功能。
43
+
44
+ ## 1.3.0 — 2026-11-15
45
+
46
+ ### 各组件显示自己的独立版本
47
+
48
+ - **需求**:插件详情页「包含的组件」列表里,每个组件都应有自己独立的版本并显示出来(此前只有整包一个版本号)。
49
+ - **根因 / 约束**:官方 `core/protocol.ts:15-24` 的 `InstalledPluginChild` 只有 `id/name/enabled/locked`,**没有 `version` 字段**;`host/state.ts:150-157` 用 loader 行构造 children 时也不带版本,因此 DSH 设置页自身不会、也无法渲染组件版本。组件显示名来自 `cordis.patch.yml` 的 loader 行,且每行 `name` 必须恰好等于包名(否则 `duplicate factory registration`),不能把版本号塞进 name。
50
+ - **数据来源(单一真相)**:各组件本就有独立的 `package.json`(聚合根包与两个自写子包、外加复用的 `@linxin666/dsh-session-archive`)。根 `lib/index.js` 新增纯函数 `collectVersions(rootDir)`,动态读取每个组件**自身的** `package.json` 版本;外部包优先用 `createRequire` 解析实际安装包,失败再定位 `node_modules` 路径,最终回退根 `dependencies` 的声明范围(去掉 `^`)。`VERSION` 常量也改为从根 `package.json` 动态读取,消除把版本号硬编码成静态常量、与包声明容易失同步的旧问题。
51
+ - **Host 半区**:新增同源回环路由 `/__dsh-plugin-yanyan-toolkit/components`,与 `/health` 共用准入 fence(`ctx.connection.requestRejection(req)===undefined`)、方法白名单与 `ctx.effect` 多路由注册骨架(复用 `packages/dsh-plugin-yanyan-update/lib/index.js:897-924`),返回 `{ok:true, components:{包名:版本}}`。
52
+ - **Client 半区**:根 `lib/client.js` 从空壳改为版本标签注入器,照抄现成轮子 `packages/dsh-plugin-yanyan-update/lib/client.js:354-392` 的「`MutationObserver(document.body)` + 16ms 节流 + 单个 `ctx.effect` 统一清理」骨架。按 `section[data-plugin-rows] li[data-plugin-row]` 与行内 `<code>` 的**真实包名**(CSS Modules 类名是 hash,不可依赖)匹配版本表,把带 `data-yanyan-component-version` 标记的 `v<版本>` 小标签追加到中文标题元素(支持 `button.rowOpen` configure 形态),用主题变量 `var(--dsw-alias-*)` 带回退色、自持 `<style>`,版本值变化才更新、靠 data 属性去重;只认本包 4 个组件包名,不误伤其它插件详情页;React 重渲染清掉标签后由 observer 自动重挂;`inject` 保持 `[]`,不依赖任何服务。
53
+ - **测试**:新增 `test/component-versions.test.mjs`,断言 `collectVersions()` 覆盖全部 4 个组件包名、值等于各 `package.json` 的 version(外部包回退声明)、`VERSION` 等于根包版本,以及 host / client 半区的关键契约(components 路由、fence、DOM 锚点、MARK、observer/effect 清理)。
54
+ - **版本**:聚合包 bump 到 `1.3.0`;三个功能子包由各自分支独立维护版本、不随聚合包强制对齐,正好体现各自独立版本:toolkit `1.3.0` / update `1.2.1` / subagent `1.2.1` / session-archive `0.4.18`。
55
+
56
+ ## 1.2.1 — 2026-11-15
57
+
58
+ ### 子智能体管理:彻底修复「一直删不掉」的根因(内存投影根治)
59
+
60
+ - **根因**:v1.2.0 的「日志原位退休」已把磁盘 `session.v4.jsonl.zstd` 里的 `subagent/catalog` 行改写为 `subagent-delete/retired`,projcache 也已清;但官方「N 个子智能体」浮层 `SubagentHeaderLineage` 只读 client 内存 store `ctx.sessions.list.getSnapshot().projectionsBySession[*].values.subagentCatalog`,宿主对已加载的 live 父会话复用内存 fold state,改磁盘 / 删 projcache 都不触发重算,残留行仍显示且点进去报 `subagent is unavailable (subagent/not-found)`。插件面板因 `localStorage` 隐藏清单过滤显示「0 个」,二者数据源相同、差别只在过滤。
61
+ - **修复(6 层删除的第 6 层)**:① `lib/log-rewrite.js` 新增 `collectRetiredIds(dshHome)`,扫全库 zstd 日志收集 `subagent-delete/retired` 的 `data.childId`;② `lib/index.js` 新增 `collectHiddenIds`(`legacyHiddenIds ∪ collectRetiredIds` 去重归一),`/config` 下发 `hiddenIds`;③ `lib/legacy-hidden-ids.json` 扩到 24 个 id 作为「旧 host 进程无需重启即生效」的兼容桥(旧 `legacyHiddenIds()` 每次请求读盘);④ `lib/client.js` 新增 `patchProjections(ctx)`,订阅 `sessions.list`、用宿主自身 `list.set()` 把隐藏 childId 从所有 `projectionsBySession[*].values.subagentCatalog` 持续剔除(幂等收敛、不自激),并在 `loadSettings`、单删成功后、首屏各调用一次。
62
+ - **验证**:新增 `test/hidden-ids.test.mjs`(4 项)与 `test/projection-patch.test.mjs`(4 项,真实 vm 沙箱执行 `patchProjections`、回归图1 的 7 个残留 id),全树 `node --test` **68/68 通过**;底层 `createSnapshotStore` 确证 `list.set()` 为宿主合法写入口;端到端实测 `/config` 下发 24 个隐藏 id、client bundle 经 HMR 已含 `patchProjections`;运行中的 GUI 刷新页面即可看到官方浮层残留行消失,重启后 host 新代码 `hiddenIds` 动态全集接管。
63
+ - **组件信息同步**:子包与根包版本一并升到 `1.2.1`,对齐 `lib/index.js` 的 `VERSION`(根包此前误留 `1.0.0`)、更新中英 `locale/*.json` 描述与双语 README / CHANGELOG。
64
+
65
+ ### 插件更新:修复「更新后插件默认关闭、不自动启动」
66
+
67
+ - **根因**:面板显示「更新完成,已即时生效。」说明走的是官方 `pluginManager.updateBundle` 首选分支。已查证官方实现(`packages/boot/plugin-manager/src/index.ts`):`updateBundle` 在 HMR 下先 `removeBundle`(内部 `selectBundle(name, false)`)再 `installBundle(spec, { enabled })`,其 `enabled` 取自 `listBundles()` 的 bundle 选择态,而 `installBundle` 只把 bundle 选择态放回(`selectBundle`),并不保证「已安装插件」页开关所依据的 entry 级 enablement(`cordis.patch.yml` 的 loader 行 override,见 `host/state.ts:145`:`rowEnabled.get(id) ?? bundleDefaults.get(id) ?? true`)。remove→install 过程把 entry 级启用态丢失,故更新后开关变灰、插件不会自动启动。`UpdateBundleOptions` 只有 `registry`/`spec`/`requestId`(strict codec,无 `enabled`),本包无法靠传参修复。
68
+ - **修复**(借鉴官方现成轮子 `host/gateway.ts:1631-1639 restoreBundleSelection` 的「方法可选 + 吞异常 + 不影响结论」):新增 `readEnablement(manager, id)` 在更新前容错捕获启用态(`listPlugins` 的 entry 级 `enabled` 优先,并带出 `entryId`;无则回退 `listBundles` 的 bundle 选择态),仅明确 `enabled === false`(用户主动关闭)时尊重、跳过恢复;新增 `restoreEnablement(manager, id, enablement)` 在更新成功后 best-effort 放回——优先 `setPluginEnabled(entryId, true)`(直接写 patch 行 override,对应开关本体),缺失或抛错回退 `setBundleEnabled(id, true)`,所有方法可选、异常吞掉、绝不改动 `ok/updated/message`。首选 `updateBundle` 与降级两阶段/覆盖安装的全部成功出口均恢复,失败出口不触碰启用态。
69
+ - 新增 8 个单测(共 22 个)覆盖:优先 `setPluginEnabled(entryId)`、回退 `setBundleEnabled`、`wasDisabled` 不恢复、恢复抛错不影响成功结论、旧 mock 无 `list*`/`set*` 方法仍通过、降级成功也恢复。根包 12 + 子包 22 全部测试通过。
70
+ - 版本 `dsh-plugin-yanyan-update` 与聚合包均 bump 到 1.2.1,使「检查更新」能识别并拉取本修复。
71
+
5
72
  ## 1.2.0 — 2026-11-14
6
73
 
7
74
  首个公开发布版本,聚合「插件更新」「会话管理」「子智能体管理」三个可独立开关的插件。
package/README.en.md ADDED
@@ -0,0 +1,82 @@
1
+ # Yanyan Toolkit
2
+
3
+ English | [中文](README.md)
4
+
5
+ > **Current version: v1.4.1** | Requires DSH **0.2.2-rc.2**
6
+
7
+ > Repository: [GitHub](https://github.com/sweet560/dsh-plugin-yanyan-toolkit)
8
+
9
+ An umbrella plugin bundle that installs four independently toggleable plugins at once. It follows the "reuse what already exists, do not reinvent the wheel" rule: session management is the existing community package, and the three custom plugins reuse verified official/community injection points wherever possible.
10
+
11
+ ## Included plugins
12
+
13
+ | Plugin | Row id | Purpose |
14
+ | --- | --- | --- |
15
+ | Plugin Update | `dsh-plugin-yanyan-update` | Adds a "Check updates" button beside the "Installed plugins" heading on the Plugins page. Clicking it opens a small dropdown listing updatable plugins (current version -> latest version), each with its own "Update" button. Covers npm registry sources and local `link:`/`file:` (git repository) sources. |
16
+ | Session Management | `session-archive` | Auto-archive sessions, auto-delete archived sessions (both off by default, configurable to 7/15/30/custom days), single and batch delete. **Directly reuses** `@linxin666/dsh-session-archive@0.4.18`, whose settings page and backend already cover the full requirement. |
17
+ | Subagent Management | `dsh-plugin-yanyan-subagent` | Adds a renameable "Agent management" tab to the session header; its popover lists all subagents of the current session and supports **permanent per-row delete** (log root-cause removal + in-memory projection fix) and a bottom "Clean all idle" action, plus idle-time auto-cleanup (off by default, configurable in minutes). |
18
+ | Icon Generator | `dsh-plugin-yanyan-icon` | Center-crops an uploaded image inside a rounded frame and generates an `icon.svg` fitted for the plugin list artwork slot (30px list row / 36px detail card), with dual-size preview and one-click download. |
19
+
20
+ Each plugin has its own loader row and can be toggled independently on the Plugins page.
21
+
22
+ ## Independent component versions
23
+
24
+ Every component in the bundle keeps its own independent `package.json` version (the umbrella root and the four plugins can be released independently, no forced lockstep): currently `dsh-plugin-yanyan-toolkit` 1.4.1, `dsh-plugin-yanyan-update` 1.2.1, `dsh-plugin-yanyan-subagent` 1.2.2, `dsh-plugin-yanyan-icon` 1.0.1, `@linxin666/dsh-session-archive` 0.4.18.
25
+
26
+ On the plugin detail page's "Included components" list, a small `v<version>` badge appears next to each component title. The official `InstalledPluginChild` protocol only has `id/name/enabled/locked` and no `version`, so DSH itself never renders per-component versions. The host half adds a `/__dsh-plugin-yanyan-toolkit/components` route that **dynamically reads each component's own `package.json`** (never hardcoded, so versions cannot drift), and the client half reuses the `MutationObserver` + `ctx.effect` injection skeleton from the Plugin Update package: it matches `[data-plugin-rows] li[data-plugin-row]` rows by the real package name in the inline `<code>` and injects the badge. It never mutates React-managed DOM, never touches other plugins' detail pages, and re-attaches automatically after a re-render.
27
+
28
+ ## Design decisions
29
+
30
+ - **Session management reused without changes**: `@linxin666/dsh-session-archive` already implements auto-archive by `updatedAt`, auto-delete by the archive ledger's `archivedAt`, single/batch physical delete, any day count from 1 to 3650, Host-side scheduling and a settings UI. Its day input accepts any integer from 1 to 3650, which already includes 7/15/30/custom.
31
+ - **Plugin update reuses the injection skeleton and fills the gap**: the official `@linxin666/dsh-client-ui-plugin-manager` provides the DOM seat `[data-plugin-panel] [data-plugin-group="bundles"]`, the check-updates button, the dropdown and the dual-channel RPC contract. Its checker, however, only covers npm registry sources (`source.kind === 'npm'`) and skips local `link:`/`file:` directories and GitHub sources. This plugin adds git local source version comparison and `git merge --ff-only` updates in its own Host half, and adds a per-row update button to the dropdown.
32
+ - **Subagent "delete" means physical deletion + log root-cause removal (irreversible)**: the old version only called `archiveSession`, which could neither delete mnemon virtual delegations (reports `session/not-found`) nor remove the append-only parent `subagentCatalog` reference, so the row stayed in the list. The host `/purge` route now reuses the existing `/api/dsh-session-archive/delete` pipeline for real sessions and falls back to cleaning session directories, projcache and catalog references, workspace accounting, archive ledger and archive markers; virtual ids go straight to the fallback. Since v1.2.0 it also borrows the in-place "retirement" idea from `dsh-plugin-subagent-clean` (MIT) to rewrite the parent log's `subagent/catalog` event into `subagent-delete/retired` (seq/time preserved, ignorable), erasing the row at its append-only source so the host cannot rebuild it. A "Clean residual rows" button (`/scrub`), a single whole-log rewrite for one-click cleanup (`/cleanup`), `ctx.sessions.refresh()` so sidebar residual rows disappear in sync, a `localStorage['yanyan-subagent.deletedIds']` hidden-list fallback and three optional model tools (`yanyan_list_subagents` / `yanyan_delete_subagent` / `yanyan_cleanup_subagents`) complete the integration. Since v1.2.1 the real root-cause layer is covered: the official "N subagents" popover reads only the client in-memory projection `projectionsBySession[*].values.subagentCatalog`, and the host reuses the in-memory fold state for a loaded live parent session, so rewriting the on-disk log / deleting projcache never recomputes it — already-retired residual rows keep showing and clicking one reports `subagent/not-found`. The host now scans the whole library for retired log rows and ships `hiddenIds` via `/config`; the client `patchProjections` subscribes to `sessions.list` and uses the host's own `list.set()` to continuously remove hidden childIds from the in-memory projection (idempotent), so the popover count drops to zero and hides itself.
33
+ - **An X at the end of each row in the official subagent list (image 3)**: the official popover rows have no row-level Slot and no `data-session-id`-style DOM anchor, so a stable injection cannot add a per-row delete there. This plugin implements per-row delete inside its own "Agent management" popover (data from `listDescendants`, with explicit ids) and contributes a "Clean all idle" button through `conversation.session.header.actions.subagent.footer`. The in-list X is only an optional, high-risk DOM-injection enhancement and is disabled by default.
34
+
35
+ ## Installation
36
+
37
+ This package is an npm bundle; its `dsh.bundle.patch` / `cordis.patch.yml` inserts five loader rows at once. Pick one of three ways:
38
+
39
+ From npm (recommended, requires Node `^22.19.0 || >=24.0.0`):
40
+
41
+ ```
42
+ dsh plugin add dsh-plugin-yanyan-toolkit
43
+ ```
44
+
45
+ From a local directory (for development):
46
+
47
+ ```
48
+ dsh plugin add file:<this package directory>
49
+ ```
50
+
51
+ Or choose the local directory / enter the npm package name from the Plugins page's "Add plugin" action.
52
+
53
+ ## Distribution and automation
54
+
55
+ - npm: `.github/workflows/publish.yml` publishes with OIDC trusted publishing (`npm publish --provenance --access public`) on push to `main`, `v*` tags, a published Release, or manual dispatch. It runs `npm test` first and polls to skip already-published versions. A one-time trusted publisher configuration for this repo/workflow on npmjs.com is required.
56
+ - Gitee: `.github/workflows/sync-gitee.yml` mirrors `main` and all tags to Gitee on push to `main` or manual dispatch. It requires the repository Secret `GITEE_SSH_PRIVATE_KEY` (a passphrase-less private key registered on the Gitee account).
57
+ - GitHub Releases: each version ships a `dsh-plugin-yanyan-toolkit-<version>.tgz` offline tarball, installable via `npm install <tgz>` and then as a local package.
58
+
59
+ ## Activation
60
+
61
+ Changes involve bundle rows and `cordis.patch.yml`, so **the user must restart the DSH service for them to take effect** (this session did not restart the running DSH service on its own). After restarting, refresh the page to see:
62
+ - the "Check updates" button beside the "Installed plugins" heading;
63
+ - a `v<version>` badge next to each component title in the "Included components" list;
64
+ - the new Session Management section in Settings (provided by the reused package);
65
+ - the new "Agent management" tab in the session header;
66
+ - the "Icon Generator" button beside the "Installed plugins" heading, with dual-size preview and one-click `icon.svg` download.
67
+
68
+ ## Layout
69
+
70
+ ```
71
+ dsh-plugin-yanyan-toolkit/ umbrella root (no-op halves, only patch and dependencies)
72
+ cordis.patch.yml 5 insert rows: self + 4 plugins
73
+ lib/index.js, lib/client.js minimal host / client halves
74
+ locale/{zh,en}.json Plugins page display metadata
75
+ packages/
76
+ dsh-plugin-yanyan-update/ Plugin Update (custom)
77
+ dsh-plugin-yanyan-subagent/ Subagent Management (custom)
78
+ dsh-plugin-yanyan-icon/ Icon Generator (custom)
79
+ (Session Management has no subdirectory; it depends on the npm package directly)
80
+ ```
81
+
82
+ The four real plugins are listed in the root package's `dependencies`; pnpm's hoisted layout lifts them and the npm package into the profile root `node_modules`, so all five loader row `name`s resolve.
package/README.i18n.yaml CHANGED
@@ -1,6 +1,7 @@
1
- # Bilingual-pair consistency record (docs/i18n.md): the git blob hash of each
2
- # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
- # after editing either side, bring the other along and re-record with:
4
- # node scripts/verify-docs.mjs --write <dir>
5
- README.md: 3157ffd6998b1d5cdd3c2faae7e19bc3787a37b6
6
- README.zh.md: 2f7ef3e8a66a4ffe8e40b21d5f4f4a879ce7f87e
1
+ # Bilingual-pair consistency record: the git blob hash of each side as of the
2
+ # last confirmed-consistent state. README.md (default, Chinese) and
3
+ # README.en.md (English) form the active pair; README.zh.md mirrors README.md.
4
+ # Re-record with: node scripts/verify-docs.mjs --write <dir>
5
+ README.md: e046d9ed15a5c6a3ade4c84b2f07b6f2d49e8eff
6
+ README.en.md: b975a95112fa62509001c2509b9fc57b98e428f0
7
+ README.zh.md: e046d9ed15a5c6a3ade4c84b2f07b6f2d49e8eff
package/README.md CHANGED
@@ -1,66 +1,82 @@
1
- # Yanyan Toolkit
1
+ # 研研工具包
2
2
 
3
- An umbrella plugin bundle that installs three independently toggleable plugins at once. It follows the "reuse what already exists, do not reinvent the wheel" rule: session management is the existing community package, and the two custom plugins reuse verified official/community injection points wherever possible.
3
+ [English](README.en.md) | 中文
4
4
 
5
- ## Included plugins
5
+ > **当前版本:v1.4.1** |适配 DSH **0.2.2-rc.2**
6
6
 
7
- | Plugin | Row id | Purpose |
7
+ > 仓库:[GitHub](https://github.com/sweet560/dsh-plugin-yanyan-toolkit)
8
+
9
+ 一个聚合插件包,安装后同时获得四个可独立开关的插件。遵循「有现成的就直接借鉴、不重复造轮子」:会话管理直接复用社区现成包,另外三个自写插件也尽量照抄官方与社区已验证的注入点。
10
+
11
+ ## 包含插件
12
+
13
+ | 插件 | 行 id | 作用 |
8
14
  | --- | --- | --- |
9
- | Plugin Update | `dsh-plugin-yanyan-update` | Adds a "Check updates" button beside the "Installed plugins" heading on the Plugins page. Clicking it opens a small dropdown listing updatable plugins (current version -> latest version), each with its own "Update" button. Covers npm registry sources and local `link:`/`file:` (git repository) sources. |
10
- | Session Management | `session-archive` | Auto-archive sessions, auto-delete archived sessions (both off by default, configurable to 7/15/30/custom days), single and batch delete. **Directly reuses** `@linxin666/dsh-session-archive@0.4.18`, whose settings page and backend already cover the full requirement. |
11
- | Subagent Management | `dsh-plugin-yanyan-subagent` | Adds a renameable "Agent management" tab to the session header; its popover lists all subagents of the current session, supports per-row delete and a bottom "Clean all idle" action, plus idle-time auto-cleanup (off by default, configurable in minutes). |
15
+ | 插件更新 | `dsh-plugin-yanyan-update` | 在插件页「已安装插件」标题行右侧提供「检查更新」按钮;点击后展开下拉小面板,逐行列出可更新插件(当前版本 → 最新版本)并带独立「更新」按钮。覆盖 npm registry 源与本地 `link:`/`file:`(git 仓库)源。 |
16
+ | 会话管理 | `session-archive` | 自动归档会话、自动删除已归档会话(默认均关闭,可设 7/15/30/自定义天数)、单条与批量删除。**直接复用** `@linxin666/dsh-session-archive@0.4.18`,其设置页与后端已完整覆盖需求。 |
17
+ | 子智能体管理 | `dsh-plugin-yanyan-subagent` | 会话顶部新增可改名的「智能体管理」标签,浮层列出当前会话所有子智能体、支持行尾逐个**彻底删除**(日志根除 + 内存投影根治)与底部「清理全部空闲」;提供按空闲时间自动清理(默认关闭、可设分钟数)。 |
18
+ | 图标生成 | `dsh-plugin-yanyan-icon` | 根据上传的图片居中裁切、套上圆角背景,生成适配插件列表图标位(列表行 30px / 详情卡片 36px)的 `icon.svg`;提供双尺寸预览与一键下载。 |
19
+
20
+ 四个插件各有独立 loader 行,可在插件页分别启停。
21
+
22
+ ## 组件独立版本与显示
23
+
24
+ 聚合包里的每个组件都保留自己独立的 `package.json` 版本(聚合根包与四个功能插件可各自发版,互不强求一致):当前为 `dsh-plugin-yanyan-toolkit` 1.4.1、`dsh-plugin-yanyan-update` 1.2.1、`dsh-plugin-yanyan-subagent` 1.2.2、`dsh-plugin-yanyan-icon` 1.0.1、`@linxin666/dsh-session-archive` 0.4.18。
12
25
 
13
- Each plugin has its own loader row and can be toggled independently on the Plugins page.
26
+ 在插件详情页「包含的组件」列表里,每个组件中文标题右侧会显示一个 `v<版本>` 小标签。官方的 `InstalledPluginChild` 协议只有 `id/name/enabled/locked`,不含 `version`,DSH 自身不会渲染组件版本;本包由 host 半区新增 `/__dsh-plugin-yanyan-toolkit/components` 路由**动态读取各组件自己的 `package.json`**(绝不写死,避免失同步),client 半区照抄 `插件更新` 包已有的 `MutationObserver` + `ctx.effect` 注入骨架,按 `[data-plugin-rows] li[data-plugin-row]` 与行内 `<code>` 里的真实包名匹配后注入版本标签,不改 React 管理的 DOM、不误伤其它插件详情页,页面重渲染后自动重挂。
14
27
 
15
- ## Design decisions
28
+ ## 设计取舍
16
29
 
17
- - **Session management reused without changes**: `@linxin666/dsh-session-archive` already implements auto-archive by `updatedAt`, auto-delete by the archive ledger's `archivedAt`, single/batch physical delete, any day count from 1 to 3650, Host-side scheduling and a settings UI. Its day input accepts any integer from 1 to 3650, which already includes 7/15/30/custom.
18
- - **Plugin update reuses the injection skeleton and fills the gap**: the official `@linxin666/dsh-client-ui-plugin-manager` provides the DOM seat `[data-plugin-panel] [data-plugin-group="bundles"]`, the check-updates button, the dropdown and the dual-channel RPC contract. Its checker, however, only covers npm registry sources (`source.kind === 'npm'`) and skips local `link:`/`file:` directories and GitHub sources. This plugin adds git local source version comparison and `git merge --ff-only` updates in its own Host half, and adds a per-row update button to the dropdown.
19
- - **Subagent "delete" means physical deletion + log root-cause removal (irreversible)**: the old version only called `archiveSession`, which could neither delete mnemon virtual delegations (reports `session/not-found`) nor remove the append-only parent `subagentCatalog` reference, so the row stayed in the list. The host `/purge` route now reuses the existing `/api/dsh-session-archive/delete` pipeline for real sessions and falls back to cleaning session directories, projcache and catalog references, workspace accounting, archive ledger and archive markers; virtual ids go straight to the fallback. Since v1.2.0 it also borrows the in-place "retirement" idea from `dsh-plugin-subagent-clean` (MIT) to rewrite the parent log's `subagent/catalog` event into `subagent-delete/retired` (seq/time preserved, ignorable), erasing the row at its append-only source so the host cannot rebuild it. A "Clean residual rows" button (`/scrub`), a single whole-log rewrite for one-click cleanup (`/cleanup`), `ctx.sessions.refresh()` so sidebar residual rows disappear in sync, a `localStorage['yanyan-subagent.deletedIds']` hidden-list fallback and three optional model tools (`yanyan_list_subagents` / `yanyan_delete_subagent` / `yanyan_cleanup_subagents`) complete the integration.
20
- - **An X at the end of each row in the official subagent list (image 3)**: the official popover rows have no row-level Slot and no `data-session-id`-style DOM anchor, so a stable injection cannot add a per-row delete there. This plugin implements per-row delete inside its own "Agent management" popover (data from `listDescendants`, with explicit ids) and contributes a "Clean all idle" button through `conversation.session.header.actions.subagent.footer`. The in-list X is only an optional, high-risk DOM-injection enhancement and is disabled by default.
30
+ - **会话管理零改动复用**:`@linxin666/dsh-session-archive` 已实现按 `updatedAt` 自动归档、按归档台账 `archivedAt` 自动删除、单条/批量物理删除、1~3650 任意天数、Host 端定时调度与设置页 UI。其天数输入框接受任意 1~3650 的整数,已包含 7/15/30/自定义,无需改造。
31
+ - **插件更新复用注入骨架,补齐现成轮子缺失的部分**:官方 `@linxin666/dsh-client-ui-plugin-manager` 的 DOM 座位 `[data-plugin-panel] [data-plugin-group="bundles"]`、「检查更新」按钮、下拉面板与双通道 RPC 契约可直接借鉴;但它的检查逻辑只覆盖 npm registry 源(`source.kind === 'npm'`),对本地 `link:`/`file:` 目录与 GitHub 源一律跳过。本插件在自写 Host 半区里补上 git 本地源的版本比对与 `git merge --ff-only` 更新,并给下拉列表每一行加独立更新按钮。
32
+ - **子智能体「删除」= 物理删除 + 日志根除(不可逆)**:旧版只调用 `archiveSession` 归档,既删不掉 mnemon 虚拟委派(会报 `session/not-found`),也移除不了父会话 `subagentCatalog` 的 append-only 引用,导致列表里仍显示该条目。现改为 host `/purge` 物理删除:真实会话优先复用现成 `/api/dsh-session-archive/delete` 管线,再兜底清会话目录、projcache 与 catalog 引用、workspace 记账、归档台账与归档标记;虚拟 id 直接走兜底。v1.2.0 起进一步借鉴 `dsh-plugin-subagent-clean`(MIT)的「原位退休」把父会话日志 `session.v4.jsonl.zstd` 里的 `subagent/catalog` 事件改写为 `subagent-delete/retired`(保留 seq/time、ignorable),从 append-only 源头根除目录行,宿主不再重建;配合「清理残留行」按钮(`/scrub`)、一键清理只改写一次日志(`/cleanup`)、`ctx.sessions.refresh()` 让侧边栏残留行同步消失,以及 `localStorage['yanyan-subagent.deletedIds']` 隐藏清单兜底。另注册三个可选模型工具(`yanyan_list_subagents` / `yanyan_delete_subagent` / `yanyan_cleanup_subagents`),让 Agent 一句话即可清理空闲 Mnemon review 子代理。v1.2.1 起补上真正的根治层:官方「N 个子智能体」浮层只读 client 内存投影 `projectionsBySession[*].values.subagentCatalog`,宿主对已加载的 live 父会话复用内存 fold state,改磁盘日志 / 删 projcache 都不会让它重算,导致磁盘已退休的残留行仍显示、点进去报 `subagent/not-found`。现由 host 扫全库退休日志行经 `/config` 下发 `hiddenIds`,client `patchProjections` 订阅 `sessions.list` 用 `list.set()` 持续从内存投影剔除隐藏 childId(幂等收敛),官方浮层计数归零后自动隐藏。
33
+ - **图 3 官方子智能体列表行尾加 X**:官方浮层行没有行级 Slot,也没有 `data-session-id` 之类的 DOM 锚点,无法通过稳定注入实现行尾删除。本插件把「逐个删除」做在自建的「智能体管理」标签浮层里(数据来自 `listDescendants`,id 明确),并在 `conversation.session.header.actions.subagent.footer` 提供「清理全部空闲」按钮。图 3 行内 X 仅作为可选的高风险 DOM 注入增强,默认不启用。
21
34
 
22
- ## Installation
35
+ ## 安装
23
36
 
24
- This package is an npm bundle; its `dsh.bundle.patch` / `cordis.patch.yml` inserts four loader rows at once. Pick one of three ways:
37
+ 本包是一个 npm 包,通过 `dsh.bundle.patch` 的 `cordis.patch.yml` 一次插入五条 loader 行。三种方式任选其一:
25
38
 
26
- From npm (recommended, requires Node `^22.19.0 || >=24.0.0`):
39
+ 从 npm 安装(推荐,需 Node `^22.19.0 || >=24.0.0`):
27
40
 
28
41
  ```
29
42
  dsh plugin add dsh-plugin-yanyan-toolkit
30
43
  ```
31
44
 
32
- From a local directory (for development):
45
+ 本地目录安装(开发调试):
33
46
 
34
47
  ```
35
- dsh plugin add file:<this package directory>
48
+ dsh plugin add file:<本包目录>
36
49
  ```
37
50
 
38
- Or choose the local directory / enter the npm package name from the Plugins page's "Add plugin" action.
51
+ 或在插件页「添加插件」里选择本地目录 / 输入 npm 包名安装。
39
52
 
40
- ## Distribution and automation
53
+ ## 分发与自动化
41
54
 
42
- - npm: `.github/workflows/publish.yml` publishes with OIDC trusted publishing (`npm publish --provenance --access public`) on push to `main`, `v*` tags, a published Release, or manual dispatch. It runs `npm test` first and polls to skip already-published versions. A one-time trusted publisher configuration for this repo/workflow on npmjs.com is required.
43
- - Gitee: `.github/workflows/sync-gitee.yml` mirrors `main` and all tags to Gitee on push to `main` or manual dispatch. It requires the repository Secret `GITEE_SSH_PRIVATE_KEY` (a passphrase-less private key registered on the Gitee account).
44
- - GitHub Releases: each version ships a `dsh-plugin-yanyan-toolkit-<version>.tgz` offline tarball, installable via `npm install <tgz>` and then as a local package.
55
+ - npm:`.github/workflows/publish.yml` 在 push `main`、`v*` 标签、Release 发布或手动触发时用 OIDC 可信发布执行 `npm publish --provenance --access public`;发布前先跑 `npm test`,并轮询跳过已发布版本。首次使用需在 npmjs.com 为本仓库与该工作流配置 trusted publisher。
56
+ - Gitee:`.github/workflows/sync-gitee.yml` 在 push `main` 或手动触发时把 main 与全部标签镜像到 Gitee;需要在仓库 Secret 配置 `GITEE_SSH_PRIVATE_KEY`(一把已注册在 Gitee 账号下的无口令私钥)。
57
+ - GitHub Release:每个版本附带 `dsh-plugin-yanyan-toolkit-<version>.tgz` 离线分发包,可直接 `npm install <tgz>` 后按本地包方式安装。
45
58
 
46
- ## Activation
59
+ ## 生效说明
47
60
 
48
- Changes involve bundle rows and `cordis.patch.yml`, so **the user must restart the DSH service for them to take effect** (this session did not restart the running DSH service on its own). After restarting, refresh the page to see:
49
- - the "Check updates" button beside the "Installed plugins" heading;
50
- - the new Session Management section in Settings (provided by the reused package);
51
- - the new "Agent management" tab in the session header.
61
+ 改动涉及 bundle 行与 `cordis.patch.yml`,**需要用户重启 DSH 服务后生效**(本会话未自行重启正在运行的 DSH 服务)。重启后刷新页面即可看到:
62
+ - 插件页「已安装插件」标题行右侧的「检查更新」按钮;
63
+ - 插件详情页「包含的组件」列表里每个组件标题右侧的 `v<版本>` 标签;
64
+ - 设置页新增的「会话管理」分区(由复用的包提供);
65
+ - 会话顶部新增的「智能体管理」标签;
66
+ - 插件页「已安装插件」标题行右侧的「图标生成」按钮,上传图片后双尺寸预览并下载 `icon.svg`。
52
67
 
53
- ## Layout
68
+ ## 目录结构
54
69
 
55
70
  ```
56
- dsh-plugin-yanyan-toolkit/ umbrella root (no-op halves, only patch and dependencies)
57
- cordis.patch.yml 4 insert rows: self + 3 plugins
58
- lib/index.js, lib/client.js minimal host / client halves
59
- locale/{zh,en}.json Plugins page display metadata
71
+ dsh-plugin-yanyan-toolkit/ 聚合根包(自身行为空壳,只承载 patch 与依赖)
72
+ cordis.patch.yml 5 行 insert:自身 + 4 个插件
73
+ lib/index.js, lib/client.js 极简 host / client 半区
74
+ locale/{zh,en}.json 插件页展示元数据
60
75
  packages/
61
- dsh-plugin-yanyan-update/ Plugin Update (custom)
62
- dsh-plugin-yanyan-subagent/ Subagent Management (custom)
63
- (Session Management has no subdirectory; it depends on the npm package directly)
76
+ dsh-plugin-yanyan-update/ 插件更新(自写)
77
+ dsh-plugin-yanyan-subagent/ 子智能体管理(自写)
78
+ dsh-plugin-yanyan-icon/ 图标生成(自写)
79
+ (会话管理无子目录,直接依赖 npm 包 @linxin666/dsh-session-archive)
64
80
  ```
65
81
 
66
- The three real plugins are listed in the root package's `dependencies`; pnpm's hoisted layout lifts them and the npm package into the profile root `node_modules`, so all four loader row `name`s resolve.
82
+ 四个真实插件列在根包 `dependencies` 里,pnpm 的 hoisted 布局会把它们与 npm 包一起提升到 profile 根 `node_modules`,五条 loader 行的 `name` 均可解析。
package/README.zh.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # 研研工具包
2
2
 
3
- 一个聚合插件包,安装后同时获得三个可独立开关的插件。遵循「有现成的就直接借鉴、不重复造轮子」:会话管理直接复用社区现成包,另外两个自写插件也尽量照抄官方与社区已验证的注入点。
3
+ [English](README.en.md) | 中文
4
+
5
+ > **当前版本:v1.4.1** |适配 DSH **0.2.2-rc.2**
6
+
7
+ > 仓库:[GitHub](https://github.com/sweet560/dsh-plugin-yanyan-toolkit)
8
+
9
+ 一个聚合插件包,安装后同时获得四个可独立开关的插件。遵循「有现成的就直接借鉴、不重复造轮子」:会话管理直接复用社区现成包,另外三个自写插件也尽量照抄官方与社区已验证的注入点。
4
10
 
5
11
  ## 包含插件
6
12
 
@@ -8,20 +14,27 @@
8
14
  | --- | --- | --- |
9
15
  | 插件更新 | `dsh-plugin-yanyan-update` | 在插件页「已安装插件」标题行右侧提供「检查更新」按钮;点击后展开下拉小面板,逐行列出可更新插件(当前版本 → 最新版本)并带独立「更新」按钮。覆盖 npm registry 源与本地 `link:`/`file:`(git 仓库)源。 |
10
16
  | 会话管理 | `session-archive` | 自动归档会话、自动删除已归档会话(默认均关闭,可设 7/15/30/自定义天数)、单条与批量删除。**直接复用** `@linxin666/dsh-session-archive@0.4.18`,其设置页与后端已完整覆盖需求。 |
11
- | 子智能体管理 | `dsh-plugin-yanyan-subagent` | 会话顶部新增可改名的「智能体管理」标签,浮层列出当前会话所有子智能体、支持行尾逐个删除与底部「清理全部空闲」;提供按空闲时间自动清理(默认关闭、可设分钟数)。 |
17
+ | 子智能体管理 | `dsh-plugin-yanyan-subagent` | 会话顶部新增可改名的「智能体管理」标签,浮层列出当前会话所有子智能体、支持行尾逐个**彻底删除**(日志根除 + 内存投影根治)与底部「清理全部空闲」;提供按空闲时间自动清理(默认关闭、可设分钟数)。 |
18
+ | 图标生成 | `dsh-plugin-yanyan-icon` | 根据上传的图片居中裁切、套上圆角背景,生成适配插件列表图标位(列表行 30px / 详情卡片 36px)的 `icon.svg`;提供双尺寸预览与一键下载。 |
19
+
20
+ 四个插件各有独立 loader 行,可在插件页分别启停。
21
+
22
+ ## 组件独立版本与显示
23
+
24
+ 聚合包里的每个组件都保留自己独立的 `package.json` 版本(聚合根包与四个功能插件可各自发版,互不强求一致):当前为 `dsh-plugin-yanyan-toolkit` 1.4.1、`dsh-plugin-yanyan-update` 1.2.1、`dsh-plugin-yanyan-subagent` 1.2.2、`dsh-plugin-yanyan-icon` 1.0.1、`@linxin666/dsh-session-archive` 0.4.18。
12
25
 
13
- 三个插件各有独立 loader 行,可在插件页分别启停。
26
+ 在插件详情页「包含的组件」列表里,每个组件中文标题右侧会显示一个 `v<版本>` 小标签。官方的 `InstalledPluginChild` 协议只有 `id/name/enabled/locked`,不含 `version`,DSH 自身不会渲染组件版本;本包由 host 半区新增 `/__dsh-plugin-yanyan-toolkit/components` 路由**动态读取各组件自己的 `package.json`**(绝不写死,避免失同步),client 半区照抄 `插件更新` 包已有的 `MutationObserver` + `ctx.effect` 注入骨架,按 `[data-plugin-rows] li[data-plugin-row]` 与行内 `<code>` 里的真实包名匹配后注入版本标签,不改 React 管理的 DOM、不误伤其它插件详情页,页面重渲染后自动重挂。
14
27
 
15
28
  ## 设计取舍
16
29
 
17
30
  - **会话管理零改动复用**:`@linxin666/dsh-session-archive` 已实现按 `updatedAt` 自动归档、按归档台账 `archivedAt` 自动删除、单条/批量物理删除、1~3650 任意天数、Host 端定时调度与设置页 UI。其天数输入框接受任意 1~3650 的整数,已包含 7/15/30/自定义,无需改造。
18
31
  - **插件更新复用注入骨架,补齐现成轮子缺失的部分**:官方 `@linxin666/dsh-client-ui-plugin-manager` 的 DOM 座位 `[data-plugin-panel] [data-plugin-group="bundles"]`、「检查更新」按钮、下拉面板与双通道 RPC 契约可直接借鉴;但它的检查逻辑只覆盖 npm registry 源(`source.kind === 'npm'`),对本地 `link:`/`file:` 目录与 GitHub 源一律跳过。本插件在自写 Host 半区里补上 git 本地源的版本比对与 `git merge --ff-only` 更新,并给下拉列表每一行加独立更新按钮。
19
- - **子智能体「删除」= 物理删除 + 日志根除(不可逆)**:旧版只调用 `archiveSession` 归档,既删不掉 mnemon 虚拟委派(会报 `session/not-found`),也移除不了父会话 `subagentCatalog` 的 append-only 引用,导致列表里仍显示该条目。现改为 host `/purge` 物理删除:真实会话优先复用现成 `/api/dsh-session-archive/delete` 管线,再兜底清会话目录、projcache 与 catalog 引用、workspace 记账、归档台账与归档标记;虚拟 id 直接走兜底。v1.2.0 起进一步借鉴 `dsh-plugin-subagent-clean`(MIT)的「原位退休」把父会话日志 `session.v4.jsonl.zstd` 里的 `subagent/catalog` 事件改写为 `subagent-delete/retired`(保留 seq/time、ignorable),从 append-only 源头根除目录行,宿主不再重建;配合「清理残留行」按钮(`/scrub`)、一键清理只改写一次日志(`/cleanup`)、`ctx.sessions.refresh()` 让侧边栏残留行同步消失,以及 `localStorage['yanyan-subagent.deletedIds']` 隐藏清单兜底。另注册三个可选模型工具(`yanyan_list_subagents` / `yanyan_delete_subagent` / `yanyan_cleanup_subagents`),让 Agent 一句话即可清理空闲 Mnemon review 子代理。
32
+ - **子智能体「删除」= 物理删除 + 日志根除(不可逆)**:旧版只调用 `archiveSession` 归档,既删不掉 mnemon 虚拟委派(会报 `session/not-found`),也移除不了父会话 `subagentCatalog` 的 append-only 引用,导致列表里仍显示该条目。现改为 host `/purge` 物理删除:真实会话优先复用现成 `/api/dsh-session-archive/delete` 管线,再兜底清会话目录、projcache 与 catalog 引用、workspace 记账、归档台账与归档标记;虚拟 id 直接走兜底。v1.2.0 起进一步借鉴 `dsh-plugin-subagent-clean`(MIT)的「原位退休」把父会话日志 `session.v4.jsonl.zstd` 里的 `subagent/catalog` 事件改写为 `subagent-delete/retired`(保留 seq/time、ignorable),从 append-only 源头根除目录行,宿主不再重建;配合「清理残留行」按钮(`/scrub`)、一键清理只改写一次日志(`/cleanup`)、`ctx.sessions.refresh()` 让侧边栏残留行同步消失,以及 `localStorage['yanyan-subagent.deletedIds']` 隐藏清单兜底。另注册三个可选模型工具(`yanyan_list_subagents` / `yanyan_delete_subagent` / `yanyan_cleanup_subagents`),让 Agent 一句话即可清理空闲 Mnemon review 子代理。v1.2.1 起补上真正的根治层:官方「N 个子智能体」浮层只读 client 内存投影 `projectionsBySession[*].values.subagentCatalog`,宿主对已加载的 live 父会话复用内存 fold state,改磁盘日志 / 删 projcache 都不会让它重算,导致磁盘已退休的残留行仍显示、点进去报 `subagent/not-found`。现由 host 扫全库退休日志行经 `/config` 下发 `hiddenIds`,client `patchProjections` 订阅 `sessions.list` 用 `list.set()` 持续从内存投影剔除隐藏 childId(幂等收敛),官方浮层计数归零后自动隐藏。
20
33
  - **图 3 官方子智能体列表行尾加 X**:官方浮层行没有行级 Slot,也没有 `data-session-id` 之类的 DOM 锚点,无法通过稳定注入实现行尾删除。本插件把「逐个删除」做在自建的「智能体管理」标签浮层里(数据来自 `listDescendants`,id 明确),并在 `conversation.session.header.actions.subagent.footer` 提供「清理全部空闲」按钮。图 3 行内 X 仅作为可选的高风险 DOM 注入增强,默认不启用。
21
34
 
22
35
  ## 安装
23
36
 
24
- 本包是一个 npm 包,通过 `dsh.bundle.patch` 的 `cordis.patch.yml` 一次插入四条 loader 行。三种方式任选其一:
37
+ 本包是一个 npm 包,通过 `dsh.bundle.patch` 的 `cordis.patch.yml` 一次插入五条 loader 行。三种方式任选其一:
25
38
 
26
39
  从 npm 安装(推荐,需 Node `^22.19.0 || >=24.0.0`):
27
40
 
@@ -47,20 +60,23 @@ dsh plugin add file:<本包目录>
47
60
 
48
61
  改动涉及 bundle 行与 `cordis.patch.yml`,**需要用户重启 DSH 服务后生效**(本会话未自行重启正在运行的 DSH 服务)。重启后刷新页面即可看到:
49
62
  - 插件页「已安装插件」标题行右侧的「检查更新」按钮;
63
+ - 插件详情页「包含的组件」列表里每个组件标题右侧的 `v<版本>` 标签;
50
64
  - 设置页新增的「会话管理」分区(由复用的包提供);
51
- - 会话顶部新增的「智能体管理」标签。
65
+ - 会话顶部新增的「智能体管理」标签;
66
+ - 插件页「已安装插件」标题行右侧的「图标生成」按钮,上传图片后双尺寸预览并下载 `icon.svg`。
52
67
 
53
68
  ## 目录结构
54
69
 
55
70
  ```
56
71
  dsh-plugin-yanyan-toolkit/ 聚合根包(自身行为空壳,只承载 patch 与依赖)
57
- cordis.patch.yml 4 行 insert:自身 + 3 个插件
72
+ cordis.patch.yml 5 行 insert:自身 + 4 个插件
58
73
  lib/index.js, lib/client.js 极简 host / client 半区
59
74
  locale/{zh,en}.json 插件页展示元数据
60
75
  packages/
61
76
  dsh-plugin-yanyan-update/ 插件更新(自写)
62
77
  dsh-plugin-yanyan-subagent/ 子智能体管理(自写)
78
+ dsh-plugin-yanyan-icon/ 图标生成(自写)
63
79
  (会话管理无子目录,直接依赖 npm 包 @linxin666/dsh-session-archive)
64
80
  ```
65
81
 
66
- 三个真实插件列在根包 `dependencies` 里,pnpm 的 hoisted 布局会把它们与 npm 包一起提升到 profile 根 `node_modules`,四条 loader 行的 `name` 均可解析。
82
+ 四个真实插件列在根包 `dependencies` 里,pnpm 的 hoisted 布局会把它们与 npm 包一起提升到 profile 根 `node_modules`,五条 loader 行的 `name` 均可解析。
package/cordis.patch.yml CHANGED
@@ -1,7 +1,7 @@
1
- # 研研工具包:一个 bundle 挂载四条 loader 行 —— 自身元数据行 + 三个功能插件行。
1
+ # 研研工具包:一个 bundle 挂载五条 loader 行 —— 自身元数据行 + 四个功能插件行。
2
2
  # 每行 name 必须互不相同且恰好等于对应包的 package.json name,否则
3
3
  # dsh-client-modules 会报 duplicate factory registration。
4
- # 三个功能插件各有独立 id,安装后可由用户在插件页单独启停。
4
+ # 四个功能插件各有独立 id,安装后可由用户在插件页单独启停。
5
5
  - insert:
6
6
  # 自身行:只承载健康检查路由与空 client 壳,不贡献任何 UI。
7
7
  - id: yanyan-toolkit-compat
@@ -33,3 +33,11 @@
33
33
  autoCleanEnabled: false
34
34
  idleMinutes: 30
35
35
  checkIntervalMin: 1
36
+
37
+ # 插件四「图标生成」:上传图片居中裁切、生成适配插件列表图标位的 icon.svg。
38
+ - id: dsh-plugin-yanyan-icon
39
+ name: 'dsh-plugin-yanyan-icon'
40
+ config:
41
+ radius: 13
42
+ padding: 3
43
+ borderWidth: 1
package/icon.svg CHANGED
@@ -1,8 +1,5 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="研研工具包">
2
- <rect x="4" y="4" width="56" height="56" rx="14" fill="#eef2ff"/>
3
- <rect x="4" y="4" width="56" height="56" rx="14" fill="none" stroke="#c7d2fe" stroke-width="2"/>
4
- <path d="M22 30h20M22 38h20M22 46h12" stroke="#4f46e5" stroke-width="3.4" stroke-linecap="round"/>
5
- <circle cx="22" cy="21" r="4.2" fill="#4f46e5"/>
6
- <circle cx="32" cy="21" r="4.2" fill="#6366f1"/>
7
- <circle cx="42" cy="21" r="4.2" fill="#818cf8"/>
8
- </svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="plugin icon">
2
+ <defs><clipPath id="yyicon-clip"><rect x="6" y="6" width="52" height="52" rx="10"/></clipPath></defs>
3
+ <rect x="3" y="3" width="58" height="58" rx="13" fill="#eef2ff" stroke="#c7d2fe" stroke-width="1"/>
4
+ <image x="6" y="6" width="52" height="52" preserveAspectRatio="xMidYMid slice" clip-path="url(#yyicon-clip)" xlink:href="data:image/png;base64,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"/>
5
+ </svg>