Keep creation as a living DAG and archive playable products separately.

Opening accept no longer locks the graph; session.json stays creation-only while play starts from an instance snapshot. Sidebar products nest run saves, and play reading fills the feed at a denser type size.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-01 00:52:05 +08:00
co-authored by Cursor
parent bd5d88abfa
commit f37388761b
22 changed files with 930 additions and 303 deletions
+49 -21
View File
@@ -85,6 +85,7 @@ import {
isInstanceStep,
findStepByRef,
findModuleByName,
hasAcceptedCloserStep,
parseCreationFlow,
DESIGN_FLOW_WORKER_ID,
DESIGN_STEP_WORKER_ID,
@@ -114,9 +115,8 @@ import {
SLOT_ASKED_QUESTIONS,
} from "../skills/question-protocol.js";
import { parseWorkerSetYaml } from "../skills/worker-set-parse.js";
import { defaultPlaySlots } from "../skills/play-slots.js";
import {
closeCreationFlowRaw,
CREATION_SEALED_WAITING_MESSAGE,
mergeOpeningTablePatch,
OPENING_CURRENT_VARS_TAG,
OPENING_INITIAL_VARS_TAG,
@@ -427,6 +427,23 @@ export class PhaseRuntime {
};
}
/** 收口已验收但未落印时补封印,并保证有可上场的运行规格 */
prepareEnterPlay(): void {
if (
this.hasAcceptedCloser() &&
!this.session.slots[SLOT_CREATION_SEALED_BY_OPENING]
) {
this.sealCreationOpening();
return;
}
if (
this.session.slots[SLOT_CREATION_SEALED_BY_OPENING] ||
this.session.slots.designInstanceReady
) {
this.ensurePlaySpecReady();
}
}
getAvailableSkills(): SkillIndexEntry[] {
return this.availableSkills;
}
@@ -1402,10 +1419,6 @@ export class PhaseRuntime {
* 验收后回到分层图:点节点即确认并进入,底栏写意见再发即改编排。
*/
private async awaitCreationStepPick(): Promise<void> {
if (this.session.slots[SLOT_CREATION_SEALED_BY_OPENING]) {
this.onMessage(CREATION_SEALED_WAITING_MESSAGE);
return;
}
this.syncSlotsToBlackboard(this.session);
const flow = parseCreationFlow(
this.blackboard.getContentByTag(CREATION_FLOW_TAG),
@@ -1474,7 +1487,7 @@ export class PhaseRuntime {
}
/**
* 选定开场白后:把候选落成 输出.开场白 / 初值表,关闭 DAG。
* 选定开场白后:把候选落成 输出.开场白 / 初值表。不关 DAG,创作流程继续。
*/
private sealCreationOpening(): void {
const selectedRaw = this.session.slots[SLOT_OPENING_SELECTED_INDEX];
@@ -1515,17 +1528,6 @@ export class PhaseRuntime {
}
}
const closed = closeCreationFlowRaw(
this.blackboard.getContentByTag(CREATION_FLOW_TAG),
);
if (closed) {
this.blackboard.write({
tag: CREATION_FLOW_TAG,
content: closed,
source: "runtime",
});
}
const n = payload?.candidates.length ?? (openingText ? 1 : 0);
const which =
n > 1 && payload
@@ -1534,7 +1536,7 @@ export class PhaseRuntime {
? "开场白"
: "开场";
this.onMessage(
`[创作收口] 已选定${which},工作流计划已关闭。可切换到「游玩」。`,
`[创作] 已选定${which}。可保存为产物后开玩,或继续补节点。`,
);
this.session = {
@@ -1542,10 +1544,37 @@ export class PhaseRuntime {
slots: {
...this.session.slots,
[SLOT_CREATION_SEALED_BY_OPENING]: true,
[CREATION_FLOW_TAG]: closed ?? this.session.slots[CREATION_FLOW_TAG],
[OPENING_OUTPUT_TAG]: openingText || this.session.slots[OPENING_OUTPUT_TAG],
},
};
this.ensurePlaySpecReady();
}
private hasAcceptedCloser(): boolean {
const flow = parseCreationFlow(
this.blackboard.getContentByTag(CREATION_FLOW_TAG),
);
return hasAcceptedCloserStep(flow, this.readAcceptedStepIds());
}
/** 收口后若还没有运行规格,用草稿或默认槽位补一份,才能进游玩 */
private ensurePlaySpecReady(): void {
const finalTag = this.blackboard.getContentByTag(WORKER_SET_FINAL_TAG)?.trim();
const draft = this.blackboard.getContentByTag(WORKER_SET_DRAFT_TAG)?.trim();
if (!finalTag) {
const spec = draft?.trim()
? draft
: JSON.stringify({ play_slots: defaultPlaySlots() });
this.writeWorkerTagContent(WORKER_SET_FINAL_TAG, spec, "opening-setup");
}
if (this.session.slots.designInstanceReady) return;
this.session = {
...this.session,
slots: {
...this.session.slots,
designInstanceReady: true,
},
};
}
/** 再编排:解开开场收口,让 design-flow 能在收口前插入节点 */
@@ -2667,7 +2696,6 @@ export class PhaseRuntime {
/** invoke_main_agent 副作用:running 时调用总管 LLM,链式推进直到需用户介入 */
private async maybeRunMainAgent(effects: PhaseEffect[]): Promise<void> {
if (inferLifecycleStage(this.session) === "play") return;
if (this.session.slots[SLOT_CREATION_SEALED_BY_OPENING]) return;
if (this.mainAgent && effects.some((e) => e.type === "invoke_main_agent")) {
await this.runMainAgent();
}