@roj-ai/debug 0.0.2

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.
Files changed (77) hide show
  1. package/dist/components/debug/DebugContext.d.ts +10 -0
  2. package/dist/components/debug/DebugNavigation.d.ts +29 -0
  3. package/dist/components/debug/DebugShell.d.ts +18 -0
  4. package/dist/components/debug/LLMCallDetail.d.ts +7 -0
  5. package/dist/components/debug/TimelineDetailInspector.d.ts +6 -0
  6. package/dist/components/debug/communication/CommunicationDiagram.d.ts +9 -0
  7. package/dist/components/debug/communication/DiagramHeader.d.ts +7 -0
  8. package/dist/components/debug/communication/ParticipantLane.d.ts +7 -0
  9. package/dist/components/debug/communication/TimeAxis.d.ts +9 -0
  10. package/dist/components/debug/communication/elements/IdleGap.d.ts +9 -0
  11. package/dist/components/debug/communication/elements/LLMBlock.d.ts +9 -0
  12. package/dist/components/debug/communication/elements/MessageArrow.d.ts +10 -0
  13. package/dist/components/debug/communication/elements/ToolBlock.d.ts +9 -0
  14. package/dist/components/debug/communication/hooks/useDiagramData.d.ts +12 -0
  15. package/dist/components/debug/communication/hooks/useTimeCompression.d.ts +7 -0
  16. package/dist/components/debug/communication/hooks/useZoomPan.d.ts +11 -0
  17. package/dist/components/debug/communication/popovers/ElementPopover.d.ts +8 -0
  18. package/dist/components/debug/communication/types.d.ts +136 -0
  19. package/dist/components/debug/index.d.ts +11 -0
  20. package/dist/components/debug/pages/AgentDetailPage.d.ts +3 -0
  21. package/dist/components/debug/pages/AgentsPage.d.ts +1 -0
  22. package/dist/components/debug/pages/CommunicationPage.d.ts +1 -0
  23. package/dist/components/debug/pages/DashboardPage.d.ts +1 -0
  24. package/dist/components/debug/pages/EventsPage.d.ts +1 -0
  25. package/dist/components/debug/pages/FilesPage.d.ts +1 -0
  26. package/dist/components/debug/pages/LLMCallPage.d.ts +1 -0
  27. package/dist/components/debug/pages/LLMCallsPage.d.ts +1 -0
  28. package/dist/components/debug/pages/LogsPage.d.ts +1 -0
  29. package/dist/components/debug/pages/MailboxPage.d.ts +1 -0
  30. package/dist/components/debug/pages/ServicesPage.d.ts +1 -0
  31. package/dist/components/debug/pages/TimelinePage.d.ts +1 -0
  32. package/dist/components/debug/pages/UserChatPage.d.ts +1 -0
  33. package/dist/components/debug/pages/index.d.ts +13 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/lib/domain-utils.d.ts +7 -0
  36. package/dist/providers/EventPollingProvider.d.ts +27 -0
  37. package/dist/stores/event-store.d.ts +93 -0
  38. package/dist/utils/format.d.ts +1 -0
  39. package/package.json +43 -0
  40. package/src/components/debug/DebugContext.tsx +18 -0
  41. package/src/components/debug/DebugNavigation.tsx +55 -0
  42. package/src/components/debug/DebugShell.tsx +321 -0
  43. package/src/components/debug/LLMCallDetail.tsx +740 -0
  44. package/src/components/debug/TimelineDetailInspector.tsx +204 -0
  45. package/src/components/debug/communication/CommunicationDiagram.tsx +260 -0
  46. package/src/components/debug/communication/DiagramHeader.tsx +113 -0
  47. package/src/components/debug/communication/ParticipantLane.tsx +60 -0
  48. package/src/components/debug/communication/TimeAxis.tsx +106 -0
  49. package/src/components/debug/communication/elements/IdleGap.tsx +90 -0
  50. package/src/components/debug/communication/elements/LLMBlock.tsx +107 -0
  51. package/src/components/debug/communication/elements/MessageArrow.tsx +119 -0
  52. package/src/components/debug/communication/elements/ToolBlock.tsx +99 -0
  53. package/src/components/debug/communication/hooks/useDiagramData.ts +294 -0
  54. package/src/components/debug/communication/hooks/useTimeCompression.ts +140 -0
  55. package/src/components/debug/communication/hooks/useZoomPan.ts +87 -0
  56. package/src/components/debug/communication/popovers/ElementPopover.tsx +158 -0
  57. package/src/components/debug/communication/types.ts +180 -0
  58. package/src/components/debug/index.ts +37 -0
  59. package/src/components/debug/pages/AgentDetailPage.tsx +1295 -0
  60. package/src/components/debug/pages/AgentsPage.tsx +297 -0
  61. package/src/components/debug/pages/CommunicationPage.tsx +89 -0
  62. package/src/components/debug/pages/DashboardPage.tsx +1504 -0
  63. package/src/components/debug/pages/EventsPage.tsx +276 -0
  64. package/src/components/debug/pages/FilesPage.tsx +366 -0
  65. package/src/components/debug/pages/LLMCallPage.tsx +32 -0
  66. package/src/components/debug/pages/LLMCallsPage.tsx +473 -0
  67. package/src/components/debug/pages/LogsPage.tsx +199 -0
  68. package/src/components/debug/pages/MailboxPage.tsx +232 -0
  69. package/src/components/debug/pages/ServicesPage.tsx +193 -0
  70. package/src/components/debug/pages/TimelinePage.tsx +569 -0
  71. package/src/components/debug/pages/UserChatPage.tsx +250 -0
  72. package/src/components/debug/pages/index.ts +13 -0
  73. package/src/index.ts +55 -0
  74. package/src/lib/domain-utils.ts +12 -0
  75. package/src/providers/EventPollingProvider.tsx +60 -0
  76. package/src/stores/event-store.ts +497 -0
  77. package/src/utils/format.ts +8 -0
