feat(events): add timeline and agent runtime baselines
This commit is contained in:
parent
c465fc3607
commit
3f2482b12c
119
packages/events/src/agent-runtime.test.ts
Normal file
119
packages/events/src/agent-runtime.test.ts
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
126
packages/events/src/agent-runtime.ts
Normal file
126
packages/events/src/agent-runtime.ts
Normal file
@ -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<typeof AgentDispatchContextSchema>;
|
||||
export type AgentSession = z.infer<typeof AgentSessionSchema>;
|
||||
export type AgentTask = z.infer<typeof AgentTaskSchema>;
|
||||
export type AgentTodo = z.infer<typeof AgentTodoSchema>;
|
||||
export type AgentRun = z.infer<typeof AgentRunSchema>;
|
||||
export type AgentApprovalCheckpoint = z.infer<typeof AgentApprovalCheckpointSchema>;
|
||||
export type AgentDispatchRequest = z.infer<typeof AgentDispatchRequestSchema>;
|
||||
export type AgentActionLog = z.infer<typeof AgentActionLogSchema>;
|
||||
@ -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';
|
||||
|
||||
94
packages/events/src/timeline.test.ts
Normal file
94
packages/events/src/timeline.test.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
||||
120
packages/events/src/timeline.ts
Normal file
120
packages/events/src/timeline.ts
Normal file
@ -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<typeof TimelineItemSchema>;
|
||||
|
||||
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)}`;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user