ai-agent-inspector 1.0.0__py3-none-any.whl → 1.1.1__py3-none-any.whl

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.
@@ -7,27 +7,42 @@
7
7
  <link rel="stylesheet" href="/ui/static/app.css">
8
8
  </head>
9
9
  <body>
10
- <button class="theme-toggle" id="themeToggle" type="button">Theme: Auto</button>
10
+ <header class="app-header">
11
+ <h1>Agent Inspector</h1>
12
+ <button class="theme-toggle" id="themeToggle" type="button">
13
+ <span>Theme</span>
14
+ <span id="themeLabel">Auto</span>
15
+ </button>
16
+ </header>
17
+
11
18
  <div class="container">
12
19
  <!-- Left Panel: Run List -->
13
20
  <div class="panel">
14
21
  <div class="panel-header">Runs</div>
15
- <div class="filters">
16
- <input type="text" class="search-input" id="searchInput" placeholder="Search runs...">
17
- <select class="filter-select" id="statusFilter">
18
- <option value="">All Status</option>
19
- <option value="completed">Completed</option>
20
- <option value="running">Running</option>
21
- <option value="failed">Failed</option>
22
- </select>
23
- <select class="filter-select" id="eventTypeFilter">
24
- <option value="">All Events</option>
25
- <option value="llm_call">LLM Calls</option>
26
- <option value="tool_call">Tool Calls</option>
27
- <option value="error">Errors</option>
28
- </select>
29
- </div>
30
22
  <div class="panel-content">
23
+ <div class="filters">
24
+ <input type="text" class="search-input" id="searchInput" placeholder="Search runs...">
25
+ <div class="filter-row">
26
+ <select class="filter-select" id="statusFilter">
27
+ <option value="">All Status</option>
28
+ <option value="completed">Completed</option>
29
+ <option value="running">Running</option>
30
+ <option value="failed">Failed</option>
31
+ </select>
32
+ <select class="filter-select" id="eventTypeFilter">
33
+ <option value="">All Events</option>
34
+ <option value="llm_call">LLM Calls</option>
35
+ <option value="tool_call">Tool Calls</option>
36
+ <option value="memory_read">Memory Access</option>
37
+ <option value="error">Errors</option>
38
+ <option value="agent_spawn">Agent Spawn</option>
39
+ <option value="agent_join">Agent Join</option>
40
+ <option value="agent_communication">Agent Communication</option>
41
+ <option value="agent_handoff">Agent Handoff</option>
42
+ <option value="task_assignment">Task Assignment</option>
43
+ </select>
44
+ </div>
45
+ </div>
31
46
  <ul class="run-list" id="runList">
32
47
  <li class="loading">Loading runs...</li>
33
48
  </ul>
@@ -35,9 +50,9 @@
35
50
  </div>
36
51
 
37
52
  <!-- Center Panel: Timeline -->
38
- <div class="panel">
39
- <div class="panel-header">Timeline</div>
40
- <div class="panel-content">
53
+ <div class="panel">
54
+ <div class="panel-header">Timeline</div>
55
+ <div class="panel-content">
41
56
  <div class="timeline" id="timeline">
42
57
  <div class="loading">Select a run to view timeline</div>
43
58
  </div>
@@ -72,6 +87,7 @@
72
87
  const statusFilter = document.getElementById('statusFilter');
73
88
  const eventTypeFilter = document.getElementById('eventTypeFilter');
74
89
  const themeToggle = document.getElementById('themeToggle');
90
+ const themeLabel = document.getElementById('themeLabel');
75
91
 
76
92
  // Load runs on page load
