接通主角绑定、开场封印与游玩辅助,让世界模拟器从创作连续落到本局。

直聘插入按模块回馈,并记录请求骨架便于排查。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 07:40:08 +08:00
co-authored by Cursor
parent 9306c8e659
commit dd0a9e87d8
88 changed files with 6869 additions and 786 deletions
+476 -4
View File
@@ -2,16 +2,23 @@ import { describe, expect, it } from "vitest";
import {
buildDictateMessages,
buildDictateSystemPrompt,
buildDictateInsertFeedbackIndex,
defaultDictateOrder,
extractDictateDialogue,
formatDictateUserFacingBrief,
isAllowedProductTag,
isDictateModeValue,
resolveModuleForInsertTag,
runDictateTurn,
sortDictateProducts,
resolveRepeatableInsertTag,
lookupDictateInsertFeedback,
} from "../src/dictate/index.js";
import {
formatModuleCatalogForDictate,
loadModuleCatalog,
} from "../src/skills/creation-flow.js";
import type { AgentDriver } from "../src/runtime/driver.js";
describe("dictate context", () => {
it("assembles full dialogue + products with almost no filtering", () => {
@@ -78,21 +85,37 @@ describe("dictate context", () => {
expect(prompt).toContain("设计.开场白");
expect(prompt).toContain("输出.开场白");
expect(prompt).toContain("insert");
expect(prompt).toContain("delete");
expect(prompt).toContain("declare_variable");
expect(prompt).toContain("undeclare_variable");
expect(prompt).toContain("declare_map");
expect(prompt).toContain("remove_map");
expect(prompt).toContain("同 position 再 insert");
expect(prompt).toContain("数据化跑团体验");
expect(prompt).toMatch(/order/);
expect(prompt).toContain("仅允许 toolcall");
expect(prompt).toContain("设计.模仿范例");
expect(prompt).toContain("设计.模仿要点");
expect(prompt).toContain("设计.叙事指南与故事推进");
expect(prompt).toContain("设计.故事推进");
expect(prompt).toContain("设计.叙事指南");
expect(prompt).toContain("大纲扩写");
expect(prompt).toContain("禁止扮演停笔");
expect(prompt).toContain("扮演停笔关掉");
expect(prompt).toContain("只挂载这些产物");
expect(prompt).toContain("【能力 · 何时落盘】");
expect(prompt).toContain("设计.美学纲领与交互范式");
expect(prompt).toContain("每轮通常只推进一刀");
expect(prompt).toContain("勿因配方名叫数据化就抢跑 D");
expect(prompt).toContain("同轮可多次 insert");
expect(prompt).toContain("不抢跑未齐条件的下游");
expect(prompt).toContain("也不自动开跑 D");
expect(prompt).toContain("快穿短局");
expect(prompt).toContain("一核短局");
expect(prompt).toMatch(/舞台骨架/);
expect(prompt).toMatch(/覆盖 C/);
expect(prompt).toContain("设计.主角设定");
expect(prompt).toContain("reply_module");
expect(prompt).toContain("对用户可见回复");
expect(prompt).toContain("有 N 个 reply_module");
expect(prompt).toContain("〔可反复〕须拆分");
expect(prompt).toContain("设计.具体实例#");
});
it("formats module catalog for dictate with land tags and when", async () => {
@@ -107,7 +130,13 @@ describe("dictate context", () => {
expect(block).toContain("declare_variable");
expect(block).toContain("设计.开场白");
expect(block).toContain("〔落档程序〕");
expect(block).toContain("设计.生成规则#");
expect(block).toContain("设计.具体实例#");
expect(block).toContain("可增殖");
expect(block).not.toContain("role=prototype");
expect(block).toContain("落盘:insert 「设计.叙事指南」");
expect(block).toContain("落盘:insert 「设计.故事推进」");
expect(block).not.toMatch(/^- 叙事指南与故事推进/m);
});
it("sorts products by relative order: neg then 0 then pos", () => {
@@ -146,4 +175,447 @@ describe("dictate context", () => {
expect(body).toContain("order=-10");
expect(body).toContain("order=10");
});
it("resolves insert tag to module and builds user-facing brief", async () => {
const catalog = await loadModuleCatalog("dialogue/world-simulator");
expect(catalog).toBeTruthy();
const mod = resolveModuleForInsertTag(
"设计.美学纲领与交互范式",
catalog!,
);
expect(mod?.id).toBe("aesthetics-interaction");
expect(
resolveModuleForInsertTag("设计.开场白", catalog!)?.id,
).toBe("opening-setup");
expect(
resolveModuleForInsertTag("设计.具体实例#lei-ying", catalog!)?.id,
).toBe("concrete-instances");
expect(
resolveModuleForInsertTag("设计.生成规则#ex-girlfriend-card", catalog!)?.id,
).toBe("generation-rules");
expect(resolveModuleForInsertTag("设计.叙事指南", catalog!)?.id).toBe(
"narrative-guide",
);
expect(resolveModuleForInsertTag("设计.故事推进", catalog!)?.id).toBe(
"story-progression",
);
expect(resolveModuleForInsertTag("设计.文风", catalog!)?.id).toBe(
"narrative-guide",
);
const index = await buildDictateInsertFeedbackIndex({
catalog: catalog!,
skillPackRoot: "dialogue/world-simulator",
});
const brief = index.get("设计.美学纲领与交互范式");
expect(brief).toBeTruthy();
expect(brief!).toContain("# 模块 · 美学纲领与交互范式");
expect(brief!).toContain("回答时请附带本模块");
expect(brief!).toContain("本模块内容包括");
expect(brief!).toContain("## 写入用户回复时做什么");
expect(brief!).toContain("## 探测与追问(本模块口径)");
expect(brief!).toContain("展示优于提问");
expect(brief!).toMatch(/自评关注|交互范式|美学纲领/);
const alias = index.get("设计.开场白");
expect(alias).toContain("开场白与开场变量");
});
it("splits repeatable insert tags and rejects bare overwrite", async () => {
const families = new Set(["设计.生成规则", "设计.具体实例"]);
const instanceBody = JSON.stringify({
schema: "context-fragment.v1",
技能: "具体实例",
正文: {
rule_id: "ex-girlfriend-card",
batch_id: "lei-ying",
records: [{ 姓名: "雷樱" }],
},
});
const first = resolveRepeatableInsertTag({
tag: "设计.具体实例",
content: instanceBody,
existingTags: [],
repeatableFamilies: families,
});
expect("tag" in first && first.tag).toBe("设计.具体实例#lei-ying");
const second = resolveRepeatableInsertTag({
tag: "设计.具体实例",
content: JSON.stringify({
schema: "context-fragment.v1",
正文: { records: [{ 姓名: "谢明澜" }] },
}),
existingTags: ["设计.具体实例#lei-ying"],
repeatableFamilies: families,
});
expect("tag" in second && second.tag).toBe("设计.具体实例#谢明澜");
const overwriteBare = resolveRepeatableInsertTag({
tag: "设计.具体实例",
content: "不是JSON就抽不出槽",
existingTags: ["设计.具体实例#lei-ying"],
repeatableFamilies: families,
});
expect("error" in overwriteBare).toBe(true);
const catalog = await loadModuleCatalog("dialogue/world-simulator");
const index = await buildDictateInsertFeedbackIndex({
catalog: catalog!,
skillPackRoot: "dialogue/world-simulator",
});
expect(
lookupDictateInsertFeedback(index, "设计.具体实例#lei-ying"),
).toContain("具体实例");
});
it("runDictateTurn remaps bare repeatable insert to #slot", async () => {
const products: { tag: string; content: string }[] = [];
const body = JSON.stringify({
schema: "context-fragment.v1",
正文: {
batch_id: "lei-ying",
records: [{ 姓名: "雷樱" }],
},
});
const driver: AgentDriver = {
async run(input) {
const outcome = await input.handleStep([
{
id: "c1",
name: "insert",
arguments: JSON.stringify({
position: "设计.具体实例",
content: body,
}),
},
]);
expect(outcome.kind).toBe("continue");
if (outcome.kind === "continue") {
const payload = JSON.parse(outcome.results[0]!.content) as {
ok?: boolean;
position?: string;
error?: string;
};
expect(payload.ok).toBe(true);
expect(payload.position).toBe("设计.具体实例#lei-ying");
}
return {
iterations: 1,
stop: { kind: "text", content: "已落实例" },
};
},
};
const result = await runDictateTurn({
llm: {} as never,
dialogue: [{ role: "user", text: "再生成一张雷樱" }],
driver,
repeatableFamilies: ["设计.具体实例", "设计.生成规则"],
handlers: {
listProducts: () => products,
writeProduct: (tag, content) => {
products.push({ tag, content });
},
deleteProduct: () => false,
readTag: () => undefined,
writeTag: () => undefined,
deleteTag: () => false,
clearDialogue: () => undefined,
},
});
expect(result.wroteTags).toEqual(["设计.具体实例#lei-ying"]);
expect(products.map((p) => p.tag)).toEqual(["设计.具体实例#lei-ying"]);
});
it("insert tool result includes reply_module from lookup", async () => {
let capturedSpec: string | undefined;
const driver: AgentDriver = {
async run(input) {
const outcome = await input.handleStep([
{
id: "c1",
name: "insert",
arguments: JSON.stringify({
position: "设计.美学纲领与交互范式",
content: "傀儡帝体验核",
}),
},
]);
expect(outcome.kind).toBe("continue");
if (outcome.kind === "continue") {
const payload = JSON.parse(outcome.results[0]!.content) as {
ok?: boolean;
reply_module?: string;
};
expect(payload.ok).toBe(true);
expect(payload.reply_module).toContain("回答时请附带本模块");
capturedSpec = payload.reply_module;
}
return {
iterations: 1,
stop: { kind: "text", content: "已落美学;下一刀建议主角名片。" },
};
},
};
const products: { tag: string; content: string }[] = [];
const result = await runDictateTurn({
llm: {} as never,
dialogue: [{ role: "user", text: "傀儡帝恋爱" }],
driver,
handlers: {
listProducts: () => products,
writeProduct: (tag, content) => {
products.push({ tag, content });
},
deleteProduct: () => false,
readTag: () => undefined,
writeTag: () => undefined,
deleteTag: () => false,
clearDialogue: () => undefined,
lookupInsertFeedback: () =>
formatDictateUserFacingBrief({
tag: "设计.美学纲领与交互范式",
module: {
id: "aesthetics-interaction",
name: "美学纲领与交互范式",
declaration: "x",
artifact: "设计.美学纲领与交互范式",
},
promptMd: [
"```probe",
"缺其中一件:追问 1~2 点",
"```",
"```principles",
"展示优于提问、大胆优于保守",
"```",
"```output",
'{"自评":{"维度":[{"名":"交互范式"},{"名":"美学纲领"}]}}',
"```",
].join("\n"),
}),
},
});
expect(capturedSpec).toContain("探测与追问");
expect(result.wroteTags).toContain("设计.美学纲领与交互范式");
expect(result.reply).toContain("已落美学");
});
it("delete removes a product tag", async () => {
const products = new Map<string, string>([
["设计.开场白", "旧开场"],
["用户.需求", "雨夜"],
]);
const driver: AgentDriver = {
async run(input) {
const outcome = await input.handleStep([
{
id: "d1",
name: "delete",
arguments: JSON.stringify({
position: "设计.开场白",
reason: "重做开场",
}),
},
]);
expect(outcome.kind).toBe("continue");
if (outcome.kind === "continue") {
const payload = JSON.parse(outcome.results[0]!.content) as {
ok?: boolean;
deleted?: string;
};
expect(payload.ok).toBe(true);
expect(payload.deleted).toBe("设计.开场白");
}
return {
iterations: 1,
stop: { kind: "text", content: "已删开场白。" },
};
},
};
const result = await runDictateTurn({
llm: {} as never,
dialogue: [{ role: "user", text: "删掉开场白" }],
driver,
handlers: {
listProducts: () =>
[...products.entries()].map(([tag, content]) => ({ tag, content })),
writeProduct: (tag, content) => {
products.set(tag, content);
},
deleteProduct: (tag) => products.delete(tag),
readTag: () => undefined,
writeTag: () => undefined,
deleteTag: () => false,
clearDialogue: () => undefined,
},
});
expect(products.has("设计.开场白")).toBe(false);
expect(products.has("用户.需求")).toBe(true);
expect(result.deletedTags).toContain("设计.开场白");
});
it("undeclare_variable and remove_map prune catalog/map docs", async () => {
let catalogRaw =
'{"schema":"variable-catalog.v1","fields":[{"key":"好感","type":"number","initial":10,"user_visible":true},{"key":"章节","type":"number","initial":1,"user_visible":true}]}';
let mapRaw =
'{"schema":"value-map.v1","maps":[{"id":"aff","field":"好感","target_tag":"上下文.角色态度","bands":[{"min":0,"content":"冷"}]}]}';
let currentRaw =
'{"rows":[{"key":"好感","value":10,"rev":1},{"key":"章节","value":1,"rev":1}]}';
const projection = new Map<string, string>([
["上下文.角色态度", "冷"],
]);
const tags = new Map<string, string>([
["设计.变量目录", catalogRaw],
["设计.变量映射", mapRaw],
["变量.当前", currentRaw],
]);
const driver: AgentDriver = {
async run(input) {
const r1 = await input.handleStep([
{
id: "u1",
name: "undeclare_variable",
arguments: JSON.stringify({ key: "好感" }),
},
]);
expect(r1.kind).toBe("continue");
const r2 = await input.handleStep([
{
id: "r1",
name: "remove_map",
arguments: JSON.stringify({ id: "aff" }),
},
]);
expect(r2.kind).toBe("continue");
if (r2.kind === "continue") {
const payload = JSON.parse(r2.results[0]!.content) as {
cleared_target?: string | null;
};
expect(payload.cleared_target).toBe("上下文.角色态度");
}
return {
iterations: 2,
stop: { kind: "text", content: "已移除好感与映射。" },
};
},
};
const result = await runDictateTurn({
llm: {} as never,
dialogue: [{ role: "user", text: "去掉好感" }],
driver,
handlers: {
listProducts: () => [],
writeProduct: () => undefined,
deleteProduct: () => false,
readTag: (tag) => tags.get(tag) ?? projection.get(tag),
writeTag: (tag, content) => {
tags.set(tag, content);
if (tag === "设计.变量目录") catalogRaw = content;
if (tag === "设计.变量映射") mapRaw = content;
if (tag === "变量.当前") currentRaw = content;
},
deleteTag: (tag) => {
const a = tags.delete(tag);
const b = projection.delete(tag);
return a || b;
},
clearDialogue: () => undefined,
},
});
expect(catalogRaw).toContain("章节");
expect(catalogRaw).not.toContain('"好感"');
expect(currentRaw).not.toContain('"好感"');
expect(tags.has("设计.变量映射")).toBe(false);
expect(projection.has("上下文.角色态度")).toBe(false);
expect(result.deletedTags.some((t) => t.includes("好感"))).toBe(true);
expect(result.deletedTags.some((t) => t.includes("aff"))).toBe(true);
});
it("parallel inserts each get a reply_module and multi-module hint", async () => {
const driver: AgentDriver = {
async run(input) {
const outcome = await input.handleStep([
{
id: "c1",
name: "insert",
arguments: JSON.stringify({
position: "设计.美学纲领与交互范式",
content: "美学",
}),
},
{
id: "c2",
name: "insert",
arguments: JSON.stringify({
position: "设计.主角设定",
content: "主角",
}),
},
]);
expect(outcome.kind).toBe("continue");
if (outcome.kind === "continue") {
const a = JSON.parse(outcome.results[0]!.content) as {
reply_module?: string;
reply_hint?: string;
};
const b = JSON.parse(outcome.results[1]!.content) as {
reply_module?: string;
reply_hint?: string;
};
expect(a.reply_module).toContain("美学纲领");
expect(b.reply_module).toContain("主角设定");
expect(a.reply_hint).toMatch(/多个 reply_module|本步已带回模块/);
expect(b.reply_hint).toContain("本步已带回模块");
}
return {
iterations: 1,
stop: { kind: "text", content: "## 模块 · 美学\n\n## 模块 · 主角" },
};
},
};
await runDictateTurn({
llm: {} as never,
dialogue: [{ role: "user", text: "都要" }],
driver,
handlers: {
listProducts: () => [],
writeProduct: () => undefined,
deleteProduct: () => false,
readTag: () => undefined,
writeTag: () => undefined,
deleteTag: () => false,
clearDialogue: () => undefined,
lookupInsertFeedback: (tag) =>
tag.includes("美学")
? formatDictateUserFacingBrief({
tag,
module: {
id: "aesthetics-interaction",
name: "美学纲领与交互范式",
declaration: "x",
artifact: tag,
},
promptMd: "```probe\n美学追问\n```",
})
: formatDictateUserFacingBrief({
tag,
module: {
id: "protagonist",
name: "主角设定",
declaration: "x",
artifact: tag,
},
promptMd: "```probe\n主角追问\n```",
}),
},
});
});
});