Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3E63DD?style=flat-square" alt="license"></a>
<a href="https://github.com/slow-stack/mneme/actions"><img src="https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml?style=flat-square&label=CI" alt="CI"></a>
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-22%2B-3E63DD?style=flat-square&logo=nodedotjs&logoColor=white" alt="node"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1627%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1651%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://codecov.io/gh/slow-stack/mneme"><img src="https://img.shields.io/codecov/c/github/slow-stack/mneme/main?style=flat-square" alt="coverage"></a>
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome"></a>
</p>
Expand Down Expand Up @@ -207,7 +207,7 @@ dsh-mneme-serve # 默认 ~/.dsh/memory + 127.0.0.1:8790

```bash
cd dsh-mneme && npm install
npm test # 1627 个测试
npm test # 1651 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
Expand Down Expand Up @@ -414,7 +414,7 @@ Community projects that wire mneme into more platforms — not part of this repo

```bash
cd dsh-mneme && npm install
npm test # 1627 tests
npm test # 1651 tests
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
Expand Down
2 changes: 2 additions & 0 deletions dsh-mneme/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

- **面板手动新增记忆(issue #386 第 5 条)**:面板此前只能读、改、删、导入,**没有任何手写入口**——想记一条只能切到对话里让模型调 `memory_save`,而模型看不见的东西(一次现场结论、一条口头约定)恰恰最需要手动落库。「更多操作」菜单新增「新增记忆…」,弹层收类型 / 标题 / 正文 / 重要性 / 标签,字段与 `memory_save` 工具对齐。类型下拉**刻意不含 `document`**:那是 agent 注册长文档的指针行,铸造口唯一(`registerDocument`,#230),列出来只会让人点了才被拒。**表单不放 scope / sensitivity**:显式归属走详情抽屉那条路,它带「放宽可见性」二次确认;在新建表单里顺手盖章会绕开那道确认。新增后端路由 `POST /api/dsh-mneme/create`,落库走 `service.saveWithDedupe`——去重键仍是 `(type, title, scope, sensitivity)`,所以**同类型同标题会并入既有条目而不是长出第二行**,服务端回 `action: "created" | "merged"` 决定提示语说「已新建」还是「已并入」:两者对用户是不同的事实,都报「已保存」会让人以为自己新写了一条、实际旧行被改。三处校验比 standalone 的 `POST /memories` 收紧(口径向 `/update` 看齐):`content` 要 trim 非空(面板是人工输入,空正文只可能是误操作)、`document` 提前 400、scope/sensitivity 不接受。写入准入(`write-admission.js`)对面板路径不生效——它无会话身份即放行——但路由仍显式处理 `action === "denied"` → 422,免得日后面板带上会话身份时静默吞掉拒绝。回归锁六条进 `test/api-routes-gaps.test.js`(①鉴权 401 且 GET 落 404;②六种 400 校验且库内零写入;③201 created 且 `source` 为 `panel`;④200 merged 且 `count() === 1`;⑤`denied` → 422 且理由随行;⑥落库异常 500),面板侧另有一条 `test/client.test.js` 锁「入口真的挂在树上」(菜单项确实调 `setCreateOpen(true)`、弹层确实被条件挂载、`onCreated` 确实刷新列表并选中新行、请求体逐字只有 type/title/content/importance/tags、表单不得长出 scope/sensitivity 输入框、类型下拉不得含 `document`)。评审期补的两处:**成功路径不得重置 `busy`**——提示要停 900ms 才关窗,那段时间按钮若重新可点,第二次同 type+title 会落进同一行并由 `appendContent` 把正文再追加一遍,提示还会从「已新建」翻成「已并入」,一次误触就写坏一条记忆;因此整个弹层只允许错误路径重置 `busy`,按钮文案也随之改成 `busy && !msg`,免得写完了还写着「保存中…」。另把字段锁从「整包里包含这些词」改成在 `CreateDialog` 源码切片上匹配真实请求体写法(在整包上断言 `clientSource.includes("title")` 恒真,等于没锁)。两侧都做了变异检验:面板/后端逐条把锁打回坏状态都转红(含「菜单项变哑」「弹层摘掉」「成功路径放回 setBusy(false)」「按钮文案只看 busy」「表单长回 scope」「不 trim title」「打错路由」这类只有肉眼才能发现的退化)。

- **面板存储回收入口(issue #386 第 4 条)**:库文件只增不减,回收一直只能靠命令行(`dsh-mneme-maintenance`)或独立服务的数据面,面板侧连「现在能收多少」都看不见。状态页新增「维护」分区与「存储回收」卡:挂载即 GET 一次预估,摆出文件体积、空闲页、本次会清掉多少条输入快照与多少条归档向量;改窗口后必须点「重新估算」,数字与输入框不一致时执行钮保持禁用——对不可逆动作来说,确认态里摆着另一个窗口的数字是最坏的一种错位。执行是**两步确认**:第一次点击只进确认态(换成仓库既有的实心红 `mneme-btndangerconfirm`),确认态里把窗口与 VACUUM 状态一并锁死显示,第二次点击才发出请求,请求体逐字 `{dryRun:false, confirm:true, olderThanDays:Number(days), vacuum}`。**VACUUM 默认关**:它会短暂独占数据库(别的进程此时写入会 `SQLITE_BUSY`),属可选动作,默认开等于每次回收都替用户独占一次库。**VACUUM 失败不得渲染成干净的成功**:两个 UPDATE 各自提交且不可逆,VACUUM 只负责把文件缩回去,半成功态单独出文案说明「清理已完成、不可逆、重跑幂等」,并且无论成败都露出回执号(缺失时明写「未记录」)——不可逆动作没有回执,出事时无法对账。面板侧不使用 `window.confirm`(宿主 webview 里不可靠),确认门是渲染出来的分支而不是阻塞弹窗。后端新增路由 `GET/POST /api/dsh-mneme/maintenance/reclaim`,底下就是 `src/maintenance.js` 那一份与 CLI、standalone 共用的实现,**未改动数据面**(`src/api-standalone.js`、`src/maintenance.js` 零改动):GET 恒为只读预估;POST 默认 dry-run,要真执行必须带 `dryRun:false` + `confirm:true`,否则回 400 `confirm-required` 并**附上那份 dry-run 报告**(确认的依据就是报告里的数字,且报告必须描述「将要执行的那个动作」——带了 `vacuum` 就得看到会 VACUUM 的那份,否则用户照着「不 VACUUM」的估算按下去)。两个方法都要鉴权:这是不可逆内容丢弃的入口,不能有例外。窗口解析在两侧**刻意不同口径**:GET 是 query 字符串域,只收十进制整数字面量(`/^\d+$/`),**不用 `Number()`**——它把空串、空数组、纯空格都化成 0,而 0 在这里的含义是「清掉全部快照」,最坏解释绝不能从一个畸形输入静默拿到;POST 收 JSON 数字域,字符串 `"3"`、`-1`、`3.5`、`true`、`{}`、`[]` 一律 400(`NaN` 不在坏输入列表里:JSON 表达不了它,`stringify` 会把它变成 `null`,而 `null` 与缺省同义、落默认窗口)。边界值 0 两侧都合法。`invalid-json` 与 `invalid-body` 分开报(前者查调用方序列化坏了,后者查把数组当 body 发了),不用会吞成 `{}` 的 `parseBody`。报告**不加包装层**,字段就在顶层——面板与 standalone 两个客户端必须看到同一个字段路径。接线:`createApi` 增第 9 个参数 `maintenance`,`src/index.js` 把实例构造前移到 webServer 块之前并同时传给面板与 standalone(构造晚了面板路由拿到 `null`,功能「看起来加了」但每次都回 503;少传则如实报 503 `maintenance-unavailable` 而不是把 `undefined.reclaim` 落成崩溃,既有 14 处不传第 9 参的调用照旧工作)。回归锁 14 条进 `test/api-routes-gaps.test.js`(鉴权、GET 只读且畸形窗口不落最坏解释、POST 默认 dry-run 且三个改库原语一个都没被调用、确认门回带同窗口报告、执行真清且行数不变只置列、非布尔 vacuum、invalid-json 与 invalid-body 分开、POST 数字域、缺依赖 503、抛错映射 500 且 logger 留住原始错误文本、非 GET/POST 落 404、报告无包装层、执行时 VACUUM 真的跑、确认报告复述调用方要的窗口),面板侧 8 条进 `test/client.test.js`(32 个 i18n 键中英成对且分区真的渲染、路由是 in-process 那条而非数据面、请求体逐字含确认门且全卡只有一处 POST、无 `window.confirm`、卡片真的挂在状态页维护分区下、成功态只在确认门另一侧且 stale 时禁用、VACUUM 失败与回执如实渲染、VACUUM 默认关且非 2xx 带出服务端 error 串且成功后复位确认态)。**独立验证**:第三方复核方独立重做了实现者报的全部 19 条后端与 12 条面板变异(全部 RED),并**额外找出 5 条契约硬要求没被任何锁钉住的覆盖洞**(最严重的是「VACUUM 默认关」——危险默认没有任何测试守住),已逐条补锁并同样做变异检验转红;另有一处诚实记录:去掉 GET 内层 catch 后**只断言状态码抓不住**(外层 catch 也兜 500),必须靠注入 logger 断言原始错误文本留住才算真锁住。**评审期(PR #410)另修三处**:① `olderThanDays` 补**上界** `MAX_RECLAIM_WINDOW_DAYS = 3650`(与 `llmAudit.retentionDays`、`recallRetentionDays` 同值),GET 与 POST 两侧统一——整数校验挡不住量级,`1e20` 也是整数,而 `maintenance.reclaim` 会拿它算 `now() - days * 86400000` 再 `toISOString()`,超出 `Date` 有效范围就抛 `RangeError`;GET 侧还要额外挡 `Number.isFinite`,否则 400 位十进制字面量化成 `Infinity` 会被当成畸形输入静默落回默认窗口 7——调用方要一个巨大窗口,实际只清了 7 天外的东西,且报告上写着 7 看不出异样。② `confirm-required` 分支里那份 dry-run 报告挪进 `try/catch`:它在 `readBody` 的回调里计算,外层 try 那时已经退栈,抛出去就是一个没人接的 rejection——调用方拿到的是「一个字节都没回」,而不是执行路径给出的 500 `internal`。③ 面板「未记录」文案改成「未记录(审计未启用或回执写入失败)」:`writeReceipt` 返回 null 有两种成因(审计被关、或回执写库失败只 warn),旧文案把后者说成前者,而这行出现在不可逆操作的结果区,用户会照它去对账。面板同时补上本地窗口上界(超界直接提示窗口不合法,不发请求,免得用户把 400 看成网络故障)。补锁三条并逐条变异转红(GET/POST 两侧量级、confirm-required 抛错映射 500、文案必须同时点出两种成因)。

## 🐛 修复

- **面板文字对比度全面达标 WCAG AA(issue #178)**:面板有 270 条 `color` 声明,此前 **70 条**在浅色或深色主题下达不到 AA(正文 4.5:1、大字号 3:1;浅色 65 条、深色 5 条,最差的两条 `.mneme-statfoot` 只有 1.26 / 1.64)。根因是面板直接拿宿主的**标签层** token 当正文色(`label-tertiary` 50 处、`label-dimmed` 4 处):这些是给分隔线/占位符用的低对比层,不是正文层。另有三处**图形**同病(不在文字口径内,按 3:1 复核):热度条与热度点的冷档用 `label-dimmed` 压在热度轨道上只有 1.14:1、关系图连线描边 1.26:1,等于把三档热度画成同一个看不见的灰。修法不是逐条换颜色,而是在四个渲染根上定义**面板自己的语义色层**:`.mneme-x,.mneme-overlay,.mneme-triggerwrap,.mneme-topentry{…}` 六条 `--mneme-*`(`fg-dim` / `graphic-dim` / `accent` / `danger` / `warn` / `ok`),每条都用 `color-mix()` 从宿主 token 混出——`fg-dim` = `label-primary` 60% + 透明(浅 4.94 / 深 6.30),其余同法。**四个根一个都不能少**:`.mneme-overlay` 与 `.mneme-topentry` 是 `createPortal` 出来的独立子树,`.mneme-triggerwrap` 挂在宿主 sidebar footer 插槽,只挂 `.mneme-x` 的话这三个根拿不到变量、静默退回旧色。顺带修掉 **5 个幻影 token**(`--dsw-alias-state-error` 19 处、`state-warning` 14 处、`state-success` 3 处、`border` 2 处、`state-warn` 1 处):这些名字宿主从未定义过,一直靠 CSS 兜底字面量在跑,宿主改配色时面板不会跟——现全部换成真名(`state-error-primary` / `static-amber-600` / `border-l1` 等)。另需分清:`label-dimmed`(4 处)与 `label-tertiary`(50 处)**都是真实存在的宿主 token**,问题不在名字而在用法——宿主把它们定位成分隔线/占位符的低对比层,面板却拿来当正文与图形色。三处 knockout 配色一并修:`.mneme-conflict-primary` 浅色主题下白字压品牌蓝 `#4176e6` 只有 4.23:1,底色换成 `--mneme-accent`(浅 5.32 / 深 7.67);`.mneme-entrybadge` 深色主题下 `bg-layer-1` 压 `#c33` 只有 3.06:1,底色换成 `--mneme-danger`;`.mneme-btndangerconfirm` 底色同步换成 `--mneme-danger`,并把写死的 `#fff` 换成 `var(--dsw-alias-bg-layer-1)`——它旧的写死白字只在旧底色上侥幸过关,底色一变浅就会在深色主题掉到 2.67:1。回归锁 `test/contrast.test.js` 五条:①把宿主 token 的浅/深取值快照硬编码进测试(CI 上没有宿主包,读不到 asar),逐条重算全部 `color` 声明的对比度,两主题都必须过,**解析不出的颜色记失败而不是跳过**(否则改成 `var(--mneme-unknown)` 就能静默溜过);②面板引用的每个 `--dsw-*` 必须在快照表里存在(幻影名回归锁);③语义色层的选择器必须恰好是那四个根(少了根主锁照样全绿——主锁只算颜色,不知道变量有没有继承到);④图形色(`background` / `stroke`,非 `color`)单独按 3:1 复核——热度条、热度点、打卡点、关系图连线都不在主锁的视野里;⑤虚线关系边不得靠 `opacity` 区分(无 `memory_id` 的边同时命中 `.mneme-gedge` 与 `.mneme-gedge-dashed`,后者的 `opacity:.7` 把 50% 描边压到浅色 2.28,而图形锁只读单条规则、看不见这种叠加)。变异检验七条:改淡 `fg-dim`、一处正文用回宿主标签色、塞回幻影名、渲染根少挂一个、热度冷档改回宿主 `label-dimmed`、一条 `color` 改成解析不出的 `var(--mneme-unknown)`、虚线边加回 `opacity:.7`,七条锁分别转红。
Expand Down
6 changes: 3 additions & 3 deletions dsh-mneme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/@modusensus/dsh-mneme?color=blue&label=npm)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
[![tests](https://img.shields.io/badge/tests-1627%20passed-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-1651%20passed-success)](https://github.com/slow-stack/mneme)
[![CI](https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml)](https://github.com/slow-stack/mneme/actions)
[![node](https://img.shields.io/badge/node-22%2B-blue)](https://nodejs.org)
[![npm downloads](https://img.shields.io/npm/d18m/@modusensus/dsh-mneme.svg?color=blue&label=downloads)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
Expand Down Expand Up @@ -613,7 +613,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
test/ # 1627 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
test/ # 1651 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
```

Expand All @@ -622,7 +622,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 1627 个测试
npm test # 运行 1651 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
Loading
Loading