增强进料配方注入,并拆分转述侧创作与跑团体验配方。
让文本生成器与数值跑团各走清晰进料路径,开玩后槽位与分工自动对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,9 +3,15 @@
|
||||
# 选定后写入黑板 tag 创作.选用配方;转述模式不种节点 DAG
|
||||
|
||||
recipes:
|
||||
- id: 数据化跑团体验
|
||||
name: 数据化跑团体验
|
||||
declaration: 回合互动跑团;跨轮数值真值 + 分档映射投影(带数值)
|
||||
- id: 文本生成器
|
||||
name: 文本生成器
|
||||
declaration: 钉需求与文风;无世界推演。正文格式与开场白另步。
|
||||
declaration: 创作收范例与模仿面;开玩后主世界出正文、转述出文风/壳
|
||||
- id: 交互式长文生成器
|
||||
name: 交互式长文生成器
|
||||
declaration: 多轮钉需求与文风;连贯长文/章段(无数值跑团)
|
||||
- id: 正文组成
|
||||
name: 正文组成
|
||||
declaration: 钉呈现格式(壳与分区);开场白按该格式生成
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# 交互式长文生成器 · Boss 直聘进料配方
|
||||
# 原「文本生成器」长文路径;多轮互动钉规格,再生成连贯长文。
|
||||
|
||||
when: >-
|
||||
用户要写较长、可分段推进的正文(章段、连载向、润色扩写);
|
||||
需要多轮确认需求/文风/结构;不要世界推演、不要跨轮数值状态机
|
||||
|
||||
core: >-
|
||||
把用户原话收成可生成长文的规格:需求、文风、结构与禁忌用 insert。
|
||||
默认无变量、无旁观、无主世界层。正文格式(设计.正文组成)与开场白按需另钉。
|
||||
|
||||
process:
|
||||
- 听清写什么、给谁看、气质与禁忌 → insert 用户.需求 / 设计.文风 等(order 偏小)。缺关键信息就问一句。
|
||||
- 需要版式 → insert 设计.正文组成;要开篇样例 → 按格式 insert 设计.开场白。
|
||||
- 不要搭跑团数值、舞台骨架、declare_variable。
|
||||
- 长章叙事落产物,不要只留在聊天确认段。
|
||||
|
||||
principles:
|
||||
- 正推长文交付规格,勿套数据化跑团。
|
||||
- 保留用户原意;规格只走 toolcall。
|
||||
- 格式与开场白分清:正文组成 ≠ 开场白。
|
||||
|
||||
brief: 交互式长文:多轮钉需求与文风 → 可分段生成
|
||||
|
||||
steps: []
|
||||
@@ -0,0 +1,28 @@
|
||||
# 数据化跑团体验 · Boss 直聘进料配方
|
||||
# 注入转述总助手「本局配方」;不种节点 DAG。
|
||||
|
||||
when: >-
|
||||
用户要回合制角色扮演 / 沉浸推演,且体验靠跨轮数值或阶段推进
|
||||
(好感、资源、章节、地点、任务阶段等);按值换态度、大纲或场景文案
|
||||
|
||||
core: >-
|
||||
把用户原话收成可开玩规格:设定类 insert;跨轮状态 declare_variable;
|
||||
阈值→文案 declare_map。作用对象是本局黑板;开玩后由主世界层读投影与真值写正文。
|
||||
默认只开主世界层,原文即终稿。
|
||||
|
||||
process:
|
||||
- 先钉站位、每轮看什么、体验核与禁忌 → insert 用户.需求 / 设计.美学 / 设计.世界设定 等(order 偏小)。缺一句关键承诺就问一句;勿问卷。
|
||||
- 必须跨轮记住的状态 → declare_variable(key、type、initial、user_visible)。只立承重字段;性格/态度长文不立成可写变量。
|
||||
- 有「到某值换一套文案」→ declare_map(field 对准真值;bands 写区间或精确值 + content;target_tag 用 上下文.* 或 大纲.*)。缺分档细节就问档位边界与每档要点,再落盘。
|
||||
- 需要版式 → insert 设计.正文组成;要第一幕 → 按该格式 insert 设计.开场白。用户本轮只谈数值则只做 D,不硬塞开场白。
|
||||
- 材料只够本配方就只做本配方;聊完一轮用自然语言短确认 + 建议下一刀补什么。
|
||||
|
||||
principles:
|
||||
- 承重才立真值;派生文案 = f(真值),只走映射投影。
|
||||
- 分档正文写在 declare_map 的 bands.content;缺则问清后再 declare,不要用 insert 把整张分档表塞进设定散文。
|
||||
- 默认无独立叙事转述、无旁观;用户点名要文风层或程序盯表时再议。
|
||||
- 保留用户原意;规格只走 toolcall,聊天只确认与建议。
|
||||
|
||||
brief: 数据化跑团:设定 + 真值 + 分档映射 → 可开玩
|
||||
|
||||
steps: []
|
||||
@@ -1,25 +1,31 @@
|
||||
# 文本生成器 · 转述进料配方
|
||||
# 钉体验与设定;不写开场叙事本身。正文组成(格式)与开场白(按格式生成)见另配方或同提示分阶段。
|
||||
# 文本生成器 · Boss 直聘进料配方
|
||||
# 创作期写出游玩要用的固定上下文;成文在游玩:主世界出正文,转述出文风/壳。
|
||||
|
||||
when: >-
|
||||
按用户需求钉清要写什么、给谁看、气质与禁忌;
|
||||
不要世界推演、不要跨轮状态机、不要把聊天确认段当成开场白
|
||||
用户要短篇/短文批量,且要强模仿给定范例;
|
||||
篇与篇不必剧情承接;成文走游玩管线(主世界 + 叙事转述)
|
||||
|
||||
core: >-
|
||||
产物落需求与文风类上下文;默认无变量、无旁观、无主世界层。
|
||||
「正文」指展示格式(设计.正文组成),不是开场白;开场白按格式另生成。
|
||||
创作进料必须 insert 齐游玩上下文:模仿范例、模仿要点、叙事指南(大纲扩写)、可选轻量需求。
|
||||
落档只挂载这些创作期产物,不另造用法。开玩后主世界写故事正文(Markdown),转述按范例改写/镶壳。
|
||||
刚需开转述;用户输入用法以创作期叙事指南为准。
|
||||
|
||||
process:
|
||||
- 听用户说清要写什么与交付形态;写入需求/文风等设定产物。
|
||||
- 缺关键信息就问;不要问卷式盘问。
|
||||
- 不要去搭世界模拟、变量、舞台骨架。
|
||||
- 不要把整章叙事只留在聊天确认段里。
|
||||
- 要齐:① insert 设计.模仿范例;② insert 设计.模仿要点(用户点名要学的面)。缺则问这两项。
|
||||
- 必 insert 设计.叙事指南与故事推进(创作期产物,开玩直接挂载)。正文至少写清:
|
||||
「用户输入用法:大纲扩写。本轮输入=短文大纲/场面要点;一次成完整篇并收束。禁止扮演停笔、禁止等用户行动。主世界交故事正文;有转述时转述负责文风与呈现,可补残稿但不改剧情。」
|
||||
- 可选 insert 用户.需求(题材/禁忌/篇幅)。
|
||||
- 齐料清单缺叙事指南 / 范例 / 要点任一 → 先补 insert,不要说可以开玩。
|
||||
- 材料齐了:告知「可以落档/开玩」;禁止在创作期写成稿短文。
|
||||
- 开玩:用户本轮=本篇大纲;读创作期已挂上下文推进。对话一长 clear_dialogue,留产物。
|
||||
|
||||
principles:
|
||||
- 正推文本交付规格,勿套世界模拟全套。
|
||||
- 保留用户原意,少二次创作。
|
||||
- 格式与开场白分清:正文组成≠开场白。
|
||||
- 游玩只消费创作期产物;用法/文风/模仿面都在 insert 里,不靠落档程序补种。
|
||||
- 创作≠生产:创作收上下文;生产在游玩。
|
||||
- 分工:主世界 = 故事正文;转述 = 文风/壳(对照范例+要点;可补残稿)。
|
||||
- 输入用法由创作期叙事指南钉死为大纲扩写。
|
||||
- 一篇一世界;少问约束;无 declare_variable。
|
||||
|
||||
brief: 文本生成:钉需求与文风(正文格式 / 开场白另步)
|
||||
brief: 文本生成:创作期钉范例+要点+大纲扩写指南 → 开玩挂载成文
|
||||
|
||||
steps: []
|
||||
|
||||
@@ -27,6 +27,10 @@ import {
|
||||
type PlaySlotsConfig,
|
||||
} from "../skills/play-slots.js";
|
||||
import { parseWorkerSetYaml } from "../skills/worker-set-parse.js";
|
||||
import {
|
||||
CREATION_SELECTED_RECIPE_TAG,
|
||||
parseSelectedRecipeRef,
|
||||
} from "../skills/creation-flow.js";
|
||||
import {
|
||||
VARIABLE_CATALOG_TAG,
|
||||
} from "../skills/variable-catalog.js";
|
||||
@@ -48,12 +52,17 @@ const SKIP_BIND_TAGS = new Set([
|
||||
"设计.worker集.草稿",
|
||||
CONTEXT_ORDER_TAG,
|
||||
"创作.进料模式",
|
||||
CREATION_SELECTED_RECIPE_TAG,
|
||||
VARIABLE_CATALOG_TAG,
|
||||
VALUE_MAP_TAG,
|
||||
]);
|
||||
|
||||
/** 偏文风/呈现:有叙事转述时挂转述;否则仍挂主世界层 */
|
||||
const STYLE_TAG_RE = /叙事指南|文风|美学|纲领|禁忌|示例|正文组成|回复格式|监控栏|篇幅|结构/;
|
||||
const STYLE_TAG_RE =
|
||||
/叙事指南|文风|美学|纲领|禁忌|示例|模仿|正文组成|回复格式|监控栏|篇幅|结构/;
|
||||
|
||||
/** 这些配方开玩时刚需叙事转述(主世界出内容、转述出文风) */
|
||||
const RECIPES_REQUIRE_NARRATOR = new Set(["文本生成器"]);
|
||||
|
||||
export type DictatePlayBindResult = {
|
||||
workerSetJson: string;
|
||||
@@ -185,7 +194,7 @@ function buildNarratorInserts(products: DictateProduct[]): ContextOrderInsert[]
|
||||
if (!isStyleProductTag(p.tag)) continue;
|
||||
inserts.push(insert(o++, p.tag, "full", "文风/呈现参考"));
|
||||
}
|
||||
inserts.push(insert(o++, "运行.本轮.裁决", "full"));
|
||||
inserts.push(insert(o++, "输出.用户展示", "full"));
|
||||
return inserts;
|
||||
}
|
||||
|
||||
@@ -204,7 +213,7 @@ function buildAuditorInserts(params: {
|
||||
for (const tag of params.runtimeTags) {
|
||||
inserts.push(insert(o++, tag, tag === "变量.当前" ? "fields" : "full"));
|
||||
}
|
||||
inserts.push(insert(o++, "运行.本轮.裁决", "full"));
|
||||
inserts.push(insert(o++, "输出.用户展示", "full"));
|
||||
inserts.push(insert(o++, "用户.最新输入", "full"));
|
||||
return inserts;
|
||||
}
|
||||
@@ -261,7 +270,7 @@ export function buildDictateContextOrder(params: {
|
||||
inserts: [
|
||||
insert(0, WORKER_PERSONA_REF, "fixed", "槽位人设"),
|
||||
insert(1, "用户.最新输入", "full"),
|
||||
insert(2, "运行.本轮.裁决", "full"),
|
||||
insert(2, "输出.用户展示", "full"),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -275,15 +284,27 @@ export function buildDictateContextOrder(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function resolvePlaySlots(existingRaw: string | undefined): PlaySlotsConfig {
|
||||
function resolvePlaySlots(
|
||||
existingRaw: string | undefined,
|
||||
board: Blackboard,
|
||||
): PlaySlotsConfig {
|
||||
const parsed = existingRaw?.trim()
|
||||
? parseWorkerSetYaml(existingRaw)
|
||||
: null;
|
||||
return parsed?.play_slots ?? defaultPlaySlots();
|
||||
const slots = { ...(parsed?.play_slots ?? defaultPlaySlots()) };
|
||||
const recipeRef = parseSelectedRecipeRef(
|
||||
board.getContentByTag(CREATION_SELECTED_RECIPE_TAG),
|
||||
);
|
||||
if (recipeRef && RECIPES_REQUIRE_NARRATOR.has(recipeRef)) {
|
||||
slots.gm = true;
|
||||
slots.narrator = true;
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成/更新 设计.worker集:保留已有 play_slots,重写 context_order 为产物挂载表。
|
||||
* 文本生成器等配方会强制打开叙事转述。
|
||||
*/
|
||||
export function bindDictateProductsToPlaySpec(board: Blackboard): DictatePlayBindResult {
|
||||
const products = collectDictateBindProducts(board);
|
||||
@@ -291,7 +312,7 @@ export function bindDictateProductsToPlaySpec(board: Blackboard): DictatePlayBin
|
||||
board.getContentByTag(WORKER_SET_FINAL_TAG)?.trim() ||
|
||||
board.getContentByTag(WORKER_SET_DRAFT_TAG)?.trim() ||
|
||||
"";
|
||||
const playSlots = resolvePlaySlots(existing || undefined);
|
||||
const playSlots = resolvePlaySlots(existing || undefined, board);
|
||||
const runtimeTags = collectRuntimeStateTags(board);
|
||||
const contextOrder = buildDictateContextOrder({
|
||||
products,
|
||||
@@ -320,7 +341,13 @@ export function bindDictateProductsToPlaySpec(board: Blackboard): DictatePlayBin
|
||||
} catch {
|
||||
row = { play_slots: playSlots, brief: "Boss直聘落档自动生成" };
|
||||
}
|
||||
if (!row.play_slots) row.play_slots = playSlots;
|
||||
row.play_slots = playSlots;
|
||||
if (playSlots.narrator) {
|
||||
row.brief =
|
||||
typeof row.brief === "string" && String(row.brief).includes("转述")
|
||||
? row.brief
|
||||
: "Boss直聘:主世界出内容,叙事转述出文风";
|
||||
}
|
||||
const withSlots = JSON.stringify(row, null, 2);
|
||||
const workerSetJson = mergeContextOrderIntoWorkerSetJson(withSlots, contextOrder);
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ export function buildDictateSystemPrompt(opts?: {
|
||||
? `
|
||||
## 本局配方
|
||||
名称:${opts.recipeName ?? "(未命名)"}
|
||||
${opts.recipeBrief ? `要点:${opts.recipeBrief}` : ""}
|
||||
${opts.recipeBrief ? `要点:\n${opts.recipeBrief}` : ""}
|
||||
按上述适用与原则整理产物;材料只够做本配方时,就只做本配方范围内的事。
|
||||
本配方若含跨轮数值:优先走 D(declare_variable / declare_map),不要只 insert 散文表。
|
||||
`
|
||||
: "";
|
||||
|
||||
@@ -45,6 +46,7 @@ ${opts.recipeBrief ? `要点:${opts.recipeBrief}` : ""}
|
||||
**分清:**
|
||||
- 正文组成(格式)→ insert \`设计.正文组成\`
|
||||
- 开场白(内容)→ insert \`设计.开场白\`;落档/开玩同步 \`输出.开场白\`
|
||||
- 多篇无关联短文(生产在游玩)→ 创作期钉 \`设计.模仿范例\` / \`设计.模仿要点\` / \`设计.叙事指南与故事推进\`(大纲扩写)
|
||||
- 好感分档性格等 → declare_variable + declare_map,不要 insert 一整张表进设定散文
|
||||
${recipeBlock}
|
||||
## 按用户本轮意图择一为主
|
||||
@@ -60,6 +62,9 @@ insert 设定类;order 偏小。回复:确认体验 + 建议补充。
|
||||
### D · 钉变量与映射
|
||||
用户提到跨轮状态、分档态度、章大纲切换时:declare_variable(含是否对用户可见)→ declare_map(分档正文)。可同轮多次调用。
|
||||
|
||||
### E · 文本生成器(创作收料 → 开玩挂载)
|
||||
必须用 insert 写齐游玩上下文:\`设计.模仿范例\`、\`设计.模仿要点\`、\`设计.叙事指南与故事推进\`(用户输入用法:大纲扩写;禁止扮演停笔)。缺任一就先补产物,不要说可以开玩。禁止在创作期写完整短文。开玩后程序只挂载这些产物:主世界出内容,转述出文风。
|
||||
|
||||
## 常用序感
|
||||
- 需求/美学等不变设定:靠前(负)
|
||||
- 正文组成:中间(0)
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ export type DictateTurnResult = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 跑一轮 Boss 直聘整理:上下文 = 预设提示 + 按相对序的产物 + 全量对话。
|
||||
* 跑一轮 Boss 直聘整理:任务 messages 由本模块拼;preset 夹心由 LLM 包装层统一装配。
|
||||
*/
|
||||
export async function runDictateTurn(params: {
|
||||
llm: LlmProvider;
|
||||
|
||||
@@ -38,8 +38,10 @@ export function defaultDictateOrder(tag: string): number {
|
||||
const t = tag.trim();
|
||||
if (t === "用户.需求") return -40;
|
||||
if (t === "设计.变量目录" || t === "设计.变量映射") return -25;
|
||||
if (/美学|纲领|禁忌|示例/.test(t)) return -30;
|
||||
if (t === "设计.模仿范例" || t === "设计.模仿要点") return -28;
|
||||
if (/美学|纲领|禁忌|示例|模仿/.test(t)) return -30;
|
||||
if (/文风|叙事指南|篇幅|结构/.test(t)) return -20;
|
||||
if (/短文集|短文\./.test(t)) return 10;
|
||||
if (/正文组成|回复格式/.test(t)) return 0;
|
||||
if (/开场白/.test(t)) return 20;
|
||||
return 0;
|
||||
|
||||
@@ -54,6 +54,8 @@ const SKILL_PACK_LABELS: Record<string, string> = {
|
||||
"expand-assistant": "扩写助手",
|
||||
文本生成器: "文本生成器",
|
||||
"text-generator": "文本生成器",
|
||||
交互式长文生成器: "交互式长文生成器",
|
||||
数据化跑团体验: "数据化跑团体验",
|
||||
};
|
||||
|
||||
/** 生命周期 / 相位 */
|
||||
|
||||
@@ -109,6 +109,7 @@ import {
|
||||
creationFlowFromRecipeSeed,
|
||||
findRecipeCatalogEntry,
|
||||
formatCreationFlowForUser,
|
||||
formatDictateRecipeBrief,
|
||||
findModuleByName,
|
||||
findStepByRef,
|
||||
type FlowStepTitleHint,
|
||||
@@ -682,14 +683,17 @@ export class SessionManager {
|
||||
s.runtime.getBlackboard(),
|
||||
s.recipeOptions,
|
||||
);
|
||||
const recipeBrief = await resolveDictateRecipeBriefForSession({
|
||||
skillName: s.runtime.getActiveSkill()?.name,
|
||||
recipeView,
|
||||
recipeOptions: s.recipeOptions,
|
||||
});
|
||||
|
||||
const result = await runDictateTurn({
|
||||
llm,
|
||||
dialogue,
|
||||
recipeName: recipeView?.name,
|
||||
recipeBrief: recipeView
|
||||
? [recipeView.declaration].filter(Boolean).join(";")
|
||||
: undefined,
|
||||
recipeBrief,
|
||||
handlers: {
|
||||
listProducts: () => this.listDictateProducts(s),
|
||||
writeProduct: (tag, content, order) => {
|
||||
@@ -3586,6 +3590,35 @@ function resolveSelectedRecipeView(
|
||||
return { id: ref, name: ref, declaration: "" };
|
||||
}
|
||||
|
||||
/** Boss 直聘:优先注入 recipe.yaml 方法论,缺则退回 catalog declaration */
|
||||
async function resolveDictateRecipeBriefForSession(params: {
|
||||
skillName?: string | null;
|
||||
recipeView: { id: string; name: string; declaration: string } | null;
|
||||
recipeOptions?: RecipeCatalogEntry[];
|
||||
}): Promise<string | undefined> {
|
||||
const view = params.recipeView;
|
||||
if (!view) return undefined;
|
||||
const entry = findRecipeCatalogEntry(
|
||||
params.recipeOptions?.length ? { recipes: params.recipeOptions } : null,
|
||||
view.id,
|
||||
);
|
||||
const skillName = params.skillName?.trim();
|
||||
if (entry && skillName) {
|
||||
try {
|
||||
const skill = await loadSkill(skillName);
|
||||
const packRoot = skill.skillPackRoot?.trim();
|
||||
if (packRoot) {
|
||||
const detail = await loadRecipeDetail(packRoot, entry);
|
||||
const brief = formatDictateRecipeBrief(detail);
|
||||
if (brief) return brief;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[会话] 加载转述配方要点失败", err);
|
||||
}
|
||||
}
|
||||
return view.declaration?.trim() || undefined;
|
||||
}
|
||||
|
||||
function buildWorkerSetUserView(
|
||||
blackboard: Blackboard,
|
||||
session?: RuntimeSession,
|
||||
|
||||
@@ -247,6 +247,8 @@ export type RecipeDetail = {
|
||||
id: string;
|
||||
name: string;
|
||||
declaration: string;
|
||||
/** 一句话方法摘要(recipe.yaml brief;转述进料优先注入) */
|
||||
brief?: string;
|
||||
/** 适用什么体验/任务 */
|
||||
when?: string;
|
||||
/** 整套设计方法的核心思路与最终目标 */
|
||||
@@ -1433,8 +1435,12 @@ export function parseRecipeYaml(
|
||||
? row.name.trim()
|
||||
: meta.name;
|
||||
|
||||
const brief =
|
||||
typeof row.brief === "string" && row.brief.trim()
|
||||
? row.brief.trim()
|
||||
: undefined;
|
||||
const seedParsed = parseCreationFlow(JSON.stringify({
|
||||
brief: typeof row.brief === "string" ? row.brief : undefined,
|
||||
brief,
|
||||
status: "open",
|
||||
steps: Array.isArray(row.steps) ? row.steps : [],
|
||||
}));
|
||||
@@ -1444,6 +1450,7 @@ export function parseRecipeYaml(
|
||||
id: meta.id,
|
||||
name,
|
||||
declaration: meta.declaration,
|
||||
...(brief ? { brief } : {}),
|
||||
when,
|
||||
...(core ? { core } : {}),
|
||||
...(process ? { process } : {}),
|
||||
@@ -1551,6 +1558,38 @@ export function formatRecipeCatalogForAgent(catalog: RecipeCatalog): string {
|
||||
return `【可选配方】(须由用户手动选择)\n${lines.join("\n")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boss 直聘 / 转述进料:把已选配方方法论收成短要点(进系统提示「本局配方」)。
|
||||
* 不含 DAG steps / design-flow 选型话术。
|
||||
*/
|
||||
export function formatDictateRecipeBrief(detail: RecipeDetail): string {
|
||||
const lines: string[] = [];
|
||||
if (detail.brief?.trim()) lines.push(detail.brief.trim());
|
||||
else if (detail.declaration?.trim()) lines.push(detail.declaration.trim());
|
||||
if (detail.when?.trim()) lines.push(`适用:${detail.when.trim()}`);
|
||||
if (detail.core?.trim()) {
|
||||
lines.push("核心:");
|
||||
lines.push(detail.core.trim());
|
||||
}
|
||||
if (detail.process) {
|
||||
lines.push("怎么做:");
|
||||
lines.push(formatRecipeFieldBlock(detail.process));
|
||||
}
|
||||
if (detail.principles) {
|
||||
lines.push("原则:");
|
||||
lines.push(formatRecipeFieldBlock(detail.principles));
|
||||
}
|
||||
if (
|
||||
!detail.core &&
|
||||
!detail.process &&
|
||||
!detail.principles &&
|
||||
detail.hint?.trim()
|
||||
) {
|
||||
lines.push(detail.hint.trim());
|
||||
}
|
||||
return lines.join("\n").trim();
|
||||
}
|
||||
|
||||
/** 注入 design-flow:用户已选配方(方法论 + 开局起点) */
|
||||
export function formatSelectedRecipeForAgent(detail: RecipeDetail): string {
|
||||
const lines: string[] = [
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
listCallableCatalogModules,
|
||||
formatModuleCatalogForAgent,
|
||||
formatRecipeCatalogForAgent,
|
||||
formatDictateRecipeBrief,
|
||||
formatSelectedRecipeForAgent,
|
||||
isCreationFlowComplete,
|
||||
loadAllRecipeDetails,
|
||||
@@ -516,12 +517,28 @@ describe("creation-flow", () => {
|
||||
);
|
||||
expect(dictate?.recipes.some((r) => r.id === "文本生成器")).toBe(true);
|
||||
expect(dictate?.recipes.some((r) => r.name === "文本生成器")).toBe(true);
|
||||
expect(dictate?.recipes.some((r) => r.name === "世界模拟器")).toBe(false);
|
||||
expect(dictate?.recipes.some((r) => r.name === "交互式长文生成器")).toBe(true);
|
||||
expect(dictate?.recipes.some((r) => r.name === "数据化跑团体验")).toBe(true);
|
||||
expect(dictate?.recipes[0]?.id).toBe("数据化跑团体验");
|
||||
const entry = dictate!.recipes.find((r) => r.id === "文本生成器")!;
|
||||
expect(entry.family).toBe("dictate");
|
||||
const detail = await loadRecipeDetail("dialogue/world-simulator", entry);
|
||||
expect(detail.core).toBeTruthy();
|
||||
expect(detail.core).toMatch(/范例|模仿|转述|游玩/);
|
||||
expect(detail.seed).toBeNull();
|
||||
expect(formatDictateRecipeBrief(detail)).toMatch(/模仿|范例|转述|开玩/);
|
||||
expect(formatDictateRecipeBrief(detail)).toMatch(/模仿要点|学哪些面|要点|文风/);
|
||||
|
||||
const longform = dictate!.recipes.find((r) => r.id === "交互式长文生成器")!;
|
||||
const longDetail = await loadRecipeDetail("dialogue/world-simulator", longform);
|
||||
expect(longDetail.brief).toMatch(/长文/);
|
||||
expect(longDetail.core).toMatch(/长文|分段/);
|
||||
|
||||
const rpg = dictate!.recipes.find((r) => r.id === "数据化跑团体验")!;
|
||||
const rpgDetail = await loadRecipeDetail("dialogue/world-simulator", rpg);
|
||||
expect(rpgDetail.brief).toMatch(/数据化跑团|真值|映射/);
|
||||
expect(rpgDetail.core).toMatch(/真值|declare_variable|数值/);
|
||||
expect(formatDictateRecipeBrief(rpgDetail)).toContain("declare_variable");
|
||||
expect(formatDictateRecipeBrief(rpgDetail)).toContain("declare_map");
|
||||
});
|
||||
|
||||
it("parses selected recipe ref", () => {
|
||||
@@ -619,7 +636,7 @@ recipes:
|
||||
name: 缺声明
|
||||
`);
|
||||
expect(cat?.recipes).toEqual([
|
||||
{ id: "ok", name: "好配方", declaration: "有声明" },
|
||||
{ id: "ok", name: "好配方", declaration: "有声明", family: "recipe" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ describe("dictate play-bind", () => {
|
||||
it("classifies style tags", () => {
|
||||
expect(isDictateStyleBindTag("设计.美学纲领")).toBe(true);
|
||||
expect(isDictateStyleBindTag("设计.正文组成")).toBe(true);
|
||||
expect(isDictateStyleBindTag("设计.模仿范例")).toBe(true);
|
||||
expect(isDictateStyleBindTag("设计.模仿要点")).toBe(true);
|
||||
expect(isDictateStyleBindTag("用户.需求")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -77,13 +79,68 @@ describe("dictate play-bind", () => {
|
||||
const narrRefs = narr!.inserts.map((i) => i.ref);
|
||||
expect(narrRefs).toContain("设计.美学纲领");
|
||||
expect(narrRefs).not.toContain("用户.需求");
|
||||
expect(narrRefs).toContain("运行.本轮.裁决");
|
||||
expect(narrRefs).toContain("输出.用户展示");
|
||||
expect(narrRefs).not.toContain("运行.本轮.裁决");
|
||||
|
||||
const gm = result.contextOrder.slots.find((s) => s.ref === "world-simulator");
|
||||
expect(gm!.inserts.map((i) => i.ref)).toContain("用户.需求");
|
||||
expect(result.workerSetJson).toContain("context_order");
|
||||
});
|
||||
|
||||
it("forces narrator on for 文本生成器 and mounts creation-period style context", () => {
|
||||
const board = new Blackboard();
|
||||
board.write({
|
||||
tag: "创作.选用配方",
|
||||
content: JSON.stringify({ id: "文本生成器", name: "文本生成器" }),
|
||||
source: "user",
|
||||
});
|
||||
board.write({
|
||||
tag: "设计.模仿范例",
|
||||
content: "范例正文",
|
||||
source: "dictate",
|
||||
metadata: { [DICTATE_ORDER_META_KEY]: -28 },
|
||||
});
|
||||
board.write({
|
||||
tag: "设计.模仿要点",
|
||||
content: "- 句式节奏\n- 感官密度",
|
||||
source: "dictate",
|
||||
metadata: { [DICTATE_ORDER_META_KEY]: -28 },
|
||||
});
|
||||
board.write({
|
||||
tag: "设计.叙事指南与故事推进",
|
||||
content: "用户输入用法:大纲扩写。禁止扮演停笔。",
|
||||
source: "dictate",
|
||||
metadata: { [DICTATE_ORDER_META_KEY]: -22 },
|
||||
});
|
||||
board.write({
|
||||
tag: "用户.需求",
|
||||
content: "短篇",
|
||||
source: "dictate",
|
||||
});
|
||||
board.write({
|
||||
tag: "设计.worker集",
|
||||
content: JSON.stringify({
|
||||
play_slots: { gm: true, narrator: false, auditor: false, perspective: false },
|
||||
}),
|
||||
source: "test",
|
||||
});
|
||||
|
||||
const result = bindDictateProductsToPlaySpec(board);
|
||||
expect(result.contextOrder.play_slots?.narrator).toBe(true);
|
||||
const narr = result.contextOrder.slots.find((s) => s.ref === "narrator");
|
||||
expect(narr).toBeTruthy();
|
||||
const narrRefs = narr!.inserts.map((i) => i.ref);
|
||||
expect(narrRefs).toContain("设计.模仿范例");
|
||||
expect(narrRefs).toContain("设计.模仿要点");
|
||||
expect(narrRefs).toContain("设计.叙事指南与故事推进");
|
||||
expect(narrRefs).not.toContain("用户.需求");
|
||||
expect(JSON.parse(result.workerSetJson).play_slots.narrator).toBe(true);
|
||||
// 落档不改写创作期叙事指南
|
||||
expect(board.getContentByTag("设计.叙事指南与故事推进")).toBe(
|
||||
"用户输入用法:大纲扩写。禁止扮演停笔。",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not invent variable mappings; binds declared map slots and catalog", () => {
|
||||
const board = new Blackboard();
|
||||
board.write({ tag: "用户.需求", content: "x", source: "dictate" });
|
||||
|
||||
@@ -64,8 +64,8 @@ describe("dictate context", () => {
|
||||
|
||||
it("system prompt requires insert toolcall and relative order", () => {
|
||||
const prompt = buildDictateSystemPrompt({
|
||||
recipeName: "正文组成",
|
||||
recipeBrief: "钉呈现壳与可见块",
|
||||
recipeName: "数据化跑团体验",
|
||||
recipeBrief: "设定 + 真值 + 分档映射\n怎么做:\n- declare_variable\n- declare_map",
|
||||
});
|
||||
expect(prompt).toContain("设计.正文组成");
|
||||
expect(prompt).toContain("设计.开场白");
|
||||
@@ -73,8 +73,15 @@ describe("dictate context", () => {
|
||||
expect(prompt).toContain("insert");
|
||||
expect(prompt).toContain("declare_variable");
|
||||
expect(prompt).toContain("declare_map");
|
||||
expect(prompt).toContain("数据化跑团体验");
|
||||
expect(prompt).toMatch(/order/);
|
||||
expect(prompt).toContain("仅允许 toolcall");
|
||||
expect(prompt).toContain("设计.模仿范例");
|
||||
expect(prompt).toContain("设计.模仿要点");
|
||||
expect(prompt).toContain("设计.叙事指南与故事推进");
|
||||
expect(prompt).toContain("大纲扩写");
|
||||
expect(prompt).toContain("禁止扮演停笔");
|
||||
expect(prompt).toContain("只挂载这些产物");
|
||||
});
|
||||
|
||||
it("sorts products by relative order: neg then 0 then pos", () => {
|
||||
|
||||
@@ -59,6 +59,8 @@ export function displaySkillPackLabel(id) {
|
||||
"expand-assistant": "扩写助手",
|
||||
文本生成器: "文本生成器",
|
||||
"text-generator": "文本生成器",
|
||||
交互式长文生成器: "交互式长文生成器",
|
||||
数据化跑团体验: "数据化跑团体验",
|
||||
};
|
||||
return map[id] ?? id;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user