Skip to content

dsh-plugin-tlmemory

Verified

dsh-plugin-tlmemory · v0.8.1 · MIT · Web UI

Cross-session and cross-project memory manager for dsh (silent turn-end sedimentation + native web client embedding)

Install

dsh plugin add dsh-plugin-tlmemory

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

Readme

dsh-plugin-tlmemory

DeepSeek Harness (DSH) 原生无感记忆与自省管理插件。

为 DeepSeek Harness 提供会话级静默记忆提炼持久化、SQLite FTS5 全文检索,以及与 DSH 官方 Shell(如 SSH、任务看板)100% 深度融合的一级主视口记忆看板。


✨ 核心特性

  • 无感静默沉淀:基于 DSH 官方 session/event 契约监听 turn/end (completed) 事件,静默后台提炼关键工程结论、用户偏好与决策规则,不抢占推理上下文。
  • 全局一级视口:与 DSH 内置「SSH / 任务看板」同级,常驻左侧导航栏核心区,一键切换主视口并支持 ‹ 返回会话 快捷回退。
  • 多工程记忆选择(按 DSH 工作区对齐):顶部下拉框列出所有有记忆数据的工程,并把不可读的 repo:<hash> 反解为可读工程名,同时标注所属工作区(工程名 [工作区名] (N条)),切换即换树。归属判定对存量数据友好:同一目录的历史 hash 变体(盘符大小写、正/反斜杠写法差异)全部认账,工程名与某个合法工作区同名的存量工程也自动对齐到该工作区。
  • 有记忆的工程绝不隐藏 / 绝不剔除(硬性铁律):工作区白名单只用来清理没有任何节点的空壳登记(例如以用户主目录启动而临时生成的空工程、宿主已删除的历史废弃目录)。只要某个工程在库里还存着节点,无论它的 scope 是否出现在 ~/.dsh/storages/workspace.json 的名单里,都照常保留并展示 —— 白名单永远不会成为「隐藏或删除用户记忆」的理由。
  • 零记忆工程自动清理 / 工程名全局唯一:没有任何记忆文件的工程(含遗留的空目录骨架)会被自动删除,仅豁免宿主当前正在打开的合法工作区 —— 它零记忆时也常驻下拉框(显示为 工程名 (0)),选中即进入「暂无记忆,点击上方「+ 新建记忆」开始沉淀」的空状态,作为「当前工程就绪、可随时沉淀」的心智锚点;同名工程(不同路径的同名仓库、目录改名遗留的旧作用域)自动追加 scope 短标识收敛为唯一名,手工重命名撞名时明确报错。
  • 作用域读取容错:请求一个不存在或已失效的工程时,GET /api/nodes 回 HTTP 200 + 空记忆树 + 可读提示(而不是 404),看板按空态渲染;下拉框在任何状态下都可以唤起,且选中的工程若不在当前清单里会自动回退到第一个有记忆的工程,不会出现「暂无工程记忆 + 下拉点不开」的死锁。
  • 树状图谱视图:与「📋 目录列表」一键互切的横向 / 竖向多叉树(工具条按钮一键反向、选择本地记忆),SVG 贝塞尔连线、拖拽平移与滚轮缩放、一键居中;检索时命中节点青色发光、整条父级路径连线加粗、未命中节点淡化,点击叶子直接唤起 Markdown 详情抽屉。默认横向:「同层兄弟」沿纵向铺开,每张卡只吃 52+22px(竖向要 208+22px),真实记忆树这种「宽而浅」的形状因此能放大 1.5~1.8 倍(实测同一作用域 25%→42%、37%→68%、49%→89%);「一键居中」另有可读保底(0.6)——装不下整棵树时不再缩成 25% 的一团灰,而是把根节点钉在左上角、由用户拖拽浏览局部。
  • 主题自适应穿透:采用透明通道透传 DSH 官方主题底色,深色/浅色皮肤实时自适应无缝融合。
  • Markdown 详情抽屉:记忆卡片列表仅展示关键简介,点击从右侧平滑滑出详情抽屉,支持安全沙箱渲染的完整 Markdown 阅读与一键复制。
  • SQLite FTS5 全文检索:底层持久化采用 SQLite FTS5 分词索引,支持毫秒级全文精确检索。
  • 受控删除(Agent 侧):新增 tlmemory_delete 工具 —— 沉淀错了的记忆模型可以直接删掉,但必须显式 confirm: true、只允许删叶子(目录级联删留给看板)、默认只能删当前工程作用域(删 global 或其它工程需显式 allow_cross_scope: true);tlmemory_query 的每条结果现已附带节点 id,模型据此精确定位,同名多条会返回候选而不会猜着删。
  • 反思提炼容错:模型先输出推理散文、或被 maxTokens 截断时不再整轮丢弃 —— 字符串感知的平衡扫描定位真正的 JSON 收尾、截断处回退到最后一个完整条目并补齐闭合括号,裸数组 / 单条对象 / 散文夹多个对象都能归一;解析彻底失败会同时写宿主 logger.warn 与 ~/.dsh/tlmemory-extract.log,不再无声吞掉。
  • 单一宿主自洽:通过 Cordis 补丁由 DSH Web 宿主自动托管 HTTP 服务(端口 4890),无需手动运行单独后台守护进程。

