@qiaolei81/copilot-session-viewer 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +25 -2
- package/.nycrc +0 -29
- package/AGENTS.md +0 -109
- package/CHANGELOG.md +0 -313
- package/CONTRIBUTING.md +0 -104
- package/RELEASE.md +0 -146
- package/docs/API.md +0 -471
- package/docs/DEVELOPMENT.md +0 -556
- package/docs/INSTALLATION.md +0 -329
- package/docs/README.md +0 -102
- package/docs/TROUBLESHOOTING.md +0 -630
- package/docs/images/homepage.png +0 -0
- package/docs/images/session-detail.png +0 -0
- package/docs/images/time-analysis.png +0 -0
- package/docs/unified-event-format-design.md +0 -844
- package/docs/unified-event-format-implementation.md +0 -350
- package/eslint.config.mjs +0 -133
- package/examples/parser-usage.js +0 -114
- package/scripts/release.sh +0 -43
|
@@ -1,844 +0,0 @@
|
|
|
1
|
-
# Unified Frontend Event Format - Design Document
|
|
2
|
-
|
|
3
|
-
## Executive Summary
|
|
4
|
-
|
|
5
|
-
This document proposes a unified event format to eliminate format-specific checks in the frontend and simplify tool call handling across three AI session formats: Copilot, Claude, and Pi-Mono.
|
|
6
|
-
|
|
7
|
-
**Current Problem:** The codebase handles three different tool event structures:
|
|
8
|
-
- **Copilot:** Independent `tool.execution_start` and `tool.execution_complete` events
|
|
9
|
-
- **Claude:** Embedded `tool_use` and `tool_result` in message content, later matched and merged
|
|
10
|
-
- **Pi-Mono:** Embedded tools in `assistant.message.data.tools` with results already merged
|
|
11
|
-
|
|
12
|
-
The frontend contains multiple format checks (`tool.type === 'tool_use'`, `_matched` flags, etc.) scattered across views.
|
|
13
|
-
|
|
14
|
-
**Proposed Solution:** Establish a single normalized tool representation at the API response layer that abstracts away source differences.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## 1. Current State Analysis
|
|
19
|
-
|
|
20
|
-
### 1.1 Current Tool Event Formats
|
|
21
|
-
|
|
22
|
-
#### Copilot Format (Independent Events)
|
|
23
|
-
```javascript
|
|
24
|
-
// Two separate events in the stream
|
|
25
|
-
{
|
|
26
|
-
type: 'tool.execution_start',
|
|
27
|
-
timestamp: '2024-01-01T10:00:00Z',
|
|
28
|
-
data: {
|
|
29
|
-
toolCallId: 'abc123',
|
|
30
|
-
toolName: 'Read',
|
|
31
|
-
arguments: { file_path: '/path/to/file' }
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
type: 'tool.execution_complete',
|
|
37
|
-
timestamp: '2024-01-01T10:00:05Z',
|
|
38
|
-
data: {
|
|
39
|
-
toolCallId: 'abc123',
|
|
40
|
-
toolName: 'Read',
|
|
41
|
-
result: 'File contents...',
|
|
42
|
-
error: null
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// After matching by sessionService._matchCopilotToolCalls():
|
|
47
|
-
{
|
|
48
|
-
type: 'assistant.message',
|
|
49
|
-
data: {
|
|
50
|
-
tools: [{
|
|
51
|
-
type: 'tool_use',
|
|
52
|
-
id: 'abc123',
|
|
53
|
-
name: 'Read',
|
|
54
|
-
input: { file_path: '/path/to/file' },
|
|
55
|
-
result: 'File contents...',
|
|
56
|
-
status: 'completed',
|
|
57
|
-
_matched: true
|
|
58
|
-
}]
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
#### Claude Format (Embedded with Separate Result)
|
|
64
|
-
```javascript
|
|
65
|
-
// User or Assistant message with tool_use
|
|
66
|
-
{
|
|
67
|
-
type: 'assistant',
|
|
68
|
-
message: {
|
|
69
|
-
content: [
|
|
70
|
-
{ type: 'text', text: 'Let me read that file.' },
|
|
71
|
-
{
|
|
72
|
-
type: 'tool_use',
|
|
73
|
-
id: 'xyz789',
|
|
74
|
-
name: 'Read',
|
|
75
|
-
input: { file_path: '/path/to/file' }
|
|
76
|
-
}
|
|
77
|
-
]
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Later: User message with tool_result
|
|
82
|
-
{
|
|
83
|
-
type: 'user',
|
|
84
|
-
message: {
|
|
85
|
-
content: [
|
|
86
|
-
{
|
|
87
|
-
type: 'tool_result',
|
|
88
|
-
tool_use_id: 'xyz789',
|
|
89
|
-
content: 'File contents...'
|
|
90
|
-
}
|
|
91
|
-
]
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// After normalization and matching by sessionService._normalizeEvent() + _matchClaudeToolResults():
|
|
96
|
-
{
|
|
97
|
-
type: 'assistant',
|
|
98
|
-
data: {
|
|
99
|
-
message: 'Let me read that file.',
|
|
100
|
-
tools: [{
|
|
101
|
-
type: 'tool_use',
|
|
102
|
-
id: 'xyz789',
|
|
103
|
-
name: 'Read',
|
|
104
|
-
input: { file_path: '/path/to/file' },
|
|
105
|
-
result: 'File contents...',
|
|
106
|
-
_matched: true
|
|
107
|
-
}]
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
#### Pi-Mono Format (Already Merged)
|
|
113
|
-
```javascript
|
|
114
|
-
// Pi-Mono parser already merges tools at parse time
|
|
115
|
-
{
|
|
116
|
-
type: 'assistant.message',
|
|
117
|
-
timestamp: '2024-01-01T10:00:00Z',
|
|
118
|
-
data: {
|
|
119
|
-
message: 'Let me read that file.',
|
|
120
|
-
tools: [{
|
|
121
|
-
name: 'Read',
|
|
122
|
-
input: { file_path: '/path/to/file' },
|
|
123
|
-
result: 'File contents...',
|
|
124
|
-
status: 'completed',
|
|
125
|
-
isError: false
|
|
126
|
-
}]
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### 1.2 Current Frontend Format Checks
|
|
132
|
-
|
|
133
|
-
**In `views/session-vue.ejs` (lines 1581-1605):**
|
|
134
|
-
```javascript
|
|
135
|
-
const getToolGroups = (event) => {
|
|
136
|
-
if (event.data?.tools && Array.isArray(event.data.tools)) {
|
|
137
|
-
return event.data.tools.map(tool => {
|
|
138
|
-
// Claude format check: {type: 'tool_use', name, input, _matched, result}
|
|
139
|
-
if (tool.type === 'tool_use') {
|
|
140
|
-
return {
|
|
141
|
-
tool: tool.name,
|
|
142
|
-
start: { data: { toolName: tool.name, arguments: tool.input } },
|
|
143
|
-
complete: tool._matched ? {
|
|
144
|
-
data: { result: tool.result, isError: tool.error ? true : false }
|
|
145
|
-
} : null
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
// Copilot/Pi-Mono format (already matched)
|
|
149
|
-
return tool;
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
return [];
|
|
153
|
-
};
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
**In `views/time-analyze.ejs` (lines 1088-1098, 1350-1360):**
|
|
157
|
-
```javascript
|
|
158
|
-
// Multiple places checking for embedded tools
|
|
159
|
-
if (e.type === 'assistant.message' && e.data?.tools) {
|
|
160
|
-
for (const tool of e.data.tools) {
|
|
161
|
-
innerEvents.push({
|
|
162
|
-
type: 'tool.execution_start',
|
|
163
|
-
timestamp: e.timestamp,
|
|
164
|
-
data: { tool: tool.name, toolName: tool.name, arguments: tool.input }
|
|
165
|
-
});
|
|
166
|
-
// ... similar expansion logic
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### 1.3 Issues with Current Approach
|
|
172
|
-
|
|
173
|
-
1. **Scattered Format Logic:** Frontend needs to understand internal matching state (`_matched` flag)
|
|
174
|
-
2. **Inconsistent Tool Schema:** Different fields across formats (`status` vs `_matched`, `isError` vs `error`)
|
|
175
|
-
3. **Duplicate Code:** Multiple places converting between formats
|
|
176
|
-
4. **Brittleness:** Changes to parser matching logic require frontend updates
|
|
177
|
-
5. **Testing Complexity:** Need to test all format variations in frontend code
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
## 2. Proposed Unified Format
|
|
182
|
-
|
|
183
|
-
### 2.1 Normalized Tool Schema
|
|
184
|
-
|
|
185
|
-
**Single canonical tool representation:**
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
interface UnifiedToolCall {
|
|
189
|
-
// Core identification
|
|
190
|
-
id: string; // Unique tool call ID
|
|
191
|
-
name: string; // Tool name (e.g., 'Read', 'Write', 'Bash')
|
|
192
|
-
|
|
193
|
-
// Timing
|
|
194
|
-
startTime: string; // ISO 8601 timestamp
|
|
195
|
-
endTime: string | null; // ISO 8601 timestamp, null if still running
|
|
196
|
-
|
|
197
|
-
// Status
|
|
198
|
-
status: 'pending' | 'running' | 'completed' | 'error';
|
|
199
|
-
|
|
200
|
-
// Input/Output
|
|
201
|
-
input: Record<string, any>; // Tool parameters
|
|
202
|
-
result: string | null; // Tool result (null if not completed)
|
|
203
|
-
error: string | null; // Error message (null if no error)
|
|
204
|
-
|
|
205
|
-
// Metadata (optional, for advanced use cases)
|
|
206
|
-
metadata?: {
|
|
207
|
-
source?: string; // 'copilot' | 'claude' | 'pi-mono'
|
|
208
|
-
subagentId?: string; // If tool was executed by subagent
|
|
209
|
-
retryCount?: number; // Number of retries
|
|
210
|
-
duration?: number; // Duration in milliseconds
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### 2.2 Normalized Assistant Message Schema
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
interface UnifiedAssistantMessage {
|
|
219
|
-
type: 'assistant.message';
|
|
220
|
-
id: string;
|
|
221
|
-
timestamp: string;
|
|
222
|
-
parentId?: string;
|
|
223
|
-
|
|
224
|
-
data: {
|
|
225
|
-
message: string; // Text content
|
|
226
|
-
model?: string; // Model used
|
|
227
|
-
tools?: UnifiedToolCall[]; // Array of tools (if any)
|
|
228
|
-
usage?: { // Token usage
|
|
229
|
-
inputTokens?: number;
|
|
230
|
-
outputTokens?: number;
|
|
231
|
-
};
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
// Internal metadata (not for frontend consumption)
|
|
235
|
-
_fileIndex?: number;
|
|
236
|
-
_turnNumber?: number;
|
|
237
|
-
_subagent?: {
|
|
238
|
-
id: string;
|
|
239
|
-
name: string;
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### 2.3 Timeline Event Schema (for time-analyze views)
|
|
245
|
-
|
|
246
|
-
```typescript
|
|
247
|
-
interface UnifiedTimelineEvent {
|
|
248
|
-
type: 'user.message' | 'assistant.turn_start' | 'assistant.turn_complete'
|
|
249
|
-
| 'tool.execution_start' | 'tool.execution_complete'
|
|
250
|
-
| 'subagent.started' | 'subagent.completed';
|
|
251
|
-
id: string;
|
|
252
|
-
timestamp: string;
|
|
253
|
-
parentId?: string;
|
|
254
|
-
|
|
255
|
-
data: {
|
|
256
|
-
message?: string;
|
|
257
|
-
tool?: UnifiedToolCall; // For tool events
|
|
258
|
-
toolCallId?: string; // For subagent events
|
|
259
|
-
agentName?: string; // For subagent events
|
|
260
|
-
[key: string]: any; // Flexible for other event types
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## 3. Transformation Strategy
|
|
268
|
-
|
|
269
|
-
### 3.1 Where to Transform
|
|
270
|
-
|
|
271
|
-
**Recommendation: Transform at the SessionService API layer** (Option B)
|
|
272
|
-
|
|
273
|
-
**Rationale:**
|
|
274
|
-
- ✅ Single point of transformation
|
|
275
|
-
- ✅ Frontend receives clean, consistent data
|
|
276
|
-
- ✅ Parsers remain focused on format-specific reading
|
|
277
|
-
- ✅ Easier to test (one transformation module vs. three parsers)
|
|
278
|
-
- ✅ Simpler to add new formats in the future
|
|
279
|
-
|
|
280
|
-
**Architecture:**
|
|
281
|
-
|
|
282
|
-
```
|
|
283
|
-
[Raw Files] → [Parser Layer] → [Matching Layer] → [Normalization Layer] → [API Response]
|
|
284
|
-
(Pi-Mono, (Source- (Unified Schema) (Clean JSON)
|
|
285
|
-
Copilot, Specific)
|
|
286
|
-
Claude)
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### 3.2 Implementation Approach
|
|
290
|
-
|
|
291
|
-
#### Phase 1: Add Unified Normalizer Module
|
|
292
|
-
|
|
293
|
-
Create `src/services/eventNormalizer.js`:
|
|
294
|
-
|
|
295
|
-
```javascript
|
|
296
|
-
class EventNormalizer {
|
|
297
|
-
/**
|
|
298
|
-
* Normalize all events to unified format
|
|
299
|
-
* @param {Array} events - Raw events from parsers
|
|
300
|
-
* @param {string} source - 'copilot' | 'claude' | 'pi-mono'
|
|
301
|
-
* @returns {Array} - Normalized events
|
|
302
|
-
*/
|
|
303
|
-
normalizeEvents(events, source) {
|
|
304
|
-
return events.map(event => this.normalizeEvent(event, source));
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Normalize a single event
|
|
309
|
-
*/
|
|
310
|
-
normalizeEvent(event, source) {
|
|
311
|
-
// Handle assistant messages with tools
|
|
312
|
-
if (this._isAssistantMessage(event)) {
|
|
313
|
-
return this._normalizeAssistantMessage(event, source);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Handle timeline events
|
|
317
|
-
if (this._isTimelineEvent(event)) {
|
|
318
|
-
return this._normalizeTimelineEvent(event, source);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Pass through other events
|
|
322
|
-
return event;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Normalize assistant message with embedded tools
|
|
327
|
-
*/
|
|
328
|
-
_normalizeAssistantMessage(event, source) {
|
|
329
|
-
const normalized = { ...event };
|
|
330
|
-
|
|
331
|
-
if (event.data?.tools && Array.isArray(event.data.tools)) {
|
|
332
|
-
normalized.data.tools = event.data.tools.map(tool =>
|
|
333
|
-
this._normalizeToolCall(tool, source, event.timestamp)
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return normalized;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Normalize a tool call to unified schema
|
|
342
|
-
*/
|
|
343
|
-
_normalizeToolCall(tool, source, messageTimestamp) {
|
|
344
|
-
// Handle Copilot/Claude format with _matched flag
|
|
345
|
-
if (tool.type === 'tool_use') {
|
|
346
|
-
return {
|
|
347
|
-
id: tool.id,
|
|
348
|
-
name: tool.name,
|
|
349
|
-
startTime: messageTimestamp,
|
|
350
|
-
endTime: tool._matched ? messageTimestamp : null,
|
|
351
|
-
status: this._computeStatus(tool),
|
|
352
|
-
input: tool.input || {},
|
|
353
|
-
result: tool.result || null,
|
|
354
|
-
error: tool.error || null,
|
|
355
|
-
metadata: {
|
|
356
|
-
source,
|
|
357
|
-
matched: tool._matched
|
|
358
|
-
}
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Handle Pi-Mono format (already has status)
|
|
363
|
-
if (tool.name && tool.status) {
|
|
364
|
-
return {
|
|
365
|
-
id: tool.id || this._generateToolId(),
|
|
366
|
-
name: tool.name,
|
|
367
|
-
startTime: messageTimestamp,
|
|
368
|
-
endTime: tool.status === 'completed' ? messageTimestamp : null,
|
|
369
|
-
status: tool.status,
|
|
370
|
-
input: tool.input || {},
|
|
371
|
-
result: tool.result || null,
|
|
372
|
-
error: tool.isError ? tool.result : null,
|
|
373
|
-
metadata: { source }
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Fallback: return as-is with minimal normalization
|
|
378
|
-
return {
|
|
379
|
-
id: tool.id || this._generateToolId(),
|
|
380
|
-
name: tool.name || 'unknown',
|
|
381
|
-
startTime: messageTimestamp,
|
|
382
|
-
endTime: null,
|
|
383
|
-
status: 'running',
|
|
384
|
-
input: tool.input || {},
|
|
385
|
-
result: null,
|
|
386
|
-
error: null,
|
|
387
|
-
metadata: { source }
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
_computeStatus(tool) {
|
|
392
|
-
if (tool.error) return 'error';
|
|
393
|
-
if (tool._matched && tool.result !== undefined) return 'completed';
|
|
394
|
-
if (tool._matched === false) return 'running';
|
|
395
|
-
return 'completed'; // Default for matched tools
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
_generateToolId() {
|
|
399
|
-
return `tool-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
_isAssistantMessage(event) {
|
|
403
|
-
return event.type === 'assistant.message' || event.type === 'assistant';
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
_isTimelineEvent(event) {
|
|
407
|
-
return event.type?.startsWith('tool.') ||
|
|
408
|
-
event.type?.startsWith('subagent.');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
_normalizeTimelineEvent(event, source) {
|
|
412
|
-
// For tool.execution_start/complete, ensure consistent schema
|
|
413
|
-
if (event.type === 'tool.execution_start' || event.type === 'tool.execution_complete') {
|
|
414
|
-
return {
|
|
415
|
-
...event,
|
|
416
|
-
data: {
|
|
417
|
-
...event.data,
|
|
418
|
-
toolCallId: event.data?.toolCallId || event.data?.id,
|
|
419
|
-
toolName: event.data?.toolName || event.data?.tool || event.data?.name
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
return event;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
module.exports = EventNormalizer;
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
#### Phase 2: Integrate into SessionService
|
|
432
|
-
|
|
433
|
-
Modify `src/services/sessionService.js`:
|
|
434
|
-
|
|
435
|
-
```javascript
|
|
436
|
-
const EventNormalizer = require('./eventNormalizer');
|
|
437
|
-
|
|
438
|
-
class SessionService {
|
|
439
|
-
constructor(sessionDir) {
|
|
440
|
-
// ... existing code ...
|
|
441
|
-
this.normalizer = new EventNormalizer();
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async getSessionEvents(sessionId) {
|
|
445
|
-
// ... existing parsing logic ...
|
|
446
|
-
|
|
447
|
-
// After expansion but before return:
|
|
448
|
-
events = this.normalizer.normalizeEvents(events, session.source);
|
|
449
|
-
|
|
450
|
-
return events;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
#### Phase 3: Simplify Frontend Code
|
|
456
|
-
|
|
457
|
-
**Before (session-vue.ejs):**
|
|
458
|
-
```javascript
|
|
459
|
-
const getToolGroups = (event) => {
|
|
460
|
-
if (event.data?.tools && Array.isArray(event.data.tools)) {
|
|
461
|
-
return event.data.tools.map(tool => {
|
|
462
|
-
if (tool.type === 'tool_use') {
|
|
463
|
-
return {
|
|
464
|
-
tool: tool.name,
|
|
465
|
-
start: { data: { toolName: tool.name, arguments: tool.input } },
|
|
466
|
-
complete: tool._matched ? {
|
|
467
|
-
data: { result: tool.result, isError: tool.error ? true : false }
|
|
468
|
-
} : null
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
return tool;
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
return [];
|
|
475
|
-
};
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
**After:**
|
|
479
|
-
```javascript
|
|
480
|
-
const getToolGroups = (event) => {
|
|
481
|
-
// Tools are already normalized to unified format
|
|
482
|
-
return event.data?.tools || [];
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
const getToolDuration = (tool) => {
|
|
486
|
-
if (tool.startTime && tool.endTime) {
|
|
487
|
-
const ms = new Date(tool.endTime) - new Date(tool.startTime);
|
|
488
|
-
return formatDuration(ms);
|
|
489
|
-
}
|
|
490
|
-
return null;
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
const getToolStatus = (tool) => {
|
|
494
|
-
switch (tool.status) {
|
|
495
|
-
case 'completed': return { icon: '✓', color: 'text-success' };
|
|
496
|
-
case 'error': return { icon: '✗', color: 'text-error' };
|
|
497
|
-
case 'running': return { icon: '⋯', color: 'text-warning' };
|
|
498
|
-
default: return { icon: '?', color: 'text-muted' };
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
---
|
|
504
|
-
|
|
505
|
-
## 4. Code Change Checklist
|
|
506
|
-
|
|
507
|
-
### 4.1 Backend Changes
|
|
508
|
-
|
|
509
|
-
#### New Files
|
|
510
|
-
- [ ] `src/services/eventNormalizer.js` - Main normalization module
|
|
511
|
-
- [ ] `src/services/__tests__/eventNormalizer.test.js` - Unit tests
|
|
512
|
-
|
|
513
|
-
#### Modified Files
|
|
514
|
-
- [ ] `src/services/sessionService.js`
|
|
515
|
-
- [ ] Import EventNormalizer
|
|
516
|
-
- [ ] Add normalizer instance to constructor
|
|
517
|
-
- [ ] Call `normalizer.normalizeEvents()` in `getSessionEvents()` after expansion
|
|
518
|
-
- [ ] Remove frontend-specific format conversion logic (keep matching logic)
|
|
519
|
-
|
|
520
|
-
- [ ] `lib/parsers/pi-mono-parser.js`
|
|
521
|
-
- [ ] No changes needed (parser output will be normalized by eventNormalizer)
|
|
522
|
-
|
|
523
|
-
- [ ] `package.json`
|
|
524
|
-
- [ ] No new dependencies needed
|
|
525
|
-
|
|
526
|
-
#### API Contract
|
|
527
|
-
- [ ] Document unified event schema in API docs
|
|
528
|
-
- [ ] Add API version header for gradual migration (optional)
|
|
529
|
-
|
|
530
|
-
### 4.2 Frontend Changes
|
|
531
|
-
|
|
532
|
-
#### Modified Files
|
|
533
|
-
- [ ] `views/session-vue.ejs`
|
|
534
|
-
- [ ] Simplify `getToolGroups()` - remove format checks
|
|
535
|
-
- [ ] Simplify `getToolStatus()` - use unified `status` field
|
|
536
|
-
- [ ] Add `getToolDuration()` - calculate from `startTime`/`endTime`
|
|
537
|
-
- [ ] Remove `tool.type === 'tool_use'` checks
|
|
538
|
-
- [ ] Remove `_matched` flag checks
|
|
539
|
-
- [ ] Use `tool.error` directly instead of `tool.isError`
|
|
540
|
-
|
|
541
|
-
- [ ] `views/time-analyze.ejs`
|
|
542
|
-
- [ ] Remove embedded tool expansion logic (lines ~1088-1098, 1350-1360)
|
|
543
|
-
- [ ] Use timeline events directly (already expanded by backend)
|
|
544
|
-
- [ ] Simplify tool event markers (use unified schema)
|
|
545
|
-
|
|
546
|
-
- [ ] `views/time-analyze-v2.ejs`
|
|
547
|
-
- [ ] Similar changes to time-analyze.ejs
|
|
548
|
-
- [ ] Update Gantt chart generation to use unified schema
|
|
549
|
-
|
|
550
|
-
#### Removed Code
|
|
551
|
-
- [ ] All `tool.type === 'tool_use'` conditionals
|
|
552
|
-
- [ ] All `_matched` flag checks
|
|
553
|
-
- [ ] Format-specific tool expansion loops
|
|
554
|
-
- [ ] Duplicate status/error mapping logic
|
|
555
|
-
|
|
556
|
-
### 4.3 Testing Changes
|
|
557
|
-
|
|
558
|
-
#### New Tests
|
|
559
|
-
- [ ] `eventNormalizer.test.js`
|
|
560
|
-
- [ ] Test Copilot format normalization
|
|
561
|
-
- [ ] Test Claude format normalization
|
|
562
|
-
- [ ] Test Pi-Mono format normalization
|
|
563
|
-
- [ ] Test tool status computation
|
|
564
|
-
- [ ] Test error handling
|
|
565
|
-
- [ ] Test edge cases (missing fields, orphaned events)
|
|
566
|
-
|
|
567
|
-
#### Updated Tests
|
|
568
|
-
- [ ] `sessionService.test.js`
|
|
569
|
-
- [ ] Update assertions to check for unified format
|
|
570
|
-
- [ ] Add integration tests with normalizer
|
|
571
|
-
|
|
572
|
-
#### Manual Testing
|
|
573
|
-
- [ ] Load Copilot session, verify tools display correctly
|
|
574
|
-
- [ ] Load Claude session, verify tools display correctly
|
|
575
|
-
- [ ] Load Pi-Mono session, verify tools display correctly
|
|
576
|
-
- [ ] Test time-analyze view with all formats
|
|
577
|
-
- [ ] Test subagent tool attribution
|
|
578
|
-
- [ ] Test error states and edge cases
|
|
579
|
-
|
|
580
|
-
---
|
|
581
|
-
|
|
582
|
-
## 5. Migration Considerations
|
|
583
|
-
|
|
584
|
-
### 5.1 Backward Compatibility
|
|
585
|
-
|
|
586
|
-
**No Breaking Changes Required:**
|
|
587
|
-
- Current frontend code will continue to work during transition
|
|
588
|
-
- Unified format is a superset of existing formats
|
|
589
|
-
- Can add feature flag for gradual rollout
|
|
590
|
-
|
|
591
|
-
### 5.2 Feature Flags
|
|
592
|
-
|
|
593
|
-
```javascript
|
|
594
|
-
// In sessionService.js
|
|
595
|
-
class SessionService {
|
|
596
|
-
constructor(sessionDir, options = {}) {
|
|
597
|
-
this.useUnifiedFormat = options.useUnifiedFormat ?? true;
|
|
598
|
-
this.normalizer = new EventNormalizer();
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
async getSessionEvents(sessionId) {
|
|
602
|
-
// ... existing code ...
|
|
603
|
-
|
|
604
|
-
if (this.useUnifiedFormat) {
|
|
605
|
-
events = this.normalizer.normalizeEvents(events, session.source);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
return events;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
### 5.3 Rollout Plan
|
|
614
|
-
|
|
615
|
-
**Phase 1: Backend (Week 1-2)**
|
|
616
|
-
1. Implement EventNormalizer module with tests
|
|
617
|
-
2. Integrate into SessionService with feature flag (default ON)
|
|
618
|
-
3. Run backend tests to ensure no regressions
|
|
619
|
-
4. Deploy to dev environment
|
|
620
|
-
|
|
621
|
-
**Phase 2: Frontend (Week 3)**
|
|
622
|
-
1. Update session-vue.ejs to use unified format
|
|
623
|
-
2. Test manually with all three formats
|
|
624
|
-
3. Fix any edge cases discovered
|
|
625
|
-
4. Deploy to staging
|
|
626
|
-
|
|
627
|
-
**Phase 3: Timeline Views (Week 4)**
|
|
628
|
-
1. Update time-analyze.ejs and time-analyze-v2.ejs
|
|
629
|
-
2. Test Gantt chart generation
|
|
630
|
-
3. Verify performance with large sessions
|
|
631
|
-
4. Deploy to production
|
|
632
|
-
|
|
633
|
-
**Phase 4: Cleanup (Week 5)**
|
|
634
|
-
1. Remove feature flag
|
|
635
|
-
2. Remove dead code (old format checks)
|
|
636
|
-
3. Update documentation
|
|
637
|
-
4. Add inline comments for future maintainers
|
|
638
|
-
|
|
639
|
-
### 5.4 Monitoring
|
|
640
|
-
|
|
641
|
-
**Metrics to Track:**
|
|
642
|
-
- API response time (should not increase)
|
|
643
|
-
- Frontend render time (should decrease slightly)
|
|
644
|
-
- Error rates (should not increase)
|
|
645
|
-
- User-reported bugs (should decrease over time)
|
|
646
|
-
|
|
647
|
-
**Rollback Strategy:**
|
|
648
|
-
- Keep feature flag for quick rollback
|
|
649
|
-
- Monitor logs for normalization errors
|
|
650
|
-
- Have old code ready to revert if needed
|
|
651
|
-
|
|
652
|
-
---
|
|
653
|
-
|
|
654
|
-
## 6. Benefits Summary
|
|
655
|
-
|
|
656
|
-
### 6.1 Code Quality
|
|
657
|
-
- ✅ **Reduced Complexity:** ~200 lines of frontend code removed
|
|
658
|
-
- ✅ **Single Source of Truth:** All format logic in one module
|
|
659
|
-
- ✅ **Easier Testing:** One set of tests instead of scattered checks
|
|
660
|
-
- ✅ **Better Maintainability:** Changes in one place, not three
|
|
661
|
-
|
|
662
|
-
### 6.2 Developer Experience
|
|
663
|
-
- ✅ **Simpler Frontend:** No format checks needed
|
|
664
|
-
- ✅ **Clear API Contract:** Well-documented unified schema
|
|
665
|
-
- ✅ **Faster Development:** Less time debugging format issues
|
|
666
|
-
- ✅ **Easier Onboarding:** New devs learn one format, not three
|
|
667
|
-
|
|
668
|
-
### 6.3 Future-Proofing
|
|
669
|
-
- ✅ **Easy to Extend:** Add new formats by updating normalizer only
|
|
670
|
-
- ✅ **API Versioning:** Can version schema independently of frontend
|
|
671
|
-
- ✅ **Tool Evolution:** Add new tool features without breaking frontends
|
|
672
|
-
- ✅ **Multi-Client Support:** Web, mobile, CLI can all use same API
|
|
673
|
-
|
|
674
|
-
---
|
|
675
|
-
|
|
676
|
-
## 7. Alternative Approaches Considered
|
|
677
|
-
|
|
678
|
-
### 7.1 Option A: Normalize in Parsers (REJECTED)
|
|
679
|
-
|
|
680
|
-
**Approach:** Make each parser output unified format directly.
|
|
681
|
-
|
|
682
|
-
**Pros:**
|
|
683
|
-
- Format handling closest to source
|
|
684
|
-
- Parser owns full transformation
|
|
685
|
-
|
|
686
|
-
**Cons:**
|
|
687
|
-
- ❌ Duplicates normalization logic across 3 parsers
|
|
688
|
-
- ❌ Harder to ensure consistency
|
|
689
|
-
- ❌ Parsers become more complex
|
|
690
|
-
- ❌ Testing burden multiplied by 3
|
|
691
|
-
|
|
692
|
-
### 7.2 Option C: Normalize in Frontend (REJECTED)
|
|
693
|
-
|
|
694
|
-
**Approach:** Keep backend as-is, do all normalization in frontend views.
|
|
695
|
-
|
|
696
|
-
**Pros:**
|
|
697
|
-
- No backend changes
|
|
698
|
-
- Frontend controls its own format
|
|
699
|
-
|
|
700
|
-
**Cons:**
|
|
701
|
-
- ❌ Duplicates logic across multiple views
|
|
702
|
-
- ❌ Each view must understand all formats
|
|
703
|
-
- ❌ API remains inconsistent
|
|
704
|
-
- ❌ Harder to add new clients (mobile, CLI)
|
|
705
|
-
|
|
706
|
-
### 7.3 Option D: GraphQL Schema (FUTURE)
|
|
707
|
-
|
|
708
|
-
**Approach:** Use GraphQL to define unified schema with resolvers.
|
|
709
|
-
|
|
710
|
-
**Pros:**
|
|
711
|
-
- ✅ Strongly typed schema
|
|
712
|
-
- ✅ Client-controlled queries
|
|
713
|
-
- ✅ Better for complex queries
|
|
714
|
-
|
|
715
|
-
**Cons:**
|
|
716
|
-
- ❌ Much larger implementation effort
|
|
717
|
-
- ❌ Overkill for current needs
|
|
718
|
-
- ❌ Learning curve for team
|
|
719
|
-
|
|
720
|
-
**Status:** Consider for future major refactoring if API complexity grows significantly.
|
|
721
|
-
|
|
722
|
-
---
|
|
723
|
-
|
|
724
|
-
## 8. Open Questions & Risks
|
|
725
|
-
|
|
726
|
-
### 8.1 Open Questions
|
|
727
|
-
|
|
728
|
-
1. **Tool Result Size:** Should we truncate large tool results (>10KB) in the API response?
|
|
729
|
-
- **Recommendation:** Add `result_preview` field (first 1000 chars), keep full result available on demand
|
|
730
|
-
|
|
731
|
-
2. **Streaming:** How to handle streaming tool execution in real-time?
|
|
732
|
-
- **Recommendation:** Out of scope for initial implementation, revisit if WebSocket support is added
|
|
733
|
-
|
|
734
|
-
3. **Tool Metadata:** What other metadata should be included?
|
|
735
|
-
- **Recommendation:** Start minimal, add as needed based on user feedback
|
|
736
|
-
|
|
737
|
-
### 8.2 Risks & Mitigation
|
|
738
|
-
|
|
739
|
-
| Risk | Impact | Probability | Mitigation |
|
|
740
|
-
|------|--------|-------------|------------|
|
|
741
|
-
| Performance regression | Medium | Low | Benchmark before/after, optimize normalizer |
|
|
742
|
-
| Breaking existing code | High | Low | Comprehensive tests, feature flag, gradual rollout |
|
|
743
|
-
| Incomplete normalization | Medium | Medium | Unit tests for all edge cases, manual testing |
|
|
744
|
-
| Schema evolution issues | Low | Medium | Version API, document schema clearly |
|
|
745
|
-
|
|
746
|
-
---
|
|
747
|
-
|
|
748
|
-
## 9. Success Criteria
|
|
749
|
-
|
|
750
|
-
### 9.1 Technical Metrics
|
|
751
|
-
- [ ] All tool events use unified schema (100% coverage)
|
|
752
|
-
- [ ] Zero frontend format checks remaining
|
|
753
|
-
- [ ] Test coverage >90% for normalizer module
|
|
754
|
-
- [ ] API response time <10% increase
|
|
755
|
-
- [ ] Frontend render time ≥10% decrease (due to simpler logic)
|
|
756
|
-
|
|
757
|
-
### 9.2 Code Metrics
|
|
758
|
-
- [ ] ≥150 lines of frontend code removed
|
|
759
|
-
- [ ] ≤300 lines of backend code added (normalizer)
|
|
760
|
-
- [ ] Net reduction in total LOC
|
|
761
|
-
- [ ] Cyclomatic complexity reduced in frontend
|
|
762
|
-
|
|
763
|
-
### 9.3 Quality Metrics
|
|
764
|
-
- [ ] Zero P0/P1 bugs introduced
|
|
765
|
-
- [ ] All existing features work as before
|
|
766
|
-
- [ ] New format handles all edge cases
|
|
767
|
-
- [ ] Documentation updated
|
|
768
|
-
|
|
769
|
-
---
|
|
770
|
-
|
|
771
|
-
## 10. Appendix
|
|
772
|
-
|
|
773
|
-
### 10.1 Example API Response (Before vs After)
|
|
774
|
-
|
|
775
|
-
**Before (Claude format):**
|
|
776
|
-
```json
|
|
777
|
-
{
|
|
778
|
-
"type": "assistant",
|
|
779
|
-
"data": {
|
|
780
|
-
"message": "Let me read that file.",
|
|
781
|
-
"tools": [{
|
|
782
|
-
"type": "tool_use",
|
|
783
|
-
"id": "xyz789",
|
|
784
|
-
"name": "Read",
|
|
785
|
-
"input": { "file_path": "/path/to/file" },
|
|
786
|
-
"result": "File contents...",
|
|
787
|
-
"_matched": true,
|
|
788
|
-
"error": null
|
|
789
|
-
}]
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
**After (Unified format):**
|
|
795
|
-
```json
|
|
796
|
-
{
|
|
797
|
-
"type": "assistant.message",
|
|
798
|
-
"data": {
|
|
799
|
-
"message": "Let me read that file.",
|
|
800
|
-
"tools": [{
|
|
801
|
-
"id": "xyz789",
|
|
802
|
-
"name": "Read",
|
|
803
|
-
"startTime": "2024-01-01T10:00:00Z",
|
|
804
|
-
"endTime": "2024-01-01T10:00:05Z",
|
|
805
|
-
"status": "completed",
|
|
806
|
-
"input": { "file_path": "/path/to/file" },
|
|
807
|
-
"result": "File contents...",
|
|
808
|
-
"error": null,
|
|
809
|
-
"metadata": {
|
|
810
|
-
"source": "claude",
|
|
811
|
-
"duration": 5000
|
|
812
|
-
}
|
|
813
|
-
}]
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
### 10.2 Schema Changelog
|
|
819
|
-
|
|
820
|
-
| Version | Date | Changes |
|
|
821
|
-
|---------|------|---------|
|
|
822
|
-
| 1.0 | 2026-02 | Initial unified schema |
|
|
823
|
-
| 1.1 | TBD | Add `result_preview` field (future) |
|
|
824
|
-
| 2.0 | TBD | Add streaming support (future) |
|
|
825
|
-
|
|
826
|
-
### 10.3 References
|
|
827
|
-
|
|
828
|
-
- Current code: `src/services/sessionService.js` (lines 206-237, 458-649)
|
|
829
|
-
- Frontend format checks: `views/session-vue.ejs` (lines 1581-1605)
|
|
830
|
-
- Pi-Mono parser: `lib/parsers/pi-mono-parser.js` (lines 124-217)
|
|
831
|
-
|
|
832
|
-
---
|
|
833
|
-
|
|
834
|
-
## Document History
|
|
835
|
-
|
|
836
|
-
| Date | Author | Changes |
|
|
837
|
-
|------|--------|---------|
|
|
838
|
-
| 2026-02-22 | Claude | Initial design document |
|
|
839
|
-
|
|
840
|
-
---
|
|
841
|
-
|
|
842
|
-
**Status:** ✅ Ready for Review
|
|
843
|
-
**Priority:** P1 - High Impact, Low Risk
|
|
844
|
-
**Estimated Effort:** 3-4 weeks (1 developer)
|