agent-ecosystem/test/main/services/team/TaskLogTranscriptCandidateSelector.test.ts

168 lines
5.5 KiB
TypeScript

import path from 'path';
import { describe, expect, it } from 'vitest';
import { TaskLogTranscriptCandidateSelector } from '../../../../src/main/services/team/taskLogs/stream/TaskLogTranscriptCandidateSelector';
import type { BoardTaskActivityRecord } from '../../../../src/main/services/team/taskLogs/activity/BoardTaskActivityRecord';
function makeRecord(args: {
id: string;
filePath: string;
sessionId?: string;
canonicalToolName?: string;
category?: NonNullable<BoardTaskActivityRecord['action']>['category'];
}): BoardTaskActivityRecord {
return {
id: args.id,
timestamp: '2026-04-30T10:00:00.000Z',
task: {
locator: { ref: 'abcd1234', refKind: 'display', canonicalId: 'task-a' },
resolution: 'resolved',
},
linkKind: 'board_action',
targetRole: 'subject',
actor: {
memberName: 'alice',
role: 'member',
sessionId: args.sessionId ?? '',
isSidechain: true,
},
actorContext: { relation: 'same_task' },
...(args.canonicalToolName || args.category
? {
action: {
canonicalToolName: args.canonicalToolName ?? 'task_add_comment',
toolUseId: `${args.id}-tool`,
category: args.category ?? 'comment',
},
}
: {}),
source: {
filePath: args.filePath,
messageUuid: `${args.id}-msg`,
toolUseId: `${args.id}-tool`,
sourceOrder: 1,
},
};
}
describe('TaskLogTranscriptCandidateSelector', () => {
it('selects direct record files and same-session files for non-read task records', () => {
const projectDir = path.join('/tmp', 'claude-project');
const rootFile = path.join(projectDir, 'session-a.jsonl');
const subagentFile = path.join(projectDir, 'session-a', 'subagents', 'agent-worker.jsonl');
const unrelatedFile = path.join(projectDir, 'session-b.jsonl');
const selector = new TaskLogTranscriptCandidateSelector();
const selection = selector.selectInferredNativeTranscriptFiles({
projectDir,
transcriptFiles: [unrelatedFile, subagentFile, rootFile],
records: [
makeRecord({
id: 'comment',
filePath: rootFile,
sessionId: 'session-a',
canonicalToolName: 'task_add_comment',
category: 'comment',
}),
],
alreadyParsedFilePaths: new Set([rootFile]),
});
expect(selection.filePaths).toEqual([subagentFile]);
expect(selection.candidates.map((candidate) => candidate.filePath)).toEqual([
rootFile,
subagentFile,
]);
expect(selection.diagnostics).toMatchObject({
recordFileCount: 1,
nonReadSessionCount: 1,
sameSessionFileCount: 2,
alreadyParsedCandidateCount: 1,
finalCandidateCount: 1,
reason: 'same_session_native_window',
});
});
it('does not expand read-only task records to every file in the same session', () => {
const projectDir = path.join('/tmp', 'claude-project');
const rootFile = path.join(projectDir, 'session-a.jsonl');
const subagentFile = path.join(projectDir, 'session-a', 'subagents', 'agent-worker.jsonl');
const selector = new TaskLogTranscriptCandidateSelector();
const selection = selector.selectInferredNativeTranscriptFiles({
projectDir,
transcriptFiles: [rootFile, subagentFile],
records: [
makeRecord({
id: 'read',
filePath: rootFile,
sessionId: 'session-a',
canonicalToolName: 'task_get',
category: 'read',
}),
],
});
expect(selection.filePaths).toEqual([rootFile]);
expect(selection.candidates.map((candidate) => candidate.filePath)).toEqual([rootFile]);
expect(selection.diagnostics).toMatchObject({
nonReadSessionCount: 0,
sameSessionFileCount: 0,
reason: 'direct_record_files',
});
});
it('falls back to direct record files when the transcript file cannot be session-indexed', () => {
const projectDir = path.join('/tmp', 'claude-project');
const outsideFile = path.join('/tmp', 'other-project', 'session-a.jsonl');
const selector = new TaskLogTranscriptCandidateSelector();
const selection = selector.selectInferredNativeTranscriptFiles({
projectDir,
transcriptFiles: [outsideFile],
records: [
makeRecord({
id: 'comment',
filePath: outsideFile,
canonicalToolName: 'task_add_comment',
category: 'comment',
}),
],
});
expect(selection.filePaths).toEqual([outsideFile]);
expect(selection.diagnostics).toMatchObject({
recordFileCount: 1,
nonReadSessionCount: 0,
sameSessionFileCount: 0,
finalCandidateCount: 1,
reason: 'direct_record_files',
});
});
it('does not select files by owner-looking names without session evidence', () => {
const projectDir = path.join('/tmp', 'claude-project');
const recordFile = path.join(projectDir, 'session-a.jsonl');
const ownerLookingFile = path.join(projectDir, 'alice-work.jsonl');
const selector = new TaskLogTranscriptCandidateSelector();
const selection = selector.selectInferredNativeTranscriptFiles({
projectDir,
transcriptFiles: [recordFile, ownerLookingFile],
records: [
makeRecord({
id: 'comment',
filePath: recordFile,
sessionId: undefined,
canonicalToolName: 'task_add_comment',
category: 'comment',
}),
],
});
expect(selection.filePaths).toEqual([recordFile]);
expect(selection.filePaths).not.toContain(ownerLookingFile);
});
});