📦 安装方式

本包是 DSH 原生 bundle 形态:package.json 的 dsh.bundle.patch 指向包内自带的 cordis.patch.yml。被列进 profile 的 dsh.profile.bundles 即自动激活,不需要再往 profile 的 cordis.patch.yml 写任何挂载条目。

为什么必须是 bundle(0.7.0 的修复):桌面端(Electron carrier)与 @deepseek-ai/dsh-plugin-manager 只认 dsh.bundle.patch 这一条激活通道 —— 安装时会以 not-a-bundle(<包名> declares no dsh.bundle)直接拒绝, 归并时也只会把它当成「普通依赖」而不写进 dsh.profile.bundles。 0.6.x 及更早版本只声明 dsh.client,在桌面端根本无法被挂载。

为什么换掉 better-sqlite3(0.8.0 的修复):桌面宿主是用 Electron 自带的 Node 跑起来的(process.execPath + ELECTRON_RUN_AS_NODE=1,实测 Node v24.18.1 / NODE_MODULE_VERSION 149),而 pnpm 是在系统 Node(v24.12.0 / ABI 137)下编译 原生模块的 —— 于是宿主挂载插件时 dlopen 直接失败: was compiled against a different Node.js version using NODE_MODULE_VERSION 137. This version of Node.js requires NODE_MODULE_VERSION 149.; 插件激活不了、4890 端口没人监听、看板一直显示「服务未启动」。 0.8.0 起持久层改用 Node 内置的 node:sqlite(零原生依赖,跨 Node / Electron 稳定), 因此 运行需 Node >= 22.13(node:sqlite 自 v22.13.0 / v23.4.0 起不再需要 --experimental-sqlite),安装时也不再需要 allowBuilds 放行原生模块。

方式一:一条命令安装并自动挂载(推荐)

dsh plugin --profile web add dsh-plugin-tlmemory

dsh plugin add 会自动完成:装依赖 → 把包名归并进 dsh.profile.bundles(幂等)。 因为是 bundle 形态,不会再往 cordis.patch.yml 追加挂载条目;0.8.0 起包内已无原生 模块,安装过程不会再有 Ignored build scripts 需要放行。 另外 dsh plugin --profile web list 可查看挂载状态,dsh plugin --profile web remove <包名> 可一键卸载。

桌面端(DSH 桌面应用):profile desktop 由 Electron 应用独占管理, dsh --profile desktop … 与 dsh plugin --profile desktop … 都会被拒绝 (error: profile "desktop" is managed exclusively by the Electron application)。 请直接在桌面端的插件管理界面安装 / 启用 dsh-plugin-tlmemory —— 它同样只按 dsh.bundle 归类,因此 0.7.0 起可以被正常识别为可激活的插件层。

