import type { LlmProvider } from "../llm/client.js"; import { DriverIterationLimitError, type AgentDriver, } from "../runtime/driver.js"; import { createPlannedDictateDriver } from "./round.js"; import { buildDictateMessages } from "./context.js"; import { buildDictateSystemPrompt } from "./prompt.js"; import { DICTATE_TOOL_DEFINITIONS } from "./tools.js"; import { isAllowedProductTag, parseDictateOrder, type DictateSelfScore, type DictateChatTurn, type DictateProduct, } from "./types.js"; import { DICTATE_MULTI_MODULE_REPLY_HINT, resolveModuleForInsertTag, } from "./insert-feedback.js"; import { dictateProductFamily, resolveRepeatableInsertTag, splitConcreteInstanceContents } from "./repeatable-tags.js"; import type { ModuleCatalog } from "../skills/creation-flow.js"; import { CREATION_PLAN_TAG, formatCreationPlanCapabilityCatalog, formatCreationPlanProgress, parseCreationPlan, requiredCreationPlanGaps, validateCreationPlan, } from "./creation-plan.js"; import { DICTATE_MODULE_READ_STATE_TAG, parseDictateModuleReadState, readLibraryEntries, serializeDictateModuleReadState, type DictateModuleReadPayload, } from "./read-module.js"; import { VARIABLE_CATALOG_TAG, parseVariableCatalog, serializeVariableCatalog, upsertVariableField, removeVariableField, seedVariablesFromCatalog, type UpsertVariableInput, } from "../skills/variable-catalog.js"; import { VARIABLE_DESIGN_TAG, syncVariableDesignToRuntime, writeVariableDesignSync, } from "../skills/variable-design-compile.js"; import { VALUE_MAP_TAG, parseValueMapDoc, serializeValueMapDoc, upsertValueMapEntry, removeValueMapEntry, type UpsertMapInput, } from "../skills/value-map.js"; import { parseTableDoc, stringifyTableDoc, } from "../blackboard/table-cells.js"; import { isDictateOpeningTag } from "./experience-anchor.js"; import { canonicalizeInsertedContent } from "../parse/json-doc.js"; import { openingContractError, parseReplyDataContract, replyContractSummary, type ReplyDataContract, } from "../skills/reply-contract.js"; function seedCurrentFromCatalog(handlers: DictateTurnHandlers): string[] { const seeded = seedVariablesFromCatalog({ catalogRaw: handlers.readTag(VARIABLE_CATALOG_TAG), currentRaw: handlers.readTag("变量.当前"), initialRaw: handlers.readTag("运行.初始变量"), actor: "system:declare_variable", }); const wrote: string[] = []; if (seeded.initial) { handlers.writeTag("运行.初始变量", seeded.initial); wrote.push("运行.初始变量"); } if (seeded.current) { handlers.writeTag("变量.当前", seeded.current); wrote.push("变量.当前"); } return wrote; } function syncDesignProductToTable( handlers: DictateTurnHandlers, designContent?: string, ): string[] { const sync = syncVariableDesignToRuntime({ designRaw: designContent ?? handlers.readTag(VARIABLE_DESIGN_TAG), catalogRaw: handlers.readTag(VARIABLE_CATALOG_TAG), currentRaw: handlers.readTag("变量.当前"), initialRaw: handlers.readTag("运行.初始变量"), }); return writeVariableDesignSync(sync, (tag, content) => handlers.writeTag(tag, content), ); } export type DictateTurnHandlers = { listProducts: () => DictateProduct[]; /** order 省略时由实现沿用原序或默认 */ writeProduct: ( tag: string, content: string, order?: number, metadata?: { layer?: "intermediate" | "final"; selfScore?: DictateSelfScore }, ) => void; /** 删除固定产物 tag;返回是否曾存在 */ deleteProduct: (tag: string) => boolean; /** 读/写变量目录与映射(程序校验后的 JSON) */ readTag: (tag: string) => string | undefined; writeTag: (tag: string, content: string) => void; /** 删除任意黑板 tag(投影 / 变量.当前 等) */ deleteTag: (tag: string) => boolean; /** 写入开场白前运行确定性收口工具,保证运行配置先于开场落盘。 */ prepareOpening?: () => void; clearDialogue: () => void; onToolCall?: (name: string, detail: string) => void; onThinkingDelta?: (delta: string) => void; onThinkingDone?: (text: string) => void; /** * insert 成功后:按 tag 反查能力,返回独立「回复模块」markdown。 * 程序写入 tool result 的 reply_module;同轮多个须在末尾回复中全部附带。 */ lookupInsertFeedback?: (tag: string) => string | undefined; }; export type DictateTurnResult = { reply: string; iterations: number; wroteTags: string[]; deletedTags: string[]; clearedDialogue: boolean; }; /** * 一轮对话落盘的工具上限。 * 串行模型往往一轮只调一个工具:读能力再写入,方案里十余项,再加上收尾回复。 */ export const DICTATE_DRIVER_MAX_ITERATIONS = 36; /** 接近上限时写进工具结果,让模型停下来回复用户。 */ export const DICTATE_TURN_LIMIT_HINT = "本轮工具次数已接近上限。停止调用工具,直接回复用户。已写入的保留;未写的能力下一轮继续。"; /** * 跑一轮对话落盘整理:任务 messages 由本模块拼;preset 夹心由 LLM 包装层统一装配。 */ export async function runDictateTurn(params: { llm: LlmProvider; dialogue: DictateChatTurn[]; handlers: DictateTurnHandlers; systemPrompt?: string; recipeName?: string; recipeBrief?: string; /** modules catalog 的何时落盘块(formatModuleCatalogForDictate) */ moduleGuide?: string; /** 尚无本局方案时显示的旧配方案例目录 */ recipeExamples?: string; /** 用于本局方案覆盖校验及方案态能力状态 */ moduleCatalog?: ModuleCatalog | null; /** 当前能力提示的可读内容与版本;存在时启用写前读取门禁 */ moduleReadIndex?: ReadonlyMap; /** catalog 中 repeatable 能力的 artifact 基名(设计.生成规则 等) */ repeatableFamilies?: ReadonlySet | readonly string[]; /** 更早对话保留轮数;默认 2,可用 DICTATE_HISTORY_TURNS 覆盖 */ historyTurns?: number; driver?: AgentDriver; }): Promise { const driver = params.driver ?? createPlannedDictateDriver({ llm: params.llm, dialogue: params.dialogue, handlers: { listProducts: params.handlers.listProducts, readTag: params.handlers.readTag, writeTag: params.handlers.writeTag, deleteTag: params.handlers.deleteTag, prepareOpening: params.handlers.prepareOpening, lookupInsertFeedback: params.handlers.lookupInsertFeedback, onThinkingDelta: params.handlers.onThinkingDelta, onThinkingDone: params.handlers.onThinkingDone, }, moduleCatalog: params.moduleCatalog, moduleReadIndex: params.moduleReadIndex, moduleGuide: params.moduleGuide, recipeExamples: params.recipeExamples, recipeName: params.recipeName, recipeBrief: params.recipeBrief, systemPrompt: params.systemPrompt, historyTurns: params.historyTurns, }); const wroteTags: string[] = []; const deletedTags: string[] = []; let clearedDialogue = false; let openingPrepared = false; let dialogue = [...params.dialogue]; const repeatableFamilies = new Set( [...(params.repeatableFamilies ?? [])].map((t) => String(t).trim()).filter(Boolean), ); const systemPrompt = params.systemPrompt ?? (() => { const products = params.handlers.listProducts(); const plan = parseCreationPlan( products.find((product) => product.tag === CREATION_PLAN_TAG)?.content, ); return buildDictateSystemPrompt({ ...(plan ? { creationPlanState: formatCreationPlanProgress({ plan, catalog: params.moduleCatalog, products, }), } : { capabilityCatalog: formatCreationPlanCapabilityCatalog(params.moduleCatalog) || params.moduleGuide, recipeExamples: params.recipeExamples, recipeName: params.recipeName, recipeBrief: params.recipeBrief, }), }); })(); const assembled = buildDictateMessages({ systemPrompt, products: params.handlers.listProducts(), dialogue, }); const userBlock = assembled.filter((m) => m.role !== "system"); let toolSteps = 0; let run; try { run = await driver.run({ system: systemPrompt, messages: userBlock, tools: DICTATE_TOOL_DEFINITIONS, caller: "dictate_agent", label: "对话落盘", maxIterations: DICTATE_DRIVER_MAX_ITERATIONS, onThinkingDelta: params.handlers.onThinkingDelta, onThinkingDone: params.handlers.onThinkingDone, handleStep: (calls) => { toolSteps += 1; const moduleNamesThisStep: string[] = []; const results = calls.map((call) => { const args = safeParseArgs(call.arguments); if ( call.name === "declare_variable" || call.name === "declare_map" || call.name === "delete" || call.name === "undeclare_variable" || call.name === "remove_map" ) { openingPrepared = false; } if (call.name === "read_module") { const ref = String(args.module_id ?? "").trim(); const module = params.moduleReadIndex?.get(ref); if (!module) { const err = `未找到能力:${ref || "空"}`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } const state = parseDictateModuleReadState( params.handlers.readTag(DICTATE_MODULE_READ_STATE_TAG), ); state.modules[module.module_id] = module.version; state.active_module_id = module.module_id; params.handlers.writeTag( DICTATE_MODULE_READ_STATE_TAG, serializeDictateModuleReadState(state), ); params.handlers.onToolCall?.( call.name, `${module.module_id}@${module.version}`, ); const replyContract = module.module_id === "opening-setup" || module.module_id === "zero-layer-opening-setup" ? contractForOpeningTag( module.module_id === "zero-layer-opening-setup" ? "设计.0层开场白与初态" : "设计.开场白", params.handlers.listProducts(), ) : undefined; return { callId: call.id, content: JSON.stringify({ ok: true, ...module, ...(replyContract ? { reply_contract: replyContractSummary(replyContract) } : module.module_id === "opening-setup" || module.module_id === "zero-layer-opening-setup" ? { reply_contract_missing: "还没有可执行的正文组成数据契约。先写正文组成,再写开场。", } : {}), }), }; } if (call.name === "read_library_entry") { const state = parseDictateModuleReadState( params.handlers.readTag(DICTATE_MODULE_READ_STATE_TAG), ); const module = state.active_module_id ? params.moduleReadIndex?.get(state.active_module_id) : undefined; if (!module) { const err = "请先 read_module 读取要使用该库的能力"; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } const result = readLibraryEntries({ module, libraryId: String(args.library_id ?? ""), entryIds: Array.isArray(args.entry_ids) ? args.entry_ids.map(String) : [], reason: typeof args.reason === "string" ? args.reason : undefined, }); params.handlers.onToolCall?.( call.name, result.error ?? `${args.library_id}: ${result.entries?.length ?? 0}`, ); return { callId: call.id, content: JSON.stringify( result.error ? { error: result.error } : { ok: true, module_id: module.module_id, library_id: String(args.library_id ?? ""), entries: result.entries, }, ), }; } if (call.name === "prepare_opening") { params.handlers.prepareOpening?.(); openingPrepared = true; params.handlers.onToolCall?.( call.name, "信息可见范围 → 游玩执行方式 → 上下文排序 → 运行配置组装", ); return { callId: call.id, content: JSON.stringify({ ok: true, runtime_prepared: true, next: "现在生成并 insert 开场白", }), }; } if (call.name === "insert" || call.name === "write_product") { let tag = String(args.position ?? args.tag ?? "").trim(); const content = canonicalizeInsertedContent(String(args.content ?? "")); if (tag === VARIABLE_CATALOG_TAG || tag === VALUE_MAP_TAG) { const err = `请用 declare_variable / declare_map,不要 insert ${tag}`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } if (!isAllowedProductTag(tag)) { const err = `拒绝写入:position 必须以「用户.」或「设计.」开头(收到:${tag || "空"})`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } if (!content.trim()) { const err = "content 不能为空"; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } const targetModule = resolveModuleForInsertTag( tag, params.moduleCatalog, ); if ( targetModule && params.moduleReadIndex && requiresModuleRead(targetModule.id, targetModule.auto) ) { const current = params.moduleReadIndex.get(targetModule.id); const state = parseDictateModuleReadState( params.handlers.readTag(DICTATE_MODULE_READ_STATE_TAG), ); if (!current || state.modules[targetModule.id] !== current.version) { const err = `写入 ${tag} 前必须先 read_module("${targetModule.id}") 读取当前提示版本`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err, required_module_id: targetModule.id, required_version: current?.version, }), }; } } const currentModule = targetModule ? params.moduleReadIndex?.get(targetModule.id) : undefined; const selfScore = parseAndValidateSelfScore( args.self_score, currentModule?.score_dimensions ?? [], ); if ("error" in selfScore) { params.handlers.onToolCall?.(call.name, selfScore.error); return { callId: call.id, content: JSON.stringify({ error: selfScore.error }), }; } if (tag === CREATION_PLAN_TAG) { const validation = validateCreationPlan(content, params.moduleCatalog); if (!validation.ok) { const err = validation.error ?? "本局创作方案校验失败"; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err, missing_ids: validation.missingIds, duplicate_ids: validation.duplicateIds, unknown_ids: validation.unknownIds, }), }; } } if (repeatableFamilies.size) { const family = dictateProductFamily(tag); const pieces = targetModule?.id === "concrete-instances" || family === "设计.具体实例" ? splitConcreteInstanceContents(content) : [content]; const baseTag = pieces.length > 1 ? "设计.具体实例" : tag; const positions: string[] = []; const resolvedPieces: Array<{ tag: string; content: string }> = []; for (const piece of pieces) { const resolved = resolveRepeatableInsertTag({ tag: baseTag, content: piece, existingTags: [ ...params.handlers.listProducts().map((p) => p.tag), ...resolvedPieces.map((item) => item.tag), ], repeatableFamilies, }); if ("error" in resolved) { params.handlers.onToolCall?.(call.name, resolved.error); return { callId: call.id, content: JSON.stringify({ error: resolved.error }), }; } resolvedPieces.push({ tag: resolved.tag, content: piece }); } let replyModule: string | undefined; for (const [index, piece] of resolvedPieces.entries()) { const pieceTag = piece.tag; const order = parseDictateOrder(args.order); const productsBeforeWrite = params.handlers.listProducts(); const isOpening = isDictateOpeningTag(pieceTag); const contractMiss = openingContractMiss( pieceTag, piece.content, params.handlers.listProducts(), ); if (contractMiss) { params.handlers.onToolCall?.(call.name, contractMiss.error); return { callId: call.id, content: JSON.stringify(contractMiss), }; } const explicitlyPrepared = openingPrepared; if (isOpening && !explicitlyPrepared) { params.handlers.prepareOpening?.(); } params.handlers.writeProduct(pieceTag, piece.content, order, { ...(targetModule ? { layer: resolveInsertLayer({ moduleId: targetModule.id, catalogLayer: targetModule.layer, content: piece.content, }), } : {}), ...(selfScore.value ? { selfScore: selfScore.value } : {}), }); if (!isOpening) openingPrepared = false; wroteTags.push(pieceTag); positions.push(pieceTag); const detail = order === undefined ? pieceTag : `${pieceTag} order=${order}`; params.handlers.onToolCall?.(call.name, detail); replyModule = params.handlers.lookupInsertFeedback?.(pieceTag)?.trim() || replyModule; const requiredGaps = isDictateOpeningTag(pieceTag) ? requiredCreationPlanGaps({ planRaw: productsBeforeWrite.find( (product) => product.tag === CREATION_PLAN_TAG, )?.content, catalog: params.moduleCatalog, products: [ ...productsBeforeWrite.filter((product) => product.tag !== pieceTag), { tag: pieceTag, content: piece.content, order }, ], }) : []; if (index === resolvedPieces.length - 1) { if (replyModule) { const title = replyModule.match(/^#\s*(?:模块|本轮关注)\s*·\s*(.+)$/m)?.[1]?.trim() || pieceTag; moduleNamesThisStep.push(title); } return { callId: call.id, content: JSON.stringify({ ok: true, position: positions[0], ...(positions.length > 1 ? { positions, split: positions.length } : {}), ...(isOpening ? { runtime_prepared_before_opening: true, preparation_mode: explicitlyPrepared ? "prepare_opening" : "implicit_fallback", } : {}), ...(order !== undefined ? { order } : {}), ...(requiredGaps.length ? { warning: `仍有“必须”能力未落:${requiredGaps.join("、")}。开场白已写入,请在准备开玩前提醒用户补齐或调整方案。`, missing_required_module_ids: requiredGaps, } : {}), ...(replyModule ? { reply_module: replyModule, reply_hint: DICTATE_MULTI_MODULE_REPLY_HINT, } : {}), }), }; } } } const order = parseDictateOrder(args.order); const productsBeforeWrite = params.handlers.listProducts(); const isOpening = isDictateOpeningTag(tag); const contractMiss = openingContractMiss( tag, content, params.handlers.listProducts(), ); if (contractMiss) { params.handlers.onToolCall?.(call.name, contractMiss.error); return { callId: call.id, content: JSON.stringify(contractMiss), }; } const explicitlyPrepared = openingPrepared; if (isOpening && !explicitlyPrepared) { params.handlers.prepareOpening?.(); } params.handlers.writeProduct(tag, content, order, { ...(targetModule ? { layer: resolveInsertLayer({ moduleId: targetModule.id, catalogLayer: targetModule.layer, content, }), } : {}), ...(selfScore.value ? { selfScore: selfScore.value } : {}), }); if (tag === VARIABLE_DESIGN_TAG || tag.startsWith(`${VARIABLE_DESIGN_TAG}#`)) { const synced = syncDesignProductToTable(params.handlers, content); wroteTags.push(...synced); } if (!isOpening) openingPrepared = false; wroteTags.push(tag); const detail = order === undefined ? tag : `${tag} order=${order}`; params.handlers.onToolCall?.(call.name, detail); const replyModule = params.handlers.lookupInsertFeedback?.(tag)?.trim() || undefined; if (replyModule) { const title = replyModule.match(/^#\s*(?:模块|本轮关注)\s*·\s*(.+)$/m)?.[1]?.trim() || tag; moduleNamesThisStep.push(title); } const requiredGaps = isDictateOpeningTag(tag) ? requiredCreationPlanGaps({ planRaw: productsBeforeWrite.find( (product) => product.tag === CREATION_PLAN_TAG, )?.content, catalog: params.moduleCatalog, products: [ ...productsBeforeWrite.filter((product) => product.tag !== tag), { tag, content, order }, ], }) : []; return { callId: call.id, content: JSON.stringify({ ok: true, position: tag, ...(isOpening ? { runtime_prepared_before_opening: true, preparation_mode: explicitlyPrepared ? "prepare_opening" : "implicit_fallback", } : {}), ...(order !== undefined ? { order } : {}), ...(requiredGaps.length ? { warning: `仍有“必须”能力未落:${requiredGaps.join("、")}。开场白已写入,请在准备开玩前提醒用户补齐或调整方案。`, missing_required_module_ids: requiredGaps, } : {}), ...(replyModule ? { reply_module: replyModule, reply_hint: DICTATE_MULTI_MODULE_REPLY_HINT, } : {}), }), }; } if (call.name === "declare_variable") { const input: UpsertVariableInput = { key: String(args.key ?? "").trim(), type: args.type != null ? String(args.type) : undefined, initial: args.initial, user_visible: args.user_visible === undefined ? undefined : Boolean(args.user_visible), note: args.note != null ? String(args.note) : undefined, }; const current = parseVariableCatalog( params.handlers.readTag(VARIABLE_CATALOG_TAG), ); const { doc, error } = upsertVariableField(current, input); if (error) { params.handlers.onToolCall?.(call.name, error); return { callId: call.id, content: JSON.stringify({ error }) }; } params.handlers.writeTag( VARIABLE_CATALOG_TAG, serializeVariableCatalog(doc), ); wroteTags.push(VARIABLE_CATALOG_TAG); const seeded = seedCurrentFromCatalog(params.handlers); wroteTags.push(...seeded); const detail = `${input.key} visible=${doc.fields.find((f) => f.key === input.key)?.user_visible !== false}`; params.handlers.onToolCall?.(call.name, detail); return { callId: call.id, content: JSON.stringify({ ok: true, key: input.key, fields: doc.fields.length }), }; } if (call.name === "declare_map") { const input: UpsertMapInput = { id: String(args.id ?? "").trim(), field: String(args.field ?? "").trim(), target_tag: String(args.target_tag ?? "").trim(), bands: args.bands, note: args.note != null ? String(args.note) : undefined, }; const current = parseValueMapDoc( params.handlers.readTag(VALUE_MAP_TAG), ); const { doc, error } = upsertValueMapEntry(current, input); if (error) { params.handlers.onToolCall?.(call.name, error); return { callId: call.id, content: JSON.stringify({ error }) }; } params.handlers.writeTag(VALUE_MAP_TAG, serializeValueMapDoc(doc)); wroteTags.push(VALUE_MAP_TAG); const detail = `${input.id} ${input.field}→${input.target_tag}`; params.handlers.onToolCall?.(call.name, detail); return { callId: call.id, content: JSON.stringify({ ok: true, id: input.id, target_tag: input.target_tag, maps: doc.maps.length, }), }; } if (call.name === "delete") { const tag = String(args.position ?? args.tag ?? "").trim(); const reason = String(args.reason ?? "").trim(); if (!isAllowedProductTag(tag)) { const err = `拒绝删除:position 必须以「用户.」或「设计.」开头(收到:${tag || "空"})`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } const existed = params.handlers.deleteProduct(tag); if (!existed) { const err = `无产物 ${tag}`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } deletedTags.push(tag); const detail = reason ? `${tag}(${reason})` : tag; params.handlers.onToolCall?.(call.name, detail); return { callId: call.id, content: JSON.stringify({ ok: true, deleted: tag }), }; } if (call.name === "undeclare_variable") { const key = String(args.key ?? "").trim(); const current = parseVariableCatalog( params.handlers.readTag(VARIABLE_CATALOG_TAG), ); const { doc, error } = removeVariableField(current, key); if (error) { params.handlers.onToolCall?.(call.name, error); return { callId: call.id, content: JSON.stringify({ error }) }; } if (doc.fields.length === 0) { params.handlers.deleteTag(VARIABLE_CATALOG_TAG); } else { params.handlers.writeTag( VARIABLE_CATALOG_TAG, serializeVariableCatalog(doc), ); } // 同步从 变量.当前 / 运行.初始变量 去掉该格 for (const tableTag of ["变量.当前", "运行.初始变量"] as const) { const raw = params.handlers.readTag(tableTag); const table = parseTableDoc(raw); if (!table?.rows.some((r) => r.key === key)) continue; const next = { rows: table.rows.filter((r) => r.key !== key), }; if (next.rows.length === 0) { params.handlers.deleteTag(tableTag); } else { params.handlers.writeTag(tableTag, stringifyTableDoc(next)); } } deletedTags.push(`${VARIABLE_CATALOG_TAG}#${key}`); params.handlers.onToolCall?.(call.name, key); return { callId: call.id, content: JSON.stringify({ ok: true, key, fields: doc.fields.length, }), }; } if (call.name === "remove_map") { const id = String(args.id ?? "").trim(); const current = parseValueMapDoc( params.handlers.readTag(VALUE_MAP_TAG), ); const { doc, error, removed } = removeValueMapEntry(current, id); if (error || !removed) { const err = error ?? `映射无 id ${id}`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; } if (doc.maps.length === 0) { params.handlers.deleteTag(VALUE_MAP_TAG); } else { params.handlers.writeTag(VALUE_MAP_TAG, serializeValueMapDoc(doc)); } const stillUsed = doc.maps.some( (m) => m.target_tag === removed.target_tag, ); if (!stillUsed) { params.handlers.deleteTag(removed.target_tag); } deletedTags.push(`${VALUE_MAP_TAG}#${id}`); params.handlers.onToolCall?.( call.name, `${id} → ${removed.target_tag}`, ); return { callId: call.id, content: JSON.stringify({ ok: true, id, cleared_target: stillUsed ? null : removed.target_tag, maps: doc.maps.length, }), }; } if (call.name === "clear_dialogue") { const reason = String(args.reason ?? "").trim() || "上下文过长"; params.handlers.clearDialogue(); clearedDialogue = true; dialogue = []; params.handlers.onToolCall?.(call.name, reason); return { callId: call.id, content: JSON.stringify({ ok: true, cleared: true, note: "对话已清空;产物与变量/映射仍在。", }), }; } const err = `未知工具:${call.name}`; params.handlers.onToolCall?.(call.name, err); return { callId: call.id, content: JSON.stringify({ error: err }) }; }); if (moduleNamesThisStep.length > 1) { const roster = moduleNamesThisStep.join("、"); for (const r of results) { try { const parsed = JSON.parse(r.content) as Record; if (typeof parsed.reply_module === "string") { parsed.reply_hint = `${DICTATE_MULTI_MODULE_REPLY_HINT} 本步已带回模块:${roster}。`; r.content = JSON.stringify(parsed); } } catch { /* ignore */ } } } if (toolSteps >= DICTATE_DRIVER_MAX_ITERATIONS - 2) { noteTurnLimit(results); } return { kind: "continue" as const, results }; }, }); } catch (err) { if (!(err instanceof DriverIterationLimitError)) throw err; return { reply: composeDictateReply({ wroteTags, deletedTags, clearedDialogue, capped: true, }), iterations: err.iterations, wroteTags: [...new Set(wroteTags)], deletedTags: [...new Set(deletedTags)], clearedDialogue, }; } return { reply: composeDictateReply({ stopText: run.stop.kind === "text" ? run.stop.content : "", wroteTags, deletedTags, clearedDialogue, }), iterations: run.iterations, wroteTags: [...new Set(wroteTags)], deletedTags: [...new Set(deletedTags)], clearedDialogue, }; } function noteTurnLimit( results: Array<{ callId: string; content: string }>, ): void { const last = results.at(-1); if (!last) return; try { const parsed = JSON.parse(last.content) as Record; parsed.turn_limit = DICTATE_TURN_LIMIT_HINT; last.content = JSON.stringify(parsed); } catch { last.content = `${last.content}\n${DICTATE_TURN_LIMIT_HINT}`; } } function composeDictateReply(params: { stopText?: string; wroteTags: string[]; deletedTags: string[]; clearedDialogue: boolean; capped?: boolean; }): string { let reply = params.stopText?.trim() ?? ""; if (!reply) { if (params.wroteTags.length || params.deletedTags.length) { const parts: string[] = []; if (params.wroteTags.length) { parts.push(`已写入:${[...new Set(params.wroteTags)].join("、")}`); } if (params.deletedTags.length) { parts.push(`已删除:${[...new Set(params.deletedTags)].join("、")}`); } reply = `${parts.join(";")}。还有要补充的吗?`; } else if (params.clearedDialogue) { reply = "对话已清空,产物都还在。可以继续说。"; } else if (params.capped) { reply = "这轮工具次数已用完,还没有新的产物。可以说要先补哪一块。"; } else { reply = "我在听,请继续说你想要的体验或设定。"; } } if (params.capped && !reply.includes("工具次数已用完")) { reply = `${reply} 这轮工具次数已用完,其余下一轮继续。`; } return reply; } function safeParseArgs(raw: string): Record { try { const parsed = JSON.parse(raw || "{}"); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return {}; } return parsed as Record; } catch { return {}; } } function contractForOpeningTag( tag: string, products: DictateProduct[], ): ReplyDataContract | undefined { const want = tag.includes("0层") ? "设计.0层正文组成" : "设计.正文组成"; const raw = products.find((product) => product.tag === want)?.content ?? products.find((product) => product.tag === "设计.回复格式")?.content; return parseReplyDataContract(raw); } function openingContractMiss( tag: string, content: string, products: DictateProduct[], ): { error: string; reply_contract?: ReturnType } | undefined { if (!isDictateOpeningTag(tag)) return undefined; const contract = contractForOpeningTag(tag, products); if (!contract) return undefined; const error = openingContractError(content, contract); if (!error) return undefined; return { error, reply_contract: replyContractSummary(contract) }; } function requiresModuleRead(moduleId: string, auto?: boolean): boolean { if (auto) return false; return !new Set([ "narrative", "variable-context", "context-order", "worker-spec", "refine", ]).has(moduleId); } function resolveInsertLayer(params: { moduleId: string; catalogLayer?: "intermediate" | "final"; content: string; }): "intermediate" | "final" { if (params.moduleId === "generation-rules") { const lifecycle = normalizeGenerationRuleLifecycle( extractGenerationRuleLifecycle(params.content), ); if (lifecycle === "seed_only") return "intermediate"; if (lifecycle === "runtime_only" || lifecycle === "seed_and_runtime") { return "final"; } } return params.catalogLayer ?? "final"; } function extractGenerationRuleLifecycle( content: string, ): string | undefined { try { const parsed = JSON.parse(content) as { 正文?: { 生命周期?: unknown }; 生命周期?: unknown; }; const raw = parsed.正文?.生命周期 ?? parsed.生命周期; return typeof raw === "string" ? raw.trim() : undefined; } catch { return undefined; } } function normalizeGenerationRuleLifecycle( raw: string | undefined, ): string | undefined { if (!raw) return undefined; if (raw === "只预生成" || raw === "seed_only") return "seed_only"; if (raw === "仅游玩期" || raw === "runtime_only") return "runtime_only"; if (raw === "预生成并游玩期" || raw === "seed_and_runtime") { return "seed_and_runtime"; } return raw; } function parseAndValidateSelfScore( raw: unknown, expectedNames: readonly string[], ): { value?: DictateSelfScore } | { error: string } { if (expectedNames.length === 0) return {}; if (!raw || typeof raw !== "object" || Array.isArray(raw)) { return { error: `self_score 必填;维度:${expectedNames.join("、")}` }; } const dims = (raw as { dims?: unknown }).dims; if (!Array.isArray(dims)) { return { error: "self_score.dims 必须是数组" }; } const parsed: DictateSelfScore["dims"] = []; for (const item of dims) { if (!item || typeof item !== "object" || Array.isArray(item)) { return { error: "self_score.dims 含无效条目" }; } const row = item as Record; const name = typeof row.name === "string" ? row.name.trim() : ""; const score = Number(row.score); const gap = typeof row.gap === "string" ? row.gap.trim() : ""; const options = Array.isArray(row.options) ? row.options.map(String).map((option) => option.trim()).filter(Boolean) : []; if (!name || !Number.isInteger(score) || score < 1 || score > 5) { return { error: "self_score 每项须含合法 name 与 1~5 整数 score" }; } if (score <= 3 && !gap) { return { error: `self_score「${name}」不高于 3 分时必须写 gap` }; } if (options.length > 0 && (options.length < 2 || options.length > 3)) { return { error: `self_score「${name}」的 options 必须为 2~3 项` }; } parsed.push({ name, score, ...(gap ? { gap } : {}), ...(options.length ? { options } : {}), }); } const actual = parsed.map((dim) => dim.name); if ( actual.length !== expectedNames.length || expectedNames.some((name) => !actual.includes(name)) || actual.some((name) => !expectedNames.includes(name)) ) { return { error: `self_score 维度必须与能力 score 一致:${expectedNames.join("、")}`, }; } return { value: { dims: parsed } }; }