TeamLeaderRunner を4モジュールに分割(execution, aggregation, common, streaming)し、 パート完了時にキュー残数が refill_threshold 以下になると追加タスクを動的に生成する worker pool 型の実行モデルを実装。ParallelLogger に LineTimeSliceBuffer を追加し ストリーミング出力を改善。deep-research ピースに team_leader 設定を追加。
121 lines
3.1 KiB
TypeScript
121 lines
3.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { join } from 'node:path';
|
|
import { PieceMovementRawSchema } from '../core/models/schemas.js';
|
|
import { normalizePieceConfig } from '../infra/config/loaders/pieceParser.js';
|
|
|
|
describe('team_leader schema', () => {
|
|
it('max_parts <= 3 の設定を受け付ける', () => {
|
|
const raw = {
|
|
name: 'implement',
|
|
team_leader: {
|
|
persona: 'team-leader',
|
|
max_parts: 3,
|
|
timeout_ms: 120000,
|
|
},
|
|
instruction_template: 'decompose',
|
|
};
|
|
|
|
const result = PieceMovementRawSchema.safeParse(raw);
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('max_parts > 3 は拒否する', () => {
|
|
const raw = {
|
|
name: 'implement',
|
|
team_leader: {
|
|
max_parts: 4,
|
|
},
|
|
instruction_template: 'decompose',
|
|
};
|
|
|
|
const result = PieceMovementRawSchema.safeParse(raw);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('refill_threshold > max_parts は拒否する', () => {
|
|
const raw = {
|
|
name: 'implement',
|
|
team_leader: {
|
|
max_parts: 2,
|
|
refill_threshold: 3,
|
|
},
|
|
instruction_template: 'decompose',
|
|
};
|
|
|
|
const result = PieceMovementRawSchema.safeParse(raw);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('parallel と team_leader の同時指定は拒否する', () => {
|
|
const raw = {
|
|
name: 'implement',
|
|
parallel: [{ name: 'sub', instruction_template: 'x' }],
|
|
team_leader: {
|
|
max_parts: 2,
|
|
},
|
|
instruction_template: 'decompose',
|
|
};
|
|
|
|
const result = PieceMovementRawSchema.safeParse(raw);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('arpeggio と team_leader の同時指定は拒否する', () => {
|
|
const raw = {
|
|
name: 'implement',
|
|
arpeggio: {
|
|
source: 'csv',
|
|
source_path: './data.csv',
|
|
template: './prompt.md',
|
|
},
|
|
team_leader: {
|
|
max_parts: 2,
|
|
},
|
|
instruction_template: 'decompose',
|
|
};
|
|
|
|
const result = PieceMovementRawSchema.safeParse(raw);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('normalizePieceConfig team_leader', () => {
|
|
it('team_leader を内部形式へ正規化する', () => {
|
|
const pieceDir = join(process.cwd(), 'src', '__tests__');
|
|
const raw = {
|
|
name: 'piece',
|
|
movements: [
|
|
{
|
|
name: 'implement',
|
|
team_leader: {
|
|
persona: 'team-leader',
|
|
max_parts: 2,
|
|
timeout_ms: 90000,
|
|
part_persona: 'coder',
|
|
part_allowed_tools: ['Read', 'Edit'],
|
|
part_edit: true,
|
|
part_permission_mode: 'edit',
|
|
},
|
|
instruction_template: 'decompose',
|
|
},
|
|
],
|
|
};
|
|
|
|
const config = normalizePieceConfig(raw, pieceDir);
|
|
const movement = config.movements[0];
|
|
expect(movement).toBeDefined();
|
|
expect(movement!.teamLeader).toEqual({
|
|
persona: 'team-leader',
|
|
personaPath: undefined,
|
|
maxParts: 2,
|
|
refillThreshold: 0,
|
|
timeoutMs: 90000,
|
|
partPersona: 'coder',
|
|
partPersonaPath: undefined,
|
|
partAllowedTools: ['Read', 'Edit'],
|
|
partEdit: true,
|
|
partPermissionMode: 'edit',
|
|
});
|
|
});
|
|
});
|