agent-ecosystem/test/main/services/team/leadSessionMessageExtractor.test.ts
2026-05-30 23:19:15 +03:00

182 lines
5 KiB
TypeScript

import * as fs from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { afterEach, describe, expect, it } from 'vitest';
import { extractLeadSessionMessagesFromJsonl } from '../../../../src/main/services/team/leadSessionMessageExtractor';
function createUserEntry(
uuid: string,
timestamp: string,
content: string
): Record<string, unknown> {
return {
uuid,
parentUuid: null,
type: 'user',
timestamp,
isSidechain: false,
userType: 'external',
cwd: '/repo',
sessionId: 'lead-1',
version: '1.0.0',
gitBranch: 'main',
message: {
role: 'user',
content,
},
};
}
function createAssistantEntry(
uuid: string,
timestamp: string,
text: string
): Record<string, unknown> {
return {
uuid,
parentUuid: null,
type: 'assistant',
timestamp,
isSidechain: false,
userType: 'external',
cwd: '/repo',
sessionId: 'lead-1',
version: '1.0.0',
gitBranch: 'main',
requestId: `req-${uuid}`,
message: {
role: 'assistant',
model: 'claude-sonnet',
id: `msg-${uuid}`,
type: 'message',
stop_reason: 'end_turn',
stop_sequence: null,
usage: {
input_tokens: 1,
output_tokens: 1,
},
content: [{ type: 'text', text }],
},
};
}
describe('extractLeadSessionMessagesFromJsonl', () => {
const tempPaths: string[] = [];
afterEach(async () => {
await Promise.all(
tempPaths.splice(0).map(async (tempPath) => {
await fs.rm(tempPath, { recursive: true, force: true });
})
);
});
it('extracts and merges command outputs without duplicating command rows', async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'lead-session-extractor-'));
tempPaths.push(dir);
const jsonlPath = path.join(dir, 'lead-1.jsonl');
const lines = [
createUserEntry(
'user-slash-1',
'2026-03-27T12:00:00.000Z',
'<command-name>/model</command-name><command-message>model</command-message><command-args>sonnet</command-args>'
),
createUserEntry(
'stdout-1',
'2026-03-27T12:00:01.000Z',
'<local-command-stdout>Model set to sonnet</local-command-stdout>'
),
createUserEntry(
'stdout-2',
'2026-03-27T12:00:02.000Z',
'<local-command-stdout>Context usage reset</local-command-stdout>'
),
createAssistantEntry('assistant-1', '2026-03-27T12:00:03.000Z', 'Regular assistant text'),
createUserEntry(
'stderr-1',
'2026-03-27T12:00:04.000Z',
'<local-command-stderr>Warning: using cached model alias</local-command-stderr>'
),
createUserEntry('user-plain-1', '2026-03-27T12:00:05.000Z', 'hello'),
createUserEntry(
'stdout-3',
'2026-03-27T12:00:06.000Z',
'<local-command-stdout>Detached output</local-command-stdout>'
),
].map((entry) => JSON.stringify(entry));
await fs.writeFile(jsonlPath, `${lines.join('\n')}\n`, 'utf8');
const messages = await extractLeadSessionMessagesFromJsonl({
jsonlPath,
leadName: 'team-lead',
leadSessionId: 'lead-1',
maxMessages: 20,
});
expect(messages).toHaveLength(3);
expect(messages[0]).toMatchObject({
from: 'team-lead',
messageKind: 'slash_command_result',
commandOutput: {
stream: 'stdout',
commandLabel: '/model',
},
text: 'Model set to sonnet\nContext usage reset',
summary: 'Model set to sonnet',
});
expect(messages[1]).toMatchObject({
messageKind: 'slash_command_result',
commandOutput: {
stream: 'stderr',
commandLabel: '/model',
},
text: 'Warning: using cached model alias',
});
expect(messages[2]).toMatchObject({
messageKind: 'slash_command_result',
commandOutput: {
stream: 'stdout',
commandLabel: '/command',
},
text: 'Detached output',
});
});
it('extracts command outputs from preloaded raw lines without reading the jsonl file', async () => {
const rawLines = [
createUserEntry(
'user-slash-raw',
'2026-03-27T12:00:00.000Z',
'<command-name>/cost</command-name><command-message>cost</command-message><command-args></command-args>'
),
createUserEntry(
'stdout-raw',
'2026-03-27T12:00:01.000Z',
'<local-command-stdout>Total cost: $1.23</local-command-stdout>'
),
].map((entry) => JSON.stringify(entry));
const messages = await extractLeadSessionMessagesFromJsonl({
jsonlPath: path.join(os.tmpdir(), 'missing-lead-session.jsonl'),
leadName: 'team-lead',
leadSessionId: 'lead-1',
maxMessages: 20,
rawLines,
});
expect(messages).toHaveLength(1);
expect(messages[0]).toMatchObject({
from: 'team-lead',
messageKind: 'slash_command_result',
commandOutput: {
stream: 'stdout',
commandLabel: '/cost',
},
text: 'Total cost: $1.23',
});
});
});