import { z } from 'zod'; import type { EcosystemEvent } from './ecosystem.js'; export const TimelineVisibilitySchema = z.enum(['private', 'org', 'shared', 'local-only']); export const TimelineItemSchema = z.object({ itemId: z.string().min(1), occurredAt: z.string().datetime(), eventName: z.string().min(1), productId: z.string().min(1), title: z.string().min(1), summary: z.string().nullable().optional(), artifactRefs: z.array(z.string().min(1)), relatedEventIds: z.array(z.string().min(1)), actorType: z.enum(['user', 'agent', 'system', 'device']), visibility: TimelineVisibilitySchema, correlationId: z.string().min(1).nullable().optional(), }); export type TimelineItem = z.infer; type EventLike = Pick< EcosystemEvent, | 'eventId' | 'eventName' | 'occurredAt' | 'productId' | 'artifactId' | 'actor' | 'trace' | 'payload' >; function titleForEvent(event: EventLike): string { if (event.eventName === 'capture.transcript.created') { return 'Transcript captured'; } if (event.eventName === 'memory.entry.created') { const kind = typeof event.payload.memoryKind === 'string' ? event.payload.memoryKind : 'memory'; return `${capitalize(kind)} memory proposed`; } if (event.eventName === 'artifact.created') { const type = typeof event.payload.artifactType === 'string' ? event.payload.artifactType : 'artifact'; const title = typeof event.payload.title === 'string' && event.payload.title ? event.payload.title : null; return title ? title : `${capitalize(type)} created`; } if (event.eventName === 'artifact.linked') { const relation = typeof event.payload.relation === 'string' ? event.payload.relation : 'linked'; return `Artifact ${relation}`; } return event.eventName; } function summaryForEvent(event: EventLike): string | null { if (event.eventName === 'capture.transcript.created') { const duration = typeof event.payload.durationMs === 'number' ? `${event.payload.durationMs}ms` : 'unknown duration'; const source = typeof event.payload.transcriptSource === 'string' ? event.payload.transcriptSource : 'unknown source'; return `${source} transcript captured (${duration})`; } if (event.eventName === 'memory.entry.created') { const sources = Array.isArray(event.payload.sourceArtifactIds) ? event.payload.sourceArtifactIds.length : 0; return `${sources} source artifact${sources === 1 ? '' : 's'} linked`; } if (event.eventName === 'artifact.created') { const type = typeof event.payload.artifactType === 'string' ? event.payload.artifactType : 'artifact'; const status = typeof event.payload.status === 'string' ? event.payload.status : 'created'; return `${type} status: ${status}`; } if (event.eventName === 'artifact.linked') { const target = typeof event.payload.targetArtifactId === 'string' ? event.payload.targetArtifactId : 'unknown target'; return `Linked to ${target}`; } return null; } function relatedEventIdsForEvent(event: EventLike): string[] { return Array.from( new Set( [event.trace.parentEventId, event.trace.causationId].filter( (value): value is string => typeof value === 'string' && value.length > 0 ) ) ); } export function buildTimelineItem(event: EventLike): TimelineItem { return TimelineItemSchema.parse({ itemId: `timeline_${event.eventId}`, occurredAt: event.occurredAt, eventName: event.eventName, productId: event.productId, title: titleForEvent(event), summary: summaryForEvent(event), artifactRefs: [event.artifactId].filter((value): value is string => Boolean(value)), relatedEventIds: relatedEventIdsForEvent(event), actorType: event.actor.actorType, visibility: 'private', correlationId: event.trace.correlationId ?? null, }); } export function buildTimelineItems(events: EventLike[]): TimelineItem[] { return events .map(buildTimelineItem) .sort((left, right) => right.occurredAt.localeCompare(left.occurredAt)); } function capitalize(value: string): string { return value.length === 0 ? value : `${value[0]!.toUpperCase()}${value.slice(1)}`; }