feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
This commit is contained in:
@@ -31,7 +31,8 @@ try {
|
||||
|
||||
import { MemoryManager } from '../manager';
|
||||
|
||||
// 与 DatabaseService.createTables 一致的记忆三表 schema(含 tf_cache 列)
|
||||
// 与 DatabaseService.createTables 一致的记忆三表 schema(含 tf_cache / embedding /
|
||||
// embedding_model 列 —— v0.8.2 P3-1 迁移 14 对齐)
|
||||
function createMemorySchema(db: any): void {
|
||||
db.exec(`
|
||||
CREATE TABLE episodic_memories (
|
||||
@@ -44,7 +45,8 @@ function createMemorySchema(db: any): void {
|
||||
created_at INTEGER NOT NULL DEFAULT 0,
|
||||
expires_at INTEGER,
|
||||
tf_cache TEXT,
|
||||
embedding BLOB
|
||||
embedding BLOB,
|
||||
embedding_model TEXT
|
||||
);
|
||||
CREATE TABLE semantic_memories (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -57,7 +59,8 @@ function createMemorySchema(db: any): void {
|
||||
updated_at INTEGER NOT NULL DEFAULT 0,
|
||||
access_count INTEGER DEFAULT 0,
|
||||
tf_cache TEXT,
|
||||
embedding BLOB
|
||||
embedding BLOB,
|
||||
embedding_model TEXT
|
||||
);
|
||||
CREATE TABLE working_memories (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -783,9 +786,9 @@ describe.skipIf(!dbAvailable)('MemoryManager — cleanupExpired', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// 注意:manager.store() 的 episodic INSERT 不含 expires_at 列(源码已知缺口,
|
||||
// main.ts 注释亦确认"expires_at 无写入方")—— 本组用例直接经 SQL 写入
|
||||
// expires_at 模拟真实过期行,锁定 cleanupExpired 自身的删除契约。
|
||||
// v0.8.2 P3-6 注释修正:v0.8.1 P0-2 已让 store() 真实写入 expires_at(本组
|
||||
// 早期版本注释"INSERT 不含 expires_at 列"已过时)。此处直接经 SQL 写入仅为
|
||||
// 测试夹具便利(绕过 store 的哈希/分词管线),锁定 cleanupExpired 的删除契约。
|
||||
const insertExpiring = (id: string, expiresAt: number, content = '带过期记忆'): void => {
|
||||
db.prepare(
|
||||
`INSERT INTO episodic_memories (id, content, source, importance, created_at, expires_at)
|
||||
@@ -984,3 +987,73 @@ describe.skipIf(!dbAvailable)('MemoryManager — v0.8.1 生命周期与混合检
|
||||
expect(row.embedding!.length % 4).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ===== v0.8.2 P3-1: access_count 保留 + 模型指纹 =====
|
||||
|
||||
describe.skipIf(!dbAvailable)('MemoryManager — v0.8.2 P3-1', () => {
|
||||
let db: InstanceType<typeof Database>;
|
||||
let mgr: MemoryManager;
|
||||
|
||||
beforeEach(() => {
|
||||
db = new Database(':memory:');
|
||||
createMemorySchema(db);
|
||||
mgr = new MemoryManager(() => db);
|
||||
});
|
||||
|
||||
it('semantic 同内容重复 store → ON CONFLICT upsert,access_count 不归零', () => {
|
||||
mgr.store({
|
||||
type: 'semantic',
|
||||
content: '用户偏好深色主题',
|
||||
source: 'user_input',
|
||||
importance: 0.9,
|
||||
});
|
||||
db.prepare('UPDATE semantic_memories SET access_count = 7').run();
|
||||
// 同内容重复写入(key = contentHash,冲突命中同一行)
|
||||
mgr.store({
|
||||
type: 'semantic',
|
||||
content: '用户偏好深色主题',
|
||||
source: 'user_input',
|
||||
importance: 0.9,
|
||||
});
|
||||
const rows = db.prepare('SELECT access_count, value FROM semantic_memories').all() as Array<{
|
||||
access_count: number;
|
||||
value: string;
|
||||
}>;
|
||||
// 旧 REPLACE 语义会删除重插 → 2 行且 access_count 归零
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0].access_count).toBe(7);
|
||||
});
|
||||
|
||||
it('embedding_model 指纹:模型不匹配的向量按缺失处理并触发重算', async () => {
|
||||
mgr.store({
|
||||
type: 'semantic',
|
||||
content: '指纹校验内容',
|
||||
summary: 'fp-check',
|
||||
source: 'agent_thought',
|
||||
importance: 0.8,
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
// 模拟"用户更换了 embedding 模型":旧向量标记为旧模型
|
||||
db.prepare(
|
||||
"UPDATE semantic_memories SET embedding_model = 'old-model' WHERE key = 'fp-check'",
|
||||
).run();
|
||||
let reembedCalls = 0;
|
||||
mgr.setEmbedder({
|
||||
modelName: 'new-model',
|
||||
embed: async () => {
|
||||
reembedCalls += 1;
|
||||
return [0.1, 0.2, 0.3];
|
||||
},
|
||||
});
|
||||
const results = await mgr.search('指纹校验内容');
|
||||
// 检索本身可用(回退 TF-IDF);旧向量被排队用新模型重算
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
expect(reembedCalls).toBeGreaterThanOrEqual(1);
|
||||
const row = db
|
||||
.prepare("SELECT embedding, embedding_model FROM semantic_memories WHERE key = 'fp-check'")
|
||||
.get() as { embedding: Buffer | null; embedding_model: string | null };
|
||||
expect(row.embedding_model).toBe('new-model');
|
||||
expect(row.embedding).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user