AieXile/dsh-memory-plugin ↗★ 0

dsh-memory-plugin

每轮注入记忆与规则并提供管理工具 适合需要跨工作区持久化管理和注入系统提示词、记忆与规则的用户。

套件
dsh-memory-plugin
相容性
待驗證
版本
1.4.0
授權
MIT
最近更新
2026年9月27日

安裝

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:AieXile/dsh-memory-plugin

dsh-memory-plugin

一个独立的 DSH 记忆 / 规则持久插件(profile bundle):与宿主项目解耦,任何工作区 都能用。它由 AnimeTrack 项目里的动态 Cordis 插件演化而来,早期以 @animetrack/memory-plugin 的名义存在,v1.4.0 起独立命名为 dsh-memory-plugin (存储路径同批迁移,旧数据自动兼容,见下文)。

它定义在磁盘上、作为 profile bundle 随启动加载,因此重启后不会消失, 不需要任何恢复步骤。

它做什么

  • 每轮注入:把最近的记忆与启用中的规则(各最多 25 条,空则不注入)作为系统提示词 文本注入;
  • 模型工具:memory_view / memory_add / memory_remove / rule_add / rule_remove / memory_debug;
  • 两个界面入口(都能读能改):
    • 标题栏图标:conversation.session.header.utilities,与「在应用中打开」「下载会话日志」 同排的 28×28 圆形图标(照搬官方按钮规则),点开后浮层锚在图标正下方;
    • 设置页:settings.section 里的一页「记忆与规则」。
  • 数据文件:/.dsh-memory/memory-store.json(v1.3.x 及更早写在 .animetrack/memory-store.json,格式完全一致,仍可读)。

记忆库落在哪个工作区(v1.3.0 起)

记忆库是按会话工作区分文件的:/.dsh-memory/memory-store.json。 目标根按这个顺序解析,并且在载入时锁定:

  1. 显式会话(面板通过 ?sessionId= 带来的那个);
  2. 当前 initiator 会话(模型工具调用就在这条异步链里);
  3. 最近一次由主会话确定的根(pinnedRoot,给没有会话上下文的设置页用);
  4. 配置根(sandboxPolicy.workspaceRoot,通常是 profile 目录)。

每个根各自持有一份内存状态,互不覆盖;写入只落在本次状态所属的那个根。

v1.2.0 及更早的实现有三个相关缺陷,v1.3.0 全部修掉,见文末「修掉的 bug」。

旧路径兼容(v1.4.0)

新路径不存在、而旧路径 /.animetrack/memory-store.json 存在时,插件会 回退读旧文件(memory_view / 面板上会标注「读自旧路径」),内容照常可用; 旧文件不会被改写或删除,下一次落盘统一写新路径 —— 也就是一次天然的迁移。 memory_debug 会同时列出 storePath / legacyStorePath / usingLegacy。

结构

dsh-memory-plugin/
  package.json        bundle 清单:dsh.bundle.patch + dsh.client(./client 导出)
  cordis.patch.yml    一行加载行:name: 'dsh-memory-plugin'
  lib/host.js         Host 半部(ESM):系统提示词注入 + 6 个工具 + 同源 HTTP 路由
  lib/client.js       Client 半部(经典脚本):__ModuleLoader__.load 注册上面两个 slot
  test/host-harness.mjs   Host 半部桩测试(真实 fs + 假 ctx,不需要 DSH)
  LICENSE / .gitignore    发布用(MIT;忽略 node_modules 与 *.tgz)
  docs/legacy/            旧「动态 Cordis 插件」实现归档(不参与打包,仅历史参考)

一个 dual-face 包只用一行:包的 "." 导出是 Host 半部,"./client" 导出是浏览器半部。

安装

这是个标准 npm 包 + DSH bundle,三种装法(plugin_manager install_bundle 都受理):

1) 从 GitHub 装(发布形态)

plugin_manager install_bundle
  target: github:AieXile/dsh-memory-plugin#v1.4.0
  # 也可以写完整地址:git+https://github.com/AieXile/dsh-memory-plugin.git#v1.4.0

