引入上下文片段、固定槽与投影排序,并完善机遇裁定与游玩期 UI。
把创作产物收敛为可挂载片段与 play_slots/context_order,同步修订世界模拟器模块与运行时拼装。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
216
tests/context-fragment.test.ts
Normal file
216
tests/context-fragment.test.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
parseContextFragment,
|
||||
projectFragmentContent,
|
||||
contextFragmentToView,
|
||||
extractFragmentAskSidecar,
|
||||
} from "../src/skills/context-fragment.js";
|
||||
import {
|
||||
parseContextOrder,
|
||||
contextOrderToSegments,
|
||||
slotOrderForRef,
|
||||
applyContextOrderEdit,
|
||||
mergeContextOrderIntoWorkerSetJson,
|
||||
serializeContextOrder,
|
||||
} from "../src/skills/context-order.js";
|
||||
import { assembleWorkerContext } from "../src/skills/context-segments.js";
|
||||
import { Blackboard } from "../src/blackboard/blackboard.js";
|
||||
import { buildDeclaredWorkerSkill } from "../src/skills/declared-worker.js";
|
||||
import { parseWorkerSetYaml } from "../src/skills/worker-set-parse.js";
|
||||
|
||||
describe("context-fragment.v1", () => {
|
||||
it("parses public header and projects summary", () => {
|
||||
const frag = parseContextFragment({
|
||||
schema: "context-fragment.v1",
|
||||
技能: "美学纲领与交互范式",
|
||||
brief: "特权求生紧张",
|
||||
mount: ["world-simulator", "narrator"],
|
||||
稳变: "stable",
|
||||
正文: { 体验: { 内核: "紧张" } },
|
||||
开放问题: [],
|
||||
})!;
|
||||
expect(frag.技能).toBe("美学纲领与交互范式");
|
||||
expect(projectFragmentContent(JSON.stringify(frag), "summary")).toBe(
|
||||
"特权求生紧张",
|
||||
);
|
||||
const view = contextFragmentToView(frag);
|
||||
expect(view.ok).toBe(true);
|
||||
expect(view.sections.some((s) => s.title === "概要")).toBe(true);
|
||||
});
|
||||
|
||||
it("extracts 自评 + 追问 into ask sidecar", () => {
|
||||
const side = extractFragmentAskSidecar({
|
||||
schema: "context-fragment.v1",
|
||||
brief: "孤立免疫",
|
||||
正文: { 美学纲领: { 体验内核: "…" } },
|
||||
自评: {
|
||||
维度: [
|
||||
{ 名: "交互范式", 分数: 70, 说明: "权限尚可" },
|
||||
{ 名: "美学纲领", 分数: 55, 说明: "边界未锁" },
|
||||
],
|
||||
薄弱点: "暴力尺度未定",
|
||||
},
|
||||
追问: {
|
||||
导语: "为贴近质感,还需确认:",
|
||||
题目: [
|
||||
{
|
||||
问: "暴力与血腥如何服务压迫感?",
|
||||
建议选项: ["偶发锋利高光", "常态压抑少见血", "其它"],
|
||||
示例: "远处尖叫,近处只留锈迹",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(side.assessment).toContain("交互范式 70%");
|
||||
expect(side.assessment).toContain("薄弱点:暴力尺度未定");
|
||||
expect(side.assessment).toContain("为贴近质感");
|
||||
expect(side.questions).toHaveLength(1);
|
||||
expect(side.questions[0]!.prompt).toContain("暴力与血腥");
|
||||
expect(side.questions[0]!.prompt).toContain("示例:远处尖叫");
|
||||
expect(side.questions[0]!.options?.map((o) => o.label)).toEqual([
|
||||
"偶发锋利高光",
|
||||
"常态压抑少见血",
|
||||
"其它",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("context-order.v1", () => {
|
||||
it("maps flat order to segments (history after stable)", () => {
|
||||
const doc = parseContextOrder({
|
||||
schema: "context-order.v1",
|
||||
slots: [
|
||||
{
|
||||
ref: "world-simulator",
|
||||
inserts: [
|
||||
{ order: 0, ref: "worker.persona", projection: "fixed" },
|
||||
{ order: 1, ref: "设计.实现机制", projection: "summary" },
|
||||
{ order: 2, ref: "对话.历史", projection: "summary" },
|
||||
{ order: 3, ref: "用户.最新输入", projection: "full" },
|
||||
],
|
||||
},
|
||||
],
|
||||
})!;
|
||||
const slot = slotOrderForRef(doc, "world-simulator")!;
|
||||
const segs = contextOrderToSegments({
|
||||
slot,
|
||||
personaText: "中立主世界层",
|
||||
});
|
||||
expect(segs[0].inline).toContain("中立主世界层");
|
||||
expect(segs[1].tags).toEqual(["设计.实现机制"]);
|
||||
expect(segs[2].tags).toEqual(["对话.历史"]);
|
||||
expect(segs[3].tier).toBe("dynamic");
|
||||
});
|
||||
|
||||
it("reorders flat list and merges into worker set JSON", () => {
|
||||
const doc = parseContextOrder({
|
||||
schema: "context-order.v1",
|
||||
slots: [
|
||||
{
|
||||
ref: "narrator",
|
||||
inserts: [
|
||||
{ order: 0, ref: "worker.persona", projection: "fixed" },
|
||||
{ order: 1, ref: "设计.叙事指南", projection: "summary" },
|
||||
{ order: 2, ref: "设计.美学纲领与交互范式", projection: "summary" },
|
||||
{ order: 3, ref: "对话.历史", projection: "summary" },
|
||||
{ order: 4, ref: "运行.本轮.裁决", projection: "full" },
|
||||
],
|
||||
},
|
||||
],
|
||||
})!;
|
||||
const moved = applyContextOrderEdit(doc, {
|
||||
action: "move",
|
||||
slotRef: "narrator",
|
||||
index: 2,
|
||||
delta: -1,
|
||||
});
|
||||
expect(moved.slots[0]!.inserts.map((i) => i.ref)).toEqual([
|
||||
"worker.persona",
|
||||
"设计.美学纲领与交互范式",
|
||||
"设计.叙事指南",
|
||||
"对话.历史",
|
||||
"运行.本轮.裁决",
|
||||
]);
|
||||
|
||||
const afterHist = applyContextOrderEdit(moved, {
|
||||
action: "set_anchor",
|
||||
slotRef: "narrator",
|
||||
index: 1,
|
||||
anchor: "post_history",
|
||||
});
|
||||
const refs = afterHist.slots[0]!.inserts.map((i) => i.ref);
|
||||
const histAt = refs.indexOf("对话.历史");
|
||||
expect(refs.indexOf("设计.美学纲领与交互范式")).toBeGreaterThan(histAt);
|
||||
|
||||
const merged = mergeContextOrderIntoWorkerSetJson(
|
||||
JSON.stringify({
|
||||
play_slots: { gm: true, narrator: true, perspective: false },
|
||||
workers: [],
|
||||
}),
|
||||
afterHist,
|
||||
);
|
||||
const again = JSON.parse(merged);
|
||||
expect(again.context_order.schema).toBe("context-order.v1");
|
||||
expect(serializeContextOrder(afterHist)).toContain("对话.历史");
|
||||
});
|
||||
|
||||
it("declared worker prefers context_order segments", () => {
|
||||
const parsed = parseWorkerSetYaml(
|
||||
JSON.stringify({
|
||||
play_slots: { gm: true, narrator: true, perspective: false },
|
||||
context_order: {
|
||||
schema: "context-order.v1",
|
||||
slots: [
|
||||
{
|
||||
ref: "world-simulator",
|
||||
inserts: [
|
||||
{ order: 0, ref: "worker.persona", projection: "fixed" },
|
||||
{ order: 1, ref: "对话.历史", projection: "summary" },
|
||||
{ order: 2, ref: "用户.最新输入", projection: "full" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
workers: [],
|
||||
}),
|
||||
);
|
||||
expect(parsed.parseError).toBeUndefined();
|
||||
const entry = parsed.workers.find((w) => w.ref === "world-simulator")!;
|
||||
const { worker } = buildDeclaredWorkerSkill({
|
||||
skillPackName: "world-simulator",
|
||||
entry,
|
||||
template: {
|
||||
id: "world-simulator",
|
||||
label: "主世界层",
|
||||
duty: "裁决",
|
||||
suggested_context: { static: ["设计.worker集"], dynamic: ["用户.最新输入"] },
|
||||
},
|
||||
workerSet: parsed,
|
||||
});
|
||||
expect(worker.contextSegments?.some((s) => s.inline?.includes("裁决"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
worker.contextSegments?.some(
|
||||
(s) => s.tier === "dynamic" && s.tags.includes("用户.最新输入"),
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
const bb = new Blackboard();
|
||||
bb.write({
|
||||
tag: "用户.最新输入",
|
||||
content: "推门进去",
|
||||
source: "test",
|
||||
});
|
||||
const text = assembleWorkerContext({
|
||||
inputs: { "用户.最新输入": "推门进去" },
|
||||
segments: worker.contextSegments,
|
||||
blackboard: bb,
|
||||
workerId: "world-simulator",
|
||||
workerName: "主世界层",
|
||||
outputTags: ["运行.本轮.裁决"],
|
||||
});
|
||||
expect(text).toContain("裁决");
|
||||
expect(text).toContain("推门进去");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user