feat(secretary): подавать ролям и редактору только последние 7 действий хода
This commit is contained in:
@@ -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) ────────────────────────────────
|
||||
|
||||
@@ -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+ из/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 категорий + стабильная тема', () => {
|
||||
|
||||
Reference in New Issue
Block a user