Files
writing-agent/tests/worker-set-view.test.ts
T
chenmoranzhiandCursor 3d720f23fe 主世界层直接写正文,转述与旁观改读用户展示输出。
去掉裁决包主路径,让无转述时可直出终稿、有转述时再改写镶壳。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 02:18:14 +08:00

174 lines
6.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { parseWorkerSetYaml } from "../src/skills/worker-set-parse.js";
import { formatWorkerSetForUser } from "../src/skills/worker-set-view.js";
const FULL_SAMPLE = `
version: 1
form_summary: 西幻升级交互,带状态跟踪
play_morphology: action_reaction_loop
workers:
- ref: world-simulator
duty: 世界推进与裁决
when: 每轮用户输入后
context:
static:
- 设计.worker集
- 世界.蓝图.确认稿
dynamic:
- 用户.最新输入
- 运行.事件流
outputs:
- 输出.用户展示
- 运行.事件流
- ref: narrator
duty: 组装用户可见回复
when: 中间产物齐后
presentation:
tone: 西幻
avoid: [空泛公告]
context:
static:
- 叙事.指南.确认稿
dynamic:
- 输出.用户展示
outputs:
- 输出.用户展示
instantiate_hints:
invoke: [opening-generator]
tag_flow:
- "用户.最新输入 → 输出.用户展示"
- "输出.用户展示 → 输出.用户展示(转述终稿)"
`;
describe("formatWorkerSetForUser", () => {
it("builds Chinese worker cards with context order", () => {
const parsed = parseWorkerSetYaml(FULL_SAMPLE)!;
const view = formatWorkerSetForUser(parsed)!;
expect(view.headline).toContain("西幻");
expect(view.playModeLabel).toBe("行动–反应循环");
expect(view.workers).toHaveLength(2);
expect(view.workers[0].displayName).toBe("主世界层");
expect(view.playSlots?.some((s) => s.id === "gm" && s.enabled)).toBe(true);
expect(view.contextOrder?.slots?.length).toBeGreaterThan(0);
expect(view.creationUnits?.map((u) => u.id)).toEqual(
expect.arrayContaining([
"fixed:aesthetics", // sample 含 narrator.presentation
"worker:world-simulator",
"worker:narrator",
]),
);
// 未写入的示例话题不出现(不是填空表)
expect(view.creationUnits?.some((u) => u.id === "fixed:input_protocol")).toBe(
false,
);
expect(view.workers[0].context.staticTags[0].tag).toBe("设计.worker集");
expect(view.workers[0].writes).toEqual(["输出.用户展示", "运行.事件流"]);
expect(view.workers[0].context.explicit).toBe(true);
expect(view.workers[1].presentation?.[0].label).toBe("语气");
expect(view.tagFlow).toHaveLength(2);
expect(view.designTasks).toHaveLength(1);
expect(view.designTasks[0].id).toBe("opening-generator");
});
it("exposes contextTags as standalone cards with mount targets", () => {
const parsed = parseWorkerSetYaml(`
narrative_guide: 残酷、不有求必应
resident_context:
- id: tone
content: 文风克制
mount: [narrator]
workers:
- ref: world-simulator
duty: 裁决
- ref: narrator
duty: 转述
presentation:
tone: 冷峻
`)!;
const view = formatWorkerSetForUser(parsed)!;
expect(view.contextTags?.length).toBeGreaterThanOrEqual(2);
const guide = view.contextTags?.find((c) => c.id === "fixed:narrative_guide");
expect(guide?.mountSummary).toBe("全部 Worker");
expect(guide?.mounts.map((m) => m.workerId).sort()).toEqual([
"narrator",
"world-simulator",
]);
const tone = view.contextTags?.find((c) => c.id === "resident:tone");
expect(tone?.mounts).toHaveLength(1);
expect(tone?.mounts[0].workerId).toBe("narrator");
expect(tone?.mountSummary).toBe(tone!.mounts[0].workerName);
const aesthetics = view.contextTags?.find((c) =>
c.id.startsWith("fixed:aesthetics"),
);
expect(aesthetics?.mounts[0].workerId).toBe("narrator");
expect(view.workers[1].readsSummary).toMatch(/美学|叙事|常驻/);
});
it("surfaces interaction paradigm, core worker, and rationale", () => {
const parsed = parseWorkerSetYaml(`
form_summary: 囚徒困境旁观
interaction_paradigm: 旁观多角(推断)
core_worker: world-simulator
reasoning: 三个角色需信息隔绝,各用 role-decide;陈述用 Markdown,不必文学转述。
workers:
- ref: world-simulator
role: core
rationale: 裁决组合结果,兼任结构化陈述。
- ref: role-decide
role: auxiliary
rationale: 仅 3 个参与者,隔绝决策上下文。
merge_considered: 不为每个 NPC 各开一个 worker。
`)!;
const view = formatWorkerSetForUser(parsed)!;
expect(view.interactionParadigm).toContain("旁观");
expect(view.coreWorker).toBe("world-simulator");
expect(view.reasoning).toContain("信息隔绝");
// 推断 play_slots 后顺序为 perspective → gm;角色用槽位中文名
const gm = view.workers.find((w) => w.id === "world-simulator");
const perspective = view.workers.find((w) => w.id === "role-decide");
expect(gm?.roleLabel).toBe("主世界层");
expect(gm?.rationale).toContain("裁决");
expect(perspective?.roleLabel).toBe("角色视角");
expect(perspective?.mergeConsidered).toContain("NPC");
});
it("merges default context when explicit context omitted", () => {
const parsed = parseWorkerSetYaml(`
workers:
- ref: variable-update
duty: 跟踪变量
when: 世界机之后
`)!;
const view = formatWorkerSetForUser(parsed)!;
expect(
view.workers[0].context.staticTags.some(
(t) =>
t.tag.includes("变量.目录") ||
t.tag.includes("变量设计") ||
t.tag.includes("设计.变量"),
),
).toBe(true);
expect(view.workers[0].context.explicit).toBe(false);
expect(view.workers[0].writes).toContain("变量.当前");
});
it("marks design tasks filled when blackboard has tags", () => {
const parsed = parseWorkerSetYaml(`
workers:
- ref: narrator
instantiate_hints:
invoke: [opening-generator]
`)!;
const view = formatWorkerSetForUser(parsed, {
filledTags: ["运行.初始变量", "输出.开场白"],
})!;
expect(view.designTasks[0].status).toBe("filled");
});
it("parses context and outputs fields", () => {
const parsed = parseWorkerSetYaml(FULL_SAMPLE)!;
expect(parsed.workers[0].context?.static).toContain("世界.蓝图.确认稿");
expect(parsed.workers[0].outputs).toContain("运行.事件流");
});
});