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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 00:39:32 +08:00
co-authored by Cursor
parent 2b74c30d36
commit e670a5129c
167 changed files with 22955 additions and 5659 deletions
+178 -225
View File
@@ -1,6 +1,20 @@
import type { RuntimeSession } from "../types/runtime.js";
import {
deriveDesignStageScope,
deriveRunWorkerScope,
instantiateMeta,
parseWorkerSetYaml,
runWorkerMeta,
} from "../skills/worker-set-parse.js";
import {
canEnterPlay,
hasAcceptedWorkerSet,
inferLifecycleStage,
type LifecycleStage,
} from "../skills/worker-declaration.js";
export type LifecycleStage = "design" | "play";
export type { LifecycleStage };
export { canEnterPlay, hasAcceptedWorkerSet, inferLifecycleStage };
export type SkillCatalogEntry = {
id: string;
@@ -9,6 +23,8 @@ export type SkillCatalogEntry = {
/** 这一步要干嘛(占位说明,详细设计后续补充) */
purpose: string;
status: "pending" | "active" | "done" | "skipped";
/** 同一 skill 被 invoke 的次数(design 阶段 instantiate 可多次) */
runCount?: number;
};
type CatalogTemplate = {
@@ -18,242 +34,172 @@ type CatalogTemplate = {
purpose: string;
};
const GENERIC_DESIGN: CatalogTemplate[] = [
{
id: "interaction-paradigm",
stage: "design",
label: "交互范式",
purpose: "弄清用户要什么体验,产出 run skill 清单(要哪些能力)。",
},
{
id: "intake",
stage: "design",
label: "启动收集",
purpose: "收集最小需求,写入用户.需求 / book.brief。",
},
{
id: "world-blueprint",
stage: "design",
label: "世界蓝图",
purpose: "定背景板与核心冲突,供后续 skill 引用。",
},
{
id: "narrative-guide",
stage: "design",
label: "叙事指南",
purpose: "定 POV、时态、文风(static 上下文上半)。",
},
{
export type BuildSkillCatalogOptions = {
/** `设计.worker集` 或 `.草稿` 的 YAML 正文 */
workerSetYaml?: string;
};
function templatesFor(_skillPackId?: string): CatalogTemplate[] {
return [];
}
type ArtifactRunStats = {
total: number;
accepted: number;
pending: number;
rejected: number;
};
function collectArtifactRuns(session: RuntimeSession): Map<string, ArtifactRunStats> {
const map = new Map<string, ArtifactRunStats>();
for (const art of session.artifacts) {
if (!art.workerId) continue;
const prev = map.get(art.workerId) ?? {
total: 0,
accepted: 0,
pending: 0,
rejected: 0,
};
prev.total += 1;
if (art.status === "accepted") prev.accepted += 1;
else if (art.status === "rejected") prev.rejected += 1;
else prev.pending += 1;
map.set(art.workerId, prev);
}
return map;
}
function statusForWorkerId(
id: string,
session: RuntimeSession,
runs: Map<string, ArtifactRunStats>,
skipped: Set<string>,
): SkillCatalogEntry["status"] {
if (skipped.has(id)) return "skipped";
const stats = runs.get(id);
if (stats?.accepted) return "done";
if (session.currentWorkerId === id) return "active";
if (stats?.pending) return "active";
return "pending";
}
function buildWorldSimulatorCatalog(
session: RuntimeSession,
lifecycle: LifecycleStage,
workerSetYaml?: string,
): SkillCatalogEntry[] {
const workerSet = parseWorkerSetYaml(workerSetYaml);
const runs = collectArtifactRuns(session);
const skipped = new Set(workerSet?.instantiate_hints?.skip ?? []);
if (lifecycle === "play") {
const runIds = deriveRunWorkerScope(workerSet);
const entries: SkillCatalogEntry[] = [
{
id: "agent-burst",
stage: "run",
label: "总管调度",
purpose: "总管 tool loop:读黑板 → 选择下一步 Worker。",
status:
session.phase === "running" && !session.currentWorkerId
? "active"
: "pending",
},
];
for (const id of runIds) {
const meta = runWorkerMeta(id);
const stats = runs.get(id);
entries.push({
id,
stage: "run",
label: meta.label,
purpose: meta.purpose,
status: statusForWorkerId(id, session, runs, new Set()),
runCount: stats?.total || undefined,
});
}
return entries;
}
const designSteps: Array<{ id: string; label: string; purpose: string }> = [
{
id: "design-flow",
label: "创作 · 流程编排",
purpose: "编排/增量修订可变 DAG → 设计.创作流程(可反复编入同能力)。",
},
{
id: "design-step",
label: "创作 · 执行步骤",
purpose: "按已认可流程执行当前一步模块。",
},
];
const entries: SkillCatalogEntry[] = designSteps.map((step) => ({
id: step.id,
stage: "design" as const,
label: step.label,
purpose: step.purpose,
status: statusForWorkerId(step.id, session, runs, skipped),
runCount: runs.get(step.id)?.total || undefined,
}));
const planned = deriveDesignStageScope(workerSet);
for (const id of planned) {
const meta = instantiateMeta(id);
const stats = runs.get(id);
entries.push({
id,
stage: "design",
label: meta.label,
purpose: meta.purpose,
status: statusForWorkerId(id, session, runs, skipped),
runCount: stats?.total || undefined,
});
}
const designIds = new Set(designSteps.map((s) => s.id));
for (const [id, stats] of runs) {
if (designIds.has(id) || id === "design-intake") continue;
if (planned.includes(id) || skipped.has(id)) continue;
const meta = instantiateMeta(id);
entries.push({
id,
stage: "design",
label: meta.label,
purpose: meta.purpose,
status: statusForWorkerId(id, session, runs, skipped),
runCount: stats.total || undefined,
});
}
entries.push({
id: "declare-ready",
stage: "design",
label: "实例就绪",
purpose: "agent 确认设计够开跑,进入游玩阶段。",
},
];
purpose: "Worker 集已 accept 即可进游玩;若声明了开局,建议先验收开场白(非强制锁定)。",
status: hasAcceptedWorkerSet(session) ? "done" : "pending",
});
const GENERIC_RUN: CatalogTemplate[] = [
{
id: "agent-burst",
stage: "run",
label: "Agent 调度",
purpose: "总管 tool loop:读黑板 → 选择 invoke 哪个 run skill。",
},
{
id: "narrator",
stage: "run",
label: "转述 / 展示",
purpose: "把世界状态编排成给用户看的叙事回复。",
},
{
id: "world-simulator",
stage: "run",
label: "世界模拟",
purpose: "裁决规则、更新事件流与可见信息。",
},
];
const BY_SKILL_PACK: Record<string, CatalogTemplate[]> = {
basic: [
{
id: "intake",
stage: "design",
label: "创作简报",
purpose: "收集题材、篇幅、风格 → book.brief。",
},
{
id: "declare-ready",
stage: "design",
label: "进入运行",
purpose: "简报确认后 declare ready。",
},
{
id: "outline",
stage: "run",
label: "生成大纲",
purpose: "根据 brief 生成 outline 产物。",
},
],
"weird-rules-short": [
{
id: "intake",
stage: "design",
label: "创作简报",
purpose: "收集规则怪谈情境与条数。",
},
{
id: "write-rules",
stage: "run",
label: "写规则",
purpose: "产出规则草稿与隐藏 core。",
},
{
id: "review-infer",
stage: "run",
label: "读者验收",
purpose: "盲读规则,不写 core。",
},
{
id: "review-author",
stage: "run",
label: "作者验收",
purpose: "对照 core 查一致性。",
},
],
"roleplay-game-theory": [
{
id: "intake",
stage: "design",
label: "博弈需求",
purpose: "收集情境、角色、轮次 → 用户.博弈需求。",
},
{
id: "setup-scenario",
stage: "design",
label: "结构化设定",
purpose: "整理为情境、规则、角色设定 tag。",
},
{
id: "declare-ready",
stage: "design",
label: "开始模拟",
purpose: "setup 验收后进入 run。",
},
{
id: "world-engine",
stage: "run",
label: "世界机",
purpose: "发可见信息、收行动、裁决回合。",
},
{
id: "role-decide",
stage: "run",
label: "角色决策",
purpose: "各角色独立产出思考与行动。",
},
{
id: "present-round",
stage: "run",
label: "回合展示",
purpose: "编排给用户看的本轮摘要。",
},
],
"world-simulator": [
{
id: "interaction-paradigm",
stage: "design",
label: "交互范式",
purpose: "定体验与 run skill 清单。",
},
{
id: "world-blueprint",
stage: "design",
label: "世界蓝图",
purpose: "背景板与核心设定。",
},
{
id: "topology",
stage: "design",
label: "拓扑 / 关系",
purpose: "地图、关系网或进阶路径(按需)。",
},
{
id: "generation-rules",
stage: "design",
label: "生成规则",
purpose: "元规则:如何生成实例内容。",
},
{
id: "narrative-guide",
stage: "design",
label: "叙事指南",
purpose: "正文气质与禁忌(static 上)。",
},
{
id: "variable-catalog",
stage: "design",
label: "变量目录",
purpose: "要跟踪的状态与更新格式。",
},
{
id: "declare-ready",
stage: "design",
label: "实例就绪",
purpose: "agent 声明可开跑。",
},
{
id: "world-simulator",
stage: "run",
label: "世界模拟器",
purpose: "每轮推进世界状态与事件流。",
},
{
id: "narrator",
stage: "run",
label: "转述者",
purpose: "把状态写成用户可见叙事。",
},
],
};
function templatesFor(skillPackId?: string): CatalogTemplate[] {
if (skillPackId && BY_SKILL_PACK[skillPackId]) {
return BY_SKILL_PACK[skillPackId];
}
return [...GENERIC_DESIGN, ...GENERIC_RUN];
}
export function inferLifecycleStage(session: RuntimeSession): LifecycleStage {
const override = session.slots.uiLifecycleStage;
if (override === "design" || override === "play") {
return override;
}
if (!session.slots.startupCompleted) return "design";
if (session.phase === "done") return "play";
return "play";
}
export function canEnterPlay(session: RuntimeSession): boolean {
return Boolean(session.slots.startupCompleted);
return entries;
}
export function buildSkillCatalog(
session: RuntimeSession,
skillPackId?: string,
lifecycle: LifecycleStage = inferLifecycleStage(session),
options?: BuildSkillCatalogOptions,
): SkillCatalogEntry[] {
if (skillPackId === "world-simulator") {
return buildWorldSimulatorCatalog(session, lifecycle, options?.workerSetYaml);
}
const templates = templatesFor(skillPackId);
const filtered = templates.filter((t) =>
lifecycle === "design" ? t.stage === "design" : t.stage === "run",
);
const workerIds = new Set(
session.artifacts.map((a) => a.workerId).filter(Boolean),
);
const acceptedWorkers = new Set(
session.artifacts
.filter((a) => a.status === "accepted")
.map((a) => a.workerId),
);
const runs = collectArtifactRuns(session);
return filtered.map((t) => {
let status: SkillCatalogEntry["status"] = "pending";
@@ -267,18 +213,25 @@ export function buildSkillCatalog(
status = "active";
}
} else if (t.id === "declare-ready") {
if (session.slots.startupCompleted) status = "done";
if (session.slots.startupCompleted || hasAcceptedWorkerSet(session)) {
status = "done";
}
} else if (t.id === "agent-burst") {
if (session.phase === "running" && !session.currentWorkerId) {
status = "active";
}
} else if (workerIds.has(t.id)) {
status = acceptedWorkers.has(t.id) ? "done" : "active";
} else if (runs.has(t.id)) {
status = statusForWorkerId(t.id, session, runs, new Set());
} else if (session.currentWorkerId === t.id) {
status = "active";
}
return { ...t, status };
const runCount = runs.get(t.id)?.total;
return {
...t,
status,
runCount: runCount || undefined,
};
});
}