|
1 | 1 | import assert from "node:assert/strict"; |
2 | | -import { mkdir, mkdtemp, readdir, readFile, rm, symlink, writeFile } from "node:fs/promises"; |
| 2 | +import { lstat, mkdir, mkdtemp, readdir, readFile, rm, symlink, writeFile } from "node:fs/promises"; |
3 | 3 | import { tmpdir } from "node:os"; |
4 | 4 | import { join } from "node:path"; |
5 | 5 | import test from "node:test"; |
@@ -101,7 +101,7 @@ test("/remember validates input, rejects busy agents, and sends live state", asy |
101 | 101 | } |
102 | 102 | }); |
103 | 103 |
|
104 | | -test("session start recommends /dream when memory is new, stale, or 70% full", async () => { |
| 104 | +test("session start recommends /dream only for valid stores within their caps", async () => { |
105 | 105 | const root = await mkdtemp(join(tmpdir(), "pi-memory-dream-reminder-")); |
106 | 106 | const agentDir = join(root, "agent"); |
107 | 107 | const memoryDir = join(root, "memory"); |
@@ -147,6 +147,28 @@ test("session start recommends /dream when memory is new, stale, or 70% full", a |
147 | 147 | assert.deepEqual(notifications, ["Memory dream recommended; run /dream."]); |
148 | 148 |
|
149 | 149 | notifications.length = 0; |
| 150 | + await rm(statePath); |
| 151 | + await writeFile(join(memoryDir, "MEMORY.md"), "x".repeat(11)); |
| 152 | + await handlers.get("session_start")!({ type: "session_start" }, ctx); |
| 153 | + assert.deepEqual(notifications, []); |
| 154 | + |
| 155 | + await writeFile(join(memoryDir, "MEMORY.md"), "123456"); |
| 156 | + await writeFile(join(memoryDir, "USER.md"), "x".repeat(11)); |
| 157 | + await handlers.get("session_start")!({ type: "session_start" }, ctx); |
| 158 | + assert.deepEqual(notifications, []); |
| 159 | + |
| 160 | + await writeFile(join(memoryDir, "USER.md"), ""); |
| 161 | + await writeFile(join(memoryDir, "MEMORY.md"), "x".repeat(MAX_FILE_BYTES + 1)); |
| 162 | + await handlers.get("session_start")!({ type: "session_start" }, ctx); |
| 163 | + assert.deepEqual(notifications, []); |
| 164 | + |
| 165 | + await rm(join(memoryDir, "MEMORY.md")); |
| 166 | + await symlink(join(root, "missing-MEMORY.md"), join(memoryDir, "MEMORY.md")); |
| 167 | + await handlers.get("session_start")!({ type: "session_start" }, ctx); |
| 168 | + assert.deepEqual(notifications, []); |
| 169 | + |
| 170 | + await rm(join(memoryDir, "MEMORY.md")); |
| 171 | + await writeFile(join(memoryDir, "MEMORY.md"), "123456"); |
150 | 172 | await writeFile(statePath, "x".repeat(65)); |
151 | 173 | await handlers.get("session_start")!({ type: "session_start" }, ctx); |
152 | 174 | assert.match(notifications[0]!, /Timestamp file is too large/); |
@@ -248,19 +270,36 @@ test("/dream reuses unchanged memory snapshots and guards the agent-global SYSTE |
248 | 270 | assert.match(messages[0]!, /one memory batch per affected target/); |
249 | 271 | assert.match(messages[0]!, /no memory call if none/); |
250 | 272 |
|
| 273 | + await rm(lastDreamPath); |
| 274 | + await dream.handler("", context(true)); |
| 275 | + await handlers.get("agent_end")!({ type: "agent_end", messages: [{ role: "assistant", stopReason: "toolUse" }] }); |
| 276 | + await handlers.get("agent_settled")!({ type: "agent_settled" }, context(true)); |
| 277 | + await assert.rejects(readFile(lastDreamPath), /ENOENT/); |
| 278 | + assert.equal(notifications.at(-1), "Dream did not complete; its timestamp was not updated."); |
| 279 | + |
| 280 | + const dreamTarget = join(root, "dream-target.txt"); |
| 281 | + await writeFile(dreamTarget, "keep this target"); |
| 282 | + await symlink(dreamTarget, lastDreamPath); |
| 283 | + await dream.handler("", context(true)); |
| 284 | + await handlers.get("agent_end")!({ type: "agent_end", messages: [{ role: "assistant", stopReason: "stop" }] }); |
| 285 | + await handlers.get("agent_settled")!({ type: "agent_settled" }, context(true)); |
| 286 | + assert.equal(await readFile(dreamTarget, "utf8"), "keep this target"); |
| 287 | + assert.equal((await lstat(lastDreamPath)).isSymbolicLink(), false); |
| 288 | + assert.ok(Number.isFinite(Date.parse((await readFile(lastDreamPath, "utf8")).trim()))); |
| 289 | + |
251 | 290 | process.argv.push(CHILD_PAYLOAD_ARG); |
252 | 291 | try { |
253 | 292 | await dream.handler("", context(true)); |
254 | | - assert.ok(messages[1]!.includes(JSON.stringify({ memory: ["stable fact"], user: ["likes concise replies"] }))); |
255 | | - assert.doesNotMatch(messages[1]!, /do not reread those files/); |
256 | | - assert.ok(messages[1]!.includes(`Read ${JSON.stringify(systemPath)} before semantic deduplication or editing.`)); |
| 293 | + assert.ok(messages.at(-1)!.includes(JSON.stringify({ memory: ["stable fact"], user: ["likes concise replies"] }))); |
| 294 | + assert.doesNotMatch(messages.at(-1)!, /do not reread those files/); |
| 295 | + assert.ok(messages.at(-1)!.includes(`Read ${JSON.stringify(systemPath)} before semantic deduplication or editing.`)); |
257 | 296 | } finally { |
258 | 297 | process.argv.pop(); |
259 | 298 | } |
260 | 299 |
|
261 | 300 | await writeFile(join(memoryDir, "MEMORY.md"), "changed fact"); |
262 | 301 | await dream.handler("", context(true)); |
263 | | - assert.ok(messages[2]!.includes(JSON.stringify({ memory: ["changed fact"], user: ["likes concise replies"] }))); |
| 302 | + assert.ok(messages.at(-1)!.includes(JSON.stringify({ memory: ["changed fact"], user: ["likes concise replies"] }))); |
264 | 303 |
|
265 | 304 | await rm(lastDreamPath); |
266 | 305 | await dream.handler("", context(true)); |
|
0 commit comments