重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
310
tests/prune-creation-messages.test.ts
Normal file
310
tests/prune-creation-messages.test.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
foldRunProcessMessages,
|
||||
pruneCreationUnitMessages,
|
||||
trimCreationDialogueMessages,
|
||||
selectCreationDialogueForAi,
|
||||
buildCreationDialogueTranscript,
|
||||
type PrunableChatMessage,
|
||||
} from "../src/server/prune-creation-messages.js";
|
||||
import {
|
||||
isDesignUnitArtifact,
|
||||
isFinalWorkerSetArtifact,
|
||||
isWorkerSetReadyForFinal,
|
||||
listCreationUnits,
|
||||
nextCreationUnitId,
|
||||
} from "../src/skills/creation-units.js";
|
||||
import type { ParsedWorkerSet } from "../src/skills/worker-set-parse.js";
|
||||
import { Blackboard } from "../src/blackboard/blackboard.js";
|
||||
import { compressAfterWorkerAccept } from "../src/runtime/compress-after-worker.js";
|
||||
import { hasAcceptedWorkerSet } from "../src/skills/worker-declaration.js";
|
||||
import { createSession } from "../src/runtime/phase-machine.js";
|
||||
|
||||
function msg(
|
||||
partial: Partial<PrunableChatMessage> & Pick<PrunableChatMessage, "id" | "role" | "text">,
|
||||
): PrunableChatMessage {
|
||||
return { createdAt: "2026-01-01T00:00:00.000Z", ...partial };
|
||||
}
|
||||
|
||||
describe("pruneCreationUnitMessages", () => {
|
||||
it("is a no-op: does not delete user-visible messages", () => {
|
||||
const messages: PrunableChatMessage[] = [
|
||||
msg({ id: "1", role: "user", text: "我想玩校园末日", kind: "user_input" }),
|
||||
msg({
|
||||
id: "2",
|
||||
role: "system",
|
||||
text: "[Worker] design-core 运行中",
|
||||
kind: "worker_running",
|
||||
actor: "design-core",
|
||||
}),
|
||||
msg({
|
||||
id: "3",
|
||||
role: "system",
|
||||
text: "[Worker] design-core 提问",
|
||||
kind: "worker_questions",
|
||||
actor: "design-core",
|
||||
}),
|
||||
msg({ id: "4", role: "user", text: "单角代入", kind: "user_input" }),
|
||||
msg({
|
||||
id: "5",
|
||||
role: "system",
|
||||
text: "[Worker] design-core 草稿",
|
||||
kind: "worker_output",
|
||||
actor: "design-core",
|
||||
body: "{ draft }",
|
||||
}),
|
||||
msg({
|
||||
id: "6",
|
||||
role: "system",
|
||||
text: "[Worker] design-core 定稿",
|
||||
kind: "worker_output",
|
||||
actor: "design-core",
|
||||
body: "{ final }",
|
||||
}),
|
||||
];
|
||||
const idsBefore = messages.map((m) => m.id);
|
||||
const result = pruneCreationUnitMessages(messages, "design-core");
|
||||
expect(result).toEqual({ removedCount: 0, productKept: false });
|
||||
expect(messages.map((m) => m.id)).toEqual(idsBefore);
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectCreationDialogueForAi / buildCreationDialogueTranscript", () => {
|
||||
it("keeps full messages for browsing; AI transcript only last AI + all users", () => {
|
||||
const messages: PrunableChatMessage[] = [
|
||||
msg({ id: "u1", role: "user", text: "荒野求生", kind: "user_input" }),
|
||||
msg({
|
||||
id: "a1",
|
||||
role: "system",
|
||||
text: "draft1",
|
||||
kind: "worker_output",
|
||||
actor: "design-core",
|
||||
}),
|
||||
msg({ id: "u2", role: "user", text: "无其它幸存者", kind: "user_input" }),
|
||||
msg({
|
||||
id: "run",
|
||||
role: "system",
|
||||
text: "running",
|
||||
kind: "worker_running",
|
||||
actor: "design-core",
|
||||
}),
|
||||
msg({
|
||||
id: "a2",
|
||||
role: "system",
|
||||
text: "draft2",
|
||||
kind: "worker_output",
|
||||
actor: "design-core",
|
||||
body: "{ v2 }",
|
||||
}),
|
||||
];
|
||||
|
||||
// 用户浏览:原数组不动
|
||||
const { removedCount } = trimCreationDialogueMessages(messages);
|
||||
expect(removedCount).toBe(0);
|
||||
expect(messages.map((m) => m.id)).toEqual(["u1", "a1", "u2", "run", "a2"]);
|
||||
|
||||
// 拼给 AI:用户全量 + 最后一次 AI
|
||||
expect(selectCreationDialogueForAi(messages).map((m) => m.id)).toEqual([
|
||||
"u1",
|
||||
"u2",
|
||||
"a2",
|
||||
]);
|
||||
const transcript = buildCreationDialogueTranscript(messages);
|
||||
expect(transcript).toContain("荒野求生");
|
||||
expect(transcript).toContain("无其它幸存者");
|
||||
expect(transcript).toContain("{ v2 }");
|
||||
expect(transcript).not.toContain("draft1");
|
||||
expect(transcript).not.toContain("running");
|
||||
});
|
||||
});
|
||||
|
||||
describe("foldRunProcessMessages", () => {
|
||||
it("marks process messages compressed without deleting", () => {
|
||||
const messages: PrunableChatMessage[] = [
|
||||
msg({
|
||||
id: "1",
|
||||
role: "system",
|
||||
text: "q",
|
||||
kind: "worker_questions",
|
||||
actor: "narrator",
|
||||
}),
|
||||
msg({
|
||||
id: "2",
|
||||
role: "system",
|
||||
text: "out",
|
||||
kind: "worker_output",
|
||||
actor: "narrator",
|
||||
}),
|
||||
];
|
||||
foldRunProcessMessages(messages, "narrator");
|
||||
expect(messages).toHaveLength(2);
|
||||
expect(messages.every((m) => m.compressed)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("listCreationUnits", () => {
|
||||
it("only lists filled fixed topics by default (not empty catalog blanks)", () => {
|
||||
const parsed: ParsedWorkerSet = {
|
||||
interaction: {
|
||||
user_stance: "单角",
|
||||
system_role: "世界",
|
||||
output: "叙事",
|
||||
},
|
||||
narrative_guide: "世界不有求必应",
|
||||
workers: [
|
||||
{
|
||||
ref: "narrator",
|
||||
duty: "转述",
|
||||
rationale: "可读",
|
||||
acceptance: "review",
|
||||
},
|
||||
{ ref: null, duty: "缺口", gap: "待定" },
|
||||
],
|
||||
resident_context: [
|
||||
{ id: "tone", content: "文风克制", position: "static" },
|
||||
],
|
||||
};
|
||||
const units = listCreationUnits(parsed, {
|
||||
acceptedUnitIds: ["fixed:interaction", "skeleton:interaction"],
|
||||
currentUnitId: "worker:narrator",
|
||||
});
|
||||
expect(units.map((u) => u.id)).toEqual([
|
||||
"phase:core",
|
||||
"fixed:narrative_guide",
|
||||
"worker:narrator",
|
||||
"worker:#2",
|
||||
"resident:tone",
|
||||
]);
|
||||
expect(units.find((u) => u.id === "phase:core")?.accepted).toBe(true);
|
||||
expect(units.every((u) => u.id !== "fixed:input_protocol" || u.filled)).toBe(
|
||||
true,
|
||||
);
|
||||
expect(units.find((u) => u.id === "fixed:input_protocol")).toBeUndefined();
|
||||
expect(units.find((u) => u.id === "worker:narrator")?.current).toBe(true);
|
||||
});
|
||||
|
||||
it("nextCreationUnitId prefers foundation fixed tags before workers", () => {
|
||||
const parsed: ParsedWorkerSet = {
|
||||
interaction: {
|
||||
user_stance: "单角",
|
||||
system_role: "世界",
|
||||
output: "叙事",
|
||||
},
|
||||
workers: [{ ref: "narrator", duty: "转述", rationale: "r" }],
|
||||
};
|
||||
expect(nextCreationUnitId(parsed, ["fixed:interaction"])).toBe(
|
||||
"fixed:narrative_guide",
|
||||
);
|
||||
});
|
||||
|
||||
it("nextCreationUnitId reaches workers after weighty fixed accepted", () => {
|
||||
const parsed: ParsedWorkerSet = {
|
||||
interaction: {
|
||||
user_stance: "单角",
|
||||
system_role: "世界",
|
||||
output: "叙事",
|
||||
},
|
||||
narrative_guide: "边界清晰",
|
||||
workers: [{ ref: "narrator", duty: "转述", rationale: "r" }],
|
||||
};
|
||||
expect(
|
||||
nextCreationUnitId(parsed, [
|
||||
"fixed:interaction",
|
||||
"fixed:narrative_guide",
|
||||
"fixed:aesthetics",
|
||||
"fixed:input_protocol",
|
||||
"fixed:core_premises",
|
||||
]),
|
||||
).toBe("worker:narrator");
|
||||
});
|
||||
});
|
||||
|
||||
describe("artifact classification", () => {
|
||||
it("distinguishes unit vs final", () => {
|
||||
expect(
|
||||
isDesignUnitArtifact({
|
||||
workerId: "design-core",
|
||||
outputTags: ["设计.worker集.草稿"],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isFinalWorkerSetArtifact({
|
||||
workerId: "design-core",
|
||||
outputTags: ["设计.worker集"],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isDesignUnitArtifact({
|
||||
workerId: "design-core",
|
||||
outputTags: ["设计.worker集", "设计.worker集.草稿"],
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasAcceptedWorkerSet", () => {
|
||||
it("does not treat draft-only accepted artifact as ready", () => {
|
||||
const session = {
|
||||
...createSession(),
|
||||
artifacts: [
|
||||
{
|
||||
id: "a1",
|
||||
workerId: "design-core",
|
||||
outputTags: ["设计.worker集.草稿"],
|
||||
status: "accepted" as const,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(hasAcceptedWorkerSet(session)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("compressAfterWorkerAccept unit mode", () => {
|
||||
it("keeps draft active and does not write brief as final", () => {
|
||||
const bb = new Blackboard();
|
||||
bb.write({
|
||||
tag: "设计.worker集.草稿",
|
||||
content: '{ "workers": [{ "ref": "narrator" }] }',
|
||||
source: "design-core",
|
||||
});
|
||||
bb.write({
|
||||
tag: "用户.worker答复",
|
||||
content: "过程问答",
|
||||
source: "user",
|
||||
});
|
||||
const result = compressAfterWorkerAccept({
|
||||
blackboard: bb,
|
||||
workerId: "design-core",
|
||||
outputTags: ["设计.worker集.草稿"],
|
||||
summary: "单位 worker:narrator",
|
||||
mode: "unit",
|
||||
});
|
||||
expect(result.archivedTags).toContain("用户.worker答复");
|
||||
expect(bb.getContentByTag("设计.worker集.草稿")).toContain("narrator");
|
||||
expect(bb.getLatestByTag("设计.worker集.草稿")?.metadata?.role).not.toBe(
|
||||
"archived",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isWorkerSetReadyForFinal", () => {
|
||||
it("requires accepted filled units", () => {
|
||||
const parsed: ParsedWorkerSet = {
|
||||
interaction: {
|
||||
user_stance: "单角",
|
||||
system_role: "世界",
|
||||
output: "叙事",
|
||||
},
|
||||
workers: [{ ref: "narrator", duty: "转述", rationale: "r" }],
|
||||
};
|
||||
expect(isWorkerSetReadyForFinal(parsed, []).ready).toBe(false);
|
||||
expect(
|
||||
isWorkerSetReadyForFinal(parsed, [
|
||||
"fixed:interaction",
|
||||
"worker:narrator",
|
||||
]).ready,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user