重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
import type { Blackboard } from "../blackboard/blackboard.js";
|
||||
|
||||
/** 下一 worker / 总管可读的定稿摘要 tag */
|
||||
export const CONTEXT_BRIEF_TAG = "上下文.定稿摘要";
|
||||
|
||||
export type CompressWorkerResult = {
|
||||
archivedTags: string[];
|
||||
finals: Array<{ tag: string; chars: number; preview: string }>;
|
||||
briefText: string;
|
||||
};
|
||||
|
||||
const ALWAYS_ARCHIVE_AFTER_ACCEPT = ["用户.worker答复", "用户.修订说明"];
|
||||
|
||||
/**
|
||||
* Worker 产物经用户验收(或自动完工)后:
|
||||
* - 终产物打 final
|
||||
* - 过程/草稿 tag 归档(不再进入后续 worker 取数)
|
||||
* - 写入定稿摘要,供下一阶段「指点」用
|
||||
*/
|
||||
export function compressAfterWorkerAccept(params: {
|
||||
blackboard: Blackboard;
|
||||
workerId: string;
|
||||
outputTags: string[];
|
||||
summary?: string;
|
||||
/**
|
||||
* final = 终稿验收(归档草稿、写定稿摘要)
|
||||
* unit = 创作单位验收(只归档过程答复,保留 设计.worker集.草稿)
|
||||
*/
|
||||
mode?: "final" | "unit";
|
||||
}): CompressWorkerResult {
|
||||
const { blackboard, workerId, outputTags, summary } = params;
|
||||
const mode = params.mode ?? "final";
|
||||
const finals: CompressWorkerResult["finals"] = [];
|
||||
const archivedTags: string[] = [];
|
||||
|
||||
const uniqueOutputs = [...new Set(outputTags.map((t) => t.trim()).filter(Boolean))];
|
||||
|
||||
if (mode === "final") {
|
||||
for (const tag of uniqueOutputs) {
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
blackboard.write({
|
||||
tag,
|
||||
content,
|
||||
source: workerId,
|
||||
metadata: {
|
||||
role: "final",
|
||||
acceptedAt: new Date().toISOString(),
|
||||
workerId,
|
||||
},
|
||||
});
|
||||
finals.push({
|
||||
tag,
|
||||
chars: content.length,
|
||||
preview: previewText(content, 120),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 单位验收:草稿保持活跃,终产物列表仅作面板提示
|
||||
for (const tag of uniqueOutputs) {
|
||||
if (tag === "用户.需求" || tag === "创作.当前单位") continue;
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
finals.push({
|
||||
tag,
|
||||
chars: content.length,
|
||||
preview: previewText(content, 120),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const toArchive = new Set<string>(ALWAYS_ARCHIVE_AFTER_ACCEPT);
|
||||
|
||||
if (mode === "final") {
|
||||
if (blackboard.getContentByTag("设计.worker集")?.trim()) {
|
||||
toArchive.add("设计.worker集.草稿");
|
||||
}
|
||||
for (const tag of uniqueOutputs) {
|
||||
if (tag.endsWith(".草稿")) continue;
|
||||
const draft = `${tag}.草稿`;
|
||||
if (blackboard.getContentByTag(draft) != null) toArchive.add(draft);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tag of toArchive) {
|
||||
if (uniqueOutputs.includes(tag) && mode === "final") continue;
|
||||
if (mode === "unit" && tag.endsWith(".草稿")) continue;
|
||||
const content = blackboard.getContentByTag(tag);
|
||||
if (content == null) continue;
|
||||
blackboard.write({
|
||||
tag,
|
||||
content: `(已压缩归档)原过程内容已折叠。定稿见:${
|
||||
finals.map((f) => f.tag).join("、") || "(无)"
|
||||
}\n\n---\n${previewText(content, 400)}`,
|
||||
source: "compress",
|
||||
metadata: {
|
||||
role: "archived",
|
||||
archivedAt: new Date().toISOString(),
|
||||
fromWorker: workerId,
|
||||
compressMode: mode,
|
||||
},
|
||||
});
|
||||
archivedTags.push(tag);
|
||||
}
|
||||
|
||||
const briefText =
|
||||
mode === "unit"
|
||||
? buildUnitBriefText({ workerId, summary, finals, archivedTags })
|
||||
: buildBriefText({
|
||||
workerId,
|
||||
summary,
|
||||
finals,
|
||||
archivedTags,
|
||||
});
|
||||
|
||||
if (mode === "final") {
|
||||
blackboard.write({
|
||||
tag: CONTEXT_BRIEF_TAG,
|
||||
content: briefText,
|
||||
source: "compress",
|
||||
metadata: { role: "final", workerId },
|
||||
});
|
||||
}
|
||||
|
||||
return { archivedTags, finals, briefText };
|
||||
}
|
||||
|
||||
function buildUnitBriefText(params: {
|
||||
workerId: string;
|
||||
summary?: string;
|
||||
finals: CompressWorkerResult["finals"];
|
||||
archivedTags: string[];
|
||||
}): string {
|
||||
return [
|
||||
`## 创作单位已验收(${params.workerId})`,
|
||||
"",
|
||||
params.summary?.trim() ? `摘要:${params.summary.trim()}` : "摘要:(无)",
|
||||
"",
|
||||
"草稿仍保留在 `设计.worker集.草稿`;下一单位继续增量,勿依赖已删的过程对话。",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function buildBriefText(params: {
|
||||
workerId: string;
|
||||
summary?: string;
|
||||
finals: CompressWorkerResult["finals"];
|
||||
archivedTags: string[];
|
||||
}): string {
|
||||
const lines = [
|
||||
`## 上一阶段定稿(${params.workerId})`,
|
||||
"",
|
||||
params.summary?.trim()
|
||||
? `摘要:${params.summary.trim()}`
|
||||
: "摘要:(无)",
|
||||
"",
|
||||
"### 保留的终产物 tag",
|
||||
];
|
||||
if (params.finals.length === 0) {
|
||||
lines.push("- (无)");
|
||||
} else {
|
||||
for (const f of params.finals) {
|
||||
lines.push(`- \`${f.tag}\`(${f.chars} 字)`);
|
||||
lines.push(` ${f.preview}`);
|
||||
}
|
||||
}
|
||||
if (params.archivedTags.length) {
|
||||
lines.push("", "### 已压缩的过程 tag");
|
||||
for (const t of params.archivedTags) {
|
||||
lines.push(`- \`${t}\`(归档,后续 worker 默认不读)`);
|
||||
}
|
||||
}
|
||||
lines.push(
|
||||
"",
|
||||
"下一 worker 应以以上终产物为准继续;勿依赖已归档的过程讨论。",
|
||||
);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
export function previewText(content: string, max: number): string {
|
||||
const t = content.replace(/\s+/g, " ").trim();
|
||||
if (t.length <= max) return t;
|
||||
return `${t.slice(0, max)}…`;
|
||||
}
|
||||
|
||||
/** 供 UI:黑板可读条目(默认隐藏 archived 全文,只给索引) */
|
||||
export function buildBoardPanel(blackboard: Blackboard): {
|
||||
finals: Array<{ tag: string; source: string; preview: string; updatedAt: string }>;
|
||||
active: Array<{ tag: string; source: string; preview: string; updatedAt: string }>;
|
||||
archivedCount: number;
|
||||
brief?: string;
|
||||
} {
|
||||
const items = latestItemsByTag(blackboard.exportItems());
|
||||
const finals: Array<{
|
||||
tag: string;
|
||||
source: string;
|
||||
preview: string;
|
||||
updatedAt: string;
|
||||
}> = [];
|
||||
const active: typeof finals = [];
|
||||
let archivedCount = 0;
|
||||
let brief: string | undefined;
|
||||
|
||||
for (const item of items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))) {
|
||||
const role = String(item.metadata?.role ?? "");
|
||||
if (item.tag === CONTEXT_BRIEF_TAG) {
|
||||
brief = item.content;
|
||||
continue;
|
||||
}
|
||||
if (role === "archived") {
|
||||
archivedCount += 1;
|
||||
continue;
|
||||
}
|
||||
const row = {
|
||||
tag: item.tag,
|
||||
source: item.source,
|
||||
preview: previewText(item.content, 160),
|
||||
updatedAt: item.updatedAt,
|
||||
};
|
||||
if (role === "final") finals.push(row);
|
||||
else active.push(row);
|
||||
}
|
||||
|
||||
return { finals, active, archivedCount, brief };
|
||||
}
|
||||
|
||||
function latestItemsByTag(
|
||||
items: import("../types/blackboard.js").BlackboardItem[],
|
||||
): import("../types/blackboard.js").BlackboardItem[] {
|
||||
const byTag = new Map<string, (typeof items)[0]>();
|
||||
for (const item of items) {
|
||||
const prev = byTag.get(item.tag);
|
||||
if (!prev || item.updatedAt > prev.updatedAt) byTag.set(item.tag, item);
|
||||
}
|
||||
return [...byTag.values()];
|
||||
}
|
||||
Reference in New Issue
Block a user