dsh-lite-memory 0.18.39 → 0.18.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/BENCH.md +7 -7
- package/docs/RELEASE-0.18.40.md +13 -0
- package/index.js +7 -1
- package/package.json +1 -1
- package/test/contract.test.mjs +5 -0
- package/test/recall.test.mjs +21 -0
package/docs/BENCH.md
CHANGED
|
@@ -25,22 +25,22 @@ npm run bench:evolution # 实验规则/样例/检查点/回执的存储与投
|
|
|
25
25
|
|
|
26
26
|
## 固定数据集性能
|
|
27
27
|
|
|
28
|
-
2026-10-29,macOS arm64,Node v24.16.0,本地 SSD。数据:项目 200 条 + 全局 200 条,全部带标题、摘要、`when`、正文;另加一条正面场景,确认全局卡片真的进得来且不超预算。0.18.
|
|
28
|
+
2026-10-29,macOS arm64,Node v24.16.0,本地 SSD。数据:项目 200 条 + 全局 200 条,全部带标题、摘要、`when`、正文;另加一条正面场景,确认全局卡片真的进得来且不超预算。0.18.40 版复测(两次取稳定的一次)。
|
|
29
29
|
|
|
30
30
|
0.18.0 起每次保存都走「写临时文件 → fsync → rename → fsync 目录」并保留上一版,代价是单次保存多两次落盘;下面这组数是带这套持久化保证后测的。
|
|
31
31
|
|
|
32
32
|
| 指标 | 数值 |
|
|
33
33
|
| --- | --- |
|
|
34
|
-
| 准备时延 P50 |
|
|
35
|
-
| 准备时延 P95 |
|
|
36
|
-
| 冷启动首次准备 |
|
|
37
|
-
| 最慢一次 |
|
|
34
|
+
| 准备时延 P50 | 48.6 ms |
|
|
35
|
+
| 准备时延 P95 | 51.4 ms |
|
|
36
|
+
| 冷启动首次准备 | 69.6 ms |
|
|
37
|
+
| 最慢一次 | 69.6 ms |
|
|
38
38
|
| 每次准备上下文 | 368 B |
|
|
39
39
|
| 正面场景上下文 | 335 B,含 1 张全局卡片,未超 3 条 / 1200 字节 |
|
|
40
40
|
| 每次准备文件读取 | 0.2 |
|
|
41
41
|
| 本次数据规模 | 项目 200 条 + 全局 200 条 |
|
|
42
42
|
|
|
43
|
-
数据翻倍到两层各 200 条后仍在 deadline 150 ms 内:0.18.
|
|
43
|
+
数据翻倍到两层各 200 条后仍在 deadline 150 ms 内:0.18.40 复测 P95 51.4 ms。模型 provider 在准备阶段无 I/O、无网络。
|
|
44
44
|
|
|
45
45
|
## 长期序列
|
|
46
46
|
|
|
@@ -52,7 +52,7 @@ npm run bench:evolution # 实验规则/样例/检查点/回执的存储与投
|
|
|
52
52
|
| 全局活跃条目 | 200 |
|
|
53
53
|
| `state.json` | 396 KB(上限 2 MiB) |
|
|
54
54
|
| 层内文件 | `state.json`、`state.prev.json` 两个,不随轮数增长 |
|
|
55
|
-
| RSS |
|
|
55
|
+
| RSS | 315 MB(含进程内缓存与测试数据;两次 301/315 MB) |
|
|
56
56
|
| 序列耗时 | 5.8 s(150 轮全落盘) |
|
|
57
57
|
|
|
58
58
|
## 实验整理成本(0.11.0)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 0.18.40
|
|
2
|
+
|
|
3
|
+
## 更新句柄也能拿到相近完整标题
|
|
4
|
+
|
|
5
|
+
`memory_save` 用 `updateTitle` 改一条记忆时,如果传的是缩短过的标题,以前只回「没有这条记忆」;现在和 `memory_read` / `memory_search` 一致,在报错里列出最多 3 条相近的完整标题,模型可以一次改对。
|
|
6
|
+
|
|
7
|
+
- 只影响失败路径:成功写入的流程、工具描述和常驻字节不变(13375 字节)。
|
|
8
|
+
- 不会模糊读取或模糊改写:仍要求完整标题或 id。
|
|
9
|
+
- 回归:`test/contract.test.mjs` 断言缩短标题的更新请求报出完整标题,且不出现 `undefined`。
|
|
10
|
+
|
|
11
|
+
## 使用记账回归
|
|
12
|
+
|
|
13
|
+
`test/recall.test.mjs` 新增用例:没有送达模型的回合结束后,条目的 `lastUsed` 不变;真正送达到卡片的回合收尾时才回写。防止以后改动把「检索过」误记为「用过」。
|
package/index.js
CHANGED
|
@@ -2094,7 +2094,13 @@ export function apply(ctx, config) {
|
|
|
2094
2094
|
}
|
|
2095
2095
|
const layer = args.scope === 'global' ? 'global' : args.scope === 'project' ? 'project' : '';
|
|
2096
2096
|
const picked = layer ? candidates.find((row) => row.scope === layer) : candidates[0];
|
|
2097
|
-
if (!picked)
|
|
2097
|
+
if (!picked) {
|
|
2098
|
+
const near = nearTitleHints(runtimeLayers(projectScope), updateTitle);
|
|
2099
|
+
if (near.length) {
|
|
2100
|
+
return saveFailure(`没有「${updateTitle}」这条记忆。相近的里有:${near.map((item) => `「${item}」`).join('、')};要新增就不用 updateTitle`, args);
|
|
2101
|
+
}
|
|
2102
|
+
return saveFailure(`没有「${updateTitle}」这条记忆;要新增就不用 updateTitle`, args);
|
|
2103
|
+
}
|
|
2098
2104
|
draft.id = picked.id;
|
|
2099
2105
|
}
|
|
2100
2106
|
/** 关联目标同样按标题解析成内部 id 再落盘。 */
|
package/package.json
CHANGED
package/test/contract.test.mjs
CHANGED
|
@@ -295,6 +295,11 @@ await assert.rejects(
|
|
|
295
295
|
);
|
|
296
296
|
const hintedRead = await exec('memory_read', { title: '生效验证顺序(含:桌面端没有 Cmd+R)' });
|
|
297
297
|
assert.equal(hintedRead.body, '正文');
|
|
298
|
+
const hintedUpdate = await exec('memory_save', { title: '缩短标题的探针', summary: '改正文', body: '新正文', updateTitle: '生效验证顺序' });
|
|
299
|
+
assert.ok(hintedUpdate.error);
|
|
300
|
+
assert.match(hintedUpdate.error, /相近的里有:「生效验证顺序(含:桌面端没有 Cmd\+R)」/);
|
|
301
|
+
assert.doesNotMatch(hintedUpdate.error, /undefined/);
|
|
302
|
+
|
|
298
303
|
|
|
299
304
|
/** 重命名后仍按原 id 定位到同一条;归档后重建同名条目也不能冒领旧 id。 */
|
|
300
305
|
const renameSaved = store.saveEntry(twinScope, { title: '改名前的探针', summary: '旧身份', body: '旧正文', writeMode: 'patch' });
|
package/test/recall.test.mjs
CHANGED
|
@@ -1009,3 +1009,24 @@ console.log('recall.test.mjs: ok');
|
|
|
1009
1009
|
assert.equal(asked(text), false, text);
|
|
1010
1010
|
}
|
|
1011
1011
|
}
|
|
1012
|
+
|
|
1013
|
+
// A32:只有真递到模型眼里的条目才记使用时间;空召回/未命中的回合不能顺手记账。
|
|
1014
|
+
{
|
|
1015
|
+
const cwd = mkdir('usage');
|
|
1016
|
+
const scope = store.scopeOf(cwd);
|
|
1017
|
+
const probe = save(scope, { title: '量子纠缠咖啡机', tags: ['记账'], summary: '只有送达才记 lastUsed' });
|
|
1018
|
+
const lastUsedOf = () => store.locateByTitle(scope, '量子纠缠咖啡机').record.lastUsed;
|
|
1019
|
+
const baseline = lastUsedOf();
|
|
1020
|
+
const runtime = boot();
|
|
1021
|
+
const agent = makeAgent('agent-usage', cwd);
|
|
1022
|
+
const missSignal = await prepare(runtime, agent, '完全无关的一串词');
|
|
1023
|
+
assert.doesNotMatch(runtime.provider.text({ agent, signal: missSignal }), /量子纠缠咖啡机/);
|
|
1024
|
+
runtime.handlers.get('agent/turn-stopping').at(-1)({ signal: missSignal });
|
|
1025
|
+
assert.equal(lastUsedOf(), baseline, '没送达的条目不能被记账');
|
|
1026
|
+
const hitSignal = await prepare(runtime, agent, '量子纠缠咖啡机', 't2');
|
|
1027
|
+
assert.match(runtime.provider.text({ agent, signal: hitSignal }), /量子纠缠咖啡机/);
|
|
1028
|
+
assert.equal(lastUsedOf(), baseline, '送达也不在 provider 路径上写盘');
|
|
1029
|
+
runtime.handlers.get('agent/turn-stopping').at(-1)({ signal: hitSignal });
|
|
1030
|
+
assert.ok(lastUsedOf(), '回合收尾时把真正送达的条目记账');
|
|
1031
|
+
assert.equal(store.locateByTitle(scope, '量子纠缠咖啡机').record.id, probe.id);
|
|
1032
|
+
}
|