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-2623%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-2638%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 @@ -270,7 +270,7 @@ dsh-mneme-serve # 默认 ~/.dsh/memory + 127.0.0.1:8790

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

```bash
cd dsh-mneme && npm install
npm test # 2623 tests
npm test # 2638 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 @@ -34,6 +34,8 @@

## 🎨 面板

- **面板「最近巩固」卡显示的可能是一次「整理」回执:organize 上线后它与巩固共用 `dream_runs`,而状态卡直接取 `runs[0]`(#495,报告由 @modusensus 提供)**:`/dream-status` 的最近 5 条 run 不分 `run_type`,一次 agent 主动整理会写两行(dryRun 报告 + apply 回执,后者 `outcome.dry_run_id` 指回报告),两行都排在真巩固之前——于是这张标题写着「最近巩固」的卡显示的是整理的时间与状态。issue 给了两条路,选了**标注**:`runs` 仍是全类型 5 条(审计面是证据,不因显示面积省略),另加顶层 `lastConsolidation`(`excludeRunType: "organize"` 后取一条)供卡面取数;跑过但只有 organize 时卡面照实说「尚未运行」,并加一句脚注点名那次整理的时间,不把它抹掉也不拿它冒充巩固。**为什么不是面板侧过滤 `runs`**:一次整理写两行,5 个名额被挤空时 `runs` 里可能一条巩固都没有,那时候「过滤后有什么」本身就是空的;而后端多给一个键,旧面板读不懂也不受影响。**同源修正**:KPI 带下方那块「上一轮跳过什么」原先也读 `lastRun`,而 organize 回执同样带 `skipped`——它的跳过不是巩固决策(例如「与既有记忆重复」),混进去会让人以为巩固那一整类动作被拦;改读 `lastConsolidation`。`TokenFlowSection` 与 `DreamStatusCards` 两处都对新键缺席的旧服务端留了退路(退回 `lastRun`,但退回时仍拒绝把 organize 行当巩固)。**不加配置开关**:这是显示口径的更正,不是可选功能。回归测试 +5(`test/api.test.js`:混排时取到真巩固轮且两路共用同一份投影 / 只有 organize 时 `lastConsolidation` 为 null;`test/client.test.js`:卡面读新键且退回分支排除 organize / KPI 带改读巩固轮;`test/store.test.js`:`excludeRunType` 是**下推**条件——在 `LIMIT` 之前生效,白名单外的值不改变查询)。变异自证四例(去掉 `excludeRunType` 两条红、`excluded` 恒为 null 时 store 用例红、卡面退回 `state.lastRun` 红、KPI 退回 `d.lastRun` 红)。

- **481–519px 是一段两不管的中间带:480 主断点之上、两栏又排不下,卡片横向溢出 2–11px 被外层 `overflow:hidden` 直接切掉(承接 #542,实测发现)**:#542 把 `minmax(252px,1fr)` 换成 `minmax(min(252px,100%),1fr)`,但那两条**只写在 `@container (max-width:480px)` 里**,而基础规则 `lib/client.js` 的 `.mneme-cards` 仍是裸 `minmax(252px,1fr)`、`.mneme-attrgrid` 仍是裸 `minmax(200px,1fr)`。于是 481px 一开始断点失效、左栏回到 216px,卡片被要求至少 252px——`Edge 156` 实测:记忆页 481/490/500/510 各有 1–2 条 `SCROLLX`(`.mneme-cards` 300 > 可用 265),实体页 detail 只剩 257–276px 而属性网格仍要 200px。**兜底必须落在基础规则上**:两处 `min()` 下限上移到基础规则(它们的正确作用域本来就是「所有宽度」,481–519px 只是其中最窄的一段),480 档里的重复声明一并删掉。修完 320–900px 全档 24 个页面 ovf=0,卡片宽 320→300 / 481→217 / 520→256 / 900→287,属性网格 481→209 / 900→628。**一处刻意的取舍**:我一度给基础规则加 `max-height:min(440px,52vh)` 想把 481–519px 那段 440px 高的图谱压下来,实测确实压到 353px——但 `52vh` 在常见窗口高度(≈680px)下对**桌面也生效**,等于顺手改了大屏观感,已回退。基础规则不带 `max-height`,限高仍只留在 480 档(`min(320px,52vh)`);**代价是 481–519px 中间带的图谱仍是 440px 高,这个中间带高度问题刻意不修**。**回归测试改锁位置**:原来那两条 `body.includes("minmax(min(252px,100%),1fr)")` 锚在 480 档 body 上,现在改成作用在「剥掉所有嵌套块之后的顶层规则」——新增一个 `topLevel` 切片器(遇到 `@` 就按括号配对跳过后整块丢弃),并断言切片长度 > 10000 防止切片器自己坏掉导致假绿;锁两件事:两处基础 `minmax(min(…,100%),1fr)`。变异自证(把基础 minmax 退回裸 252 → 红;恢复后 85/85)。全量 `npm test` 2609 / 2608 pass / 0 fail / 1 skipped。**已知未覆盖**:仍无真机(`adb devices` 为空,需要设备接上并重启宿主让插件重新探测 adb),宽度结论来自 Edge headless + 真实 CSS 的静态测量,交互(拖拽、滚动链、软键盘)未经真机验证。
- **面板此前根本没有移动端断点:320–430px 的手机上,记忆页与实体页的两栏骨架把主区挤成一条缝,导航项还会压到「⋯」按钮底下**:整份 `lib/client.js` 只有 8 条 `@container` 规则、0 条 `@media`,而且这 8 条全是「把数字改小」式收窄——记忆页左栏 216px + 卡片最小 252px + 沟槽 48px = 面板至少 517px 才放得下,实体页 rail 224px + 属性网格 200px + 48px = 至少 473px,而目标上沿只有 430px。**收窄数字永远追不上,必须动骨架**:新增 `@container (max-width: 480px)` 主断点,把 `.mneme-xmain` 与 `.mneme-ent` 从 `flex-direction:row` 翻成 `column`,左栏 / rail 改成满宽 + 限高可滚(`max-height:min(42vh,300px)` / `min(40vh,280px)` + `overscroll-behavior:contain`,滚到底不漏给页面),卡片与属性网格的 `minmax(252px,1fr)` 改成 `minmax(min(252px,100%),1fr)`(**`min()` 是防溢出的关键:320px 下 `minmax(252px,1fr)` 仍会把轨道撑到 252px 之外**),抽屉从 `width:min(432px,52%)` 改 `100%`(52% 相对的是 `.mneme-xmain`,320 下只剩 166px;抽屉自带 ✕,满宽不丢关闭入口),顶部导航改左对齐 + 右侧留 52px 给 `position:absolute` 的 `⋯`,并新增 `@container (max-width: 360px)` 兜底档(隐藏 14px 图标、导航横向可滚、卡片单列)。**三处刻意的取舍**:① **`@container` 里一个颜色都不写**——`test/contrast.test.js` 的提取器用 `/^([^{]+)\{([^}]*)\}$/` 只匹配单层花括号,`@container` / `@media` 的双层块整串被跳过,在里面写 `color` 就绕过了 #178 的 AA 锁;新增的回归测试会把这条纪律变成闸门(遍历所有双层块,出现 `color` / `fill` / `stroke` / `background` 即失败)。② **overlay 外框只能走 `@media`**:`.mneme-overlay` 不是 `.mneme-x` 的后代(`container-type:inline-size` 在 `.mneme-x` 上),容器查询够不到它,所以它的 `calc(100vw - 16px)` / `calc(100dvh - 16px)` 放在 `@media (max-width:640px)` 里。③ **触屏删除钮只加常显、不动配色**:`.mneme-set-del{opacity:0}` 靠 `:hover` / `:focus-within` 恢复,触屏永远没有 hover——改法是 `@media (hover:none){.mneme-set-del{opacity:1}}`,参照同文件 `.mneme-persona-del` 的常显范式;#457(内联 `opacity:1` 覆写)与 #459(连带点亮规则区真删除钮)两次回滚的教训就是**别顺手把配色也改了**。顺带给 `.mneme-set-del` / `.mneme-footbtn` 补 `min-height:24px`(WCAG 2.5.8 AA 的触控下限)。图谱另修三症:内联 `height:440` 被 `max-height:min(320px,52vh)` 压住(CSS 2.1 §10.7:`max-height` 优先于 `height`)、`touch-action:none` 改 `pan-y` 让页面能纵向滚、`.mneme-glabel` 从 11px 提到 14px 抵消 SVG 缩放(380 用户单位压到 288px 时缩放比 0.758,11px 视觉只剩 8.3px)。弹层固定宽同样越界:`.mneme-sharedialog{width:340px}` 在 320 屏上是硬矛盾,三条都改 `min(原宽,calc(100cqw - 32px))`。**回归测试 +1**(`test/client.test.js` 的 `mobile: the narrow-container breakpoints reflow the two-column skeletons instead of shrinking numbers`):锁两档断点存在、锁骨架翻转与 `minmax(min(...))`、锁抽屉满宽与弹层 `cqw` 上界、锁 overlay 走 `@media`、**锁双层块内不得出现颜色声明**、锁 `hover:none` 的常显。变异自证三例(把 `@container` 换成 `@media`、在 `.mneme-ent` 里插 `color:red`、把 `minmax(min(252px,100%),1fr)` 改回 `minmax(252px,1fr)`)逐条变红,恢复后 90/90 全绿。**已知未覆盖**:实际视觉未在真机或 Playwright 上验证过(本机无 playwright),全部结论是 CSS 级推演;`100vh` 未换成 `dvh` 的位置仍有 3 处(`2108` / `2493` / `2596`),`safe-area-inset-*` 全文件 0 处——这三项留作后续。

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-2623%20passed-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-2638%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 @@ -719,7 +719,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync,prepack 钩子在发布时自动跑;一致性由 scripts/check-sync.js 在 CI 与 release.yml 校验——它只查不同步;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
test/ # 2623 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
test/ # 2638 个 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 @@ -728,7 +728,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 2623 个测试
npm test # 运行 2638 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
14 changes: 12 additions & 2 deletions dsh-mneme/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1769,7 +1769,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
policy: "open",
handler(req, res) {
try {
const runs = (service.listDreamRuns?.({ limit: 5 }) ?? []).map((r) => ({
const toRun = (r) => ({
created_at: r.created_at,
status: r.status,
provider: r.provider ?? null,
Expand All @@ -1787,7 +1787,16 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
// 被发现。这里只回聚合(原因 + 动作 + 条数),明细仍留在库里。
skippedCount: Array.isArray(r.skipped) ? r.skipped.length : 0,
skippedReasons: summarizeSkipped(r.skipped)
}));
});
// Issue #495:organize 上线后 runs 会混进 run_type="organize",而面板那张
// 卡的标题是「最近巩固」——一次 agent 主动整理就会把它顶成整理回执。
// 两条路里选了「标注」而不是过滤:runs 仍是全类型 5 条(审计面是证据,
// 不因显示面积省略),另给一个只含 auto/sleep 的 lastConsolidation 供那张
// 卡取数;面板再用 lastRun 是否为 organize 决定要不要标一句脚注。
// 这里不能只靠 runs[0] 的 run_type 过滤——一次整理写两行(dryRun 报告 +
// apply 回执),挤空 5 个名额时 runs 里可能一条巩固都没有。
const runs = (service.listDreamRuns?.({ limit: 5 }) ?? []).map(toRun);
const lastConsolidation = service.listDreamRuns?.({ limit: 1, excludeRunType: "organize" })?.[0];
const pendingConflicts = service.countConflictPending?.() ?? 0;
const ids = new Set();
for (const row of service.listConflictPending?.({ limit: 200, includeResolved: false }) ?? []) {
Expand All @@ -1798,6 +1807,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
}
sendJson(res, 200, {
lastRun: runs[0] ?? null,
lastConsolidation: lastConsolidation ? toRun(lastConsolidation) : null,
runs,
pendingConflicts,
pendingMemoryIds: [...ids].slice(0, 200)
Expand Down
Loading
Loading