改写回合推演技能合同,并接上画面工作流库。
旧产物名仍能读到,避免改名后上下文对不上。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 工作流库 → 创作提示里的短目录。不含 ComfyUI 节点图。
|
||||
*/
|
||||
|
||||
import { listImageWorkflows, type ImageWorkflowEntry } from "./store.js";
|
||||
|
||||
export function formatImageWorkflowCatalogForPrompt(
|
||||
entries?: ImageWorkflowEntry[],
|
||||
): string {
|
||||
const workflows = entries ?? listImageWorkflows();
|
||||
if (!workflows.length) return "";
|
||||
|
||||
const lines = workflows.map(
|
||||
(entry) => `- ${entry.id}|${entry.name}|${entry.summary}`,
|
||||
);
|
||||
|
||||
return `## 【工作流库 · 可选用】
|
||||
|
||||
本机已导出的画面工作流。选定一个 id 写入本步产物。这里只有短名和一句画面说明。
|
||||
|
||||
${lines.join("\n")}`;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
getImageWorkflow,
|
||||
getImageWorkflowsDir,
|
||||
listImageWorkflows,
|
||||
type ImageWorkflowEntry,
|
||||
} from "./store.js";
|
||||
export { formatImageWorkflowCatalogForPrompt } from "./catalog.js";
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 本机 ComfyUI 工作流库。
|
||||
* 目录:{用户数据}/image-workflows/<id>/
|
||||
* meta.json 短名、一句画面说明、提示词节点
|
||||
* workflow.json ComfyUI「API 格式」导出的节点图
|
||||
* 创作模型只看短目录;节点图留在磁盘,提交生图时才读。
|
||||
*/
|
||||
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { getUserDataDir } from "../config/user-data-dir.js";
|
||||
|
||||
export type ImageWorkflowEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
/** 给创作模型看的一句画面说明 */
|
||||
summary: string;
|
||||
/** ComfyUI API JSON 里的节点 id */
|
||||
promptNode: string;
|
||||
/** 该节点上要覆盖的字段,CLIP 文本一般为 text */
|
||||
promptField: string;
|
||||
/** 可选:这种风格的提示词习惯,仍不是节点图 */
|
||||
promptNotes?: string;
|
||||
};
|
||||
|
||||
const DIR_NAME = "image-workflows";
|
||||
|
||||
export function getImageWorkflowsDir(): string {
|
||||
return path.join(getUserDataDir(), DIR_NAME);
|
||||
}
|
||||
|
||||
function asString(v: unknown): string | undefined {
|
||||
if (typeof v !== "string") return undefined;
|
||||
const t = v.trim();
|
||||
return t || undefined;
|
||||
}
|
||||
|
||||
function readJson(file: string): unknown | null {
|
||||
try {
|
||||
return JSON.parse(readFileSync(file, "utf8")) as unknown;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function parseEntry(folderName: string, dir: string): ImageWorkflowEntry | null {
|
||||
const metaPath = path.join(dir, "meta.json");
|
||||
const workflowPath = path.join(dir, "workflow.json");
|
||||
if (!existsSync(metaPath) || !existsSync(workflowPath)) return null;
|
||||
const meta = readJson(metaPath);
|
||||
const workflow = readJson(workflowPath);
|
||||
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return null;
|
||||
if (!workflow || typeof workflow !== "object" || Array.isArray(workflow)) return null;
|
||||
const row = meta as Record<string, unknown>;
|
||||
const id = asString(row.id) ?? folderName.trim();
|
||||
const name = asString(row.name);
|
||||
const summary = asString(row.summary);
|
||||
const promptNode = asString(row.prompt_node) ?? asString(row.promptNode);
|
||||
const promptField = asString(row.prompt_field) ?? asString(row.promptField);
|
||||
if (!id || !name || !summary || !promptNode || !promptField) return null;
|
||||
const promptNotes = asString(row.prompt_notes) ?? asString(row.promptNotes);
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
summary,
|
||||
promptNode,
|
||||
promptField,
|
||||
...(promptNotes ? { promptNotes } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/** 扫描目录。缺文件或字段不全的文件夹跳过。 */
|
||||
export function listImageWorkflows(): ImageWorkflowEntry[] {
|
||||
const root = getImageWorkflowsDir();
|
||||
if (!existsSync(root)) return [];
|
||||
const out: ImageWorkflowEntry[] = [];
|
||||
let names: string[] = [];
|
||||
try {
|
||||
names = readdirSync(root);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
for (const name of names) {
|
||||
if (!name || name.startsWith(".")) continue;
|
||||
const dir = path.join(root, name);
|
||||
try {
|
||||
if (!statSync(dir).isDirectory()) continue;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const entry = parseEntry(name, dir);
|
||||
if (entry) out.push(entry);
|
||||
}
|
||||
out.sort((a, b) => a.name.localeCompare(b.name, "zh"));
|
||||
return out;
|
||||
}
|
||||
|
||||
export function getImageWorkflow(id: string): ImageWorkflowEntry | null {
|
||||
const key = id.trim();
|
||||
if (!key) return null;
|
||||
return listImageWorkflows().find((entry) => entry.id === key) ?? null;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
* 新库:实现 formatForPrompt,在本文件 register。
|
||||
*/
|
||||
|
||||
import { formatImageWorkflowCatalogForPrompt } from "../image-workflow/catalog.js";
|
||||
import { formatPreferenceCatalogForPrompt } from "../preference/catalog.js";
|
||||
import { formatStylePackCatalogForPrompt } from "../style-pack/catalog.js";
|
||||
|
||||
@@ -74,6 +75,12 @@ registerLibrary({
|
||||
formatForPrompt: formatPreferenceCatalogForPrompt,
|
||||
});
|
||||
|
||||
registerLibrary({
|
||||
id: "image-workflows",
|
||||
label: "工作流库",
|
||||
formatForPrompt: formatImageWorkflowCatalogForPrompt,
|
||||
});
|
||||
|
||||
/**
|
||||
* 从模块列表收集绑定的库 id(去重、保序)。
|
||||
* 对话落盘:通常传入 intake≠recipe 的模块。
|
||||
|
||||
@@ -91,6 +91,8 @@ import {
|
||||
isInstanceStep,
|
||||
findStepByRef,
|
||||
findModuleByName,
|
||||
legacyTagsRetiredByWrite,
|
||||
readModuleArtifactContent,
|
||||
hasAcceptedCloserStep,
|
||||
parseCreationFlow,
|
||||
DESIGN_FLOW_WORKER_ID,
|
||||
@@ -1127,15 +1129,18 @@ export class PhaseRuntime {
|
||||
const other = findStepByRef(flow, item);
|
||||
return other?.name === step.name;
|
||||
}).length;
|
||||
const tag = findModuleByName(catalog, step.name)?.artifact?.trim();
|
||||
if (tag) {
|
||||
const stored = readModuleArtifactContent(
|
||||
findModuleByName(catalog, step.name),
|
||||
(tag) => this.blackboard.getContentByTag(tag),
|
||||
);
|
||||
if (stored?.content) {
|
||||
const pruned = pruneRepeatableArtifactContent({
|
||||
raw: this.blackboard.getContentByTag(tag) ?? "",
|
||||
raw: stored.content,
|
||||
step,
|
||||
remainingSameModule,
|
||||
});
|
||||
this.blackboard.write({
|
||||
tag,
|
||||
tag: stored.tag,
|
||||
content: pruned,
|
||||
source: "runtime",
|
||||
});
|
||||
@@ -1710,11 +1715,12 @@ export class PhaseRuntime {
|
||||
}
|
||||
|
||||
const pendingModule = findModuleByName(catalog, step.name);
|
||||
const storedArtifact = readModuleArtifactContent(pendingModule, (tag) =>
|
||||
this.blackboard.getContentByTag(tag),
|
||||
);
|
||||
if (reenter) {
|
||||
const artTag = pendingModule?.artifact?.trim();
|
||||
const hasArt = Boolean(
|
||||
artTag && this.blackboard.getContentByTag(artTag)?.trim(),
|
||||
);
|
||||
const artTag = storedArtifact?.tag;
|
||||
const hasArt = Boolean(storedArtifact?.content);
|
||||
if (
|
||||
!isStepAccepted(step, accepted) &&
|
||||
!hasArt &&
|
||||
@@ -1764,7 +1770,7 @@ export class PhaseRuntime {
|
||||
if (reenter) {
|
||||
const existing = this.ensureReenterArtifact(
|
||||
pinned.id,
|
||||
pendingModule?.artifact,
|
||||
storedArtifact?.tag ?? pendingModule?.artifact,
|
||||
);
|
||||
if (existing) {
|
||||
this.reopenArtifactForRevision(existing, pinned);
|
||||
@@ -2719,6 +2725,11 @@ export class PhaseRuntime {
|
||||
content: toWrite,
|
||||
source: workerId,
|
||||
});
|
||||
if (toWrite.trim()) {
|
||||
for (const oldTag of legacyTagsRetiredByWrite(tag)) {
|
||||
this.blackboard.deleteByTag(oldTag);
|
||||
}
|
||||
}
|
||||
if (
|
||||
inferLifecycleStage(this.session) === "play" &&
|
||||
(tag === "输出.用户展示" || tag === "输出.开场白")
|
||||
|
||||
@@ -36,11 +36,12 @@ const WORKER_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
const FIXED_TOPIC_LABELS: Record<string, string> = {
|
||||
"aesthetics-interaction": "美学纲领与交互范式",
|
||||
"aesthetics-interaction": "美学纲领",
|
||||
interaction: "交互范式",
|
||||
narrative_guide: "叙事指南与故事推进",
|
||||
"narrative-guide": "叙事指南",
|
||||
"story-progression": "故事推进",
|
||||
"image-prompt": "画面与生图",
|
||||
"story-progression": "故事推进与交互范式",
|
||||
input_protocol: "输入协议",
|
||||
core_premise: "核心前提",
|
||||
aesthetics: "美学纲领",
|
||||
@@ -58,6 +59,11 @@ const CREATION_MODE_LABELS: Record<string, string> = {
|
||||
listen_serial: "听书连载",
|
||||
};
|
||||
|
||||
/** 顶栏产品面(与作品对话并列) */
|
||||
const PRODUCT_SURFACE_LABELS: Record<string, string> = {
|
||||
"direct-chat": "直聊",
|
||||
};
|
||||
|
||||
const SKILL_PACK_LABELS: Record<string, string> = {
|
||||
"world-simulator": "回合推演",
|
||||
"expand-assistant": "扩写助手",
|
||||
@@ -74,6 +80,12 @@ export function displayStageLabel(id: string | undefined | null): string {
|
||||
return STAGE_LABELS[id] ?? id;
|
||||
}
|
||||
|
||||
/** 顶栏产品面展示名 */
|
||||
export function displayProductSurfaceLabel(id: string | undefined | null): string {
|
||||
if (!id) return "";
|
||||
return PRODUCT_SURFACE_LABELS[id] ?? id;
|
||||
}
|
||||
|
||||
/** 进料方式展示名(新建对话框 / 工作面 kicker) */
|
||||
export function displayCreationModeLabel(id: string | undefined | null): string {
|
||||
if (!id) return "";
|
||||
|
||||
@@ -496,6 +496,7 @@ const SYNTH_FALLBACK_CONTEXT: Record<
|
||||
"设计.变量设计与更新规则",
|
||||
"设计.生成规则",
|
||||
"设计.变量控制上下文",
|
||||
"设计.画面与生图",
|
||||
],
|
||||
dynamic: [
|
||||
"输出.用户展示",
|
||||
@@ -508,13 +509,14 @@ const SYNTH_FALLBACK_CONTEXT: Record<
|
||||
"world-simulator": {
|
||||
static: [
|
||||
"设计.舞台骨架",
|
||||
"设计.世界法则",
|
||||
"设计.实现机制",
|
||||
"设计.变量设计与更新规则",
|
||||
"设计.生成规则",
|
||||
"设计.叙事指南与故事推进",
|
||||
"设计.叙事指南",
|
||||
"设计.故事推进",
|
||||
"设计.正文组成",
|
||||
"设计.故事推进与交互范式",
|
||||
"设计.监控栏",
|
||||
],
|
||||
dynamic: [
|
||||
@@ -529,7 +531,7 @@ const SYNTH_FALLBACK_CONTEXT: Record<
|
||||
"设计.叙事指南与故事推进",
|
||||
"设计.叙事指南",
|
||||
"设计.故事推进",
|
||||
"设计.正文组成",
|
||||
"设计.故事推进与交互范式",
|
||||
"设计.监控栏",
|
||||
],
|
||||
dynamic: ["输出.用户展示"],
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
formatAcceptedUnitsForPrompt,
|
||||
} from "./creation-units.js";
|
||||
import { projectFragmentContent } from "./context-fragment.js";
|
||||
import { tagReadFallbacks } from "./creation-flow.js";
|
||||
import {
|
||||
buildHistoryFallback,
|
||||
isDialogueHistoryRef,
|
||||
@@ -79,6 +80,21 @@ function readTagContent(
|
||||
inputs: Record<string, string>,
|
||||
blackboard: Blackboard,
|
||||
inputMerge: BlackboardInputMerge,
|
||||
): string {
|
||||
const direct = readOneTag(tag, inputs, blackboard, inputMerge);
|
||||
if (direct) return direct;
|
||||
for (const alt of tagReadFallbacks(tag)) {
|
||||
const found = readOneTag(alt, inputs, blackboard, inputMerge);
|
||||
if (found) return found;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function readOneTag(
|
||||
tag: string,
|
||||
inputs: Record<string, string>,
|
||||
blackboard: Blackboard,
|
||||
inputMerge: BlackboardInputMerge,
|
||||
): string {
|
||||
if (inputs[tag]?.trim()) return inputs[tag]!.trim();
|
||||
const items = blackboard.queryByPatterns([tag], inputMerge);
|
||||
|
||||
+101
-13
@@ -1309,16 +1309,95 @@ export function creationFlowFromRecipeSeed(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 改名后,旧步骤名和旧产物 tag 仍对上同一能力。
|
||||
* 新写入用目录里的现名和现 tag;读取时两边都认。
|
||||
*/
|
||||
const MODULE_LEGACY: Record<
|
||||
string,
|
||||
{ names: string[]; artifacts: string[]; current: string }
|
||||
> = {
|
||||
mechanism: {
|
||||
names: ["实现机制"],
|
||||
artifacts: ["设计.实现机制"],
|
||||
current: "设计.世界法则",
|
||||
},
|
||||
};
|
||||
|
||||
/** 读上下文时,现用 tag 与旧 tag 互为兜底。 */
|
||||
export function tagReadFallbacks(tag: string): string[] {
|
||||
const t = tag.trim();
|
||||
for (const legacy of Object.values(MODULE_LEGACY)) {
|
||||
if (t === legacy.current) return [...legacy.artifacts];
|
||||
if (legacy.artifacts.includes(t)) return [legacy.current];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/** 写成现用 tag 后,旧 tag 应清掉,避免两份法则同时进上下文。 */
|
||||
export function legacyTagsRetiredByWrite(tag: string): string[] {
|
||||
const t = tag.trim();
|
||||
for (const legacy of Object.values(MODULE_LEGACY)) {
|
||||
if (t === legacy.current) return [...legacy.artifacts];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/** 现用产物 tag 在前,旧 tag 在后。 */
|
||||
export function moduleArtifactTags(
|
||||
mod: Pick<ModuleCatalogEntry, "id" | "artifact">,
|
||||
): string[] {
|
||||
const current = mod.artifact?.trim();
|
||||
const legacy = MODULE_LEGACY[mod.id]?.artifacts ?? [];
|
||||
return [...new Set([...(current ? [current] : []), ...legacy])];
|
||||
}
|
||||
|
||||
export function moduleOwnsArtifactTag(
|
||||
mod: Pick<ModuleCatalogEntry, "id" | "artifact">,
|
||||
tag: string,
|
||||
): boolean {
|
||||
const bare = tag.trim();
|
||||
if (!bare) return false;
|
||||
return moduleArtifactTags(mod).some(
|
||||
(artifact) => bare === artifact || bare.startsWith(`${artifact}#`),
|
||||
);
|
||||
}
|
||||
|
||||
function filledOwnsModule(
|
||||
filled: ReadonlySet<string>,
|
||||
mod: Pick<ModuleCatalogEntry, "id" | "artifact">,
|
||||
): boolean {
|
||||
return moduleArtifactTags(mod).some((tag) => filled.has(tag));
|
||||
}
|
||||
|
||||
/** 黑板里已有内容的那条 tag;都空时返回现用 tag。 */
|
||||
export function readModuleArtifactContent(
|
||||
mod: Pick<ModuleCatalogEntry, "id" | "artifact"> | null | undefined,
|
||||
read: (tag: string) => string | undefined,
|
||||
): { tag: string; content: string } | null {
|
||||
if (!mod) return null;
|
||||
const tags = moduleArtifactTags(mod);
|
||||
for (const tag of tags) {
|
||||
const content = read(tag)?.trim() || "";
|
||||
if (content) return { tag, content };
|
||||
}
|
||||
const current = tags[0];
|
||||
return current ? { tag: current, content: "" } : null;
|
||||
}
|
||||
|
||||
/** 无 id 时的兜底(目录仍应显式写 id) */
|
||||
function slugFromName(name: string): string {
|
||||
const map: Record<string, string> = {
|
||||
美学纲领与交互范式: "aesthetics-interaction",
|
||||
交互范式: "interaction",
|
||||
美学纲领: "aesthetics",
|
||||
美学纲领: "aesthetics-interaction",
|
||||
故事推进与交互范式: "story-progression",
|
||||
叙事指南: "narrative-guide",
|
||||
画面与生图: "image-prompt",
|
||||
叙事指南与故事推进: "narrative",
|
||||
故事推进: "story-progression",
|
||||
实现机制: "mechanism",
|
||||
世界法则: "mechanism",
|
||||
实现机制: "mechanism", // 旧称
|
||||
舞台骨架: "world-blueprint",
|
||||
世界蓝图与人文地理: "world-blueprint", // 旧称
|
||||
世界蓝图: "world-blueprint", // 旧简称
|
||||
@@ -1958,8 +2037,8 @@ export function formatFlowProgressForAgent(params: {
|
||||
for (const step of flow.steps) {
|
||||
if (isPrototypeStep(step)) continue;
|
||||
const acceptedHere = isStepAccepted(step, accepted);
|
||||
const art = catalog ? artifactTagForStep(step.name, catalog) : null;
|
||||
const hasArtifact = Boolean(art && filled.has(art));
|
||||
const modForArt = catalog ? findModuleByName(catalog, step.name) : null;
|
||||
const hasArtifact = Boolean(modForArt && filledOwnsModule(filled, modForArt));
|
||||
const repeatable = repeatableNames.has(step.name);
|
||||
if (acceptedHere || (!repeatable && hasArtifact)) {
|
||||
markDone(
|
||||
@@ -1981,7 +2060,7 @@ export function formatFlowProgressForAgent(params: {
|
||||
if (catalog) {
|
||||
for (const mod of catalogModulesForIntake(catalog, "recipe")) {
|
||||
if (mod.repeatable === true) continue;
|
||||
if (!mod.artifact || !filled.has(mod.artifact)) continue;
|
||||
if (!filledOwnsModule(filled, mod)) continue;
|
||||
if (doneNames.has(mod.name)) continue;
|
||||
markDone(mod.name, undefined, "产物已在黑板", false);
|
||||
}
|
||||
@@ -2032,7 +2111,7 @@ export function formatFlowProgressForAgent(params: {
|
||||
catalog && flow
|
||||
? flow.steps.filter((step) => {
|
||||
if (isPrototypeStep(step)) return false;
|
||||
const mod = catalog.modules.find((m) => m.name === step.name);
|
||||
const mod = findModuleByName(catalog, step.name);
|
||||
return Boolean(mod?.closer) && !isStepAccepted(step, accepted);
|
||||
})
|
||||
: [];
|
||||
@@ -2131,7 +2210,10 @@ export function validateCreationFlow(
|
||||
const seenIds = new Set<string>();
|
||||
const allowed = catalog
|
||||
? new Set(
|
||||
catalogModulesForIntake(catalog, "recipe").map((m) => m.name),
|
||||
catalogModulesForIntake(catalog, "recipe").flatMap((m) => [
|
||||
m.name,
|
||||
...(MODULE_LEGACY[m.id]?.names ?? []),
|
||||
]),
|
||||
)
|
||||
: null;
|
||||
const reportedFreshDup = new Set<string>();
|
||||
@@ -2147,7 +2229,7 @@ export function validateCreationFlow(
|
||||
errors.push(`「${step.name}」不在模块目录中`);
|
||||
}
|
||||
|
||||
const mod = catalog?.modules.find((m) => m.name === step.name);
|
||||
const mod = catalog ? findModuleByName(catalog, step.name) : null;
|
||||
if (isReviseStep(step)) {
|
||||
const ref = step.revises?.trim() ?? "";
|
||||
if (!ref) {
|
||||
@@ -2220,7 +2302,7 @@ export function validateStepReadyToRun(
|
||||
step: CreationFlowStep,
|
||||
catalog: ModuleCatalog | null,
|
||||
): CreationFlowValidation {
|
||||
const mod = catalog?.modules.find((m) => m.name === step.name);
|
||||
const mod = findModuleByName(catalog, step.name);
|
||||
if (!mod?.params?.length) return { ok: true, errors: [] };
|
||||
const missing = missingRequiredStepParams(step, mod);
|
||||
if (missing.length === 0) return { ok: true, errors: [] };
|
||||
@@ -2260,7 +2342,7 @@ export function artifactTagForStep(
|
||||
name: string,
|
||||
catalog: ModuleCatalog | null,
|
||||
): string | null {
|
||||
return catalog?.modules.find((m) => m.name === name)?.artifact ?? null;
|
||||
return findModuleByName(catalog, name)?.artifact ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2458,7 +2540,7 @@ export function listCallableCatalogModules(params: {
|
||||
for (const mod of catalogModulesForIntake(catalog, "recipe")) {
|
||||
if (scheduled.has(mod.name)) continue;
|
||||
if (!mod.repeatable) {
|
||||
if (mod.artifact && filled.has(mod.artifact)) continue;
|
||||
if (filledOwnsModule(filled, mod)) continue;
|
||||
if (accepted.has(mod.name)) continue;
|
||||
}
|
||||
out.push({
|
||||
@@ -2566,7 +2648,7 @@ export function formatCreationFlowForUser(
|
||||
...(blockedBy?.length ? { blockedBy } : {}),
|
||||
runState,
|
||||
...(mod?.artifact ? { artifactTag: mod.artifact } : {}),
|
||||
...(mod?.artifact && filled.has(mod.artifact) ? { hasArtifact: true } : {}),
|
||||
...(mod && filledOwnsModule(filled, mod) ? { hasArtifact: true } : {}),
|
||||
...(!proto && s.params ? { params: s.params } : {}),
|
||||
...(paramsMissing.length > 0 ? { paramsMissing } : {}),
|
||||
};
|
||||
@@ -2579,7 +2661,13 @@ export function findModuleByName(
|
||||
name: string,
|
||||
): ModuleCatalogEntry | null {
|
||||
if (!catalog) return null;
|
||||
return catalog.modules.find((m) => m.name === name) ?? null;
|
||||
const exact = catalog.modules.find((m) => m.name === name);
|
||||
if (exact) return exact;
|
||||
const legacyId = Object.entries(MODULE_LEGACY).find(([, legacy]) =>
|
||||
legacy.names.includes(name),
|
||||
)?.[0];
|
||||
if (!legacyId) return null;
|
||||
return catalog.modules.find((m) => m.id === legacyId) ?? null;
|
||||
}
|
||||
|
||||
/** 已验收步骤名列表(JSON 数组或换行文本) */
|
||||
|
||||
@@ -240,7 +240,7 @@ const DEFAULT_WORKER_CONTRACTS: Record<
|
||||
"设计.叙事指南与故事推进",
|
||||
"设计.叙事指南",
|
||||
"设计.故事推进",
|
||||
"设计.正文组成",
|
||||
"设计.故事推进与交互范式",
|
||||
"设计.监控栏",
|
||||
],
|
||||
dynamic: [
|
||||
@@ -268,7 +268,7 @@ const DEFAULT_WORKER_CONTRACTS: Record<
|
||||
"设计.叙事指南与故事推进",
|
||||
"设计.叙事指南",
|
||||
"设计.故事推进",
|
||||
"设计.正文组成",
|
||||
"设计.故事推进与交互范式",
|
||||
"设计.监控栏",
|
||||
],
|
||||
dynamic: ["输出.用户展示", "用户.最新输入", "变量.当前"],
|
||||
|
||||
Reference in New Issue
Block a user