Steer generation rules toward direction pools and readable fills.
Direction-first pools, recipes, and grade manifestation replace enum-tag stuffing; spawned products can be deleted and in-flight runs aborted. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+89
-26
@@ -28,6 +28,8 @@ import {
|
||||
hasSelectableCreationWork,
|
||||
hasAcceptedCloserStep,
|
||||
removeUnstartedInstance,
|
||||
removeRepeatableInstance,
|
||||
pruneRepeatableArtifactContent,
|
||||
looksLikeCreationFlowDoc,
|
||||
parseCreationFlow,
|
||||
pickRecordedStepId,
|
||||
@@ -37,7 +39,6 @@ import {
|
||||
parseRecipeYaml,
|
||||
parseSelectedRecipeRef,
|
||||
resolveDesignStepBinding,
|
||||
shouldSkipModuleOpening,
|
||||
isProgressPointerTag,
|
||||
stringifyCreationFlow,
|
||||
stripReviseSteps,
|
||||
@@ -776,31 +777,6 @@ modules:
|
||||
美学纲领与交互范式: "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);
|
||||
@@ -1253,6 +1229,93 @@ modules:
|
||||
expect(accepted.steps.some((s) => s.id === "生成规则#1")).toBe(true);
|
||||
});
|
||||
|
||||
it("removeRepeatableInstance drops accepted spawn and closer dep", () => {
|
||||
const flow = parseCreationFlow(`{
|
||||
"status": "open",
|
||||
"steps": [
|
||||
{ "name": "美学纲领与交互范式", "depends_on": [] },
|
||||
{ "id": "生成规则", "name": "生成规则", "role": "prototype", "depends_on": ["美学纲领与交互范式"] },
|
||||
{ "id": "生成规则#1", "name": "生成规则", "role": "instance", "from": "生成规则", "depends_on": ["美学纲领与交互范式"], "params": { "rule_id": "core-female-roles" } },
|
||||
{ "name": "开场白与开场变量", "depends_on": ["美学纲领与交互范式", "生成规则#1"] }
|
||||
]
|
||||
}`)!;
|
||||
const dropped = removeRepeatableInstance({
|
||||
flow,
|
||||
stepId: "生成规则#1",
|
||||
catalog: sampleCatalog,
|
||||
});
|
||||
expect("error" in dropped).toBe(false);
|
||||
if ("error" in dropped) return;
|
||||
expect(dropped.flow.steps.map((s) => s.id)).toEqual([
|
||||
"美学纲领与交互范式",
|
||||
"生成规则",
|
||||
"开场白与开场变量",
|
||||
]);
|
||||
expect(
|
||||
dropped.flow.steps.find((s) => s.name === "开场白与开场变量")?.depends_on,
|
||||
).toEqual(["美学纲领与交互范式"]);
|
||||
});
|
||||
|
||||
it("removeRepeatableInstance blocks when another instance depends on it", () => {
|
||||
const flow = parseCreationFlow(`{
|
||||
"steps": [
|
||||
{ "name": "美学纲领与交互范式", "depends_on": [] },
|
||||
{ "id": "生成规则", "name": "生成规则", "role": "prototype", "depends_on": ["美学纲领与交互范式"] },
|
||||
{ "id": "生成规则#1", "name": "生成规则", "role": "instance", "from": "生成规则", "depends_on": ["美学纲领与交互范式"], "params": { "target": "4位核心女性", "rule_id": "core-female-roles" } },
|
||||
{ "id": "具体实例", "name": "具体实例", "role": "prototype", "depends_on": ["生成规则"] },
|
||||
{ "id": "具体实例#1", "name": "具体实例", "role": "instance", "from": "具体实例", "depends_on": ["生成规则#1"] }
|
||||
]
|
||||
}`)!;
|
||||
const blocked = removeRepeatableInstance({
|
||||
flow,
|
||||
stepId: "生成规则#1",
|
||||
catalog: sampleCatalog,
|
||||
});
|
||||
expect(blocked).toMatchObject({ error: expect.stringContaining("具体实例") });
|
||||
const ok = removeRepeatableInstance({
|
||||
flow,
|
||||
stepId: "具体实例#1",
|
||||
catalog: sampleCatalog,
|
||||
});
|
||||
expect("error" in ok).toBe(false);
|
||||
});
|
||||
|
||||
it("pruneRepeatableArtifactContent strips the deleted rule_id", () => {
|
||||
const raw = JSON.stringify({
|
||||
schema: "context-fragment.v1",
|
||||
技能: "生成规则",
|
||||
brief: "两条规则",
|
||||
正文: {
|
||||
rules: [
|
||||
{ rule_id: "core-female-roles", 对象: "女臣" },
|
||||
{ rule_id: "zombies", 对象: "丧尸" },
|
||||
],
|
||||
},
|
||||
});
|
||||
const step = {
|
||||
id: "生成规则#1",
|
||||
name: "生成规则",
|
||||
role: "instance" as const,
|
||||
depends_on: [],
|
||||
params: { rule_id: "core-female-roles" },
|
||||
};
|
||||
const pruned = JSON.parse(
|
||||
pruneRepeatableArtifactContent({
|
||||
raw,
|
||||
step,
|
||||
remainingSameModule: 1,
|
||||
}),
|
||||
);
|
||||
expect(pruned.正文.rules.map((r) => r.rule_id)).toEqual(["zombies"]);
|
||||
expect(
|
||||
pruneRepeatableArtifactContent({
|
||||
raw,
|
||||
step,
|
||||
remainingSameModule: 0,
|
||||
}),
|
||||
).toBe("");
|
||||
});
|
||||
|
||||
it("blocks 具体实例 spawn until a 生成规则 instance is accepted", () => {
|
||||
const flow = parseCreationFlow(`{
|
||||
"steps": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildChanceBatchFromGenerationRule,
|
||||
isElementPool,
|
||||
runMaintainNeedGenerateSampling,
|
||||
} from "../src/skills/maintain-need-generate.js";
|
||||
|
||||
@@ -40,4 +41,61 @@ describe("maintain need_generate sampling", () => {
|
||||
expect(result?.schema).toBe("chance.batch.v1");
|
||||
expect(result?.results[0]?.id).toBe("pool:mood");
|
||||
});
|
||||
|
||||
it("treats 方向池 as non-element even if leftover 条目 exist", () => {
|
||||
expect(
|
||||
isElementPool({
|
||||
池型: "方向",
|
||||
方向: { 冷焰系: "静、刺、余烬不散,如…" },
|
||||
条目: [{ id: "should-not-draw" }],
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("skips 方向池 when building chance batch", () => {
|
||||
const rules = JSON.stringify({
|
||||
正文: {
|
||||
rules: [
|
||||
{
|
||||
rule_id: "stand",
|
||||
池: [
|
||||
{
|
||||
pool_id: "ability",
|
||||
池型: "方向",
|
||||
方向: { 时空操作系: "暂停、加速、回溯。例如 The World" },
|
||||
},
|
||||
{
|
||||
pool_id: "stand-type",
|
||||
池型: "元素",
|
||||
条目: [{ id: "close-range", 权重: 1 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const batch = buildChanceBatchFromGenerationRule(rules, "stand");
|
||||
expect(batch).toHaveLength(1);
|
||||
expect(batch![0]!.id).toBe("pool:stand-type");
|
||||
});
|
||||
|
||||
it("returns null when a rule only has 方向池", () => {
|
||||
const rules = JSON.stringify({
|
||||
正文: {
|
||||
rules: [
|
||||
{
|
||||
rule_id: "nickname",
|
||||
池: [
|
||||
{
|
||||
pool_id: "nick-style",
|
||||
池型: "方向",
|
||||
方向: { 自然现象系: "以天气、地理命名" },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(buildChanceBatchFromGenerationRule(rules, "nickname")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -658,6 +658,32 @@ describe("phase machine", () => {
|
||||
expect(left.effects).toEqual([]);
|
||||
});
|
||||
|
||||
it("leaving from review_artifact returns to the pick graph", () => {
|
||||
let session = createSession("default");
|
||||
const artifact = createArtifact({
|
||||
workerId: "design-step",
|
||||
stepId: "生成规则#1",
|
||||
outputTags: ["设计.生成规则"],
|
||||
});
|
||||
session = {
|
||||
...session,
|
||||
phase: "waiting_user",
|
||||
waitingReason: { kind: "review_artifact", artifactId: artifact.id },
|
||||
pendingArtifactId: artifact.id,
|
||||
artifacts: [artifact],
|
||||
currentWorkerId: "design-step",
|
||||
currentStepId: "生成规则#1",
|
||||
};
|
||||
const left = applyEvent(session, {
|
||||
type: "user_left_creation_step",
|
||||
payload: {},
|
||||
});
|
||||
expect(left.session.phase).toBe("waiting_user");
|
||||
expect(left.session.waitingReason?.kind).toBe("pick_creation_step");
|
||||
expect(left.session.pendingArtifactId).toBeUndefined();
|
||||
expect(left.session.currentStepId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("replan from the pick graph runs design-flow", () => {
|
||||
let session = createSession("default");
|
||||
session = {
|
||||
|
||||
+468
-1
@@ -3,12 +3,36 @@ import {
|
||||
createMockMainAgentResponse,
|
||||
createMockToolCall,
|
||||
MockLlmProvider,
|
||||
type LlmProvider,
|
||||
} from "../src/llm/client.js";
|
||||
import { PhaseRuntime, createDecision } from "../src/runtime/phase-runtime.js";
|
||||
import { createSession } from "../src/runtime/phase-machine.js";
|
||||
import { createArtifact, createSession } from "../src/runtime/phase-machine.js";
|
||||
import { Blackboard } from "../src/blackboard/blackboard.js";
|
||||
import { parseCreationFlow } from "../src/skills/creation-flow.js";
|
||||
|
||||
function countLlmCalls(llm: MockLlmProvider): { calls: () => number } {
|
||||
let n = 0;
|
||||
const bump = () => {
|
||||
n += 1;
|
||||
};
|
||||
const origComplete = llm.complete.bind(llm);
|
||||
llm.complete = (async (messages, options) => {
|
||||
bump();
|
||||
return origComplete(messages, options);
|
||||
}) as LlmProvider["complete"];
|
||||
const origStream = llm.completeStream.bind(llm);
|
||||
llm.completeStream = (async (messages, options, callbacks) => {
|
||||
bump();
|
||||
return origStream(messages, options, callbacks);
|
||||
}) as LlmProvider["completeStream"];
|
||||
const origTools = llm.completeWithTools.bind(llm);
|
||||
llm.completeWithTools = (async (messages, options) => {
|
||||
bump();
|
||||
return origTools(messages, options);
|
||||
}) as LlmProvider["completeWithTools"];
|
||||
return { calls: () => n };
|
||||
}
|
||||
|
||||
describe("phase runtime", () => {
|
||||
it("auto-loads default orchestrator and awaits first user input", async () => {
|
||||
const runtime = new PhaseRuntime();
|
||||
@@ -178,6 +202,120 @@ describe("phase runtime", () => {
|
||||
expect(runtime.getSession().waitingReason?.kind).toBe("pick_creation_step");
|
||||
});
|
||||
|
||||
it("picking a node shows module opening instead of calling LLM", async () => {
|
||||
const llm = new MockLlmProvider([
|
||||
JSON.stringify({
|
||||
outputs: { "设计.舞台骨架": { 范围: "不该到这一步" } },
|
||||
summary: "不应生成",
|
||||
askUser: null,
|
||||
}),
|
||||
]);
|
||||
const llmCalls = countLlmCalls(llm);
|
||||
const runtime = new PhaseRuntime({ llm });
|
||||
await runtime.start();
|
||||
runtime.getBlackboard().write({
|
||||
tag: "设计.创作流程",
|
||||
content: JSON.stringify({
|
||||
version: 1,
|
||||
status: "open",
|
||||
steps: [
|
||||
{
|
||||
id: "舞台骨架",
|
||||
name: "舞台骨架",
|
||||
depends_on: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
await runtime.enterSeededCreationPick();
|
||||
await runtime.pickCreationStep("舞台骨架");
|
||||
const session = runtime.getSession();
|
||||
expect(session.phase).toBe("waiting_user");
|
||||
expect(session.waitingReason?.kind).toBe("worker_questions");
|
||||
if (session.waitingReason?.kind === "worker_questions") {
|
||||
expect(session.waitingReason.questions[0]?.id).toBe("module-opening");
|
||||
expect(session.waitingReason.questions[0]?.prompt).toContain(
|
||||
"玩家实际会待着玩的地方",
|
||||
);
|
||||
}
|
||||
expect(llmCalls.calls()).toBe(0);
|
||||
});
|
||||
|
||||
it("picking a root node still shows opening even if 用户.需求 already has text", async () => {
|
||||
const llm = new MockLlmProvider([
|
||||
JSON.stringify({
|
||||
outputs: { "设计.舞台骨架": { 范围: "不该到这一步" } },
|
||||
summary: "不应生成",
|
||||
askUser: null,
|
||||
}),
|
||||
]);
|
||||
const llmCalls = countLlmCalls(llm);
|
||||
const runtime = new PhaseRuntime({ llm });
|
||||
await runtime.start();
|
||||
runtime.getBlackboard().write({
|
||||
tag: "设计.创作流程",
|
||||
content: JSON.stringify({
|
||||
version: 1,
|
||||
status: "open",
|
||||
steps: [
|
||||
{
|
||||
id: "舞台骨架",
|
||||
name: "舞台骨架",
|
||||
depends_on: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
runtime.getBlackboard().write({
|
||||
tag: "用户.需求",
|
||||
content: "丧尸世界只有我不会被感染",
|
||||
source: "user",
|
||||
});
|
||||
runtime.getSession().slots["用户.需求"] = "丧尸世界只有我不会被感染";
|
||||
await runtime.enterSeededCreationPick();
|
||||
await runtime.pickCreationStep("舞台骨架");
|
||||
expect(runtime.getSession().waitingReason?.kind).toBe("worker_questions");
|
||||
expect(llmCalls.calls()).toBe(0);
|
||||
});
|
||||
|
||||
it("leaving during opening and picking again shows opening instead of calling LLM", async () => {
|
||||
const llm = new MockLlmProvider([
|
||||
JSON.stringify({
|
||||
outputs: { "设计.舞台骨架": { 范围: "不该到这一步" } },
|
||||
summary: "不应生成",
|
||||
askUser: null,
|
||||
}),
|
||||
]);
|
||||
const llmCalls = countLlmCalls(llm);
|
||||
const runtime = new PhaseRuntime({ llm });
|
||||
await runtime.start();
|
||||
runtime.getBlackboard().write({
|
||||
tag: "设计.创作流程",
|
||||
content: JSON.stringify({
|
||||
version: 1,
|
||||
status: "open",
|
||||
steps: [
|
||||
{
|
||||
id: "舞台骨架",
|
||||
name: "舞台骨架",
|
||||
depends_on: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
await runtime.enterSeededCreationPick();
|
||||
await runtime.pickCreationStep("舞台骨架");
|
||||
expect(runtime.getSession().waitingReason?.kind).toBe("worker_questions");
|
||||
await runtime.leaveCreationStep();
|
||||
expect(runtime.getSession().waitingReason?.kind).toBe("pick_creation_step");
|
||||
await runtime.pickCreationStep("舞台骨架");
|
||||
expect(runtime.getSession().waitingReason?.kind).toBe("worker_questions");
|
||||
expect(llmCalls.calls()).toBe(0);
|
||||
});
|
||||
|
||||
it("unusable main agent output without DAG starts design-flow", async () => {
|
||||
const llm = new MockLlmProvider(["Let me think about routing"]);
|
||||
const runtime = new PhaseRuntime({ llm, autoStubWorker: true });
|
||||
@@ -294,6 +432,335 @@ describe("phase runtime", () => {
|
||||
).toEqual(["美学纲领与交互范式"]);
|
||||
});
|
||||
|
||||
it("leaveCreationStep from review_artifact returns to pick without unsawning a filled instance", async () => {
|
||||
const artifact = createArtifact({
|
||||
workerId: "design-step",
|
||||
stepId: "生成规则#1",
|
||||
outputTags: ["设计.生成规则"],
|
||||
});
|
||||
const session = createSession("default");
|
||||
session.phase = "waiting_user";
|
||||
session.waitingReason = { kind: "review_artifact", artifactId: artifact.id };
|
||||
session.pendingArtifactId = artifact.id;
|
||||
session.currentWorkerId = "design-step";
|
||||
session.currentStepId = "生成规则#1";
|
||||
session.artifacts = [artifact];
|
||||
session.slots = { startupCompleted: true };
|
||||
const blackboard = new Blackboard();
|
||||
blackboard.write({
|
||||
tag: "设计.创作流程",
|
||||
content: JSON.stringify({
|
||||
version: 1,
|
||||
status: "open",
|
||||
steps: [
|
||||
{
|
||||
id: "美学纲领与交互范式",
|
||||
name: "美学纲领与交互范式",
|
||||
depends_on: [],
|
||||
},
|
||||
{
|
||||
id: "生成规则",
|
||||
name: "生成规则",
|
||||
role: "prototype",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
},
|
||||
{
|
||||
id: "生成规则#1",
|
||||
name: "生成规则",
|
||||
role: "instance",
|
||||
from: "生成规则",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
},
|
||||
],
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
blackboard.write({
|
||||
tag: "创作.当前步骤",
|
||||
content: "生成规则#1",
|
||||
source: "runtime",
|
||||
});
|
||||
const runtime = new PhaseRuntime({
|
||||
initialSession: session,
|
||||
initialBlackboardItems: blackboard.exportItems(),
|
||||
});
|
||||
await runtime.leaveCreationStep();
|
||||
expect(runtime.getSession().waitingReason?.kind).toBe("pick_creation_step");
|
||||
expect(runtime.getSession().pendingArtifactId).toBeUndefined();
|
||||
const flow = parseCreationFlow(
|
||||
runtime.getBlackboard().getContentByTag("设计.创作流程"),
|
||||
);
|
||||
expect(flow?.steps.map((s) => s.id)).toContain("生成规则#1");
|
||||
});
|
||||
|
||||
it("abortCurrentRun restores the snapshot and waits instead of rerunning", async () => {
|
||||
const runtime = new PhaseRuntime({ autoStubWorker: true });
|
||||
await runtime.start();
|
||||
await runtime.submitInput("网恋对象对话");
|
||||
await runtime.submitDecision(
|
||||
createDecision({
|
||||
action: "run_worker",
|
||||
reason: "编排创作流程",
|
||||
workerId: "design-flow",
|
||||
requiresApproval: false,
|
||||
}),
|
||||
);
|
||||
const snap = runtime.getLastWorkerRunSnapshot();
|
||||
expect(snap?.workerId).toBe("design-flow");
|
||||
const artifactsAtSnapshot = snap!.runtimeSession.artifacts.length;
|
||||
|
||||
const session = runtime.getSession();
|
||||
session.phase = "running";
|
||||
session.waitingReason = undefined;
|
||||
session.currentWorkerId = "design-flow";
|
||||
|
||||
runtime.abortCurrentRun();
|
||||
expect(runtime.getSession().phase).toBe("waiting_user");
|
||||
expect(runtime.getSession().currentWorkerId).toBeUndefined();
|
||||
expect(runtime.getSession().artifacts.length).toBe(artifactsAtSnapshot);
|
||||
});
|
||||
|
||||
it("reentering an accepted instance reopens its artifact for revision", async () => {
|
||||
const artifact = {
|
||||
...createArtifact({
|
||||
workerId: "design-step",
|
||||
stepId: "生成规则#1",
|
||||
outputTags: ["设计.生成规则"],
|
||||
}),
|
||||
status: "accepted",
|
||||
};
|
||||
const session = createSession("default");
|
||||
session.phase = "waiting_user";
|
||||
session.waitingReason = { kind: "pick_creation_step" };
|
||||
session.slots = {
|
||||
startupCompleted: true,
|
||||
creationAcceptedUnits: ["生成规则#1"],
|
||||
};
|
||||
session.artifacts = [artifact];
|
||||
const blackboard = new Blackboard();
|
||||
blackboard.write({
|
||||
tag: "设计.创作流程",
|
||||
content: JSON.stringify({
|
||||
version: 1,
|
||||
status: "open",
|
||||
steps: [
|
||||
{
|
||||
id: "美学纲领与交互范式",
|
||||
name: "美学纲领与交互范式",
|
||||
depends_on: [],
|
||||
},
|
||||
{
|
||||
id: "生成规则",
|
||||
name: "生成规则",
|
||||
role: "prototype",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
},
|
||||
{
|
||||
id: "生成规则#1",
|
||||
name: "生成规则",
|
||||
role: "instance",
|
||||
from: "生成规则",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
params: { target: "4位核心女性" },
|
||||
},
|
||||
],
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
blackboard.write({
|
||||
tag: "创作.已验收单位",
|
||||
content: JSON.stringify(["生成规则#1"]),
|
||||
source: "runtime",
|
||||
});
|
||||
blackboard.write({
|
||||
tag: "设计.生成规则",
|
||||
content: "已有规则正文",
|
||||
source: "runtime",
|
||||
});
|
||||
const runtime = new PhaseRuntime({
|
||||
initialSession: session,
|
||||
initialBlackboardItems: blackboard.exportItems(),
|
||||
});
|
||||
await runtime.pickCreationStep("生成规则#1", undefined, { reenter: true });
|
||||
const next = runtime.getSession();
|
||||
expect(next.waitingReason?.kind).toBe("review_artifact");
|
||||
if (next.waitingReason?.kind === "review_artifact") {
|
||||
expect(next.waitingReason.artifactId).toBe(artifact.id);
|
||||
}
|
||||
expect(next.pendingArtifactId).toBe(artifact.id);
|
||||
expect(next.currentStepId).toBe("生成规则#1");
|
||||
expect(next.phase).toBe("waiting_user");
|
||||
expect(runtime.getBlackboard().getContentByTag("创作.继承修改")).toBe("1");
|
||||
expect(next.slots.revisionTargetArtifactId).toBe(artifact.id);
|
||||
expect(
|
||||
next.artifacts.find((a) => a.id === artifact.id)?.status,
|
||||
).toBe("under_review");
|
||||
});
|
||||
|
||||
it("revising a reentered accepted instance does not bounce to the node graph", async () => {
|
||||
const artifact = {
|
||||
...createArtifact({
|
||||
workerId: "design-step",
|
||||
stepId: "生成规则#1",
|
||||
outputTags: ["设计.生成规则"],
|
||||
}),
|
||||
status: "accepted",
|
||||
};
|
||||
const session = createSession("default");
|
||||
session.phase = "waiting_user";
|
||||
session.waitingReason = { kind: "pick_creation_step" };
|
||||
session.slots = {
|
||||
startupCompleted: true,
|
||||
creationAcceptedUnits: ["生成规则#1"],
|
||||
};
|
||||
session.artifacts = [artifact];
|
||||
const blackboard = new Blackboard();
|
||||
blackboard.write({
|
||||
tag: "设计.创作流程",
|
||||
content: JSON.stringify({
|
||||
version: 1,
|
||||
status: "open",
|
||||
steps: [
|
||||
{
|
||||
id: "美学纲领与交互范式",
|
||||
name: "美学纲领与交互范式",
|
||||
depends_on: [],
|
||||
},
|
||||
{
|
||||
id: "生成规则",
|
||||
name: "生成规则",
|
||||
role: "prototype",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
},
|
||||
{
|
||||
id: "生成规则#1",
|
||||
name: "生成规则",
|
||||
role: "instance",
|
||||
from: "生成规则",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
params: { target: "4位核心女性" },
|
||||
},
|
||||
],
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
blackboard.write({
|
||||
tag: "创作.已验收单位",
|
||||
content: JSON.stringify(["生成规则#1"]),
|
||||
source: "runtime",
|
||||
});
|
||||
blackboard.write({
|
||||
tag: "设计.生成规则",
|
||||
content: "已有规则正文",
|
||||
source: "runtime",
|
||||
});
|
||||
const runtime = new PhaseRuntime({
|
||||
initialSession: session,
|
||||
initialBlackboardItems: blackboard.exportItems(),
|
||||
});
|
||||
await runtime.pickCreationStep("生成规则#1", undefined, { reenter: true });
|
||||
await runtime.rejectArtifact("把对象改成四位男性");
|
||||
const next = runtime.getSession();
|
||||
expect(next.waitingReason?.kind).not.toBe("pick_creation_step");
|
||||
expect(next.phase).toBe("running");
|
||||
expect(next.currentWorkerId).toBe("design-step");
|
||||
expect(next.slots.revisionTargetArtifactId).toBe(artifact.id);
|
||||
});
|
||||
|
||||
it("deleteCreationStep removes an accepted repeatable instance", async () => {
|
||||
const artifact = {
|
||||
...createArtifact({
|
||||
workerId: "design-step",
|
||||
stepId: "生成规则#1",
|
||||
outputTags: ["设计.生成规则"],
|
||||
}),
|
||||
status: "accepted",
|
||||
};
|
||||
const session = createSession("default");
|
||||
session.phase = "waiting_user";
|
||||
session.waitingReason = { kind: "pick_creation_step" };
|
||||
session.slots = {
|
||||
startupCompleted: true,
|
||||
creationAcceptedUnits: ["美学纲领与交互范式", "生成规则#1"],
|
||||
};
|
||||
session.artifacts = [artifact];
|
||||
const blackboard = new Blackboard();
|
||||
blackboard.write({
|
||||
tag: "设计.创作流程",
|
||||
content: JSON.stringify({
|
||||
version: 1,
|
||||
status: "open",
|
||||
steps: [
|
||||
{
|
||||
id: "美学纲领与交互范式",
|
||||
name: "美学纲领与交互范式",
|
||||
depends_on: [],
|
||||
},
|
||||
{
|
||||
id: "生成规则",
|
||||
name: "生成规则",
|
||||
role: "prototype",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
},
|
||||
{
|
||||
id: "生成规则#1",
|
||||
name: "生成规则",
|
||||
role: "instance",
|
||||
from: "生成规则",
|
||||
depends_on: ["美学纲领与交互范式"],
|
||||
params: { target: "4位核心女性", rule_id: "core-female-roles" },
|
||||
},
|
||||
{
|
||||
id: "开场白与开场变量",
|
||||
name: "开场白与开场变量",
|
||||
depends_on: ["美学纲领与交互范式", "生成规则#1"],
|
||||
},
|
||||
],
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
blackboard.write({
|
||||
tag: "创作.已验收单位",
|
||||
content: JSON.stringify(["美学纲领与交互范式", "生成规则#1"]),
|
||||
source: "runtime",
|
||||
});
|
||||
blackboard.write({
|
||||
tag: "设计.生成规则",
|
||||
content: JSON.stringify({
|
||||
schema: "context-fragment.v1",
|
||||
技能: "生成规则",
|
||||
brief: "女臣",
|
||||
正文: { rules: [{ rule_id: "core-female-roles", 对象: "女臣" }] },
|
||||
}),
|
||||
source: "runtime",
|
||||
});
|
||||
const runtime = new PhaseRuntime({
|
||||
initialSession: session,
|
||||
initialBlackboardItems: blackboard.exportItems(),
|
||||
});
|
||||
await runtime.deleteCreationStep("生成规则#1");
|
||||
const flow = parseCreationFlow(
|
||||
runtime.getBlackboard().getContentByTag("设计.创作流程"),
|
||||
);
|
||||
expect(flow?.steps.map((s) => s.id)).toEqual([
|
||||
"美学纲领与交互范式",
|
||||
"生成规则",
|
||||
"开场白与开场变量",
|
||||
]);
|
||||
expect(
|
||||
flow?.steps.find((s) => s.name === "开场白与开场变量")?.depends_on,
|
||||
).toEqual(["美学纲领与交互范式"]);
|
||||
expect(runtime.getSession().slots.creationAcceptedUnits).toEqual([
|
||||
"美学纲领与交互范式",
|
||||
]);
|
||||
expect(
|
||||
runtime.getSession().artifacts.find((a) => a.id === artifact.id)?.status,
|
||||
).toBe("rejected");
|
||||
await expect(
|
||||
runtime.deleteCreationStep("美学纲领与交互范式"),
|
||||
).rejects.toThrow(/可增殖/);
|
||||
});
|
||||
|
||||
it("prepareEnterPlay seals when closer is already accepted", () => {
|
||||
const session = createSession("default");
|
||||
const blackboard = new Blackboard();
|
||||
|
||||
Reference in New Issue
Block a user