桌面端看板仍显示「服务未启动」?(0.8.1 的修复):桌面端主窗口文档来自 dsh-app://app/…(Electron 自有协议),渲染层 Origin 是 dsh-app://app,属非回环 HTTP。 0.8.0 及更早的 CORS 白名单只回写 127.0.0.1 / localhost,于是外壳对看板的跨源探针被 CORS policy 拦掉、读不到响应,被误判成离线 —— 但宿主侧其实已经起来了(可自行验证: curl http://127.0.0.1:4890/api/health 返回 {"ok":true,"service":"tlmemory"})。 0.8.1 起白名单一并放行 dsh-app: 协议;null / file: / 其它自定义协议仍然拒绝。 升级后请重启桌面应用。

--profile 必须紧跟 dsh plugin,且只能出现一次:dsh plugin --profile web add --profile=1 这类写法会显式报错并拒绝执行(旧版会静默丢弃参数、同时把整条命令改道到另一个 profile)。 命令的失败以稳定退出码分类:0 成功 / 1 用法或前置条件不满足 / 2 pnpm 缺失 / 3 清单非法 / 4 归并不一致 / 124 pnpm 超时,其余透传 pnpm;每条诊断行都带 dsh: diagnose: <code>: 前缀,便于脚本与 agent 直接匹配。

本层全局选项(任何子命令都可用,且不会转发给 pnpm):

选项 作用
--json stdout 只输出一行结果 JSON(phase / exitCode / pnpm / addedBundles / mounts / allowBuilds / diagnostics…),人类日志改走 stderr;也可用 DSH_PLUGIN_OUTPUT=json
--timeout <值> pnpm 超时(1500 / 30s / 5m),超时以 124 结束;也可用 DSH_PLUGIN_TIMEOUT
--yes 确认「用内置默认层栈首次创建无模板的同名 profile」;没有它时未知 profile 名的首次创建会被拒绝(避免手误留下半成品 profile)
--no-lock 跳过 profile 互斥锁

其它内建保障:profile 自己是 workspace root 时自动给 add/remove/update 补 -w; 会写盘的子命令默认持有 <profile>/.dsh-plugin.lock;回写 package.json 前先存 .bak-dsh-plugin-manifest-<时间戳> 并原子替换;pnpm 的输出被实时转发同时被捕获, 失败时给出分类后的诊断(adding-to-root / ignored-builds / fetch-404 / windows-file-locked / network …)。

方式二:手工安装(等价于方式一的三步)

进入你的 DSH profile 目录(例如 ~/.dsh/profiles/web):

cd ~/.dsh/profiles/web

# 从 npm 安装
pnpm add dsh-plugin-tlmemory

0.8.0 起持久层走 Node 内置的 node:sqlite,没有原生模块需要编译,也不需要放行 allowBuilds;请确保宿主 Node >= 22.13。装完直接把包名加进该 profile 的 package.json —— 只装依赖、不写这一行是不会生效的:

"dsh": { "profile": { "bundles": ["…", "dsh-plugin-tlmemory"] } }

⚙️ 配置

看板服务随宿主零配置自启,无需任何开关声明。想改配置就在自己的 profile 补丁层 ~/.dsh/profiles/web/cordis.patch.yml 里按 id 覆盖(写 - id: 覆写行,不要再写 - insert:):

- id: dsh-plugin-tlmemory
  config:
    serverPort: 4890           # 看板服务端口(默认 4890)
    maxRecallCount: 5          # 单轮最多注入系统提示词的记忆条数
    enableAutoReflection: true # 会话结束异步自动反思提炼
    compactionInterval: 20     # 每累计 N 次沉淀触发一轮强化衰减 + 矛盾检测

⚠️ 从 0.6.x 升级:先删掉旧的手工挂载条目

0.6.x 不是 bundle,只能靠 profile 补丁层里的 - insert: 挂载,而且当时文档给的 id 是 tlmemory-runtime(不是包名)。Cordis 按 id 去重而不按 name,所以旧的 tlmemory-runtime 条目和 0.7.0 的 bundle 条目会同时生效 —— 同一个包被挂载两次, 结果是两个数据库句柄、两个抢 4890 的监听实例。升级步骤:

  1. 在 profile 的 cordis.patch.yml 里删掉整段 - insert: - id: tlmemory-runtime …;
  2. 把 dsh-plugin-tlmemory 加进 dsh.profile.bundles(dsh plugin add 会自动做);
  3. 需要自定义配置时,按上面的 - id: dsh-plugin-tlmemory 覆写行写。