DSH 先 git ls-remote 探一次仓库(默认 5s 超时,禁用凭据助手与交互提示), 再由 pnpm 拉取,并按本包 files 列表打包安装。#v1.4.0 是 tag,也能写分支或 commit。 这是「GitHub 发布」的标准形态:仓库本身就是包,不需要 extra 构建产物。

2) 从 npm 装(发布后)

plugin_manager install_bundle
  target: dsh-memory-plugin@1.4.0

包名不带 scope,publishConfig.access = public 也已声明,推 npm 时不需要额外占坑。

3) 从本地目录装(开发用,当前 profile 就是这样)

plugin_manager install_bundle
  target: F:\Projects\dsh-memory-plugin

本地路径会写成 link:(junction),改完源码重启 DSH 即生效; GitHub / npm 装的是快照副本,之后改源码不再影响已装的那份。

install_bundle 自己完成 pnpm 安装与 bundle 选择 —— 不要手工改 profile 的 package.json / cordis.patch.yml,也不要手工跑 pnpm。 该操作要求会话为 danger-full-access,或审批策略为 ask 并由你批准。

从 v1.3.x 升级(包名与条目 id 都变了)

profile 里登记的 bundle 名与 Cordis 条目 id 都已更换,所以要重新装一次:

  1. plugin_manager remove_bundle → 移除 @animetrack/memory-plugin(只解除 profile 的 链接,记忆库文件不动);
  2. plugin_manager install_bundle → 装 dsh-memory-plugin;
  3. 重启 DSH(原因见下文「改了代码怎么生效」:旧实例注册的路由/工具在那个进程里 不会释放);
  4. 若 profile 的 cordis.patch.yml 里还留着 id: animetrack-memory-rules 的旧条目, 删掉它(新版条目叫 dsh-memory-rules)。

工具名(memory_* / rule_*)、数据格式与工作区解析规则都没有变, 记忆与规则不需要搬运。

发布 / 更新流程

本仓库自身就是包(F:\Projects\dsh-memory-plugin),开发、提交、发版都在这里:

cd F:\Projects\dsh-memory-plugin
git remote add origin https://github.com/AieXile/dsh-memory-plugin.git   # 首次
# 改代码 → 同步提升 package.json 的 version 与 README 的版本说明
git add -A
git commit -m "feat: ..."
git tag v1.4.0
git push origin main --tags

npm 侧(可选):pnpm pack 先看打包内容,再 pnpm publish。