@@ -0,0 +1,497 @@
1
+ import type { AgentId, ProjectionEvent } from '@roj-ai/shared'
2
+ import type { DomainEvent } from '@roj-ai/sdk'
3
+ import {
4
+ type AgentDetailProjectionState,
5
+ type AgentRegistryState,
6
+ type AgentTreeNode,
7
+ type AgentTreeProjectionState,
8
+ type ChatDebugState,
9
+ type DebugChatMessage,
10
+ type GetAgentDetailResponse,
11
+ type GetMetricsResponse,
12
+ type GlobalMailboxMessage,
13
+ type MailboxState,
14
+ type MetricsState,
15
+ type ServicesProjectionState,
16
+ type SessionInfoState,
17
+ type TimelineItem,
18
+ type TimelineState,
19
+ applyEventToAgentDetail,
20
+ applyEventToAgentRegistry,
21
+ applyEventToAgentTree,
22
+ applyEventToChatDebug,
23
+ applyEventToMailbox,
24
+ applyEventToMetrics,
25
+ applyEventToServices,
26
+ applyEventToSessionInfo,
27
+ applyEventToTimeline,
28
+ buildAgentTreeFromProjection,
29
+ createAgentDetailProjectionState,
30
+ createAgentRegistryState,
31
+ createAgentTreeProjectionState,
32
+ createChatDebugState,
33
+ createMailboxState,
34
+ createMetricsState,
35
+ createServicesProjectionState,
36
+ createSessionInfoState,
37
+ createTimelineState,
38
+ getAgentDetail,
39
+ getChatDebugMessages,
40
+ getMailboxMessages,
41
+ getTimelineItems,
42
+ isDomainEvent,
43
+ metricsStateToResponse,
44
+ } from '@roj-ai/shared'
45
+ import { useMemo } from 'react'
46
+ import { create } from 'zustand'
47
+ import { api, unwrap } from '@roj-ai/client'
48
+
49
+ /**
50
+ * Event store for managing session events and derived state.
51
+ *
52
+ * This store:
53
+ * - Loads and polls for events from the server
54
+ * - Applies events incrementally to projection states (no SessionState needed)
55
+ * - Provides selectors for derived data (agent tree, timeline, mailbox, metrics)
56
+ *
57
+ * All projections are computed incrementally - adding a new event only updates
58
+ * the affected projection state, not rebuilds from scratch.
59
+ */
60
+ interface EventStoreState {
61
+ // Core state
62
+ sessionId: string | null
63
+ events: DomainEvent[]
64
+ lastIndex: number
65
+ isLoading: boolean
66
+ error: string | null
67
+
68
+ // Projection states (incremental reducers - internal)
69
+ sessionInfoState: SessionInfoState
70
+ agentRegistryState: AgentRegistryState
71
+ agentTreeProjectionState: AgentTreeProjectionState
72
+ agentDetailProjectionState: AgentDetailProjectionState
73
+ servicesProjectionState: ServicesProjectionState
74
+ metricsState: MetricsState
75
+ timelineState: TimelineState
76
+ mailboxState: MailboxState
77
+ chatDebugState: ChatDebugState
78
+
79
+ // Derived data (cached, updated when events change)
80
+ metrics: GetMetricsResponse
81
+ timeline: TimelineItem[]
82
+ agentTree: AgentTreeNode[]
83
+ globalMailbox: GlobalMailboxMessage[]
84
+ chatDebugMessages: DebugChatMessage[]
85
+
86
+ // Polling state
87
+ isPolling: boolean
88
+ pollIntervalMs: number
89
+
90
+ // Actions
91
+ loadSession: (sessionId: string) => Promise<void>
92
+ fetchNewEvents: () => Promise<void>
93
+ reset: () => void
94
+ startPolling: () => void
95
+ stopPolling: () => void
96
+ }
97
+
98
+ // Polling interval reference (stored outside Zustand for cleanup)
99
+ let pollingIntervalId: ReturnType<typeof setInterval> | null = null
100
+
101
+ // Counter to track which loadSession call is current (for cancellation)
102
+ let loadSessionCounter = 0
103
+
104
+ // Default empty metrics
105
+ const emptyMetrics: GetMetricsResponse = {
106
+ totalTokens: 0,
107
+ promptTokens: 0,
108
+ completionTokens: 0,
109
+ llmCalls: 0,
110
+ toolCalls: 0,
111
+ agentCount: 0,
112
+ totalCost: 0,
113
+ durationMs: 0,
114
+ byProvider: {},
115
+ }
116
+
117
+ interface ProjectionStates {
118
+ sessionInfoState: SessionInfoState
119
+ agentRegistryState: AgentRegistryState
120
+ agentTreeProjectionState: AgentTreeProjectionState
121
+ agentDetailProjectionState: AgentDetailProjectionState
122
+ servicesProjectionState: ServicesProjectionState
123
+ metricsState: MetricsState
124
+ timelineState: TimelineState
125
+ mailboxState: MailboxState
126
+ chatDebugState: ChatDebugState
127
+ }
128
+
129
+ function createInitialProjections(): ProjectionStates {
130
+ return {
131
+ sessionInfoState: createSessionInfoState(),
132
+ agentRegistryState: createAgentRegistryState(),
133
+ agentTreeProjectionState: createAgentTreeProjectionState(),
134
+ agentDetailProjectionState: createAgentDetailProjectionState(),
135
+ servicesProjectionState: createServicesProjectionState(),
136
+ metricsState: createMetricsState(),
137
+ timelineState: createTimelineState(),
138
+ mailboxState: createMailboxState(),
139
+ chatDebugState: createChatDebugState(),
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Compute all derived data from projection states.
145
+ */
146
+ function computeDerivedData(projections: ProjectionStates): {
147
+ metrics: GetMetricsResponse
148
+ timeline: TimelineItem[]
149
+ agentTree: AgentTreeNode[]
150
+ globalMailbox: GlobalMailboxMessage[]
151
+ chatDebugMessages: DebugChatMessage[]
152
+ } {
153
+ return {
154
+ metrics: metricsStateToResponse(projections.metricsState, projections.agentRegistryState.count),
155
+ timeline: getTimelineItems(projections.timelineState, projections.agentRegistryState),
156
+ agentTree: buildAgentTreeFromProjection(projections.agentTreeProjectionState),
157
+ globalMailbox: getMailboxMessages(projections.mailboxState),
158
+ chatDebugMessages: getChatDebugMessages(projections.chatDebugState),
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Apply a single event to all projection states.
164
+ * Does NOT compute derived data - call computeDerivedData separately at the end.
165
+ */
166
+ function applyEventToProjections(projections: ProjectionStates, event: DomainEvent): ProjectionStates {
167
+ const e = event as ProjectionEvent
168
+ // Update registry first (other projections may need agent names)
169
+ const agentRegistryState = applyEventToAgentRegistry(projections.agentRegistryState, e)
170
+
171
+ return {
172
+ sessionInfoState: applyEventToSessionInfo(projections.sessionInfoState, e),
173
+ agentRegistryState,
174
+ agentTreeProjectionState: applyEventToAgentTree(projections.agentTreeProjectionState, e),
175
+ agentDetailProjectionState: applyEventToAgentDetail(projections.agentDetailProjectionState, e),
176
+ servicesProjectionState: applyEventToServices(projections.servicesProjectionState, e),
177
+ metricsState: applyEventToMetrics(projections.metricsState, e),
178
+ timelineState: applyEventToTimeline(projections.timelineState, e, agentRegistryState),
179
+ mailboxState: applyEventToMailbox(projections.mailboxState, e, agentRegistryState),
180
+ chatDebugState: applyEventToChatDebug(projections.chatDebugState, e, agentRegistryState),
181
+ }
182
+ }
183
+
184
+ export const useEventStore = create<EventStoreState>((set, get) => ({
185
+ sessionId: null,
186
+ events: [],
187
+ lastIndex: -1,
188
+ ...createInitialProjections(),
189
+ // Derived data (cached)
190
+ metrics: emptyMetrics,
191
+ timeline: [],
192
+ agentTree: [],
193
+ globalMailbox: [],
194
+ chatDebugMessages: [],
195
+ isLoading: false,
196
+ error: null,
197
+ isPolling: false,
198
+ pollIntervalMs: 2000,
199
+
200
+ loadSession: async (sessionId: string) => {
201
+ // Stop any existing polling
202
+ get().stopPolling()
203
+
204
+ // Increment counter to invalidate any in-flight loadSession calls
205
+ const thisLoadId = ++loadSessionCounter
206
+
207
+ set({
208
+ sessionId,
209
+ events: [],
210
+ lastIndex: -1,
211
+ ...createInitialProjections(),
212
+ metrics: emptyMetrics,
213
+ timeline: [],
214
+ agentTree: [],
215
+ globalMailbox: [],
216
+ chatDebugMessages: [],
217
+ isLoading: true,
218
+ error: null,
219
+ })
220
+
221
+ try {
222
+ // Load all events for the session
223
+ const response = unwrap(await api.call('sessions.getEvents', { sessionId, limit: 10000 }))
224
+
225
+ // Check if this load was superseded by a newer one
226
+ if (loadSessionCounter !== thisLoadId) {
227
+ return
228
+ }
229
+
230
+ // Validate unknown[] to DomainEvent[]
231
+ const validEvents = response.events.filter(isDomainEvent)
232
+
233
+ if (validEvents.length === 0) {
234
+ set({
235
+ isLoading: false,
236
+ lastIndex: response.lastIndex,
237
+ })
238
+ return
239
+ }
240
+
241
+ // First event should be session_created
242
+ const firstEvent = validEvents[0]
243
+ const e = firstEvent as ProjectionEvent
244
+ if (e.type !== 'session_created') {
245
+ throw new Error('First event must be session_created')
246
+ }
247
+
248
+ // Apply all events to projections
249
+ let projections = createInitialProjections()
250
+ for (const event of validEvents) {
251
+ projections = applyEventToProjections(projections, event)
252
+ }
253
+
254
+ // Compute derived data once at the end
255
+ const derived = computeDerivedData(projections)
256
+
257
+ set({
258
+ events: validEvents,
259
+ lastIndex: response.lastIndex,
260
+ ...projections,
261
+ ...derived,
262
+ isLoading: false,
263
+ })
264
+
265
+ // Start polling for new events
266
+ get().startPolling()
267
+ } catch (err) {
268
+ set({
269
+ isLoading: false,
270
+ error: err instanceof Error ? err.message : 'Failed to load session events',
271
+ })
272
+ }
273
+ },
274
+
275
+ fetchNewEvents: async () => {
276
+ const { sessionId, lastIndex } = get()
277
+ if (!sessionId) return
278
+
279
+ try {
280
+ // Fetch events since last index
281
+ const response = unwrap(await api.call('sessions.getEvents', {
282
+ sessionId,
283
+ since: lastIndex,
284
+ limit: 1000,
285
+ }))
286
+
287
+ // Check if session was reset/changed while we were fetching
288
+ const currentState = get()
289
+ if (currentState.sessionId !== sessionId) {
290
+ return
291
+ }
292
+
293
+ // Validate unknown[] to DomainEvent[]
294
+ const newEvents = response.events.filter(isDomainEvent)
295
+
296
+ if (newEvents.length === 0) {
297
+ // No new events, just update lastIndex in case it changed
298
+ if (response.lastIndex !== lastIndex) {
299
+ set({ lastIndex: response.lastIndex })
300
+ }
301
+ return
302
+ }
303
+
304
+ // Get current projection states
305
+ const { events } = get()
306
+ let projections: ProjectionStates = {
307
+ sessionInfoState: currentState.sessionInfoState,
308
+ agentRegistryState: currentState.agentRegistryState,
309
+ agentTreeProjectionState: currentState.agentTreeProjectionState,
310
+ agentDetailProjectionState: currentState.agentDetailProjectionState,
311
+ servicesProjectionState: currentState.servicesProjectionState,
312
+ metricsState: currentState.metricsState,
313
+ timelineState: currentState.timelineState,
314
+ mailboxState: currentState.mailboxState,
315
+ chatDebugState: currentState.chatDebugState,
316
+ }
317
+
318
+ // If we don't have any agents yet, check if first event is session_created
319
+ if (projections.agentRegistryState.count === 0 && events.length === 0) {
320
+ const allEvents = [...events, ...newEvents]
321
+ const firstEvent = allEvents[0]
322
+ if (!firstEvent || firstEvent.type !== 'session_created') {
323
+ // Wait for session_created event
324
+ set({ lastIndex: response.lastIndex })
325
+ return
326
+ }
327
+
328
+ // Initialize from scratch
329
+ projections = createInitialProjections()
330
+ for (const event of allEvents) {
331
+ projections = applyEventToProjections(projections, event)
332
+ }
333
+
334
+ const derived = computeDerivedData(projections)
335
+ set({
336
+ events: allEvents,
337
+ lastIndex: response.lastIndex,
338
+ ...projections,
339
+ ...derived,
340
+ error: null,
341
+ })
342
+ return
343
+ }
344
+
345
+ // Apply only new events incrementally
346
+ for (const event of newEvents) {
347
+ projections = applyEventToProjections(projections, event)
348
+ }
349
+
350
+ const derived = computeDerivedData(projections)
351
+ set({
352
+ events: [...events, ...newEvents],
353
+ lastIndex: response.lastIndex,
354
+ ...projections,
355
+ ...derived,
356
+ error: null,
357
+ })
358
+ } catch (err) {
359
+ // Don't set error on poll failures to avoid UI flicker
360
+ console.error('Failed to fetch new events:', err)
361
+ }
362
+ },
363
+
364
+ reset: () => {
365
+ get().stopPolling()
366
+ // Invalidate any in-flight loadSession calls
367
+ loadSessionCounter++
368
+ set({
369
+ sessionId: null,
370
+ events: [],
371
+ lastIndex: -1,
372
+ ...createInitialProjections(),
373
+ metrics: emptyMetrics,
374
+ timeline: [],
375
+ agentTree: [],
376
+ globalMailbox: [],
377
+ chatDebugMessages: [],
378
+ isLoading: false,
379
+ error: null,
380
+ })
381
+ },
382
+
383
+ startPolling: () => {
384
+ if (pollingIntervalId) {
385
+ clearInterval(pollingIntervalId)
386
+ }
387
+
388
+ const { pollIntervalMs, fetchNewEvents } = get()
389
+ pollingIntervalId = setInterval(fetchNewEvents, pollIntervalMs)
390
+ set({ isPolling: true })
391
+ },
392
+
393
+ stopPolling: () => {
394
+ if (pollingIntervalId) {
395
+ clearInterval(pollingIntervalId)
396
+ pollingIntervalId = null
397
+ }
398
+ set({ isPolling: false })
399
+ },
400
+ }))
401
+
402
+ // ============================================================================
403
+ // Selectors - return cached data from store
404
+ // ============================================================================
405
+
406
+ /**
407
+ * Get agent tree (cached in store).
408
+ */
409
+ export function selectAgentTree(state: EventStoreState): AgentTreeNode[] {
410
+ return state.agentTree
411
+ }
412
+
413
+ /**
414
+ * Get timeline items (cached in store).
415
+ */
416
+ export function selectTimeline(state: EventStoreState): TimelineItem[] {
417
+ return state.timeline
418
+ }
419
+
420
+ /**
421
+ * Get global mailbox messages (cached in store).
422
+ */
423
+ export function selectGlobalMailbox(state: EventStoreState): GlobalMailboxMessage[] {
424
+ return state.globalMailbox
425
+ }
426
+
427
+ /**
428
+ * Get metrics (cached in store).
429
+ */
430
+ export function selectMetrics(state: EventStoreState): GetMetricsResponse {
431
+ return state.metrics
432
+ }
433
+
434
+ // ============================================================================
435
+ // React hooks for derived data
436
+ // ============================================================================
437
+
438
+ /**
439
+ * Hook to get agent tree (cached in store).
440
+ */
441
+ export function useAgentTree(): AgentTreeNode[] {
442
+ return useEventStore((s) => s.agentTree)
443
+ }
444
+
445
+ /**
446
+ * Hook to get timeline items (cached in store).
447
+ */
448
+ export function useTimeline(): TimelineItem[] {
449
+ return useEventStore((s) => s.timeline)
450
+ }
451
+
452
+ /**
453
+ * Hook to get global mailbox messages (cached in store).
454
+ */
455
+ export function useGlobalMailbox(): GlobalMailboxMessage[] {
456
+ return useEventStore((s) => s.globalMailbox)
457
+ }
458
+
459
+ /**
460
+ * Hook to get metrics (cached in store).
461
+ */
462
+ export function useMetrics(): GetMetricsResponse {
463
+ return useEventStore((s) => s.metrics)
464
+ }
465
+
466
+ /**
467
+ * Hook to get raw events.
468
+ */
469
+ export function useEvents(): DomainEvent[] {
470
+ return useEventStore((s) => s.events)
471
+ }
472
+
473
+ /**
474
+ * Hook to get chat debug messages (cached in store).
475
+ */
476
+ export function useChatDebug(): DebugChatMessage[] {
477
+ return useEventStore((s) => s.chatDebugMessages)
478
+ }
479
+
480
+ /**
481
+ * Hook to get session info (metadata from session lifecycle events).
482
+ */
483
+ export function useSessionInfo(): SessionInfoState {
484
+ return useEventStore((s) => s.sessionInfoState)
485
+ }
486
+
487
+ /**
488
+ * Hook to get agent detail by ID.
489
+ * Uses useMemo to avoid creating new objects on every render.
490
+ */
491
+ export function useAgentDetail(agentId: AgentId): GetAgentDetailResponse | null {
492
+ const agentDetailProjectionState = useEventStore((s) => s.agentDetailProjectionState)
493
+
494
+ return useMemo(() => {
495
+ return getAgentDetail(agentDetailProjectionState, agentId)
496
+ }, [agentDetailProjectionState, agentId])
497
+ }
@@ -0,0 +1,8 @@
1
+ export function formatDuration(ms: number): string {
2
+ if (ms < 1000) return `${Math.round(ms)}ms`
3
+ const seconds = Math.floor(ms / 1000)
4
+ if (seconds < 60) return `${seconds}s`
5
+ const minutes = Math.floor(seconds / 60)
6
+ const remainingSeconds = seconds % 60
7
+ return `${minutes}m ${remainingSeconds}s`
8
+ }