Repository navigation
Releases: LLYlab/DSHEssentialTools
Release list
DSHEssentialTools v2.14.1 — teardown keeps intent
v2.14.1 — 修「关一次总开关/切一次模式,装过的东西就全忘了」+ boot 早期恢复失败
实测现场:重启后 adu_status 显示 warm 四个模块在,但只有历史里有的 plugins 没回来。
挖出两个叠在一起的问题:
1) 临时拆台把"意图"也删了
_aduUnmountAll()(总开关关闭、模式切换、插件卸载时调用)走的是 _aduUnmount(),
而 _aduUnmount() 在 v2.11 起会 _aduWanted.delete(id) 并落盘 —— 于是"临时拆台"
等于"用户不想装了",下一次启动就不再恢复。这就是"装过的东西一个都没回来"的主因。
- 拆出
_aduDisposeGroup(key):只销毁注册,不动 wanted / 持久化。 _aduUnmountAll()改用它;只有模型/用户显式adu_unload才清意图。
2) boot 早期读不到存储 → 恢复空转
storageDomain 在宿主启动早期可能还没就绪,det.adu.mounted 读到空表,于是"上次装的"
一个都没回来(warm 兜住了四个,历史里的 plugins 就丢了)。
- 新增
_aduScheduleRestore():0s / 2s / 6s / 15s 各试一次,_aduRestoreMounts()幂等。
3) 状态可自证
adu_status 与 _aduStatus() 现在回传 wanted(期望集合)、warm(开局自动装)、
diag(最近失败账本)。以后"为什么没装/为什么失败"在状态里就能看到,不必再猜。
测试
alv-adu166 → 176:拆台不清 wanted(行为)、拆台后自愈装回、状态含 wanted/diag、
源码守卫(拆台不得走_aduUnmount)。- 全量:631 断言全绿。
Install
powershell dsh plugin --profile web add github:LLYlab/DSHEssentialTools
DSHEssentialTools v2.14.0 — warm startup, web reconnect grace
v2.14.0 — 默认就装(warm)· 浏览器断连自动等一次 · 提示不再每步重注册
三条反馈:「不会自动装载东西」「不会用」「手动装载后 web 频繁失效」。
1) 自动装载:开局就把最常用的四个模块装上(warm)
2.12/2.13 只有"上次装过什么"会恢复,没装过的就一直是空的 —— 新会话/新机器上只有 adu_* 五个
元工具,模型自然"不会用"。现在:
- 新增配置
aduWarm,默认["dlt","alv","browser","web"]:启动时与det.adu.mounted合并后一起装载。
于是写代码(dlt)、看文档(dlt)、看图(alv)、查网页(browser/web)一开局就可用;
只剩最重、最少用的plugins(19 个工具)保持按需。 aduWarm: []回到纯按需;aduWarm: "all"全装。- 面板与
adu_status会显示warm,一眼看清"开局自动装了什么"。
2) web 频繁失效:断连不再立刻判死
扩展是 MV3 service worker,休眠/重启时 socket 掉一下再连回来是常态。旧实现一看到断连就
直接把失败甩给模型(2.10 引入的 fail-fast),观感就是"web 工具频繁失效"。现在:
- 调用前若扩展未连上,先最多等 1.5s(每 300ms 探一次)让 service worker 连回
127.0.0.1:9123; - 若命令中途断连(
browser-disconnected),等 600ms 后重试一次; - 仍然失败时错误文案区分三层:桥(未启动)/ 扩展(未连接)/ 档位(只读、只写、关闭),并给下一步。
detHealth新增「浏览器桥」检查:桥是否运行、扩展是否连接、当前档位、网络权限是否到位。
3) 提示抖动:文本没变就不重注册
_refreshAduPrompt() 原来每次装载/卸载都 dispose + 重注册系统提示段。系统提示每变一次,
宿主可能重开一个请求序列(缓存失效、界面观感抖动)。现在先比对文本,没变就什么都不做。
测试
alv-adu157 → 166:_aduWarmIds默认值 /aduWarm: []/"all"三种形态、
浏览器断连等待与可读错误、detHealth的桥检查、提示缓存守卫。- 全量:128 + 60 + 23 + 47 + 17 + 37 + 22 + 40 + 71 + 10 + 166 = 621 断言全绿。
Install
powershell dsh plugin --profile web add github:LLYlab/DSHEssentialTools
DSHEssentialTools v2.13.0 — specialised tools first, pwsh as fallback
v2.13.0 — 治「模型疯狂 pwsh」:工具选择优先级提示 + 修掉 dlt_run 的输出校验 bug
反馈:「AI 不喜欢用这些 tool 了,现在疯狂 pwsh」。查下来是两个原因叠加,都不是模型"变懒":
1) dlt_run / dlt_build 的输出曾被宿主判为非法
输出 schema 声明 exitCode: number,但超时或进程起不来时真实值是 null → 宿主
「无损 JSON / 类型校验」直接判 invalid output。模型撞一次 value.exitCode must be a number,
之后就会本能地绕开这个工具、回到万能的 pwsh。
- 修:新增
normalizedRun(),exitCode统一归一化(拿不到就是-1),并显式给timedOut。 - 顺手把
dlt_run的描述改成「首选执行入口 …… 而不是 pwsh 里拼命令行」。
2) ADU 的提示只讲"怎么装",没讲"什么时候该用它而不是 pwsh"
旧提示的重点是"按需装载、粘性、忘了就 rememory",模型的默认推理就变成了
「我手里没有专用工具 → 用 pwsh」。现在 tool:det-adu 这一段整个重写为工具选择优先级:
- 先列这一轮真实可用的专用工具(按已装模块动态生成,不推荐手里没有的工具):
文档 →dlt_doc_*、执行/编译 →dlt_run/dlt_build、环境 →dlt_env、
图片 →alv_*、浏览器 →det_browser、网页 →web_*、插件 →det_global_plugin_*。 - 再明确写:pwsh 只用于 git、目录/文件日常整理、以及确实没有专用工具时的临时探测;
判据是「只要上面有一件工具能直接完成这件事,就先用它」。 - 装载/卸载后即时刷新这段文案(
_refreshAduPrompt),保证"可用清单"永远是真的;全装模式下同样注册。
测试
alv-adu145 → 157:提示生成器与_refreshAduPrompt的源码守卫;dlt-tools的
normalizedRun与exitCode: number双声明守卫(并禁止宿主不认识的nullable);
行为上断言"已装模块的工具被点名、未装的不点名、未装单独提示 adu_load"。- 全量:128 + 60 + 23 + 47 + 17 + 37 + 22 + 40 + 71 + 10 + 157 = 612 断言全绿。
Install
powershell dsh plugin --profile web add github:LLYlab/DSHEssentialTools
DSHEssentialTools v2.12.0 — self-healing ADU
v2.12.0 — 自愈式按需装载:工具不会再「自己没了」
用户反馈三条:「旧对话会自动卸载已经用过的工具」「有时 DSH 不会启用 ADU」「adu 下一轮才生效」。
根因(第一条是真 bug,不是错觉)
_syncDetRuntimeFeatures() 在按需模式下有一段隐式卸载:
if (lazy && !mounted("alv")) this.alv.dispose(); // ← 旧实现而这个函数会被每一次 feature 变更、master 同步、模块装载之后调用。叠加 v2.11.0 的
"恢复静默失效"(aduModule 忘了 import):重启后 _aduGroups 是空的 → 之后任何一次同步
都会把 dlt/alv 卸掉 → 用户看到的就是"我用过的工具自己没了"。
修法:把"装着"变成期望集合,并让它自愈
| 机制 | 做法 |
|---|---|
| 期望集合 | _aduWanted(内存)+ det.adu.mounted(落盘)。装载=加入、卸载=移除,两者始终一致 |
| 不再隐式卸载 | 按需模式下 _syncDetRuntimeFeatures 只自愈、不 dispose;装与卸只归 ADU 管 |
| 每步自愈 | 挂 agent/pre-step:每一步请求前比对 wanted 与已装,缺了就补。重启竞态、热重载、误卸都靠它兜回来 |
| 用过即钉住 | 挂 tools/pre-execute:某模块的工具被真正调用过一次 → 加入 wanted 并落盘。用过的工具从此参与自愈 |
| 面板自愈 | 打开 ADU 面板(aduStatus)也会顺手对齐一次,并回传 wanted / drift |
| 模式可查 | detFeatureGet 新增 aduMode(lazy / atLoad / boot / wanted / loaded),"有时不启用"不再靠猜 |
关于「下一轮才生效」
装配顺序是 systemPrompt.assemble()(先取工具快照)→ agent/pre-step → 建请求,
所以本步内挂载不可能改本步的工具表 —— 这是宿主结构,不是 DET 能绕的。但:
- 它不是"下一轮"而是"下一步":同一轮里如果还有后续步骤(工具调用循环),新工具就会出现;
adu_load的说明已改成准确表述;- 自愈保证"装过的东西"永远在下一次装配里,而不是等用户再问一遍。
测试
tests/alv-adu.test.mjs130 → 145:源码守卫(不得隐式 dispose、必须有 pre-step 自愈 /
pre-execute 钉住 / aduMode)+ 行为(wanted 记账、清空_aduGroups后自愈补装、
调用一次工具即钉住并落盘)。- 全量:128 + 60 + 23 + 47 + 17 + 37 + 22 + 40 + 71 + 10 + 145 = 600 断言全绿。
Install
powershell dsh plugin --profile web add github:LLYlab/DSHEssentialTools
DSHEssentialTools v2.11.1 — sticky restore & real unload fixes
v2.11.1 — 修「粘性装载其实没生效」+「卸载是假卸载」;ALV 边线标签改画在边栏
两条都是 v2.11.0 里静默失效的缺陷,靠新加的行为测试(真服务实例 + 会回收的假 tools.register)抓出来的:
1) 粘性恢复:aduModule 忘了 import,ReferenceError 被吞
_aduMountedSetting() 用 aduModule() 过滤已装模块,但 lib/index.js 没有导入它。
于是每次读 det.adu.mounted 都在 catch {} 里炸掉、返回空数组 —— 重启后永远不会装回任何模块,
而界面上一切正常(这正是最坏的一种坏)。修:补 import;并且 catch 现在写进失败账本
(_diagPush("adu", "read det.adu.mounted", …)),不再静默。
2) 卸载是假卸载:dlt / alv 只清了账本,工具还在
这两个模块的工具注册在各自 bridge 内部,_aduGroups[id] 存的是空数组(truthy),
旧代码判断 if (!disposers) 才 dispose —— 空数组绕过了这个分支,于是 adu_unload alv
删了账本、写了持久化,8 个 alv_* 工具却仍然可用。修:卸载时先按 key dispose bridge,再跑 disposer。
3) ALV alv_edges 的标签不再压正文
原来 y=81(4) 这类标签直接画在线上,实测会盖住表格末尾几行(尤其最后一条线)。
现在标签画在左侧 72px 边栏里(gutter 可调、labels:false 可关),输出图比原图宽
gutter 像素,回传 toSource.x = -gutter 与一句换算说明。
测试
tests/alv-adu.test.mjs118 → 130:新增「粘性装载(行为)」段 —— 用apply()造真服务实例 +
会回收的tools.register,断言:按det.adu.mounted恢复、幂等、卸载后工具真的消失、
持久化写回[]、卸载后可重新装载。- 全量:128 + 60 + 23 + 47 + 17 + 37 + 22 + 40 + 71 + 10 + 130 = 585 断言全绿。
- 文档:
docs/DET功能.md补 DLT / ALV / ADU 三节并修正工具总数(45);docs/GUIDE.md新增
「12. ADU 按需装载 与 ALV 辅助线视觉」;ARCHITECTURE.md新增 §11.7(粘性装载不变量 + 两个缺陷的教训)。
Install
powershell dsh plugin --profile web add github:LLYlab/DSHEssentialTools
DSHEssentialTools v2.11.0 — ADU is sticky · adu_rememory
v2.11.0 — ADU 改成「只装不卸」:粘性装载 + adu_rememory 重新注入工具记忆
一句需求:「ADU 不主动卸载工具(agent 仍可卸,虽然那多少是'何意味'),但要提供 rememory tool list 重新增强局部记忆。」
上一版把 ADU 当预算工具(用完就卸载,让下一轮 schema 变轻)。这一版按用户口径改成记忆工具:
装载是持久的,真正稀缺的不是 schema 预算,而是模型对"我手里有什么"的局部记忆 —— 尤其是长对话被压缩之后。
1) 粘性装载:ADU 不再主动卸载
- 装载落盘到
det.adu.mounted;宿主启动时_aduRestoreMounts()自动装回。重启后工具还在,这是"不主动卸载"的一半。 - 另一半:ADU 自身没有任何"空闲/切会话/到点"的自动卸载路径。只有三种情况会让模块消失:
agent 显式adu_unload、总开关关闭、插件被卸载。 adu_unload保留(能力还在),但描述改成「大多数时候不需要」:卸载只会让后续轮次少工具,并不会帮你记住什么。
2) adu_rememory:重新注入工具索引(新增第 6 个元工具)
- 新增静态索引
ADU_TOOL_INDEX(45 条:39 个模块工具 + 6 个元工具),每条 = 关键参数摘要 + 一句话用途。 adu_rememory(可带id只看某模块,full附带代价/风险)把索引压成一张表返回:工具记忆(ADU 按需模式 · 2 组 · 14 个工具) ## alv · ALV 辅助线视觉 alv_grid(path, cell?, majorEvery?, guides?, out?) — 加栅格+标尺+辅助线,建立像素坐标系- 它不重新装载(本来就没卸),也不占工具 schema 的预算 —— 这正是压缩之后需要的东西。
aduToolIndex()是索引的唯一装配点,rememory 与测试共用,避免"文档写一套、实现另一套"。
3) 顺手修掉两个真问题
- 模式切换此前要重启才生效:
_loadExtension()有_extLoaded守卫,而它不区分"按需 / 全装"。于是在设置里开关 ADU,
重启前完全无效,且_aduGroups会与全装模式重复注册同一批工具。新增_applyLoadMode():模式变化时整体
_unloadExtension()→_loadExtension(),并重跑运行时特性同步。 - ALV 的系统提示段一直缺席:
lib/alv.js用了不存在的sp.register({id,role,content}),静默失败(返回 null 不报错)。
改成宿主真正的systemPrompt.section({name, order, text})(与 DLT 集成同一个 API)。按需模式另加一段
tool:det-adu提示:按需 + 粘性 + 忘了就 rememory。
4) 管理器面板
- ADU 面板文案改为「6 个元工具 / 装载是粘性的 / 重启后自动装回」,并提示
adu_rememory的用途。 - 「全部卸下」标注为「(不常用)」——不再鼓励用户把已装模块卸掉。
测试
tests/alv-adu.test.mjs96 → 118 条:索引覆盖率(模块表里每个工具都必须有索引条目,且索引里不得有已不存在的工具)、
参数/说明非空、按模块筛选、rememory 端到端(无损 JSON)、以及粘性装载的源码守卫(持久化键、恢复路径、模式重装)。- 全量:128 + 60 + 23 + 47 + 17 + 37 + 22 + 40 + 71 + 10 + 118 = 573 断言全绿。
Install
powershell dsh plugin --profile web add github:LLYlab/DSHEssentialTools
DSHEssentialTools v2.10.1 — adu_load output fix
v2.10.1 — 修 adu_load 被宿主的「无损 JSON」校验拒收(装载成功却报 invalid output)
真实故障:adu_load id=alv 明明把 8 个工具全装上了(adu_status 显示已装),调用本身却返回
tool "adu_load" returned invalid output: value is not lossless JSON。
真因:宿主会拿 JSON.parse(JSON.stringify(out)) 与 out 做深比较来校验工具输出;任何值为
undefined 的字段都会在序列化时被丢掉,于是"成功装载"被判成非法输出。adu_load 的结果行里
error: res.error 在成功路径上正是 undefined。
- 修:结果行只放有定义的字段(
error仅在失败时出现)。 - 守卫:新增 16 条断言 —— 用假桥真跑 5 个元工具的
execute,逐条校验输出「无损 JSON」,
并钉住「成功结果不得携带 error 键」。这类缺陷以后会在测试里先炸,而不是在用户手里。 - 同一轮自检顺带确认:ALV 引擎(Pillow/numpy/OpenCV + Windows OCR)在升级后的宿主上可用,
alv八件套装卸正常;DSH 侧 junction 仍指向 0.2.0-rc.2(本次 DSH 升级未生效,见下条)。 - 断言总数:535 → 551。
⚠ 本次自检还发现:上一轮的 DSH 升级器在「停掉宿主」之后被宿主进程树一并杀掉(Windows job
object 语义),所以 DSH 仍是 0.2.0-rc.2;已改用 WMI 脱离创建进程的方式重跑升级。
Install
powershell dsh plugin --profile web add github:LLYlab/DSHEssentialTools
DSHEssentialTools v2.10.0 — ALV vision scaffolding · ADU lazy loading
v2.10.0 — ALV 辅助线视觉 · ADU 按需装载 · 失败账本 · 工具预算
这一版回答三个一直存在的问题:
- 模型说"我看不清这张图"时,它其实缺的是坐标系。(→ ALV)
- 每一轮只用到一两个工具,却要为三十多个工具的 schema 付预算。(→ ADU)
- 失败了只有一句
browser-timeout,攒不下能用来优化的记录。(→ 失败账本 + 按命令预算)
1) ALV — Aided Line Vision(辅助线视觉,8 个 alv_* 工具)
视觉模型看到的是"一张图",不是"一个坐标系"。当任务变成"这张表第 3 行错了没""这条线在哪一列结束""(1130,740) 印的料号是什么",需要的不是更聪明的模型,而是脚手架。ALV 用纯几何把这些脚手架造出来 —— 无 ML、无网络、无 API Key。
| 工具 | 干什么 |
|---|---|
alv_info |
尺寸/模式/DPI/亮度/暗像素比例/16 段直方图(可选噪声估计)。据此决定要不要裁剪放大或二值化。 |
alv_grid |
坐标系本体:栅格 + 四周坐标标尺 + 任意位置的辅助线(guides:{horizontal:[470],vertical:[430]}),返回整张新图与换算说明。 |
alv_crop |
按 box 精确裁剪、放大(小字必须先放大再 OCR);不给 box 就自动裁掉四周均匀空白。返回 toSource 换算。 |
alv_edges |
硬编码边缘位置提取:Canny + 概率霍夫 → 线段坐标 + 合并后的行/列边线位置(horizontalPositions:[{y,votes}]),并输出标好 y= / x= 的副本。 |
alv_ocr |
硬编码 OCR:走 Windows 本机 OCR(离线、带逐词包围盒)。mode=lines 按视觉行合并并自动去掉中文词间空格;mode=cells 逐格识别直接产出表格矩阵。 |
alv_tile |
带重叠切块 + 放大,一张大字图分块喂给模型。 |
alv_annotate |
按坐标画 box/line/arrow/cross/circle/text —— 把"我算出来的位置"精确画给用户看。 |
alv_enhance |
灰度/自动对比度/自适应二值化/锐化/放大(mode=auto 全上)。 |
引擎随包:py/alv.py(Pillow + numpy + OpenCV,跑在 python312 环境)+ py/alv_ocr.ps1(Windows.Media.Ocr 的 WinRT 桥,纯 ASCII 源文件,输出 UTF-8 JSON)。全程离线,OCR 语言跟随系统(本机 zh-Hans-CN)。
一个真实回合长这样:
info → 900×600, 暗像素 3.9%
edges → horizontalPositions: y=81(4) y=141(4) y=201(4) y=261(4) y=321(4) y=381(4)
ocr → 型号数量 / ALV-100 12 18.50 222.00 / … / TOTAL 471.00
ALV 同时注入一段系统提示,告诉模型"别猜坐标,先造脚手架,再 read_image 亲眼看"。
2) ADU — AutoDETUsage(按需装载,5 个 adu_* 工具)
旧行为是一次性把 30+ 个模型工具塞进上下文:每次请求都要携带全部 schema,工具越多模型越容易挑错,而绝大多数回合只用得到一两个。
ADU 把"装载"变成显式协议:
启动 → 只注册 adu_list / adu_describe / adu_load / adu_unload / adu_status
adu_list → 模块表:每个模块会带来哪些工具、代价多少、风险是什么、装没装
adu_describe id=… → 深查:逐个子能力、每个工具的用途、副作用、建议
adu_load id=… → 真正装载(等价于打开对应功能开关,下一轮生效)
adu_unload id=… → 卸下,让后面的上下文重新变轻
模块(装载单元):plugins(全局插件库 + CDM + TCT + MDA + MMS,19 个工具)、browser、web、dlt、alv;security / project 标为常驻(纯监听 / 端点,不在装载清单里)。
三条纪律:
- 模块表是唯一事实源(
lib/adu.js),元工具只做校验与转述,不自己持有状态。 - 装卸走同一根 disposer 管线:与总开关、与 UI 上点开关完全一致,不会出现"两套状态各说各话"。状态记账在
_aduGroups(id → [disposer])。 - 关掉即回到全装:
adu开关或配置aduBoot: "eager"是最快的退路;总开关关闭时 ADU 一起撤走(完全原生)。
设置页新增「ADU · 按需装载(模块表)」:逐模块「装载/卸下」+「全部装载/全部卸下」,并显示当前模式与已装模块。
3) 失败账本 + 按命令超时(修 browser-timeout 只能盲重试)
数据来自一次真实失败:对一个 5000+ 节点的重页面调 screenshot,旧实现所有命令共用 15s 常数 → 必然超时,而错误只有一句 browser-timeout:
- 按命令给预算(
lib/browser.js的CMD_TIMEOUTS):screenshot/run45s、读类 25s、click/fill20s、list_tabs10s;调用方仍可传timeoutMs覆盖(det_browser新增该参数)。 - 结构化失败:超时结果带上
cmd/waitedMs/tabId/hint,其中hint直接给下一步(换read_text、先web_focus、重启扩展…)。 - 断连即刻结算:扩展 socket 关闭时把在途命令立刻判为
browser-disconnected,不再静默等满 45s。 - 失败账本
_diagPush:最近 200 条(kind/what/message/at),由 ALV/ADU/浏览器失败路径写入;新增端点detHealth汇总已有事实(启动健康、插件库「意图↔事实」一致性、DLT/ALV/ADU 装载事实、失败账本),设置页新增「健康与失败诊断」面板。
4) 前端:信息架构与可用性
- 新增分组「识图与工具预算」(
alv/adu两个开关),分组样式改为左侧 2px 强调条 + 轻底色,长列表里能一眼收拢(仍只用宿主--dsw-alias-*token,架构守卫会拦硬编码色值)。 - 每个分项开关现在是真正的
role="switch"+aria-checked+ Tab/Space/Enter:原先只有onClick,键盘用户走不到这些开关。 - 新增三块面板:ADU 模块表、ALV 状态(引擎/OCR 桥是否在位 + 工具胶囊)、健康与失败诊断。
- ADU 模块行给"已装/未装/常驻"三色徽标、代价与风险一行说明,装/卸按钮就地生效。
5) 清洁与测试
- 清掉仓库里 28 个
.bak-*备份 + 1 份过期发布说明(10.31 MB → 6.38 MB);.npm-cache/与*.bak*本来就在.gitignore里,这次是物理删除。 - 新增
tests/alv-adu.test.mjs(79 条断言):模块表契约、开关语义、源码守卫(ALV/ADU 必须真的被index.js导入并走_aduGroups记账、_loadExtension在按需模式下必须提前返回)、客户端面板与归组、真跑引擎(info/grid/edges/annotate/crop/未知 op/缺图)。 - 测试产物改写临时目录(测试不再往仓库里留图)。
- 全量:unit 128 · host 60 · adapt 23 · client-structure 47 · client-load 17 · dlt-host 37 · dlt-integration 22 · draft 40 · gp-state 71 · pause 10 · alv-adu 79 = 534 断言全绿。
- 依赖区间抬到
^0.2.1-rc.3(DSH 0.2.1 起预发布版才算在区间内;此前^0.2.0-rc.2按 semver 预发布规则并不匹配 0.2.1-rc.x)。仍未声明engines.dsh,但^0.2.1-rc.3已让声明层与实测宿主一致。
Install
`powershell
dsh plugin --profile web add github:LLYlab/DSHEssentialTools
or
npm i -g dsh-essential-tools
`
DSHEssentialTools v2.9.4
Changelog
v2.9.4 — 修好「DET 与它管理的插件」的联动;草图判定改成真·实时;草图拆成独立包
- 依赖对齐 DSH 0.2:
@deepseek-ai/dsh-typert-protocol由^0.1.7-rc.2抬到^0.2.0-rc.2,并补齐此前漏声明的运行时依赖@deepseek-ai/dsh-tools/dsh-llm/dsh-credentials/dsh-storage-domain/dsh-session(统一^0.2.0-rc.2)—— 这五个此前只靠在运行时从宿主树里解析(junction + 运行时解析模式),声明层是缺的。代码本身无需适配 —— 0.2.0-rc.2 下 10 个测试文件全绿(约 449 条断言),升级后的真实宿主里 DET 工具、端点与.dset-*客户端界面均已实测渲染。
三处都是用户实测反馈:「关掉一些插件反而影响了 DET 的自带功能/UI」、
「总开关关闭后该停的没停(或该恢复的没恢复)」、「在 DET 里禁用/启用常驻插件不生效或重启后回弹」,
以及「自动切换视觉模型居然不是实时检查模型表判定的」。
1) 联动:意图与事实彻底分开(修「重启后回弹」)
根因是一个字段被当成两个东西用:globallyEnabled 既是「用户的启用意图」,
又是「总开关/兜底的记账」,还被 gpList 拿 loader 的实际状态回写。
三条路径互相覆盖,于是「在 DET 里禁用常驻插件 → 重启后又活了」反复出现。
- 新增权威字段
enabledIntent(global.js域里是可选字段 → 旧记录照样能读,
读到旧记录时由permanentIntent推断一次并自动迁移)。 permanentIntent()的优先级重写:档位「全局禁用」= 硬否决(按定义「拒绝任何启用」)
→ 其次enabledIntent→ 最后才是旧记录的兼容推断(level优先于会被记账污染的globallyEnabled)。gpList不再把 loader 的事实回写进意图字段(旧代码那句
updateMeta(p.id, { globallyEnabled: actualEnabled })正是抹掉意图的凶手,
而它上面的注释还写着「不再用事实覆盖意图」—— 注释与代码相反)。现在事实只以
actualEnabled/fiberPhase/stateMismatch的形式如实报告,一个字都不落回存储。gpSetPermanentEnabled不再改档位。旧实现把用户的五档改写成
always/disabled,ai-approve/frozen就这么被永久销毁了。二分开关表达的是
「装不装」,与「允许谁启用」是两个维度,现在分开存(档位原样保留,界面也据此提示
「五档档位保持「x」不变」)。- 档位改动也立刻落到 loader:改成「全局禁用」= 卸下条目;从「全局禁用」改回别的档位
则按enabledIntent重新装回(顺手给旧记录补上意图字段,否则「本来想开着」会被推断丢掉)。 - 兜底不再被自己撤销:总开关暂停 / 启动连续失败兜底都写
enabledIntent=false
(这是有意的停用),于是重启后_applyPersistentPermanentStates不会又把它装回来。 - 暂停快照的
kind如实标注(permanent/library)。旧代码一律写library,
导致_resumeManagedPlugins里判kind === "installed"的那条分支永远不命中(死代码)。
2) 联动:不再强迫整页刷新(修「关掉插件反而影响 DET 自带功能/UI」)
- 旧实现在常驻插件启停后无条件
window.location.reload()——
即使宿主回了applyError(什么都没生效)也照刷。一次整页刷新会把 DET 自己的
界面、设置页位置、未保存的输入一起重置;这就是「关掉一个插件却把 DET 自己的功能和
界面也搅了」。 - 现在:只在你真的改了 loader 状态时提示,并把「刷新前端」做成你点才刷的按钮。
回报以宿主给的actualEnabled为准(loader 已实时禁用/实时未生效:<原因>),
不再「说成功了但其实没生效」。 - 常驻插件不再被误当成动态 Cordis 插件:
_stopSession以前会拿占位pluginId
"permanent"去问动态运行器「停掉一个叫 permanent 的插件」(空转的误调用),现在挡掉。 - 顺带确认并保留平台已有的正确行为:动态插件的浏览器半区由平台在宿主停用广播
cordis/dynamic-retract时自行 retract,DET 不需要(也不该)再维护一条手动卸载旁路。
3) 草图:判定改成真·实时(用户口径:「不是实时检查模型表判定的」)
两处陈旧,一起修:
- Host 侧拿的是历史模型:
sessionModelRoute只读会话日志里最后一条
request/header—— 那是已经跑过的那一轮。用户在模型选择器里换了模型后,会话先落一条
model/selection、投影里出现pending,要等下一轮请求才会被消费;只看request/header
就永远慢一拍,于是「当前模型能收图」判断错误,发送时才被宿主以
MODEL_DOES_NOT_SUPPORT_IMAGES顶回来。
→ 新增liveModelSelection():优先读会话投影modelSelection的pending ?? lastUsed,
再退事件日志(最后一条model/selection若晚于最后一条request/header就采信它),
最后才退宿主默认模型;返回里带source/live如实标注来源。 - Client 侧复用了挂载那一刻的结论:
doImport拿support状态当缓存,
画布开着时换模型完全被忽略。→ 每次点「导入」都重新问一遍宿主。 - 判据收口到一处
modelAcceptsImages():只认宿主模型表的inputModalities,
draftSupport与dltVisionRoutes共用,两处判定不会漂移;拿不到就按「收不了」处理。 - 附件白名单也参与判定(
attachments.imageLimits不收image/png时不当图片)。
4) 草图:其余可复现缺陷
- 撤销真的能撤:历史栈改为在落笔之前抓快照(并记住当时的「画布是否为空」)。
旧实现在落笔之后才入栈,于是第一次「撤销」拿回的是刚画完的那张 —— 看起来毫无反应,
而且第一笔永远撤不掉。 - 交付不撒谎:
allowImage为真却挂不上附件(客户端服务缺失 /addAttachments拒绝)时
如实回报imageFailure/delivered,按钮上显示「⚠ 已存成 PNG:…(作为图片附件失败:…)」,
不再谎报「已作为图片注入」。 - 落盘收窄 + 同名不覆盖:
dir只接受本会话工作区(其余忽略并如实回报
dirIgnored);同秒连画两张自动加-2/-3,不再覆盖第一张。
5) 草图拆成独立包发布:dsh-sketch-draft
草图这块不需要 DET 的任何东西(只依赖宿主的 llm / sessions / sessionProjections /
attachments),所以拆成独立可安装、可发布的包:C:\Users\L2959\.dsh\plugins\dsh-sketch-draft
(npm 名 dsh-sketch-draft,已发布 dsh-sketch-draft@1.0.0(dist-tag latest),自带 cordis.patch.yml / README / CHANGELOG / install.ps1 / publish.ps1 /
85 项断言的测试)。拆出来之后:不想装 DET 的人也能用;DET 可以卸掉自己那份;
这条链上的修复能独立发版。
单拥有者约定(拆包后「同一个功能有两个实现」的唯一协调点):新增
subordinateGrants().grants.sketchDraft。DET 在场且「草图导入」开着 →
独立包读到这个声明后主动让位(一个插槽都不注册),所以输入框旁永远只有一个铅笔;
DET 关掉 dltDraft(或总开关关闭)→ 独立包自动接手,反之 DET 不需要知道独立包存在。
让位方向只有一条,不会互相抢。
测试
- 新增
tests/draft.test.mjs(40 条):liveModelSelection的四种来源与优先级、
modelAcceptsImages的保守语义、draftSupport的合成结论与原因、附件白名单、
draftSave的路径收窄 / 魔数 / 同名不覆盖,以及「客户端不得复用旧结论」的源码守卫。 tests/gp-state.test.mjs从 35 条扩到 71 条:意图模型重写后的优先级、硬否决、
「只改装不装、不销毁档位」、被否决时一个 loader 改动都不发,并新增「subordinateGrants().grants.sketchDraft与 dltDraft 开关联动」的断言与源码守卫
(拦住「事实覆盖意图」与「强制整页刷新」回流)。- 新增独立包
dsh-sketch-draft/tests/sketch-draft.test.mjs(85 条):包身份与apply
装载契约、四个端点、让位矩阵(含 DET 抛错时不失效)、client bundle 真执行
(含「被让位时不注册按钮」)。 - 全量:DET unit 128 · host 60 · adapt 23 · client-structure 47 ·
client-load 17 · pause 10 · gp-state 71 · dlt-host 37 · dlt-integration 22 ·
draft 40;独立包 sketch-draft 85。合计 540。
v2.9.3 — 真正修好「右下角看不见单价了」(真因:状态框拿不到当前会话)
v2.9.2 只治了「模型目录服务取太早」,用户复测依旧失败。这次不再靠猜:给单价那一格加了自报状态(class 上带 dset-sb-price-* / dset-sb-diag-*,可从 DOM 直接读),于是从运行中的页面上一格一格定位到了真因链:
dset-sb-diag-nosid→ 状态框拿不到当前会话 id。它读的是sessions.list.getSnapshot().current,而这个宿主版本的会话列表快照只有byId:「当前会话」由 ui-session 自己的绑定源持有,于是这里长期返回 undefined,凡是依赖它的都静默失效(单价、本对话花费…)。
→ 改为:优先槽位给的props.sessionId(DET 自己的工具栏一直是这么用的)+ 旧字段current+ 「会话列表里被主视图持有的那个」三级兜底。dset-sb-diag-nomodel→ 客户端拿不到跨插件的modelDirectories,所以算不出「当前模型」。
→ 改为直接问宿主:新增端点dltSessionRoute(读会话最近一次request/header,退回宿主默认模型),这才是「谁在跑」的事实源;modelDirectories 只当切模型时的可选加速。dset-sb-diag-routefail→ 宿主端那份新代码还没装载(客户端 bundle 会热更新,宿主半区必须重启 DSH)。这也说明 v2.9.1/v2.9.2 那批宿主侧修复(插件管理、dltVisionRoutes)此前根本没生效。
→ 顺手把单价做成不依赖重启也一定显示:只要有单价数据,就算拿不准当前模型也按表内第一条显示(tooltip 里注明「未匹配到当前模型,按表内第一条显示」),绝不再因为模型是 null 把整格变成「—」。
实测(页面上直接读 DOM class):修复前 …price-nomodel…diag-nosid(空)→ 中途 …nomodel…diag-routefail → 现在 …price-ok_guess…diag-routefail,单价已显示;等 DSH 重启后宿主路由生效,会变成 price-ok(精确匹配当前模型)。
v2.9.2 — 修「右下角看不见单价了」
用户反馈:v2.9.1 把状态框改成两列网格后,右下角的单价看不见了。查下来是取数链路两处老毛病被新样式放大暴露(宿主侧官网抓取本身没问题 —— 直接跑 fetchDsPrice() 198ms 就拿到 CNY 单价表):
- 模型目录服务抓得太早:
DshStatusBox在挂载那一刻把props.modelDirectories抓进闭包,而 DET 的状态框往往比 model-selection 半区先挂载 → 拿到undefined→curModel永远是 null → 算不出单价 → 那一格只剩一个「—」(旧版是「价格 —」,看着像"没这一项",新版标签写着「单价」就显得是"单价没了")。改成惰性解析 + 每秒重试(最多 30 次),服务晚到也能接上(拿到就退化成订阅)。 - 单价只在启动那一刻取一次:那一刻网络抖一下/端点没就绪,整局都不会再取。改成未取到时每 15 秒自动重试(上限 8 次),并支持点那一格手动重取。
- 顺便把单价格式改回一眼能认的写法:
命中 ¥0.02 · 未命中 ¥0.04 · 输出 ¥1(v2.9.1 的紧凑写法¥0.02 / ¥0.04 / ¥1丢了字段名),取不到时显示「载入中…」/「未取到 · 点我重试」而不是光秃秃一个「—」。 - 单价那一格额外自报状态:class 上带
dset-sb-price-ok / -pending / -err / -noprice / -nomodel,于是「到底是没取到数据、还是没拿到当前模型、还是这一半根本没换新代码」可以从 DOM 一眼读出(排查用;旧版没有这个 class)。
v2.9.1 — 右下状态框重排 / 草图改为「作为图片注入」/ 修好常驻插件禁用不生效
三处都是用户实测反馈(「排版不美观(尤其是右下)」「不能在对话中直接注入 draft(作为图像)」「插件管理有问题」)。
1) 右下角状态框重做
- 收起态从「三行各自 flex 右对齐」改成两列网格:左列是右对齐的灰字标签(单价 / 余额 / MMS),右列是数值(
tabular-nums),行宽由 grid 自动对齐,不再一行宽一行窄、左缘犬牙交错。 - MMS 那行的长句收进
title,值只留「省钱中 / 关闭」;峰/谷徽标并入「单价」标签;余额与「本对话 ¥」同格呈现。 - 展开卡片(
.dset-statusbox-line那一套)保持不变。
2) 草图:从「让模型自己读文件」改成「直接作为图片注入」
- 旧行为:当前模型不支持图片时,草图落盘成 PNG 并把路径插进输入框 —— 模型得自己调
read_image去读(用户明确不接受:「这玩意居然需要模型自己调工具从文件夹读」)。 - 新行为:点「导入」时
- 当前模型就收图 → 直接作为图片附件放进输入框;
- 不收图但有视觉模型 → 先自动切到一个可用的视觉模型(新端点
dltVisionRoutes:Host 逐个resolveModelInfo筛inputModalities含image的,同 provider + 名字带 vision 的优先),再作为图片附件注入;导入后在按钮旁给一个「切回 <原模型>」(改的是会话路由,不动档位/偏好); - 一个视觉模型都没有 → 才退回「存 PNG + 插路径」,并如实说明原因。
- 主按钮如实写明这次要做什么:「导入为图片」/「切到视觉模型并导入」/「存 PNG 并插入路径」;切模型失败就报错,不再偷偷降级成插路径(旧代码的失败分支还会谎报「路径已插进输入框」)。
3) 插件管理:修「在 DET 里禁用常驻插件,重启后又活了」
三个真实缺陷一起修,并新增一个不用重启的「立即应用」入口:
- 名字对不上(禁用永远落不到条目上):profile 的
insert行有两套名字 ——id: dbs/name: 'dsh-bgm-service'(包名)。旧代码只按entry.options.name匹配,于是库里moduleName: 'dbs'的记录永远找不到 loader 条目,_applyPersistentPermanentStates直接continue。新增matchLoaderEntry()按options.name → options.id → options.module → entry.id → id 末段依次匹配(外加大小写不敏感兜底),并把记录里的moduleName就地修成 loader 那一套。 - 意图被事实覆盖(档位说禁用、却永远不会禁):
gpList以前拿 loader 的实际状态回写globallyEnabled,把用户「禁用」的意图抹平。现在permanentIntent()让档位成为唯一意图来源 ——level=disabled要停、level=always要开(_disableAllGlobalPlugins因总开关/兜底写下的globallyEnabled=false只是记账,不再被当成意图)、其余档位才看globallyEnabled;gpSetPermanentEnabled()改为同时写档位,二分开关照样表达意图。 - 启动时应用从未执行(死锁):
loader.await()会等 loader 树里所有条目的 init/inertia 任务,其中就包括 DET 自己这一条 —— 从一个插件的apply()里 await 自己必然永不返回。实测后果:tables.boot.lastBootAt停在 9 天前,禁用状态一次都没应用上。改成「延迟 3 秒 + 首个会话出现」两脚触发,并在总开关重新打开时再应用一次(总开关关闭期间一件不碰,保持「完全原生」口径)。
- 新增端点
gpApplyPermanentStates+ 插件管理页右上角「立即应用禁用状态」按钮:不必重启 DSH 就能把「档位=禁用」的常驻插件当场卸下,逐条回报「已应用 / 修正记录 / 失败 / 跳过 / 总开关关闭」。 - 列表更诚实:新增「意图与事实相反」提示(档位说禁用但仍在运行 → 明确指出点哪个按钮),启用/禁用两个快捷档位按意图高亮而不是按当前装载状态。
测试
- 新增
tests/gp-state.test.mjs(35 条):四种名字的匹配(含 dbs 的dbsvsdsh-bgm-service别名)、容器/分组条目不参与、意图推导(档位优先 + 记账值不算数)、应用函数把意图落到 loader、幂等、总开关关闭时一件不碰、loader.update失败如实进 failed、容器条目永不碰、记录自愈(moduleName + globallyEnabled)。 - 全量:unit 128 · host 60 · adapt 23 · client-structure 47 · client-load 17 · pause 10 · dlt-host 37 · dlt-integration 22 · gp-state 35。
v2.9.0 — DLT 集成:把 DLT 的全部功能搬进 DET(DLT 本体整体停摆)
一个插件干完两个插件的事:DLT(dsh-light-tool)的全部能力现在由 DET 自己提供,DET 总开关打开时 DLT 整体停摆。
自带引擎,不靠 DLT 在场
- 新增
lib/dlt-engine.js(DLT 纯逻辑层lib/core.js的副本:环境表 / 进程执行 / Python 文档引擎 / Office COM / Edge 无头 / 官网定价解析)与随包资产py/dlt_docs.py、py/dlt_office.ps1。DLT 卸载后 DET 依旧完整可用 —— 这是「DLT 可彻底停摆甚至卸载」的前提。 package.json的files补上py,并把dsh-typert-protocol/schemastery之外的依赖按现状(经 profile 解析的@deepseek-ai/dsh-tools)沿用。
六个模型工具 + 系统提示(同名)
- 新增
lib/dlt-tools.js,注册与 DLT 完全相同的六个工具:dlt_env/dlt_run/dlt_build/dlt_doc_read/dlt_doc_write/dlt_doc_convert,并注入同名的tool:dlt系统提示段落。模型侧的习惯、提示词、既有文档与测试都不用改 —— 变的只是「谁提供」。 - 六个 DET 功能键逐个开关:
dltDocs/dltPreview/dltEnv/dltRun/dltCost/dltDraft(默认全开),归在管理器新增的 **「DLT...
DSHEssentialTools v2.7.0
Changelog
v2.7.0 — 可安装的 bundle(manifest 完整化)
- 新增
dsh.bundle.patch:package.json的dsh节除client外,现在声明bundle.patch → ./cordis.patch.yml,并在仓库根补上了对应的cordis.patch.yml。在此之前 DET 只声明dsh.client,而单有dsh.client并不构成可安装的 bundle:dsh plugin add装不上,各插件收录库的静态校验会直接判invalid—— 中心 Registry 的rejected.json里 DET 的理由正是package.json does not declare a safe dsh.bundle.patch。 - 一条命令安装:
dsh plugin --profile web add dsh-essential-tools或dsh plugin --profile web add github:LLYlab/DSHEssentialTools。装完自动并入当前 profile 的 bundle 层,不再需要手工往cordis.patch.yml里插insert块(从源码挂载仍然可以那样做)。 cordis.patch.yml故意不写config:所有键都有中性默认值(路径为空串 / Debug / x64 / bootFailLimit=2),所以干净 profile 装上去就能直接启动,不带入任何个人或机器路径;要接自己的工程,在自己的 patch 层里按id覆盖(config是整块替换,不是逐字段深合并)。- README:Quick start 增加 Option C(GitHub 安装),并说明 bundle patch 会自动注册。
- 功能无变化:未改动任何
lib/代码,本次是打包/安装契约的修复。
v2.6.0 — 停用范围收窄到「DET 全局插件库」;DLT 这类插件不再被误伤
v2.5.2 为了让
topo也能被停用,把范围扩大成「loader 里所有非框架、非 DET 自身的常驻插件」。这个盲扫踩了两个坑:①cordis:include—— 它是持有整份cordis.yml子树的容器(dbs/dlt/topo的 loader id 都是它的孩子include:xxx),停它等于去拆整棵 profile 插件树,include 那侧随后按配置重建孩子,刚停掉的插件又被拉起来 —— 实测就是「关了 DET,dbs / topo 照旧在跑」;②把不属于 DET 的插件(如dlt—— 它有自己的「DLT 管理器」总开关)也一起停了。本版把范围收回到 DET 自己的库,并把「停没停」变成可核对的事实。
停用范围 = DET 全局插件库(不再盲扫 loader)
- 只遍历
global.list()(即「全局插件管理」里那份库)。不在库里的常驻插件(如dlt)永不碰 —— 它们各有自己的开关。 - 库里一条记录要能被停,必须绑定到 loader 条目(记录里有
moduleName,由「全局插件管理 → 扫描已安装插件 → 纳入」写入)。 - 容器/内置永不入列:拥有子树(
entry.subtree)或名字以cordis:开头的条目(如cordis:include)在停用与恢复两条路径上都被跳过 —— 既修掉「停不干净」,也排除拆整棵插件树的风险。 - 库里声称常驻、但绑定不上的记录,不再靠猜名字去停别人的插件:写进
det.master.unmanaged,设置页如实列「没被停用 + 原因(去『纳入』绑定)」。gpMasterState新增unmanaged/unmanagedCount。
停用后核对,不再「写下就算停」
_verifyPause:逐条回读 loader 的真实状态(entry.disabled),没停的补一次(250ms 后重试),仍没停就记applied: false+ 可读原因。快照det.master.paused现在带applied,页面区分「已确认停用 N / ⚠ 未能停用 M(+原因)」,并提供一键刷新页面 —— 入口被停用后该插件的 client 包不再下发,刷新即彻底干净。- 恢复侧同样只认快照:
_resumeManagedPlugins跳过旧快照里残留的容器条目(skipped),不会去替 loader 开关容器。
修复
global.upsert:「纳入」(code 传空)不再用空值覆盖库里记录原有的host/client动态代码。_pauseItemState:只要记录绑定了 loader 条目就以 loader 为准 —— 此前非permanent的绑定记录会被错判成「会话实例已停」。
测试
- 新增
tests/pause.test.mjs(假 loader / 假存储驱动真实方法,10 条):容器不入列、DLT 全程不被 update、绑定不上的进 unmanaged、停不下来的补刀并如实上报、旧快照恢复不碰容器。跑法见docs/DET发布.md。
v2.5.2 — 总开关的停用范围补全:随 DSH 常驻、但未纳入库的插件(dbs / topo)
2.5.1 的总开关只停了「全局插件库里有记录」的插件。本机实测发现:真正在跑的
topo是随 DSH 常驻装载、未纳入库的插件(loader 里是include:topo),库中只有一个动态副本gp-topo—— 于是关掉总开关后topo仍在跑(topo_ds_ask仍可用)。本版把这类插件也纳入停用范围。
- 停用范围 = DET 管理范围内的全部插件:
- 全局插件库里当时真的开着的插件(常驻按 loader 实际状态判断,动态看会话实例);
- 外加 loader 里所有非框架(
@deepseek-ai/*除外)、非 DET 自身、且当前未禁用的常驻插件 —— 也就是 DET「已安装插件」里能看到的那些(如topo)。
- 恢复不变:两类都写进快照
det.master.paused(库外常驻记录kind: "installed"+moduleName),重新打开总开关时按快照重新装载 / 还原会话记录;本来就没开的插件不进快照。重复关闭(含关着总开关重启)不会覆盖已有快照;DET 自己与框架组件永不被动。 - 关闭态可核对:DET 管理器列出的清单会区分「常驻已安装 · 已卸载」与「常驻 · 已卸载 / 会话实例已停」。
- 提醒:被 loader 卸载的常驻插件,其前端界面要等一次页面刷新(强刷)才会消失;后端能力(如
topo_ds_ask)立即失效。
v2.5.1 — 指示状态=实际状态;总开关关闭时一并停用 DET 管控的插件
2.5.0 的总开关只卸下了 DET 自己的注入,DET 管控的插件(dbs / topo 等)仍在跑;全局插件管理的指示也仍以「记录」为准。本版把这两件事补全。
总开关关闭 = 真的完全原生
- 一并停用 DET 管控的插件:常驻插件经
loader.update(disabled:true)实时卸载(其界面/扩展通道随之失效),动态插件停掉各会话实例并清空启用映射。 - 可逆:关闭前把「当时真正开着的」记成快照(
det.master.paused),重新打开时按快照恢复 —— 常驻插件重新装载、会话启用记录还原(客户端gpSync据此在对应会话重新启用)。本来就没开的插件不进快照,避免恢复时凭空打开。 - 快照不会被清空:重复关闭(含「关着总开关重启」)时,新结果为空就保留上一份,恢复信息不丢。
- 绝不碰自己:
_disableAllGlobalPlugins明确跳过 DET 管理器自身。 - 可核对:DET 管理器在关闭态会列出「已随总开关停用的 DET 管控插件」(常驻已卸载 / 保留的会话记录数);新增端点
gpMasterState。
指示状态 = 实际状态(全局插件管理)
- 常驻插件以 loader 为唯一事实源:记录里的
globallyEnabled只是 DET 的期望值,真正决定「装没装」的是宿主 loader 的entry.disabled。gpList现在按 loader 核对,不一致时自动回写纠正并在卡片上标注;同时显示fiber阶段(装载失败 / 进行中)。 - 会话状态区分「记录」与「在跑」:徽标改为按实际
running渲染 —— 「本会话运行中」/「已启用记录 · 未运行」/「本会话未打开 · 打开后恢复」;记录与运行清单不符且超过 15 秒才标stale,避免刚启用的一瞬间误报。 - 对话内一致:
det_global_plugin_list的工具文本同样报告实际状态(常驻按 loader、动态按是否有活跃运行),不再只报「档位 + 记录」。
修复
- 会话登记簿自检会登记一条
id=""的幽灵会话(自检报告里出现added: [""],下一轮又被当失联删除,来回抖动)。原因是个别SessionHeader缺id;现在无 id 的头一律跳过,reconcileRegistry再做一层防御。
v2.5.0 — 总开关:一键「完全原生」
DET 管理器顶部新增总开关(
det.features.master,默认开)。它是「DET 扩展 ↔ 完全原生 DSH」的唯一开关:关闭后,DET 对 DSH 的全部改动只剩「设置 → DET 管理器」这一页与其中的这个总开关本身,其余一律卸下且可随时恢复。
- 关闭总开关时(宿主侧):释放全部 DET 注入 ——
det_*/web_*模型工具(全局插件管理、TCT、CDM、MDA、浏览器、网络工具共 24 个)、系统提示注入(全局插件说明 / 网络权限档位 / MMS)、tools/pre-execute安全审计监听、本地浏览器桥(WS server,已连接的扩展会断开)、会话侧边栏登记簿的自动登记与节流自检,以及启动健康兜底(不再自动禁用其它插件);MDA 分组模式复位为native。 - 关闭总开关时(客户端侧):撤销全部界面注入 —— 右侧 ▶🗎🕘🧩🛡 工具栏、右下角余额/花费/MMS 状态框、输入框网络权限控件、VTD 对话标签与消息操作、MDA 侧栏浮层、设置页的「全局插件管理」与「MDA 分组」两个条目;同时停止余额轮询、全局插件自动启用同步与
ai-auto自动批准,并清掉写在<html>上的定位/占位 CSS 变量与属性。 - 保留:typert 管理端点与「设置 → DET 管理器」设置页(总开关关闭后它是重新打开 DET 的唯一切入点),总开关在页面上始终可见。
- 可逆且即时:开关持久化在
det.features,改动即刻生效、无需重启;关闭时的操作全部登记在 disposer 表里,重新打开会完整恢复(分项开关的持久化取值原样保留)。宿主侧自检:24 个工具 + 2 段系统提示 → 0 → 24/2。 - 默认值:沿用升级前的状态(
master默认true),不会改变现有用户的用法。
v2.4.1 — 浏览器桥稳定性 + 安全加固 + README / 用户指导手册
- 浏览器桥只保留最新连接:扩展重载 / MV3 Service Worker 重启后旧 socket 可能残留(
close未触发),此时命令会发到死连接上,表现为间歇性browser-timeout(实测见过 6 条残留)。新连接建立时清掉更早的连接,_firstSocket改为取最后加入的一条。 - 扩展档位切换经 background RPC:弹窗 / 选项页不再直接写
chrome.storage,而是chrome.runtime.sendMessage({type:'setMode'})→ 同时持久化 + 更新徽标 + 通知宿主。修掉「改成『启用』后det_browser仍报ext-mode-off」(宿主收不到通知,继续按旧档位拒绝)。 - 安全:
_fetch在 DNS 解析后再判一次私网(防 DNS rebinding);_fetch/ 余额 / 单价统一 15s 超时(此前一个不响应的主机能把端点挂死);读到响应体前先看Content-Length,避免超大响应整体进内存。 - 加固:
cmd.exe调用的mkdir/rmdir路径整体加引号——未加引号时空格会被拆成多参数,& | ^等元字符会被解释执行(命令注入)。id 已白名单化,仍补引号保护。 - 扫描器误报:
exec(/child_process规则加词边界(\b),browserExec(不再被误报为进程执行。 - 修复:inventory 行的
packages可能缺失(不同 DSH 版本),复用动态插件时加容错,避免 TypeError;GitHub 商店搜索结果的name/verificationStatus归一化,否则模型工具的 output schema 会拿到undefined被判为非法 JSON。 - 余额刷新更跟手:TTL 8s → 4s,配合前端 5s 轮询。
- 文档:重写
README.md(价值主张 / 亮点 / 快速开始 / 文档索引),新增面向使用者的 docs/GUIDE.md 用户指导手册(安装、上手、逐功能详解、权限模型、扩展装载、FAQ、故障排查、卸载回滚、速查表)。 - 发布:GitHub
main= tagv2.4.1(Release 资产install.ps1/README.md/GUIDE.md)+ npmdsh-essential-tools@2.4.1(latest)。新增publish-otp.ps1(非交互环境用 OTP 一次完成发布 + 校验)。
v2.4.0 — 浏览器控制(DET → 你已登录的浏览器,扩展方案)
- DSH 控制扩展(
browser-extension/):MV3 浏览器扩展(Chrome/Edge),通过本地 WebSocket 与 DSH 宿主通信;四档开关(关闭/只读/只写/启用)由用户在扩展弹窗控制;DSH 只读、可调用。 - 本地 WebSocket 桥(
lib/browser.js):DSH 宿主启动仅绑127.0.0.1:9123的 WS server,路由扩展命令;握手校验 Origin,防跨源。 - det_browser 模型工具:read_text / read_dom / screenshot / get_url / get_title / navigate / click / fill / run。只读/只写模式门禁在扩展端强制;「只写」不回传页面内容。
- 网络权限第4档(使用用户浏览器)= 启用浏览器控制:低于该档 DET 拦截;扩展未连接/未开启时报错。
- 高危审批:非 Full access 模式下,浏览器动作(读写)经
tools/pre-execute{kind:'ask'}走产品审批;Full access 免审。 - 客户端:DET 管理器新增「浏览器控制」状态块(Web权限档位/桥运行/扩展连接/模式)。
v2.3.4 — VTD 分叉流式 + 对话对齐产品样式
- 分叉也要流式:VTD 对话页签不再固定 4s 轮询。宿主
treeView新增generating(open-turn)信号;客户端据此在生成中高频刷新(≈700ms)、空闲低频(≈2.5s),并带生长检测兜底(宿主未更新时仍能流式)。生成中自动滚到底部、显示「正在生成…」提示。 - 更像标准对话:去掉普通用户/助手消息上的「用户 #seq / 助手 #seq」噪音标签,渲染与产品聊天更一致;保留分叉
<N>选择器与编辑/重试等 VTD 特有能力。
v2.3.3 — 关闭开关的副作用:插件管理关=自动禁用所有插件;MDA 关=回到原生
- 在 DET 管理器关闭「插件管理」时,自动禁用所有全局插件(停各会话实例;常驻插件置为禁用并实时卸载)。
- 关闭「MDA 分组」时,自动把 MDA 分组模式重置为原生(native)(不再残留 workspace/model),让 DET 近乎完全关闭、回到净版 DSH。
v2.3.1 — DET 功能开关新增「MDA 分组 / 插件管理」,可近乎完全关闭
DET 管理器新增两个开关键,把「插件管理」与「MDA 分组」也纳入开关——关掉后对应 UI 不再渲染,让 DET 几乎完全关闭。开关即时生效并持久化。
- 新开关:插件管理(工具栏「🧩插件」按钮/当前对话插件控制 + 全局插件管理)、MDA 分组(左侧栏分组入口 + 分组树/模型合作)。
- 关闭后:工具栏「插件」按钮消失、左侧栏「MDA 分组」入口消失、设置里对应区块显示"已关闭";DET 管理器始终保留以便重新开启。
v2.3.0 — MDA(混合同对话)分层 · CDM 跨对话记忆 · TCT 临时对话
DET 第二轮重大优化:MDA(Mixing Dialogue Agent)旨在更好地管理对话、降低成本。新增设置「MDA 分组」(仿「外观」三选一:原生/工作区组/模型组)、跨对话记忆 CDM、一次性临时对话 TCT,以及插件管理 UI 精简。
✨ 新增
- MDA 分层:设置新增「MDA 分组」(仿「外观」,三选一单选 + 图标):
- 原生分组(默认,行为与现在一致);工作区组(按 工作区→分支模型区域→会话 分组,区域内共享 CDM 记忆 + 插件清单);模型组(同工作区组,并允许模型合作)。
- 主视图左侧栏底部「🔀 MDA 分组」入口:工作区/区域/会话分组树(可折叠),工作区「+」建分组+新对话,区域「+」组内新对话。
- CDM(CrossDialogueMemory):跨对话读取/搜索对话段——
cdm_list/cdm_search(默认限定当前工作区;cross提权可跨工作区)/cdm_read。 - TCT(Temp Chat Tool):一次性临时对话——
det_tct(简短 prompt + 可选 preset + 权限控制 → 单段 feedback → 会话即焚、无持久化);DET 设置内可选 TCT 模型。 - 模型合作/介绍:
mda_card(用 TCT 生成模型介绍)、mda_activate(激活其它模型;⚠ 耗提示词,不鼓励)、mda_list_areas。
🧹 UI 精简
- 插件卡片底部改为小图标(✎ 编辑打开内联代码工作区 / 🔍 AI 安全审查 / 🗑 删除),删除「本会话停用」;设置页 DET 区块可折叠;新增高性能安全审查提示词。
🐛 修复 / 加固
- 全局插件库读取崩溃(存储域读表
entries())、并发首开竞态(opening防重入)。 - 常驻插件(如 DBS)全局「启用/禁用」二分开关(经
loader.update实时卸载/装载、跨重启持久化、切换后自动刷新前端)。 - 工具栏面板渲染改为兄弟节点(fragment),修复挤压/闪动;分组树支持折叠;移除与原生的单插槽冲突。
v2.2.0 — 更贴合原生 DSH 风格 + 简洁界面 · 全局插件管理强化
主打「更贴近原生 DSH 的观感与交互」与「更简洁的界面」:面板、标签、工具栏对齐 DSH 原生设计语言,常用操作收拢到一处。
✨ 改进与亮点
- 更贴合原生 DSH 风格
- 全局插件管理面板、VTD 对话页签、右侧工具栏统一对齐 DSH 原生层级/色彩变量/折叠与卡片语言,观感一致。
- 更简洁的界面
- 面板去重、操作收拢;常驻插件改为「启用/禁用」二分开关,替代繁琐五档;
- 扫描 / 纳入 / 刷新在各自标签内一键完成,减少跳转。
- 全局插件管理强化
- 修复全局插件库读取崩溃(
dsh_global_plugins域读表由.all()改为entries())与并发首开竞态(opening防重入); - 扫描已安装插件:列出随 DSH 常驻的永久宿主插件(自动排除 DET 管理器本身),可一键纳入全局插件库(
gpScanInstalled/gpImportInstalled/det_global_plugin_scan_installed/det_global_plugin_import_installed); - 常驻插件二分开关:对 DBS 这类跨会话插件做「启用/禁用」,实时经 Loader 卸载/装载、
globallyEnabled持久化、重启后再应用;切换后自动刷新前端让插件 UI 消失/重现(gpSetPermanentEnabled/det_global_plugin_set_enabled); - 两种从 GitHub 安装插件的方式:
- 直接下载(
det_global_plugin_github_direct/gpGithubDirect):按约定格式(dsh-plugin.json或plugin/host.js+plugin/client.js)拉取并入库,返回可疑代码扫描警告; - AI 读取源码自行编写(
det_global_plugin_github_rebuild→det_global_plugin_github_save):拉取 README 与源码、注入「病毒/漏洞检查上下文」供 AI 审查后自行编写等价版本入库——不直接执行第三方代码。
- 直接下载(
- 修复全局插件库读取崩溃(
🐛 修复
- 全局插件库列表读取崩溃(
.all is not a function)。 - 全局插件库并发首次打开时的
already-open竞态。
🔒 安全
- 全链路 SSRF 防护 + 可疑代码静态扫描(`scanC...