引入上下文片段、固定槽与投影排序,并完善机遇裁定与游玩期 UI。

把创作产物收敛为可挂载片段与 play_slots/context_order,同步修订世界模拟器模块与运行时拼装。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-03 01:36:32 +08:00
parent 00dfcb6615
commit 94f67fa744
69 changed files with 7786 additions and 1283 deletions

View 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("推门进去");
});
});