feat(secretary): подавать ролям и редактору только последние 7 действий хода

This commit is contained in:
Дмитрий
2026-06-26 16:23:53 +03:00
parent ec349edc29
commit bee723b414
4 changed files with 44 additions and 5 deletions
+10 -3
View File
@@ -119,10 +119,17 @@ function clamp(s) {
return s.slice(0, cap) + `…[вырезано ${s.length - cap} знаков]`;
}
/** Текст обмена из спана {user, assistant, actions} (заменяет exchange(span) песочницы). */
// Сколько ПОСЛЕДНИХ действий хода показывать ролям/редактору: длинный ход с десятками Read
// не возим целиком (анти-раздувание). Суть хода всё равно фиксирует «step» редактора.
export const MAX_EXCHANGE_ACTIONS = 7;
/** Текст обмена из спана {user, assistant, actions}. Показываем последние MAX_EXCHANGE_ACTIONS действий. */
export function renderExchangeText(spanEx) {
const acts = ((spanEx.actions || []).map((a) => `${a.tool} in=${a.input ?? ''}${a.result != null ? `${clamp(String(a.result))}` : ''}`).join('\n')) || '—';
return `[ЮЗЕР]: ${spanEx.user || ''}\n[АССИСТЕНТ]: ${spanEx.assistant || ''}\n[ДЕЙСТВИЯ]:\n${acts}`;
const all = spanEx.actions || [];
const shown = all.slice(-MAX_EXCHANGE_ACTIONS);
const head = all.length > shown.length ? ` (показаны последние ${shown.length} из ${all.length} действий)\n` : '';
const acts = (shown.map((a) => `${a.tool} in=${a.input ?? ''}${a.result != null ? `${clamp(String(a.result))}` : ''}`).join('\n')) || '—';
return `[ЮЗЕР]: ${spanEx.user || ''}\n[АССИСТЕНТ]: ${spanEx.assistant || ''}\n[ДЕЙСТВИЯ]:\n${head}${acts}`;
}
// ── USER-сборщики (берут готовый spanText) ────────────────────────────────
+17
View File
@@ -134,3 +134,20 @@ describe('renderExchangeText input cap', () => {
delete process.env.SECRETARY_INPUT_CAP;
});
});
describe('renderExchangeText — последние 7 действий', () => {
it('режет до последних 7 и помечает сколько было', () => {
const actions = Array.from({ length: 10 }, (_, i) => ({ tool: 'Read', input: `f${i}`, result: `r${i}` }));
const t = renderExchangeText({ user: 'u', assistant: 'a', actions });
expect(t).toContain('f9'); // последнее показано
expect(t).toContain('f3'); // граница последних 7 (индексы 3..9)
expect(t).not.toContain('f2'); // 8-е с конца отброшено
expect(t).toMatch(/последние 7 из 10/);
});
it('≤7 действий — без пометки', () => {
const actions = Array.from({ length: 5 }, (_, i) => ({ tool: 'Read', input: `f${i}`, result: `r${i}` }));
const t = renderExchangeText({ user: 'u', assistant: 'a', actions });
expect(t).toContain('f0');
expect(t).not.toMatch(/последние \d+ из/);
});
});
+9 -2
View File
@@ -1,5 +1,9 @@
// Секретарь-«редактор»: модель правит весь протокол, хук сторожит потери (спека reconcile).
// Сколько ПОСЛЕДНИХ действий хода подавать редактору (анти-раздувание: длинный ход с десятками
// Read не возим целиком). Держим в синхроне с MAX_EXCHANGE_ACTIONS из secretary-harvest.mjs.
const MAX_EXCHANGE_ACTIONS = 7;
/** Запрос к модели-редактору: весь протокол + обмен → весь обновлённый протокол. */
export function buildReconcilePrompt({ protocol = {}, lastExchange = {}, remark = null } = {}) {
const system = [
@@ -27,8 +31,11 @@ export function buildReconcilePrompt({ protocol = {}, lastExchange = {}, remark
].join('\n');
const sec = (name, arr) => `${name}:\n` + ((arr || []).map((e) =>
` - ${e.struck ? '[зачёркнуто] ' : ''}${e.text}${e.why ? ' — ' + e.why : ''}`).join('\n') || ' (пусто)');
const acts = ((lastExchange.actions || []).map((a) =>
`${a.tool} in=${a.input ?? ''}${a.result != null ? `\n${String(a.result).replace(/\n/g, '\n ')}` : ''}`).join('\n')) || '—';
const allActs = lastExchange.actions || [];
const shownActs = allActs.slice(-MAX_EXCHANGE_ACTIONS);
const actsHead = allActs.length > shownActs.length ? ` (показаны последние ${shownActs.length} из ${allActs.length} действий)\n` : '';
const acts = actsHead + ((shownActs.map((a) =>
`${a.tool} in=${a.input ?? ''}${a.result != null ? `\n${String(a.result).replace(/\n/g, '\n ')}` : ''}`).join('\n')) || '—');
const user = [
`Тема дела: ${protocol.subject || '(нет)'}`,
sec('Решения', protocol.decisions), sec('Альтернативы', protocol.alternatives),
+8
View File
@@ -124,6 +124,14 @@ describe('buildReconcilePrompt', () => {
expect(user).toContain('{"f":"x"}');
expect(user).toContain('СОДЕРЖИМОЕ');
});
it('Последний обмен — только последние 7 действий с пометкой', () => {
const actions = Array.from({ length: 10 }, (_, i) => ({ tool: 'Read', input: `f${i}`, result: `R${i}` }));
const { user } = buildReconcilePrompt({ protocol: { decisions: [], open: [], will: [], doneNext: [] }, lastExchange: { user: 'u', assistant: 'a', actions } });
expect(user).toContain('f9');
expect(user).toContain('f3');
expect(user).not.toContain('f2');
expect(user).toMatch(/последние 7 из 10/);
});
});
describe('reconcile — 9 категорий + стабильная тема', () => {