From 3f2482b12cea51b14d11a4de841a223175c30d23 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Fri, 3 Apr 2026 19:53:41 -0700 Subject: [PATCH] feat(events): add timeline and agent runtime baselines --- packages/events/src/agent-runtime.test.ts | 119 ++++++++++++++++++++ packages/events/src/agent-runtime.ts | 126 ++++++++++++++++++++++ packages/events/src/index.ts | 27 +++++ packages/events/src/timeline.test.ts | 94 ++++++++++++++++ packages/events/src/timeline.ts | 120 +++++++++++++++++++++ 5 files changed, 486 insertions(+) create mode 100644 packages/events/src/agent-runtime.test.ts create mode 100644 packages/events/src/agent-runtime.ts create mode 100644 packages/events/src/timeline.test.ts create mode 100644 packages/events/src/timeline.ts diff --git a/packages/events/src/agent-runtime.test.ts b/packages/events/src/agent-runtime.test.ts new file mode 100644 index 00000000..707aa095 --- /dev/null +++ b/packages/events/src/agent-runtime.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, it } from 'vitest'; +import { + AgentActionLogSchema, + AgentApprovalCheckpointSchema, + AgentDispatchRequestSchema, + AgentRunSchema, + AgentSessionSchema, + AgentTaskSchema, + AgentTodoSchema, +} from './agent-runtime.js'; + +describe('agent runtime contract baseline', () => { + it('validates a dispatched Cowork session with task and approval checkpoint', () => { + const session = AgentSessionSchema.parse({ + sessionId: 'sess_cowork_1', + productId: 'cowork', + userId: 'saravana', + status: 'waiting-approval', + startedAt: '2026-04-03T16:00:00.000Z', + updatedAt: '2026-04-03T16:05:00.000Z', + resumable: true, + currentTaskId: 'task_cowork_1', + memoryRefs: ['mem_proj_1'], + artifactRefs: ['art_note_1'], + approvalRefs: ['approval_1'], + dispatchContext: { + originSurface: 'browser', + originProductId: 'notelett', + dispatchMode: 'remote', + initiatedAt: '2026-04-03T15:59:00.000Z', + }, + }); + + const task = AgentTaskSchema.parse({ + taskId: 'task_cowork_1', + sessionId: session.sessionId, + title: 'Investigate imported roadmap risks', + intent: 'Audit the imported note and produce findings', + status: 'blocked', + priority: 'high', + createdAt: '2026-04-03T16:00:00.000Z', + updatedAt: '2026-04-03T16:05:00.000Z', + }); + + const approval = AgentApprovalCheckpointSchema.parse({ + approvalId: 'approval_1', + sessionId: session.sessionId, + runId: 'run_cowork_1', + actionLabel: 'Delete temporary export', + riskLevel: 'high', + status: 'requested', + requestedAt: '2026-04-03T16:05:00.000Z', + resolvedAt: null, + resolverSurface: null, + }); + + expect(session.dispatchContext?.originSurface).toBe('browser'); + expect(task.status).toBe('blocked'); + expect(approval.status).toBe('requested'); + }); + + it('validates a scheduled FlowMonk run with todos and action logs', () => { + const dispatch = AgentDispatchRequestSchema.parse({ + dispatchId: 'dispatch_flow_1', + targetProductId: 'flowmonk', + targetExecutor: 'flowmonk', + userId: 'saravana', + title: 'Execute weekly planning refresh', + intent: 'Rebuild plan, routines, and habits for next week', + artifactRefs: ['art_plan_template_1'], + memoryRefs: ['mem_goal_1'], + dispatchContext: { + originSurface: 'web', + originProductId: 'flowmonk', + dispatchMode: 'scheduled', + initiatedAt: '2026-04-03T17:00:00.000Z', + }, + }); + + const run = AgentRunSchema.parse({ + runId: 'run_flow_1', + sessionId: 'sess_flow_1', + productId: 'flowmonk', + status: 'running', + startedAt: '2026-04-03T17:01:00.000Z', + completedAt: null, + checkpointArtifactId: 'art_plan_1', + correlationId: 'corr_phase2', + }); + + const todo = AgentTodoSchema.parse({ + todoId: 'todo_flow_1', + sessionId: 'sess_flow_1', + text: 'Generate routines from approved plan', + status: 'in-progress', + createdAt: '2026-04-03T17:01:30.000Z', + updatedAt: '2026-04-03T17:02:00.000Z', + }); + + const actionLog = AgentActionLogSchema.parse({ + actionLogId: 'alog_flow_1', + sessionId: 'sess_flow_1', + runId: 'run_flow_1', + eventName: 'agent.run.started', + occurredAt: '2026-04-03T17:01:00.000Z', + actorType: 'agent', + correlationId: 'corr_phase2', + payload: { + dispatchId: dispatch.dispatchId, + title: dispatch.title, + }, + }); + + expect(dispatch.dispatchContext.dispatchMode).toBe('scheduled'); + expect(run.checkpointArtifactId).toBe('art_plan_1'); + expect(todo.status).toBe('in-progress'); + expect(actionLog.eventName).toBe('agent.run.started'); + }); +}); diff --git a/packages/events/src/agent-runtime.ts b/packages/events/src/agent-runtime.ts new file mode 100644 index 00000000..7ca6b18a --- /dev/null +++ b/packages/events/src/agent-runtime.ts @@ -0,0 +1,126 @@ +import { z } from 'zod'; + +export const AgentDispatchContextSchema = z.object({ + originSurface: z.enum(['browser', 'mobile', 'desktop', 'web', 'product-api']), + originProductId: z.string().min(1), + dispatchMode: z.enum(['interactive', 'queued', 'scheduled', 'remote']), + initiatedAt: z.string().datetime(), +}); + +export const AgentSessionStatusSchema = z.enum([ + 'active', + 'paused', + 'waiting-approval', + 'completed', + 'failed', + 'cancelled', +]); + +export const AgentSessionSchema = z.object({ + sessionId: z.string().min(1), + productId: z.string().min(1), + userId: z.string().min(1), + status: AgentSessionStatusSchema, + startedAt: z.string().datetime(), + updatedAt: z.string().datetime(), + resumable: z.boolean(), + currentTaskId: z.string().min(1).nullable().optional(), + memoryRefs: z.array(z.string().min(1)), + artifactRefs: z.array(z.string().min(1)), + approvalRefs: z.array(z.string().min(1)), + dispatchContext: AgentDispatchContextSchema.nullable().optional(), +}); + +export const AgentTaskStatusSchema = z.enum([ + 'queued', + 'running', + 'blocked', + 'completed', + 'failed', + 'cancelled', +]); + +export const AgentTaskSchema = z.object({ + taskId: z.string().min(1), + sessionId: z.string().min(1), + title: z.string().min(1), + intent: z.string().min(1), + status: AgentTaskStatusSchema, + priority: z.string().min(1).nullable().optional(), + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), +}); + +export const AgentTodoStatusSchema = z.enum(['open', 'in-progress', 'done', 'dropped']); + +export const AgentTodoSchema = z.object({ + todoId: z.string().min(1), + sessionId: z.string().min(1), + text: z.string().min(1), + status: AgentTodoStatusSchema, + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), +}); + +export const AgentRunStatusSchema = z.enum([ + 'running', + 'paused', + 'completed', + 'failed', + 'cancelled', +]); + +export const AgentRunSchema = z.object({ + runId: z.string().min(1), + sessionId: z.string().min(1), + productId: z.string().min(1), + status: AgentRunStatusSchema, + startedAt: z.string().datetime(), + completedAt: z.string().datetime().nullable().optional(), + checkpointArtifactId: z.string().min(1).nullable().optional(), + correlationId: z.string().min(1).nullable().optional(), +}); + +export const AgentApprovalCheckpointSchema = z.object({ + approvalId: z.string().min(1), + sessionId: z.string().min(1), + runId: z.string().min(1), + actionLabel: z.string().min(1), + riskLevel: z.enum(['low', 'medium', 'high', 'critical']), + status: z.enum(['requested', 'approved', 'denied', 'expired']), + requestedAt: z.string().datetime(), + resolvedAt: z.string().datetime().nullable().optional(), + resolverSurface: z.enum(['mobile', 'web', 'desktop']).nullable().optional(), +}); + +export const AgentDispatchRequestSchema = z.object({ + dispatchId: z.string().min(1), + targetProductId: z.string().min(1), + targetExecutor: z.enum(['cowork', 'jarvisjr', 'flowmonk', 'generic-agent']), + userId: z.string().min(1), + title: z.string().min(1), + intent: z.string().min(1), + artifactRefs: z.array(z.string().min(1)).default([]), + memoryRefs: z.array(z.string().min(1)).default([]), + dispatchContext: AgentDispatchContextSchema, +}); + +export const AgentActionLogSchema = z.object({ + actionLogId: z.string().min(1), + sessionId: z.string().min(1), + runId: z.string().min(1), + eventName: z.string().min(1), + occurredAt: z.string().datetime(), + actorType: z.enum(['user', 'agent', 'system', 'device']), + correlationId: z.string().min(1).nullable().optional(), + payload: z.record(z.unknown()), +}); + +export type AgentDispatchContext = z.infer; +export type AgentSession = z.infer; +export type AgentTask = z.infer; +export type AgentTodo = z.infer; +export type AgentRun = z.infer; +export type AgentApprovalCheckpoint = z.infer; +export type AgentDispatchRequest = z.infer; +export type AgentActionLog = z.infer; diff --git a/packages/events/src/index.ts b/packages/events/src/index.ts index 636009dc..35e2911d 100644 --- a/packages/events/src/index.ts +++ b/packages/events/src/index.ts @@ -3,6 +3,16 @@ export type { EmitResult, EmitError } from './memory.js'; export { DurableEventBus } from './durable.js'; export type { DurableEventBusOptions } from './durable.js'; export { PlatformEventSchemas } from './types.js'; +export { + AgentActionLogSchema, + AgentApprovalCheckpointSchema, + AgentDispatchContextSchema, + AgentDispatchRequestSchema, + AgentRunSchema, + AgentSessionSchema, + AgentTaskSchema, + AgentTodoSchema, +} from './agent-runtime.js'; export { ArtifactCreatedBySchema, ArtifactLineageStepSchema, @@ -34,6 +44,22 @@ export { ArtifactLinkedEventSchema, ArtifactLinkedPayloadSchema, } from './ecosystem.js'; +export { + buildTimelineItem, + buildTimelineItems, + TimelineItemSchema, + TimelineVisibilitySchema, +} from './timeline.js'; +export type { + AgentActionLog, + AgentApprovalCheckpoint, + AgentDispatchContext, + AgentDispatchRequest, + AgentRun, + AgentSession, + AgentTask, + AgentTodo, +} from './agent-runtime.js'; export type { PlatformEventName, PlatformEventPayload, @@ -49,3 +75,4 @@ export type { Phase1EcosystemEvent, TranscriptArtifactEnvelope, } from './ecosystem.js'; +export type { TimelineItem } from './timeline.js'; diff --git a/packages/events/src/timeline.test.ts b/packages/events/src/timeline.test.ts new file mode 100644 index 00000000..bdfddf75 --- /dev/null +++ b/packages/events/src/timeline.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from 'vitest'; +import { buildTimelineItems, TimelineItemSchema } from './timeline.js'; + +describe('timeline contract baseline', () => { + it('builds unified timeline items across phases 1 to 3', () => { + const items = buildTimelineItems([ + { + eventId: 'evt_capture_1', + eventName: 'capture.transcript.created', + occurredAt: '2026-04-03T12:00:00.000Z', + productId: 'lysnrai', + artifactId: 'art_transcript_1', + actor: { actorType: 'user', actorId: 'saravana' }, + trace: { correlationId: 'corr_phase1', causationId: null, parentEventId: null }, + payload: { durationMs: 42150, transcriptSource: 'microphone' }, + }, + { + eventId: 'evt_plan_1', + eventName: 'artifact.created', + occurredAt: '2026-04-03T13:00:00.000Z', + productId: 'flowmonk', + artifactId: 'art_plan_1', + actor: { actorType: 'agent', actorId: 'phase2-plan-exporter' }, + trace: { correlationId: 'corr_phase2', causationId: null, parentEventId: null }, + payload: { artifactType: 'plan', title: 'FlowMonk weekly plan', status: 'draft' }, + }, + { + eventId: 'evt_trail_1', + eventName: 'artifact.created', + occurredAt: '2026-04-03T14:00:00.000Z', + productId: 'actiontrail', + artifactId: 'art_trail_1', + actor: { actorType: 'agent', actorId: 'phase3-audit-importer' }, + trace: { + correlationId: 'corr_phase3', + causationId: 'task_phase3', + parentEventId: 'task_phase3', + }, + payload: { + artifactType: 'trail-report', + title: 'Cowork audit report for task task_phase3', + status: 'recorded', + }, + }, + { + eventId: 'evt_memory_1', + eventName: 'memory.entry.created', + occurredAt: '2026-04-03T14:10:00.000Z', + productId: 'mindlyst', + artifactId: 'art_memory_1', + actor: { actorType: 'agent', actorId: 'phase3-audit-note-importer' }, + trace: { + correlationId: 'corr_phase3', + causationId: 'evt_note_linked_1', + parentEventId: 'evt_note_linked_1', + }, + payload: { + memoryKind: 'insight', + sourceArtifactIds: ['art_note_1', 'art_trail_1'], + }, + }, + ]); + + expect(items.map(item => item.eventName)).toEqual([ + 'memory.entry.created', + 'artifact.created', + 'artifact.created', + 'capture.transcript.created', + ]); + expect(items[0]?.title).toBe('Insight memory proposed'); + expect(items[1]?.title).toBe('Cowork audit report for task task_phase3'); + expect(items[2]?.summary).toBe('plan status: draft'); + expect(items[3]?.summary).toContain('microphone transcript captured'); + }); + + it('exposes a stable timeline item schema', () => { + const item = TimelineItemSchema.parse({ + itemId: 'timeline_evt_1', + occurredAt: '2026-04-03T14:10:00.000Z', + eventName: 'memory.entry.created', + productId: 'mindlyst', + title: 'Insight memory proposed', + summary: '2 source artifacts linked', + artifactRefs: ['art_memory_1'], + relatedEventIds: ['evt_note_linked_1'], + actorType: 'agent', + visibility: 'private', + correlationId: 'corr_phase3', + }); + + expect(item.visibility).toBe('private'); + expect(item.relatedEventIds).toHaveLength(1); + }); +}); diff --git a/packages/events/src/timeline.ts b/packages/events/src/timeline.ts new file mode 100644 index 00000000..64264832 --- /dev/null +++ b/packages/events/src/timeline.ts @@ -0,0 +1,120 @@ +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 [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)}`; +}