完善创作节点循环与等待态交互,并大幅打磨 Web 壳与会话运行时。

- 节点循环:配方开局直坐 DAG 第一步;验收后先问下一步意向,再确认开干并可钉编排参数;「按意见修改」只重跑当前节点。
- 询问分流:能力 opening 走说话面引导,可跳过追问按题干去重;追问与产物同轮挂载,避免拆成两段历史。
- 运行时:补强 revision 重跑、创作流程合并/进度指针、工具循环与 worker 执行;新增运行日志与 web:watch。
- 前端:统一等待态文案与底栏主按钮,完善询问卡/产物验收/呈现壳样式与交互。
- 同步世界模拟器模块提示、编排文档与相关测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 01:28:12 +08:00
parent 20938b04d7
commit ec474cb946
56 changed files with 7460 additions and 1372 deletions

View File

@@ -6,6 +6,7 @@ import {
artifactTagForStep,
extractModuleOpening,
formatCreationFlowForUser,
formatFlowProgressForAgent,
formatModuleCatalogForAgent,
formatRecipeCatalogForAgent,
formatSelectedRecipeForAgent,
@@ -14,16 +15,23 @@ import {
loadModuleCatalog,
loadModulePrompt,
loadRecipeCatalog,
mergeCreationFlowPreservingAccepted,
needsFlowExpansion,
nextPendingStep,
parseCreationFlow,
pickRecordedStepId,
parseModuleCatalog,
parseModuleOpeningState,
parseRecipeCatalog,
parseRecipeYaml,
parseSelectedRecipeRef,
resolveDesignStepBinding,
shouldSkipModuleOpening,
isProgressPointerTag,
stringifyCreationFlow,
creationFlowFromRecipeSeed,
validateCreationFlow,
validateStepReadyToRun,
} from "../src/skills/creation-flow.js";
import { loadWorkerSkillWithContext } from "../src/skills/loader.js";
@@ -67,6 +75,79 @@ describe("creation-flow", () => {
});
});
it("keeps accepted steps intact when the planner rewrites the flow", () => {
const prev = `{
"status": "open",
"steps": [
{ "id": "美学纲领与交互范式", "name": "美学纲领与交互范式", "depends_on": [] }
]
}`;
const rewritten = `{
"status": "open",
"steps": [
{ "id": "美学纲领与交互范式", "name": "美学纲领与交互范式", "depends_on": ["实现机制"] },
{ "id": "实现机制", "name": "实现机制", "depends_on": [] }
]
}`;
const merged = mergeCreationFlowPreservingAccepted({
prevRaw: prev,
nextRaw: rewritten,
acceptedStepIds: ["美学纲领与交互范式"],
});
expect(merged.restored).toEqual(["美学纲领与交互范式"]);
const flow = parseCreationFlow(merged.raw)!;
expect(flow.steps[0]?.depends_on).toEqual([]);
expect(flow.steps.map((s) => s.id)).toEqual([
"美学纲领与交互范式",
"实现机制",
]);
});
it("re-inserts an accepted step the planner dropped", () => {
const prev = `{
"status": "open",
"steps": [
{ "id": "美学纲领与交互范式", "name": "美学纲领与交互范式", "depends_on": [] }
]
}`;
const rewritten = `{
"status": "open",
"steps": [{ "id": "实现机制", "name": "实现机制", "depends_on": [] }]
}`;
const merged = mergeCreationFlowPreservingAccepted({
prevRaw: prev,
nextRaw: rewritten,
acceptedStepIds: ["美学纲领与交互范式"],
});
expect(parseCreationFlow(merged.raw)!.steps.map((s) => s.id)).toEqual([
"美学纲领与交互范式",
"实现机制",
]);
});
it("leaves an untouched rewrite alone", () => {
const prev = `{
"status": "open",
"steps": [
{ "id": "美学纲领与交互范式", "name": "美学纲领与交互范式", "depends_on": [] }
]
}`;
const rewritten = `{
"status": "open",
"steps": [
{ "id": "美学纲领与交互范式", "name": "美学纲领与交互范式", "depends_on": [] },
{ "id": "实现机制", "name": "实现机制", "depends_on": ["美学纲领与交互范式"] }
]
}`;
const merged = mergeCreationFlowPreservingAccepted({
prevRaw: prev,
nextRaw: rewritten,
acceptedStepIds: ["美学纲领与交互范式"],
});
expect(merged.restored).toEqual([]);
expect(merged.raw).toBe(rewritten);
});
it("allows duplicate capability names with distinct ids", () => {
const flow = parseCreationFlow(`{
"status": "open",
@@ -171,6 +252,29 @@ describe("creation-flow", () => {
});
expect(view.steps[2]?.occurrence).toBe(2);
expect(view.steps[2]?.repeatable).toBe(true);
expect(view.steps[0]?.runState).toBe("current");
expect(view.steps[1]?.runState).toBe("pending");
expect(view.steps[2]?.runState).toBe("pending");
});
it("marks accepted steps done and the next pending current", () => {
const flow = parseCreationFlow(`{
"brief": "网恋回合",
"status": "open",
"steps": [
{ "name": "美学纲领与交互范式", "depends_on": [] },
{ "name": "生成规则", "depends_on": ["美学纲领与交互范式"] },
{ "id": "生成规则#2", "name": "生成规则", "depends_on": ["生成规则"] }
]
}`)!;
const view = formatCreationFlowForUser(flow, sampleCatalog, [
"美学纲领与交互范式",
]);
expect(view.steps.map((s) => s.runState)).toEqual([
"done",
"current",
"pending",
]);
});
it("loads world-simulator catalog and formats for agent", async () => {
@@ -273,6 +377,18 @@ steps:
expect(formatted).toContain("用户已选配方");
expect(formatted).toContain("核心思路");
expect(formatted).toContain("增量");
const seeded = creationFlowFromRecipeSeed(detail);
expect(seeded?.steps[0]?.name).toBe("美学纲领与交互范式");
expect(seeded?.status).toBe("open");
const raw = stringifyCreationFlow(seeded!);
expect(parseCreationFlow(raw)?.steps[0]?.id).toBe("美学纲领与交互范式");
expect(creationFlowFromRecipeSeed({ seed: null })).toBeNull();
expect(
creationFlowFromRecipeSeed({
seed: { version: 1, status: "open", steps: [] },
}),
).toBeNull();
});
it("falls back to legacy hint when methodology absent", () => {
@@ -326,9 +442,50 @@ recipes:
expect(selected.promptBody).toContain("核心思路");
expect(selected.promptBody).toContain("何时用");
expect(selected.promptBody).toContain("增量");
expect(selected.promptBody).toContain("【流程进度】");
expect(selected.promptBody).not.toContain("【导演】用户尚未手动选择");
});
it("tells design-flow which steps are done and not to reschedule them", async () => {
const flowRaw = JSON.stringify({
steps: [{ name: "美学纲领与交互范式", depends_on: [] }],
});
const loaded = await loadWorkerSkillWithContext(
"world-simulator",
"design-flow",
undefined,
{
selectedRecipeRef: "world-simulator",
flowRaw,
acceptedStepNames: ["美学纲领与交互范式"],
filledArtifactTags: ["设计.美学纲领与交互范式"],
},
);
expect(loaded.promptBody).toContain("【流程进度】");
expect(loaded.promptBody).toContain("已完成:");
expect(loaded.promptBody).toContain("美学纲领与交互范式");
expect(loaded.promptBody).toContain("已验收");
expect(loaded.promptBody).toContain("禁止再排一次执行");
expect(loaded.promptBody).toContain("生成规则");
});
it("formatFlowProgressForAgent keeps seed steps as draft not new work", () => {
const flow = parseCreationFlow(`{
"steps": [
{ "name": "美学纲领与交互范式", "depends_on": [] }
]
}`)!;
const text = formatFlowProgressForAgent({
flow,
acceptedStepIds: [],
catalog: sampleCatalog,
});
expect(text).toContain("草案已有、尚未验收");
expect(text).toContain("美学纲领与交互范式");
expect(text).toContain("不要当作新规划再写一遍");
expect(text).toContain("可反复追加:生成规则、具体实例");
});
it("injects aesthetics-interaction module prompt into design-step", async () => {
const flowRaw = JSON.stringify({
steps: [{ name: "美学纲领与交互范式", depends_on: [] }],
@@ -344,6 +501,8 @@ recipes:
},
);
expect(loaded.worker.outputTags).toContain("设计.美学纲领与交互范式");
expect(loaded.worker.outputTags).not.toContain("创作.当前步骤");
expect(loaded.worker.inputTags).toContain("创作.当前步骤");
expect(loaded.promptBody).toContain("美学纲领与交互范式");
expect(loaded.promptBody).toContain("体验契约");
expect(loaded.promptBody).toContain("程序开场");
@@ -386,6 +545,35 @@ recipes:
: "shown",
});
expect(
shouldSkipModuleOpening({
demand: "丧尸世界只有我不会被感染",
dependsOn: [],
}),
).toBe(true);
expect(
shouldSkipModuleOpening({
demand: "丧尸世界只有我不会被感染",
dependsOn: ["美学纲领与交互范式"],
}),
).toBe(false);
expect(
shouldSkipModuleOpening({
demand: "",
dependsOn: [],
}),
).toBe(false);
expect(
shouldSkipModuleOpening({
demand: "已有需求",
dependsOn: [],
phase: "shown",
}),
).toBe(false);
expect(isProgressPointerTag("创作.当前步骤")).toBe(true);
expect(isProgressPointerTag("设计.美学纲领与交互范式")).toBe(false);
expect(isProgressPointerTag("设计.创作流程")).toBe(false);
const binding = await resolveDesignStepBinding({
skillPackRoot: "dialogue/world-simulator",
flowRaw: JSON.stringify({
@@ -428,9 +616,11 @@ recipes:
const missing = parseCreationFlow(`{
"steps": [{ "name": "生成规则", "depends_on": [] }]
}`)!;
const bad = validateCreationFlow(missing, catalog);
expect(bad.ok).toBe(false);
expect(bad.errors.some((e) => e.includes("target"))).toBe(true);
// 编排期允许空壳;必填 params 在确认开干时钉齐
expect(validateCreationFlow(missing, catalog).ok).toBe(true);
const stepReady = validateStepReadyToRun(missing.steps[0]!, catalog);
expect(stepReady.ok).toBe(false);
expect(stepReady.errors.some((e) => e.includes("target"))).toBe(true);
const block = formatModuleCatalogForAgent(catalog!);
expect(block).toContain("编排参数");
@@ -483,6 +673,43 @@ recipes:
).toBeNull();
});
it("pickRecordedStepId ignores LLM junk on 创作.当前步骤", () => {
const flow = parseCreationFlow(`{
"status": "open",
"steps": [
{ "name": "美学纲领与交互范式", "depends_on": [] },
{ "name": "生成规则", "depends_on": ["美学纲领与交互范式"] }
]
}`)!;
expect(
pickRecordedStepId({
flow,
alreadyAccepted: [],
pinnedUnitId: "美学纲领与交互范式",
writtenCurrentStep: JSON.stringify({
schema: "context-fragment.v1",
: { : {}, : {} },
}),
}),
).toBe("美学纲领与交互范式");
expect(
pickRecordedStepId({
flow,
alreadyAccepted: [],
pinnedUnitId: "flow",
writtenCurrentStep: "交互范式",
}),
).toBe("美学纲领与交互范式");
expect(
pickRecordedStepId({
flow,
alreadyAccepted: ["美学纲领与交互范式"],
pinnedUnitId: "",
writtenCurrentStep: "",
}),
).toBe("生成规则");
});
it("open flow needs expansion after listed steps accepted", () => {
const flow = parseCreationFlow(`{
"status": "open",