重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-30 00:39:32 +08:00
parent 2b74c30d36
commit e670a5129c
167 changed files with 22955 additions and 5659 deletions

View File

@@ -0,0 +1,134 @@
/**
* Worker / Agent 结构化追问协议。
* UI左右分页选项卡经 composer 发送payload 须含问+答(可附自由补充)。
*/
import type {
QuestionAnswer,
QuestionItem,
QuestionOption,
} from "../types/questions.js";
export type { QuestionAnswer, QuestionItem, QuestionOption };
function letterId(i: number): string {
return String.fromCharCode(65 + (i % 26));
}
/** 把 askUser 原始值规范成 QuestionItem[](兼容纯 string[] */
export function normalizeQuestions(raw: unknown): QuestionItem[] {
if (!Array.isArray(raw)) return [];
const out: QuestionItem[] = [];
for (let i = 0; i < raw.length; i++) {
const item = raw[i];
if (typeof item === "string") {
const prompt = item.trim();
if (!prompt) continue;
out.push({
id: `q${i + 1}`,
prompt,
allowOther: true,
required: true,
});
continue;
}
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
const row = item as Record<string, unknown>;
const prompt =
typeof row.prompt === "string"
? row.prompt.trim()
: typeof row.question === "string"
? row.question.trim()
: typeof row.text === "string"
? row.text.trim()
: "";
if (!prompt) continue;
const id =
typeof row.id === "string" && row.id.trim()
? row.id.trim()
: `q${i + 1}`;
let options: QuestionOption[] | undefined;
if (Array.isArray(row.options)) {
options = [];
for (let j = 0; j < row.options.length; j++) {
const opt = row.options[j];
if (typeof opt === "string") {
const label = opt.trim();
if (!label) continue;
options.push({ id: letterId(j), label, editable: true });
continue;
}
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
const o = opt as Record<string, unknown>;
const label =
typeof o.label === "string"
? o.label.trim()
: typeof o.text === "string"
? o.text.trim()
: "";
if (!label) continue;
options.push({
id:
typeof o.id === "string" && o.id.trim()
? o.id.trim()
: letterId(j),
label,
editable: o.editable === false ? false : true,
});
}
if (options.length === 0) options = undefined;
}
out.push({
id,
prompt,
options,
allowOther: row.allowOther === false ? false : true,
required: row.required === false ? false : true,
});
}
return out;
}
/** 发给 AI必须含问题与答案可选自由补充 */
export function formatQuestionAnswersForAi(
questions: QuestionItem[],
answers: QuestionAnswer[],
note?: string,
): string {
const byId = new Map(answers.map((a) => [a.questionId, a]));
const lines: string[] = ["【追问作答】"];
for (const q of questions) {
const a = byId.get(q.id);
const answer = a?.text?.trim() || "(未答)";
lines.push(`问:${q.prompt}`);
lines.push(`答:${answer}`);
lines.push("");
}
const trimmedNote = note?.trim();
if (trimmedNote) {
lines.push("【补充】");
lines.push(trimmedNote);
}
return lines.join("\n").trim();
}
/** 气泡摘要:答句 + 可选补充 */
export function formatQuestionAnswersForDisplay(
questions: QuestionItem[],
answers: QuestionAnswer[],
note?: string,
): string {
const byId = new Map(answers.map((a) => [a.questionId, a]));
const parts: string[] = [];
for (const q of questions) {
const a = byId.get(q.id);
if (!a?.text?.trim()) continue;
parts.push(a.text.trim());
}
const trimmedNote = note?.trim();
if (trimmedNote) parts.push(trimmedNote);
return parts.length ? parts.join("\n") : "(已提交追问作答)";
}
export function questionPrompts(questions: QuestionItem[]): string[] {
return questions.map((q) => q.prompt).filter(Boolean);
}