引入上下文片段、固定槽与投影排序,并完善机遇裁定与游玩期 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

@@ -27,7 +27,8 @@ const WORKER_LABELS: Record<string, string> = {
"agent-burst": "编排器调度",
narrator: "叙事转述",
"role-decide": "角色决策",
"world-simulator": "世界推演",
"world-simulator": "世界",
chance: "机遇裁定",
"round-present": "回合呈现",
outline: "大纲 / 细纲",
"chapter-writer": "章节正文",
@@ -36,7 +37,7 @@ const WORKER_LABELS: Record<string, string> = {
const FIXED_TOPIC_LABELS: Record<string, string> = {
"aesthetics-interaction": "美学纲领与交互范式",
interaction: "交互范式",
narrative_guide: "叙事指南",
narrative_guide: "叙事指南与故事推进",
input_protocol: "输入协议",
core_premise: "核心前提",
aesthetics: "美学纲领",

View File

@@ -67,6 +67,15 @@ import {
formatWorkerSetForUser,
type WorkerSetUserView,
} from "../skills/worker-set-view.js";
import {
CONTEXT_ORDER_TAG,
applyContextOrderEdit,
mergeContextOrderIntoWorkerSetJson,
parseContextOrder,
serializeContextOrder,
synthesizeContextOrderFromWorkers,
type ContextOrderEdit,
} from "../skills/context-order.js";
import {
CREATION_FLOW_TAG,
CREATION_SELECTED_RECIPE_TAG,
@@ -292,6 +301,80 @@ export class SessionManager {
return this.toView(sessionId);
}
/**
* 编排上下文投影排序:写入 设计.worker集.context_order若有规格
* 并同步 设计.上下文投影排序。下次声明驱动拼装即按新序。
*/
patchContextOrder(sessionId: string, edit: ContextOrderEdit): SessionView {
const s = this.require(sessionId);
const bb = s.runtime.getBlackboard();
const workerSetRaw =
bb.getContentByTag("设计.worker集")?.trim() ||
bb.getContentByTag("设计.worker集.草稿")?.trim() ||
"";
const orderTagRaw = bb.getContentByTag(CONTEXT_ORDER_TAG)?.trim() || "";
let current = parseContextOrder(
workerSetRaw
? parseWorkerSetYaml(workerSetRaw).context_order
: undefined,
);
if (!current) current = parseContextOrder(orderTagRaw);
if (!current && workerSetRaw) {
const parsed = parseWorkerSetYaml(workerSetRaw);
// 与检查器合成逻辑一致:缺 context 时用包内默认契约(视图侧也会合成)
current = synthesizeContextOrderFromWorkers(
parsed.workers.map((w) => ({
ref: w.ref,
name: w.name,
context: w.context,
})),
parsed.play_slots,
);
}
if (!current && edit.action !== "replace") {
throw new Error("尚无上下文投影排序可编辑;请先完成游玩拓扑/细化终稿,或提交完整 replace");
}
if (!current && edit.action === "replace") {
current = parseContextOrder(edit.context_order);
if (!current) throw new Error("context_order 无法解析");
}
const next = applyContextOrderEdit(current!, edit);
const serialized = serializeContextOrder(next);
bb.write({
tag: CONTEXT_ORDER_TAG,
content: serialized,
source: "user",
});
if (workerSetRaw) {
const targetTag = bb.getContentByTag("设计.worker集")?.trim()
? "设计.worker集"
: bb.getContentByTag("设计.worker集.草稿")?.trim()
? "设计.worker集.草稿"
: null;
if (targetTag) {
bb.write({
tag: targetTag,
content: mergeContextOrderIntoWorkerSetJson(workerSetRaw, next),
source: "user",
});
}
} else {
// 尚无规格:至少落下排序表,供细化终稿合并
bb.write({
tag: CONTEXT_ORDER_TAG,
content: serialized,
source: "user",
});
}
if (s.bookId) this.persist(s);
return this.toView(sessionId);
}
/**
* 打开 Book优先恢复磁盘快照无快照则新建 Session。
* 若该 Book 已在内存中,直接返回当前视图。

View File

@@ -291,6 +291,96 @@ const server = createServer(async (req, res) => {
return;
}
if (req.method === "POST" && sub === "/context-order") {
const body = JSON.parse(await readBody(req)) as {
action?: string;
slotRef?: string;
index?: number;
delta?: number;
anchor?: string;
projection?: string;
context_order?: unknown;
};
try {
const action = body.action?.trim();
if (action === "replace") {
const view = sessionManager.patchContextOrder(sessionId, {
action: "replace",
context_order: body.context_order,
});
json(res, 200, view);
return;
}
if (action === "move") {
const delta = body.delta === 1 || body.delta === -1 ? body.delta : 0;
if (!body.slotRef?.trim() || typeof body.index !== "number" || !delta) {
json(res, 400, { error: "move 需要 slotRef、index、delta(±1)" });
return;
}
const view = sessionManager.patchContextOrder(sessionId, {
action: "move",
slotRef: body.slotRef.trim(),
index: body.index,
delta,
});
json(res, 200, view);
return;
}
if (action === "set_anchor") {
if (
!body.slotRef?.trim() ||
typeof body.index !== "number" ||
(body.anchor !== "pre_history" && body.anchor !== "post_history")
) {
json(res, 400, {
error: "set_anchor 需要 slotRef、index、anchor(pre_history|post_history)",
});
return;
}
const view = sessionManager.patchContextOrder(sessionId, {
action: "set_anchor",
slotRef: body.slotRef.trim(),
index: body.index,
anchor: body.anchor,
});
json(res, 200, view);
return;
}
if (action === "set_projection") {
const proj = body.projection;
if (
!body.slotRef?.trim() ||
typeof body.index !== "number" ||
(proj !== "fixed" &&
proj !== "full" &&
proj !== "summary" &&
proj !== "fields")
) {
json(res, 400, {
error: "set_projection 需要 slotRef、index、projection",
});
return;
}
const view = sessionManager.patchContextOrder(sessionId, {
action: "set_projection",
slotRef: body.slotRef.trim(),
index: body.index,
projection: proj,
});
json(res, 200, view);
return;
}
json(res, 400, {
error: "action 须为 move | set_anchor | set_projection | replace",
});
} catch (err) {
json(res, 400, {
error: err instanceof Error ? err.message : "编排失败",
});
}
return;
}
if (req.method === "POST" && sub === "/context-traces/prune") {
try {
const result = sessionManager.pruneSessionContextTraces(sessionId);