默认只开主世界层,无转述时直接把原文当终稿呈现。
避免再经转述改写一轮,并让主世界层在关转述时走可见正文协议。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -120,7 +120,7 @@ import {
|
||||
SLOT_ASKED_QUESTIONS,
|
||||
} from "../skills/question-protocol.js";
|
||||
import { parseWorkerSetYaml } from "../skills/worker-set-parse.js";
|
||||
import { defaultPlaySlots } from "../skills/play-slots.js";
|
||||
import { defaultPlaySlots, isNarratorEnabled } from "../skills/play-slots.js";
|
||||
import {
|
||||
mergeOpeningTablePatch,
|
||||
OPENING_CURRENT_VARS_TAG,
|
||||
@@ -1252,7 +1252,16 @@ export class PhaseRuntime {
|
||||
): import("../worker/executor.js").WorkerRunResult {
|
||||
if (!isPlayLayerActive(this.session.slots)) return result;
|
||||
const outputs = { ...result.outputs };
|
||||
if (Object.keys(outputs).length === 0 && isPlayPresentWorker(workerId)) {
|
||||
if (
|
||||
Object.keys(outputs).length === 0 &&
|
||||
isPlayPresentWorker(workerId, {
|
||||
narratorEnabled: isNarratorEnabled(
|
||||
parseWorkerSetYaml(
|
||||
this.blackboard.getContentByTag("设计.worker集") ?? "",
|
||||
)?.play_slots,
|
||||
),
|
||||
})
|
||||
) {
|
||||
const shell =
|
||||
parseShellAdaptationFromReplyFormat(
|
||||
this.blackboard.getContentByTag("设计.正文组成") ??
|
||||
|
||||
@@ -512,6 +512,8 @@ const SYNTH_FALLBACK_CONTEXT: Record<
|
||||
"设计.变量设计与更新规则",
|
||||
"设计.生成规则",
|
||||
"设计.叙事指南与故事推进",
|
||||
"设计.正文组成",
|
||||
"设计.监控栏",
|
||||
],
|
||||
dynamic: [
|
||||
"变量.当前",
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from "./context-order.js";
|
||||
import type { ContextSegmentDef } from "./context-segments.js";
|
||||
import { PLAY_VISIBLE_BODY_INSTRUCTION } from "./present-packet.js";
|
||||
import { isNarratorEnabled } from "./play-slots.js";
|
||||
|
||||
type WorkerTemplateDoc = {
|
||||
id?: string;
|
||||
@@ -220,7 +221,9 @@ export function buildDeclaredWorkerSkill(params: {
|
||||
`执行 ${id}`;
|
||||
const excerpt = params.template?.prompt_excerpt?.trim() || "";
|
||||
const visibleBodyRule =
|
||||
id === "narrator" || id === "round-present"
|
||||
id === "narrator" ||
|
||||
id === "round-present" ||
|
||||
(id === "world-simulator" && !isNarratorEnabled(params.workerSet?.play_slots))
|
||||
? PLAY_VISIBLE_BODY_INSTRUCTION
|
||||
: "";
|
||||
const excerptWithRule = [excerpt, visibleBodyRule].filter(Boolean).join("\n\n");
|
||||
|
||||
+30
-18
@@ -11,13 +11,13 @@ export type OnDemandSlotId = "chance";
|
||||
|
||||
export type PlaySlotsConfig = {
|
||||
/**
|
||||
* 旁观维护(副 LLM:表/规则检查,默认空操作);默认 true。
|
||||
* 每轮可上场,但不等于每轮改表。缺省键视为开启。
|
||||
* 旁观维护(副 LLM:表/规则检查,默认空操作);默认 false。
|
||||
* 每轮可上场,但不等于每轮改表。缺省键视为关闭。
|
||||
*/
|
||||
auditor?: boolean;
|
||||
/** 主世界层(裁决);默认 true */
|
||||
/** 主世界层(裁决 / 无转述时兼写可见正文);默认 true */
|
||||
gm: boolean;
|
||||
/** 叙事转述;默认 true */
|
||||
/** 叙事转述;默认 false(主世界层直接交用户可见原文) */
|
||||
narrator: boolean;
|
||||
/** 单角色知密视角;默认 false */
|
||||
perspective: boolean;
|
||||
@@ -53,12 +53,13 @@ export const PLAY_SLOT_META: Record<
|
||||
},
|
||||
gm: {
|
||||
label: "主世界层",
|
||||
purpose: "读真值与 Progressive 投影,输出结构化裁决包;可提议变量变更。",
|
||||
defaultAcceptance: "continue",
|
||||
purpose:
|
||||
"读真值与 Progressive 投影并推进世界;无独立转述时直接写用户可见正文,有转述时出裁决包。",
|
||||
defaultAcceptance: "review",
|
||||
},
|
||||
narrator: {
|
||||
label: "叙事转述",
|
||||
purpose: "只读裁决包 + 文风常驻,输出用户可见正文。",
|
||||
purpose: "只读裁决包 + 文风常驻,输出用户可见正文(可选;默认关)。",
|
||||
defaultAcceptance: "review",
|
||||
},
|
||||
perspective: {
|
||||
@@ -102,14 +103,19 @@ export function playSlotIdForRef(ref: string): PlaySlotId | undefined {
|
||||
|
||||
export function defaultPlaySlots(): PlaySlotsConfig {
|
||||
return {
|
||||
auditor: true,
|
||||
auditor: false,
|
||||
gm: true,
|
||||
narrator: true,
|
||||
narrator: false,
|
||||
perspective: false,
|
||||
chance: false,
|
||||
};
|
||||
}
|
||||
|
||||
/** 是否启用独立转述层(未显式 true 视为关) */
|
||||
export function isNarratorEnabled(slots: PlaySlotsConfig | undefined | null): boolean {
|
||||
return slots?.narrator === true;
|
||||
}
|
||||
|
||||
export function parsePlaySlots(raw: unknown): PlaySlotsConfig | undefined {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
||||
const row = raw as Record<string, unknown>;
|
||||
@@ -154,10 +160,7 @@ export function refForSlot(
|
||||
}
|
||||
|
||||
export function enabledPlaySlotIds(slots: PlaySlotsConfig): PlaySlotId[] {
|
||||
return PLAY_SLOT_ORDER.filter((id) => {
|
||||
if (id === "auditor") return slots.auditor !== false;
|
||||
return Boolean(slots[id]);
|
||||
});
|
||||
return PLAY_SLOT_ORDER.filter((id) => Boolean(slots[id]));
|
||||
}
|
||||
|
||||
export function enabledOnDemandSlotIds(slots: PlaySlotsConfig): OnDemandSlotId[] {
|
||||
@@ -188,14 +191,19 @@ export function workerEntryForSlot(
|
||||
): WorkerSetEntry {
|
||||
const meta = PLAY_SLOT_META[id];
|
||||
const ref = refForSlot(slots, id);
|
||||
const soloGm = id === "gm" && !isNarratorEnabled(slots);
|
||||
return {
|
||||
ref,
|
||||
name: meta.label,
|
||||
role: id,
|
||||
duty: meta.purpose,
|
||||
duty: soloGm
|
||||
? "读真值与投影,推进世界,并直接写出用户可见正文(无独立转述层,原文即终稿)。"
|
||||
: meta.purpose,
|
||||
when:
|
||||
id === "gm"
|
||||
? "每轮用户输入后首先上场(主 LLM / 裁决权威)"
|
||||
? soloGm
|
||||
? "每轮用户输入后上场;直接交用户可见正文"
|
||||
: "每轮用户输入后首先上场(主 LLM / 裁决权威)"
|
||||
: id === "perspective"
|
||||
? "强信息隔离且本轮需要该角色独立反应时(主世界层之后)"
|
||||
: id === "narrator"
|
||||
@@ -203,13 +211,17 @@ export function workerEntryForSlot(
|
||||
: "转述完成后;表维护合并进变量.当前,供下一轮主世界层读取",
|
||||
rationale:
|
||||
id === "gm"
|
||||
? "等同 SillyTavern 主 LLM:读设定与状态并产出裁决"
|
||||
? soloGm
|
||||
? "默认少槽:主世界层兼呈现,避免转述再改写一遍"
|
||||
: "有独立转述时:主世界层出裁决,正文交给转述"
|
||||
: id === "perspective"
|
||||
? "知密内容不能进主世界层上下文"
|
||||
: id === "narrator"
|
||||
? "用户可见正文与裁决分离,避免文风与规则互相挤压"
|
||||
: "表与规则补充延后执行,改表结果作用于下一轮而非本轮重裁",
|
||||
acceptance: meta.defaultAcceptance,
|
||||
acceptance:
|
||||
id === "gm" && isNarratorEnabled(slots) ? "continue" : meta.defaultAcceptance,
|
||||
outputs: soloGm ? ["输出.用户展示"] : undefined,
|
||||
invocation: "turn",
|
||||
};
|
||||
}
|
||||
@@ -356,7 +368,7 @@ export function formatPlayAgentRosterForPrompt(): string {
|
||||
];
|
||||
for (const id of PLAY_SLOT_ORDER) {
|
||||
const meta = PLAY_SLOT_META[id];
|
||||
const def = id === "perspective" ? "默认关" : "默认开";
|
||||
const def = id === "gm" ? "默认开" : "默认关";
|
||||
lines.push(
|
||||
`- \`${id}\` → ref=\`${DEFAULT_PLAY_SLOT_REFS[id]}\` **${meta.label}**(${def}):${meta.purpose}`,
|
||||
);
|
||||
|
||||
@@ -198,10 +198,15 @@ export const PLAY_VISIBLE_BODY_INSTRUCTION = [
|
||||
"禁止把 tag 名(如「输出.用户展示」)、压缩摘要、过程日志写进正文。",
|
||||
].join("\n");
|
||||
|
||||
/** 游玩期把裁决写成用户可见终稿的执行单元 */
|
||||
export function isPlayPresentWorker(workerId: string): boolean {
|
||||
/** 游玩期把用户可见终稿写出的执行单元(有转述用转述;无则主世界层直接交原文) */
|
||||
export function isPlayPresentWorker(
|
||||
workerId: string,
|
||||
opts?: { narratorEnabled?: boolean },
|
||||
): boolean {
|
||||
const id = workerId.trim();
|
||||
return id === "narrator" || id === "round-present";
|
||||
if (id === "narrator" || id === "round-present") return true;
|
||||
if (id === "world-simulator" && opts?.narratorEnabled === false) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 按能力探测走 json_schema / forced_tool 时用的 present.v1 形状(全 required,可 strict) */
|
||||
|
||||
@@ -236,6 +236,9 @@ const DEFAULT_WORKER_CONTRACTS: Record<
|
||||
"世界.蓝图.确认稿",
|
||||
"世界.拓扑.*",
|
||||
"设计.变量设计与更新规则",
|
||||
"设计.叙事指南与故事推进",
|
||||
"设计.正文组成",
|
||||
"设计.监控栏",
|
||||
],
|
||||
dynamic: [
|
||||
"变量.当前",
|
||||
@@ -246,7 +249,7 @@ const DEFAULT_WORKER_CONTRACTS: Record<
|
||||
"运行.本轮.旁观",
|
||||
],
|
||||
},
|
||||
outputs: ["运行.本轮.裁决", "运行.事件流", "运行.本轮.变量变更"],
|
||||
outputs: ["输出.用户展示", "运行.本轮.裁决", "运行.事件流", "运行.本轮.变量变更"],
|
||||
},
|
||||
"variable-update": {
|
||||
context: {
|
||||
@@ -767,10 +770,7 @@ export function formatWorkerSetForUser(
|
||||
? PLAY_SLOT_ORDER.map((id: PlaySlotId) => ({
|
||||
id,
|
||||
label: PLAY_SLOT_META[id].label,
|
||||
enabled:
|
||||
id === "auditor"
|
||||
? parsed.play_slots!.auditor !== false
|
||||
: Boolean(parsed.play_slots![id]),
|
||||
enabled: Boolean(parsed.play_slots![id]),
|
||||
ref: refForSlot(parsed.play_slots!, id),
|
||||
}))
|
||||
: undefined,
|
||||
|
||||
+10
-6
@@ -46,6 +46,7 @@ import {
|
||||
GM_CHANCE_HARNESS_INSTRUCTION,
|
||||
} from "../skills/gm-tools.js";
|
||||
import { runGmHarness } from "./gm-harness.js";
|
||||
import { isNarratorEnabled } from "../skills/play-slots.js";
|
||||
import {
|
||||
mergeQuestionsPreferFragment,
|
||||
normalizeQuestions,
|
||||
@@ -772,17 +773,20 @@ export async function runWorkerSkill(params: WorkerRunParams): Promise<WorkerRun
|
||||
inputs[CONTEXT_BRIEF_TAG] = priorBrief;
|
||||
}
|
||||
|
||||
const playSlots = parseWorkerSetYaml(
|
||||
params.blackboard.getContentByTag("设计.worker集") ?? "",
|
||||
)?.play_slots;
|
||||
|
||||
const useGmHarness =
|
||||
isPlayLayerActive(params.slots) &&
|
||||
worker.id === "world-simulator" &&
|
||||
gmChanceToolsEnabled(
|
||||
parseWorkerSetYaml(
|
||||
params.blackboard.getContentByTag("设计.worker集") ?? "",
|
||||
)?.play_slots,
|
||||
);
|
||||
gmChanceToolsEnabled(playSlots);
|
||||
|
||||
const playPresent =
|
||||
isPlayLayerActive(params.slots) && isPlayPresentWorker(worker.id);
|
||||
isPlayLayerActive(params.slots) &&
|
||||
isPlayPresentWorker(worker.id, {
|
||||
narratorEnabled: isNarratorEnabled(playSlots),
|
||||
});
|
||||
const playShell: PresentShellId | undefined = playPresent
|
||||
? parseShellAdaptationFromReplyFormat(
|
||||
params.blackboard.getContentByTag("设计.正文组成") ??
|
||||
|
||||
Reference in New Issue
Block a user