77
93
  window.addEventListener('load', () => {
@@ -108,21 +124,39 @@
108
124
 
109
125
  function renderRuns(runsToRender) {
110
126
  if (runsToRender.length === 0) {
111
- runList.innerHTML = '<li class="loading" style="padding: 40px;">No runs found</li>';
127
+ runList.innerHTML = '<li class="loading" style="padding: 40px; background: none;">No runs found</li>';
112
128
  return;
113
129
  }
114
130
 
115
- runList.innerHTML = runsToRender.map(run => `
116
- <li class="run-item ${currentRunId === run.id ? 'active' : ''}"
131
+ runList.innerHTML = runsToRender.map(run => {
132
+ const statusIcon = getStatusIcon(run.status);
133
+ return `
134
+ <li class="run-card ${run.status} ${currentRunId === run.id ? 'active' : ''}"
117
135
  data-run-id="${run.id}"
118
136
  onclick="selectRun('${run.id}')">
119
- <div class="run-name">${escapeHtml(run.name || 'Unnamed Run')}</div>
137
+ <div class="run-header">
138
+ <div class="run-id">${escapeHtml(run.name || run.id || 'Unnamed Run')}</div>
139
+ <span class="run-status-badge ${run.status}">
140
+ ${statusIcon} ${run.status}
141
+ </span>
142
+ </div>
143
+ <div class="run-description">${escapeHtml(run.description || `Run ${run.id} with ${run.event_count || 0} events`)}</div>
120
144
  <div class="run-meta">
121
- ${formatTimestamp(run.started_at)}
122
- <span class="run-status ${run.status}">${run.status}</span>
145
+ <span class="run-time">${formatTimeOnly(run.started_at)}</span>
146
+ <span>${formatDateOnly(run.started_at)}</span>
123
147
  </div>
124
148
  </li>
125
- `).join('');
149
+ `}).join('');
150
+ }
151
+
152
+ function getStatusIcon(status) {
153
+ const icons = {
154
+ 'completed': '✓',
155
+ 'running': '⟳',
156
+ 'failed': '✕',
157
+ 'pending': '○'
158
+ };
159
+ return icons[status] || '●';
126
160
  }
127
161
 
128
162
  function filterRuns() {
@@ -131,7 +165,9 @@
131
165
 
132
166
  const filtered = runs.filter(run => {
133
167
  const matchesSearch = !searchTerm ||
134
- (run.name && run.name.toLowerCase().includes(searchTerm));
168
+ (run.name && run.name.toLowerCase().includes(searchTerm)) ||
169
+ (run.id && run.id.toLowerCase().includes(searchTerm)) ||
170
+ (run.description && run.description.toLowerCase().includes(searchTerm));
135
171
  const matchesStatus = !status || run.status === status;
136
172
  return matchesSearch && matchesStatus;
137
173
  });
@@ -143,7 +179,7 @@
143
179
  currentRunId = runId;
144
180
 
145
181
  // Update UI
146
- document.querySelectorAll('.run-item').forEach(item => {
182
+ document.querySelectorAll('.run-card').forEach(item => {
147
183
  item.classList.remove('active');
148
184
  if (item.dataset.runId === runId) {
149
185
  item.classList.add('active');
@@ -151,7 +187,7 @@
151
187
  });
152
188
 
153
189
  // Clear detail view
154
- detailView.innerHTML = '<div class="detail-empty">Loading...</div>';
190
+ detailView.innerHTML = '<div class="detail-empty">Select an event to view details</div>';
155
191
 
156
192
  // Load timeline
157
193
  await loadTimeline(runId);
@@ -161,7 +197,7 @@
161
197
  try {
162
198
  timeline.innerHTML = '<div class="loading">Loading timeline...</div>';
163
199
 
164
- const response = await fetch(`${API_BASE}/runs/${runId}/timeline`);
200
+ const response = await fetch(`${API_BASE}/runs/${runId}/timeline?include_data=true`);
165
201
  const data = await response.json();
166
202
  const events = data.events || [];
167
203
 
@@ -174,7 +210,7 @@
174
210
 
175
211
  function renderTimeline(events) {
176
212
  if (events.length === 0) {
177
- timeline.innerHTML = '<div class="loading">No events in this run</div>';
213
+ timeline.innerHTML = '<div class="loading" style="background: none;">No events in this run</div>';
178
214
  return;
179
215
  }
180
216
 
@@ -184,32 +220,111 @@
184
220
  : events;
185
221
 
186
222
  if (filteredEvents.length === 0) {
187
- timeline.innerHTML = '<div class="loading">No events match filter</div>';
223
+ timeline.innerHTML = '<div class="loading" style="background: none;">No events match filter</div>';
188
224
  return;
189
225
  }
190
226
 
227
+ const eventClasses = {
228
+ 'llm_call': 'llm',
229
+ 'tool_call': 'tool',
230
+ 'memory_read': 'memory',
231
+ 'memory_write': 'memory',
232
+ 'error': 'error',
233
+ 'final_answer': 'final',
234
+ 'run_start': 'llm',
235
+ // Multi-agent classes
236
+ 'agent_spawn': 'agent',
237
+ 'agent_join': 'agent',
238
+ 'agent_leave': 'agent',
239
+ 'agent_communication': 'communication',
240
+ 'agent_handoff': 'handoff',
241
+ 'task_assignment': 'task',
242
+ 'task_completion': 'task'
243
+ };
244
+
191
245
  timeline.innerHTML = `
192
- <div class="event-connector"></div>
193
- ${filteredEvents.map(event => `
194
- <div class="timeline-event"
246
+ <div class="timeline-connector"></div>
247
+ ${filteredEvents.map(event => {
248
+ const eventClass = eventClasses[event.type] || 'llm';
249
+ const icon = getEventIcon(event.type);
250
+ const badge = getEventTypeBadge(event.type);
251
+ const time = formatTimeOnly(event.timestamp);
252
+ return `
253
+ <div class="timeline-event ${eventClass}"
195
254
  onclick="showEventDetail('${event.id}')"
196
255
  data-event-id="${event.id}">
197
- <div class="event-icon ${event.type}">
198
- ${getEventIcon(event.type)}
199
- </div>
200
- <div class="event-content">
201
- <div class="event-type">${formatEventType(event.type)}</div>
202
- <div class="event-timestamp">${formatTimestamp(event.timestamp)}</div>
203
- <div class="event-summary">${getEventSummary(event)}</div>
256
+ <div class="timeline-event-time">${time}</div>
257
+ <div class="timeline-event-card">
258
+ <div class="event-icon-wrapper">${icon}</div>
259
+ <div class="event-details">
260
+ <div class="event-type-label">
261
+ ${formatEventType(event.type)}
262
+ <span class="event-type-badge">${badge}</span>
263
+ </div>
264
+ <div class="event-summary">${getEventSummary(event)}</div>
265
+ ${getEventPreview(event)}
266
+ </div>
204
267
  </div>
205
268
  </div>
206
- `).join('')}
269
+ `}).join('')}
207
270
  `;
208
271
  }
209
272
 
273
+ function getEventTypeBadge(type) {
274
+ const badges = {
275
+ 'llm_call': 'LLM',
276
+ 'tool_call': 'TOOL',
277
+ 'memory_read': 'MEMORY',
278
+ 'memory_write': 'MEMORY',
279
+ 'error': 'ERROR',
280
+ 'final_answer': 'FINAL',
281
+ // Multi-agent badges
282
+ 'agent_spawn': 'AGENT',
283
+ 'agent_join': 'JOIN',
284
+ 'agent_leave': 'LEAVE',
285
+ 'agent_communication': 'MSG',
286
+ 'agent_handoff': 'HANDOFF',
287
+ 'task_assignment': 'TASK',
288
+ 'task_completion': 'DONE'
289
+ };
290
+ return badges[type] || type.toUpperCase();
291
+ }
292
+
293
+ function getEventPreview(event) {
294
+ const summary = getEventSummary(event);
295
+ let preview = '';
296
+ if (event.type === 'llm_call' && event.prompt) {
297
+ preview = event.prompt.substring(0, 60) + (event.prompt.length > 60 ? '...' : '');
298
+ } else if (event.type === 'tool_call' && event.tool_name) {
299
+ preview = `${event.tool_name}(${JSON.stringify(event.tool_args || {}).substring(0, 50)})`;
300
+ } else if ((event.type === 'memory_read' || event.type === 'memory_write') && event.memory_key) {
301
+ preview = `Key: ${event.memory_key}`;
302
+ } else if (event.type === 'agent_communication' && event.message_content) {
303
+ preview = event.message_content.substring(0, 80) + (event.message_content.length > 80 ? '...' : '');
304
+ } else if (event.type === 'agent_handoff' && event.handoff_reason) {
305
+ preview = `Reason: ${event.handoff_reason}`;
306
+ } else if (event.type === 'task_assignment' || event.type === 'task_completion') {
307
+ const taskName = event.task_name || event.data?.task_name;
308
+ if (taskName && taskName.length > 45) {
309
+ preview = taskName.length > 120 ? taskName.substring(0, 120) + '…' : taskName;
310
+ }
311
+ }
312
+ if (!preview || preview === summary) return '';
313
+ return `<div class="event-data-preview">${escapeHtml(preview)}</div>`;
314
+ }
315
+
210
316
  async function showEventDetail(eventId) {
211
317
  currentEventId = eventId;
212
318
 
319
+ // Highlight selected event
320
+ document.querySelectorAll('.timeline-event').forEach(el => {
321
+ el.style.opacity = '0.6';
322
+ });
323
+ const selectedEvent = document.querySelector(`[data-event-id="${eventId}"]`);
324
+ if (selectedEvent) {
325
+ selectedEvent.style.opacity = '1';
326
+ }
327
+
213
328
  try {
214
329
  const response = await fetch(`${API_BASE}/runs/${currentRunId}/steps/${eventId}/data`);
215
330
  const data = await response.json();
@@ -227,122 +342,243 @@
227
342
  return;
228
343
  }
229
344
 
230
- const sections = [];
231
- const richBlocks = [];
232
-
233
- // Basic info
234
- sections.push({
235
- label: 'Event ID',
236
- value: event.event_id || 'N/A'
237
- });
238
-
239
- sections.push({
240
- label: 'Type',
241
- value: formatEventType(event.type)
242
- });
243
-
244
- sections.push({
245
- label: 'Timestamp',
246
- value: formatTimestamp(event.timestamp_ms)
247
- });
345
+ let html = '';
248
346
 
249
- sections.push({
250
- label: 'Status',
251
- value: event.status || 'N/A'
252
- });
253
-
254
- if (event.duration_ms !== undefined) {
255
- sections.push({
256
- label: 'Duration',
257
- value: `${event.duration_ms}ms`
258
- });
259
- }
347
+ // Basic Info Section
348
+ html += `
349
+ <div class="detail-section">
350
+ <div class="detail-section-header">
351
+ <span class="detail-section-title">Event Information</span>
352
+ </div>
353
+ <div class="detail-grid">
354
+ <div class="detail-label">Event ID</div>
355
+ <div class="detail-value">${event.event_id || 'N/A'}</div>
356
+
357
+ <div class="detail-label">Type</div>
358
+ <div class="detail-value">${formatEventType(event.type)}</div>
359
+
360
+ <div class="detail-label">Timestamp</div>
361
+ <div class="detail-value">${formatTimestamp(event.timestamp_ms || event.timestamp)}</div>
362
+
363
+ <div class="detail-label">Status</div>
364
+ <div class="detail-value">${event.status || 'N/A'}</div>
365
+
366
+ ${event.duration_ms ? `
367
+ <div class="detail-label">Duration</div>
368
+ <div class="detail-value">${event.duration_ms}ms</div>
369
+ ` : ''}
370
+ </div>
371
+ </div>
372
+ `;
260
373
 
261
- // Type-specific details
374
+ // Type-specific sections
262
375
  if (event.type === 'llm_call') {
263
376
  if (event.model) {
264
- sections.push({ label: 'Model', value: event.model });
377
+ html += createDetailSection('Model', event.model);
265
378
  }
266
379
  if (event.prompt) {
267
380
  const parsed = parseMaybeJson(event.prompt);
268
381
  if (parsed && Array.isArray(parsed)) {
269
- richBlocks.push({
270
- label: 'Prompt',
271
- html: renderChatMessages(parsed)
272
- });
382
+ html += createChatSection('Prompt', parsed);
273
383
  } else {
274
- sections.push({ label: 'Prompt', value: event.prompt });
384
+ html += createCodeSection('Prompt', event.prompt);
275
385
  }
276
386
  }
277
387
  if (event.response) {
278
- sections.push({ label: 'Response', value: event.response });
388
+ html += createCodeSection('Response', event.response);
279
389
  }
280
- if (event.total_tokens) {
281
- sections.push({ label: 'Tokens', value: event.total_tokens.toString() });
390
+ if (event.total_tokens !== undefined) {
391
+ html += createDetailSection('Tokens', `
392
+ <div style="display: flex; gap: 16px; font-size: 12px;">
393
+ <span>Input: <strong>${event.input_tokens || 0}</strong></span>
394
+ <span>Output: <strong>${event.output_tokens || 0}</strong></span>
395
+ <span>Total: <strong>${event.total_tokens}</strong></span>
396
+ </div>
397
+ `, true);
282
398
  }
283
399
  } else if (event.type === 'tool_call') {
284
400
  if (event.tool_name) {
285
- sections.push({ label: 'Tool', value: event.tool_name });
401
+ html += createDetailSection('Tool Name', event.tool_name);
286
402
  }
287
403
  if (event.tool_args) {
288
- sections.push({
289
- label: 'Arguments',
290
- value: JSON.stringify(event.tool_args, null, 2)
291
- });
404
+ html += createCodeSection('Arguments', syntaxHighlightJson(event.tool_args));
292
405
  }
293
- if (event.tool_result) {
294
- sections.push({
295
- label: 'Result',
296
- value: JSON.stringify(event.tool_result, null, 2)
297
- });
406
+ if (event.tool_result !== undefined) {
407
+ html += createCodeSection('Result', syntaxHighlightJson(event.tool_result));
298
408
  }
299
409
  } else if (event.type === 'memory_read' || event.type === 'memory_write') {
300
410
  if (event.memory_key) {
301
- sections.push({ label: 'Key', value: event.memory_key });
411
+ html += createDetailSection('Memory Key', event.memory_key);
302
412
  }
303
- if (event.memory_value) {
304
- sections.push({
305
- label: 'Value',
306
- value: JSON.stringify(event.memory_value, null, 2)
307
- });
413
+ if (event.memory_value !== undefined) {
414
+ html += createCodeSection('Value', syntaxHighlightJson(event.memory_value));
308
415
  }
309
416
  } else if (event.type === 'error') {
310
417
  if (event.error_type) {
311
- sections.push({ label: 'Error Type', value: event.error_type });
418
+ html += createDetailSection('Error Type', event.error_type);
312
419
  }
313
420
  if (event.error_message) {
314
- sections.push({ label: 'Message', value: event.error_message });
421
+ html += createCodeSection('Message', event.error_message);
422
+ }
423
+ if (event.stack_trace) {
424
+ html += createCodeSection('Stack Trace', event.stack_trace);
315
425
  }
316
426
  } else if (event.type === 'final_answer') {
317
427
  if (event.answer) {
318
- sections.push({ label: 'Answer', value: event.answer });
428
+ html += createCodeSection('Answer', event.answer);
429
+ }
430
+ } else if (event.type === 'agent_spawn') {
431
+ if (event.agent_name) {
432
+ html += createDetailSection('Agent Name', event.agent_name);
433
+ }
434
+ if (event.agent_id) {
435
+ html += createDetailSection('Agent ID', event.agent_id);
436
+ }
437
+ if (event.agent_role) {
438
+ html += createDetailSection('Role', event.agent_role);
439
+ }
440
+ if (event.parent_run_id) {
441
+ html += createDetailSection('Parent Run', event.parent_run_id);
442
+ }
443
+ if (event.agent_config && Object.keys(event.agent_config).length > 0) {
444
+ html += createCodeSection('Configuration', syntaxHighlightJson(event.agent_config));
445
+ }
446
+ } else if (event.type === 'agent_join') {
447
+ if (event.agent_name) {
448
+ html += createDetailSection('Agent Name', event.agent_name);
449
+ }
450
+ if (event.group_name) {
451
+ html += createDetailSection('Group', event.group_name);
452
+ }
453
+ if (event.group_id) {
454
+ html += createDetailSection('Group ID', event.group_id);
455
+ }
456
+ } else if (event.type === 'agent_leave') {
457
+ if (event.agent_name) {
458
+ html += createDetailSection('Agent Name', event.agent_name);
459
+ }
460
+ if (event.reason) {
461
+ html += createDetailSection('Reason', event.reason);
462
+ }
463
+ } else if (event.type === 'agent_communication') {
464
+ if (event.from_agent_name) {
465
+ html += createDetailSection('From', event.from_agent_name);
466
+ }
467
+ if (event.to_agent_name) {
468
+ html += createDetailSection('To', event.to_agent_name);
469
+ }
470
+ if (event.message_type) {
471
+ html += createDetailSection('Type', event.message_type);
472
+ }
473
+ if (event.message_content) {
474
+ html += createCodeSection('Message', event.message_content);
475
+ }
476
+ if (event.group_id) {
477
+ html += createDetailSection('Group ID', event.group_id);
478
+ }
479
+ } else if (event.type === 'agent_handoff') {
480
+ if (event.from_agent_name) {
481
+ html += createDetailSection('From Agent', event.from_agent_name);
482
+ }
483
+ if (event.to_agent_name) {
484
+ html += createDetailSection('To Agent', event.to_agent_name);
485
+ }
486
+ if (event.handoff_reason) {
487
+ html += createDetailSection('Reason', event.handoff_reason);
488
+ }
489
+ if (event.context_summary) {
490
+ html += createDetailSection('Context', event.context_summary);
491
+ }
492
+ } else if (event.type === 'task_assignment') {
493
+ if (event.task_name) {
494
+ html += createDetailSection('Task', event.task_name);
495
+ }
496
+ if (event.assigned_to_agent_name) {
497
+ html += createDetailSection('Assigned To', event.assigned_to_agent_name);
498
+ }
499
+ if (event.priority) {
500
+ html += createDetailSection('Priority', event.priority);
501
+ }
502
+ if (event.task_data && Object.keys(event.task_data).length > 0) {
503
+ html += createCodeSection('Task Data', syntaxHighlightJson(event.task_data));
504
+ }
505
+ } else if (event.type === 'task_completion') {
506
+ if (event.task_name) {
507
+ html += createDetailSection('Task', event.task_name);
508
+ }
509
+ if (event.completed_by_agent_name) {
510
+ html += createDetailSection('Completed By', event.completed_by_agent_name);
511
+ }
512
+ if (event.success !== undefined) {
513
+ html += createDetailSection('Success', event.success ? 'Yes' : 'No');
514
+ }
515
+ if (event.result !== undefined) {
516
+ html += createCodeSection('Result', syntaxHighlightJson(event.result));
517
+ }
518
+ if (event.completion_time_ms) {
519
+ html += createDetailSection('Time', `${event.completion_time_ms}ms`);
319
520
  }
320
521
  }
321
522
 
322
- // Metadata
523
+ // Metadata section
323
524
  if (event.metadata && Object.keys(event.metadata).length > 0) {
324
- sections.push({
325
- label: 'Metadata',
326
- value: JSON.stringify(event.metadata, null, 2)
327
- });
525
+ html += createCodeSection('Metadata', syntaxHighlightJson(event.metadata));
328
526
  }
329
527
 
330
- // Render sections
331
- const sectionHtml = sections.map(section => `
528
+ detailView.innerHTML = html;
529
+ }
530
+
531
+ function createDetailSection(label, value, isHtml = false) {
532
+ return `
332
533
  <div class="detail-section">
333
- <div class="detail-label">${escapeHtml(section.label)}</div>
334
- <div class="detail-value">${escapeHtml(section.value)}</div>
534
+ <div class="detail-section-header">
535
+ <span class="detail-section-title">${label}</span>
536
+ </div>
537
+ <div class="detail-value ${isHtml ? '' : ''}">${isHtml ? value : escapeHtml(value)}</div>
335
538
  </div>
336
- `).join('');
539
+ `;
540
+ }
337
541
 
338
- const richHtml = richBlocks.map(block => `
542
+ function createCodeSection(label, content) {
543
+ return `
339
544
  <div class="detail-section">
340
- <div class="detail-label">${escapeHtml(block.label)}</div>
341
- <div class="detail-chat">${block.html}</div>
545
+ <div class="detail-section-header">
546
+ <span class="detail-section-title">${label}</span>
547
+ </div>
548
+ <div class="detail-value code">${content}</div>
549
+ </div>
550
+ `;
551
+ }
552
+
553
+ function createChatSection(label, messages) {
554
+ const messagesHtml = messages.map(msg => `
555
+ <div class="chat-message ${escapeHtml(msg.role || 'unknown')}">
556
+ <div class="chat-role">${escapeHtml(msg.role || 'unknown')}</div>
557
+ <div class="chat-content">${escapeHtml(msg.content || '')}</div>
342
558
  </div>
343
559
  `).join('');
344
560
 
345
- detailView.innerHTML = richHtml + sectionHtml;
561
+ return `
562
+ <div class="detail-section">
563
+ <div class="detail-section-header">
564
+ <span class="detail-section-title">${label}</span>
565
+ </div>
566
+ <div class="chat-container">${messagesHtml}</div>
567
+ </div>
568
+ `;
569
+ }
570
+
571
+ function syntaxHighlightJson(obj) {
572
+ const json = JSON.stringify(obj, null, 2);
573
+ return json
574
+ .replace(/&/g, '&amp;')
575
+ .replace(/</g, '&lt;')
576
+ .replace(/>/g, '&gt;')
577
+ .replace(/(".*?"):/g, '<span class="json-key">$1</span>:')
578
+ .replace(/: (".*?")/g, ': <span class="json-string">$1</span>')
579
+ .replace(/: (\d+)/g, ': <span class="json-number">$1</span>')
580
+ .replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>')
581
+ .replace(/: (null)/g, ': <span class="json-null">$1</span>');
346
582
  }
347
583
 
348
584
  function getEventIcon(type) {
@@ -353,45 +589,137 @@
353
589
  'memory_write': '✍️',
354
590
  'error': '❌',
355
591
  'final_answer': '✅',
356
- 'run_start': '▶️'
592
+ 'run_start': '▶️',
593
+ // Multi-agent icons
594
+ 'agent_spawn': '👤',
595
+ 'agent_join': '📥',
596
+ 'agent_leave': '📤',
597
+ 'agent_communication': '💬',
598
+ 'agent_handoff': '🔀',
599
+ 'task_assignment': '📝',
600
+ 'task_completion': '✓'
357
601
  };
358
602
  return icons[type] || '📌';
359
603
  }
360
604
 
361
605
  function formatEventType(type) {
362
- return type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
606
+ const labels = {
607
+ 'llm_call': 'LLM Call',
608
+ 'tool_call': 'Tool Call',
609
+ 'memory_read': 'Memory Read',
610
+ 'memory_write': 'Memory Write',
611
+ 'error': 'Error',
612
+ 'final_answer': 'Final Answer',
613
+ 'run_start': 'Run Started',
614
+ // Multi-agent labels
615
+ 'agent_spawn': 'Agent Spawned',
616
+ 'agent_join': 'Agent Joined',
617
+ 'agent_leave': 'Agent Left',
618
+ 'agent_communication': 'Agent Message',
619
+ 'agent_handoff': 'Agent Handoff',
620
+ 'task_assignment': 'Task Assigned',
621
+ 'task_completion': 'Task Completed'
622
+ };
623
+ return labels[type] || type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
363
624
  }
364
625
 
365
626
  function getEventSummary(event) {
627
+ let role = '';
628
+ let agentName = '';
629
+ let agentRole = '';
630
+ let reason = '';
631
+
366
632
  switch (event.type) {
367
633
  case 'llm_call':
368
- return event.model ? `Model: ${event.model}` : 'LLM Call';
634
+ return event.model ? `Model: ${event.model}` : 'LLM processing';
369
635
  case 'tool_call':
370
- return event.tool_name ? `Tool: ${event.tool_name}` : 'Tool Call';
636
+ return event.tool_name ? `Tool: ${event.tool_name}` : 'Tool execution';
371
637
  case 'memory_read':
372
- return event.memory_key ? `Read: ${event.memory_key}` : 'Memory Read';
638
+ return event.memory_key ? `Reading: ${event.memory_key}` : 'Memory access';
373
639
  case 'memory_write':
374
- return event.memory_key ? `Write: ${event.memory_key}` : 'Memory Write';
640
+ return event.memory_key ? `Writing: ${event.memory_key}` : 'Memory update';
375
641
  case 'error':
376
- return event.error_message || 'Error occurred';
642
+ return event.error_message || 'An error occurred';
377
643
  case 'final_answer':
378
- return event.answer ? event.answer.substring(0, 50) + '...' : 'Final answer';
644
+ return 'Final response generated';
645
+ // Multi-agent events - check both direct fields and data field
646
+ case 'agent_spawn':
647
+ agentName = event.agent_name || (event.data?.agent_name || '');
648
+ agentRole = event.agent_role || (event.data?.agent_role || '');
649
+ role = agentRole ? `(${agentRole})` : '';
650
+ return agentName ? `${agentName}${role} - Spawned` : (event.agent_id || event.name || 'Agent spawned');
651
+ case 'agent_join':
652
+ agentName = event.agent_name || (event.data?.agent_name || '');
653
+ agentRole = event.agent_role || (event.data?.agent_role || '');
654
+ role = agentRole ? `(${agentRole})` : '';
655
+ return agentName ? `${agentName}${role} - Joined` : (event.agent_id || event.name || 'Agent joined');
656
+ case 'agent_leave':
657
+ agentName = event.agent_name || (event.data?.agent_name || '');
658
+ reason = event.data?.reason || event.reason ? `(${event.data?.reason || event.reason})` : '';
659
+ return agentName ? `${agentName} left${reason}` : (event.agent_id || event.name || 'Agent left');
660
+ case 'agent_communication': {
661
+ const fromLabel = event.from_agent_name || event.from_agent_id || event.data?.from_agent_name || event.data?.from_agent_id;
662
+ const toLabel = event.to_agent_name || event.to_agent_id || event.data?.to_agent_name || event.data?.to_agent_id;
663
+ if (fromLabel) return toLabel ? `${fromLabel} → ${toLabel}` : `${fromLabel} → All`;
664
+ return event.name || 'Agent message';
665
+ }
666
+ case 'agent_handoff': {
667
+ const fromLabel = event.from_agent_name || event.from_agent_id || event.data?.from_agent_name || event.data?.from_agent_id;
668
+ const toLabel = event.to_agent_name || event.to_agent_id || event.data?.to_agent_name || event.data?.to_agent_id;
669
+ return (fromLabel && toLabel) ? `${fromLabel} → ${toLabel}` : (event.name || 'Agent handoff');
670
+ }
671
+ case 'task_assignment': {
672
+ const taskLabel = event.task_name || event.task_id || event.data?.task_name || event.data?.task_id;
673
+ const assigneeLabel = event.assigned_to_agent_name || event.assigned_to_agent_id || event.data?.assigned_to_agent_name || event.data?.assigned_to_agent_id;
674
+ if (!taskLabel || !assigneeLabel) return event.name || 'Task assigned';
675
+ const shortTask = taskLabel.length > 45 ? taskLabel.substring(0, 45) + '…' : taskLabel;
676
+ return `${shortTask} → ${assigneeLabel}`;
677
+ }
678
+ case 'task_completion': {
679
+ const taskLabel = event.task_name || event.task_id || event.data?.task_name || event.data?.task_id;
680
+ if (!taskLabel) return event.name || 'Task completed';
681
+ const shortTask = taskLabel.length > 45 ? taskLabel.substring(0, 45) + '…' : taskLabel;
682
+ return `${shortTask} completed`;
683
+ }
379
684
  default:
380
685
  return event.name || event.type;
381
686
  }
382
687
  }
383
688
 
384
689
  function formatTimestamp(ms) {
690
+ if (!ms) return 'N/A';
385
691
  const date = new Date(ms);
386
692
  return date.toLocaleString();
387
693
  }
388
694
 
695
+ function formatTimeOnly(ms) {
696
+ if (!ms) return '';
697
+ const date = new Date(ms);
698
+ return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
699
+ }
700
+
701
+ function formatDateOnly(ms) {
702
+ if (!ms) return '';
703
+ const date = new Date(ms);
704
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
705
+ }
706
+
389
707
  function escapeHtml(text) {
708
+ if (text === null || text === undefined) return '';
390
709
  const div = document.createElement('div');
391
710
  div.textContent = text;
392
711
  return div.innerHTML;
393
712
  }
394
713
 
714
+ function parseMaybeJson(text) {
715
+ if (!text) return null;
716
+ try {
717
+ return JSON.parse(text);
718
+ } catch (e) {
719
+ return null;
720
+ }
721
+ }
722
+
395
723
  function initTheme() {
396
724
  const saved = localStorage.getItem('agent_inspector_theme') || 'auto';
397
725
  applyTheme(saved);
@@ -413,27 +741,7 @@
413
741
  root.classList.add('theme-dark');
414
742
  }
415
743
  localStorage.setItem('agent_inspector_theme', theme);
416
- const label = theme === 'auto'
417
- ? 'Theme: Auto'
418
- : `Theme: ${theme[0].toUpperCase()}${theme.slice(1)}`;
419
- themeToggle.textContent = label;
420
- }
421
-
422
- function parseMaybeJson(text) {
423
- try {
424
- return JSON.parse(text);
425
- } catch (e) {
426
- return null;
427
- }
428
- }
429
-
430
- function renderChatMessages(messages) {
431
- return messages.map(msg => `
432
- <div class="chat-row ${escapeHtml(msg.role || 'unknown')}">
433
- <div class="chat-role">${escapeHtml(msg.role || 'unknown')}</div>
434
- <div class="chat-content">${escapeHtml(msg.content || '')}</div>
435
- </div>
436
- `).join('');
744
+ themeLabel.textContent = theme === 'auto' ? 'Auto' : theme.charAt(0).toUpperCase() + theme.slice(1);
437
745
  }
438
746
  </script>
439
747
  <script src="/ui/static/app.js" defer></script>