diff --git a/src/server/message-branch.ts b/src/server/message-branch.ts index 61fd70a..837e38f 100644 --- a/src/server/message-branch.ts +++ b/src/server/message-branch.ts @@ -215,6 +215,23 @@ export function isRefreshableMessage(msg: BranchableMessage): boolean { return kind === "worker_questions" || kind === "worker_output"; } +/** 与 refreshMessage 同一套门槛:类型对,且有 worker 快照或该条检查点。 */ +export function canAttemptRefresh( + msg: BranchableMessage, + opts: { + lastWorkerId?: string; + hasLastWorkerSnapshot: boolean; + hasMessageCheckpoint: boolean; + }, +): boolean { + if (!isRefreshableMessage(msg)) return false; + const workerId = + (typeof msg.actor === "string" && msg.actor.trim()) || + opts.lastWorkerId?.trim(); + if (!workerId) return false; + return opts.hasMessageCheckpoint || opts.hasLastWorkerSnapshot; +} + export function findPrecedingUserIndex(messages: BranchableMessage[], fromIndex: number): number { for (let i = fromIndex - 1; i >= 0; i--) { if (messages[i].role === "user") return i; diff --git a/tests/message-branch.test.ts b/tests/message-branch.test.ts index 600d3cd..7bafc29 100644 --- a/tests/message-branch.test.ts +++ b/tests/message-branch.test.ts @@ -5,6 +5,8 @@ import { ensureBranchForEdit, recordPreMessageCheckpoint, switchBranchVariant, + canAttemptRefresh, + isRefreshableMessage, } from "../src/server/message-branch.js"; import { createSession } from "../src/runtime/phase-machine.js"; @@ -62,3 +64,61 @@ describe("message-branch", () => { expect(branch.variants[0].messages[0].text).toBe("首句"); }); }); + +describe("canAttemptRefresh", () => { + const output = { + id: "w1", + role: "system" as const, + text: "场面", + createdAt: "1", + kind: "worker_output", + actor: "world-simulator", + }; + + it("rejects user bubbles", () => { + expect( + isRefreshableMessage({ + id: "u1", + role: "user", + text: "行动", + createdAt: "1", + kind: "user_input", + }), + ).toBe(false); + expect( + canAttemptRefresh( + { + id: "u1", + role: "user", + text: "行动", + createdAt: "1", + kind: "user_input", + }, + { + lastWorkerId: "world-simulator", + hasLastWorkerSnapshot: true, + hasMessageCheckpoint: true, + }, + ), + ).toBe(false); + }); + + it("allows worker output when a snapshot exists", () => { + expect( + canAttemptRefresh(output, { + lastWorkerId: "world-simulator", + hasLastWorkerSnapshot: true, + hasMessageCheckpoint: false, + }), + ).toBe(true); + }); + + it("rejects worker output with no snapshot and no checkpoint", () => { + expect( + canAttemptRefresh(output, { + hasLastWorkerSnapshot: false, + hasMessageCheckpoint: false, + }), + ).toBe(false); + }); +});