/** * 阶段机运行层(有副作用的执行器)。 * * 职责: * - 持有可变 session,统一 dispatch(event) 入口 * - 调用纯函数 applyEvent,再 processEffects 执行 IO(消息、worker、验收) * - 启动时从 skills/ 加载 registry,驱动 skill 选择与 SKILL.md 启动询问 * * 与 phase-machine.ts 的分工:前者「算状态」,本文件「跑状态」。 */ import { randomUUID } from "node:crypto"; import { applyEvent, createArtifact, createSession, getAllowedEvents, } from "./phase-machine.js"; import { Blackboard } from "../blackboard/blackboard.js"; import type { LlmProvider } from "../llm/client.js"; import { DEFAULT_WORKERS, isUnusableMainAgentOutput, MainAgent, } from "../main-agent/main-agent.js"; import { listSkills, listWorkerSkills, loadSkill, resolveSkillId } from "../skills/loader.js"; import { buildInstanceWorkerDeclaration, formatUndeclaredWorkerError, inferLifecycleStage, mergeDeclaredWorkersForAgent, readWorkerSetYamlForDeclaration, shouldEnforceWorkerDeclaration, } from "../skills/worker-declaration.js"; import { resolveDefaultOrchestratorId } from "../config/default-orchestrator.js"; import { toActiveSkillSnapshot } from "../skills/snapshot.js"; import { runWorkerSkill } from "../worker/executor.js"; import { resolveWorkerId } from "../worker/resolve-id.js"; import { resolveWorkerLlmProvider } from "../skills/worker-llm.js"; import { DIALOGUE_HISTORY_TAG, appendDialogueHistoryTurn, } from "../skills/dialogue-history.js"; import { extractIntakeFromMessage } from "../intake/extract.js"; import { readIntakeValues } from "../intake/intake.js"; import { compressAfterWorkerAccept } from "./compress-after-worker.js"; import { CREATION_ACCEPTED_UNITS_TAG, CREATION_CURRENT_UNIT_TAG, CREATION_ACCEPTED_CONTENT_TAG, WORKER_SET_DRAFT_TAG, WORKER_SET_FINAL_TAG, SLOT_CREATION_ACCEPTED_UNITS, SLOT_CREATION_CURRENT_UNIT, SLOT_CREATION_UNIT_ANCHOR_AT, extractUnitContentFromDraft, isDesignDiskWorker, isDesignUnitArtifact, normalizeCreationUnitId, parseAcceptedContentStore, parseAcceptedUnits, dropAcceptedUnitContent, upsertAcceptedUnitContent, } from "../skills/creation-units.js"; import { CREATION_FLOW_TAG, CREATION_CURRENT_STEP_TAG, CREATION_PROPOSED_STEP_TAG, CREATION_INHERIT_EXISTING_TAG, CREATION_MODULE_OPENING_TAG, CREATION_MODULE_OPENING_STATE_TAG, SLOT_CREATION_MODULE_OPENING_STATE, loadModuleCatalog, isReviseStep, isStepAccepted, isInheritExistingFlag, mergeCreationFlowPreservingAccepted, nextPendingStep, listReadySteps, listSelectablePrototypes, listRepeatableSpawns, isStepReady, hasSelectableCreationWork, spawnRepeatableCreationStep, spawnInstanceFromPrototype, removeUnstartedInstance, removeRepeatableInstance, pruneRepeatableArtifactContent, isPrototypeStep, isPrototypeSelectable, isInstanceStep, findStepByRef, findModuleByName, hasAcceptedCloserStep, parseCreationFlow, DESIGN_FLOW_WORKER_ID, DESIGN_STEP_WORKER_ID, parseModuleOpeningState, patchCreationFlowStepParams, pickRecordedStepId, resolveDesignStepBinding, isProgressPointerTag, missingRequiredStepParams, stepUnitId, stringifyCreationFlow, stringifyModuleOpeningState, finalizeCreationFlow, type CreationFlow, type CreationFlowStep, type CreationFlowStepParams, type ModuleCatalog, type ModuleCatalogEntry, type ModuleParamSpec, } from "../skills/creation-flow.js"; import { appendAskedQuestions, dropAlreadyAskedQuestions, MODULE_OPENING_QUESTION_ID, normalizeQuestions, parseAskedQuestions, SLOT_ASKED_QUESTIONS, } from "../skills/question-protocol.js"; import { parseWorkerSetYaml } from "../skills/worker-set-parse.js"; import { defaultPlaySlots, isNarratorEnabled } from "../skills/play-slots.js"; import { mergeOpeningTablePatch, OPENING_CURRENT_VARS_TAG, OPENING_INITIAL_VARS_TAG, OPENING_OUTPUT_TAG, OPENING_SETUP_ARTIFACT_TAG, parseOpeningSealPayload, reopenCreationFlowRaw, SLOT_CREATION_SEALED_BY_OPENING, SLOT_OPENING_SELECTED_INDEX, } from "../skills/opening-seal.js"; import { CREATION_INTAKE_MODE_TAG, isDictateModeValue, } from "../dictate/types.js"; import { applyDictatePlayBind } from "../dictate/play-bind.js"; import { VARIABLE_CATALOG_TAG, seedVariablesFromCatalog, } from "../skills/variable-catalog.js"; import { VALUE_MAP_TAG, reprojectValueMaps, } from "../skills/value-map.js"; import { readPlayTurnQueue, withPlayTurnQueue, isPlayLayerActive, } from "../skills/play-turn.js"; import { PRESENT_TAG, isPlayFinalVisibleWorker, isPlayPresentWorker, parseShellAdaptationFromReplyFormat, presentOutputFromFallback, } from "../skills/present-packet.js"; import { debugLog, labelAction, logStateChange, sessionSnap } from "../log.js"; import { executeChance, executeChanceBatch, resolveChanceBatchRequest, resolveChanceRequest, } from "../skills/chance-tools.js"; import { resolveAcceptanceModeForWorker, resolveRunnableWorker, } from "../skills/declared-worker.js"; import { mergeTableCells, parseTableDoc, stringifyTableDoc, type TableDoc, } from "../blackboard/table-cells.js"; import { SIDE_EFFECT_FIRED_TAG, applySideEffectTagActions, evaluateSideEffects, parseFiredRegistry, parseSideEffectRules, stringifyFiredRegistry, } from "../blackboard/table-side-effects.js"; import { SETTLEMENT_TAG, parseSettlementPacket, settlementChangesToTablePatch, } from "../skills/settlement-packet.js"; import { MAINTAIN_TAG, maintainOpsToTablePatch, parseMaintainPacket, } from "../skills/maintain-packet.js"; import { MAINTAIN_GENERATE_TAG, runMaintainNeedGenerateSampling, } from "../skills/maintain-need-generate.js"; import { mountResidentContextForWorker as writeResidentContextTags, parseResidentContext, } from "../skills/resident-context.js"; import type { AcceptanceMode, ActiveSkillSnapshot, ApplyEventResult, ArtifactRecord, MainAgentDecision, PhaseEffect, RuntimeEvent, RuntimeSession, SkillIndexEntry, } from "../types/runtime.js"; import type { BlackboardItem } from "../types/blackboard.js"; /** 每轮开始前清掉的 transient tag(避免主世界层读到上一轮裁决/旁观包) */ const PLAY_ROUND_TRANSIENT_TAGS = [ SETTLEMENT_TAG, MAINTAIN_TAG, MAINTAIN_GENERATE_TAG, "运行.本轮.机遇", "运行.本轮.变量变更", ] as const; export type PhaseRuntimeOptions = { presetId?: string; flowId?: string; /** worker 完成后的默认验收模式,会写入 worker_started 事件 */ acceptanceMode?: AcceptanceMode; /** true 时 run_worker 自动走占位 worker(演示 / 测试用) */ autoStubWorker?: boolean; /** 传入后 invoke_main_agent 会自动调用总管 LLM */ llm?: LlmProvider; /** 运行时黑板;worker 产出写入此处 */ blackboard?: Blackboard; /** 副作用 emit_message 与用户提示的回调(Web / CLI 接入点) */ onMessage?: (message: string) => void; /** 总管 reasoning 流式增量 */ onAgentThinkingDelta?: (delta: string) => void; /** 一轮总管 LLM 思考完成 */ onAgentThinkingDone?: (text: string) => void; /** worker 开始流式输出 */ onWorkerStreamStart?: (workerId: string) => void; /** worker 思维链流式增量 */ onWorkerThinkingDelta?: (workerId: string, delta: string) => void; /** worker 正文/JSON 流式增量 */ onWorkerOutputDelta?: (workerId: string, delta: string) => void; /** worker 流式结束 */ onWorkerStreamDone?: (workerId: string) => void; /** 从磁盘恢复时使用,跳过 createSession */ initialSession?: RuntimeSession; /** 与 initialSession 一并恢复黑板 */ initialBlackboardItems?: BlackboardItem[]; }; /** * 阶段机运行层:包装纯函数 phase-machine,处理副作用。 * 启动时加载 skills/,先选 skill,再读 SKILL.md 启动询问。 */ export class PhaseRuntime { private session: RuntimeSession; private readonly acceptanceMode: AcceptanceMode; private autoStubWorker: boolean; private readonly onMessage: (message: string) => void; private readonly onAgentThinkingDelta?: (delta: string) => void; private readonly onAgentThinkingDone?: (text: string) => void; private readonly onWorkerStreamStart?: (workerId: string) => void; private readonly onWorkerThinkingDelta?: (workerId: string, delta: string) => void; private readonly onWorkerOutputDelta?: (workerId: string, delta: string) => void; private readonly onWorkerStreamDone?: (workerId: string) => void; private blackboard: Blackboard; private llm?: LlmProvider; private mainAgent?: MainAgent; /** autoStubWorker=false 时,run_worker effect 暂存于此,等 startPendingWorker() */ private pendingWorkerEffect: Extract | null = null; /** 最近一次 worker 启动前的快照,供 UI 重 roll 使用 */ private lastWorkerRunSnapshot: { workerId: string; runtimeSession: RuntimeSession; blackboardItems: BlackboardItem[]; } | null = null; /** start() 时从 registry 加载;legacy skill_selection 会话仍用编号解析 */ private availableSkills: SkillIndexEntry[] = []; constructor(options: PhaseRuntimeOptions = {}) { this.session = options.initialSession ?? createSession(options.presetId ?? "default", options.flowId); this.acceptanceMode = options.acceptanceMode ?? "user_confirmed"; this.autoStubWorker = options.autoStubWorker ?? false; this.onMessage = options.onMessage ?? (() => {}); this.onAgentThinkingDelta = options.onAgentThinkingDelta; this.onAgentThinkingDone = options.onAgentThinkingDone; this.onWorkerStreamStart = options.onWorkerStreamStart; this.onWorkerThinkingDelta = options.onWorkerThinkingDelta; this.onWorkerOutputDelta = options.onWorkerOutputDelta; this.onWorkerStreamDone = options.onWorkerStreamDone; this.blackboard = options.blackboard ?? new Blackboard(); if (options.initialBlackboardItems?.length) { this.blackboard.seed(options.initialBlackboardItems); } if (options.llm) { this.llm = options.llm; this.mainAgent = new MainAgent(options.llm); } } getBlackboard(): Blackboard { return this.blackboard; } hasMainAgent(): boolean { return Boolean(this.mainAgent); } /** 设置变更后热更新 LLM(API profile / preset) */ reloadLlm(llm: LlmProvider, autoStubWorker?: boolean): void { this.llm = llm; this.mainAgent = new MainAgent(llm); if (autoStubWorker !== undefined) { this.autoStubWorker = autoStubWorker; } } getLlm(): LlmProvider | undefined { return this.llm; } getSession(): RuntimeSession { return this.session; } getLastWorkerRunSnapshot(): { workerId: string; runtimeSession: RuntimeSession; blackboardItems: BlackboardItem[]; } | null { return this.lastWorkerRunSnapshot ? { workerId: this.lastWorkerRunSnapshot.workerId, runtimeSession: structuredClone(this.lastWorkerRunSnapshot.runtimeSession), blackboardItems: structuredClone(this.lastWorkerRunSnapshot.blackboardItems), } : null; } /** 从 checkpoint 恢复 runtime + 黑板(编辑 / 切换分支) */ restoreFromCheckpoint( session: RuntimeSession, blackboardItems: BlackboardItem[], ): void { this.session = structuredClone(session); this.blackboard = new Blackboard(); if (blackboardItems.length) { this.blackboard.seed(structuredClone(blackboardItems)); } this.pendingWorkerEffect = null; this.lastWorkerRunSnapshot = null; } /** * 提前停止当前生成:还原开跑快照,回到生成前的等待面(或分层图)。 * 不重跑。 */ abortCurrentRun(): RuntimeSession { const snap = this.lastWorkerRunSnapshot; if (snap) { this.restoreFromCheckpoint(snap.runtimeSession, snap.blackboardItems); } return this.settleAfterAbort(); } /** 停止后落到可操作等待态,避免停在 running 空转。 */ private settleAfterAbort(): RuntimeSession { const s = this.session; if (s.waitingReason) return s; const pendingId = s.pendingArtifactId?.trim(); const revisionTarget = typeof s.slots.revisionTargetArtifactId === "string" ? s.slots.revisionTargetArtifactId.trim() : ""; const reviewId = pendingId && s.artifacts.some((a) => a.id === pendingId) ? pendingId : revisionTarget && s.artifacts.some((a) => a.id === revisionTarget) ? revisionTarget : ""; if (reviewId) { this.session = { ...s, phase: "waiting_user", waitingReason: { kind: "review_artifact", artifactId: reviewId }, pendingArtifactId: reviewId, currentWorkerId: undefined, pendingDecision: undefined, updatedAt: new Date().toISOString(), }; return this.session; } if (this.hasCreationFlow()) { this.clearProposedStep(); this.session = { ...s, phase: "waiting_user", waitingReason: { kind: "pick_creation_step" }, pendingDecision: undefined, currentWorkerId: undefined, updatedAt: new Date().toISOString(), }; return this.session; } if (this.recoverOrphanedRun()) return this.session; if (s.phase === "running") { this.session = { ...s, phase: "waiting_user", waitingReason: { kind: "input", message: "已停止生成,可继续说明。" }, currentWorkerId: undefined, pendingDecision: undefined, updatedAt: new Date().toISOString(), }; } return this.session; } /** * 执行中卡住:有进行中的 worker 则按开跑快照重跑;否则重跑总管。 */ async retryStuckRun(): Promise { const workerId = this.session.currentWorkerId; const snap = this.lastWorkerRunSnapshot; if (workerId && snap && snap.workerId === workerId) { this.restoreFromCheckpoint( snap.runtimeSession, snap.blackboardItems, ); return this.rerunWorker(workerId); } if (this.session.phase === "running" && !this.session.waitingReason) { await this.runMainAgent(); return this.session; } throw new Error("当前没有可重试的执行"); } /** LLM/解析失败后离开 running,避免界面一直转圈。 */ async failRun(reason: string): Promise { if (this.session.phase !== "running") return this.session; const result = this.commitEvent({ type: "runtime_failed", payload: { reason }, }); await this.processEffects(result.effects); return this.session; } /** * 进程里留下的「执行中、没 worker」:配方已预置 DAG 时改回点选等待。 * 读档 / 轮询时调用,避免界面一直转圈。 */ recoverOrphanedRun(): boolean { if (this.session.phase !== "running" || this.session.waitingReason) { return false; } if (this.session.currentWorkerId) return false; if (!this.hasCreationFlow()) return false; debugLog("step", "恢复中断的调度,等待点选节点"); this.clearProposedStep(); this.session = { ...this.session, phase: "waiting_user", waitingReason: { kind: "pick_creation_step" }, pendingDecision: undefined, currentWorkerId: undefined, updatedAt: new Date().toISOString(), }; return true; } /** * 配方已预置 DAG:跳过「先描述需求」,直接进入点选图。 * 图上节点来自当前剧本 seed,换配方即换开局。 */ async enterSeededCreationPick(): Promise { if (!this.hasCreationFlow()) return; this.session = { ...this.session, slots: { ...this.session.slots, startupCompleted: true, }, }; await this.awaitCreationStepPick(); } /** * 转述整理式进料:跳过配方 DAG / 点选节点,直接等用户说话。 * 总管不跑 design-flow;由 session-manager 的 dictate 对话环处理输入。 */ enterDictateIntake(message?: string): void { this.session = { ...this.session, phase: "waiting_user", waitingReason: { kind: "input", message: message?.trim() || "转述整理:直接说你想要的体验与设定;我会整理进产物。缺什么会问你。", }, pendingDecision: undefined, currentWorkerId: undefined, pendingArtifactId: undefined, resumeContext: undefined, slots: { ...this.session.slots, startupCompleted: true, }, updatedAt: new Date().toISOString(), }; } /** 重 roll 指定 worker(刷新 Skill 回复) */ async rerunWorker(workerId: string): Promise { const effect: Extract = { type: "run_worker", workerId, }; if (this.autoStubWorker) { await this.runStubWorker(effect); } else if (this.llm) { await this.runRealWorker(effect); } else { this.pendingWorkerEffect = effect; this.onMessage(`[阶段机] 重 roll worker: ${workerId}。输入 /worker-start`); } return this.session; } setLifecycleStage(stage: "design" | "play"): void { this.session = { ...this.session, slots: { ...this.session.slots, uiLifecycleStage: stage }, updatedAt: new Date().toISOString(), }; } /** 收口已验收但未落印时补封印,并保证有可上场的运行规格 */ 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(); return; } // 转述:无节点收口,落档/开玩时直接封印并补默认运行规格 if (this.isDictateIntakeMode()) { this.ensurePlaySpecReady(); } } private isDictateIntakeMode(): boolean { return isDictateModeValue( this.blackboard.getContentByTag(CREATION_INTAKE_MODE_TAG), ); } getAvailableSkills(): SkillIndexEntry[] { return this.availableSkills; } /** 恢复会话或首次 start 前加载 skill 列表(供 UI 展示) */ async ensureAvailableSkills(): Promise { if (this.availableSkills.length === 0) { this.availableSkills = await listSkills(); } } getActiveSkill(): ActiveSkillSnapshot | undefined { return this.session.slots.activeSkill as ActiveSkillSnapshot | undefined; } getAllowedEventTypes(): RuntimeEvent["type"][] { return getAllowedEvents(this.session); } /** * 是否处于「running 且无事可做,等总管决策」状态。 * Web 层据此显示「生成大纲」等按钮。 */ needsMainAgentDecision(): boolean { return ( this.session.phase === "running" && !this.session.currentWorkerId && !this.pendingWorkerEffect ); } /** 加载 registry → 自动进入默认 orchestrator → intake(无选包阶段) */ async start(): Promise { this.availableSkills = await listSkills(); if (this.availableSkills.length === 0) { throw new Error("skills/registry 中没有注册任何 orchestrator"); } const defaultId = resolveDefaultOrchestratorId(this.availableSkills); const initialSkill = toActiveSkillSnapshot(await loadSkill(defaultId)); await this.dispatch({ type: "session_started", payload: { presetId: this.session.presetId, flowId: this.session.flowId, availableSkills: this.availableSkills, initialSkill, }, }); return this.session; } /** 启动并加载指定 orchestrator(legacy 包 / 测试用) */ async startWithOrchestrator(orchestratorId: string): Promise { this.availableSkills = await listSkills(); if (this.availableSkills.length === 0) { throw new Error("skills/registry 中没有注册任何 orchestrator"); } const initialSkill = toActiveSkillSnapshot(await loadSkill(orchestratorId)); await this.dispatch({ type: "session_started", payload: { presetId: this.session.presetId, flowId: this.session.flowId, availableSkills: this.availableSkills, initialSkill, }, }); return this.session; } /** 解析并加载 SKILL.md → 触发 skill_selected → waiting_user(input) */ async selectSkill(skillIdOrName: string): Promise { const dir = await resolveSkillId(skillIdOrName); if (!dir) { throw new Error(`未找到 skill: ${skillIdOrName}`); } const parsed = await loadSkill(dir); await this.dispatch({ type: "skill_selected", payload: { skill: toActiveSkillSnapshot(parsed) }, }); return this.session; } /** applyEvent 并打状态日志(dispatch / 总管决策共用) */ private commitEvent(event: RuntimeEvent): ApplyEventResult { const before = sessionSnap(this.session); const result = applyEvent(this.session, event); this.session = result.session; logStateChange(event, before, result); return result; } /** 统一事件入口:applyEvent + 更新 session + 处理 effects + 可选总管 LLM */ async dispatch(event: RuntimeEvent): Promise { const result = this.commitEvent(event); if ( event.type === "user_submitted_input" || event.type === "user_confirmed_intake" ) { this.syncSlotsToBlackboard(this.session); } if (result.error && event.type !== "runtime_failed") { this.onMessage(result.error); } await this.processEffects(result.effects); await this.maybeRunMainAgent(result.effects); return result; } /** 将 slots 中的启动目标等同步为黑板 tag(迁移期 tag 名可与旧 key 相同) */ private syncSlotsToBlackboard(session: RuntimeSession): void { const activeSkill = session.slots.activeSkill as ActiveSkillSnapshot | undefined; const demandTag = activeSkill?.startupTargetKey || "用户.需求"; const demandContent = session.slots[demandTag]; if (typeof demandContent === "string" && demandContent.trim()) { this.blackboard.write({ tag: demandTag, content: demandContent.trim(), source: "user", }); } const latestInput = session.slots["用户.最新输入"]; if (typeof latestInput === "string" && latestInput.trim()) { this.blackboard.write({ tag: "用户.最新输入", content: latestInput.trim(), source: "user", }); // 游玩期把用户话追加进可投影的「对话.历史」标签 if (inferLifecycleStage(session) === "play") { this.appendDialogueHistory("用户", latestInput.trim()); } } const workerReply = session.slots["用户.worker答复"]; if (typeof workerReply === "string" && workerReply.trim()) { this.blackboard.write({ tag: "用户.worker答复", content: workerReply.trim(), source: "user", }); } const revision = (typeof session.slots["用户.修订说明"] === "string" && session.slots["用户.修订说明"]) || (typeof session.slots.revisionInstruction === "string" && session.slots.revisionInstruction); if (typeof revision === "string" && revision.trim()) { this.blackboard.write({ tag: "用户.修订说明", content: revision.trim(), source: "user", }); } const nextIntent = session.slots["用户.下一步意向"]; if (typeof nextIntent === "string" && nextIntent.trim()) { this.blackboard.write({ tag: "用户.下一步意向", content: nextIntent.trim(), source: "user", }); } } /** * 用户文本输入的统一入口。 * legacy skill_selection 阶段会先解析为 selectSkill,否则走 user_submitted_input。 */ async submitInput(text: string): Promise { if (this.session.waitingReason?.kind === "skill_selection") { const picked = await this.resolveSkillFromUserInput(text); if (!picked) { throw new Error( `无法识别 skill: ${text}。旧版会话请用 skill id(如 world-simulator)或列表编号。`, ); } return this.selectSkill(picked); } if (this.session.waitingReason?.kind === "intake") { const skill = this.getActiveSkill(); const fields = skill?.intakeFields ?? []; const current = readIntakeValues(this.session.slots); const intakeValues = await extractIntakeFromMessage( text, fields, current, this.llm, ); await this.dispatch({ type: "user_submitted_input", payload: { text, intakeValues }, }); return this.session; } await this.dispatch({ type: "user_submitted_input", payload: { text } }); return this.session; } /** 必要项已填完:确认进入实例化 / 下一阶段 */ async confirmIntake(): Promise { if (this.session.waitingReason?.kind !== "intake") { throw new Error("当前不在填空收集阶段"); } await this.dispatch({ type: "user_confirmed_intake", payload: {} }); this.syncSlotsToBlackboard(this.session); return this.session; } /** 支持按编号(1-based)或 skill name 解析 */ private async resolveSkillFromUserInput(text: string): Promise { const trimmed = text.trim(); const num = Number(trimmed); if (Number.isInteger(num) && num >= 1 && num <= this.availableSkills.length) { const entry = this.availableSkills[num - 1]; return resolveSkillId(entry.name); } return resolveSkillId(trimmed); } /** 提交总管决策(通常来自 Main Agent / 手动按钮) */ async submitDecision(decision: MainAgentDecision): Promise { await this.dispatch({ type: "main_agent_decision_created", payload: { decision }, }); return this.session; } /** 用户确认 pendingDecision(approve_step);可顺带钉死下一步 params */ async approve(stepParams?: CreationFlowStepParams): Promise { const id = this.session.pendingDecision?.id; if (!id) throw new Error("当前没有待确认的决策"); if (stepParams && Object.keys(stepParams).length > 0) { this.applyProposedStepParams(stepParams); } const readyErr = this.assertProposedStepReady(); if (readyErr) throw new Error(readyErr); await this.dispatch({ type: "user_approved_next_step", payload: { decisionId: id } }); return this.session; } /** 用户拒绝 pendingDecision:回到流程编排,可追加节点 */ async rejectStep(reason?: string): Promise { const id = this.session.pendingDecision?.id; if (!id) throw new Error("当前没有待拒绝的决策"); await this.dispatch({ type: "user_rejected_next_step", payload: { decisionId: id, reason }, }); return this.session; } /** * 离开当前技能步,回到分层图。 * 可增殖节点刚增殖出、尚未验收的空实例从图上撤掉。 */ async leaveCreationStep(): Promise { if (this.session.phase === "running" && !this.session.waitingReason) { this.abortCurrentRun(); } if (this.session.waitingReason?.kind === "pick_creation_step") { return this.session; } const waiting = this.session.waitingReason?.kind; const allowed = waiting === "worker_questions" || waiting === "input" || waiting === "revision" || waiting === "approve_step" || waiting === "review_artifact"; if (!allowed) { throw new Error("当前不能返回节点选择"); } if (inferLifecycleStage(this.session) === "play") { throw new Error("游玩中不能返回创作节点"); } const stepId = this.readPinnedStepId(); this.forgetUnansweredModuleOpening(stepId); const removed = this.unspawnCurrentInstanceIfEmpty(); this.clearProposedStep(); this.clearCurrentStepPointers(); const result = await this.dispatch({ type: "user_left_creation_step", payload: {}, }); if (result.error) { throw new Error(result.error); } const stepName = removed?.name?.trim(); this.onMessage( removed ? `已取消刚增殖的「${stepName || stepId}」,回到节点选择。` : "已回到节点选择。", ); return this.session; } private unspawnCurrentInstanceIfEmpty(): { id: string; name: string } | null { const stepId = this.readPinnedStepId(); if (!stepId) return null; const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); if (!flow) return null; const step = findStepByRef(flow, stepId); if (!step || !isInstanceStep(step)) return null; const accepted = this.readAcceptedStepIds(); if (isStepAccepted(step, accepted)) return null; if ( this.session.artifacts.some( (a) => a.stepId === step.id && a.status !== "rejected", ) ) { return null; } const next = removeUnstartedInstance(flow, step.id, accepted); if (next.steps.length === flow.steps.length) return null; this.writeCreationFlow(next); debugLog("step", `取消未完成实例 ${step.id}`); return { id: step.id, name: step.name }; } /** 离开节点时若默认问题只展示过、还没答:清状态,下次点进再出引导。 */ private forgetUnansweredModuleOpening(stepId: string): void { const id = stepId.trim(); if (!id) return; const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); const step = flow ? findStepByRef(flow, id) : null; const key = step ? stepUnitId(step) : id; const state = parseModuleOpeningState( this.session.slots[SLOT_CREATION_MODULE_OPENING_STATE] ?? this.blackboard.getContentByTag(CREATION_MODULE_OPENING_STATE_TAG), ); if (state[key] !== "shown") return; delete state[key]; const raw = stringifyModuleOpeningState(state); this.blackboard.write({ tag: CREATION_MODULE_OPENING_STATE_TAG, content: raw, source: "runtime", }); this.blackboard.write({ tag: CREATION_MODULE_OPENING_TAG, content: "", source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [SLOT_CREATION_MODULE_OPENING_STATE]: raw, [CREATION_MODULE_OPENING_TAG]: undefined, }, }; } private clearCurrentStepPointers(): void { this.blackboard.write({ tag: CREATION_CURRENT_STEP_TAG, content: "", source: "runtime", }); this.blackboard.write({ tag: CREATION_INHERIT_EXISTING_TAG, content: "", source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_CURRENT_STEP_TAG]: undefined, [CREATION_INHERIT_EXISTING_TAG]: undefined, [SLOT_CREATION_CURRENT_UNIT]: undefined, }, }; } /** 开场白等步骤上发现漏了前序节点:再进流程编排,不整局重开 */ async replanCreationFlow(reason?: string): Promise { const waiting = this.session.waitingReason?.kind; if (waiting === "approve_step") { return this.rejectStep(reason); } await this.dispatch({ type: "user_requested_flow_replan", payload: { reason }, }); return this.session; } /** 图上点选:普通节点开干;原型槽位先增殖再进去;已完成节点可 reenter 重进 */ async pickCreationStep( stepId: string, stepParams?: CreationFlowStepParams, opts?: { reenter?: boolean }, ): Promise { const id = stepId.trim(); this.syncSlotsToBlackboard(this.session); const catalog = await this.loadActiveModuleCatalog(); const flow = this.readNormalizedFlow(catalog); const step = flow ? findStepByRef(flow, id) : null; if (flow && step && isPrototypeStep(step)) { if (opts?.reenter) { throw new Error("原型槽位不能回头修改,请点已完成的实例"); } const spawned = spawnInstanceFromPrototype({ flow, catalog, prototypeId: step.id, acceptedStepIds: this.readAcceptedStepIds(), }); if ("error" in spawned) throw new Error(spawned.error); this.writeCreationFlow(spawned.flow); debugLog("step", `点原型 ${step.id} → ${spawned.step.id}`); await this.startCreationStep(spawned.step.id, { stepParams }); return this.session; } await this.startCreationStep(id, { stepParams, reenter: opts?.reenter === true, }); return this.session; } /** 可反复能力:新编一条再进去 */ async spawnCreationStep(moduleName: string): Promise { this.syncSlotsToBlackboard(this.session); const catalog = await this.loadActiveModuleCatalog(); const flow = this.readNormalizedFlow(catalog); if (!flow) throw new Error("还没有工作流计划,无法追加节点"); const spawned = spawnRepeatableCreationStep({ flow, catalog, moduleName, acceptedStepIds: this.readAcceptedStepIds(), }); if ("error" in spawned) throw new Error(spawned.error); this.writeCreationFlow(spawned.flow); debugLog("step", `新开 ${spawned.step.id}(${spawned.step.name})`); await this.startCreationStep(spawned.step.id); return this.session; } /** 图上删除可增殖产物(已验收也可)。 */ async deleteCreationStep(stepId: string): Promise { const id = stepId.trim(); if (!id) throw new Error("请指定要删除的产物"); this.syncSlotsToBlackboard(this.session); const catalog = await this.loadActiveModuleCatalog(); const flow = this.readNormalizedFlow(catalog); if (!flow) throw new Error("还没有工作流计划"); const result = removeRepeatableInstance({ flow, stepId: id, catalog }); if ("error" in result) throw new Error(result.error); this.writeCreationFlow(result.flow); this.forgetDeletedCreationStep(result.removed, result.flow, catalog); this.onMessage(`已删除「${result.removed.name}」产物 ${result.removed.id}。`); debugLog("step", `删除产物 ${result.removed.id}`); return this.session; } private forgetDeletedCreationStep( step: CreationFlowStep, flow: CreationFlow, catalog: ModuleCatalog | null, ): void { const unitId = step.id; const accepted = this.readAcceptedStepIds().filter((item) => item !== unitId); const acceptedRaw = JSON.stringify(accepted); this.blackboard.write({ tag: CREATION_ACCEPTED_UNITS_TAG, content: acceptedRaw, source: "runtime", }); const contentRaw = dropAcceptedUnitContent( this.blackboard.getContentByTag(CREATION_ACCEPTED_CONTENT_TAG), unitId, ); this.blackboard.write({ tag: CREATION_ACCEPTED_CONTENT_TAG, content: contentRaw, source: "runtime", }); const now = new Date().toISOString(); const artifacts = this.session.artifacts.map((a) => a.stepId === unitId && a.status !== "rejected" ? { ...a, status: "rejected" as const, updatedAt: now } : a, ); const remainingSameModule = accepted.filter((item) => { const other = findStepByRef(flow, item); return other?.name === step.name; }).length; const tag = findModuleByName(catalog, step.name)?.artifact?.trim(); if (tag) { const pruned = pruneRepeatableArtifactContent({ raw: this.blackboard.getContentByTag(tag) ?? "", step, remainingSameModule, }); this.blackboard.write({ tag, content: pruned, source: "runtime", }); } const pinned = this.readPinnedStepId(); this.session = { ...this.session, artifacts, currentStepId: pinned === unitId ? undefined : this.session.currentStepId, slots: { ...this.session.slots, [SLOT_CREATION_ACCEPTED_UNITS]: accepted, [CREATION_ACCEPTED_UNITS_TAG]: acceptedRaw, [CREATION_ACCEPTED_CONTENT_TAG]: contentRaw, ...(tag ? { [tag]: this.blackboard.getContentByTag(tag) ?? "" } : {}), }, updatedAt: now, }; if (pinned === unitId) this.clearCurrentStepPointers(); } private writeCreationFlow(flow: CreationFlow): void { const content = stringifyCreationFlow(flow); this.blackboard.write({ tag: CREATION_FLOW_TAG, content, source: "user", }); this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_FLOW_TAG]: content }, }; } private readNormalizedFlow(catalog: ModuleCatalog | null): CreationFlow | null { const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); if (!flow) return null; const next = finalizeCreationFlow(flow, catalog); const changed = JSON.stringify(next.steps) !== JSON.stringify(flow.steps); if (changed) this.writeCreationFlow(next); return next; } /** 将实例 Worker 声明写入 slots,便于 session 持久化与调试 */ private persistInstanceWorkerDeclaration(): void { const active = this.getActiveSkill(); if (!shouldEnforceWorkerDeclaration(active?.name)) return; const declaration = this.getInstanceWorkerDeclaration(); this.session = { ...this.session, slots: { ...this.session.slots, instanceWorkerDeclaration: { sourceTag: declaration.sourceTag, accepted: declaration.accepted, playWorkerIds: declaration.playWorkerIds, onDemandWorkerIds: declaration.onDemandWorkerIds, designEndWorkerIds: declaration.designEndWorkerIds, activeWorkerIds: declaration.activeWorkerIds, }, }, }; } /** 用户接受 pendingArtifact;创作单位验收 vs 终稿验收分流 */ async acceptArtifact( artifactId?: string, opts?: { openingIndex?: number }, ): Promise { const id = artifactId ?? this.session.pendingArtifactId; if (!id) throw new Error("当前没有待验收的产物"); const artifact = this.session.artifacts.find((a) => a.id === id); if (artifact && opts?.openingIndex != null && Number.isFinite(opts.openingIndex)) { this.session = { ...this.session, slots: { ...this.session.slots, [SLOT_OPENING_SELECTED_INDEX]: Math.trunc(opts.openingIndex), }, }; } const unitAccept = artifact ? isDesignUnitArtifact(artifact) : false; // 细化终稿写「设计.worker集」,不是单位草稿,但仍须记入已验收步骤, // 否则编排器会当成没做完再跑一遍。须在 dispatch 之前写入。 const shouldRecordStep = Boolean( artifact && (unitAccept || artifact.workerId === "design-step"), ); if (artifact && shouldRecordStep) { const slots = { ...this.session.slots }; this.recordCreationUnitAccepted(slots, artifact); this.session = { ...this.session, slots }; } await this.dispatch({ type: "user_accepted_artifact", payload: { artifactId: id } }); this.persistInstanceWorkerDeclaration(); if (artifact) { const result = compressAfterWorkerAccept({ blackboard: this.blackboard, workerId: artifact.workerId, outputTags: artifact.outputTags, summary: artifact.summary, mode: unitAccept ? "unit" : "final", }); const slots: Record = { ...this.session.slots, lastCompressBrief: result.briefText, lastCompressedWorkerId: artifact.workerId, }; if (shouldRecordStep) { const unitId = String(slots[SLOT_CREATION_CURRENT_UNIT] ?? "").trim(); this.onMessage( `[创作单位已验收] ${unitId || artifact.workerId}:${ artifact.summary?.trim() || "草稿单位" };过程对话将折叠,草稿保留。`, ); slots[SLOT_CREATION_CURRENT_UNIT] = undefined; slots[SLOT_CREATION_UNIT_ANCHOR_AT] = undefined; } if (!unitAccept) { this.onMessage( `[上下文已压缩] ${artifact.workerId}:保留 ${result.finals.map((f) => f.tag).join("、") || "(无终产物)"};归档 ${result.archivedTags.length} 个过程 tag。下一阶段以定稿为准。`, ); } this.session = { ...this.session, slots }; } return this.session; } /** 用户拒绝 pendingArtifact:有意见则立刻按「用户.修订说明」在现有产物上改 */ async rejectArtifact(reason?: string, artifactId?: string): Promise { const id = artifactId ?? this.session.pendingArtifactId; if (!id) throw new Error("当前没有待拒绝的产物"); await this.dispatch({ type: "user_rejected_artifact", payload: { artifactId: id, reason }, }); return this.session; } /** * 按意见重跑却没写出新产物:退回**本次修订的那一份**产物验收,重试追问改挂为可选。 * 只认 beginRevisionRerun 钉下的底稿;否则会把无关的旧产物(如上一轮流程编排) * 错误地挂回验收面。首跑无产物仍走阻塞追问。 * @returns true = 已退回验收,调用方应 return */ private async restoreRevisionTargetForReview( workerId: string, questions: import("../types/questions.js").QuestionItem[] | string[], ): Promise { const targetId = this.session.slots.revisionTargetArtifactId; if (typeof targetId !== "string" || !targetId) return false; const target = this.session.artifacts.find( (a) => a.id === targetId && a.workerId === workerId && a.status === "revision_requested", ); if (!target) return false; await this.dispatch({ type: "worker_revision_produced_nothing", payload: { artifactId: target.id, questions }, }); return true; } /** * 游玩层不走创作 HITL:终稿槽必须交出用户可见正文,其它执行单元空产物也继续。 */ private settlePlayWorkerResult( workerId: string, result: import("../worker/executor.js").WorkerRunResult, ): import("../worker/executor.js").WorkerRunResult { if (!isPlayLayerActive(this.session.slots)) return result; const outputs = { ...result.outputs }; const narratorEnabled = isNarratorEnabled( parseWorkerSetYaml( this.blackboard.getContentByTag("设计.worker集") ?? "", )?.play_slots, ); if ( Object.keys(outputs).length === 0 && isPlayFinalVisibleWorker(workerId, { narratorEnabled }) ) { const shell = parseShellAdaptationFromReplyFormat( this.blackboard.getContentByTag("设计.正文组成") ?? this.blackboard.getContentByTag("设计.回复格式"), )?.shell_id ?? "prose"; const preview = result.preview?.trim() ?? ""; const looksAsk = /可验收|追问|请再补|askUser/.test(preview) || /^\s*-\s/.test(preview); // 主世界终稿用纯正文;转述仍可落 present 包 outputs[PRESENT_TAG] = isPlayPresentWorker(workerId) ? presentOutputFromFallback(looksAsk ? "" : preview, shell) : looksAsk ? "(本轮场面未写完)" : preview || "(本轮场面未写完)"; } return { ...result, outputs, askUser: undefined, askAssessment: undefined, }; } /** worker 调用 ask_user 能力时,由外层触发此事件(仅无产物时阻塞) */ async workerAsk( questions: import("../types/questions.js").QuestionItem[] | string[], ): Promise { const workerId = this.session.currentWorkerId; if (!workerId) throw new Error("当前没有运行中的 worker"); this.rememberAskedQuestions(normalizeQuestions(questions)); await this.dispatch({ type: "worker_needs_input", payload: { workerId, stepId: this.session.currentStepId, questions }, }); return this.session; } private askedQuestionHistory() { return parseAskedQuestions(this.session.slots[SLOT_ASKED_QUESTIONS]); } private rememberAskedQuestions( questions: readonly import("../types/questions.js").QuestionItem[], ): void { if (!questions.length) return; const next = appendAskedQuestions(this.askedQuestionHistory(), questions); this.session = { ...this.session, slots: { ...this.session.slots, [SLOT_ASKED_QUESTIONS]: next }, }; } /** * 挂在验收态的可跳过追问:本局问过的不再问一次。 * 编排层常把上一步已问过的体验题再抛一遍,用户会读成「又要重做那一步」。 */ private freshSidecarQuestions( questions: readonly import("../types/questions.js").QuestionItem[] | undefined, ): import("../types/questions.js").QuestionItem[] | undefined { if (!questions?.length) return undefined; const fresh = dropAlreadyAskedQuestions(questions, this.askedQuestionHistory()); if (!fresh.length) return undefined; this.rememberAskedQuestions(fresh); return fresh; } /** * 验收态下作答或跳过挂载追问:不离开 review_artifact。 * answersText 缺省 = 跳过。 */ async resolveSidecarQuestions(answersText?: string): Promise { await this.dispatch({ type: "user_resolved_sidecar_questions", payload: { answersText }, }); return this.session; } /** 写入创作过程对话摘要(design worker contextSegments 用) */ writeCreationDialogue(transcript: string): void { const tag = "创作.对话"; this.blackboard.write({ tag, content: transcript, source: "system", }); this.session = { ...this.session, slots: { ...this.session.slots, [tag]: transcript }, }; } /** * 手动标记 worker 完成:先创建 artifact 写入 session,再 dispatch worker_completed。 * 真实 worker 集成时,产物内容应在此之前写入 slots / blackboard。 */ async workerComplete(summary?: string): Promise { const workerId = this.session.currentWorkerId; if (!workerId) throw new Error("当前没有运行中的 worker"); const artifact = createArtifact({ workerId, stepId: this.session.currentStepId, outputTags: ["output.草稿"], summary: summary ?? `${workerId} 产物`, }); this.session = { ...this.session, artifacts: [...this.session.artifacts, artifact] }; await this.dispatch({ type: "worker_completed", payload: { artifactId: artifact.id }, }); return this.session; } /** 非 autoStub 模式下,手动启动 pendingWorkerEffect */ async startPendingWorker(): Promise { const effect = this.pendingWorkerEffect; if (!effect) throw new Error("没有待启动的 worker"); this.pendingWorkerEffect = null; await this.runStubWorker(effect, false); return this.session; } /** 消费 applyEvent 返回的 PhaseEffect 列表 */ private async processEffects(effects: PhaseEffect[]): Promise { for (const effect of effects) { if (effect.type === "run_worker") { debugLog("step", `启动执行单元 ${effect.workerId}`); } else if (effect.type === "propose_next_creation_step") { debugLog("step", "提案下一步"); } else if (effect.type === "resume_worker") { debugLog("step", `恢复执行单元 ${this.session.resumeContext?.workerId ?? "未知"}`); } switch (effect.type) { case "emit_message": this.onMessage(effect.message); break; case "invoke_main_agent": if (!this.mainAgent) { this.onMessage( "[阶段机] running:等待总管决策。请用 /decide 提交 run_worker 或 finish。", ); } break; case "run_worker": if ( effect.workerId === DESIGN_STEP_WORKER_ID && !this.canRunPinnedDesignStep() ) { await this.awaitCreationStepPick(); break; } if (this.autoStubWorker) { await this.runStubWorker(effect); } else if (this.llm) { await this.runRealWorker(effect); } else { this.pendingWorkerEffect = effect; this.onMessage( `[阶段机] 待启动 worker: ${effect.workerId}。输入 /worker-start`, ); } break; case "resume_worker": { const ctx = this.session.resumeContext; if (!ctx) break; const workerEffect: Extract = { type: "run_worker", workerId: ctx.workerId, }; if (this.autoStubWorker) { await this.runStubWorker(workerEffect); } else if (this.llm) { await this.runRealWorker(workerEffect); } else { this.pendingWorkerEffect = workerEffect; this.onMessage(`[阶段机] 恢复 worker: ${ctx.workerId}。输入 /worker-start`); } break; } case "run_programmatic_review": await this.runProgrammaticReview(effect.artifactId); break; case "propose_next_creation_step": await this.proposeNextCreationStep(); break; case "seal_creation_opening": this.sealCreationOpening(); break; case "unseal_creation_opening": this.unsealCreationOpening(); break; case "run_play_turn": await this.startPlayTurn(); break; case "continue_play_turn": await this.continuePlayTurn(); break; } } } /** * 用户填完「下一步想写什么」后:有可点节点则停在分层图;程序步自动开干;否则交总管扩步。 */ private async proposeNextCreationStep(): Promise { await this.awaitCreationStepPick(); } private readAcceptedStepIds(): string[] { return parseAcceptedUnits( this.session.slots[SLOT_CREATION_ACCEPTED_UNITS] ?? this.blackboard.getContentByTag(CREATION_ACCEPTED_UNITS_TAG), ); } private async loadActiveModuleCatalog(): Promise { const active = this.getActiveSkill(); if (!active?.name) return null; try { const skill = await loadSkill(active.name); if (!skill.skillPackRoot) return null; return await loadModuleCatalog(skill.skillPackRoot); } catch { return null; } } private readPinnedStepId(): string { const fromCurrent = this.blackboard .getContentByTag(CREATION_CURRENT_STEP_TAG) ?.trim(); if (fromCurrent) return fromCurrent; const raw = (typeof this.session.slots[CREATION_PROPOSED_STEP_TAG] === "string" ? String(this.session.slots[CREATION_PROPOSED_STEP_TAG]) : null) || this.blackboard.getContentByTag(CREATION_PROPOSED_STEP_TAG); if (!raw?.trim()) return ""; try { const doc = JSON.parse(raw) as { stepId?: unknown }; return typeof doc.stepId === "string" ? doc.stepId.trim() : ""; } catch { return ""; } } private hasPinnedReadyStep(): boolean { const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); if (!flow) return false; const step = findStepByRef(flow, this.readPinnedStepId()); if (!step) return false; return isStepReady(flow, step, this.readAcceptedStepIds()); } /** 已验收节点回头改:isStepReady 为 false,但仍须跑 worker,不能弹回分层图。 */ private canRunPinnedDesignStep(): boolean { if (this.hasPinnedReadyStep()) return true; if (typeof this.session.slots.revisionTargetArtifactId === "string") { return Boolean(this.session.slots.revisionTargetArtifactId.trim()); } if ( isInheritExistingFlag( this.blackboard.getContentByTag(CREATION_INHERIT_EXISTING_TAG) ?? this.session.slots[CREATION_INHERIT_EXISTING_TAG], ) ) { return true; } const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); const step = flow ? findStepByRef(flow, this.readPinnedStepId()) : null; return Boolean(step && isReviseStep(step)); } private pinCreationStepSnapshot(params: { stepId: string; name: string; declaration: string; params: Record; paramsMissing: string[]; paramSpecs: ModuleParamSpec[]; kind?: ModuleCatalogEntry["kind"]; closer?: boolean; revise: boolean; revises?: string; }): void { const intent = typeof this.session.slots["用户.下一步意向"] === "string" ? String(this.session.slots["用户.下一步意向"]).trim() : ""; const snapshot = { stepId: params.stepId, name: params.name, declaration: params.declaration || undefined, params: params.params, paramsMissing: params.paramsMissing, paramSpecs: params.paramSpecs.map((p) => ({ key: p.key, label: p.label, required: Boolean(p.required), hint: p.hint, })), intent: intent || undefined, ...(params.kind ? { kind: params.kind } : {}), ...(params.closer ? { closer: true } : {}), ...(params.revise ? { mode: "revise" as const } : {}), ...(params.revises ? { revises: params.revises } : {}), }; this.blackboard.write({ tag: CREATION_PROPOSED_STEP_TAG, content: JSON.stringify(snapshot), source: "runtime", }); this.blackboard.write({ tag: CREATION_CURRENT_STEP_TAG, content: params.stepId, source: "runtime", }); this.blackboard.write({ tag: CREATION_INHERIT_EXISTING_TAG, content: params.revise ? "1" : "", source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_PROPOSED_STEP_TAG]: JSON.stringify(snapshot), [CREATION_CURRENT_STEP_TAG]: params.stepId, [CREATION_INHERIT_EXISTING_TAG]: params.revise ? "1" : "", }, }; } private async startCreationStep( stepId: string, opts?: { stepParams?: CreationFlowStepParams; reenter?: boolean }, ): Promise { this.syncSlotsToBlackboard(this.session); let flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); if (!flow) throw new Error("还没有工作流计划"); let step = findStepByRef(flow, stepId); if (!step) throw new Error(`找不到节点:${stepId}`); const accepted = this.readAcceptedStepIds(); const catalog = await this.loadActiveModuleCatalog(); const reenter = opts?.reenter === true; if (isPrototypeStep(step)) { if (!isPrototypeSelectable(flow, step, accepted)) { throw new Error(`「${step.name}」的前置还没齐,不能增殖`); } const spawned = spawnInstanceFromPrototype({ flow, catalog, prototypeId: step.id, acceptedStepIds: accepted, }); if ("error" in spawned) throw new Error(spawned.error); const content = stringifyCreationFlow(spawned.flow); this.blackboard.write({ tag: CREATION_FLOW_TAG, content, source: "user", }); this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_FLOW_TAG]: content }, }; flow = spawned.flow; step = spawned.step; } else if (!reenter && !isStepReady(flow, step, accepted)) { throw new Error(`「${step.name}」的前置节点还没完成,不能开始`); } const pendingModule = findModuleByName(catalog, step.name); if (reenter) { const artTag = pendingModule?.artifact?.trim(); const hasArt = Boolean( artTag && this.blackboard.getContentByTag(artTag)?.trim(), ); if ( !isStepAccepted(step, accepted) && !hasArt && !this.findReenterArtifact(step.id, artTag) ) { throw new Error(`「${step.name}」还没完成,请直接进入而不是回头修改`); } } const paramSpecs = pendingModule?.params ?? []; const auto = pendingModule?.auto === true; if (opts?.stepParams && Object.keys(opts.stepParams).length > 0) { const next = patchCreationFlowStepParams(flow, step.id, opts.stepParams); const content = stringifyCreationFlow(next); this.blackboard.write({ tag: CREATION_FLOW_TAG, content, source: "user", }); this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_FLOW_TAG]: content }, }; } const latest = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); const pinned = latest ? findStepByRef(latest, step.id) ?? step : step; const params = pinned.params ? { ...pinned.params } : {}; const paramsMissing = missingRequiredStepParams(pinned, pendingModule); this.pinCreationStepSnapshot({ stepId: pinned.id, name: pinned.name, declaration: pendingModule?.declaration?.trim() || "", params, paramsMissing, paramSpecs, kind: pendingModule?.kind, closer: pendingModule?.closer === true, revise: reenter || isReviseStep(pinned), revises: pinned.revises, }); const waitingPick = this.session.waitingReason?.kind === "pick_creation_step"; const needsParamForm = paramsMissing.length > 0 && !auto; if (reenter) { const existing = this.ensureReenterArtifact( pinned.id, pendingModule?.artifact, ); if (existing) { this.reopenArtifactForRevision(existing, pinned); return; } throw new Error( `找不到「${pinned.name}」的已有产物,无法回头修改。要新开一条请点原型上的「增殖」。`, ); } if (needsParamForm) { const decision = createDecision({ action: "run_worker", workerId: DESIGN_STEP_WORKER_ID, reason: `「${pinned.name}」还缺参数:${paramsMissing.join("、")}`, requiresApproval: true, }); await this.dispatch({ type: "main_agent_decision_created", payload: { decision }, }); return; } if (waitingPick) { await this.dispatch({ type: "user_picked_creation_step", payload: { stepId: pinned.id }, }); return; } const decision = createDecision({ action: "run_worker", workerId: DESIGN_STEP_WORKER_ID, reason: reenter || isReviseStep(pinned) ? `回头修改 · ${pinned.name}` : `开始:${pinned.name}`, requiresApproval: false, }); await this.dispatch({ type: "main_agent_decision_created", payload: { decision }, }); } /** 回头修改:打开该步已有产物,让用户像未验收时一样写意见再改。 */ private findReenterArtifact( stepId: string, artifactTag?: string, ): ArtifactRecord | undefined { const id = stepId.trim(); const tag = artifactTag?.trim(); if (!id && !tag) return undefined; const usable = this.session.artifacts.filter( (a) => a.workerId === DESIGN_STEP_WORKER_ID && a.status !== "rejected", ); const byStep = id ? usable.filter((a) => a.stepId === id) : []; const byTag = tag ? usable.filter((a) => a.outputTags.includes(tag)) : []; const matches = byStep.length ? byStep : byTag; const accepted = matches.filter((a) => a.status === "accepted"); const pool = accepted.length ? accepted : matches; return pool.at(-1); } private ensureReenterArtifact( stepId: string, artifactTag?: string, ): ArtifactRecord | undefined { const found = this.findReenterArtifact(stepId, artifactTag); if (found) return found; const tag = artifactTag?.trim(); let content = tag ? this.blackboard.getContentByTag(tag)?.trim() || "" : ""; if (!content) { content = this.readAcceptedUnitSlice(stepId); if (content && tag) { this.blackboard.write({ tag, content, source: "runtime", }); } } if (!tag || !content) return undefined; const artifact = createArtifact({ workerId: DESIGN_STEP_WORKER_ID, stepId, outputTags: [tag], summary: "既有产物", }); this.session = { ...this.session, artifacts: [...this.session.artifacts, artifact], }; return artifact; } private readAcceptedUnitSlice(stepId: string): string { const store = parseAcceptedContentStore( this.blackboard.getContentByTag(CREATION_ACCEPTED_CONTENT_TAG), ); const entry = store.units[normalizeCreationUnitId(stepId)]; if (!entry || entry.content == null) return ""; return typeof entry.content === "string" ? entry.content.trim() : JSON.stringify(entry.content, null, 2).trim(); } private reopenArtifactForRevision( artifact: ArtifactRecord, step: { id: string; name: string }, ): void { const now = new Date().toISOString(); this.session = { ...this.session, phase: "waiting_user", waitingReason: { kind: "review_artifact", artifactId: artifact.id }, pendingArtifactId: artifact.id, currentWorkerId: DESIGN_STEP_WORKER_ID, currentStepId: step.id, pendingDecision: undefined, artifacts: this.session.artifacts.map((a) => a.id === artifact.id ? { ...a, status: "under_review", updatedAt: now } : a, ), slots: { ...this.session.slots, revisionTargetArtifactId: artifact.id, }, updatedAt: now, }; this.onMessage( `已回到「${step.name}」,可按意见修改现有产物(继承原文),或返回节点再选别的步骤。`, ); } /** * 验收后回到分层图:点节点即确认并进入,底栏写意见再发即改编排。 */ private async awaitCreationStepPick(): Promise { this.syncSlotsToBlackboard(this.session); const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); const accepted = this.readAcceptedStepIds(); if (!flow) { debugLog("step", "暂无可用流程,等待点选或改编排"); await this.enterCreationStepPickWait(); return; } const catalog = await this.loadActiveModuleCatalog(); const ready = listReadySteps(flow, accepted); const autoReady = ready.filter((step) => { const mod = findModuleByName(catalog, step.name); return ( mod?.auto === true && missingRequiredStepParams(step, mod).length === 0 ); }); if (autoReady.length > 0 && autoReady.length === ready.length) { const protoReady = listSelectablePrototypes(flow, accepted).length > 0; const spawnReady = catalog ? listRepeatableSpawns(flow, catalog, accepted).some((s) => s.ready) : false; if (!protoReady && !spawnReady) { debugLog("step", `程序步直接开始 ${autoReady[0]!.id}`); await this.startCreationStep(autoReady[0]!.id); return; } } debugLog( "step", hasSelectableCreationWork(flow, catalog, accepted) ? "等待点选创作节点" : "图上暂无已就绪节点,点节点进入或在底栏写意见改编排", ); await this.enterCreationStepPickWait(); } private async enterCreationStepPickWait(): Promise { this.clearProposedStep(); if (this.session.waitingReason?.kind === "pick_creation_step") return; if (this.session.phase === "running") { await this.dispatch({ type: "creation_step_pick_awaited", payload: {} }); return; } this.session = { ...this.session, phase: "waiting_user", waitingReason: { kind: "pick_creation_step" }, pendingDecision: undefined, currentWorkerId: undefined, updatedAt: new Date().toISOString(), }; } private clearProposedStep(): void { this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_PROPOSED_STEP_TAG]: undefined, }, }; } /** * 选定开场白后:把候选落成 输出.开场白 / 初值表。不关 DAG,创作流程继续。 */ private sealCreationOpening(): void { const selectedRaw = this.session.slots[SLOT_OPENING_SELECTED_INDEX]; const selectedIndex = typeof selectedRaw === "number" ? selectedRaw : typeof selectedRaw === "string" && selectedRaw.trim() ? Number(selectedRaw) : 0; const fragmentRaw = this.blackboard.getContentByTag(OPENING_SETUP_ARTIFACT_TAG) ?? ""; const payload = parseOpeningSealPayload(fragmentRaw, selectedIndex); const existingOpening = this.blackboard.getContentByTag(OPENING_OUTPUT_TAG)?.trim(); const openingText = payload?.selectedText?.trim() || existingOpening || ""; if (openingText) { this.writeWorkerTagContent(OPENING_OUTPUT_TAG, openingText, "opening-setup"); } if (payload?.variables.length) { const source = "worker:opening-setup"; const initialPatch = mergeOpeningTablePatch( this.blackboard.getContentByTag(OPENING_INITIAL_VARS_TAG), payload.variables, source, ); if (initialPatch) { this.writeWorkerTagContent(OPENING_INITIAL_VARS_TAG, initialPatch, "opening-setup"); } const currentPatch = mergeOpeningTablePatch( this.blackboard.getContentByTag(OPENING_CURRENT_VARS_TAG), payload.variables, source, ); if (currentPatch) { this.writeWorkerTagContent(OPENING_CURRENT_VARS_TAG, currentPatch, "opening-setup"); } } const n = payload?.candidates.length ?? (openingText ? 1 : 0); const which = n > 1 && payload ? `第 ${payload.selectedIndex + 1}/${n} 条` : n ? "开场白" : "开场"; this.onMessage( `[创作] 已选定${which}。可保存为产物后开玩,或继续补节点。`, ); this.session = { ...this.session, slots: { ...this.session.slots, [SLOT_CREATION_SEALED_BY_OPENING]: true, [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 { // Boss 直聘:落档/开玩时自动挂载产物;种变量初值;按映射重投影 if (this.isDictateIntakeMode()) { const bound = applyDictatePlayBind(this.blackboard); if (bound.boundTags.length) { this.onMessage( `[Boss直聘] 已自动挂载 ${bound.boundTags.length} 个产物到游玩上下文:${bound.boundTags.join("、")}`, ); } else { this.onMessage("[Boss直聘] 已生成默认游玩规格(尚无产物可挂载)"); } this.seedVariablesAndReproject("opening-setup"); } else { 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, }, }; } /** 目录初值 → 变量.当前;再按 设计.变量映射 重写投影 tag */ private seedVariablesAndReproject(actor: string): void { const seeded = seedVariablesFromCatalog({ catalogRaw: this.blackboard.getContentByTag(VARIABLE_CATALOG_TAG), currentRaw: this.blackboard.getContentByTag(OPENING_CURRENT_VARS_TAG), initialRaw: this.blackboard.getContentByTag(OPENING_INITIAL_VARS_TAG), actor: `system:${actor}`, }); if (seeded.initial) { this.blackboard.write({ tag: OPENING_INITIAL_VARS_TAG, content: seeded.initial, source: actor, }); } if (seeded.current) { this.blackboard.write({ tag: OPENING_CURRENT_VARS_TAG, content: seeded.current, source: actor, }); if (seeded.seededKeys.length) { this.onMessage( `[变量] 已种初值:${seeded.seededKeys.join("、")}`, ); } } this.applyValueMapProjections(actor); } /** 按当前真值持续投影映射 tag(非 once) */ private applyValueMapProjections(workerId: string): void { if (!this.blackboard.getContentByTag(VALUE_MAP_TAG)?.trim()) return; const result = reprojectValueMaps({ blackboard: this.blackboard, source: `value-map:${workerId}`, }); if (result.written.length) { this.onMessage( `[变量映射] 投影 ${result.written .map((w) => `${w.field}=${String(w.value)}→${w.tag}`) .join("、")}`, ); } } /** 再编排:解开开场收口,让 design-flow 能在收口前插入节点 */ private unsealCreationOpening(): void { const reopened = reopenCreationFlowRaw( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); if (reopened) { this.blackboard.write({ tag: CREATION_FLOW_TAG, content: reopened, source: "runtime", }); } const slots = { ...this.session.slots }; delete slots[SLOT_CREATION_SEALED_BY_OPENING]; if (reopened) slots[CREATION_FLOW_TAG] = reopened; this.session = { ...this.session, slots }; } private applyProposedStepParams(params: CreationFlowStepParams): void { const raw = (typeof this.session.slots[CREATION_PROPOSED_STEP_TAG] === "string" ? String(this.session.slots[CREATION_PROPOSED_STEP_TAG]) : null) || this.blackboard.getContentByTag(CREATION_PROPOSED_STEP_TAG); if (!raw?.trim()) return; let snap: { stepId?: string; kind?: string; paramSpecs?: Array<{ key: string; required?: boolean }>; }; try { snap = JSON.parse(raw) as typeof snap; } catch { return; } const stepId = typeof snap.stepId === "string" ? snap.stepId.trim() : ""; if (!stepId) return; const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); if (!flow) return; const next = patchCreationFlowStepParams(flow, stepId, params); const content = stringifyCreationFlow(next); this.blackboard.write({ tag: CREATION_FLOW_TAG, content, source: "user", }); const paramsMissing = snap.kind === "prior-artifact" ? [] : (snap.paramSpecs ?? []) .filter((p) => p.required) .map((p) => p.key) .filter((key) => { const v = params[key]; return v == null || (typeof v === "string" && !v.trim()); }); const snapshot = { ...snap, params, paramsMissing, }; this.blackboard.write({ tag: CREATION_PROPOSED_STEP_TAG, content: JSON.stringify(snapshot), source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_FLOW_TAG]: content, [CREATION_PROPOSED_STEP_TAG]: JSON.stringify(snapshot), }, }; } private assertProposedStepReady(): string | null { const decision = this.session.pendingDecision; if (decision?.workerId !== "design-step") return null; const raw = (typeof this.session.slots[CREATION_PROPOSED_STEP_TAG] === "string" ? String(this.session.slots[CREATION_PROPOSED_STEP_TAG]) : null) || this.blackboard.getContentByTag(CREATION_PROPOSED_STEP_TAG); if (!raw?.trim()) return null; try { const snap = JSON.parse(raw) as { paramsMissing?: string[] }; if (snap.paramsMissing?.length) { return `请先补齐必填参数:${snap.paramsMissing.join("、")}`; } } catch { /* ignore */ } return null; } private getInstanceWorkerDeclaration() { return buildInstanceWorkerDeclaration( this.session, this.blackboard, inferLifecycleStage(this.session), ); } /** world-simulator:run_worker 必须在 设计.worker集 声明的 activeWorkerIds 内 */ private assertWorkerDeclared(workerId: string): void { const active = this.getActiveSkill(); if (!shouldEnforceWorkerDeclaration(active?.name)) return; const declaration = this.getInstanceWorkerDeclaration(); const resolved = resolveWorkerId(workerId); if (!declaration.activeWorkerIds.includes(resolved)) { throw new Error(formatUndeclaredWorkerError(resolved, declaration)); } } private async runRealWorker( effect: Extract, ): Promise { this.assertWorkerDeclared(effect.workerId); const activeSkill = this.getActiveSkill(); if (!activeSkill?.name) { throw new Error("当前没有 active skill,无法运行 worker"); } const workerId = resolveWorkerId(effect.workerId); // 机遇裁定:纯程序,不走 LLM if (workerId === "chance") { await this.runChanceWorker(effect); return; } if (!this.llm) { throw new Error("未配置 LLM,无法运行 worker"); } this.lastWorkerRunSnapshot = { workerId, runtimeSession: structuredClone(this.session), blackboardItems: this.blackboard.exportItems(), }; let slots = { ...this.session.slots }; this.syncSlotsToBlackboard(this.session); if (effect.workerContext?.roleId?.trim()) { const roleId = effect.workerContext.roleId.trim(); slots["世界.当前角色.id"] = roleId; this.blackboard.write({ tag: "世界.当前角色.id", content: roleId, source: "runtime", }); this.session = { ...this.session, slots }; } const acceptanceMode = resolveAcceptanceModeForWorker({ session: this.session, blackboard: this.blackboard, workerId: effect.workerId, }); this.session = ( await this.dispatch({ type: "worker_started", payload: { workerId: effect.workerId, stepId: this.session.currentStepId, acceptanceMode: this.session.resumeContext?.acceptanceMode ?? acceptanceMode, }, }) ).session; if (isDesignDiskWorker(workerId)) { this.beginCreationUnitRun(workerId); } // design-step:能力「默认问题」由程序先发出,等用户首答后再调 LLM if (workerId === "design-step") { const delivered = await this.maybeDeliverModuleOpening(activeSkill.name); if (delivered) return; } const resolved = await resolveRunnableWorker({ skillPackName: activeSkill.name, workerId: effect.workerId, session: this.session, blackboard: this.blackboard, }); const worker = resolved.worker; this.ensureResidentContextMounted(workerId); const workerLlm = resolveWorkerLlmProvider({ worker, bindings: (await loadSkill(activeSkill.name)).workerLlmBindings, slots, fallbackLlm: this.llm, }); this.onMessage( `[Worker] ${workerId} 执行中…${resolved.source === "declaration" ? "(声明驱动)" : ""}`, ); debugLog("step", `调用模型 ${workerId}`); this.onWorkerStreamStart?.(workerId); try { const result = await runWorkerSkill({ skillName: activeSkill.name, workerId: effect.workerId, slots, blackboard: this.blackboard, llm: workerLlm, declared: { worker: resolved.worker, promptBody: resolved.promptBody }, stream: { onThinkingDelta: (delta) => this.onWorkerThinkingDelta?.(workerId, delta), onOutputDelta: (delta) => this.onWorkerOutputDelta?.(workerId, delta), }, }); const settled = this.settlePlayWorkerResult(workerId, result); if (settled.askUser?.length && Object.keys(settled.outputs).length === 0) { // 无产物:阻塞追问(信息不足,必须补) if (await this.restoreRevisionTargetForReview(workerId, settled.askUser)) { return; } await this.workerAsk(settled.askUser); return; } if ( workerId === "design-step" && Object.keys(settled.outputs).length === 0 ) { const retryQuestions = settled.askUser?.length ? settled.askUser : [ { id: "design-step-retry", prompt: "这一步还没写出可验收的产物。请再补一点你最在意的体验或参与方式;也可以说「按已有描述先出一版」。", allowOther: true, required: true, }, ]; if (await this.restoreRevisionTargetForReview(workerId, retryQuestions)) { return; } await this.workerAsk(retryQuestions); return; } slots = { ...this.session.slots }; const writtenTags: string[] = []; for (const [tag, content] of Object.entries(settled.outputs)) { if (isProgressPointerTag(tag)) continue; const written = this.writeWorkerTagContent(tag, content, workerId); slots[tag] = written; writtenTags.push(tag); } this.session = { ...this.session, slots }; const artifact = createArtifact({ workerId: effect.workerId, stepId: this.session.currentStepId, outputTags: (writtenTags.length > 0 ? writtenTags : worker.outputTags).filter( (t) => !isProgressPointerTag(t), ), summary: settled.summary, }); this.session = { ...this.session, artifacts: [...this.session.artifacts, artifact] }; const hasPlayVisible = Object.keys(settled.outputs).some( (t) => t === "输出.用户展示" || t === "输出.开场白", ); this.onMessage( `[Worker] ${workerId} 已完成\n\n${settled.preview}${ !hasPlayVisible && settled.preview.length >= 4000 ? "\n\n…" : "" }`, ); await this.dispatch({ type: "worker_completed", payload: { artifactId: artifact.id, // 有产物时 askUser 挂到验收态,不阻断 Accept questions: this.freshSidecarQuestions(settled.askUser), assessment: settled.askAssessment?.trim() || undefined, }, }); this.maybeCompressAcceptedArtifact(artifact.id); } finally { this.onWorkerStreamDone?.(workerId); } } /** 机遇裁定:程序掷骰/比点/抽签,写入 运行.本轮.机遇 */ private async runChanceWorker( effect: Extract, ): Promise { const workerId = "chance"; this.lastWorkerRunSnapshot = { workerId, runtimeSession: structuredClone(this.session), blackboardItems: this.blackboard.exportItems(), }; const acceptanceMode = resolveAcceptanceModeForWorker({ session: this.session, blackboard: this.blackboard, workerId: effect.workerId, }); this.session = ( await this.dispatch({ type: "worker_started", payload: { workerId: effect.workerId, stepId: this.session.currentStepId, acceptanceMode: this.session.resumeContext?.acceptanceMode ?? acceptanceMode, }, }) ).session; const boardRaw = this.blackboard.getContentByTag("运行.机会请求"); const batch = resolveChanceBatchRequest({ workerContext: effect.workerContext ?? null, blackboardRequestJson: boardRaw, }); const result = batch?.length ? batch.length === 1 ? executeChance(batch[0]!) : executeChanceBatch(batch) : (() => { const single = resolveChanceRequest({ workerContext: effect.workerContext ?? null, blackboardRequestJson: boardRaw, }); if (single) return executeChance(single); return { schema: "chance.v1" as const, op: "roll" as const, ok: false, summary: "机遇失败:缺少请求(workerContext.chance.requests 或 运行.机会请求)", detail: {}, error: "missing_request", }; })(); const content = JSON.stringify(result, null, 2); const written = this.writeWorkerTagContent( "运行.本轮.机遇", content, workerId, ); this.session = { ...this.session, slots: { ...this.session.slots, "运行.本轮.机遇": written }, }; const artifact = createArtifact({ workerId: effect.workerId, stepId: this.session.currentStepId, outputTags: ["运行.本轮.机遇"], summary: result.summary, }); this.session = { ...this.session, artifacts: [...this.session.artifacts, artifact], }; this.onMessage(`[机遇裁定] ${result.summary}\n\n${content}`); await this.dispatch({ type: "worker_completed", payload: { artifactId: artifact.id }, }); this.maybeCompressAcceptedArtifact(artifact.id); } /** * 占位 worker:worker_started → 可选自动 worker_completed。 * 演示与单测用,真实环境应替换为真实 worker 调度。 */ private async runStubWorker( effect: Extract, autoComplete = true, ): Promise { this.assertWorkerDeclared(effect.workerId); const workerId = resolveWorkerId(effect.workerId); this.lastWorkerRunSnapshot = { workerId, runtimeSession: structuredClone(this.session), blackboardItems: this.blackboard.exportItems(), }; const acceptanceMode = resolveAcceptanceModeForWorker({ session: this.session, blackboard: this.blackboard, workerId: effect.workerId, }); this.session = ( await this.dispatch({ type: "worker_started", payload: { workerId: effect.workerId, stepId: this.session.currentStepId, acceptanceMode: this.session.resumeContext?.acceptanceMode ?? acceptanceMode, }, }) ).session; this.onMessage(`[Worker 占位] ${effect.workerId} 已开始。`); if (!autoComplete) return; const lastInput = String(this.session.slots.lastUserInput ?? ""); const value = `[${effect.workerId} 占位输出]\n${lastInput ? `输入: ${lastInput}\n` : ""}说明: stub worker,后续替换为真实 LLM worker。`; const outputTags = await this.resolveWorkerOutputTags(effect.workerId); const tagsToWrite = outputTags.length > 0 ? outputTags : ["output.草稿"]; for (const tag of tagsToWrite) { this.blackboard.write({ tag, content: value, source: effect.workerId, }); } const artifact = createArtifact({ workerId: effect.workerId, stepId: this.session.currentStepId, outputTags: tagsToWrite, summary: `${effect.workerId} 占位产物`, }); this.session = { ...this.session, artifacts: [...this.session.artifacts, artifact] }; await this.dispatch({ type: "worker_completed", payload: { artifactId: artifact.id }, }); this.maybeCompressAcceptedArtifact(artifact.id); } /** 表类 tag 走字段格合并,尊重 user 手改与 rev;合并后算边沿副作用 */ private writeWorkerTagContent( tag: string, content: string, workerId: string, ): string { const tableTags = new Set(["运行.初始变量", "变量.当前"]); let toWrite = content; let prevDoc: TableDoc | null = null; let nextDoc: TableDoc | null = null; if (tableTags.has(tag)) { const patch = parseTableDoc(content); if (patch) { prevDoc = parseTableDoc(this.blackboard.getContentByTag(tag)); const { doc, skipped } = mergeTableCells({ current: prevDoc, patch, actor: `worker:${workerId}`, }); nextDoc = doc; toWrite = stringifyTableDoc(doc); if (skipped.length) { this.onMessage( `[表合并] ${tag}:跳过 ${skipped.map((s) => `${s.key}(${s.reason})`).join("、")}`, ); } } } if (tag === CREATION_FLOW_TAG) { const merged = mergeCreationFlowPreservingAccepted({ prevRaw: this.blackboard.getContentByTag(tag), nextRaw: toWrite, acceptedStepIds: parseAcceptedUnits( this.session.slots[SLOT_CREATION_ACCEPTED_UNITS] ?? this.blackboard.getContentByTag(CREATION_ACCEPTED_UNITS_TAG), ), }); toWrite = merged.raw; if (merged.restored.length) { this.onMessage( `[流程编排] 已验收步骤按原样保留:${merged.restored.join("、")}`, ); } } this.blackboard.write({ tag, content: toWrite, source: workerId, }); if ( inferLifecycleStage(this.session) === "play" && (tag === "输出.用户展示" || tag === "输出.开场白") ) { const narratorEnabled = isNarratorEnabled( parseWorkerSetYaml( this.blackboard.getContentByTag("设计.worker集") ?? "", )?.play_slots, ); // 有转述时:主世界草稿不进对话史,只记终稿(转述);开场白始终记 if ( tag === "输出.开场白" || isPlayFinalVisibleWorker(workerId, { narratorEnabled }) ) { this.appendDialogueHistory("助手", toWrite); } } if (nextDoc) { this.applyTableSideEffects(prevDoc, nextDoc, workerId); } if (tag === OPENING_CURRENT_VARS_TAG || tag === "变量.当前") { this.applyValueMapProjections(workerId); } // 遗留裁决包 / 旁观维护包 → 合并进变量.当前(可触发 side_effects) if (tag === SETTLEMENT_TAG) { this.applySettlementVariableChanges(toWrite, workerId); } else if (tag === MAINTAIN_TAG) { this.applyMaintainTableOps(toWrite, workerId); } return toWrite; } /** settlement.v1.variable_changes → 变量.当前 */ private applySettlementVariableChanges( settlementRaw: string, workerId: string, ): void { const view = parseSettlementPacket(settlementRaw); const changes = view.packet?.variable_changes; if (!view.ok || !changes?.length) return; const current = parseTableDoc( this.blackboard.getContentByTag("变量.当前"), ); const patch = settlementChangesToTablePatch(changes, current); if (!patch) return; this.writeWorkerTagContent( "变量.当前", stringifyTableDoc(patch), workerId, ); this.onMessage( `[裁决合并] 变量.当前 ← ${changes.map((c) => c.key).join("、")}`, ); } /** maintain.v1.table_ops → 变量.当前(及其他已支持表) */ private applyMaintainTableOps(maintainRaw: string, workerId: string): void { const view = parseMaintainPacket(maintainRaw); const ops = view.packet?.table_ops; if (!view.ok || !ops?.length) { if (view.ok && view.packet?.need_generate) { this.applyMaintainNeedGenerateSampling(view.packet.need_generate, workerId); } return; } const current = parseTableDoc( this.blackboard.getContentByTag("变量.当前"), ); const patch = maintainOpsToTablePatch(ops, current, "变量.当前"); if (!patch) return; this.writeWorkerTagContent( "变量.当前", stringifyTableDoc(patch), workerId, ); const keys = ops .filter((o) => (o.tag?.trim() || "变量.当前") === "变量.当前") .map((o) => o.key); if (keys.length) { this.onMessage(`[旁观维护] 变量.当前 ← ${keys.join("、")}`); } if (view.packet?.need_generate) { this.applyMaintainNeedGenerateSampling(view.packet.need_generate, workerId); } } /** need_generate:按生成规则合同程序批量抽样,写入 运行.本轮.旁观.生成抽样 */ private applyMaintainNeedGenerateSampling( need: { rule_id: string; reason: string }, workerId: string, ): void { const rulesRaw = this.blackboard.getContentByTag("设计.生成规则"); const result = runMaintainNeedGenerateSampling({ need, generationRulesRaw: rulesRaw, }); if (!result) { this.onMessage( `[旁观维护] need_generate=${need.rule_id}:无可程序抽样的元素池(方向池由填写方按规则自创)`, ); return; } const content = JSON.stringify( { rule_id: need.rule_id, reason: need.reason, chance: result }, null, 2, ); this.writeWorkerTagContent(MAINTAIN_GENERATE_TAG, content, workerId); this.onMessage(`[旁观维护] 生成抽样 ${need.rule_id}:${result.summary}`); } /** 避免 syncSlots 重复把同一句用户输入追加进历史 */ private lastAppendedUserHistory = ""; /** 追加一轮到黑板「对话.历史」(排序表可投影裁剪) */ private appendDialogueHistory(role: "用户" | "助手" | "系统", text: string): void { const body = text.trim(); if (!body) return; if (role === "用户") { if (this.lastAppendedUserHistory === body) return; this.lastAppendedUserHistory = body; } const prev = this.blackboard.getContentByTag(DIALOGUE_HISTORY_TAG) ?? ""; const next = appendDialogueHistoryTurn(prev, { role, text: body }); if (next === prev.trim()) return; this.blackboard.write({ tag: DIALOGUE_HISTORY_TAG, content: next, source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [DIALOGUE_HISTORY_TAG]: next }, }; } /** 从 Worker 集 tables.side_effects 算边沿触发并写 tag / 记 fired */ private applyTableSideEffects( prev: TableDoc | null, next: TableDoc, workerId: string, ): void { const raw = readWorkerSetYamlForDeclaration(this.blackboard, this.session); const parsed = raw ? parseWorkerSetYaml(raw.yaml) : null; const rules = parseSideEffectRules(parsed?.tables); if (rules.length === 0) return; const fired = parseFiredRegistry( this.blackboard.getContentByTag(SIDE_EFFECT_FIRED_TAG), ); const { triggers, nextFired, queuedWorkers } = evaluateSideEffects({ prev, next, rules, fired, }); if (triggers.length === 0) return; const { writtenTags } = applySideEffectTagActions({ blackboard: this.blackboard, triggers, source: `side-effect:${workerId}`, }); this.blackboard.write({ tag: SIDE_EFFECT_FIRED_TAG, content: stringifyFiredRegistry(nextFired), source: "system:table-side-effect", }); const ruleIds = triggers.map((t) => t.rule.id).join("、"); this.onMessage( `[表副作用] 边沿触发 ${ruleIds}` + (writtenTags.length ? ` → 写入 ${writtenTags.join("、")}` : "") + (queuedWorkers.length ? `;建议调度 ${queuedWorkers.map((q) => q.workerId).join("、")}` : ""), ); if (queuedWorkers.length) { this.session = { ...this.session, slots: { ...this.session.slots, pendingSideEffectWorkers: JSON.stringify(queuedWorkers), }, }; } } /** 按规格把常驻上下文写入黑板,供本 worker inputTags 读取 */ private ensureResidentContextMounted(workerId: string): void { const raw = readWorkerSetYamlForDeclaration(this.blackboard, this.session); const parsed = raw ? parseWorkerSetYaml(raw.yaml) : null; const entries = parseResidentContext(parsed?.resident_context); if (entries.length === 0) return; writeResidentContextTags({ blackboard: this.blackboard, entries, workerId, source: "system:resident-context", }); } /** * 若本步能力声明了默认问题且尚未收过首答:写开场白、workerAsk,不调 LLM。 * @returns true = 已进入等待用户,调用方应 return */ private async maybeDeliverModuleOpening( skillPackName: string, ): Promise { const skill = await loadSkill(skillPackName); const packRoot = skill.skillPackRoot; if (!packRoot) return false; const flowRaw = this.blackboard.getContentByTag(CREATION_FLOW_TAG); const currentStepName = this.blackboard.getContentByTag(CREATION_CURRENT_STEP_TAG)?.trim() || String(this.session.slots[SLOT_CREATION_CURRENT_UNIT] ?? "").trim(); const accepted = parseAcceptedUnits( this.session.slots[SLOT_CREATION_ACCEPTED_UNITS] ?? this.blackboard.getContentByTag(CREATION_ACCEPTED_UNITS_TAG), ); const binding = await resolveDesignStepBinding({ skillPackRoot: packRoot, flowRaw, currentStepName, acceptedStepNames: accepted, inheritExisting: isInheritExistingFlag( this.blackboard.getContentByTag(CREATION_INHERIT_EXISTING_TAG) ?? this.session.slots[CREATION_INHERIT_EXISTING_TAG], ), }); if (!binding?.opening?.trim()) return false; const stepKey = stepUnitId(binding.step); const state = parseModuleOpeningState( this.session.slots[SLOT_CREATION_MODULE_OPENING_STATE] ?? this.blackboard.getContentByTag(CREATION_MODULE_OPENING_STATE_TAG), ); const phase = state[stepKey]; if (phase === "answered") return false; if (phase === "shown" && this.session.resumeContext) { // 用户刚答完默认问题 → 标记已答,继续走 LLM state[stepKey] = "answered"; const raw = stringifyModuleOpeningState(state); this.blackboard.write({ tag: CREATION_MODULE_OPENING_STATE_TAG, content: raw, source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [SLOT_CREATION_MODULE_OPENING_STATE]: raw, }, resumeContext: undefined, }; return false; } // 首次,或开场已发出但用户还没答(离开节点 / 恢复丢了 resume):展示引导,等用户先说一轮 state[stepKey] = "shown"; const raw = stringifyModuleOpeningState(state); this.blackboard.write({ tag: CREATION_MODULE_OPENING_TAG, content: binding.opening.trim(), source: "runtime", }); this.blackboard.write({ tag: CREATION_MODULE_OPENING_STATE_TAG, content: raw, source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [CREATION_MODULE_OPENING_TAG]: binding.opening.trim(), [SLOT_CREATION_MODULE_OPENING_STATE]: raw, }, }; const questions = normalizeQuestions([ { id: MODULE_OPENING_QUESTION_ID, prompt: binding.opening.trim(), allowOther: true, required: true, }, ]); // 正文由说话面 openingGuide 展示(对齐美学纲领);workerAsk 只发短调度句 await this.workerAsk(questions); return true; } /** design-flow / design-step:钉当前步骤 */ private beginCreationUnitRun(workerId: string): void { const accepted = parseAcceptedUnits( this.session.slots[SLOT_CREATION_ACCEPTED_UNITS] ?? this.blackboard.getContentByTag(CREATION_ACCEPTED_UNITS_TAG), ); let current = this.blackboard.getContentByTag(CREATION_CURRENT_UNIT_TAG)?.trim() || String(this.session.slots[SLOT_CREATION_CURRENT_UNIT] ?? "").trim(); if (workerId === "design-flow") { current = "flow"; } else if (workerId === "design-step") { const flow = parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ); const pinnedId = this.readPinnedStepId(); const pinned = flow && pinnedId ? findStepByRef(flow, pinnedId) : null; const pending = pinned ?? nextPendingStep(flow, accepted); current = pending ? stepUnitId(pending) : current || "(无待执行步骤)"; this.blackboard.write({ tag: CREATION_CURRENT_STEP_TAG, content: current, source: "runtime", }); } if (!current) current = "flow"; debugLog("step", `钉住当前步骤 ${workerId} → ${current}`); const anchorAt = new Date().toISOString(); this.blackboard.write({ tag: CREATION_CURRENT_UNIT_TAG, content: current, source: "runtime", }); this.blackboard.write({ tag: CREATION_ACCEPTED_UNITS_TAG, content: JSON.stringify(accepted), source: "runtime", }); this.session = { ...this.session, slots: { ...this.session.slots, [SLOT_CREATION_CURRENT_UNIT]: current, [SLOT_CREATION_ACCEPTED_UNITS]: accepted, [SLOT_CREATION_UNIT_ANCHOR_AT]: anchorAt, }, }; } private recordCreationUnitAccepted( slots: Record, artifact: { summary?: string; outputTags: string[]; workerId?: string }, ): void { const fromBoard = this.blackboard.getContentByTag(CREATION_CURRENT_UNIT_TAG)?.trim(); const fromSlot = String(slots[SLOT_CREATION_CURRENT_UNIT] ?? "").trim(); const fromStep = this.blackboard.getContentByTag(CREATION_CURRENT_STEP_TAG)?.trim(); const fromSummary = artifact.summary?.match(/单位\s+([^\s·]+)/)?.[1]?.trim(); // 接受工作流计划 ≠ 某一步做完;「flow」不得写入已验收步骤 if (artifact.workerId === "design-flow") return; const prev = parseAcceptedUnits( slots[SLOT_CREATION_ACCEPTED_UNITS] ?? this.blackboard.getContentByTag(CREATION_ACCEPTED_UNITS_TAG), ); const unitId = artifact.workerId === "design-step" ? pickRecordedStepId({ flow: parseCreationFlow( this.blackboard.getContentByTag(CREATION_FLOW_TAG), ), alreadyAccepted: prev, pinnedUnitId: fromSlot || fromBoard, writtenCurrentStep: fromStep, summaryHint: fromSummary, }) ?? "" : fromBoard || fromSlot || fromSummary || ""; if (!unitId || unitId === "flow") return; const next = unitId && !prev.includes(unitId) ? [...prev, unitId] : prev; if (next !== prev) { debugLog("step", `验收步骤 ${unitId} 已验收 ${next.join("、")}`); } slots[SLOT_CREATION_ACCEPTED_UNITS] = next; slots[SLOT_CREATION_CURRENT_UNIT] = unitId || undefined; this.blackboard.write({ tag: CREATION_ACCEPTED_UNITS_TAG, content: JSON.stringify(next), source: "runtime", }); // 前情提要:写入该单位「最后一次验收」的内容切片 if (unitId) { let slice: unknown = null; if (artifact.workerId === "design-step" || artifact.workerId === "design-flow") { const pack = this.getActiveSkill()?.skillPackRoot; // skillPackRoot may not be on snapshot — resolve via artifact tags const contentTag = artifact.outputTags.find( (t) => t !== CREATION_CURRENT_STEP_TAG && t !== CREATION_CURRENT_UNIT_TAG, ) || artifact.outputTags[0]; if (contentTag) { const raw = this.blackboard.getContentByTag(contentTag)?.trim(); if (raw) { try { slice = JSON.parse(raw); } catch { slice = raw; } } } void pack; } else { const draftRaw = this.blackboard.getContentByTag(WORKER_SET_DRAFT_TAG)?.trim() || this.blackboard.getContentByTag(WORKER_SET_FINAL_TAG)?.trim() || ""; const parsed = draftRaw ? parseWorkerSetYaml(draftRaw) : null; slice = extractUnitContentFromDraft(parsed, unitId); } if (slice != null) { const existing = this.blackboard.getContentByTag(CREATION_ACCEPTED_CONTENT_TAG); const updated = upsertAcceptedUnitContent(existing, { unitId, content: slice, summary: artifact.summary, }); this.blackboard.write({ tag: CREATION_ACCEPTED_CONTENT_TAG, content: updated, source: "runtime", }); slots[CREATION_ACCEPTED_CONTENT_TAG] = updated; } } } private maybeCompressAcceptedArtifact(artifactId: string): void { const artifact = this.session.artifacts.find((a) => a.id === artifactId); if (!artifact || artifact.status !== "accepted") return; const result = compressAfterWorkerAccept({ blackboard: this.blackboard, workerId: artifact.workerId, outputTags: artifact.outputTags, summary: artifact.summary, }); this.session = { ...this.session, slots: { ...this.session.slots, lastCompressBrief: result.briefText, lastCompressedWorkerId: artifact.workerId, }, }; this.onMessage( `[上下文已压缩] ${artifact.workerId}:保留 ${result.finals.map((f) => f.tag).join("、") || "(无终产物)"};归档 ${result.archivedTags.length} 个过程 tag。下一阶段以定稿为准。`, ); } private async resolveWorkerOutputTags(workerId: string): Promise { const active = this.getActiveSkill(); if (!active?.name) return []; try { const resolved = await resolveRunnableWorker({ skillPackName: active.name, workerId, session: this.session, blackboard: this.blackboard, }); return resolved.worker.outputTags; } catch { return []; } } /** 简易程序验收:summary 非空则通过,否则 revision */ private async runProgrammaticReview(artifactId: string): Promise { await this.dispatch({ type: "programmatic_review_started", payload: { artifactId } }); const artifact = this.session.artifacts.find((a) => a.id === artifactId); const passed = Boolean(artifact?.summary && artifact.summary.length > 0); await this.dispatch( passed ? { type: "programmatic_review_passed", payload: { artifactId } } : { type: "programmatic_review_failed", payload: { artifactId, reason: "产物 summary 为空" }, }, ); } /** 游玩:按 playWorkerIds 开一轮 */ private async startPlayTurn(): Promise { this.syncSlotsToBlackboard(this.session); this.clearPlayRoundTransientTags(); const ids = this.getInstanceWorkerDeclaration().playWorkerIds; if (!ids.length) { this.onMessage( "[游玩] 运行规格里没有可上场的执行单元,无法推进回合。", ); this.waitForPlayInput(); return; } const [first, ...rest] = ids; this.session = { ...this.session, slots: withPlayTurnQueue(this.session.slots, rest), }; await this.processEffects([{ type: "run_worker", workerId: first }]); } /** 游玩:跑管线下一个;队空则等用户 */ private async continuePlayTurn(): Promise { if (this.session.phase === "waiting_user") return; const queue = readPlayTurnQueue(this.session.slots); if (!queue.length) { this.waitForPlayInput(); return; } const [next, ...rest] = queue; this.session = { ...this.session, slots: withPlayTurnQueue(this.session.slots, rest), }; await this.processEffects([{ type: "run_worker", workerId: next }]); } private waitForPlayInput(): void { this.session = { ...this.session, phase: "waiting_user", waitingReason: { kind: "input" }, currentWorkerId: undefined, pendingArtifactId: undefined, pendingDecision: undefined, resumeContext: undefined, slots: withPlayTurnQueue(this.session.slots, undefined), updatedAt: new Date().toISOString(), }; } /** 新回合开始前归档上一轮 transient 产物(表合并结果保留在 变量.当前) */ private clearPlayRoundTransientTags(): void { const slots = { ...this.session.slots }; const archivedAt = new Date().toISOString(); for (const tag of PLAY_ROUND_TRANSIENT_TAGS) { const hadBoard = this.blackboard.getLatestByTag(tag) != null; const hadSlot = tag in slots; if (!hadBoard && !hadSlot) continue; this.blackboard.write({ tag, content: "", source: "runtime", metadata: { role: "archived", archivedAt, reason: "play_round_reset", }, }); delete slots[tag]; } this.session = { ...this.session, slots }; } /** invoke_main_agent 副作用:running 时调用总管 LLM,链式推进直到需用户介入 */ private async maybeRunMainAgent(effects: PhaseEffect[]): Promise { if (inferLifecycleStage(this.session) === "play") return; if (this.mainAgent && effects.some((e) => e.type === "invoke_main_agent")) { await this.runMainAgent(); } } private hasCreationFlow(): boolean { return Boolean( parseCreationFlow(this.blackboard.getContentByTag(CREATION_FLOW_TAG)), ); } private async commitMainAgentDecision( decision: MainAgentDecision, ): Promise { debugLog( "step", `总管决策 ${labelAction(decision.action)}${decision.workerId ? ` ${decision.workerId}` : ""}`, ); this.onMessage(`[总管] ${decision.action}: ${decision.reason}`); const result = this.commitEvent({ type: "main_agent_decision_created", payload: { decision }, }); if (result.error) this.onMessage(result.error); await this.processEffects(result.effects); await this.maybeRunMainAgent(result.effects); } private async runMainAgent(): Promise { if (!this.mainAgent || this.session.phase !== "running") { return; } if (this.hasCreationFlow()) { debugLog("step", "已有创作流程,等待点选节点"); await this.awaitCreationStepPick(); return; } debugLog("step", "总管开始"); const availableWorkers = await this.resolveAvailableWorkers(); try { const decision = await this.mainAgent.runToolLoop( { session: this.session, blackboardIndex: this.blackboard.listTagIndex(), availableWorkers, }, { handlers: { readBlackboard: (tags) => this.readBlackboardForAgent(tags), listWorkers: () => availableWorkers, listArtifacts: () => this.session.artifacts.map((a) => ({ id: a.id, workerId: a.workerId, status: a.status, summary: a.summary, outputTags: a.outputTags, })), onToolCall: (name, detail) => { const preview = detail.length > 120 ? `${detail.slice(0, 120)}…` : detail; this.onMessage(`[总管 tool] ${name}${preview ? `: ${preview}` : ""}`); }, onThinkingDelta: (delta) => { this.onAgentThinkingDelta?.(delta); }, onThinkingDone: (text) => { this.onAgentThinkingDone?.(text); this.onMessage(`[总管 思考]\n\n${text}`); }, }, }, ); await this.commitMainAgentDecision(decision); } catch (err) { const reason = err instanceof Error ? err.message : String(err); if (this.hasCreationFlow()) { debugLog("step", "总管无可用决策,改为等待点选"); await this.awaitCreationStepPick(); return; } if (isUnusableMainAgentOutput(err)) { debugLog("step", "总管未给出工具调用,改为启动 design-flow"); await this.commitMainAgentDecision( createDecision({ action: "run_worker", workerId: DESIGN_FLOW_WORKER_ID, reason: "尚无创作流程,开始编排近期起点", requiresApproval: false, }), ); return; } debugLog("step", `总管失败 ${reason.slice(0, 120)}`); await this.failRun(reason); } } /** 总管 read_blackboard tool:按 tag 或模式读取正文 */ private readBlackboardForAgent(tags: string[]): Record { const out: Record = {}; for (const pattern of tags) { const items = this.blackboard.queryByPatterns([pattern], "latest"); if (items.length === 0) { const slotVal = this.session.slots[pattern]; if (typeof slotVal === "string" && slotVal.trim()) { out[pattern] = slotVal.trim(); } else { out[pattern] = ""; } continue; } for (const item of items) { out[item.tag] = item.content; } } return out; } private async resolveAvailableWorkers(): Promise< Array<{ id: string; description: string }> > { const active = this.getActiveSkill(); if (!active?.name) return [...DEFAULT_WORKERS]; try { const workers = await listWorkerSkills(active.name); const installed = workers.map((w) => ({ id: w.id, description: w.description })); if (!shouldEnforceWorkerDeclaration(active.name)) { return installed.length > 0 ? installed : [...DEFAULT_WORKERS]; } const declaration = this.getInstanceWorkerDeclaration(); const merged = mergeDeclaredWorkersForAgent(installed, declaration); return merged.length > 0 ? merged : installed; } catch { return [...DEFAULT_WORKERS]; } } } /** 构造总管决策,强制 statePatchAllowed=false */ export function createDecision( partial: Omit & Partial>, ): MainAgentDecision { return { id: partial.id ?? randomUUID(), statePatchAllowed: false, ...partial, }; } /** CLI 调试:单行摘要当前会话状态 */ export function formatSession(session: RuntimeSession): string { const active = session.slots.activeSkill as ActiveSkillSnapshot | undefined; const lines = [ `phase: ${session.phase}`, session.waitingReason ? `waitingReason: ${session.waitingReason.kind}` : null, active ? `skill: ${active.name}` : null, session.slots.startupCompleted ? "startup: done" : null, session.slots["book.brief"] ? "book.brief: yes" : null, session.currentWorkerId ? `worker: ${session.currentWorkerId}` : null, `artifacts: ${session.artifacts.length}`, `events: ${session.history.length}`, ]; return lines.filter(Boolean).join("\n"); } /** 集成测试用:从 start 到 finish 的最小闭环脚本 */ export async function runMinimalClosedLoop( runtime: PhaseRuntime, ): Promise { await runtime.startWithOrchestrator("world-simulator"); await runtime.submitInput("科幻长篇,带世界运转,约 20 万字交互"); const decision = createDecision({ action: "run_worker", reason: "根据用户需求产出 Worker 集", workerId: "design-flow", requiresApproval: false, }); await runtime.submitDecision(decision); if (runtime.getSession().currentWorkerId) { await runtime.workerComplete(); } if (runtime.getSession().pendingArtifactId) { await runtime.acceptArtifact(); } await runtime.submitDecision( createDecision({ action: "finish", reason: "演示结束", requiresApproval: false }), ); return runtime.getSession(); }