# 开发指南 面向想要修改、测试或发布本插件的开发者。**0.4.0 起本插件面向 DSH 0.1.2-rc.1+**,设计原则见 `README.md`「兼容性」章节。 ## 目录结构 ``` dsh-memory-manager/ ├── lib/ │ ├── index.js # 插件装配:name/inject/Config/apply、settings、事件、agent/pre-step 注入、启动预载 │ ├── util.js # 通用工具(日志 / fs / 字段清洗;零 DSH 依赖) │ ├── memory.js # 记忆库(Markdown + front-matter,扫描 / 解析 / 序列化 / backlink) │ ├── plan.js # 注入计划 / once 队列 / 轮次排除存储 / 自动注入 / 注入渲染 │ ├── sessions.js # 会话视图(live + sessionQuery)、轮次构建、排除 / 恢复、跨工作区列表 │ ├── llm.js # LLM 辅助(ctx.llm.stream):印象建议 / 智能合并 / 会话总结 │ ├── tools.js # 6 个 Agent 记忆工具(defineTool 或内置等价实现) │ ├── api.js # HTTP 路由(webServer 兼容通道 + connection.rpc 标准通道)+ RPC 分发 │ └── client.js # Client 半区(浏览器 bundle)。插槽、面板、错误边界、日志上报 ├── cordis.patch.yml # DSH bundle 挂载补丁(insert 行 id: memory-manager) ├── tests/ │ ├── test-apply.mjs # Host 集成回归:mock ctx 调用 apply(),断言注册 / 注入 / 各 op │ └── smoke-client.mjs# Client 冒烟:mock 浏览器 + SSR 渲染各组件 + 按钮断言 ├── examples/ │ └── memory-library/ # 示例记忆库(memories / pinned / config.json) ├── docs/ # 开源文档(本目录) ├── package.json # 包元数据、bundle/client 声明、peerDependencies ├── CHANGELOG.md ├── LICENSE # MIT └── README.md ``` ## Host 半区(lib/*.js) - 入口导出 `name` / `inject` / `apply` / `Config`。`inject = ['settings', 'tools']` 声明硬依赖(均为 dsh-base root 级服务);其余服务一律 `ctx.get('name')` 可选读取(支持 `ctx.get(name, false)` loose 模式兜底旧版行为)。 - `apply` 构建统一共享状态对象 `m`,依次安装:`installMemory` → `installPlan` → `installSessions` → `installLlm` → settings 注册 → `installTools`(await,动态 import)→ `installApi` → `agent/pre-step` 注入段 → `agent/created` 预载 → 启动扫描。所有副作用挂 disposers,统一回滚。 - **关键实现**: - settings 命名空间 `memory-manager`,`applies:'live'`(`scope.get()` / `scope.watch()` / `scope.update()`);旧版 `config.json` 自动迁移。 - **注入**:`agent/pre-step` 瀑布(payload `{agent, messages, turn, step, signal}`,返回 `{kind:'enter', messages}`)。仅用户消息触发的请求注入一条 `form:'snapshot'` 的 plugin 消息(UI 折叠、模型可见、随会话日志持久化),不依赖 `systemPrompt.section`(极简 complete 模式也生效)。 - **会话读取**:live `ctx.sessions.get(id)` 优先(`session.surface.nodes` + `snapshotEvents()`),只读回退 `ctx.sessionQuery.readSurface()`;`excludeTurn` 需要可写 live 会话(`session.append(type, data, { surfaceOp, sourceEventSeqs })`)。 - **规约记忆自动注入**:`agent/created`(`global:true`)预载计划;`isNewSession` 以「无 `user/message` / `assistant/message` 事件」判定,把启用中的规约记忆与最近 8 条会话总结记忆并入计划。 - **会话总结**:`session.summarize` 直接调 `ctx.llm.stream`(`agentDefaultModel.currentSelection()` 选择路由),`extractJson` 稳健抽取 JSON,`merge` 时先 `groupSameTransactions` 分组;结果自动入库。 - 6 个 Agent 工具:`memory_search` / `memory_recall` / `memory_save` / `memory_set_enabled` / `session_inject` / `memory_pin`。 - **API 双通道**:`/_dsh/memory-manager/api`(兼容,始终注册)+ `ctx.connection.rpc.intercept('/api', 'memory-manager/*')`(标准,存在才注册)。 ## Client bundle(lib/client.js) `lib/client.js` 是**浏览器 bundle 构建产物格式**,而非源码 TS/JSX: - 最外层为 `window.__ModuleLoader__.load({ id: "...", factory: (require) => { ... } })`(0.1.2 客户端模块系统仍使用该注册契约;bundle 由 DSH 按 `dsh.client` 声明自动扫描并服务)。 - factory 内是 **CJS**(`const React = require("react")`),使用 `React.createElement`(`const h = React.createElement`),**不得出现 `` JSX**。 - `exports.apply(ctx)` 在浏览器端注册**6 个插槽**(`conversation.input.left` / `conversation.session.header.actions` / `shell.overlay` ×2 / `settings.section` / `conversation.chat.assistant-actions`)—— 这些槽名与租约在 0.1.2 的 `SlotMap` 中逐一验证仍存在。 - 会话标准 props:session 作用域槽位组件收到 `sessionId` / `useSession` / `useSessions` / `useProjection`;`ctx.sessions.open(id)` 切换会话;`[data-chat-anchor-key]` + `[data-conversation-scroll]` 用于消息定位。 - **任何修改后必须保持 `window.__ModuleLoader__.load({ id, factory })` 包裹格式**;否则无法注入浏览器运行时。 主要组件:`Panel`(右侧面板,计划 / 记忆库 / 消息三 tab)、`GraphPanel` / `GraphDetail`(左侧图谱浮层,`mg-*` 样式 + `graphLayout` 力导向 / `radialLayout` 焦点径向布局)、`SummarizeDialog`(会话总结)、`SaveDialog` / `ComposeDialog` / `EditDialog`、`InputButton`、`MessageActions`、`JumpReceiver`(消息跳转接收器)、`SettingsSection`、`Boundary`。面板 / 图谱浮层 / 跳转接收的模块级状态为 `panel = { open, sessionId, tab, crashed }` 与 `graph = { open }`。 > 修改 client 后刷新页面即生效;若涉及新增 Host 能力(如新 op),需重启 DSH。 ## 测试 ### 语法检查 ```bash node --check lib/index.js node --check lib/client.js node --check tests/test-apply.mjs node --check tests/smoke-client.mjs ``` ### 依赖解析(本地跑测试) 两个测试脚本需要 `schemastery`、`@deepseek-ai/dsh-tools`(测试时可解析)、`react` / `react-dom`。仓库自身不安装这些依赖,可按以下方式链接到 DSH 检出: ```bash # 在仓库根创建 node_modules 链接(node_modules 已 gitignore): # node_modules/@deepseek-ai/dsh-tools → /packages/core/tools # node_modules/schemastery → /vendor/schemastery(与 npm schemastery API 兼容) # tests/.deps/node_modules/{react,react-dom} → pnpm store 中的 react@18.3.1 与 react-dom@18.3.1 ``` `smoke-client.mjs` 通过 `DSH_TEST_DEPS` 环境变量指向包含 `react` / `react-dom` 的目录(**必须是真实目录而非符号链接**,createRequire 按 realpath 解析)。 ### 1) Host 集成回归 —— `tests/test-apply.mjs` ```bash node tests/test-apply.mjs [插件模块路径] [临时记忆库路径] ``` - 默认加载本仓库 `lib/index.js`。 - 用 **mock ctx**(settings / tools / agents / sessionQuery / sessions / workspaceRegistry / llm / agentDefaultModel / webServer)调用 `apply(ctx, {})`,断言: 1. settings 注册 `memory-manager`; 2. 6 个工具注册; 3. `webServer.register` 路由注册; 4. 经路由 handler 走一遍业务层(`state.setLibrary` → `memory.save` → `state.get` → `library.scan` → `plan.addMemory` → `sessions.list`),验证「规约记忆新会话自动注入」; 5. `agent/pre-step` 监听存在且注入快照消息(`source.kind==='plugin'`、`form` 含 sections); 6. dispose 成功。 ### 2) Client 冒烟 + SSR —— `tests/smoke-client.mjs` ```bash DSH_TEST_DEPS=/path/to/deps node tests/smoke-client.mjs ``` | 环境变量 | 默认 | 含义 | |---|---|---| | `DSH_TEST_DEPS` | 仓库自身 `node_modules` | 指向含 `react` / `react-dom` 的目录,用于解析依赖 | | `DSH_TEST_CLIENT` | 本仓库 `lib/client.js` | 指向要测试的 client bundle 路径 | 该脚本: 1. mock 浏览器环境(`window.__ModuleLoader__`、`document`、`fetch`),读取 client bundle **注入测试导出**(`module.exports.__test = { ... }`),再 `eval` 执行。 2. 调用 `mod.apply(ctx)`,断言返回 disposer,并校验插槽注册(6 个)。 3. 用 `react-dom/server` 的 `renderToString` 对每个组件进行 SSR 渲染(含 hooks 违规 / 渲染异常检测)。 4. 按钮文字断言:确保按钮经 `h()` 调用、children 未丢失。 5. 结构性检查:禁止直接组件调用(`PlanTab({...})` 这类写法),防止 hooks 挂错链回归。 任一断言失败即 `process.exit(1)`。 > **回归防护要点**(历史事故教训):凡是带 children 的组件一律用 `h(Component, props)` 调用 —— 直接函数调用虽不报错但会**丢失 children**(按钮渲染为空)并可能引发 hooks 链错乱导致面板崩溃。`smoke-client.mjs` 专门守护这两点。 ## 发布清单 1. **版本号**:更新 `package.json` 的 `version`(语义化版本)。 2. **CHANGELOG**:在 `CHANGELOG.md` 顶部新增新版本小节(描述新增 / 修复 / 破坏性变更),旧的正式版本归档,「Unreleased」段保持留空供下一次迭代填写。 3. **校验 `files` 字段**:确认 `lib`、`cordis.patch.yml`、`docs`、`examples`、`README.md`、`LICENSE` 均在发布清单内。 4. **回归测试**:跑 `node --check` + 两个测试脚本,全部通过。 5. **构建产物**:确保 `lib/client.js` 仍是 `window.__ModuleLoader__.load` 包裹的 CJS bundle 格式,且 `package.json` 的 `dsh.client.inject` 为当前 DSH 的行名。 6. **提交**:更新 README(如需)、CHANGELOG、版本号一并提交。 ## 环境与落盘 - Host 启动日志:`/memory-manager-boot.log`(默认「用户主目录/.dsh/」,可用 `DSH_MEMORY_LOG_DIR` 重定向)。 - 前端日志:`/memory-manager-client.log`(经 `diag.log` op 上报落盘)。