重构世界模拟器为模块化配方架构,完善创作编排、会话运行时与 Web UI,并清理过时技能。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
134
src/skills/question-protocol.ts
Normal file
134
src/skills/question-protocol.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user