Files
boss/tests/device-heartbeat-codex-message-sync.test.ts
2026-06-08 12:22:50 +08:00

623 lines
21 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import { mkdtemp, rm } from "node:fs/promises";
let runtimeRoot = "";
let readState: (typeof import("../src/lib/boss-data"))["readState"];
let upsertDeviceHeartbeat: (typeof import("../src/lib/boss-data"))["upsertDeviceHeartbeat"];
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
async function setup() {
if (runtimeRoot) return;
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-device-heartbeat-message-sync-"));
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
const data = await import("../src/lib/boss-data.ts");
readState = data.readState;
upsertDeviceHeartbeat = data.upsertDeviceHeartbeat;
writeState = data.writeState;
}
test.after(async () => {
if (runtimeRoot) {
await rm(runtimeRoot, { recursive: true, force: true });
}
});
test("device heartbeat records recent codex desktop activity without mirroring reply text", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-sync",
token: "device-message-sync-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-main",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-main",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-main",
);
assert.ok(importedProject, "expected heartbeat auto-import to create the thread conversation");
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
lastActiveAt: "2026-04-20T10:02:10.000Z",
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
body: "桌面 Codex 已经把会话实时同步链路修好了。",
sentAt: "2026-04-20T10:02:10.000Z",
},
],
},
],
});
let nextState = await readState();
let nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const mirroredMessage = nextProject?.messages.find(
(message) => message.externalMessageId === "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
);
assert.equal(mirroredMessage, undefined);
assert.equal(nextProject?.messages.some((message) => message.externalMessageId), false);
assert.equal(nextProject?.threadMeta.lastObservedCodexActivityAt, "2026-04-20T10:02:10.000Z");
assert.equal(nextProject?.unreadCount, 0);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
body: "桌面 Codex 已经把会话实时同步链路修好了。",
sentAt: "2026-04-20T10:02:10.000Z",
},
],
},
],
});
nextState = await readState();
nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const mirroredCopies = nextProject?.messages.filter(
(message) => message.externalMessageId === "codex-thread:thread-boss-main:2026-04-20T10:02:10.000Z:reply-1",
);
assert.equal(mirroredCopies?.length, 0);
assert.equal(nextProject?.unreadCount, 0);
});
test("device heartbeat records codex activity without appending uncorrelated desktop replies", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-activity-only",
token: "device-message-activity-only-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "juyuwan",
folderRef: "/Users/kris/Documents/juyuwan",
threadId: "thread-juyuwan-activity-only",
threadDisplayName: "juyuwan",
codexFolderRef: "/Users/kris/Documents/juyuwan",
codexThreadRef: "thread-juyuwan-activity-only",
lastActiveAt: "2026-06-07T16:30:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-juyuwan-activity-only",
);
assert.ok(importedProject, "expected heartbeat auto-import to create the thread conversation");
importedProject!.messages = [];
importedProject!.preview = "";
importedProject!.unreadCount = 0;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
lastActiveAt: "2026-06-07T16:34:15.000Z",
recentAssistantMessages: [
{
messageId: "codex-thread:thread-juyuwan-activity-only:2026-06-07T16:34:15.000Z:final-1",
body: "已完成下一步并实机验证了。APK 已安装到 K30 Ultra。",
sentAt: "2026-06-07T16:34:15.000Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const mirroredMessage = nextProject?.messages.find(
(message) =>
message.externalMessageId ===
"codex-thread:thread-juyuwan-activity-only:2026-06-07T16:34:15.000Z:final-1",
);
const progressEvent = nextState.threadProgressEvents.find(
(event) => event.projectId === importedProject?.id && event.createdAt === "2026-06-07T16:34:15.000Z",
);
assert.equal(mirroredMessage, undefined);
assert.equal(nextProject?.messages.length, 0);
assert.equal(nextProject?.preview, "");
assert.equal(nextProject?.unreadCount, 0);
assert.equal(nextProject?.threadMeta.lastObservedCodexActivityAt, "2026-06-07T16:34:15.000Z");
assert.ok(progressEvent, "expected heartbeat activity to remain visible as a thread progress event");
});
test("device heartbeat does not duplicate a reply already written by task completion", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-completion-dedupe",
token: "device-message-completion-dedupe-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-completion-dedupe",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-completion-dedupe",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-completion-dedupe",
);
assert.ok(importedProject);
const directReplyBody = "BOSS回归APP消息已收到。";
const targetProject = initialState.projects.find((project) => project.id === importedProject?.id);
assert.ok(targetProject);
targetProject!.messages = [
{
id: "msg-direct-completion",
sender: "device",
senderLabel: "Boss开发主线程",
body: directReplyBody,
sentAt: "2026-04-20T10:02:10.000Z",
kind: "text",
},
];
targetProject!.preview = directReplyBody;
targetProject!.lastMessageAt = "2026-04-20T10:02:10.000Z";
targetProject!.unreadCount = 1;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-completion-dedupe:2026-04-20T10:02:10.001Z:reply-1",
body: directReplyBody,
sentAt: "2026-04-20T10:02:10.001Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const matchingReplies = nextProject?.messages.filter((message) => message.body === directReplyBody);
assert.equal(matchingReplies?.length, 1);
assert.equal(matchingReplies?.[0]?.externalMessageId, undefined);
assert.equal(nextProject?.unreadCount, 1);
assert.equal(nextProject?.preview, directReplyBody);
});
test("device heartbeat does not duplicate a takeover reply already written by master agent", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-master-dedupe",
token: "device-message-master-dedupe-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-master-dedupe",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-master-dedupe",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-master-dedupe",
);
assert.ok(importedProject);
const replyBody = "主Agent托管链路已收到。";
const targetProject = initialState.projects.find((project) => project.id === importedProject?.id);
assert.ok(targetProject);
targetProject!.messages = [
{
id: "msg-master-completion",
sender: "master",
senderLabel: "主 Agent",
body: replyBody,
sentAt: "2026-04-20T10:02:10.000Z",
kind: "text",
},
];
targetProject!.preview = replyBody;
targetProject!.lastMessageAt = "2026-04-20T10:02:10.000Z";
targetProject!.unreadCount = 1;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-master-dedupe:2026-04-20T10:02:10.001Z:reply-1",
body: replyBody,
sentAt: "2026-04-20T10:02:10.001Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
const matchingReplies = nextProject?.messages.filter((message) => message.body === replyBody);
assert.equal(matchingReplies?.length, 1);
assert.equal(matchingReplies?.[0]?.sender, "master");
assert.equal(matchingReplies?.[0]?.externalMessageId, undefined);
assert.equal(nextProject?.unreadCount, 1);
assert.equal(nextProject?.preview, replyBody);
});
test("device heartbeat ignores commentary and final assistant text as chat messages", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-phase",
token: "device-message-phase-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-phase",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-phase",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-phase",
);
assert.ok(importedProject);
importedProject!.messages = [];
importedProject!.preview = "";
importedProject!.unreadCount = 0;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
lastActiveAt: "2026-04-20T10:05:00.000Z",
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-phase:2026-04-20T10:03:00.000Z:commentary-1",
body: "我先检查聊天折叠链路,确认过程消息不会直接展开。",
sentAt: "2026-04-20T10:03:00.000Z",
phase: "commentary",
},
{
messageId: "codex-thread:thread-boss-phase:2026-04-20T10:05:00.000Z:final-1",
body: "这轮已经完成折叠修复,未读现在只会算最终结果。",
sentAt: "2026-04-20T10:05:00.000Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-phase",
);
const processMessage = nextProject?.messages.find(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-phase:2026-04-20T10:03:00.000Z:commentary-1",
);
const finalMessage = nextProject?.messages.find(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-phase:2026-04-20T10:05:00.000Z:final-1",
);
assert.ok(nextProject);
assert.equal(processMessage, undefined);
assert.equal(finalMessage, undefined);
assert.equal(nextProject?.messages.length, 0);
assert.equal(nextProject?.preview, "");
assert.equal(nextProject?.unreadCount, 0);
assert.equal(nextProject?.threadMeta.lastObservedCodexActivityAt, "2026-04-20T10:05:00.000Z");
});
test("device heartbeat does not replay old desktop replies after conversation history is cleared", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-reset",
token: "device-message-reset-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-reset",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-reset",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const initialState = await readState();
const importedProject = initialState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-reset",
);
assert.ok(importedProject);
initialState.conversationHistoryClearedAt = "2026-04-20T10:10:00.000Z";
const targetProject = initialState.projects.find((project) => project.id === importedProject?.id);
assert.ok(targetProject);
targetProject!.messages = [];
targetProject!.preview = "";
targetProject!.unreadCount = 0;
await writeState(initialState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
lastActiveAt: "2026-04-20T10:12:00.000Z",
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-reset:2026-04-20T10:05:00.000Z:old-final",
body: "这条旧回复不应该在清空历史后被重新导回。",
sentAt: "2026-04-20T10:05:00.000Z",
phase: "final_answer",
},
{
messageId: "codex-thread:thread-boss-reset:2026-04-20T10:11:00.000Z:new-final",
body: "这条新回复应该继续同步回来。",
sentAt: "2026-04-20T10:11:00.000Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find((project) => project.id === importedProject?.id);
assert.ok(nextProject);
assert.equal(
nextProject?.messages.some(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-reset:2026-04-20T10:05:00.000Z:old-final",
),
false,
);
assert.equal(
nextProject?.messages.some(
(message) =>
message.externalMessageId === "codex-thread:thread-boss-reset:2026-04-20T10:11:00.000Z:new-final",
),
false,
);
assert.equal(nextProject?.preview, "");
assert.equal(nextProject?.unreadCount, 0);
assert.equal(nextProject?.threadMeta.lastObservedCodexActivityAt, "2026-04-20T10:12:00.000Z");
});
test("device heartbeat process text is kept out of the chat transcript", async () => {
await setup();
const seedHeartbeat = {
deviceId: "device-message-legacy-process",
token: "device-message-legacy-process-token",
name: "Mac Studio",
avatar: "M",
account: "krisolo",
status: "online" as const,
quota5h: 76,
quota7d: 85,
projects: [],
endpoint: "mac://kris.local",
projectCandidates: [
{
folderName: "boss",
folderRef: "/Users/kris/code/boss",
threadId: "thread-boss-legacy-process",
threadDisplayName: "Boss开发主线程",
codexFolderRef: "/Users/kris/code/boss",
codexThreadRef: "thread-boss-legacy-process",
lastActiveAt: "2026-04-20T10:00:00.000Z",
suggestedImport: true,
},
],
};
await upsertDeviceHeartbeat(seedHeartbeat);
await upsertDeviceHeartbeat(seedHeartbeat);
const resetState = await readState();
resetState.conversationHistoryClearedAt = undefined;
const importedProject = resetState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-legacy-process",
);
assert.ok(importedProject);
importedProject!.messages = [];
importedProject!.preview = "";
importedProject!.unreadCount = 0;
await writeState(resetState);
await upsertDeviceHeartbeat({
...seedHeartbeat,
projectCandidates: [
{
...seedHeartbeat.projectCandidates[0],
lastActiveAt: "2026-04-20T10:05:00.000Z",
recentAssistantMessages: [
{
messageId: "codex-thread:thread-boss-legacy-process:2026-04-20T10:03:00.000Z:commentary-legacy",
body: "我继续把这条链路又往下收了一层,补的是“历史脏消息”的兼容,不只是新消息规则。",
sentAt: "2026-04-20T10:03:00.000Z",
phase: "commentary",
},
{
messageId: "codex-thread:thread-boss-legacy-process:2026-04-20T10:05:00.000Z:final-1",
body: "这轮已经完成折叠修复,未读现在只会算最终结果。",
sentAt: "2026-04-20T10:05:00.000Z",
phase: "final_answer",
},
],
},
],
});
const nextState = await readState();
const nextProject = nextState.projects.find(
(project) => project.threadMeta.codexThreadRef === "thread-boss-legacy-process",
);
const legacyProcessMessage = nextProject?.messages.find(
(message) =>
message.externalMessageId ===
"codex-thread:thread-boss-legacy-process:2026-04-20T10:03:00.000Z:commentary-legacy",
);
assert.ok(nextProject);
assert.equal(legacyProcessMessage, undefined);
assert.equal(nextProject?.messages.length, 0);
assert.equal(nextProject?.preview, "");
assert.equal(nextProject?.unreadCount, 0);
assert.equal(nextProject?.threadMeta.lastObservedCodexActivityAt, "2026-04-20T10:05:00.000Z");
});