重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 00:39:32 +08:00
parent 2b74c30d36
commit e670a5129c
167 changed files with 22955 additions and 5659 deletions

View File

@@ -0,0 +1,260 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { parse as parseYaml } from "yaml";
import type { Blackboard } from "../blackboard/blackboard.js";
import type { RuntimeSession } from "../types/runtime.js";
import type { AcceptanceMode } from "../types/runtime.js";
import type { ParsedWorkerSkill } from "./types.js";
import {
parseWorkerSetYaml,
type ParsedWorkerSet,
type WorkerSetEntry,
} from "./worker-set-parse.js";
import {
buildInstanceWorkerDeclaration,
inferLifecycleStage,
readWorkerSetYamlForDeclaration,
} from "./worker-declaration.js";
import { loadWorkerSkill, SKILLS_ROOT } from "./loader.js";
import { CONTEXT_BRIEF_TAG } from "../runtime/compress-after-worker.js";
import { isDesignDiskWorker } from "./creation-units.js";
import {
entriesForWorker,
formatResidentPromptSection,
parseResidentContext,
residentTagFor,
} from "./resident-context.js";
type WorkerTemplateDoc = {
id?: string;
label?: string;
duty?: string;
prompt_excerpt?: string;
suggested_context?: { static?: string[]; dynamic?: string[] };
suggested_outputs?: string[];
};
/**
* 解析本次 worker 的验收模式(创作 / run 共用入口)。
* - design-* / opening-generator 磁盘创作 worker → 始终 user_confirmed
* - Worker 集 acceptance: review → user_confirmedcontinue → no_confirmation
* - 未声明 acceptancedesign 生命周期默认确认play 缺省按 review稳妥
*/
export function resolveAcceptanceModeForWorker(params: {
session: RuntimeSession;
blackboard: Blackboard;
workerId: string;
}): AcceptanceMode {
const workerId = params.workerId.trim();
if (isDesignDiskWorker(workerId) || workerId === "opening-generator") {
return "user_confirmed";
}
const decl = buildInstanceWorkerDeclaration(
params.session,
params.blackboard,
inferLifecycleStage(params.session),
);
const entry = decl.parsed?.workers.find((w) => w.ref?.trim() === workerId);
if (entry?.acceptance === "continue") return "no_confirmation";
if (entry?.acceptance === "review") return "user_confirmed";
// 未写明:创作阶段默认验收;游玩缺省也验收(避免静默连跑)
return "user_confirmed";
}
/** 从声明 + 可选模板构建可执行 worker有磁盘 SKILL 时优先磁盘design-intake */
export async function resolveRunnableWorker(params: {
skillPackName: string;
workerId: string;
session: RuntimeSession;
blackboard: Blackboard;
skillsRoot?: string;
}): Promise<{
worker: ParsedWorkerSkill;
promptBody: string;
source: "disk" | "declaration";
}> {
const root = params.skillsRoot ?? SKILLS_ROOT;
try {
const { loadWorkerSkillWithContext } = await import("./loader.js");
const flowRaw = params.blackboard.getContentByTag("设计.创作流程");
const currentStepName = params.blackboard.getContentByTag("创作.当前步骤");
const selectedRecipeRef = params.blackboard.getContentByTag("创作.选用配方");
const acceptedRaw =
params.session.slots?.creationAcceptedUnits ??
params.blackboard.getContentByTag("创作.已验收单位");
const { parseAcceptedSteps } = await import("./creation-flow.js");
const withCtx = await loadWorkerSkillWithContext(
params.skillPackName,
params.workerId,
root,
{
flowRaw,
currentStepName,
acceptedStepNames: parseAcceptedSteps(acceptedRaw),
selectedRecipeRef,
},
);
return {
worker: withCtx.worker,
promptBody: withCtx.promptBody,
source: "disk",
};
} catch {
// fall through to declaration
}
const raw = readWorkerSetYamlForDeclaration(params.blackboard, params.session);
const parsed = raw ? parseWorkerSetYaml(raw.yaml) : null;
const entry = parsed?.workers.find(
(w) => w.ref?.trim() === params.workerId.trim(),
);
if (!entry?.ref) {
throw new Error(
`未找到 worker「${params.workerId}」的磁盘 SKILL且 设计.worker集 中无对应声明`,
);
}
const template = await loadWorkerTemplate(
params.skillPackName,
entry.ref,
root,
);
const built = buildDeclaredWorkerSkill({
skillPackName: params.skillPackName,
entry,
template,
workerSet: parsed,
});
return { ...built, source: "declaration" };
}
async function loadWorkerTemplate(
skillPackName: string,
workerId: string,
skillsRoot: string,
): Promise<WorkerTemplateDoc | null> {
const { loadSkill } = await import("./loader.js");
let packRoot: string | undefined;
try {
const skill = await loadSkill(skillPackName, skillsRoot);
packRoot = skill.skillPackRoot;
} catch {
return null;
}
if (!packRoot) return null;
const file = path.join(
skillsRoot,
packRoot,
"worker-templates",
`${workerId}.yaml`,
);
try {
const raw = await readFile(file, "utf8");
const doc = parseYaml(raw) as WorkerTemplateDoc;
return doc && typeof doc === "object" ? doc : null;
} catch {
return null;
}
}
export function buildDeclaredWorkerSkill(params: {
skillPackName: string;
entry: WorkerSetEntry;
template: WorkerTemplateDoc | null;
workerSet: ParsedWorkerSet | null;
}): { worker: ParsedWorkerSkill; promptBody: string } {
const id = params.entry.ref!.trim();
const staticTags =
params.entry.context?.static ??
params.template?.suggested_context?.static ??
["设计.worker集", CONTEXT_BRIEF_TAG];
const dynamicTags =
params.entry.context?.dynamic ??
params.template?.suggested_context?.dynamic ??
["用户.最新输入"];
const outputTags =
params.entry.outputs?.length
? params.entry.outputs
: params.template?.suggested_outputs?.length
? params.template.suggested_outputs
: ["输出.用户展示"];
const resident = parseResidentContext(params.workerSet?.resident_context);
const residentForWorker = entriesForWorker(resident, id);
const residentStaticTags = residentForWorker
.filter((e) => e.position !== "dynamic")
.map(residentTagFor);
const residentDynamicTags = residentForWorker
.filter((e) => e.position === "dynamic")
.map(residentTagFor);
const inputTags = [
...new Set([
...staticTags,
...residentStaticTags,
...dynamicTags,
...residentDynamicTags,
CONTEXT_BRIEF_TAG,
]),
];
const duty =
params.entry.duty?.trim() ||
params.template?.duty?.trim() ||
`执行 ${id}`;
const excerpt = params.template?.prompt_excerpt?.trim() || "";
const presentation = params.entry.presentation
? JSON.stringify(params.entry.presentation, null, 2)
: "";
const narrative = params.workerSet?.narrative_guide?.trim() || "";
const premises = (params.workerSet?.core_premises ?? []).filter(Boolean);
const residentSection = formatResidentPromptSection(resident, id);
const body = [
`# ${params.template?.label ?? id}`,
"",
"## 角色与职责",
"",
duty,
"",
excerpt ? `## 写法要点\n\n${excerpt}` : "",
presentation ? `## presentation实例\n\n\`\`\`json\n${presentation}\n\`\`\`` : "",
narrative ? `## 叙事指南\n\n${narrative}` : "",
premises.length
? `## 核心实现前提\n\n${premises.map((p) => `- ${p}`).join("\n")}`
: "",
residentSection,
params.entry.rationale
? `## 为何需要本 worker\n\n${params.entry.rationale}`
: "",
"",
"## 输出",
"",
`写入 outputTags${outputTags.join("、")}`,
]
.filter(Boolean)
.join("\n");
const worker: ParsedWorkerSkill = {
id,
skill: params.skillPackName,
name: params.template?.label ?? id,
description: duty.slice(0, 200),
version: 1,
inputTags,
outputTags,
inputMerge: "latest",
path: `declaration:${id}`,
body,
};
const promptBody = [
"(本 worker 由 设计.worker集 声明驱动,无独立磁盘 SKILL。",
"",
body,
].join("\n");
return { worker, promptBody };
}