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

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

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
+112
View File
@@ -5,6 +5,7 @@ import {
parseOpeningSealPayload,
openingVariablesToTableDoc,
reopenCreationFlowRaw,
splitOpeningDocument,
} from "../src/skills/opening-seal.js";
const fragment = JSON.stringify({
@@ -63,6 +64,117 @@ describe("opening-seal", () => {
]);
});
it("reads 用户角色 from candidate meta and 正文", () => {
const raw = JSON.stringify({
schema: "context-fragment.v1",
技能: "开场白与开场变量",
正文: {
开场白全文: "@玩家站在债契前。",
开场白候选: [
{
meta: {
用户角色: { 名字: "沈聿", 简介: "本地放贷人;与这对母女相识但不熟。" },
},
正文: "@玩家站在债契前。",
},
{
meta: {
用户角色: { 名字: "阿泽", 简介: "刚穿进来的社畜。" },
},
正文: "门外有人喊 @玩家 的名字。",
},
],
开场变量: [{ 名: "债额", 值: 3 }],
},
});
const first = parseOpeningSealPayload(raw, 0);
expect(first?.persona).toEqual({
name: "沈聿",
description: "本地放贷人;与这对母女相识但不熟。",
});
const second = parseOpeningSealPayload(raw, 1);
expect(second?.selectedText).toContain("门外");
expect(second?.persona).toEqual({
name: "阿泽",
description: "刚穿进来的社畜。",
});
});
it("serializes present.v1 正文 and per-candidate 开场变量", () => {
const packet = {
schema: "present.v1",
shell: "chat_monitor",
blocks: {
monitor: { 债额: "三两" },
body: "@玩家站在债契前。",
},
meta: { suggested_actions: ["问债"] },
};
const raw = JSON.stringify({
schema: "context-fragment.v1",
技能: "开场白与开场变量",
正文: {
开场白全文: packet,
开场白候选: [
{
meta: {
用户角色: { 名字: "沈聿", 简介: "放贷人" },
开场变量: [{ 名: "债额", 值: 3, 依据: "桌上债契" }],
},
正文: packet,
},
{
meta: {
用户角色: { 名字: "阿泽", 简介: "社畜" },
开场变量: [{ 名: "口粮", 值: 1 }],
},
正文: {
schema: "present.v1",
shell: "chat_monitor",
blocks: { body: "门外有人喊 @玩家。" },
},
},
],
开场变量: [{ 名: "债额", 值: 3, 依据: "桌上债契" }],
},
});
const first = parseOpeningSealPayload(raw, 0);
expect(first?.candidates).toHaveLength(2);
const selected = JSON.parse(first!.selectedText);
expect(selected.schema).toBe("present.v1");
expect(selected.shell).toBe("chat_monitor");
expect(selected.blocks.body).toContain("@玩家");
expect(first?.persona).toEqual({ name: "沈聿", description: "放贷人" });
expect(first?.variables).toEqual([
{ name: "债额", value: 3, note: "桌上债契" },
]);
const second = parseOpeningSealPayload(raw, 1);
expect(second?.persona?.name).toBe("阿泽");
expect(second?.variables).toEqual([{ name: "口粮", value: 1 }]);
expect(JSON.parse(second!.selectedText).blocks.body).toContain("门外");
});
it("splits YAML frontmatter meta from opening 正文", () => {
const nested = splitOpeningDocument(`---
用户角色:
名字: 沈聿
简介: 本地放贷人
---
清晨,@玩家推开堂屋门。
`);
expect(nested.persona).toEqual({ name: "沈聿", description: "本地放贷人" });
expect(nested.text).toBe("清晨,@玩家推开堂屋门。");
const dotted = splitOpeningDocument(`---
用户角色.名字: 阿泽
用户角色.简介: 刚穿进来的社畜
---
门外有人喊 @玩家。
`);
expect(dotted.persona).toEqual({ name: "阿泽", description: "刚穿进来的社畜" });
expect(dotted.text).toBe("门外有人喊 @玩家。");
});
it("builds table rows from opening variables", () => {
const payload = parseOpeningSealPayload(fragment, 0);
const doc = openingVariablesToTableDoc(payload!.variables);