163 lines
5.1 KiB
TypeScript
163 lines
5.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { createDecision } from "../src/runtime/orchestrator.js";
|
|
import {
|
|
applyEvent,
|
|
createArtifact,
|
|
createSession,
|
|
} from "../src/runtime/phase-machine.js";
|
|
import { loadSkill } from "../src/skills/loader.js";
|
|
import { toActiveSkillSnapshot } from "../src/skills/snapshot.js";
|
|
|
|
const mockSkills = [
|
|
{ name: "world-simulator", description: "默认", category: "dialogue" },
|
|
];
|
|
|
|
describe("phase machine", () => {
|
|
it("runs design-core approve loop on agent-first pack", async () => {
|
|
const snap = toActiveSkillSnapshot(await loadSkill("world-simulator"));
|
|
|
|
let session = applyEvent(createSession("default"), {
|
|
type: "session_started",
|
|
payload: {
|
|
presetId: "default",
|
|
availableSkills: mockSkills,
|
|
initialSkill: snap,
|
|
},
|
|
}).session;
|
|
|
|
session = applyEvent(session, {
|
|
type: "user_submitted_input",
|
|
payload: { text: "1v1 网恋对话" },
|
|
}).session;
|
|
expect(session.phase).toBe("running");
|
|
|
|
const decision = createDecision({
|
|
action: "run_worker",
|
|
reason: "产出 Worker 集",
|
|
workerId: "design-core",
|
|
requiresApproval: true,
|
|
});
|
|
session = applyEvent(session, {
|
|
type: "main_agent_decision_created",
|
|
payload: { decision },
|
|
}).session;
|
|
expect(session.waitingReason?.kind).toBe("approve_step");
|
|
|
|
session = applyEvent(session, {
|
|
type: "user_approved_next_step",
|
|
payload: { decisionId: decision.id },
|
|
}).session;
|
|
session = applyEvent(session, {
|
|
type: "worker_started",
|
|
payload: { workerId: "design-core", acceptanceMode: "user_confirmed" },
|
|
}).session;
|
|
|
|
const artifact = createArtifact({
|
|
workerId: "design-core",
|
|
outputTags: ["设计.worker集"],
|
|
});
|
|
session = { ...session, artifacts: [artifact] };
|
|
session = applyEvent(session, {
|
|
type: "worker_completed",
|
|
payload: { artifactId: artifact.id },
|
|
}).session;
|
|
expect(session.waitingReason?.kind).toBe("review_artifact");
|
|
|
|
session = applyEvent(session, {
|
|
type: "user_accepted_artifact",
|
|
payload: { artifactId: artifact.id },
|
|
}).session;
|
|
expect(session.slots.designInstanceReady).toBe(true);
|
|
expect(session.phase).toBe("running");
|
|
});
|
|
|
|
it("hangs optional questions under review_artifact without blocking accept", () => {
|
|
let session = createSession("default");
|
|
session = {
|
|
...session,
|
|
phase: "running" as const,
|
|
acceptanceMode: "user_confirmed" as const,
|
|
};
|
|
const artifact = createArtifact({
|
|
workerId: "design-core",
|
|
outputTags: ["设计.worker集.草稿"],
|
|
});
|
|
session = { ...session, artifacts: [artifact] };
|
|
session = applyEvent(session, {
|
|
type: "worker_completed",
|
|
payload: {
|
|
artifactId: artifact.id,
|
|
questions: [
|
|
{
|
|
id: "q1",
|
|
prompt: "更偏哪种节奏?",
|
|
options: [{ id: "A", label: "慢热拉扯" }],
|
|
},
|
|
],
|
|
},
|
|
}).session;
|
|
|
|
expect(session.waitingReason?.kind).toBe("review_artifact");
|
|
if (session.waitingReason?.kind !== "review_artifact") return;
|
|
expect(session.waitingReason.questions?.length).toBe(1);
|
|
expect(session.waitingReason.questions?.[0]?.required).toBe(false);
|
|
|
|
// 不答追问,直接接受产物
|
|
session = applyEvent(session, {
|
|
type: "user_accepted_artifact",
|
|
payload: { artifactId: artifact.id },
|
|
}).session;
|
|
expect(session.phase).toBe("running");
|
|
});
|
|
|
|
it("sidecar skip clears questions but stays in review", () => {
|
|
let session = createSession("default");
|
|
session = {
|
|
...session,
|
|
phase: "running" as const,
|
|
acceptanceMode: "user_confirmed" as const,
|
|
};
|
|
const artifact = createArtifact({
|
|
workerId: "design-core",
|
|
outputTags: ["设计.worker集.草稿"],
|
|
});
|
|
session = { ...session, artifacts: [artifact] };
|
|
session = applyEvent(session, {
|
|
type: "worker_completed",
|
|
payload: {
|
|
artifactId: artifact.id,
|
|
questions: ["还想补一点感官细节吗?"],
|
|
},
|
|
}).session;
|
|
|
|
session = applyEvent(session, {
|
|
type: "user_resolved_sidecar_questions",
|
|
payload: {},
|
|
}).session;
|
|
expect(session.waitingReason?.kind).toBe("review_artifact");
|
|
if (session.waitingReason?.kind !== "review_artifact") return;
|
|
expect(session.waitingReason.questions).toBeUndefined();
|
|
expect(session.pendingArtifactId).toBe(artifact.id);
|
|
});
|
|
|
|
it("accepting design-core with only draft does not set designInstanceReady", () => {
|
|
let session = createSession();
|
|
const artifact = createArtifact({
|
|
workerId: "design-core",
|
|
outputTags: ["设计.worker集.草稿", "创作.当前单位"],
|
|
});
|
|
session = {
|
|
...session,
|
|
artifacts: [artifact],
|
|
pendingArtifactId: artifact.id,
|
|
phase: "waiting_user",
|
|
waitingReason: { kind: "review_artifact", artifactId: artifact.id },
|
|
};
|
|
session = applyEvent(session, {
|
|
type: "user_accepted_artifact",
|
|
payload: { artifactId: artifact.id },
|
|
}).session;
|
|
expect(session.slots.designInstanceReady).toBeUndefined();
|
|
});
|
|
});
|