deskpaw-memory/test/topics.test.js

206 lines
14 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { MemoryStore } from "../src/store.js";
import { workdayAt } from "../src/workday.js";
function withStore(run) {
const directory = mkdtempSync(join(tmpdir(), "deskpaw-topics-"));
const store = new MemoryStore(join(directory, "memory.sqlite"), join(directory, "bundles"));
try { run(store); } finally {
store.close();
rmSync(directory, { recursive: true, force: true });
}
}
test("workday has a fixed 03:30 Asia/Shanghai boundary", () => {
assert.equal(workdayAt(Date.parse("2026-09-22T19:29:59Z")), "2026-09-22");
assert.equal(workdayAt(Date.parse("2026-09-22T19:30:00Z")), "2026-09-23");
});
test("lower maintenance groups chat into a stable cross-day topic", () => withStore((store) => {
const first = Date.parse("2026-09-22T19:29:00Z");
const second = Date.parse("2026-09-22T19:31:00Z");
store.rememberInteractions({ interactions: [
{ interaction_id: "one", source: "feishu", user: "我们聊聊记忆架构", manager: "可以讨论短期记忆。", created_at: first },
{ interaction_id: "two", source: "terminal", user: "继续聊记忆架构", manager: "继续讨论话题单元。", created_at: second },
] });
const firstPlan = store.maintenancePlan({ now: second });
assert.equal(firstPlan.workday, "2026-09-22");
assert.deepEqual(firstPlan.interactions.map((item) => item.interaction_id), ["one"]);
assert.deepEqual(store.commitMaintenance({ workday: firstPlan.workday, topics: [
{ title: "记忆架构", category: "chat", summary: "讨论短期记忆。", result: "待续", issues: "话题边界", interaction_ids: ["one"] },
] }), { assigned: 1, facts: 0, skills: 0 });
const firstTopic = store.recall({ query: "记忆架构" }).related_topics[0];
const secondPlan = store.maintenancePlan({ now: second });
assert.equal(secondPlan.workday, "2026-09-23");
assert.deepEqual(secondPlan.interactions.map((item) => item.interaction_id), ["two"]);
store.commitMaintenance({ workday: secondPlan.workday, topics: [
{ topic_id: firstTopic.topic_id, title: "记忆架构", category: "chat", summary: "讨论话题单元。", result: "确定 03:30 边界", issues: "待实施", interaction_ids: ["two"] },
] });
const detail = store.topicDetail({ topic_id: firstTopic.topic_id });
assert.equal(detail.segments.length, 2);
assert.deepEqual(detail.interactions.map((item) => item.interaction_id), ["one", "two"]);
assert.equal(store.recall({ query: "记忆架构" }).recent_workdays.length, 2);
}));
test("later batches see the previous segment for the same workday", () => withStore((store) => {
const at = Date.parse("2026-09-23T04:00:00Z");
store.rememberInteractions({ interactions: [
{ interaction_id: "first", source: "terminal", user: "讨论记忆压缩", manager: "记录短期", created_at: at },
{ interaction_id: "later", source: "terminal", user: "继续讨论记忆压缩", manager: "讨论长期", created_at: at + 60_000 },
] });
const workday = workdayAt(at);
store.commitMaintenance({ workday, topics: [
{ title: "记忆压缩", category: "chat", summary: "短期设计", segment_summary: "确定短期记忆的设计", result: "已定义短期", issues: "长期待议", interaction_ids: ["first"] },
] });
const plan = store.maintenancePlan({ now: at + 60_000 });
assert.deepEqual(plan.interactions.map((row) => row.interaction_id), ["later"]);
const topic = plan.topics.find((row) => row.title === "记忆压缩");
assert.equal(topic.workday_summary, "确定短期记忆的设计");
assert.equal(topic.workday_result, "已定义短期");
assert.equal(topic.workday_issues, "长期待议");
}));
test("facts require verbatim user evidence and skills remain drafts", () => withStore((store) => {
store.rememberInteractions({ interactions: [
{ interaction_id: "pref", source: "feishu", user: "我喜欢简洁的回复", manager: "知道了", created_at: Date.now() },
] });
const workday = store.maintenancePlan().workday;
const result = store.commitMaintenance({ workday, topics: [], facts: [
{ category: "profile", profile_kind: "preference", statement: "用户偏好简洁回复", scope: "global", evidence_interaction_id: "pref", user_quote: "我喜欢简洁的回复" },
{ category: "environment", statement: "用户电脑永久需要 VPN", scope: "global", evidence_interaction_id: "pref", user_quote: "用户电脑永久需要 VPN" },
], skills: [
{ title: "回复", body: "简短回答", evidence_interaction_id: "pref", user_quote: "我喜欢简洁的回复" },
] });
assert.deepEqual(result, { assigned: 0, facts: 1, skills: 1 });
const recalled = store.recall({ query: "回复偏好" });
assert.equal(recalled.core_facts[0].statement, "用户偏好简洁回复");
const skill = store.db.prepare("SELECT skill_id,status FROM memory_skills").get();
assert.equal(skill.status, "draft");
assert.throws(() => store.setSkillStatus({ skill_id: skill.skill_id, status: "active" }), /confirmation/);
assert.equal(store.setSkillStatus({ skill_id: skill.skill_id, status: "active", confirmed_by_user: true }).status, "active");
assert.equal(store.recall({ query: "简短回答" }).relevant_skills[0].skill_id, skill.skill_id);
}));
test("nightly review has a fixed prior workday and does not rewrite raw interactions", (t) => withStore((store) => {
const eventAt = Date.parse("2026-09-22T12:00:00Z");
const reviewAt = Date.parse("2026-09-22T19:31:00Z");
t.mock.method(Date, "now", () => reviewAt);
store.rememberInteractions({ interactions: [
{ interaction_id: "night", source: "terminal", user: "讨论一下话题", manager: "先记录", created_at: eventAt },
] });
store.commitMaintenance({ workday: "2026-09-22", topics: [
{ title: "话题讨论", category: "chat", summary: "初稿", result: "未完成", issues: "待续", interaction_ids: ["night"] },
] });
const plan = store.maintenancePlan({ now: reviewAt });
assert.equal(plan.review_workday, "2026-09-22");
assert.equal(plan.review_topics.length, 1);
const topicId = plan.review_topics[0].topic_id;
store.commitMaintenance({ workday: plan.workday, review_workday: plan.review_workday, nightly: true,
topic_reviews: [{ topic_id: topicId, summary: "讨论话题边界", result: "形成初步定义", issues: "待验证" }] });
assert.equal(store.maintenanceStatus(reviewAt).nightly_due, false);
assert.equal(store.topicDetail({ topic_id: topicId }).segments[0].summary, "讨论话题边界");
assert.equal(store.topicDetail({ topic_id: topicId }).interactions[0].user_text, "讨论一下话题");
}));
test("relevance prioritizes linked topics and requires two workdays for device facts", () => withStore((store) => {
const first = Date.parse("2026-09-21T04:00:00Z");
const second = Date.parse("2026-09-22T04:00:00Z");
store.rememberInteractions({ interactions: [
{ interaction_id: "linked", source: "feishu", user: "讨论键鼠", manager: "记录了", task_id: "task-1", created_at: first },
{ interaction_id: "scope", source: "terminal", user: "这台 Windows 电脑运行 Codex 要开 VPN", manager: "收到", created_at: first },
{ interaction_id: "scope-again", source: "terminal", user: "这台 Windows 电脑运行 Codex 要开 VPN", manager: "已复核", created_at: second },
] });
store.commitMaintenance({ workday: workdayAt(first), topics: [
{ title: "键鼠", category: "execution", summary: "讨论纯 HID 控制", interaction_ids: ["linked"] },
{ title: "网络", category: "chat", summary: "讨论 Codex 网络", interaction_ids: ["scope"] },
], facts: [
{ category: "environment", statement: "运行 Codex 要开 VPN", scope: "windows-pc", evidence_interaction_id: "scope", user_quote: "这台 Windows 电脑运行 Codex 要开 VPN" },
] });
const linked = store.recall({ query: "刚才说的事", previous_interaction_id: "linked", task_id: "task-1" });
assert.equal(linked.related_topics[0].title, "键鼠");
assert.equal(store.recall({ query: "Codex", scope: "windows-pc" }).related_facts.length, 0);
store.commitMaintenance({ workday: workdayAt(second), facts: [
{ category: "environment", statement: "运行 Codex 要开 VPN", scope: "windows-pc", evidence_interaction_id: "scope-again", user_quote: "这台 Windows 电脑运行 Codex 要开 VPN" },
] });
assert.equal(store.recall({ query: "Codex" }).related_facts.length, 0);
assert.equal(store.recall({ query: "Codex", scope: "windows-pc" }).related_facts.length, 1);
}));
test("episodic profile claims stay out of core while a current project is related-only", () => withStore((store) => {
store.rememberInteractions({ interactions: [
{ interaction_id: "desk", source: "feishu", user: "我今天就在电脑前,可以自己核对效果", manager: "收到", created_at: Date.now() },
{ interaction_id: "project", source: "feishu", user: "我在推进纯 HDMI 加 HID 控制电脑的优化", manager: "了解", created_at: Date.now() },
{ interaction_id: "schedule", source: "feishu", user: "每天 8:30 分享推文到三个群", manager: "收到", created_at: Date.now() },
] });
store.commitMaintenance({ workday: store.maintenancePlan().workday, facts: [
{ category: "profile", profile_kind: "identity", statement: "用户当时在电脑前可自行核对", scope: "global", evidence_interaction_id: "desk", user_quote: "我今天就在电脑前" },
{ category: "profile", statement: "用户在推进 HDMI HID 优化", scope: "global", evidence_interaction_id: "project", user_quote: "我在推进纯 HDMI 加 HID" },
{ category: "profile", profile_kind: "work_style", statement: "用户每天 8:30 分享推文到三个群", scope: "global", evidence_interaction_id: "schedule", user_quote: "每天 8:30 分享推文到三个群" },
{ category: "project", statement: "用户在推进纯 HDMI 与 HID 控制电脑项目", scope: "global", evidence_interaction_id: "project", user_quote: "我在推进纯 HDMI 加 HID" },
] });
const recall = store.recall({ query: "HDMI HID 项目" });
assert.equal(recall.core_facts.length, 0);
assert.equal(recall.related_facts.length, 1);
assert.equal(recall.related_facts[0].category, "project");
}));
test("nightly review expires stale candidates and inactive facts cannot be revived by maintenance", () => withStore((store) => {
const old = Date.now() - 5 * 86_400_000;
store.rememberInteractions({ interactions: [
{ interaction_id: "old-device", source: "feishu", user: "鼠标临时没有反应", manager: "已记录", created_at: old },
{ interaction_id: "pref", source: "feishu", user: "我喜欢简洁回答", manager: "收到", created_at: old },
] });
const day = workdayAt(old);
store.commitMaintenance({ workday: day, facts: [
{ category: "environment", statement: "鼠标没有反应", scope: "device-a", evidence_interaction_id: "old-device", user_quote: "鼠标临时没有反应" },
{ category: "profile", profile_kind: "preference", statement: "用户喜欢简洁回答", scope: "global", evidence_interaction_id: "pref", user_quote: "我喜欢简洁回答" },
] });
const profileId = store.db.prepare("SELECT fact_id FROM memory_facts WHERE category='profile'").get().fact_id;
store.commitMaintenance({ workday: workdayAt(Date.now()), nightly: true,
fact_reviews: [{ fact_id: profileId, status: "inactive" }] });
assert.deepEqual(store.db.prepare("SELECT status FROM memory_facts ORDER BY category").all().map((row) => row.status), ["inactive", "inactive"]);
store.commitMaintenance({ workday: day, facts: [
{ category: "profile", profile_kind: "preference", statement: "用户喜欢简洁回答", scope: "global", evidence_interaction_id: "pref", user_quote: "我喜欢简洁回答" },
] });
assert.equal(store.db.prepare("SELECT status FROM memory_facts WHERE fact_id=?").get(profileId).status, "inactive");
assert.equal(store.maintenancePlan().facts.length, 0);
}));
test("one observed environment failure stays a candidate until a second workday corroborates it", () => withStore((store) => {
const first = Date.parse("2026-09-21T04:00:00Z");
const second = Date.parse("2026-09-22T04:00:00Z");
store.completeTask({ task_id: "check-1", objective: "检查网络", status: "completed", result: "Codex 直连超时" });
store.completeTask({ task_id: "check-2", objective: "再次检查网络", status: "completed", result: "Codex 直连超时" });
store.rememberInteractions({ interactions: [
{ interaction_id: "obs-1", source: "terminal", user: "检查网络", manager: "任务完成", task_id: "check-1", created_at: first },
{ interaction_id: "obs-2", source: "terminal", user: "再次检查网络", manager: "任务完成", task_id: "check-2", created_at: second },
] });
const fact = (id) => ({ category: "environment", statement: "该设备 Codex 直连可能超时", scope: "pc-a",
evidence_interaction_id: id, evidence_quote: "Codex 直连超时" });
store.commitMaintenance({ workday: workdayAt(first), facts: [fact("obs-1")] });
assert.equal(store.db.prepare("SELECT status FROM memory_facts").get().status, "candidate");
assert.equal(store.recall({ query: "Codex", scope: "pc-a" }).related_facts.length, 0);
store.commitMaintenance({ workday: workdayAt(second), facts: [fact("obs-2")] });
assert.equal(store.db.prepare("SELECT status FROM memory_facts").get().status, "active");
assert.equal(store.recall({ query: "Codex", scope: "pc-a" }).related_facts.length, 1);
}));
test("topic detail pagination does not skip interactions with identical timestamps", () => withStore((store) => {
const at = Date.parse("2026-09-23T04:00:00Z");
store.rememberInteractions({ interactions: [
{ interaction_id: "a", source: "terminal", user: "第一条", manager: "收到", created_at: at },
{ interaction_id: "b", source: "terminal", user: "第二条", manager: "收到", created_at: at },
] });
store.commitMaintenance({ workday: workdayAt(at), topics: [
{ title: "连续对话", summary: "两条对话", interaction_ids: ["a", "b"] },
] });
const id = store.recall({ query: "连续对话" }).related_topics[0].topic_id;
const first = store.topicDetail({ topic_id: id, limit: 1 });
const second = store.topicDetail({ topic_id: id, limit: 1, after: first.next_after });
assert.deepEqual([first.interactions[0].interaction_id, second.interactions[0].interaction_id], ["a", "b"]);
}));