90f1360065
Секретарь перестал терять промпт владельца при обрыве (сбой API / ручной стоп / жёсткий крах). Источник правды — транскрипт на диске: сырьё (Слой 1) пересобирается из всего транскрипта на каждом завершении, а не дописывается по последнему обмену. - classifyEntry/assembleExchanges: распознавание машинных меток (isApiErrorMessage, [Request interrupted by user] обе формы, isCompactSummary, isMeta) — метка не считается настоящим промптом; промпт после обрыва помечается продолжением (cont=1), хвост — tail=1. - realBoundariesFromRaw: продолжение не открывает новый спан (одна работа не дробится). - честные пометки спана: «(связь прерывалась — продолжено)» / «(прервана, не завершена)». - stop-хук: пересборка сырья из транскрипта + догон недоразобранного хвоста прошлых (умерших) сессий дела при «включи секретаря <дело>» (_sessions.json, secretary-sessions). - parseLastExchange → тонкая обёртка над assembleExchanges (без дубля логики). Свод секретаря зелёный: 172 теста / 12 файлов. Спека: docs/superpowers/specs/2026-06-23-secretary-interruption-resilience-spec.md План: docs/superpowers/plans/2026-06-23-secretary-interruption-resilience.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
47 lines
2.7 KiB
JavaScript
47 lines
2.7 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { planActivation, planPromptTurn } from './secretary-prompt-hook.mjs';
|
|
|
|
describe('planPromptTurn — вкл/выкл (границы спанов определяет stop-хук из сырья)', () => {
|
|
it('cmd=off → флажок mode:closing с сохранением полей', () => {
|
|
const r = planPromptTurn({ cmd: 'off', flag: { mode: 'on', work: 'дело', spanCursor: 0, session: 's' } });
|
|
expect(r.flag.mode).toBe('closing');
|
|
expect(r.flag.work).toBe('дело');
|
|
expect(r.flag.spanCursor).toBe(0);
|
|
});
|
|
it('обычный промпт (cmd=null) → ничего не пишем', () => {
|
|
expect(planPromptTurn({ cmd: null, flag: { mode: 'on' } }).flag).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('planActivation — решение хука: активировать или переспросить', () => {
|
|
it('новое имя (нет похожих) — флажок on с work', () => {
|
|
const r = planActivation({ requested: 'биллинг', existing: ['general'], startedAtTurn: 3, session: 's1' });
|
|
expect(r.confirm).toBe(false);
|
|
expect(r.flag).toEqual({ mode: 'on', startedAtTurn: 3, work: 'биллинг', session: 's1', catchUp: [] });
|
|
});
|
|
it('точное совпадение — флажок on с существующим именем', () => {
|
|
const r = planActivation({ requested: 'general', existing: ['general'], startedAtTurn: 0, session: 's2' });
|
|
expect(r.confirm).toBe(false);
|
|
expect(r.flag.work).toBe('general');
|
|
});
|
|
it('похожее имя — переспросить: флажок не ставить, дать кандидатов и подсказку', () => {
|
|
const r = planActivation({ requested: 'создание', existing: ['создание-секретаря'], startedAtTurn: 1, session: 's3' });
|
|
expect(r.confirm).toBe(true);
|
|
expect(r.flag).toBeUndefined();
|
|
expect(r.candidates).toContain('создание-секретаря');
|
|
expect(r.context).toContain('создание-секретаря');
|
|
});
|
|
it('проставляет catchUp из прошлых сессий дела', () => {
|
|
const plan = planActivation({
|
|
requested: 'наставник', existing: ['наставник'], startedAtTurn: 0, session: 's2',
|
|
sessionsOfCase: [{ session: 's1', cursor: 2 }, { session: 's2', cursor: 0 }],
|
|
});
|
|
expect(plan.confirm).toBe(false);
|
|
expect(plan.flag.catchUp).toEqual([{ session: 's1', cursor: 2 }]);
|
|
});
|
|
it('без прошлых сессий — catchUp пустой', () => {
|
|
const plan = planActivation({ requested: 'новое', existing: [], session: 's1', sessionsOfCase: [] });
|
|
expect(plan.flag.catchUp).toEqual([]);
|
|
});
|
|
});
|