Files
goodbuddy/src/main/assistant/supervisor-service.test.ts
T
mesalogo db5e43d7d5 fix: complete supervisor review coverage and execution workflow
Replace truncated review input with paged, resumable batches and retain extracted facts and source references through navigation summaries. Expose concurrency, timeouts, batch budgets and response capacity in supervisor settings.

Separate automatic supervision from review results, activity and settings; use a standard plan modal and show execution stages with saved batch details. Fix historical entity identity mapping, navigation output validation and arbitrary summary length failures.

Include focused regressions, isolated real-data validation scripts and updated design and verification records. Overall layout acceptance remains outstanding; this commit does not claim that all visual issues are resolved.

Release note: 监督回顾支持完整分页、分批保存和暂停后继续,可配置并发、超时及响应容量;修复实体引用、汇总校验和长摘要报错,并重新组织自动监督与活动记录入口。
2026-09-25 22:32:43 +08:00

135 lines
4.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import type {
SupervisionEvidence,
SupervisionRunRequest
} from '../../shared/supervision-contracts'
import { SupervisorService } from './supervisor-service'
const request: SupervisionRunRequest = {
trigger: 'manual',
scope: { kind: 'global' },
timeRange: {
from: '2026-09-01T00:00:00.000Z',
to: '2026-09-22T00:00:00.000Z'
}
}
const evidence: SupervisionEvidence[] = [
{
id: 'conversation-1',
sourceType: 'conversation',
sourceId: 'conversation-1',
title: 'Story Graph 讨论',
content: '确定监督者统一入口。',
occurredAt: '2026-09-21T10:00:00.000Z'
}
]
const output = {
summary: '监督者统一入口已经确定。',
changeDigest: '新增统一运行领域层。',
openItems: ['接入 SQLite'],
events: [
{
title: '确定统一入口',
description: '监督者复用心跳触发。',
occurredAt: '2026-09-21T10:00:00.000Z',
eventType: 'decision' as const,
entityIds: ['supervisor'],
sourceReferenceIds: ['conversation-1']
}
],
entities: [
{
id: 'supervisor',
label: '监督者',
description: '统一的智能 Agent 应用。',
sourceReferenceIds: ['conversation-1']
}
],
entityChanges: [],
relations: []
}
describe('SupervisorService', () => {
it('rejects a source alias when it identifies more than one input fragment', async () => {
const fragments = [0, 1].map(index => ({ ...evidence[0]!, id: `fragment-${index}`,
locator: { source: 'message:shared', start: index, end: index + 1 } }))
const service = new SupervisorService({ collect: async () => fragments }, { summarize: async () => ({
...output, entities: [], events: [{ ...output.events[0], entityIds: [], sourceReferenceIds: ['message:shared'] }]
}) }, { save: vi.fn() })
await expect(service.run(request)).rejects.toThrow('本次范围之外')
})
it('rejects invented persisted identities even when a local model id matches', async () => {
const id = '00000000-0000-4000-8000-000000000001'
const save = vi.fn()
const service = new SupervisorService({ collect: async () => evidence },
{ summarize: async () => ({ ...output, events: [], entities: [{ ...output.entities[0], id, persistedId: id }] }) },
{ candidates: async () => [], save })
await expect(service.run(request)).rejects.toThrow('outside KNOWN ENTITIES')
expect(save).not.toHaveBeenCalled()
})
it('shares bounded evidence and stores a validated result', async () => {
const collect = vi.fn(async () => evidence)
const summarize = vi.fn(async () => JSON.stringify(output))
const save = vi.fn(async () => undefined)
const service = new SupervisorService(
{ collect },
{ summarize },
{ save }
)
const result = await service.run(request)
expect(result.output.summary).toBe(output.summary)
expect(summarize).toHaveBeenCalledWith(
expect.objectContaining({ evidence, outputContract: expect.stringContaining('local entity id'),
systemInstruction: expect.stringContaining('Entity IDs are local to this output') })
)
expect(save).toHaveBeenCalledWith(result)
})
it('rejects references outside the frozen evidence set', async () => {
const invalidOutput = {
...output,
events: output.events.map((event) => ({
...event,
sourceReferenceIds: ['outside-scope']
}))
}
const service = new SupervisorService(
{ collect: async () => evidence },
{ summarize: async () => invalidOutput },
{ save: async () => undefined }
)
await expect(service.run(request)).rejects.toThrow(
'监督者结果引用了本次范围之外的来源'
)
})
it('rejects relations that point to missing entities', async () => {
const invalidOutput = {
...output,
relations: [
{
fromEntityId: 'supervisor',
toEntityId: 'missing-entity',
relationType: 'related' as const,
reason: '测试',
sourceReferenceIds: ['conversation-1']
}
]
}
const service = new SupervisorService(
{ collect: async () => evidence },
{ summarize: async () => invalidOutput },
{ save: async () => undefined }
)
await expect(service.run(request)).rejects.toThrow(
'监督者结果引用了不存在的知识实体'
)
})
})