agent-memory-landing.test.js
4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
* Unit Tests — 记忆 / 会话的落点下沉(28 号设计 §6 批次 3,第 13 条)
*
* 这条改动的失败模式是**「记忆是空的」而不是报错**:写侧(memory-service 存摘要)与
* 读侧(prompt-builder 拿 agent-manager 算出的 companyDir 去读)只要算出的字符串差一点,
* 目录照建、文件照写、prompt 照拼,只是 agent 从此失忆。所以断言全部落在
* 「写进去的那份,读侧照它自己的算法能不能读回来」上,而不是「函数返回了什么字符串」。
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'fs';
import path from 'path';
const locator = require('../../services/agent-locator');
const { saveSessionSummary, loadRecentMemory } = require('../../services/memory-service');
// 本体是真建在注册表里的常驻条目(28 号裁决:测试建出来的 agent 不删),
// 所以 id 固定;afterAll 只清公司态子树 —— 记忆归属 (agentId, companyId),
// 那才是这次跑写出来的东西,本体目录本身留着。
const AGENT_ID = 'zz-mem-agent';
const BODY_DIR = locator.bodyDir(AGENT_ID);
const CO_A = 'zz-mem-co-a';
const CO_B = 'zz-mem-co-b';
/** 清掉本次跑写出来的公司态(含无公司调用落的 `_global/`);本体目录本身不动 */
const clearCompanyState = () => {
for (const cid of ['_global', CO_A, CO_B]) {
fs.rmSync(locator.companyDir(AGENT_ID, cid), { recursive: true, force: true });
}
};
const META = {
status: 'completed',
title: '写一份测试用的战略蓝图',
createdAt: new Date(Date.now() - 60_000).toISOString(),
updatedAt: new Date().toISOString(),
stats: { toolCallCount: 3 },
};
describe('记忆落点 = 公司态目录', () => {
beforeAll(() => {
// 常驻本体:内容每次重写(跨次跑不漂),三份定义写齐 = 注册表里是一份完整本体。
// 这也是第一条用例的前提:本体根上不许有 memory/,所以上一次的公司态得先清干净。
fs.mkdirSync(BODY_DIR, { recursive: true });
fs.writeFileSync(path.join(BODY_DIR, 'agent.md'), '# zz-mem-agent\n\n单测用的常驻本体。\n', 'utf-8');
fs.writeFileSync(path.join(BODY_DIR, 'soul.md'), '# zz-mem-agent 的人格\n\n记性好。\n', 'utf-8');
fs.writeFileSync(path.join(BODY_DIR, 'config.json'),
JSON.stringify({ id: AGENT_ID, name: AGENT_ID, type: 'skill', status: 'active' }, null, 2), 'utf-8');
clearCompanyState();
});
afterAll(clearCompanyState);
it('全局 agent(无公司)落 _global/,不再落本体根 —— 本体根只放只读本体', () => {
saveSessionSummary(AGENT_ID, 'conv-global', META, '全局那次的输出');
expect(fs.existsSync(path.join(locator.companyDir(AGENT_ID), 'memory', 'short-term'))).toBe(true);
expect(fs.existsSync(path.join(BODY_DIR, 'memory'))).toBe(false);
expect(loadRecentMemory(AGENT_ID)).toContain('全局那次的输出');
});
it('两家公司各记各的:A 的摘要不会出现在 B 的记忆里', () => {
saveSessionSummary(`${CO_A}:${AGENT_ID}`, 'conv-a', META, 'A 公司那次的输出');
saveSessionSummary(`${CO_B}:${AGENT_ID}`, 'conv-b', META, 'B 公司那次的输出');
const memA = loadRecentMemory(`${CO_A}:${AGENT_ID}`);
const memB = loadRecentMemory(`${CO_B}:${AGENT_ID}`);
expect(memA).toContain('A 公司那次的输出');
expect(memA).not.toContain('B 公司那次的输出');
expect(memB).toContain('B 公司那次的输出');
});
it('写侧的落点 = 读侧(prompt-builder)拿到的 companyDir —— 两边算出同一个字符串', () => {
// prompt-builder 走的是 agent-manager 挂在 agentConfig 上的 companyDir,
// 也就是 locator.companyDir(aid, cid);它和 memory-service 的落点必须重合。
const readSide = locator.companyDir(AGENT_ID, CO_A);
expect(fs.existsSync(path.join(readSide, 'memory', 'short-term'))).toBe(true);
expect(loadRecentMemory(`${CO_A}:${AGENT_ID}`)).toBe(loadRecentMemory(AGENT_ID, readSide));
});
it('companyStateDir:公司私有目录还在的老公司走老地方(过渡期,批次 4 删)', () => {
// 挑一个真实存在的存量私有目录,证明过渡期分支没被绕过 ——
// 绕过的表现是这些公司的历史记忆一次性全部读不到。
const legacy = locator.legacyCompanyAgentDir('mycompany', 'brd-planning');
if (fs.existsSync(legacy)) {
expect(locator.companyStateDir('mycompany:brd-planning')).toBe(legacy);
}
// 没有私有目录的(新布局)一律落公司态目录
expect(locator.companyStateDir(`${CO_A}:${AGENT_ID}`)).toBe(locator.companyDir(AGENT_ID, CO_A));
expect(locator.companyStateDir(AGENT_ID)).toBe(locator.companyDir(AGENT_ID, '_global'));
});
});