Files
brain/tools/secretary-distill.mjs
T

76 lines
5.1 KiB
JavaScript

// Разбор ОДНОГО завершённого спана: reconcile (категории + суть шага) → шаг → аудит (скрытые
// вопросы). Общий код для stop-хука (живой разбор) и пересборки дела из сырья (одинаковое качество).
// Чистая оркестрация над reconcile/audit/layer1; единственный побочный эффект — вызовы callModel.
import { reconcileTurn, mergeTurnIntoProtocol, collapseProtocol } from './secretary-reconcile.mjs';
import { buildAuditPrompt, parseAuditResponse, applyAudit, preserveRegistry } from './secretary-audit.mjs';
import { buildStepLine } from './secretary-layer1.mjs';
import { fluffyPipelineOn } from './secretary-flag.mjs';
import { diffTrunk } from './secretary-trunkdiff.mjs';
import { parseLoose } from './secretary-armor.mjs';
import { resolveModel } from './secretary-models.mjs';
import { SYS_11, SYS_12, SYS_13, diagFindings, user11, user12, user13, userG, renderExchangeText } from './secretary-harvest.mjs';
import { SYS_G } from './secretary-gardener.mjs';
import { applyResults } from './secretary-apply.mjs';
/** Разобрать спан и вернуть НОВЫЙ протокол (вход не мутируется reconcile-веткой; collapse — копия).
* proto — текущий протокол; spanEx — склеенный обмен спана {user,assistant,actions};
* {start,end} — границы спана (в ходах сырья); opts: { callModel|null, session, diag }.
* Без callModel (нет ключа) — пишется только детерминированный шаг, категории/СВ не трогаются. */
export async function distillSpan(proto, spanEx, { start, end, note = '' }, { callModel, session, diag, flags } = {}) {
// Снимок реестра СВ ДО reconcile (reconcile перенумеровывает hidden) — вернём после merge.
const svSnapshot = JSON.parse(JSON.stringify({
hidden: proto.hidden || [], acceptance: proto.acceptance || [],
tails: proto.tails || [], nextSvId: proto.nextSvId || 1,
}));
let updated = null;
if (callModel) {
updated = await reconcileTurn({ proto, ex: spanEx, turn: start, session, callModel, diag });
}
const modelStep = (updated && updated.step) || null;
if (updated && 'step' in updated) delete updated.step;
const step = { turn: start, session,
text: buildStepLine({ turn: start, endTurn: end, user: spanEx.user, assistant: spanEx.assistant,
actions: (spanEx.actions || []).map((a) => a.tool), essence: modelStep, note }) };
const toWrite = mergeTurnIntoProtocol({ proto, updated, step });
// Реестр СВ — вотчина аудитора/садовника: вернуть из снимка ДО reconcile.
preserveRegistry(toWrite, svSnapshot);
// НОВЫЙ конвейер «пушистое дерево» (подпроект A) — за флагом. Флаг ВЫКЛ → старый аудит ниже.
const fluffy = (flags && typeof flags.fluffy === 'boolean') ? flags.fluffy : fluffyPipelineOn();
if (callModel && fluffy) {
try {
const diff = diffTrunk(proto, toWrite); // движение ствола до/после редактора
const spanText = renderExchangeText(spanEx);
const txt = (r) => (typeof r === 'string' ? r : (r && r.text) || '');
const call = (role, system, user) => callModel({ system, user, model: resolveModel(role) });
// сбор: 1.1 диагностика ∥ 1.2 ловец, затем 1.3 брейншторм (после них)
const [r11, r12] = await Promise.all([
call('diagnostic', SYS_11, user11(toWrite, spanText, { start, end })),
call('catcher', SYS_12, user12(toWrite, spanText)),
]);
const d11 = diagFindings(parseLoose(txt(r11), 'lenses'));
const d12 = parseLoose(txt(r12), 'dropped');
const r13 = await call('brainstorm', SYS_13, user13(toWrite, spanText, { start, end }, JSON.stringify(d11.new), JSON.stringify(d12.dropped)));
const d13 = parseLoose(txt(r13), 'forks');
// садовник (после сбора; видит дифф ствола + свежие ветки)
const rG = await call('gardener', SYS_G, userG(toWrite, spanText, diff));
const dG = parseLoose(txt(rG), 'tend');
return collapseProtocol(applyResults(toWrite, start, d11, d12, d13, dG));
} catch (e) { if (typeof diag === 'function') diag({ turn: start, reason: 'fluffy-fail', error: e && e.message }); return collapseProtocol(toWrite); }
}
// СТАРЫЙ путь (аудит 9-линз) — без изменений, пока флаг выключен.
if (callModel) {
try {
const auditMsgs = buildAuditPrompt(toWrite, spanEx);
const raw = await callModel(auditMsgs);
applyAudit(toWrite, parseAuditResponse(typeof raw === 'string' ? raw : (raw?.text || '')), start);
} catch (e) { if (typeof diag === 'function') diag({ turn: start, reason: 'audit-fail', error: e && e.message }); }
}
return collapseProtocol(toWrite);
}