Files
goodbuddy/src/shared/supervision-contracts.ts
T
mesalogo 43be465286 feat: suggest stalled stories and reusable experiences, and let agents read stories through MCP
Also includes pending workspace changes: unsaved-change guard, keep-alive cache and live message store, knowledge workspace dialogs and perf driver updates.
2026-10-02 18:23:08 +08:00

269 lines
11 KiB
TypeScript

import { z } from 'zod'
import { heartbeatScopeSchema } from './assistant-contracts'
export const supervisionTriggerSchema = z.enum(['manual', 'heartbeat'])
export type SupervisionTrigger = z.infer<typeof supervisionTriggerSchema>
export const supervisionTimeRangeSchema = z
.object({
from: z.string().datetime({ offset: true }),
to: z.string().datetime({ offset: true })
})
.strict()
.refine((value) => Date.parse(value.from) <= Date.parse(value.to), {
message: '监督者时间范围无效'
})
export const supervisionRunRequestSchema = z
.object({
trigger: supervisionTriggerSchema,
scope: heartbeatScopeSchema,
timeRange: supervisionTimeRangeSchema,
// Reviews skip processed, unchanged sources by default. Only an explicit
// user re-analysis reads the whole interval again; it never moves progress.
reanalyze: z.boolean().optional()
})
.strict()
export type SupervisionRunRequest = z.infer<typeof supervisionRunRequestSchema>
/** Whether a review reads and advances the shared incremental progress. */
export function isIncrementalReview(request: Pick<SupervisionRunRequest, 'trigger' | 'reanalyze'>): boolean {
return request.trigger === 'heartbeat' || request.reanalyze !== true
}
export const supervisionSuggestionKindSchema = z.enum(['open_item', 'conflict', 'convention', 'revision', 'stalled', 'experience'])
export type SupervisionSuggestionKind = z.infer<typeof supervisionSuggestionKindSchema>
export type SupervisionSuggestion = {
id: string
resultId: string | null
heartbeatRunId: string | null
scope: SupervisionRunRequest['scope']
kind: SupervisionSuggestionKind
title: string
detail: string
sourceIds: string[]
entityId: string | null
relationId: string | null
taskId: string | null
status: 'pending' | 'accepted' | 'dismissed'
createdAt: string
/** Story the suggestion is about (stalled story, or where an experience may apply). */
storyId?: string | null
experienceId?: string | null
}
export const supervisionSuggestionListRequestSchema = z.object({
status: z.enum(['pending', 'all']).default('pending'),
limit: z.number().int().min(1).max(100).default(50),
offset: z.number().int().min(0).max(100_000).default(0)
}).strict()
export const supervisionSuggestionActionSchema = z.object({
id: z.string().uuid(),
action: z.enum(['accept', 'dismiss'])
}).strict()
export type SupervisionSuggestionAction = z.infer<typeof supervisionSuggestionActionSchema>
export const supervisionSuggestionRetrySchema = z.object({ heartbeatRunId: z.string().min(1).max(256) }).strict()
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()
export type SupervisionActivityRequest = z.input<typeof supervisionActivityRequestSchema>
export type SupervisionActivity = {
id: string
kind: 'supervision' | 'heartbeat'
trigger: 'manual' | 'scheduled' | 'heartbeat'
status: 'running' | 'completed' | 'failed' | 'skipped' | 'no_change' | 'paused' | 'cancelled'
reviewProgress?: import('./supervision-review-contracts').SupervisionReviewProgress
scope: SupervisionRunRequest['scope'] | null
startedAt: string
completedAt: string | null
timeRange: SupervisionRunRequest['timeRange'] | null
error: string | null
summary: string | null
resultId: string | null
heartbeatStatus: 'claimed' | 'completed' | 'failed' | 'skipped' | 'no_change' | null
supervisionStatus: 'running' | 'completed' | 'failed' | 'no_change' | 'paused' | 'cancelled' | null
/** Intervention after an automatic review; null when not applicable. */
suggestionStatus?: 'running' | 'completed' | 'failed' | 'skipped' | null
suggestionError?: string | null
suggestionCount?: number
}
export const supervisionEvidenceSchema = z
.object({
id: z.string().min(1).max(256),
sourceType: z.enum(['conversation', 'task', 'note', 'memory', 'knowledge']),
sourceId: z.string().min(1).max(256),
title: z.string().max(500),
content: z.string().max(8_000),
occurredAt: z.string().datetime({ offset: true }),
locator: z.record(z.string(), z.unknown()).optional()
})
.strict()
export type SupervisionEvidence = z.infer<typeof supervisionEvidenceSchema>
const sourceReferenceIdsSchema = z.array(z.string().min(1).max(256))
export const supervisionEventSchema = z
.object({
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}$/)),
sourceReferenceIds: sourceReferenceIdsSchema
})
.strict()
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),
description: z.string(),
sourceReferenceIds: sourceReferenceIdsSchema
})
.strict()
export const supervisionRelationSchema = z
.object({
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(),
sourceReferenceIds: sourceReferenceIdsSchema
})
.strict()
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(),
sourceReferenceIds: sourceReferenceIdsSchema
}).strict()
export const supervisionSummaryOutputSchema = z
.object({
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()
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).optional(),
description: z.string().optional()
})
.strict()
export const supervisionRelationActionSchema = z
.object({
resultId: z.string().min(1).optional(),
relationId: z.string().uuid(),
action: z.enum(['confirm', 'revoke'])
})
.strict()
export const supervisionSourceRequestSchema = z
.object({ sourceId: z.string().min(1).max(256) })
.strict()
export const supervisionContinueContextRequestSchema = z.object({
sourceId: z.string().min(1).max(256),
resultId: z.string().min(1).max(256)
}).strict()
export type SupervisionContinueContextRequest = z.infer<typeof supervisionContinueContextRequestSchema>
export const supervisionContinueRequestSchema = z.object({
conversationId: z.string().min(1).max(128),
prompt: z.string().trim().min(1).max(12_000),
runtimeSelection: z.unknown().optional(),
projectId: z.string().min(1).max(128).optional()
}).strict()
export type SupervisionContinueRequest = z.infer<typeof supervisionContinueRequestSchema>
export const supervisionKnowledgePreviewRequestSchema = z.object({
operation: z.enum(['create-entity', 'update-entity']),
libraryId: z.string().uuid(),
entityId: z.string().uuid().optional(),
label: z.string().trim().min(1).max(120),
type: z.string().trim().min(1).max(120),
description: z.string().trim().max(2_000),
aliases: z.array(z.string().trim().min(1).max(120)).max(50),
sourceId: z.string().min(1).max(256)
}).strict().superRefine((value, context) => {
if (value.operation === 'update-entity' && !value.entityId) {
context.addIssue({ code: 'custom', path: ['entityId'], message: 'update-entity requires entityId' })
}
})
export type SupervisionKnowledgePreviewRequest = z.infer<typeof supervisionKnowledgePreviewRequestSchema>
export const supervisionKnowledgeCommitRequestSchema = z.object({
previewId: z.string().uuid()
}).strict()
export type SupervisionKnowledgeCommitRequest = z.infer<typeof supervisionKnowledgeCommitRequestSchema>
export type SupervisionEntityAction = z.infer<typeof supervisionEntityActionSchema>
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(),
scope: heartbeatScopeSchema, timeRange: supervisionTimeRangeSchema,
openItems: z.array(z.string())
})
export type SupervisionResultView = z.infer<typeof supervisionResultViewSchema>
export const supervisionTargetSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('conversation'), conversationId: z.string().min(1).max(128) }).strict(),
z.object({ type: z.literal('task'), taskId: z.string().min(1).max(128) }).strict()
])
export type SupervisionTarget = z.infer<typeof supervisionTargetSchema>
export const supervisionOverviewRequestSchema = z.object({ target: supervisionTargetSchema.optional(), resultId: z.string().min(1).max(256).optional() }).strict()
export const supervisionGraphRequestSchema = z.object({
resultId: z.string().min(1).optional(), storyLineId: z.string().min(1).optional()
}).strict()
export type SupervisionGraphRequest = z.infer<typeof supervisionGraphRequestSchema>
export const supervisionGraphViewSchema = z.object({
storyLine: z.object({ id: z.string(), scope_json: z.string() }).nullable(),
events: z.array(z.object({ id: z.string(), title: z.string(), description: z.string(), occurred_at: z.string(),
project_id: z.string().nullish(), started_at: z.string().nullish(), ended_at: z.string().nullish() })),
entities: z.array(z.object({ id: z.string(), canonical_label: z.string(), description: z.string(), confirmation_state: z.string() })),
relations: z.array(z.object({ id: z.string(), from_entity_id: z.string(), to_entity_id: z.string(), relation_type: z.string(), reason: z.string(), confirmation_state: z.string() })),
sources: z.array(z.object({ id: z.string(), title: z.string(), occurred_at: z.string() })),
eventEntities: z.array(z.object({ event_id: z.string(), entity_id: z.string() })),
eventSources: z.array(z.object({ event_id: z.string(), source_id: z.string() })),
attention: z.array(z.object({ start: z.string(), turns: z.number(), characters: z.number() })).optional()
})
export type SupervisionAttentionSlot = { start: string; turns: number; characters: number }
export type SupervisionGraphView = z.infer<typeof supervisionGraphViewSchema>