fix: preserve toolArguments through normalization and activity state

- Add toolArguments to normalizeRunTimelineEvent output so persisted
  sessions retain tool argument data across app restarts
- Add toolArguments field to AgentActivityState and propagate it in
  applySessionEventActivity for real-time activity labels
- Add tests for normalization round-trip with and without toolArguments
- Add test for activity state including toolArguments

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-08 12:55:03 +02:00
co-authored by Copilot
parent b9e73831e8
commit fa8f6ef4b3
4 changed files with 95 additions and 0 deletions
+2
View File
@@ -7,6 +7,7 @@ export interface AgentActivityState {
agentName: string;
activityType?: SessionEventRecord['activityType'];
toolName?: string;
toolArguments?: Record<string, unknown>;
}
export interface SessionUsageState {
@@ -45,6 +46,7 @@ export function applySessionEventActivity(
agentName: event.agentName?.trim() || event.agentId?.trim() || agentKey,
activityType: event.activityType,
toolName: event.toolName,
toolArguments: event.toolArguments,
},
},
};
+1
View File
@@ -494,6 +494,7 @@ function normalizeRunTimelineEvent(
targetAgentName: normalizeOptionalString(event.targetAgentName),
toolName: normalizeOptionalString(event.toolName),
toolCallId: normalizeOptionalString(event.toolCallId),
toolArguments: event.toolArguments,
fileChanges: normalizeToolCallFileChanges(event.fileChanges),
approvalId: normalizeOptionalString(event.approvalId),
approvalKind: event.approvalKind,
+26
View File
@@ -99,6 +99,32 @@ describe('session activity helpers', () => {
});
});
test('includes toolArguments in activity state', () => {
const event: SessionEventRecord = {
sessionId: 'session-1',
kind: 'agent-activity',
occurredAt: '2026-03-23T00:00:00.000Z',
activityType: 'tool-calling',
agentId: 'architect',
agentName: 'Architect',
toolName: 'view',
toolArguments: { path: 'src/main.ts', view_range: [1, 50] },
};
const result = applySessionEventActivity({}, event);
expect(result).toEqual({
'session-1': {
architect: {
agentId: 'architect',
agentName: 'Architect',
activityType: 'tool-calling',
toolName: 'view',
toolArguments: { path: 'src/main.ts', view_range: [1, 50] },
},
},
});
});
test('warns when an agent-activity event is missing identifiers', () => {
const originalWarn = console.warn;
const warnings: unknown[][] = [];
+66
View File
@@ -9,6 +9,7 @@ import {
upsertRunApprovalEvent,
upsertRunMessageEvent,
} from '@shared/domain/runTimeline';
import type { SessionRunRecord } from '@shared/domain/runTimeline';
import type { ProjectRecord } from '@shared/domain/project';
import type { PendingApprovalRecord } from '@shared/domain/approval';
@@ -265,6 +266,71 @@ describe('run timeline helpers', () => {
expect(normalizeSessionRunRecords(undefined)).toEqual([]);
});
test('preserves toolArguments through normalization round-trip', () => {
const baseRun = createSessionRunRecord({
requestId: 'turn-1',
project: createProject(),
workspaceKind: 'project',
workflow: createWorkflow(),
triggerMessageId: 'msg-user-1',
startedAt: '2026-03-23T00:00:01.000Z',
});
const run = appendRunActivityEvent(baseRun, {
activityType: 'tool-calling',
occurredAt: '2026-03-23T00:00:02.000Z',
agentId: 'agent-writer',
toolName: 'view',
toolCallId: 'tool-call-view-1',
toolArguments: { path: 'src/main.ts', view_range: [1, 50] },
});
// Simulate persisting and reloading via normalizeSessionRunRecords
const roundTripped = normalizeSessionRunRecords(
JSON.parse(JSON.stringify([run])) as SessionRunRecord[],
);
expect(roundTripped).toHaveLength(1);
const toolCallEvent = roundTripped[0].events.find(
(event) => event.kind === 'tool-call',
);
expect(toolCallEvent).toBeDefined();
expect(toolCallEvent!.toolArguments).toEqual({
path: 'src/main.ts',
view_range: [1, 50],
});
});
test('handles missing toolArguments gracefully during normalization', () => {
const baseRun = createSessionRunRecord({
requestId: 'turn-1',
project: createProject(),
workspaceKind: 'project',
workflow: createWorkflow(),
triggerMessageId: 'msg-user-1',
startedAt: '2026-03-23T00:00:01.000Z',
});
const run = appendRunActivityEvent(baseRun, {
activityType: 'tool-calling',
occurredAt: '2026-03-23T00:00:02.000Z',
agentId: 'agent-writer',
toolName: 'rg',
toolCallId: 'tool-call-rg-1',
// No toolArguments provided
});
const roundTripped = normalizeSessionRunRecords(
JSON.parse(JSON.stringify([run])) as SessionRunRecord[],
);
const toolCallEvent = roundTripped[0].events.find(
(event) => event.kind === 'tool-call',
);
expect(toolCallEvent).toBeDefined();
expect(toolCallEvent!.toolArguments).toBeUndefined();
});
test('tracks approval checkpoints as a single timeline event that can be resolved later', () => {
const baseRun = createSessionRunRecord({
requestId: 'turn-1',