623 lines
21 KiB
TypeScript
623 lines
21 KiB
TypeScript
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");
|
||
});
|