@agent-relay/dashboard 2.0.81 → 2.0.82

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 (244) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
  3. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
  4. package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
  5. package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
  6. package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
  7. package/out/about.html +2 -2
  8. package/out/about.txt +1 -1
  9. package/out/app/onboarding.html +1 -1
  10. package/out/app/onboarding.txt +1 -1
  11. package/out/app.html +1 -1
  12. package/out/app.txt +2 -2
  13. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
  14. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  15. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  16. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  17. package/out/blog.html +2 -2
  18. package/out/blog.txt +1 -1
  19. package/out/careers.html +2 -2
  20. package/out/careers.txt +1 -1
  21. package/out/changelog.html +2 -2
  22. package/out/changelog.txt +1 -1
  23. package/out/cloud/link.html +1 -1
  24. package/out/cloud/link.txt +2 -2
  25. package/out/complete-profile.html +2 -2
  26. package/out/complete-profile.txt +1 -1
  27. package/out/connect-repos.html +1 -1
  28. package/out/connect-repos.txt +1 -1
  29. package/out/contact.html +2 -2
  30. package/out/contact.txt +1 -1
  31. package/out/docs.html +2 -2
  32. package/out/docs.txt +1 -1
  33. package/out/history.html +1 -1
  34. package/out/history.txt +2 -2
  35. package/out/index.html +1 -1
  36. package/out/index.txt +2 -2
  37. package/out/login.html +2 -2
  38. package/out/login.txt +1 -1
  39. package/out/metrics.html +1 -1
  40. package/out/metrics.txt +2 -2
  41. package/out/pricing.html +2 -2
  42. package/out/pricing.txt +1 -1
  43. package/out/privacy.html +2 -2
  44. package/out/privacy.txt +1 -1
  45. package/out/providers/setup/claude.html +1 -1
  46. package/out/providers/setup/claude.txt +1 -1
  47. package/out/providers/setup/codex.html +1 -1
  48. package/out/providers/setup/codex.txt +1 -1
  49. package/out/providers/setup/cursor.html +1 -1
  50. package/out/providers/setup/cursor.txt +1 -1
  51. package/out/providers.html +1 -1
  52. package/out/providers.txt +1 -1
  53. package/out/security.html +2 -2
  54. package/out/security.txt +1 -1
  55. package/out/signup.html +2 -2
  56. package/out/signup.txt +1 -1
  57. package/out/terms.html +2 -2
  58. package/out/terms.txt +1 -1
  59. package/package.json +7 -1
  60. package/src/app/about/page.tsx +7 -0
  61. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
  62. package/src/app/app/[[...slug]]/page.tsx +23 -0
  63. package/src/app/app/onboarding/page.tsx +394 -0
  64. package/src/app/apple-icon.png +0 -0
  65. package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
  66. package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
  67. package/src/app/blog/page.tsx +15 -0
  68. package/src/app/careers/page.tsx +7 -0
  69. package/src/app/changelog/page.tsx +7 -0
  70. package/src/app/cloud/link/page.tsx +464 -0
  71. package/src/app/complete-profile/page.tsx +204 -0
  72. package/src/app/connect-repos/page.tsx +410 -0
  73. package/src/app/contact/page.tsx +7 -0
  74. package/src/app/docs/page.tsx +7 -0
  75. package/src/app/favicon.png +0 -0
  76. package/src/app/globals.css +200 -0
  77. package/src/app/history/page.tsx +658 -0
  78. package/src/app/layout.tsx +25 -0
  79. package/src/app/login/page.tsx +424 -0
  80. package/src/app/metrics/page.tsx +781 -0
  81. package/src/app/page.tsx +59 -0
  82. package/src/app/pricing/page.tsx +7 -0
  83. package/src/app/privacy/page.tsx +7 -0
  84. package/src/app/providers/page.tsx +193 -0
  85. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
  86. package/src/app/providers/setup/[provider]/constants.ts +35 -0
  87. package/src/app/providers/setup/[provider]/page.tsx +42 -0
  88. package/src/app/security/page.tsx +7 -0
  89. package/src/app/signup/page.tsx +533 -0
  90. package/src/app/terms/page.tsx +7 -0
  91. package/src/components/ActivityFeed.tsx +216 -0
  92. package/src/components/AddWorkspaceModal.tsx +170 -0
  93. package/src/components/AgentCard.test.tsx +134 -0
  94. package/src/components/AgentCard.tsx +585 -0
  95. package/src/components/AgentList.test.tsx +147 -0
  96. package/src/components/AgentList.tsx +419 -0
  97. package/src/components/AgentLogPreview.tsx +173 -0
  98. package/src/components/AgentProfilePanel.tsx +569 -0
  99. package/src/components/App.tsx +3424 -0
  100. package/src/components/BillingPanel.tsx +922 -0
  101. package/src/components/BillingResult.tsx +447 -0
  102. package/src/components/BroadcastComposer.tsx +690 -0
  103. package/src/components/ChannelAdminPanel.tsx +773 -0
  104. package/src/components/ChannelBrowser.tsx +385 -0
  105. package/src/components/ChannelChat.tsx +261 -0
  106. package/src/components/ChannelSidebar.tsx +399 -0
  107. package/src/components/CloudSessionProvider.tsx +130 -0
  108. package/src/components/CommandPalette.tsx +815 -0
  109. package/src/components/ConfirmationDialog.tsx +133 -0
  110. package/src/components/ConversationHistory.tsx +518 -0
  111. package/src/components/CoordinatorPanel.tsx +956 -0
  112. package/src/components/DecisionQueue.tsx +717 -0
  113. package/src/components/DirectMessageView.tsx +164 -0
  114. package/src/components/FileAutocomplete.tsx +368 -0
  115. package/src/components/FleetOverview.tsx +278 -0
  116. package/src/components/LogViewer.tsx +310 -0
  117. package/src/components/LogViewerPanel.tsx +482 -0
  118. package/src/components/Logo.tsx +284 -0
  119. package/src/components/MentionAutocomplete.tsx +384 -0
  120. package/src/components/MessageComposer.tsx +473 -0
  121. package/src/components/MessageList.tsx +725 -0
  122. package/src/components/MessageSenderName.tsx +91 -0
  123. package/src/components/MessageStatusIndicator.tsx +142 -0
  124. package/src/components/NewConversationModal.tsx +400 -0
  125. package/src/components/NotificationToast.tsx +488 -0
  126. package/src/components/OnlineUsersIndicator.tsx +164 -0
  127. package/src/components/Pagination.tsx +124 -0
  128. package/src/components/PricingPlans.tsx +386 -0
  129. package/src/components/ProjectList.tsx +711 -0
  130. package/src/components/ProviderAuthFlow.tsx +343 -0
  131. package/src/components/ProviderConnectionList.tsx +375 -0
  132. package/src/components/ProvisioningProgress.tsx +730 -0
  133. package/src/components/ReactionChips.tsx +70 -0
  134. package/src/components/ReactionPicker.tsx +121 -0
  135. package/src/components/RepoAccessPanel.tsx +787 -0
  136. package/src/components/RepositoriesPanel.tsx +901 -0
  137. package/src/components/ServerCard.tsx +202 -0
  138. package/src/components/SessionExpiredModal.tsx +128 -0
  139. package/src/components/SpawnModal.test.tsx +190 -0
  140. package/src/components/SpawnModal.tsx +1001 -0
  141. package/src/components/TaskAssignmentUI.tsx +375 -0
  142. package/src/components/TerminalProviderSetup.tsx +517 -0
  143. package/src/components/ThemeProvider.tsx +159 -0
  144. package/src/components/ThinkingIndicator.tsx +231 -0
  145. package/src/components/ThreadList.tsx +198 -0
  146. package/src/components/ThreadPanel.tsx +405 -0
  147. package/src/components/TrajectoryViewer.tsx +698 -0
  148. package/src/components/TypingIndicator.tsx +69 -0
  149. package/src/components/UsageBanner.tsx +231 -0
  150. package/src/components/UserProfilePanel.tsx +233 -0
  151. package/src/components/WorkspaceContext.tsx +95 -0
  152. package/src/components/WorkspaceSelector.tsx +234 -0
  153. package/src/components/WorkspaceStatusIndicator.tsx +396 -0
  154. package/src/components/XTermInteractive.tsx +516 -0
  155. package/src/components/XTermLogViewer.tsx +719 -0
  156. package/src/components/channels/ChannelDialogs.tsx +1411 -0
  157. package/src/components/channels/ChannelHeader.tsx +317 -0
  158. package/src/components/channels/ChannelMessageList.tsx +463 -0
  159. package/src/components/channels/ChannelViewV1.tsx +146 -0
  160. package/src/components/channels/MessageInput.tsx +302 -0
  161. package/src/components/channels/SearchInput.tsx +172 -0
  162. package/src/components/channels/SearchResults.tsx +336 -0
  163. package/src/components/channels/api.test.ts +1527 -0
  164. package/src/components/channels/api.ts +703 -0
  165. package/src/components/channels/index.ts +76 -0
  166. package/src/components/channels/mockApi.ts +344 -0
  167. package/src/components/channels/types.ts +566 -0
  168. package/src/components/hooks/index.ts +58 -0
  169. package/src/components/hooks/useAgentLogs.ts +504 -0
  170. package/src/components/hooks/useAgents.ts +127 -0
  171. package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
  172. package/src/components/hooks/useBroadcastDedup.ts +86 -0
  173. package/src/components/hooks/useChannelAdmin.ts +329 -0
  174. package/src/components/hooks/useChannelBrowser.ts +239 -0
  175. package/src/components/hooks/useChannelCommands.ts +138 -0
  176. package/src/components/hooks/useChannels.ts +367 -0
  177. package/src/components/hooks/useDebounce.ts +29 -0
  178. package/src/components/hooks/useDirectMessage.test.ts +952 -0
  179. package/src/components/hooks/useDirectMessage.ts +141 -0
  180. package/src/components/hooks/useMessages.ts +310 -0
  181. package/src/components/hooks/useOrchestrator.test.ts +165 -0
  182. package/src/components/hooks/useOrchestrator.ts +424 -0
  183. package/src/components/hooks/usePinnedAgents.test.ts +356 -0
  184. package/src/components/hooks/usePinnedAgents.ts +140 -0
  185. package/src/components/hooks/usePresence.test.ts +245 -0
  186. package/src/components/hooks/usePresence.ts +377 -0
  187. package/src/components/hooks/useRecentRepos.ts +130 -0
  188. package/src/components/hooks/useSession.ts +209 -0
  189. package/src/components/hooks/useThread.ts +138 -0
  190. package/src/components/hooks/useTrajectory.ts +265 -0
  191. package/src/components/hooks/useWebSocket.ts +290 -0
  192. package/src/components/hooks/useWorkspaceMembers.ts +132 -0
  193. package/src/components/hooks/useWorkspaceRepos.ts +73 -0
  194. package/src/components/hooks/useWorkspaceStatus.ts +237 -0
  195. package/src/components/index.ts +81 -0
  196. package/src/components/layout/Header.tsx +311 -0
  197. package/src/components/layout/RepoContextHeader.tsx +361 -0
  198. package/src/components/layout/Sidebar.archive.test.tsx +126 -0
  199. package/src/components/layout/Sidebar.test.tsx +691 -0
  200. package/src/components/layout/Sidebar.tsx +900 -0
  201. package/src/components/layout/index.ts +7 -0
  202. package/src/components/settings/BillingSettingsPanel.tsx +564 -0
  203. package/src/components/settings/SettingsPage.tsx +683 -0
  204. package/src/components/settings/TeamSettingsPanel.tsx +560 -0
  205. package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
  206. package/src/components/settings/index.ts +11 -0
  207. package/src/components/settings/types.ts +79 -0
  208. package/src/components/utils/messageFormatting.test.tsx +331 -0
  209. package/src/components/utils/messageFormatting.tsx +597 -0
  210. package/src/index.ts +63 -0
  211. package/src/landing/AboutPage.tsx +77 -0
  212. package/src/landing/BlogContent.tsx +187 -0
  213. package/src/landing/BlogPage.tsx +47 -0
  214. package/src/landing/CareersPage.tsx +53 -0
  215. package/src/landing/ChangelogPage.tsx +33 -0
  216. package/src/landing/ContactPage.tsx +41 -0
  217. package/src/landing/DocsPage.tsx +43 -0
  218. package/src/landing/LandingPage.tsx +702 -0
  219. package/src/landing/PricingPage.tsx +549 -0
  220. package/src/landing/PrivacyPage.tsx +117 -0
  221. package/src/landing/SecurityPage.tsx +42 -0
  222. package/src/landing/StaticPage.tsx +165 -0
  223. package/src/landing/TermsPage.tsx +125 -0
  224. package/src/landing/blogData.ts +312 -0
  225. package/src/landing/index.ts +18 -0
  226. package/src/landing/styles.css +3673 -0
  227. package/src/lib/agent-merge.test.ts +43 -0
  228. package/src/lib/agent-merge.ts +35 -0
  229. package/src/lib/api.ts +1294 -0
  230. package/src/lib/cloudApi.ts +893 -0
  231. package/src/lib/colors.test.ts +175 -0
  232. package/src/lib/colors.ts +218 -0
  233. package/src/lib/config.ts +109 -0
  234. package/src/lib/hierarchy.ts +242 -0
  235. package/src/lib/stuckDetection.ts +142 -0
  236. package/src/lib/useUrlRouting.ts +190 -0
  237. package/src/types/index.ts +317 -0
  238. package/src/types/threading.ts +7 -0
  239. package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
  240. package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
  241. /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
  243. /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
  244. /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
