feat: ship enterprise control and desktop governance
This commit is contained in:
522
tests/device-heartbeat-codex-message-sync.test.ts
Normal file
522
tests/device-heartbeat-codex-message-sync.test.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
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 mirrors recent codex desktop replies into the matching thread conversation once", 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],
|
||||
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.ok(mirroredMessage);
|
||||
assert.equal(mirroredMessage?.sender, "device");
|
||||
assert.equal(mirroredMessage?.senderLabel, "Boss开发主线程");
|
||||
assert.equal(mirroredMessage?.body, "桌面 Codex 已经把会话实时同步链路修好了。");
|
||||
assert.equal(nextProject?.lastMessageAt, "2026-04-20T10:02:10.000Z");
|
||||
assert.equal(nextProject?.preview, "桌面 Codex 已经把会话实时同步链路修好了。");
|
||||
assert.equal(nextProject?.unreadCount, 1);
|
||||
|
||||
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, 1);
|
||||
assert.equal(nextProject?.unreadCount, 1);
|
||||
});
|
||||
|
||||
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 does not count commentary replies as unread and keeps only the final result unread", 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);
|
||||
|
||||
await upsertDeviceHeartbeat({
|
||||
...seedHeartbeat,
|
||||
projectCandidates: [
|
||||
{
|
||||
...seedHeartbeat.projectCandidates[0],
|
||||
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?.kind, "thread_process");
|
||||
assert.equal(finalMessage?.kind, "text");
|
||||
assert.equal(nextProject?.preview, "这轮已经完成折叠修复,未读现在只会算最终结果。");
|
||||
assert.equal(nextProject?.unreadCount, 1);
|
||||
});
|
||||
|
||||
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",
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(nextProject?.preview, "这条新回复应该继续同步回来。");
|
||||
assert.equal(nextProject?.unreadCount, 1);
|
||||
});
|
||||
|
||||
test("device heartbeat legacy process text is normalized to thread_process and does not become preview", 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;
|
||||
await writeState(resetState);
|
||||
|
||||
await upsertDeviceHeartbeat({
|
||||
...seedHeartbeat,
|
||||
projectCandidates: [
|
||||
{
|
||||
...seedHeartbeat.projectCandidates[0],
|
||||
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?.kind, "thread_process");
|
||||
assert.equal(nextProject?.preview, "这轮已经完成折叠修复,未读现在只会算最终结果。");
|
||||
assert.equal(nextProject?.unreadCount, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user