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: 监督回顾支持完整分页、分批保存和暂停后继续,可配置并发、超时及响应容量;修复实体引用、汇总校验和长摘要报错,并重新组织自动监督与活动记录入口。
This commit is contained in:
mesalogo
2026-09-25 22:32:43 +08:00
parent f68584b4e2
commit db5e43d7d5
77 changed files with 6182 additions and 770 deletions
@@ -1,4 +1,5 @@
import { z } from 'zod'
import { supervisionReviewSettingsSchema } from './supervision-review-contracts'
import { magicNoteCommentFormatSchema } from './magic-notes-contracts'
import { localToolEnvironmentSettingsSchema } from './local-tool-environment-contracts'
import { modelDownloadSourceSchema } from './model-download-contracts'
@@ -31,6 +32,11 @@ export const magicNoteCanvasPageCountSchema = z.number().int().min(1).max(8)
export const updateSourceSchema = z.enum(['github', 'mirror'])
export type UpdateSource = z.infer<typeof updateSourceSchema>
export const defaultSupervisionTimeoutSeconds = 240
export const defaultSupervisorModelConcurrency = 1
export const supervisorModelConcurrencySchema = z.number().int().min(1).max(4)
export const supervisionTimeoutSecondsSchema = z.number().int().min(30).max(600)
export const builtInApplicationIds = ['magic-notes', 'knowledge', 'heartbeat', 'local-inference'] as const
export type BuiltInApplicationId = typeof builtInApplicationIds[number]
export const editableApplicationIds = ['magic-notes', 'heartbeat', 'local-inference'] as const
@@ -61,6 +67,10 @@ const applicationPreferencesSchema = z
localInferenceEnabled: z.boolean().default(true),
magicNotesEnabled: z.boolean().default(true),
heartbeatEnabled: z.boolean().optional(),
heartbeatReportTimeoutSeconds: supervisionTimeoutSecondsSchema.optional(),
supervisorOrganizeTimeoutSeconds: supervisionTimeoutSecondsSchema.optional(),
supervisorModelConcurrency: supervisorModelConcurrencySchema.optional(),
supervisionReview: supervisionReviewSettingsSchema.optional(),
magicNotesShowIncompleteTodoCount: z.boolean().default(true),
magicNoteCommentMode: magicNoteCommentModeSchema.default('immediate'),
magicNoteCommentFormat: magicNoteCommentFormatSchema.default('combined'),
+3
View File
@@ -2192,6 +2192,9 @@ export type DesktopApi = {
}>
}
supervision: {
pause: (input: { runId: string }) => Promise<void>
resume: (input: { runId: string }) => Promise<unknown>
batches: (input: { runId: string; offset?: number; limit?: number }) => Promise<import('./supervision-review-contracts').SupervisionReviewBatch[]>
activity: (input?: import('./supervision-contracts').SupervisionActivityRequest) => Promise<import('./supervision-contracts').SupervisionActivity[]>
overview: (input?: { target?: import('./supervision-contracts').SupervisionTarget; resultId?: string }) => Promise<import('./supervision-contracts').SupervisionResultView[]>
run: (input: unknown) => Promise<unknown>
+3
View File
@@ -266,6 +266,9 @@ export const ipcChannels = {
heartbeatsHistory: 'heartbeats:history',
supervisionOverview: 'supervision:overview',
supervisionActivity: 'supervision:activity',
supervisionPause: 'supervision:pause',
supervisionResume: 'supervision:resume',
supervisionBatches: 'supervision:batches',
supervisionRun: 'supervision:run',
supervisionGraph: 'supervision:graph',
supervisionSource: 'supervision:source',
+20
View File
@@ -0,0 +1,20 @@
import { expect, it } from 'vitest'
import { supervisionNavigationOutputSchema, supervisionSummaryOutputSchema, supervisionResultViewSchema } from './supervision-contracts'
it('accepts complete generated content beyond former per-field and collection limits', () => {
const content = 'Retained detail. '.repeat(900)
const output = {
summary: content, changeDigest: content, openItems: Array(21).fill(content),
events: Array.from({ length: 41 }, (_, i) => ({ title: content, description: content, occurredAt: '2026-09-24T00:00:00Z', eventType: 'decision', entityIds: [`entity_${i}`], sourceReferenceIds: Array.from({ length: 21 }, (_, j) => `source_${j}`) })),
entities: Array.from({ length: 41 }, (_, i) => ({ id: `entity_${i}`, label: content, description: content, sourceReferenceIds: [] })),
entityChanges: Array.from({ length: 81 }, () => ({ entityId: 'entity_0', changeType: 'revised', description: content, sourceReferenceIds: [] })),
relations: Array.from({ length: 81 }, () => ({ fromEntityId: 'entity_0', toEntityId: 'entity_1', relationType: 'related', reason: content, sourceReferenceIds: [] }))
}
expect(supervisionSummaryOutputSchema.parse(output)).toEqual(output)
const navigation = { summary: content, changeDigest: content, openItems: output.openItems }
expect(supervisionNavigationOutputSchema.parse(navigation)).toEqual(navigation)
const result = { ...navigation, id: 'result', storyLineId: 'story', sourceId: null, createdAt: '2026-09-24T00:00:00Z', scope: { kind: 'global' }, timeRange: { from: '2026-09-17T08:03:00Z', to: '2026-09-24T08:03:00Z' } }
expect(supervisionResultViewSchema.parse(result)).toEqual(result)
expect(supervisionNavigationOutputSchema.safeParse({ ...navigation, events: output.events }).success).toBe(false)
expect(supervisionSummaryOutputSchema.safeParse({ ...output, events: undefined }).success).toBe(false)
})
+33 -19
View File
@@ -25,6 +25,7 @@ export const supervisionRunRequestSchema = z
export type SupervisionRunRequest = z.infer<typeof supervisionRunRequestSchema>
export const supervisionActivityRequestSchema = z.object({
configId: z.string().min(1).max(256).optional(),
limit: z.number().int().min(1).max(100).default(50),
offset: z.number().int().min(0).max(100_000).default(0)
}).strict()
@@ -33,7 +34,8 @@ export type SupervisionActivity = {
id: string
kind: 'supervision' | 'heartbeat'
trigger: 'manual' | 'scheduled' | 'heartbeat'
status: 'running' | 'completed' | 'failed' | 'skipped' | 'no_change'
status: 'running' | 'completed' | 'failed' | 'skipped' | 'no_change' | 'paused'
reviewProgress?: import('./supervision-review-contracts').SupervisionReviewProgress
scope: SupervisionRunRequest['scope'] | null
startedAt: string
completedAt: string | null
@@ -42,7 +44,7 @@ export type SupervisionActivity = {
summary: string | null
resultId: string | null
heartbeatStatus: 'claimed' | 'completed' | 'failed' | 'skipped' | 'no_change' | null
supervisionStatus: 'running' | 'completed' | 'failed' | 'no_change' | null
supervisionStatus: 'running' | 'completed' | 'failed' | 'no_change' | 'paused' | null
}
export const supervisionEvidenceSchema = z
@@ -59,15 +61,15 @@ export const supervisionEvidenceSchema = z
export type SupervisionEvidence = z.infer<typeof supervisionEvidenceSchema>
const sourceReferenceIdsSchema = z.array(z.string().min(1).max(256)).max(20)
const sourceReferenceIdsSchema = z.array(z.string().min(1).max(256))
export const supervisionEventSchema = z
.object({
title: z.string().min(1).max(240),
description: z.string().max(2_000),
title: z.string().min(1),
description: z.string(),
occurredAt: z.string().datetime({ offset: true }),
eventType: z.enum(['decision', 'change', 'discussion', 'milestone']),
entityIds: z.array(z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/)).max(40),
entityIds: z.array(z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/)),
sourceReferenceIds: sourceReferenceIdsSchema
})
.strict()
@@ -76,8 +78,8 @@ export const supervisionEntitySchema = z
.object({
id: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
persistedId: z.string().uuid().optional(),
label: z.string().min(1).max(240),
description: z.string().max(2_000),
label: z.string().min(1),
description: z.string(),
sourceReferenceIds: sourceReferenceIdsSchema
})
.strict()
@@ -87,7 +89,7 @@ export const supervisionRelationSchema = z
fromEntityId: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
toEntityId: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
relationType: z.enum(['supports', 'depends-on', 'contrasts', 'related']),
reason: z.string().max(1_000),
reason: z.string(),
sourceReferenceIds: sourceReferenceIdsSchema
})
.strict()
@@ -95,19 +97,19 @@ export const supervisionRelationSchema = z
export const supervisionEntityChangeSchema = z.object({
entityId: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
changeType: z.enum(['proposed', 'added', 'verified', 'revised']),
description: z.string().max(2_000),
description: z.string(),
sourceReferenceIds: sourceReferenceIdsSchema
}).strict()
export const supervisionSummaryOutputSchema = z
.object({
summary: z.string().min(1).max(12_000),
changeDigest: z.string().max(4_000),
openItems: z.array(z.string().min(1).max(500)).max(20),
events: z.array(supervisionEventSchema).max(40),
entities: z.array(supervisionEntitySchema).max(40),
entityChanges: z.array(supervisionEntityChangeSchema).max(80),
relations: z.array(supervisionRelationSchema).max(80)
summary: z.string().min(1),
changeDigest: z.string(),
openItems: z.array(z.string().min(1)),
events: z.array(supervisionEventSchema),
entities: z.array(supervisionEntitySchema),
entityChanges: z.array(supervisionEntityChangeSchema),
relations: z.array(supervisionRelationSchema)
})
.strict()
@@ -115,13 +117,23 @@ export type SupervisionSummaryOutput = z.infer<
typeof supervisionSummaryOutputSchema
>
// Navigation combines saved summaries; it cannot create or replace leaf facts.
export const supervisionNavigationOutputSchema = supervisionSummaryOutputSchema.pick({
summary: true, changeDigest: true, openItems: true
}).extend({
events: z.array(z.never()).max(0).optional(),
entities: z.array(z.never()).max(0).optional(),
entityChanges: z.array(z.never()).max(0).optional(),
relations: z.array(z.never()).max(0).optional()
}).strict()
export const supervisionEntityActionSchema = z
.object({
resultId: z.string().min(1).optional(),
entityId: z.string().regex(/^[a-zA-Z0-9_-]{1,120}$/),
action: z.enum(['confirm', 'revise', 'revoke']),
label: z.string().trim().min(1).max(240).optional(),
description: z.string().max(2_000).optional()
label: z.string().trim().min(1).optional(),
description: z.string().optional()
})
.strict()
@@ -176,6 +188,8 @@ export type SupervisionEntityAction = z.infer<typeof supervisionEntityActionSche
export type SupervisionRelationAction = z.infer<typeof supervisionRelationActionSchema>
export const supervisionResultViewSchema = z.object({
runId: z.string().optional(),
coverage: z.record(z.string(), z.unknown()).optional(),
storyLineId: z.string(),
sourceId: z.string().nullable(),
id: z.string(), summary: z.string(), changeDigest: z.string(), createdAt: z.string(),
@@ -0,0 +1,37 @@
import { z } from 'zod'
import type { SupervisionEvidence, SupervisionSummaryOutput } from './supervision-contracts'
export const supervisionReviewSettingsSchema = z.object({
pageSize: z.number().int().min(1).max(200).default(50),
batchCharacters: z.number().int().min(1000).max(16000).default(8000),
batchMessages: z.number().int().min(1).max(50).default(20),
executionSeconds: z.number().int().min(30).max(3600).default(300),
responseKiB: z.number().int().min(100).max(16384).optional()
}).strict()
export type SupervisionReviewSettings = z.infer<typeof supervisionReviewSettingsSchema>
export const defaultSupervisionReviewSettings = supervisionReviewSettingsSchema.parse({})
export const supervisionReviewIdSchema = z.object({ runId: z.string().uuid() }).strict()
export const supervisionBatchesRequestSchema = supervisionReviewIdSchema.extend({
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(20).default(10)
})
export type SupervisionReviewProgress = {
runId: string
batches: number
characters: number
sources: number
remainingSources: number
complete: boolean
restartRequired?: boolean
inFlight?: number
phase?: 'collecting' | 'extracting' | 'summarizing' | 'saving'
navigationNodes?: number
settings?: SupervisionReviewSettings & { timeoutSeconds: number; concurrency: number }
}
export type SupervisionReviewBatch = {
id: string
projectId: string
conversationId: string
evidence: SupervisionEvidence[]
output: SupervisionSummaryOutput
}