端口冲突自愈:4890 被前序 tlmemory 实例占用时,新实例会经健康探测确认同名进程 后自动复用(多宿主并存无需手工分工);被无关进程占用时自动顺延端口;连续顺延 仍失败时在控制台打印 EADDRINUSE 排查指引。

禁区端口自检:浏览器与 fetch 共同封禁的端口(如 3659 / 4045 / 4190 / 5060 / 5061 / 6000 / 6566)无法打开看板也无法被 HTTP 客户端访问,绑定后会被自检识别并 自动换端口重试 —— 配置 serverPort: 0 交由系统分配时同样保证落在可用端口上。


🚀 启动与使用

1. 启动 DSH Web 宿主

在任意终端路径下直接启动:

dsh web

Node 版本:运行需 Node >= 22.13 —— 0.8.0 起持久层用 Node 内置的 node:sqlite (v22.13.0 / v23.4.0 起不再需要 --experimental-sqlite,v24.2.0 起不再 experimental), 因此不再有原生二进制,也就不再有 NODE_MODULE_VERSION 不匹配这类问题。

2. 访问记忆看板

  1. 打开浏览器进入 DSH 界面(通常为 http://127.0.0.1:3080)。
  2. 在左侧边栏顶部(「+ 新会话」下方、「技能中心」旁)点击 「记忆看板」。
  3. 中间主视口将平滑接管展示记忆树:顶部下拉框切换任意工程(默认选中当前所在工程), 「全局偏好」胶囊单独查看跨工程偏好;右上角滑块在「📋 目录列表」与「🌲 树状图谱」 之间切换,选中工程、搜索关键词与图谱的平移缩放都会原样保留。 图谱右上角工具条里的「横向 / 竖向」按钮可一键反转树的生长方向(切换后自动重新居中, 选择记在本地,下次打开沿用);「居中」保证可读尺寸:整棵树装得下就全览,装不下就把根节点 放到左上角、由你拖拽浏览局部(滚轮仍可缩到 25% 看全局地形)。「📋 目录列表」只列真正的 记忆条目,目录骨架不再混进列表。
  4. 正常进行对话,每个完成的回合(Turn)将由后台自动提炼关键信息沉淀入库。

3. Agent 侧记忆工具(模型可直接调用)

工具 用途 关键约束
tlmemory_save 显式沉淀一条记忆 content 截断到 80 字;tree_scope 决定落 global 树还是当前工程树
tlmemory_query FTS5 全文检索既有记忆 结果已附带节点 id,可直接交给删除工具精确定位
tlmemory_delete 删除一条记忆(不可逆) 必须 confirm: true;仅叶子(目录级联删请走看板);默认只删当前工程,global / 其它工程需 allow_cross_scope: true;同名多条返回候选要求改用 id

看板上的删除按钮与待确认区的「拒绝」是同一套底层能力(DELETE /api/nodes/:id, 由 trg_nodes_ad 触发器同步 FTS5 索引);Agent 工具只是把这条路径按「必须显式确认 + 作用域隔离 + 仅叶子」收窄后开放给模型,避免一次措辞含糊的调用毁掉正确记忆。


🛠️ 项目常用命令(开发与测试)

# 运行单元测试(全工作区)
pnpm run test

# 构建全部产物:dist/(Node 核心)+ web/client.js(客户端插件)+ web/dist/(看板前端)
pnpm run build

# 客户端 Bundle 真实冒烟走查(jsdom 加载真实产物,需先 build)
pnpm run smoke:client

# dsh CLI 增强层的单测 / 安装(可选,见 tools/dsh-plugin-cmd)
pnpm run test:cli
pnpm run install:cli

根目录 pnpm run build 是递归构建(workspace 同时包含 packages/tlmemory 与 packages/tlmemory/web),因此 dist/、web/client.js、web/dist/ 会被一并刷新 —— 发包前跑这一条即可。单独构建看板前端用 pnpm --dir packages/tlmemory/web run build。


📄 License

MIT License