feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m45s
CI / 全量测试 (Electron ABI) (push) Failing after 6m28s
CI / 产物编译验证 (push) Successful in 11m18s

This commit is contained in:
2026-09-08 14:30:27 +08:00
parent 69776e447f
commit 4cd6e997b5
86 changed files with 4303 additions and 956 deletions
@@ -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 upsertaccess_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();
});
});