feat(observer): capture ask_user_question events with answer_kind classification (Task 4)
Add extractAskUserQuestionEvents() — for each AskUserQuestion toolUseResult emits one event per question with answer_kind: option|custom|no_answer and question_count. Integrated into parseTranscript events pipeline. 7 new tests (263 total, 0 failed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1003,3 +1003,80 @@ describe('parseTranscript — task_cost integration (Task 2)', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
import { extractAskUserQuestionEvents } from './observer-transcript-parser.mjs';
|
||||
|
||||
describe('extractAskUserQuestionEvents (Task 4)', () => {
|
||||
it('records "option" when user picked an offered label exactly', () => {
|
||||
const turn = [
|
||||
{ message: { content: [{ type: 'tool_use', name: 'AskUserQuestion',
|
||||
input: { questions: [{ question: 'q1', options: [{ label: 'A' }, { label: 'B' }] }] } }] } },
|
||||
{ toolUseResult: { questions: [{ question: 'q1', options: [{ label: 'A' }, { label: 'B' }] }],
|
||||
answers: { q1: 'A' } } },
|
||||
];
|
||||
const evs = extractAskUserQuestionEvents(turn);
|
||||
expect(evs).toHaveLength(1);
|
||||
expect(evs[0].kind).toBe('ask_user_question');
|
||||
expect(evs[0].question_count).toBe(1);
|
||||
expect(evs[0].answer_kind).toBe('option');
|
||||
});
|
||||
it('records "custom" when user wrote free-text Other', () => {
|
||||
const turn = [
|
||||
{ message: { content: [{ type: 'tool_use', name: 'AskUserQuestion',
|
||||
input: { questions: [{ question: 'q1', options: [{ label: 'A' }, { label: 'B' }] }] } }] } },
|
||||
{ toolUseResult: { questions: [{ question: 'q1', options: [{ label: 'A' }, { label: 'B' }] }],
|
||||
answers: { q1: 'C — мой вариант' } } },
|
||||
];
|
||||
const evs = extractAskUserQuestionEvents(turn);
|
||||
expect(evs[0].answer_kind).toBe('custom');
|
||||
});
|
||||
it('records "no_answer" when answer key missing', () => {
|
||||
const turn = [
|
||||
{ toolUseResult: { questions: [{ question: 'q1', options: [{ label: 'A' }, { label: 'B' }] }],
|
||||
answers: {} } },
|
||||
];
|
||||
const evs = extractAskUserQuestionEvents(turn);
|
||||
expect(evs[0].answer_kind).toBe('no_answer');
|
||||
});
|
||||
it('reports question_count from toolUseResult.questions length', () => {
|
||||
const turn = [
|
||||
{ toolUseResult: { questions: [
|
||||
{ question: 'q1', options: [{ label: 'A' }] },
|
||||
{ question: 'q2', options: [{ label: 'B' }] },
|
||||
], answers: { q1: 'A', q2: 'B' } } },
|
||||
];
|
||||
const evs = extractAskUserQuestionEvents(turn);
|
||||
expect(evs).toHaveLength(2); // one event per question
|
||||
expect(evs[0].question_count).toBe(2);
|
||||
expect(evs[1].question_count).toBe(2);
|
||||
});
|
||||
it('returns empty for a turn without AskUserQuestion', () => {
|
||||
expect(extractAskUserQuestionEvents([{ message: { content: [{ type: 'tool_use', name: 'Read' }] } }])).toEqual([]);
|
||||
});
|
||||
it('handles null/undefined turn safely', () => {
|
||||
expect(extractAskUserQuestionEvents(null)).toEqual([]);
|
||||
expect(extractAskUserQuestionEvents(undefined)).toEqual([]);
|
||||
expect(extractAskUserQuestionEvents([])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTranscript — ask_user_question events (Task 4)', () => {
|
||||
it('emits ask_user_question event from AskUserQuestion + toolUseResult', () => {
|
||||
const transcript = [
|
||||
JSON.stringify({ sessionId: 's1' }),
|
||||
JSON.stringify({ type: 'user', message: { role: 'user', content: 'делай' }, uuid: 'u1', timestamp: '2026-05-20T00:00:00Z' }),
|
||||
JSON.stringify({ type: 'assistant', message: { role: 'assistant', content: [
|
||||
{ type: 'tool_use', id: 't1', name: 'AskUserQuestion',
|
||||
input: { questions: [{ question: 'q', options: [{ label: 'X' }, { label: 'Y' }] }] } },
|
||||
] }, uuid: 'u2', timestamp: '2026-05-20T00:00:01Z' }),
|
||||
JSON.stringify({ type: 'user', message: { role: 'user', content: [
|
||||
{ type: 'tool_result', tool_use_id: 't1' },
|
||||
] }, toolUseResult: { questions: [{ question: 'q', options: [{ label: 'X' }, { label: 'Y' }] }],
|
||||
answers: { q: 'X' } }, uuid: 'u3', timestamp: '2026-05-20T00:00:02Z' }),
|
||||
].join('\n');
|
||||
const ep = parseTranscript(transcript);
|
||||
const aq = ep.events.filter((e) => e.kind === 'ask_user_question');
|
||||
expect(aq).toHaveLength(1);
|
||||
expect(aq[0].answer_kind).toBe('option');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user