mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-28 23:48:39 +02:00
feat: add rich run timeline backend
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,7 @@ function createSession(
|
||||
updatedAt: '2026-03-23T00:00:00.000Z',
|
||||
status,
|
||||
messages,
|
||||
runs: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import { applySessionEventWorkspace } from '@renderer/lib/sessionWorkspace';
|
||||
import type { SessionEventRecord } from '@shared/domain/event';
|
||||
import type { SessionRunRecord } from '@shared/domain/runTimeline';
|
||||
import { createWorkspaceSettings } from '@shared/domain/tooling';
|
||||
import type { WorkspaceState } from '@shared/domain/workspace';
|
||||
|
||||
@@ -21,6 +22,7 @@ describe('session workspace helpers', () => {
|
||||
updatedAt: '2026-03-23T00:00:00.000Z',
|
||||
status: 'idle',
|
||||
messages: [],
|
||||
runs: [],
|
||||
},
|
||||
],
|
||||
selectedSessionId: 'session-1',
|
||||
@@ -185,6 +187,47 @@ describe('session workspace helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('applies live run snapshots without waiting for a workspace refresh', () => {
|
||||
const run: SessionRunRecord = {
|
||||
id: 'run-1',
|
||||
requestId: 'turn-1',
|
||||
projectId: 'project-1',
|
||||
projectPath: 'C:\\workspace\\alpha',
|
||||
workspaceKind: 'project',
|
||||
patternId: 'pattern-1',
|
||||
patternName: 'Sequential Review',
|
||||
patternMode: 'sequential',
|
||||
triggerMessageId: 'msg-user-1',
|
||||
startedAt: '2026-03-23T00:00:01.000Z',
|
||||
status: 'running',
|
||||
agents: [
|
||||
{
|
||||
agentId: 'agent-1',
|
||||
agentName: 'Writer',
|
||||
model: 'gpt-5.4',
|
||||
},
|
||||
],
|
||||
events: [
|
||||
{
|
||||
id: 'run-event-1',
|
||||
kind: 'run-started',
|
||||
occurredAt: '2026-03-23T00:00:01.000Z',
|
||||
status: 'completed',
|
||||
messageId: 'msg-user-1',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const updated = applySessionEventWorkspace(createWorkspace(), {
|
||||
sessionId: 'session-1',
|
||||
kind: 'run-updated',
|
||||
occurredAt: '2026-03-23T00:00:01.000Z',
|
||||
run,
|
||||
} satisfies SessionEventRecord);
|
||||
|
||||
expect(updated?.sessions[0].runs).toEqual([run]);
|
||||
});
|
||||
|
||||
test('ignores events for unknown sessions', () => {
|
||||
const workspace = createWorkspace();
|
||||
expect(
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
import type { PatternDefinition } from '@shared/domain/pattern';
|
||||
import {
|
||||
appendRunActivityEvent,
|
||||
completeSessionRunRecord,
|
||||
createSessionRunRecord,
|
||||
normalizeSessionRunRecords,
|
||||
upsertRunMessageEvent,
|
||||
} from '@shared/domain/runTimeline';
|
||||
import type { ProjectRecord } from '@shared/domain/project';
|
||||
|
||||
function createPattern(): PatternDefinition {
|
||||
return {
|
||||
id: 'pattern-sequential',
|
||||
name: 'Sequential Trio Review',
|
||||
description: 'Sequential handoff review flow.',
|
||||
mode: 'sequential',
|
||||
availability: 'available',
|
||||
maxIterations: 1,
|
||||
agents: [
|
||||
{
|
||||
id: 'agent-writer',
|
||||
name: 'Writer',
|
||||
description: 'Writes the draft.',
|
||||
instructions: 'Write.',
|
||||
model: 'gpt-5.4',
|
||||
},
|
||||
{
|
||||
id: 'agent-reviewer',
|
||||
name: 'Reviewer',
|
||||
description: 'Reviews the draft.',
|
||||
instructions: 'Review.',
|
||||
model: 'claude-sonnet-4.5',
|
||||
},
|
||||
],
|
||||
createdAt: '2026-03-23T00:00:00.000Z',
|
||||
updatedAt: '2026-03-23T00:00:00.000Z',
|
||||
};
|
||||
}
|
||||
|
||||
function createProject(): ProjectRecord {
|
||||
return {
|
||||
id: 'project-1',
|
||||
name: 'alpha',
|
||||
path: 'C:\\workspace\\alpha',
|
||||
addedAt: '2026-03-23T00:00:00.000Z',
|
||||
};
|
||||
}
|
||||
|
||||
describe('run timeline helpers', () => {
|
||||
test('creates a persistent run record with agent lanes and a trigger event', () => {
|
||||
const run = createSessionRunRecord({
|
||||
requestId: 'turn-1',
|
||||
project: createProject(),
|
||||
workspaceKind: 'project',
|
||||
pattern: createPattern(),
|
||||
triggerMessageId: 'msg-user-1',
|
||||
startedAt: '2026-03-23T00:00:01.000Z',
|
||||
});
|
||||
|
||||
expect(run).toMatchObject({
|
||||
requestId: 'turn-1',
|
||||
projectId: 'project-1',
|
||||
projectPath: 'C:\\workspace\\alpha',
|
||||
patternId: 'pattern-sequential',
|
||||
patternName: 'Sequential Trio Review',
|
||||
patternMode: 'sequential',
|
||||
triggerMessageId: 'msg-user-1',
|
||||
status: 'running',
|
||||
});
|
||||
expect(run.agents).toEqual([
|
||||
{
|
||||
agentId: 'agent-writer',
|
||||
agentName: 'Writer',
|
||||
model: 'gpt-5.4',
|
||||
reasoningEffort: undefined,
|
||||
},
|
||||
{
|
||||
agentId: 'agent-reviewer',
|
||||
agentName: 'Reviewer',
|
||||
model: 'claude-sonnet-4.5',
|
||||
reasoningEffort: undefined,
|
||||
},
|
||||
]);
|
||||
expect(run.events[0]).toMatchObject({
|
||||
kind: 'run-started',
|
||||
status: 'completed',
|
||||
messageId: 'msg-user-1',
|
||||
});
|
||||
});
|
||||
|
||||
test('records grouped message steps and settles the run on completion', () => {
|
||||
const baseRun = createSessionRunRecord({
|
||||
requestId: 'turn-1',
|
||||
project: createProject(),
|
||||
workspaceKind: 'project',
|
||||
pattern: createPattern(),
|
||||
triggerMessageId: 'msg-user-1',
|
||||
startedAt: '2026-03-23T00:00:01.000Z',
|
||||
});
|
||||
|
||||
const streamingRun = upsertRunMessageEvent(baseRun, {
|
||||
messageId: 'msg-assistant-1',
|
||||
authorName: 'Writer',
|
||||
content: 'Draft',
|
||||
occurredAt: '2026-03-23T00:00:02.000Z',
|
||||
status: 'running',
|
||||
});
|
||||
|
||||
const completedRun = completeSessionRunRecord(
|
||||
upsertRunMessageEvent(streamingRun, {
|
||||
messageId: 'msg-assistant-1',
|
||||
authorName: 'Writer',
|
||||
content: 'Draft polished into the final answer.',
|
||||
occurredAt: '2026-03-23T00:00:03.000Z',
|
||||
status: 'completed',
|
||||
}),
|
||||
'2026-03-23T00:00:04.000Z',
|
||||
);
|
||||
|
||||
const messageEvent = completedRun.events.find((event) => event.kind === 'message');
|
||||
expect(messageEvent).toMatchObject({
|
||||
messageId: 'msg-assistant-1',
|
||||
agentId: 'agent-writer',
|
||||
agentName: 'Writer',
|
||||
content: 'Draft polished into the final answer.',
|
||||
status: 'completed',
|
||||
updatedAt: '2026-03-23T00:00:03.000Z',
|
||||
});
|
||||
expect(completedRun.status).toBe('completed');
|
||||
expect(completedRun.completedAt).toBe('2026-03-23T00:00:04.000Z');
|
||||
expect(completedRun.events.at(-1)).toMatchObject({
|
||||
kind: 'run-completed',
|
||||
status: 'completed',
|
||||
});
|
||||
});
|
||||
|
||||
test('captures handoffs with explicit source and target agents', () => {
|
||||
const run = appendRunActivityEvent(
|
||||
createSessionRunRecord({
|
||||
requestId: 'turn-1',
|
||||
project: createProject(),
|
||||
workspaceKind: 'project',
|
||||
pattern: createPattern(),
|
||||
triggerMessageId: 'msg-user-1',
|
||||
startedAt: '2026-03-23T00:00:01.000Z',
|
||||
}),
|
||||
{
|
||||
activityType: 'handoff',
|
||||
occurredAt: '2026-03-23T00:00:02.000Z',
|
||||
sourceAgentId: 'agent-writer',
|
||||
agentId: 'agent-reviewer',
|
||||
},
|
||||
);
|
||||
|
||||
expect(run.events.at(-1)).toMatchObject({
|
||||
kind: 'handoff',
|
||||
agentId: 'agent-writer',
|
||||
agentName: 'Writer',
|
||||
sourceAgentId: 'agent-writer',
|
||||
sourceAgentName: 'Writer',
|
||||
targetAgentId: 'agent-reviewer',
|
||||
targetAgentName: 'Reviewer',
|
||||
});
|
||||
});
|
||||
|
||||
test('normalizes missing run collections to an empty array', () => {
|
||||
expect(normalizeSessionRunRecords(undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -51,6 +51,7 @@ function createSession(overrides?: Partial<SessionRecord>): SessionRecord {
|
||||
updatedAt: '2026-03-23T00:00:00.000Z',
|
||||
status: 'idle',
|
||||
messages: [],
|
||||
runs: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ function createSession(overrides?: Partial<SessionRecord>): SessionRecord {
|
||||
createdAt: '2026-03-23T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
runs: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -148,6 +149,7 @@ describe('session library helpers', () => {
|
||||
updatedAt: '2026-03-23T00:07:00.000Z',
|
||||
});
|
||||
expect(session.messages[0]?.pending).toBe(false);
|
||||
expect(session.runs).toEqual([]);
|
||||
});
|
||||
|
||||
test('searches across session title, messages, projects, and patterns', () => {
|
||||
|
||||
Reference in New Issue
Block a user