@@ -0,0 +1,516 @@
1
+ /**
2
+ * XTermInteractive Component
3
+ *
4
+ * Interactive terminal using xterm.js that allows user input.
5
+ * Used for direct terminal access to agents during setup, debugging,
6
+ * or when prompts require user interaction (skill loading, auth flows, etc.)
7
+ */
8
+
9
+ import React, { useRef, useEffect, useCallback, useState } from 'react';
10
+ import { Terminal } from '@xterm/xterm';
11
+ import { FitAddon } from '@xterm/addon-fit';
12
+ import { getAgentColor } from '../lib/colors';
13
+ import { useWorkspaceWsUrl } from './WorkspaceContext';
14
+
15
+ export interface XTermInteractiveProps {
16
+ /** Agent name to connect to */
17
+ agentName: string;
18
+ /** Maximum height of the terminal */
19
+ maxHeight?: string;
20
+ /** Whether to show the header bar */
21
+ showHeader?: boolean;
22
+ /** Callback when close button is clicked */
23
+ onClose?: () => void;
24
+ /** Custom class name */
25
+ className?: string;
26
+ }
27
+
28
+ // Theme matching the dashboard dark theme
29
+ const TERMINAL_THEME = {
30
+ background: '#0d0f14',
31
+ foreground: '#c9d1d9',
32
+ cursor: '#58a6ff',
33
+ cursorAccent: '#0d0f14',
34
+ selectionBackground: '#264f78',
35
+ selectionForeground: '#ffffff',
36
+ black: '#484f58',
37
+ red: '#f85149',
38
+ green: '#3fb950',
39
+ yellow: '#d29922',
40
+ blue: '#58a6ff',
41
+ magenta: '#bc8cff',
42
+ cyan: '#39c5cf',
43
+ white: '#b1bac4',
44
+ brightBlack: '#6e7681',
45
+ brightRed: '#ff7b72',
46
+ brightGreen: '#56d364',
47
+ brightYellow: '#e3b341',
48
+ brightBlue: '#79c0ff',
49
+ brightMagenta: '#d2a8ff',
50
+ brightCyan: '#56d4dd',
51
+ brightWhite: '#ffffff',
52
+ };
53
+
54
+ export function XTermInteractive({
55
+ agentName,
56
+ maxHeight = '500px',
57
+ showHeader = true,
58
+ onClose,
59
+ className = '',
60
+ }: XTermInteractiveProps) {
61
+ const containerRef = useRef<HTMLDivElement>(null);
62
+ const terminalRef = useRef<Terminal | null>(null);
63
+ const fitAddonRef = useRef<FitAddon | null>(null);
64
+ const wsRef = useRef<WebSocket | null>(null);
65
+ const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
66
+ const reconnectAttemptsRef = useRef(0);
67
+ const hasShownConnectedRef = useRef(false); // Prevent duplicate "Connected" messages
68
+
69
+ const [isConnected, setIsConnected] = useState(false);
70
+ const [isConnecting, setIsConnecting] = useState(false);
71
+ const [error, setError] = useState<Error | null>(null);
72
+
73
+ const colors = getAgentColor(agentName);
74
+
75
+ // Get WebSocket URL from workspace context (handles cloud vs local mode)
76
+ const logStreamUrl = useWorkspaceWsUrl(`/ws/logs/${encodeURIComponent(agentName)}`);
77
+
78
+ // Initialize terminal
79
+ useEffect(() => {
80
+ if (!containerRef.current) return;
81
+
82
+ // Reset connected message flag when agent changes
83
+ hasShownConnectedRef.current = false;
84
+
85
+ const terminal = new Terminal({
86
+ theme: TERMINAL_THEME,
87
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
88
+ fontSize: 12,
89
+ lineHeight: 1.4,
90
+ convertEol: true,
91
+ scrollback: 10000,
92
+ cursorBlink: true,
93
+ cursorStyle: 'block',
94
+ disableStdin: false, // Enable input!
95
+ allowProposedApi: true,
96
+ });
97
+
98
+ const fitAddon = new FitAddon();
99
+
100
+ terminal.loadAddon(fitAddon);
101
+
102
+ terminal.open(containerRef.current);
103
+ fitAddon.fit();
104
+
105
+ terminalRef.current = terminal;
106
+ fitAddonRef.current = fitAddon;
107
+
108
+ // Handle user input - send to WebSocket
109
+ // Suppress input when user has text selected (e.g., copying OAuth URL)
110
+ terminal.onData((data) => {
111
+ // Don't send input if user has text selected (likely copying a URL)
112
+ const selection = terminal.getSelection();
113
+ if (selection && selection.length > 0) {
114
+ return; // User is selecting/copying text, don't send input
115
+ }
116
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
117
+ wsRef.current.send(JSON.stringify({
118
+ type: 'input',
119
+ agent: agentName,
120
+ data,
121
+ }));
122
+ }
123
+ });
124
+
125
+ // Handle resize
126
+ const resizeObserver = new ResizeObserver(() => {
127
+ fitAddon.fit();
128
+ });
129
+ resizeObserver.observe(containerRef.current);
130
+
131
+ return () => {
132
+ resizeObserver.disconnect();
133
+ terminal.dispose();
134
+ terminalRef.current = null;
135
+ fitAddonRef.current = null;
136
+ };
137
+ }, [agentName]);
138
+
139
+ // Connect to WebSocket
140
+ const connect = useCallback(() => {
141
+ if (wsRef.current?.readyState === WebSocket.OPEN ||
142
+ wsRef.current?.readyState === WebSocket.CONNECTING) {
143
+ return;
144
+ }
145
+
146
+ setIsConnecting(true);
147
+ setError(null);
148
+
149
+ const ws = new WebSocket(logStreamUrl);
150
+ wsRef.current = ws;
151
+
152
+ ws.onopen = () => {
153
+ setIsConnected(true);
154
+ setIsConnecting(false);
155
+ setError(null);
156
+ reconnectAttemptsRef.current = 0;
157
+
158
+ // Only show connected message once per session
159
+ if (!hasShownConnectedRef.current) {
160
+ hasShownConnectedRef.current = true;
161
+ terminalRef.current?.writeln(`\x1b[90m[Connected to ${agentName} - Interactive Mode]\x1b[0m`);
162
+ terminalRef.current?.writeln(`\x1b[90m[You can type directly in this terminal]\x1b[0m\n`);
163
+ }
164
+ };
165
+
166
+ ws.onclose = (event) => {
167
+ setIsConnected(false);
168
+ setIsConnecting(false);
169
+ wsRef.current = null;
170
+
171
+ if (reconnectTimeoutRef.current) {
172
+ clearTimeout(reconnectTimeoutRef.current);
173
+ reconnectTimeoutRef.current = null;
174
+ }
175
+
176
+ // Don't reconnect for agent not found
177
+ if (event.code === 4404) {
178
+ terminalRef.current?.writeln(`\x1b[31m[Agent not found]\x1b[0m`);
179
+ return;
180
+ }
181
+
182
+ // Schedule reconnect
183
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttemptsRef.current), 30000);
184
+ reconnectAttemptsRef.current++;
185
+
186
+ terminalRef.current?.writeln(`\x1b[90m[Disconnected. Reconnecting in ${delay / 1000}s...]\x1b[0m`);
187
+
188
+ reconnectTimeoutRef.current = setTimeout(() => {
189
+ connect();
190
+ }, delay);
191
+ };
192
+
193
+ ws.onerror = () => {
194
+ setError(new Error('WebSocket connection error'));
195
+ setIsConnecting(false);
196
+ };
197
+
198
+ ws.onmessage = (event) => {
199
+ try {
200
+ const data = JSON.parse(event.data);
201
+
202
+ // Handle different message types
203
+ if (data.type === 'error') {
204
+ terminalRef.current?.writeln(`\x1b[31mError: ${data.error}\x1b[0m`);
205
+ return;
206
+ }
207
+
208
+ if (data.type === 'subscribed') {
209
+ return;
210
+ }
211
+
212
+ // Handle history (initial log dump)
213
+ if (data.type === 'history' && Array.isArray(data.lines)) {
214
+ data.lines.forEach((line: string) => {
215
+ terminalRef.current?.writeln(line);
216
+ });
217
+ return;
218
+ }
219
+
220
+ // Handle live output
221
+ if (data.type === 'log' || data.type === 'output') {
222
+ const content = data.content || data.data || data.message || '';
223
+ if (content) {
224
+ // Write raw content - xterm.js handles ANSI codes natively
225
+ terminalRef.current?.write(content);
226
+ }
227
+ return;
228
+ }
229
+
230
+ // Handle batch of lines
231
+ if (data.lines && Array.isArray(data.lines)) {
232
+ data.lines.forEach((line: string | { content: string }) => {
233
+ const content = typeof line === 'string' ? line : line.content;
234
+ terminalRef.current?.writeln(content);
235
+ });
236
+ }
237
+ } catch {
238
+ // Plain text message
239
+ if (typeof event.data === 'string') {
240
+ terminalRef.current?.write(event.data);
241
+ }
242
+ }
243
+ };
244
+ }, [logStreamUrl, agentName]);
245
+
246
+ // Disconnect from WebSocket
247
+ const disconnect = useCallback(() => {
248
+ if (reconnectTimeoutRef.current) {
249
+ clearTimeout(reconnectTimeoutRef.current);
250
+ reconnectTimeoutRef.current = null;
251
+ }
252
+
253
+ if (wsRef.current) {
254
+ wsRef.current.close();
255
+ wsRef.current = null;
256
+ }
257
+
258
+ setIsConnected(false);
259
+ setIsConnecting(false);
260
+ }, []);
261
+
262
+ // Clear terminal
263
+ const clear = useCallback(() => {
264
+ terminalRef.current?.clear();
265
+ }, []);
266
+
267
+ // Focus terminal
268
+ const focus = useCallback(() => {
269
+ terminalRef.current?.focus();
270
+ }, []);
271
+
272
+ // Auto-connect on mount
273
+ useEffect(() => {
274
+ connect();
275
+ return () => {
276
+ disconnect();
277
+ };
278
+ }, [connect, disconnect]);
279
+
280
+ // Focus terminal when connected
281
+ useEffect(() => {
282
+ if (isConnected) {
283
+ setTimeout(() => focus(), 100);
284
+ }
285
+ }, [isConnected, focus]);
286
+
287
+ return (
288
+ <div
289
+ className={`xterm-interactive flex flex-col rounded-xl overflow-hidden border border-[#2a2d35] shadow-2xl ${className}`}
290
+ style={{
291
+ background: 'linear-gradient(180deg, #0d0f14 0%, #0a0c10 100%)',
292
+ boxShadow: `0 0 60px -15px ${colors.primary}25, 0 25px 50px -12px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255,255,255,0.02)`,
293
+ }}
294
+ onClick={focus}
295
+ >
296
+ {/* Header */}
297
+ {showHeader && (
298
+ <div
299
+ className="flex items-center justify-between px-4 py-3 border-b border-[#21262d]"
300
+ style={{
301
+ background: 'linear-gradient(180deg, #161b22 0%, #0d1117 100%)',
302
+ }}
303
+ >
304
+ <div className="flex items-center gap-3">
305
+ <div className="flex items-center gap-2">
306
+ {/* Traffic light buttons */}
307
+ <div className="flex gap-1.5">
308
+ <div className="w-3 h-3 rounded-full bg-[#ff5f56] border border-[#e0443e]" />
309
+ <div className="w-3 h-3 rounded-full bg-[#ffbd2e] border border-[#dea123]" />
310
+ <div className="w-3 h-3 rounded-full bg-[#27c93f] border border-[#1aab29]" />
311
+ </div>
312
+ </div>
313
+ <div className="w-px h-4 bg-[#30363d]" />
314
+ <div className="flex items-center gap-2">
315
+ <TerminalIcon />
316
+ <span className="text-sm font-semibold" style={{ color: colors.primary }}>
317
+ {agentName}
318
+ </span>
319
+ <span className="px-1.5 py-0.5 rounded-full bg-accent-purple/20 text-[10px] text-accent-purple uppercase tracking-wider">
320
+ Interactive
321
+ </span>
322
+ <ConnectionBadge isConnected={isConnected} isConnecting={isConnecting} />
323
+ </div>
324
+ </div>
325
+ <div className="flex items-center gap-1.5">
326
+ {/* Clear logs */}
327
+ <button
328
+ className="p-1.5 rounded-lg hover:bg-[#21262d] text-[#8b949e] hover:text-[#c9d1d9] transition-all duration-200"
329
+ onClick={clear}
330
+ title="Clear terminal"
331
+ >
332
+ <TrashIcon />
333
+ </button>
334
+ {/* Connection toggle */}
335
+ <button
336
+ className={`p-1.5 rounded-lg transition-all duration-200 ${
337
+ isConnected
338
+ ? 'hover:bg-[#f85149]/10 text-[#8b949e] hover:text-[#f85149]'
339
+ : 'bg-[#3fb950]/20 text-[#3fb950] shadow-[0_0_12px_rgba(63,185,80,0.25)]'
340
+ }`}
341
+ onClick={isConnected ? disconnect : connect}
342
+ title={isConnected ? 'Disconnect' : 'Connect'}
343
+ >
344
+ {isConnected ? <PauseIcon /> : <PlayIcon />}
345
+ </button>
346
+ {/* Close button */}
347
+ {onClose && (
348
+ <>
349
+ <div className="w-px h-4 bg-[#30363d] mx-1" />
350
+ <button
351
+ className="p-1.5 rounded-lg hover:bg-[#f85149]/10 text-[#8b949e] hover:text-[#f85149] transition-all duration-200"
352
+ onClick={onClose}
353
+ title="Close"
354
+ >
355
+ <CloseIcon />
356
+ </button>
357
+ </>
358
+ )}
359
+ </div>
360
+ </div>
361
+ )}
362
+
363
+ {/* Error message */}
364
+ {error && (
365
+ <div className="px-4 py-2 bg-[#3d1d20] border-b border-[#f85149]/30 text-sm text-[#f85149] flex items-center gap-2">
366
+ <ErrorIcon />
367
+ <span>{error.message}</span>
368
+ <button
369
+ className="ml-auto text-xs px-2 py-0.5 rounded bg-[#f85149]/20 hover:bg-[#f85149]/30 transition-colors"
370
+ onClick={connect}
371
+ >
372
+ Retry
373
+ </button>
374
+ </div>
375
+ )}
376
+
377
+ {/* Terminal container */}
378
+ <div
379
+ ref={containerRef}
380
+ className="flex-1 overflow-hidden"
381
+ style={{ maxHeight, minHeight: '300px' }}
382
+ />
383
+
384
+ {/* Footer status bar */}
385
+ <div
386
+ className="flex items-center justify-between px-4 py-2.5 border-t border-[#21262d] text-xs"
387
+ style={{
388
+ background: 'linear-gradient(180deg, #0d1117 0%, #0a0c10 100%)',
389
+ }}
390
+ >
391
+ <div className="flex items-center gap-3">
392
+ <span className="text-[#6e7681]">
393
+ Type directly to interact with the agent
394
+ </span>
395
+ </div>
396
+ <div className="flex items-center gap-2">
397
+ <span className="text-[#6e7681] font-mono uppercase tracking-wider text-[10px]">
398
+ Interactive PTY
399
+ </span>
400
+ <div
401
+ className={`w-2 h-2 rounded-full transition-all duration-300 ${
402
+ isConnected
403
+ ? 'bg-[#3fb950]'
404
+ : isConnecting
405
+ ? 'bg-[#d29922] animate-pulse'
406
+ : 'bg-[#484f58]'
407
+ }`}
408
+ style={{
409
+ boxShadow: isConnected ? '0 0 8px rgba(63,185,80,0.6)' : 'none',
410
+ }}
411
+ />
412
+ </div>
413
+ </div>
414
+ </div>
415
+ );
416
+ }
417
+
418
+ // Icon components
419
+ function TerminalIcon() {
420
+ return (
421
+ <svg
422
+ width="16"
423
+ height="16"
424
+ viewBox="0 0 24 24"
425
+ fill="none"
426
+ stroke="currentColor"
427
+ strokeWidth="2"
428
+ strokeLinecap="round"
429
+ strokeLinejoin="round"
430
+ className="text-[#8b949e]"
431
+ >
432
+ <polyline points="4 17 10 11 4 5" />
433
+ <line x1="12" y1="19" x2="20" y2="19" />
434
+ </svg>
435
+ );
436
+ }
437
+
438
+ function TrashIcon() {
439
+ return (
440
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
441
+ <polyline points="3 6 5 6 21 6" />
442
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
443
+ </svg>
444
+ );
445
+ }
446
+
447
+ function CloseIcon() {
448
+ return (
449
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
450
+ <line x1="18" y1="6" x2="6" y2="18" />
451
+ <line x1="6" y1="6" x2="18" y2="18" />
452
+ </svg>
453
+ );
454
+ }
455
+
456
+ function PlayIcon() {
457
+ return (
458
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
459
+ <polygon points="5 3 19 12 5 21 5 3" />
460
+ </svg>
461
+ );
462
+ }
463
+
464
+ function PauseIcon() {
465
+ return (
466
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
467
+ <rect x="6" y="4" width="4" height="16" />
468
+ <rect x="14" y="4" width="4" height="16" />
469
+ </svg>
470
+ );
471
+ }
472
+
473
+ function ErrorIcon() {
474
+ return (
475
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
476
+ <circle cx="12" cy="12" r="10" />
477
+ <line x1="12" y1="8" x2="12" y2="12" />
478
+ <line x1="12" y1="16" x2="12.01" y2="16" />
479
+ </svg>
480
+ );
481
+ }
482
+
483
+ function ConnectionBadge({
484
+ isConnected,
485
+ isConnecting,
486
+ }: {
487
+ isConnected: boolean;
488
+ isConnecting: boolean;
489
+ }) {
490
+ if (isConnecting) {
491
+ return (
492
+ <span className="flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-[#d29922]/20 text-[10px] text-[#d29922] uppercase tracking-wider">
493
+ <span className="w-1.5 h-1.5 rounded-full bg-[#d29922] animate-pulse" />
494
+ connecting
495
+ </span>
496
+ );
497
+ }
498
+
499
+ if (isConnected) {
500
+ return (
501
+ <span className="flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-[#238636]/20 text-[10px] text-[#3fb950] uppercase tracking-wider">
502
+ <span className="w-1.5 h-1.5 rounded-full bg-[#3fb950] shadow-[0_0_4px_rgba(63,185,80,0.5)]" />
503
+ live
504
+ </span>
505
+ );
506
+ }
507
+
508
+ return (
509
+ <span className="flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-[#484f58]/20 text-[10px] text-[#484f58] uppercase tracking-wider">
510
+ <span className="w-1.5 h-1.5 rounded-full bg-[#484f58]" />
511
+ offline
512
+ </span>
513
+ );
514
+ }
515
+
516
+ export default XTermInteractive;