@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.
- package/dist/components/debug/DebugContext.d.ts +10 -0
- package/dist/components/debug/DebugNavigation.d.ts +29 -0
- package/dist/components/debug/DebugShell.d.ts +18 -0
- package/dist/components/debug/LLMCallDetail.d.ts +7 -0
- package/dist/components/debug/TimelineDetailInspector.d.ts +6 -0
- package/dist/components/debug/communication/CommunicationDiagram.d.ts +9 -0
- package/dist/components/debug/communication/DiagramHeader.d.ts +7 -0
- package/dist/components/debug/communication/ParticipantLane.d.ts +7 -0
- package/dist/components/debug/communication/TimeAxis.d.ts +9 -0
- package/dist/components/debug/communication/elements/IdleGap.d.ts +9 -0
- package/dist/components/debug/communication/elements/LLMBlock.d.ts +9 -0
- package/dist/components/debug/communication/elements/MessageArrow.d.ts +10 -0
- package/dist/components/debug/communication/elements/ToolBlock.d.ts +9 -0
- package/dist/components/debug/communication/hooks/useDiagramData.d.ts +12 -0
- package/dist/components/debug/communication/hooks/useTimeCompression.d.ts +7 -0
- package/dist/components/debug/communication/hooks/useZoomPan.d.ts +11 -0
- package/dist/components/debug/communication/popovers/ElementPopover.d.ts +8 -0
- package/dist/components/debug/communication/types.d.ts +136 -0
- package/dist/components/debug/index.d.ts +11 -0
- package/dist/components/debug/pages/AgentDetailPage.d.ts +3 -0
- package/dist/components/debug/pages/AgentsPage.d.ts +1 -0
- package/dist/components/debug/pages/CommunicationPage.d.ts +1 -0
- package/dist/components/debug/pages/DashboardPage.d.ts +1 -0
- package/dist/components/debug/pages/EventsPage.d.ts +1 -0
- package/dist/components/debug/pages/FilesPage.d.ts +1 -0
- package/dist/components/debug/pages/LLMCallPage.d.ts +1 -0
- package/dist/components/debug/pages/LLMCallsPage.d.ts +1 -0
- package/dist/components/debug/pages/LogsPage.d.ts +1 -0
- package/dist/components/debug/pages/MailboxPage.d.ts +1 -0
- package/dist/components/debug/pages/ServicesPage.d.ts +1 -0
- package/dist/components/debug/pages/TimelinePage.d.ts +1 -0
- package/dist/components/debug/pages/UserChatPage.d.ts +1 -0
- package/dist/components/debug/pages/index.d.ts +13 -0
- package/dist/index.d.ts +9 -0
- package/dist/lib/domain-utils.d.ts +7 -0
- package/dist/providers/EventPollingProvider.d.ts +27 -0
- package/dist/stores/event-store.d.ts +93 -0
- package/dist/utils/format.d.ts +1 -0
- package/package.json +43 -0
- package/src/components/debug/DebugContext.tsx +18 -0
- package/src/components/debug/DebugNavigation.tsx +55 -0
- package/src/components/debug/DebugShell.tsx +321 -0
- package/src/components/debug/LLMCallDetail.tsx +740 -0
- package/src/components/debug/TimelineDetailInspector.tsx +204 -0
- package/src/components/debug/communication/CommunicationDiagram.tsx +260 -0
- package/src/components/debug/communication/DiagramHeader.tsx +113 -0
- package/src/components/debug/communication/ParticipantLane.tsx +60 -0
- package/src/components/debug/communication/TimeAxis.tsx +106 -0
- package/src/components/debug/communication/elements/IdleGap.tsx +90 -0
- package/src/components/debug/communication/elements/LLMBlock.tsx +107 -0
- package/src/components/debug/communication/elements/MessageArrow.tsx +119 -0
- package/src/components/debug/communication/elements/ToolBlock.tsx +99 -0
- package/src/components/debug/communication/hooks/useDiagramData.ts +294 -0
- package/src/components/debug/communication/hooks/useTimeCompression.ts +140 -0
- package/src/components/debug/communication/hooks/useZoomPan.ts +87 -0
- package/src/components/debug/communication/popovers/ElementPopover.tsx +158 -0
- package/src/components/debug/communication/types.ts +180 -0
- package/src/components/debug/index.ts +37 -0
- package/src/components/debug/pages/AgentDetailPage.tsx +1295 -0
- package/src/components/debug/pages/AgentsPage.tsx +297 -0
- package/src/components/debug/pages/CommunicationPage.tsx +89 -0
- package/src/components/debug/pages/DashboardPage.tsx +1504 -0
- package/src/components/debug/pages/EventsPage.tsx +276 -0
- package/src/components/debug/pages/FilesPage.tsx +366 -0
- package/src/components/debug/pages/LLMCallPage.tsx +32 -0
- package/src/components/debug/pages/LLMCallsPage.tsx +473 -0
- package/src/components/debug/pages/LogsPage.tsx +199 -0
- package/src/components/debug/pages/MailboxPage.tsx +232 -0
- package/src/components/debug/pages/ServicesPage.tsx +193 -0
- package/src/components/debug/pages/TimelinePage.tsx +569 -0
- package/src/components/debug/pages/UserChatPage.tsx +250 -0
- package/src/components/debug/pages/index.ts +13 -0
- package/src/index.ts +55 -0
- package/src/lib/domain-utils.ts +12 -0
- package/src/providers/EventPollingProvider.tsx +60 -0
- package/src/stores/event-store.ts +497 -0
- 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
|
+
}
|