依赖与兼容

  • 零依赖、零 peer:不 import 任何 @deepseek-ai/*。官方兼容性门禁只检查包声明的 peers,而 profile 里并没有装官方包 —— 声明 peer 会让 pnpm 去 registry 拉整套官方依赖, 得不偿失。所以本包不参与 DSH 版本门禁,能否运行取决于所用服务是否还在: fs / systemPrompt / tools / webServer(必需),sandboxPolicy / agents(可选读取)。
  • 无需构建:Host 半部是 ESM 源码;Client 半部是页面直接加载的经典脚本。
  • 已在 DSH 0.1.7-rc.2 + pnpm 11.7(Windows)实测。

生效与验证

改了代码怎么生效:必须重启 DSH

实测结论(2026-09-28):

操作是否载入新代码
只改 lib/host.js❌ 无热重载:HMR 的 watch 根是 profile 目录,插件经 junction 指向工作区,改动收不到
plugin_manager set_plugin(disable→enable)⚠️ 新模块确实被 import,但旧 fiber 的注册没释放
remove_bundle + install_bundle⚠️ 同上,旧注册仍在
重启 DSH✅ 干净的单实例

原因:ctx.webServer.register 的路由不随 Cordis fiber 释放,于是新实例注册同一路径时抛 webserver: duplicate exact route "/dsh-memory/memory-store"(现场写进崩溃日志); ctx.tools.register 则按名字保留先注册的那份定义。两者叠加的结果是:旧代码继续服务, 新代码只在提示词段落上生效 —— 看上去「没改对」,其实是没换上去。

所以:改 Host 半部后请重启 DSH。Client 半部(lib/client.js)是经典脚本,刷新页面即可。

验证清单

  • 验证 Host:cordis_inspect_query → host / Config / listConfigs, entry = include:dsh-memory-rules;status: inactive 表示没激活。
  • 验证工具:新会话的模型工具表里应出现 memory_view 等;调用应返回 store 里的内容。 用 memory_debug 可以直接看到「已知根 / 各根的条目数 / 新旧路径 / 预热事件 / 上次落盘错误」。
  • 验证路由:GET /dsh-memory/memory-store?sessionId= 应返回 { ok, value, root, storePath, usingLegacy, error },root 就是该会话的工作区。
  • 验证界面:会话标题栏右侧多出一个圆形图标(与「在应用中打开」同排), 点开显示「规则 N / 记忆 N」,底部一行「存储位置:…」; 设置页里也多出「记忆与规则」一页。

不改 DSH 的自测

node test/host-harness.mjs

用真实文件系统 + 假 ctx 直接驱动 lib/host.js 的 apply(),覆盖:工具/段落/路由注册、 无会话时不误读配置根、按会话切根读写、切根不互相覆盖、路由 ?sessionId= 精确命中、 预热事件、坏 JSON 时拒绝写入并保持文件原样、旧路径回退读取与「读旧写新」迁移。 36 项断言,全部通过为退出码 0。

卸载 / 回退

plugin_manager remove_bundle,或从 dsh.profile.bundles 移除该条目。 数据文件不会被删除(已实测:remove_bundle 只解除 profile 的 link,插件目录与 store 都原样保留)。

注意:移除会让提示词注入立刻消失,但本进程内已经注册的路由与工具不会随之释放 (原因见上文),要彻底清干净还是得重启 DSH。

界面与读写通道

两个界面入口都能查看、添加、删除、启用/停用规则与记忆。

读写走的是一条自建同源 HTTP 路由:

GET  /dsh-memory/memory-store?sessionId=
     -> { ok, value: { memories, rules }, root, storePath, usingLegacy, error }
POST /dsh-memory/memory-store?sessionId=   { action, ...args }
     action ∈ state | memoryAdd | memoryRemove | ruleAdd | ruleRemove | ruleToggle

sessionId 可以省略:标题栏浮层会带上它(slot 的标准 props 里有),设置页没有会话上下文, Host 就回退到 pinnedRoot。响应里的 root / storePath 是本次读写的实际位置,界面会显示出来。

为什么用它:常规 Client 插件没有动态插件沙箱私有的 host.call 通道, 而生成的 Remote codec 需要部署的构建工具。Host 半部用 ctx.webServer.register() 自己开一条路由(inject 里已声明 webServer),页面端同源 fetch 即可 —— 不需要任何生成物。这条路由只监听 127.0.0.1,沿用页面自身的会话。

限制

  • 浮层位置:锚在图标正下方(.dmp-pop{position:absolute;top:calc(100% + 6px);right:0}), 所以它随图标移动,不再固定在右上角。
  • 路由路径 /dsh-memory/memory-store 是硬编码的;若与别的插件冲突, ctx.webServer.register 会抛错(路由是组合级契约),现场写入崩溃日志。 该注册不随 fiber 释放,所以重新装载本插件(而非重启进程)会稳定复现这条冲突。
  • 取数失败时不会静默留空:标题栏图标出现红点,浮层与设置页显示错误条目。
  • 记忆库按工作区分文件:换工作区就是另一本记忆库(设计如此,注入也只注入本工作区的)。
  • 首次往一个全新工作区写入时,需要 .dsh-memory 目录能被创建; 否则落盘失败会在工具返回里明说(不会静默丢数据)。
  • 旧路径 .animetrack/memory-store.json 只读不写,迁移后它会作为历史文件留在原地; 确认不再需要时可以手动删除。

视觉(对齐桌面端)

  • 标题栏图标完全照搬官方按钮规则:28×28、border-radius:999px、display:grid、 color:var(--dsw-alias-label-tertiary)、hover 用 --dsw-alias-interactive-bg-hover。
  • 浮层与设置页取自应用自带的 Cordis 面板(dsh-client-ui-cordis/lib/client.js): --dsw-specific-menu、--dsw-elevation-prominent、--dsw-menu-backdrop-filter、 --dsw-alias-border-l4、--dsw-alias-button-ghost-active-fill、--dsw-font-xs-13; 12px 圆角、.5px 描边、z-index:30,浮层宽 320px。

排查:Host fiber failed

若 plugin_manager list_plugins 里该行为 fiberPhase: "failed",插件会把现场写入:

/.memory-plugin-crash.log         (首选;已 gitignore,随包走)
/memory-plugin-crash.log     (退路,%TEMP%)

该文件只为定位问题,排查完可直接删除。

版本变更

v1.4.0:独立命名 + 存储路径迁移

项v1.3.xv1.4.0
npm 包名@animetrack/memory-plugindsh-memory-plugin
Cordis 条目 id / Host 插件名animetrack-memory-rulesdsh-memory-rules
Client 模块 id@animetrack/memory-plugindsh-memory-plugin
提示词段落名animetrack:memory-rules / animetrack:memory-guidancedsh-memory:memory-rules / dsh-memory:memory-guidance
HTTP 路由/animetrack/memory/memory-store/dsh-memory/memory-store
CSS 类前缀atm-dmp-
记忆库文件/.animetrack/memory-store.json/.dsh-memory/memory-store.json(旧文件只读回退,写入自动迁移)
工具名、数据格式、工作区解析—不变(向后兼容)

升级到 v1.4.0 需要重装 bundle 并重启 DSH,见上文「从 v1.3.x 升级」。

转换时修掉的三个 bug

旧动态插件里存在、并在转换时修正:

  1. 注入用的是 ctx.systemPrompt.context()。它注册的是 runtime context(作为 user 角色的 来源快照进入模型历史),不是系统提示词。已改为 ctx.systemPrompt.section()。
  2. store 只在工具调用时懒加载,而 section 的 text() 是同步的,导致首轮注入为空。 已改为 session/created / agent/created 预热 + 每次操作前 ensureLoaded()。

转换过程中还踩到并修掉一个 Cordis 契约问题:

  1. ctx.tools 必须先声明注入。动态插件沙箱里 ctx.tools 是直接可用的, 但常规插件会抛 cannot get property "tools" without inject。 inject 必须列出真正用到的服务:['fs', 'systemPrompt', 'tools', 'webServer']。 (反过来说,用不到的服务不要写进 inject;sandboxPolicy / agents 是可选读取, 走 ctx.get 即可。)

v1.3.0 修掉的根目录 bug(影响数据安全)

v1.2.0 在 DSH 启动时就激活,而那一刻还没有任何会话:

  1. sandboxPolicy.resolve() 返回的是「无会话回退根」(profile 目录),于是插件读的是 \.animetrack\memory-store.json(通常不存在), 内存状态为空;而 loaded 只置一次,之后再也不会读 —— 工作区里已有的记忆和规则 全部看不见(表现为 memory_view 显示 (none),面板空白)。
  2. 写入时 persist() 重新解析了一次根,解析到会话工作区,于是用内存里的空状态 覆盖工作区里那份有数据的 store —— 一次 memory_add 或面板上点一下「添加」就会 丢掉全部既有条目。这是数据丢失级的缺陷。
  3. 面板走 HTTP 路由,浏览器请求里没有 initiator,同样解析不到工作区。

修法:目标根按「显式会话 → 当前 initiator → 最近一次主会话根 → 配置根」解析, 每个根一份独立状态、懒载入一次,写入只落在本次状态所属的那个根; 另外载入失败(文件损坏等)时拒绝写入并明说原因,绝不用空状态覆盖现场。

复现过这个坑:2026-09-28 修复时工作区 store 里有 3 条记忆 + 3 条规则, 而运行中的插件视图是空的 —— 任何一次写入都会清空它们。

与旧动态插件的关系

旧(动态 Cordis 插件)新(本 bundle)
cordis_define + cordis_run 在内存中创建从磁盘加载,随启动生效
重启后消失,需按动态插件的恢复步骤重建持久,无需恢复
Client 半部用沙箱 host.call 通信改用自建同源 HTTP 路由(可读可写)
右侧浮动面板标题栏图标浮层 + 设置页整页
源码存档 memory-plugin.host.js / .client.jslib/host.js / lib/client.js

旧动态插件的两份源码与恢复步骤(dynamic-plugin.host.js / dynamic-plugin.client.js / dynamic-plugin-restore.md)归档在 docs/legacy/,仅作历史参考、不参与打包; lib/ 下这份 bundle 才是权威实现。

许可

MIT © 2026 Aelius —— 见 LICENSE。