629ed8b00e
By default the orchestrator is resilient: a single reviewer (or context gatherer) failure is logged and the round continues with the survivors, aborting only when all reviewers fail. The new --fail-fast flag flips to strict mode — any reviewer or context-gathering failure re-throws immediately and terminates the whole flow. Wired through the review and discuss commands via OrchestratorOptions.failFast, with a regression test and README docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { DebateOrchestrator } from '../../src/orchestrator/orchestrator.js'
|
|
import type { AIProvider } from '../../src/providers/types.js'
|
|
import type { Reviewer } from '../../src/orchestrator/types.js'
|
|
|
|
function makeProvider(name: string, response: string): AIProvider {
|
|
return {
|
|
name,
|
|
async chat() { return response },
|
|
async *chatStream() { yield response },
|
|
}
|
|
}
|
|
|
|
function makeFailingProvider(name: string): AIProvider {
|
|
return {
|
|
name,
|
|
async chat() { throw new Error(`${name} crashed`) },
|
|
async *chatStream() { throw new Error(`${name} crashed`) },
|
|
}
|
|
}
|
|
|
|
function makeReviewer(id: string, provider: AIProvider): Reviewer {
|
|
return { id, provider, systemPrompt: 'Review the code.' }
|
|
}
|
|
|
|
describe('DebateOrchestrator resilience', () => {
|
|
it('should complete review when one reviewer fails in streaming mode', async () => {
|
|
const goodProvider = makeProvider('good', 'LGTM, no issues found.')
|
|
const badProvider = makeFailingProvider('bad')
|
|
|
|
const reviewers = [
|
|
makeReviewer('good-reviewer', goodProvider),
|
|
makeReviewer('bad-reviewer', badProvider),
|
|
]
|
|
const summarizer = makeReviewer('summarizer', makeProvider('sum', 'Final conclusion.'))
|
|
const analyzer = makeReviewer('analyzer', makeProvider('analyzer', 'Analysis done.'))
|
|
|
|
const orchestrator = new DebateOrchestrator(reviewers, summarizer, analyzer, {
|
|
maxRounds: 1,
|
|
interactive: false,
|
|
checkConvergence: false,
|
|
})
|
|
|
|
const result = await orchestrator.runStreaming('test', 'Review this code')
|
|
expect(result.finalConclusion).toBeTruthy()
|
|
expect(result.messages.some(m => m.reviewerId === 'good-reviewer')).toBe(true)
|
|
})
|
|
|
|
it('should fail if ALL reviewers fail', async () => {
|
|
const reviewers = [
|
|
makeReviewer('bad-1', makeFailingProvider('bad1')),
|
|
makeReviewer('bad-2', makeFailingProvider('bad2')),
|
|
]
|
|
const summarizer = makeReviewer('summarizer', makeProvider('sum', 'Final conclusion.'))
|
|
const analyzer = makeReviewer('analyzer', makeProvider('analyzer', 'Analysis done.'))
|
|
|
|
const orchestrator = new DebateOrchestrator(reviewers, summarizer, analyzer, {
|
|
maxRounds: 1,
|
|
interactive: false,
|
|
checkConvergence: false,
|
|
})
|
|
|
|
await expect(orchestrator.runStreaming('test', 'Review this code'))
|
|
.rejects.toThrow('All reviewers failed')
|
|
})
|
|
|
|
it('should abort immediately when failFast is enabled and any reviewer fails', async () => {
|
|
const goodProvider = makeProvider('good', 'LGTM, no issues found.')
|
|
const badProvider = makeFailingProvider('bad')
|
|
|
|
const reviewers = [
|
|
makeReviewer('good-reviewer', goodProvider),
|
|
makeReviewer('bad-reviewer', badProvider),
|
|
]
|
|
const summarizer = makeReviewer('summarizer', makeProvider('sum', 'Final conclusion.'))
|
|
const analyzer = makeReviewer('analyzer', makeProvider('analyzer', 'Analysis done.'))
|
|
|
|
const orchestrator = new DebateOrchestrator(reviewers, summarizer, analyzer, {
|
|
maxRounds: 1,
|
|
interactive: false,
|
|
checkConvergence: false,
|
|
failFast: true,
|
|
})
|
|
|
|
await expect(orchestrator.runStreaming('test', 'Review this code'))
|
|
.rejects.toThrow(/bad-reviewer.*fail-fast/)
|
|
})
|
|
})
|