@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,232 @@
1
+ import type { GlobalMailboxMessage } from '@roj-ai/shared'
2
+ import { useState } from 'react'
3
+ import { useEventStore, useGlobalMailbox } from '../../../stores/event-store'
4
+ import { DebugLink } from '../DebugNavigation'
5
+
6
+ export function MailboxPage() {
7
+ // Get mailbox from event store (already loaded by DebugLayout)
8
+ const messages = useGlobalMailbox()
9
+ const isLoading = useEventStore((s) => s.isLoading)
10
+ const error = useEventStore((s) => s.error)
11
+
12
+ const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
13
+
14
+ const toggleExpanded = (id: string) => {
15
+ setExpandedIds((prev) => {
16
+ const next = new Set(prev)
17
+ if (next.has(id)) {
18
+ next.delete(id)
19
+ } else {
20
+ next.add(id)
21
+ }
22
+ return next
23
+ })
24
+ }
25
+
26
+ // Calculate stats
27
+ const total = messages.length
28
+ const pendingCount = messages.filter((m) => !m.consumed).length
29
+ const consumedCount = messages.filter((m) => m.consumed).length
30
+
31
+ return (
32
+ <div className="space-y-4">
33
+ {/* Summary */}
34
+ <div className="flex items-center gap-6 text-sm">
35
+ <span className="text-slate-600">
36
+ <span className="font-medium text-slate-900">{total}</span> messages
37
+ </span>
38
+ <span className="text-slate-600">
39
+ <span className="font-medium text-green-600">{consumedCount}</span> read
40
+ </span>
41
+ {pendingCount > 0 && (
42
+ <span className="text-slate-600">
43
+ <span className="font-medium text-blue-600">{pendingCount}</span> pending
44
+ </span>
45
+ )}
46
+ </div>
47
+
48
+ {/* Error */}
49
+ {error && <div className="text-red-500 text-sm">{error}</div>}
50
+
51
+ {/* Loading */}
52
+ {isLoading && messages.length === 0 && <div className="text-slate-500 text-sm">Loading mailbox...</div>}
53
+
54
+ {/* Table */}
55
+ {messages.length > 0 && (
56
+ <div className="bg-white rounded-md border border-slate-200 overflow-hidden">
57
+ <div className="overflow-x-auto">
58
+ <table className="w-full text-sm">
59
+ <thead className="bg-slate-50 border-b border-slate-200">
60
+ <tr>
61
+ <th className="px-3 py-2 text-left font-medium text-slate-600">From</th>
62
+ <th className="px-3 py-2 text-center font-medium text-slate-600 w-8"></th>
63
+ <th className="px-3 py-2 text-left font-medium text-slate-600">To</th>
64
+ <th className="px-3 py-2 text-left font-medium text-slate-600">Message</th>
65
+ <th className="px-3 py-2 text-left font-medium text-slate-600">Time</th>
66
+ <th className="px-3 py-2 text-center font-medium text-slate-600">Status</th>
67
+ </tr>
68
+ </thead>
69
+ <tbody className="divide-y divide-slate-200">
70
+ {messages.map((msg) => (
71
+ <MessageRow
72
+ key={msg.id}
73
+ message={msg}
74
+ isExpanded={expandedIds.has(msg.id)}
75
+ onToggleExpand={() => toggleExpanded(msg.id)}
76
+ />
77
+ ))}
78
+ </tbody>
79
+ </table>
80
+ </div>
81
+ </div>
82
+ )}
83
+
84
+ {/* Empty state */}
85
+ {!isLoading && messages.length === 0 && <div className="text-slate-500 text-sm">No messages found</div>}
86
+ </div>
87
+ )
88
+ }
89
+
90
+ function MessageRow({
91
+ message,
92
+ isExpanded,
93
+ onToggleExpand,
94
+ }: {
95
+ message: GlobalMailboxMessage
96
+ isExpanded: boolean
97
+ onToggleExpand: () => void
98
+ }) {
99
+ const isLongMessage = message.content.length > 100
100
+ const displayContent = isExpanded
101
+ ? message.content
102
+ : message.content.slice(0, 100) + (isLongMessage ? '...' : '')
103
+
104
+ return (
105
+ <tr className="hover:bg-slate-50">
106
+ {/* From */}
107
+ <td className="px-3 py-2">
108
+ <AgentBadge
109
+ agentId={message.fromAgentId}
110
+ agentName={message.fromAgentName}
111
+
112
+ />
113
+ </td>
114
+
115
+ {/* Arrow */}
116
+ <td className="px-3 py-2 text-center text-slate-400">
117
+ <ArrowIcon />
118
+ </td>
119
+
120
+ {/* To */}
121
+ <td className="px-3 py-2">
122
+ <AgentBadge
123
+ agentId={message.toAgentId}
124
+ agentName={message.toAgentName}
125
+
126
+ />
127
+ </td>
128
+
129
+ {/* Message */}
130
+ <td className="px-3 py-2">
131
+ <div
132
+ className={`text-slate-700 ${isLongMessage ? 'cursor-pointer' : ''}`}
133
+ onClick={isLongMessage ? onToggleExpand : undefined}
134
+ >
135
+ <span className="whitespace-pre-wrap break-words">{displayContent}</span>
136
+ {isLongMessage && (
137
+ <button
138
+ onClick={(e) => {
139
+ e.stopPropagation()
140
+ onToggleExpand()
141
+ }}
142
+ className="ml-2 text-violet-600 hover:text-violet-800 text-xs"
143
+ >
144
+ {isExpanded ? 'less' : 'more'}
145
+ </button>
146
+ )}
147
+ </div>
148
+ </td>
149
+
150
+ {/* Time */}
151
+ <td className="px-3 py-2 font-mono text-xs text-slate-500 whitespace-nowrap">
152
+ {new Date(message.timestamp).toLocaleTimeString()}
153
+ </td>
154
+
155
+ {/* Status */}
156
+ <td className="px-3 py-2 text-center">
157
+ <StatusBadge consumed={message.consumed} />
158
+ </td>
159
+ </tr>
160
+ )
161
+ }
162
+
163
+ function AgentBadge({
164
+ agentId,
165
+ agentName,
166
+ }: {
167
+ agentId: string
168
+ agentName: string
169
+ }) {
170
+ const isSpecial = agentId === 'user' || agentId === 'orchestrator' || agentId === 'communicator'
171
+
172
+ if (isSpecial) {
173
+ return (
174
+ <span
175
+ className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium ${
176
+ agentId === 'user'
177
+ ? 'bg-blue-100 text-blue-700'
178
+ : 'bg-purple-100 text-purple-700'
179
+ }`}
180
+ >
181
+ <span className="truncate max-w-24" title={agentName}>
182
+ {agentName}
183
+ </span>
184
+ </span>
185
+ )
186
+ }
187
+
188
+ return (
189
+ <DebugLink
190
+ to={`agents/${agentId}`}
191
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium bg-slate-100 text-slate-700 hover:bg-slate-200"
192
+ >
193
+ <span className="truncate max-w-24" title={agentName}>
194
+ {agentName}
195
+ </span>
196
+ </DebugLink>
197
+ )
198
+ }
199
+
200
+ function StatusBadge({ consumed }: { consumed: boolean }) {
201
+ if (consumed) {
202
+ return (
203
+ <span className="inline-flex items-center gap-1 text-xs text-slate-500">
204
+ <CheckIcon />
205
+ <span>Read</span>
206
+ </span>
207
+ )
208
+ }
209
+
210
+ return (
211
+ <span className="inline-flex items-center gap-1 text-xs text-blue-600">
212
+ <span className="w-2 h-2 rounded-full bg-blue-500 animate-pulse" />
213
+ <span>Pending</span>
214
+ </span>
215
+ )
216
+ }
217
+
218
+ function ArrowIcon() {
219
+ return (
220
+ <svg className="w-4 h-4 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24">
221
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
222
+ </svg>
223
+ )
224
+ }
225
+
226
+ function CheckIcon() {
227
+ return (
228
+ <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
229
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
230
+ </svg>
231
+ )
232
+ }
@@ -0,0 +1,193 @@
1
+ import { useCallback, useState } from 'react'
2
+ import type { ServiceEntry, ServiceStatus } from '@roj-ai/shared'
3
+ import { api } from '@roj-ai/client'
4
+ import { useDebugSessionId } from '../DebugNavigation'
5
+ import { useEventStore } from '../../../stores/event-store'
6
+
7
+ const statusColors: Record<ServiceStatus, string> = {
8
+ stopped: 'bg-slate-100 text-slate-700',
9
+ starting: 'bg-yellow-100 text-yellow-700',
10
+ ready: 'bg-green-100 text-green-700',
11
+ stopping: 'bg-orange-100 text-orange-700',
12
+ failed: 'bg-red-100 text-red-700',
13
+ paused: 'bg-blue-100 text-blue-700',
14
+ }
15
+
16
+ function formatTimestamp(ts: number | undefined): string {
17
+ if (ts === undefined) return '-'
18
+ return new Date(ts).toLocaleTimeString()
19
+ }
20
+
21
+ export function ServicesPage() {
22
+ const sessionId = useDebugSessionId()
23
+ const services = useEventStore((s) => s.servicesProjectionState.services)
24
+
25
+ if (!sessionId) return null
26
+
27
+ if (services.size === 0) {
28
+ return (
29
+ <div className="bg-white rounded-md border border-slate-200 p-8 text-center text-slate-500 text-sm">
30
+ No services configured
31
+ </div>
32
+ )
33
+ }
34
+
35
+ return (
36
+ <div className="flex flex-col gap-4">
37
+ <div className="bg-white rounded-md border border-slate-200 overflow-hidden">
38
+ <table className="w-full text-sm">
39
+ <thead>
40
+ <tr className="border-b border-slate-200 bg-slate-50 text-left text-xs font-medium text-slate-500 uppercase tracking-wider">
41
+ <th className="px-4 py-3">Service</th>
42
+ <th className="px-4 py-3">Status</th>
43
+ <th className="px-4 py-3">Port</th>
44
+ <th className="px-4 py-3">Started</th>
45
+ <th className="px-4 py-3">Ready</th>
46
+ <th className="px-4 py-3">Stopped</th>
47
+ <th className="px-4 py-3">Error</th>
48
+ <th className="px-4 py-3">Actions</th>
49
+ </tr>
50
+ </thead>
51
+ <tbody className="divide-y divide-slate-100">
52
+ {[...services.entries()].map(([key, entry]) => (
53
+ <ServiceRow key={key} entry={entry} sessionId={sessionId} />
54
+ ))}
55
+ </tbody>
56
+ </table>
57
+ </div>
58
+ </div>
59
+ )
60
+ }
61
+
62
+ function ServiceRow({ entry, sessionId }: { entry: ServiceEntry; sessionId: string }) {
63
+ const [loading, setLoading] = useState<string | null>(null)
64
+ const [error, setError] = useState<string | null>(null)
65
+ const [logs, setLogs] = useState<string[] | null>(null)
66
+
67
+ const callAction = useCallback(async (action: 'start' | 'stop' | 'restart') => {
68
+ setLoading(action)
69
+ setError(null)
70
+ try {
71
+ const result = action === 'start'
72
+ ? await api.call('services.start', { sessionId, serviceType: entry.serviceType })
73
+ : action === 'stop'
74
+ ? await api.call('services.stop', { sessionId, serviceType: entry.serviceType })
75
+ : await api.call('services.restart', { sessionId, serviceType: entry.serviceType })
76
+
77
+ if (!result.ok) {
78
+ setError(result.error.message)
79
+ }
80
+ } catch (e) {
81
+ setError(e instanceof Error ? e.message : 'Unknown error')
82
+ } finally {
83
+ setLoading(null)
84
+ }
85
+ }, [sessionId, entry.serviceType])
86
+
87
+ const fetchLogs = useCallback(async () => {
88
+ if (logs !== null) {
89
+ setLogs(null)
90
+ return
91
+ }
92
+ setLoading('logs')
93
+ setError(null)
94
+ try {
95
+ const result = await api.call('services.logs', { sessionId, serviceType: entry.serviceType, lines: 100 })
96
+ if (result.ok) {
97
+ setLogs(result.value.lines)
98
+ } else {
99
+ setError(result.error.message)
100
+ }
101
+ } catch (e) {
102
+ setError(e instanceof Error ? e.message : 'Unknown error')
103
+ } finally {
104
+ setLoading(null)
105
+ }
106
+ }, [sessionId, entry.serviceType, logs])
107
+
108
+ const isRunning = entry.status === 'ready' || entry.status === 'starting'
109
+ const isStopped = entry.status === 'stopped' || entry.status === 'failed'
110
+
111
+ return (
112
+ <>
113
+ <tr className="hover:bg-slate-50 transition-colors">
114
+ <td className="px-4 py-3 font-mono font-medium text-slate-900">{entry.serviceType}</td>
115
+ <td className="px-4 py-3">
116
+ <span className={`inline-block text-xs px-2 py-0.5 rounded-full font-medium ${statusColors[entry.status]}`}>
117
+ {entry.status}
118
+ </span>
119
+ </td>
120
+ <td className="px-4 py-3 font-mono text-slate-600">{entry.port ?? '-'}</td>
121
+ <td className="px-4 py-3 text-slate-500">{formatTimestamp(entry.startedAt)}</td>
122
+ <td className="px-4 py-3 text-slate-500">{formatTimestamp(entry.readyAt)}</td>
123
+ <td className="px-4 py-3 text-slate-500">{formatTimestamp(entry.stoppedAt)}</td>
124
+ <td className="px-4 py-3 text-red-600 text-xs">{entry.error ?? '-'}</td>
125
+ <td className="px-4 py-3">
126
+ <div className="flex items-center gap-1.5">
127
+ {isStopped && (
128
+ <ActionButton onClick={() => callAction('start')} loading={loading === 'start'} color="green">
129
+ Start
130
+ </ActionButton>
131
+ )}
132
+ {isRunning && (
133
+ <ActionButton onClick={() => callAction('stop')} loading={loading === 'stop'} color="red">
134
+ Stop
135
+ </ActionButton>
136
+ )}
137
+ {isRunning && (
138
+ <ActionButton onClick={() => callAction('restart')} loading={loading === 'restart'} color="yellow">
139
+ Restart
140
+ </ActionButton>
141
+ )}
142
+ <ActionButton onClick={fetchLogs} loading={loading === 'logs'} color="slate" active={logs !== null}>
143
+ Logs
144
+ </ActionButton>
145
+ </div>
146
+ {error && <div className="mt-1 text-xs text-red-600">{error}</div>}
147
+ </td>
148
+ </tr>
149
+ {logs !== null && (
150
+ <tr>
151
+ <td colSpan={8} className="px-4 py-2 bg-slate-900">
152
+ <div className="max-h-80 overflow-auto font-mono text-xs text-slate-300 whitespace-pre-wrap">
153
+ {logs.length === 0
154
+ ? <span className="text-slate-500">No logs available</span>
155
+ : logs.join('\n')}
156
+ </div>
157
+ </td>
158
+ </tr>
159
+ )}
160
+ </>
161
+ )
162
+ }
163
+
164
+ const buttonColors = {
165
+ green: 'bg-green-50 text-green-700 hover:bg-green-100 border-green-200',
166
+ red: 'bg-red-50 text-red-700 hover:bg-red-100 border-red-200',
167
+ yellow: 'bg-yellow-50 text-yellow-700 hover:bg-yellow-100 border-yellow-200',
168
+ slate: 'bg-slate-50 text-slate-700 hover:bg-slate-100 border-slate-200',
169
+ }
170
+
171
+ function ActionButton({
172
+ onClick,
173
+ loading,
174
+ color,
175
+ active,
176
+ children,
177
+ }: {
178
+ onClick: () => void
179
+ loading: boolean
180
+ color: keyof typeof buttonColors
181
+ active?: boolean
182
+ children: React.ReactNode
183
+ }) {
184
+ return (
185
+ <button
186
+ onClick={onClick}
187
+ disabled={loading}
188
+ className={`px-2 py-1 text-xs font-medium rounded border transition-colors disabled:opacity-50 ${active ? 'ring-1 ring-offset-1 ring-slate-400' : ''} ${buttonColors[color]}`}
189
+ >
190
+ {loading ? '...' : children}
191
+ </button>
192
+ )
193
+ }