takt/src/__tests__/team-leader-execution.test.ts
nrslib 798e89605d feat: TeamLeader に refill threshold と動的パート追加を導入
TeamLeaderRunner を4モジュールに分割(execution, aggregation, common, streaming)し、
パート完了時にキュー残数が refill_threshold 以下になると追加タスクを動的に生成する
worker pool 型の実行モデルを実装。ParallelLogger に LineTimeSliceBuffer を追加し
ストリーミング出力を改善。deep-research ピースに team_leader 設定を追加。
2026-02-26 22:33:22 +09:00

86 lines
2.6 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { runTeamLeaderExecution } from '../core/piece/engine/team-leader-execution.js';
import type { PartDefinition, PartResult } from '../core/models/types.js';
function makePart(id: string): PartDefinition {
return {
id,
title: `title-${id}`,
instruction: `do-${id}`,
timeoutMs: undefined,
};
}
function makeResult(part: PartDefinition): PartResult {
return {
part,
response: {
persona: `execute.${part.id}`,
status: 'done',
content: `done ${part.id}`,
timestamp: new Date(),
},
};
}
describe('runTeamLeaderExecution', () => {
it('refill threshold 到達時に追加パートを取り込んで完了する', async () => {
const part1 = makePart('p1');
const part2 = makePart('p2');
const part3 = makePart('p3');
const requestMoreParts = vi.fn()
.mockResolvedValueOnce({
done: false,
reasoning: 'need one more',
parts: [{ id: 'p3', title: 'title-p3', instruction: 'do-p3', timeoutMs: undefined }],
})
.mockResolvedValueOnce({
done: true,
reasoning: 'enough',
parts: [],
});
const runPart = vi.fn(async (part: PartDefinition) => makeResult(part));
const result = await runTeamLeaderExecution({
initialParts: [part1, part2],
maxConcurrency: 2,
refillThreshold: 1,
runPart,
requestMoreParts,
});
expect(result.plannedParts.map((p) => p.id)).toEqual(['p1', 'p2', 'p3']);
expect(result.partResults.map((r) => r.part.id).sort()).toEqual(['p1', 'p2', 'p3']);
expect(runPart).toHaveBeenCalledTimes(3);
expect(requestMoreParts).toHaveBeenCalledTimes(2);
expect(result.partResults.some((r) => r.part.id === part3.id)).toBe(true);
});
it('重複IDだけ返された場合は追加せず終了する', async () => {
const part1 = makePart('p1');
const onPlanningNoNewParts = vi.fn();
const runPart = vi.fn(async (part: PartDefinition) => makeResult(part));
const requestMoreParts = vi.fn().mockResolvedValue({
done: false,
reasoning: 'duplicate only',
parts: [{ id: 'p1', title: 'dup', instruction: 'dup', timeoutMs: undefined }],
});
const result = await runTeamLeaderExecution({
initialParts: [part1],
maxConcurrency: 1,
refillThreshold: 0,
runPart,
requestMoreParts,
onPlanningNoNewParts,
});
expect(result.plannedParts.map((p) => p.id)).toEqual(['p1']);
expect(result.partResults).toHaveLength(1);
expect(onPlanningNoNewParts).toHaveBeenCalledTimes(1);
});
});