moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
/**
* PM-3: Session Timeline — server-side tests.
*
* Tests the parseSessionTimeline function that extracts tool events
* from session JSONL files.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import {
parseSessionTimeline,
type TimelineEvent,
} from '../../src/local/routes/sessions.js';
describe('parseSessionTimeline', () => {
let tmpDir: string;
let sessionsDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-timeline-test-'));
sessionsDir = path.join(tmpDir, 'sessions');
fs.mkdirSync(sessionsDir, { recursive: true });
});
afterEach(() => {
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* */ }
});
function writeSession(id: string, messages: Array<{ role: string; content: string }>) {
const meta = JSON.stringify({ type: 'meta', title: `Session ${id}`, created: '2026-03-19T10:00:00.000Z' });
const lines = [
meta,
...messages.map(m => JSON.stringify({ ...m, timestamp: '2026-03-19T10:05:00.000Z' })),
];
const filePath = path.join(sessionsDir, `${id}.jsonl`);
fs.writeFileSync(filePath, lines.join('\n') + '\n');
return filePath;
}
it('returns empty array for non-existent file', () => {
expect(parseSessionTimeline('/nonexistent/file.jsonl')).toEqual([]);
});
it('returns empty array for empty session file', () => {
const filePath = path.join(sessionsDir, 'empty.jsonl');
fs.writeFileSync(filePath, '', 'utf-8');
expect(parseSessionTimeline(filePath)).toEqual([]);
});
it('extracts tool events from assistant messages with tool patterns', () => {
const filePath = writeSession('s1', [
{ role: 'user', content: 'Search the web for AI news' },
{ role: 'assistant', content: 'Searching the web for "AI news"... Found several results about recent developments.' },
{ role: 'assistant', content: 'Reading file: src/index.ts... The file contains the main entry point.' },
]);
const events = parseSessionTimeline(filePath);
expect(events.length).toBeGreaterThanOrEqual(2);
const webSearch = events.find(e => e.toolName === 'web_search');
expect(webSearch).toBeDefined();
expect(webSearch!.status).toBe('success');
expect(webSearch!.inputPreview).toContain('AI news');
const readFile = events.find(e => e.toolName === 'read_file');
expect(readFile).toBeDefined();
expect(readFile!.inputPreview).toContain('src/index.ts');
});
it('returns empty array for session with only user messages', () => {
const filePath = writeSession('s2', [
{ role: 'user', content: 'Hello' },
{ role: 'user', content: 'How are you?' },
]);
const events = parseSessionTimeline(filePath);
expect(events).toEqual([]);
});
it('detects sub-agent nesting via spawn_agent pattern', () => {
const filePath = writeSession('s3', [
{ role: 'user', content: 'Research this topic' },
{ role: 'assistant', content: 'Spawning sub-agent "researcher" (researcher)... to analyze the topic.' },
{ role: 'assistant', content: 'Searching the web for "topic analysis"... Found relevant results.' },
{ role: 'assistant', content: 'Saving to memory... Stored the research results.' },
]);
const events = parseSessionTimeline(filePath);
// The spawn_agent event should be a top-level event with children
const spawnEvent = events.find(e => e.toolName === 'spawn_agent');
expect(spawnEvent).toBeDefined();
expect(spawnEvent!.children).toBeDefined();
expect(spawnEvent!.children!.length).toBeGreaterThanOrEqual(1);
});
it('detects error status from error content', () => {
const filePath = writeSession('s4', [
{ role: 'user', content: 'Read a file' },
{ role: 'assistant', content: 'Reading file: missing.ts... Error: file not found, unable to read.' },
]);
const events = parseSessionTimeline(filePath);
expect(events.length).toBeGreaterThanOrEqual(1);
const errorEvent = events.find(e => e.status === 'error');
expect(errorEvent).toBeDefined();
expect(errorEvent!.toolName).toBe('read_file');
});
it('assigns sequential IDs to events', () => {
const filePath = writeSession('s5', [
{ role: 'user', content: 'Do some work' },
{ role: 'assistant', content: 'Searching the web for "test query"... done.' },
{ role: 'assistant', content: 'Saving to memory... saved.' },
]);
const events = parseSessionTimeline(filePath);
expect(events.length).toBeGreaterThanOrEqual(2);
const ids = events.map(e => e.id);
// All IDs should be unique
expect(new Set(ids).size).toBe(ids.length);
// IDs should follow tl-N pattern
expect(ids[0]).toBe('tl-0');
expect(ids[1]).toBe('tl-1');
});
it('includes timestamp from the session message', () => {
const filePath = writeSession('s6', [
{ role: 'user', content: 'Test' },
{ role: 'assistant', content: 'Checking git status... Clean working tree.' },
]);
const events = parseSessionTimeline(filePath);
expect(events.length).toBeGreaterThanOrEqual(1);
expect(events[0].timestamp).toBe('2026-03-19T10:05:00.000Z');
});
});