dsh-memo
给 DSH(DeepSeek Harness)加一组按需查备忘录的工具,外加一个会围着书架转圈、抽书查看的小动画。
一句话:把一份 19 万字符的 MEMO.md 切成几百段,每次只取需要的那一段——而不是把整份读进上下文。
它解决什么问题
AI 助手在长期工作中会攒出一份越来越大的备忘录(本机的 MEMO.md 已经 19 万字符 ≈ 3 万 token)。
每次要查"那个端口是多少",如果把整份读进去,一次就烧掉 3 万 token,而且大部分内容跟当前问题无关。
这个插件把备忘录做成可按需索引的知识库:
- 想找东西 → 先看目录(约 600 token),再取那一节(几百到几千 token)
- 想全局扫一眼 → 取「精简版」(约为原文 1/5)
- 每次查询省了多少,右上角面板实时显示
安装
DSH 插件的安装位置有三个,缺一个都不生效:
$profile = "$env:USERPROFILE\.dsh\profiles\desktop"
$src = "C:\path\to\dsh-memo" # 你解压出来的目录
# 1) 依赖:在 profile 的 package.json 里加一行
# "dsh-memo": "link:<上面那个 $src>"
# 2) 启动清单:同文件的 dsh.profile.bundles 数组里加 "dsh-memo"
# 3) 软链:
cmd /c mklink /J "$profile\node_modules\dsh-memo" "$src"
然后关掉 DSH 窗口重新打开(client 插件不热重载)。
仓库里的 scripts/install-local.mjs 可以自动做上面三步:
node scripts/install-local.mjs --profile desktop
工具(模型用)
| 工具 | 用途 | 典型消耗 |
|---|---|---|
memo_outline |
目录:节号 / 标题 / 行号 / 字符数 / 估算 token | ~600 tok |
memo_section |
按节号取原文(0.2、3.6、5 …) |
0.5~4k tok |
memo_search |
关键词或正则搜索,带上下文与所属节 | ~300 tok |
memo_project |
按项目名取整张卡(支持中文名与别名) | 1~4k tok |
memo_quick |
速查:machine / ports / interpreters / dirs / commands | ~150 tok |
memo_status |
§6 当前状态表 | ~500 tok |
memo_playbook |
§5 里的可复用套路 | ~300 tok |
memo_append |
追加内容(自动备份 + 读回校验 + 失败回滚) | 一行确认 |
memo_lite |
取精简版(全局速览) | 1~3k tok |
memo_compact |
生成精简版 MEMO.lite.md(原文件一字不动) |
一行确认 |
面板(人用)
右上角一个书签按钮,点开是面板:
- 备忘录规模(字符 / 行 / 节 / 项目卡)
- 本次运行统计:查询次数、累计返回字符、比整份读省了多少、精简版省了多少
- 最近查询:时间 / 工具 / 参数 / 返回大小 / 节省比例(失败标红)
- 分类:按用途分组(速查 / 环境与坑 / 项目 / 套路 / 状态 / 专题),组可折叠,点任意节就地预览
- 搜索框:自己搜备忘录
- 精简:点按钮 → 确认 → 生成
MEMO.lite.md
原理
1. 索引:把 Markdown 切成节
lib/memo-core.js 只做一件事:把 MEMO.md 解析成「节」的数组。
- 只认
##和###两级标题(#当文件标题) - 每节的区间 = 本节标题行 → 下一个「同级或更高级」标题之前
- 节号从标题前缀提取:
## 3.6 QQ 机器人→ id3.6;没有数字前缀的用h<行号> - 每次查询前先
stat比对mtime和size,文件没变就复用上次的索引
所有查询共用这个索引,返回统一的 { text, chars, tokens }。
2. 为什么能省 token
| 操作 | 返回量 |
|---|---|
| 整份读 | ~191,000 字符 |
memo_outline |
~2,000 字符 |
memo_section 0.2 |
880 字符 |
memo_quick ports |
880 字符 |
一次「查端口」从 19 万字符降到 880,省 99.5%。
3. 分类:按节号前缀归类
规则只看节号前缀,不看标题文字,所以换一份备忘录只要节号约定相同就自动生效:
0.x → 速查
1 / 2 / 4 / 7 → 环境与坑
3 / 3.x → 项目
5 → 套路
6 → 状态
其余 → 专题
⚠ 一个坑:父节的字符数包含全部子节(§3 的 range 覆盖了 3.1~3.18)。
统计总数时只能累加叶子节,否则数目会翻倍——第一次实现就是这么错的。
4. 精简备忘录:逐行打分
不是"删东西",而是另存一份高密度版,原文件一个字不动。
对每一行按「信息密度」打分:
| 特征 | 分数 |
|---|---|
| 标题行 | 必留(100) |
表格行 | ... | |
45 |
含路径(C:\ / ~/ / /api/ / 文件扩展名) |
42 |
含端口 / 127.0.0.1 / localhost |
36 |
| 含命令(pip / npm / git / curl / pwsh …) | 30 |
| 含「坑 / 踩过 / 注意 / 不要 / 禁止 / 必须 / 铁律」 | 30 |
| 列表项 | 22 |
| 又长又没有信号(>160 字且分数 <25) | 0 —— 直接丢 |
然后按节的预算截断:
预算 = max(280, 节字符数 × ratio) // ratio 默认 0.16
短节(<700 字符)整段保留,不值得压
代码块只留前 3 行
超出预算时从最低分行开始丢
⚠ 第二个坑:父节只输出标题,正文交给子节各自压缩。
否则 §3 会把所有 3.x 再压一遍,同一段内容在精简版里出现两次(第一次实现输出 6.4 万字符、内容重复)。
实测(19 万字符的备忘录):191,790 → 36,053 字符,省 81%,86 个章节标题全部保留。
产物落在同目录的 MEMO.lite.md,随时可以重新生成。
5. 书架动画
分两层,主方案是预渲染视频,兜底才是实时绘制。
5.1 场景设计
- 三个双面书架摆成等边三角形(边长 2.45,重心在原点),每个书架朝外转,中间留出空地
- 每层 8 本书,书是有厚度的三维盒子(前面 / 左面 / 右面三个面),高矮胖瘦用确定性伪随机,正反两面都摆满
- 相机绕 Y 轴 8 秒转满一圈,同时抬高 2.5 米(约 25° 俯视),所以三角形的三个顶点分得开
- 每转 90° 抽一本书:那本书从它自己的槽位离开(架上当场留一个洞),朝镜头飞 2.8 米、边飞边自转一圈、体积放大到 2.2 倍,然后沿原路落回同一个洞
5.2 为什么落地看不出接缝
架上书和飞行书用同一个绘制函数、同一组尺寸与颜色:
u=0 飞行书 = 原尺寸,位置 = 架上位置 → 与架上那本完全重合
u=1 飞行书 = 原尺寸,位置 = 架上位置 → 落回原位
中间 放大 1 + sin(πu)×1.15 倍,飞到镜头前
所以「动画放完突然补一本书上去」这种事不会发生——它从来没有消失过,只是离开了原位。
早期版本踩过两次:
- 飞行书单独画成亮边框的盒子 → 落地瞬间换皮,像凭空补一本
- 书架上不画书(只有格子)→ 抽出来就是凭空出现
5.3 怎么渲染成视频
用 Remotion 逐帧渲染成 VP9 + alpha 透明的 webm:
# 透明版(给插件用)
npx remotion render MemoShelf out/memo-shelf.webm `
--codec=vp9 --pixel-format=yuva420p --image-format=png --crf=45
# 带界面底图的演示片(给人看)
npx remotion render MemoShelfButton out/demo.mp4 --codec=h264
--pixel-format=yuva420p必须有,否则没有 alpha 通道--crf=45把 8 秒 1080p 压到 5~7 MB(不带这个参数会到 15 MB)- 8 秒正好转满一圈,首尾角度一致 → 可以无缝循环播放
5.4 触发与播放
host 侧:每次工具调用 → stats.calls++ → 写 query-log.jsonl
client 侧:每 1.4 秒拉 /api/memo/ping.json
发现 calls 变大 → 按钮加 playing 类 → 播视频
播满 8 秒没有新查询 → 收回成书签
- 动画不是全屏浮层,而是右上角书签按钮自己从 34×34 展开成 200×200 的圆角小窗(CSS transition)
- 视频是按钮的子元素(
position:absolute; inset:0; object-fit:cover; border-radius:inherit),所以展开收起时位置自动跟随 - 1600×900 的画面塞进正方形窗口时居中裁切
5.5 Canvas 兜底
如果视频加载失败(没打包 / 解码失败 / 浏览器不支持 alpha),lib/client.js 里的 Canvas 场景会用同一套投影数学实时画一遍:
- 同样的三角函数投影(绕 Y 轴旋转 + 25° 俯仰 + 透视)
- 同样的三角形布局与抽书逻辑
- 尺寸固定 200×200,画在按钮里
兜底版比视频版朴素一些(没有辉光滤镜),但布局和动作一致。
效果一致性(装好就是视频里那样)
| 保证项 | 做法 |
|---|---|
| 动画画面 | 视频文件打包在 assets/memo-shelf.webm,不是运行时现算的,安装即带 |
| 动画参数 | 全硬编码在源码里,不读配置文件,不会被环境改动 |
| 摆放与节奏 | 三角形、绕圈速度、抽书间隔都是常量 |
| 兜底 | 视频不可用时用 Canvas 画同一套场景,不会变成"什么都没有" |
| 备忘录路径 | file 留空时自动用 ~/.dsh/MEMO.md,换机器不用改 |
唯一的运行时依赖是 Chromium 内核(DSH 桌面端是 Electron,天然支持 VP9 alpha webm)。 如果你的宿主不是 Chromium,动画会退到 Canvas 兜底版。
配置
cordis.patch.yml:
- insert:
- id: memo
name: 'dsh-memo'
config:
# file: '' # 留空 = 自动取 ~/.dsh/MEMO.md
tokenChars: 3.2 # token 估算系数(中英混排经验值,只用于展示)
自测
node scripts/selftest-core.mjs # 解析 / 检索 / 追加 / 分类 / 精简,41 项
node scripts/preview/screenshot.mjs # 用无头 Edge 截图面板(离线,不连 DSH)
scripts/preview/index.html 加载的是真实的 lib/client.js,只是用假宿主和假 /api 响应,
所以它能当 UI 的离线回归测试用:
#cats 截分类面板、#confirm 截精简确认层、#anim 截按钮动画。
边界
- 只依赖 DSH 提供的
tools、webServer、slots服务,不 spawn 任何外部进程,不需要 Python / CLI / 本地服务 webServer缺失时工具照常可用,只是没有面板和动画- 面板端点只放行本机请求(
127.0.0.1/localhost) - 精简版和备份都写在备忘录同目录:
MEMO.lite.md和memo-backups/
License
MIT
No comments yet. Be the first to write one.