fix(secretary): разбор хвоста пропускает tool_result (ловит промпт+действия), провенанс по реальному ходу, кириллица в имени дела
Корень бага: в формате Anthropic tool_result — сообщения role:user; parseLastExchange брал их вместо настоящего промпта, теряя текст юзера и действия. + хук форсит реальный turn (Хайку его не знает) + work-slug принимает кириллицу. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ function main() {
|
||||
try { mkdirSync(dirname(FLAG), { recursive: true }); } catch { /* ignore */ }
|
||||
|
||||
if (cmd === 'on') {
|
||||
const m = prompt.match(/секретар[а-я]*\s+(?:для\s+|по\s+)?([a-zA-Z0-9-]{2,})/);
|
||||
const m = prompt.match(/секретар[а-я]*\s+(?:для\s+|по\s+)?([a-zA-Zа-яёА-ЯЁ0-9-]{2,})/);
|
||||
const work = (m && m[1]) || 'general';
|
||||
try { writeFileSync(FLAG, JSON.stringify({ mode: 'on', startedAtTurn: turnCount(rawFile), work, session })); } catch { /* ignore */ }
|
||||
} else if (cmd === 'off') {
|
||||
|
||||
@@ -61,7 +61,10 @@ async function main() {
|
||||
});
|
||||
const extraction = parseExtractionResponse(typeof text === 'string' ? text : '');
|
||||
if (extraction) {
|
||||
for (const d of extraction.decisions) { if (!Array.isArray(d.turns) || !d.turns.length) d.turns = [turn]; }
|
||||
// Номер хода знает только хук — форсим реальный turn на все записи (Хайку его не знает).
|
||||
for (const arr of [extraction.decisions, extraction.will, extraction.open, extraction.doneNext, extraction.supersede]) {
|
||||
for (const e of (arr || [])) { e.turns = [turn]; }
|
||||
}
|
||||
const workDir = join(secdir, work);
|
||||
const protoJson = join(workDir, 'protocol.json');
|
||||
let proto = EMPTY_PROTOCOL();
|
||||
|
||||
@@ -12,12 +12,23 @@ function parseLines(text) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
// Настоящий промпт пользователя (НЕ tool_result): content — строка или массив с text-блоком.
|
||||
// В формате Anthropic tool_result — это сообщения role:user, их пропускаем, иначе теряются
|
||||
// и настоящий промпт, и все действия ассистента до него.
|
||||
function isRealUserPrompt(msg) {
|
||||
if (!msg || msg.role !== 'user') return false;
|
||||
const c = msg.content;
|
||||
if (typeof c === 'string') return true;
|
||||
if (Array.isArray(c)) return c.some((b) => b && b.type === 'text');
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Последний обмен из стенограммы: { user, assistant, actions:[{tool,input}] }. */
|
||||
export function parseLastExchange(transcriptText) {
|
||||
const entries = parseLines(transcriptText);
|
||||
let u = -1;
|
||||
for (let i = entries.length - 1; i >= 0; i--) {
|
||||
if (entries[i] && entries[i].message && entries[i].message.role === 'user') { u = i; break; }
|
||||
if (entries[i] && isRealUserPrompt(entries[i].message)) { u = i; break; }
|
||||
}
|
||||
const userContent = u >= 0 ? entries[u].message.content : '';
|
||||
const user = typeof userContent === 'string'
|
||||
|
||||
@@ -24,4 +24,18 @@ describe('parseLastExchange', () => {
|
||||
expect(ex.assistant).toBe('а');
|
||||
expect(ex.actions).toEqual([]);
|
||||
});
|
||||
it('пропускает tool_result (role:user) — берёт настоящий промпт + все действия', () => {
|
||||
const t = [
|
||||
JSON.stringify({ message: { role: 'user', content: 'настоящий вопрос' } }),
|
||||
JSON.stringify({ message: { role: 'assistant', content: [
|
||||
{ type: 'text', text: 'думаю' }, { type: 'tool_use', name: 'Read', input: { f: 'a' } }] } }),
|
||||
JSON.stringify({ message: { role: 'user', content: [{ type: 'tool_result', content: 'результат' }] } }),
|
||||
JSON.stringify({ message: { role: 'assistant', content: [{ type: 'text', text: 'готово' }] } }),
|
||||
].join('\n');
|
||||
const ex = parseLastExchange(t);
|
||||
expect(ex.user).toBe('настоящий вопрос');
|
||||
expect(ex.assistant).toContain('думаю');
|
||||
expect(ex.assistant).toContain('готово');
|
||||
expect(ex.actions).toEqual([{ tool: 'Read', input: '{"f":"a"}' }]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user