Files
brain/tools/secretary-extract.mjs
T

46 lines
2.3 KiB
JavaScript

// LLM-извлечение сути (обёртка зовёт мотор; здесь — чистые prompt-builder + parser).
/** Собрать запрос к LLM: из последнего обмена + списка открытых дел → {system, user}. */
export function buildExtractionPrompt({ lastExchange = {}, worksIndex = [] } = {}) {
const system = [
'Ты — секретарь протокола работ. Извлеки СУТЬ последнего обмена по 9 пунктам.',
'Верни ТОЛЬКО JSON без markdown, поля:',
'{ "work":"<slug дела или NEW>", "decisions":[{"text","why","turns":[]}],',
' "supersede":[{"oldText","newText","turns":[]}], "will":[{"text","turns":[]}],',
' "open":[{"text","turns":[]}], "doneNext":[{"text","done":false,"turns":[]}] }',
'Если сути нет — все массивы пустые.',
].join('\n');
const works = worksIndex.length
? worksIndex.map((w) => `- ${w.slug}: ${w.title}${w.goal}`).join('\n')
: '(нет открытых дел)';
const acts = (lastExchange.actions ?? []).map((a) => a.tool).join(', ') || '—';
const user = [
'Открытые дела:', works, '',
'Последний обмен:',
`Пользователь: ${lastExchange.user ?? ''}`,
`Ассистент: ${lastExchange.assistant ?? ''}`,
`Действия: ${acts}`,
'', 'Извлеки суть. Верни JSON.',
].join('\n');
return { system, user };
}
/** Разобрать ответ LLM в структуру для applyExtraction; null при сбое (тихо). */
export function parseExtractionResponse(llmText) {
if (typeof llmText !== 'string' || !llmText.trim()) return null;
let s = llmText.trim().replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```$/, '').trim();
s = s.replace(/,(\s*[}\]])/g, '$1'); // хвостовые запятые — частый quirk LLM
let parsed;
try { parsed = JSON.parse(s); } catch { return null; }
if (!parsed || typeof parsed !== 'object') return null;
const arr = (x) => (Array.isArray(x) ? x : []);
return {
work: typeof parsed.work === 'string' ? parsed.work : null,
decisions: arr(parsed.decisions),
supersede: arr(parsed.supersede),
will: arr(parsed.will),
open: arr(parsed.open),
doneNext: arr(parsed.doneNext),
};
}