@agents-at-scale/ark 0.1.34 → 0.1.35-rc.1

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 (132) hide show
  1. package/dist/arkServices.d.ts +50 -0
  2. package/dist/arkServices.js +153 -0
  3. package/dist/charts/charts.d.ts +5 -0
  4. package/dist/charts/charts.js +6 -0
  5. package/dist/charts/dependencies.d.ts +6 -0
  6. package/dist/charts/dependencies.js +50 -0
  7. package/dist/charts/types.d.ts +40 -0
  8. package/dist/charts/types.js +1 -0
  9. package/dist/commands/agents/index.d.ts +2 -0
  10. package/dist/commands/agents/index.js +56 -0
  11. package/dist/commands/agents/selector.d.ts +8 -0
  12. package/dist/commands/agents/selector.js +53 -0
  13. package/dist/commands/agents.d.ts +2 -0
  14. package/dist/commands/agents.js +53 -0
  15. package/dist/commands/chat/index.d.ts +2 -0
  16. package/dist/commands/chat/index.js +45 -0
  17. package/dist/commands/chat.d.ts +2 -0
  18. package/dist/commands/chat.js +45 -0
  19. package/dist/commands/cluster/get.d.ts +2 -0
  20. package/dist/commands/cluster/get.js +39 -0
  21. package/dist/commands/cluster/index.js +2 -4
  22. package/dist/commands/completion/index.d.ts +2 -0
  23. package/dist/commands/completion/index.js +268 -0
  24. package/dist/commands/completion.js +159 -2
  25. package/dist/commands/config/index.d.ts +2 -0
  26. package/dist/commands/config/index.js +42 -0
  27. package/dist/commands/config.d.ts +0 -3
  28. package/dist/commands/config.js +38 -321
  29. package/dist/commands/dashboard/index.d.ts +3 -0
  30. package/dist/commands/dashboard/index.js +39 -0
  31. package/dist/commands/dashboard.d.ts +3 -0
  32. package/dist/commands/dashboard.js +39 -0
  33. package/dist/commands/dev/index.d.ts +2 -0
  34. package/dist/commands/dev/index.js +9 -0
  35. package/dist/commands/dev/tool/check.d.ts +2 -0
  36. package/dist/commands/dev/tool/check.js +142 -0
  37. package/dist/commands/dev/tool/clean.d.ts +2 -0
  38. package/dist/commands/dev/tool/clean.js +153 -0
  39. package/dist/commands/dev/tool/generate.d.ts +2 -0
  40. package/dist/commands/dev/tool/generate.js +28 -0
  41. package/dist/commands/dev/tool/index.d.ts +2 -0
  42. package/dist/commands/dev/tool/index.js +14 -0
  43. package/dist/commands/dev/tool/init.d.ts +2 -0
  44. package/dist/commands/dev/tool/init.js +320 -0
  45. package/dist/commands/dev/tool/shared.d.ts +5 -0
  46. package/dist/commands/dev/tool/shared.js +256 -0
  47. package/dist/commands/dev/tool/status.d.ts +2 -0
  48. package/dist/commands/dev/tool/status.js +136 -0
  49. package/dist/commands/dev/tool.d.ts +2 -0
  50. package/dist/commands/dev/tool.js +559 -0
  51. package/dist/commands/generate/config.js +5 -24
  52. package/dist/commands/generate/generators/mcpserver.d.ts +2 -1
  53. package/dist/commands/generate/generators/mcpserver.js +26 -5
  54. package/dist/commands/install/index.d.ts +6 -0
  55. package/dist/commands/install/index.js +165 -0
  56. package/dist/commands/install.d.ts +3 -0
  57. package/dist/commands/install.js +147 -0
  58. package/dist/commands/models/create.d.ts +1 -0
  59. package/dist/commands/models/create.js +213 -0
  60. package/dist/commands/models/index.d.ts +2 -0
  61. package/dist/commands/models/index.js +65 -0
  62. package/dist/commands/models/selector.d.ts +8 -0
  63. package/dist/commands/models/selector.js +53 -0
  64. package/dist/commands/routes/index.d.ts +2 -0
  65. package/dist/commands/routes/index.js +101 -0
  66. package/dist/commands/routes.d.ts +2 -0
  67. package/dist/commands/routes.js +101 -0
  68. package/dist/commands/status/index.d.ts +3 -0
  69. package/dist/commands/status/index.js +33 -0
  70. package/dist/commands/status.d.ts +3 -0
  71. package/dist/commands/status.js +33 -0
  72. package/dist/commands/targets/index.d.ts +2 -0
  73. package/dist/commands/targets/index.js +65 -0
  74. package/dist/commands/targets.d.ts +2 -0
  75. package/dist/commands/targets.js +65 -0
  76. package/dist/commands/teams/index.d.ts +2 -0
  77. package/dist/commands/teams/index.js +54 -0
  78. package/dist/commands/teams/selector.d.ts +8 -0
  79. package/dist/commands/teams/selector.js +55 -0
  80. package/dist/commands/tools/index.d.ts +2 -0
  81. package/dist/commands/tools/index.js +54 -0
  82. package/dist/commands/tools/selector.d.ts +8 -0
  83. package/dist/commands/tools/selector.js +53 -0
  84. package/dist/commands/uninstall/index.d.ts +2 -0
  85. package/dist/commands/uninstall/index.js +84 -0
  86. package/dist/commands/uninstall.d.ts +2 -0
  87. package/dist/commands/uninstall.js +83 -0
  88. package/dist/components/ChatUI.d.ts +16 -0
  89. package/dist/components/ChatUI.js +801 -0
  90. package/dist/components/StatusView.d.ts +10 -0
  91. package/dist/components/StatusView.js +39 -0
  92. package/dist/components/statusChecker.d.ts +10 -13
  93. package/dist/components/statusChecker.js +128 -65
  94. package/dist/config.js +3 -10
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.js +31 -36
  97. package/dist/lib/arkApiClient.d.ts +53 -0
  98. package/dist/lib/arkApiClient.js +102 -0
  99. package/dist/lib/arkApiProxy.d.ts +9 -0
  100. package/dist/lib/arkApiProxy.js +22 -0
  101. package/dist/lib/arkServiceProxy.d.ts +14 -0
  102. package/dist/lib/arkServiceProxy.js +93 -0
  103. package/dist/lib/arkStatus.d.ts +5 -0
  104. package/dist/lib/arkStatus.js +20 -0
  105. package/dist/lib/chatClient.d.ts +33 -0
  106. package/dist/lib/chatClient.js +101 -0
  107. package/dist/lib/cluster.d.ts +2 -1
  108. package/dist/lib/cluster.js +27 -3
  109. package/dist/lib/commandUtils.d.ts +4 -0
  110. package/dist/lib/commandUtils.js +18 -0
  111. package/dist/lib/commandUtils.test.d.ts +1 -0
  112. package/dist/lib/commandUtils.test.js +44 -0
  113. package/dist/lib/config.d.ts +24 -80
  114. package/dist/lib/config.js +68 -205
  115. package/dist/lib/config.test.d.ts +1 -0
  116. package/dist/lib/config.test.js +93 -0
  117. package/dist/lib/dev/tools/analyzer.d.ts +30 -0
  118. package/dist/lib/dev/tools/analyzer.js +190 -0
  119. package/dist/lib/dev/tools/discover_tools.py +392 -0
  120. package/dist/lib/dev/tools/mcp-types.d.ts +28 -0
  121. package/dist/lib/dev/tools/mcp-types.js +86 -0
  122. package/dist/lib/dev/tools/types.d.ts +50 -0
  123. package/dist/lib/dev/tools/types.js +1 -0
  124. package/dist/lib/output.d.ts +36 -0
  125. package/dist/lib/output.js +89 -0
  126. package/dist/lib/types.d.ts +8 -3
  127. package/dist/types/types.d.ts +40 -0
  128. package/dist/types/types.js +1 -0
  129. package/dist/ui/MainMenu.js +158 -90
  130. package/dist/ui/statusFormatter.d.ts +4 -1
  131. package/dist/ui/statusFormatter.js +91 -19
  132. package/package.json +16 -4
@@ -0,0 +1,801 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text, useInput, useApp } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import Spinner from 'ink-spinner';
5
+ import chalk from 'chalk';
6
+ import * as React from 'react';
7
+ import { marked } from 'marked';
8
+ // @ts-ignore - no types available
9
+ import TerminalRenderer from 'marked-terminal';
10
+ import { ChatClient, } from '../lib/chatClient.js';
11
+ import { AgentSelector } from '../commands/agents/selector.js';
12
+ import { ModelSelector } from '../commands/models/selector.js';
13
+ import { TeamSelector } from '../commands/teams/selector.js';
14
+ import { ToolSelector } from '../commands/tools/selector.js';
15
+ // Generate a unique ID for messages
16
+ let messageIdCounter = 0;
17
+ const generateMessageId = () => {
18
+ return `msg-${Date.now()}-${messageIdCounter++}`;
19
+ };
20
+ // Configure marked with terminal renderer for markdown output
21
+ const configureMarkdown = () => {
22
+ marked.setOptions({
23
+ renderer: new TerminalRenderer({
24
+ showSectionPrefix: false,
25
+ width: 80,
26
+ reflowText: true,
27
+ preserveNewlines: true,
28
+ }),
29
+ });
30
+ };
31
+ const ChatUI = ({ initialTargetId, arkApiClient, arkApiProxy, config, }) => {
32
+ const { exit } = useApp();
33
+ const [messages, setMessages] = React.useState([]);
34
+ const [input, setInput] = React.useState('');
35
+ const [isTyping, setIsTyping] = React.useState(false);
36
+ const [target, setTarget] = React.useState(null);
37
+ const [availableTargets, setAvailableTargets] = React.useState([]);
38
+ const [error, setError] = React.useState(null);
39
+ const [isLoading, setIsLoading] = React.useState(true);
40
+ const [targetIndex, setTargetIndex] = React.useState(0);
41
+ const [abortController, setAbortController] = React.useState(null);
42
+ const [showCommands, setShowCommands] = React.useState(false);
43
+ const [filteredCommands, setFilteredCommands] = React.useState([]);
44
+ const [inputKey, setInputKey] = React.useState(0); // Key to force re-mount of TextInput
45
+ const [outputFormat, setOutputFormat] = React.useState(config?.chat?.outputFormat || 'text');
46
+ const [showAgentSelector, setShowAgentSelector] = React.useState(false);
47
+ const [showModelSelector, setShowModelSelector] = React.useState(false);
48
+ const [showTeamSelector, setShowTeamSelector] = React.useState(false);
49
+ const [showToolSelector, setShowToolSelector] = React.useState(false);
50
+ // Message history navigation
51
+ const [messageHistory, setMessageHistory] = React.useState([]);
52
+ const [historyIndex, setHistoryIndex] = React.useState(-1);
53
+ // Initialize chat config from config prop
54
+ const [chatConfig, setChatConfig] = React.useState({
55
+ streamingEnabled: config?.chat?.streaming ?? true,
56
+ currentTarget: undefined,
57
+ });
58
+ const chatClientRef = React.useRef(undefined);
59
+ // Configure markdown when output format changes
60
+ React.useEffect(() => {
61
+ if (outputFormat === 'markdown') {
62
+ configureMarkdown();
63
+ }
64
+ }, [outputFormat]);
65
+ // Initialize chat client and fetch targets on mount
66
+ React.useEffect(() => {
67
+ const initializeChat = async () => {
68
+ try {
69
+ // Use the provided ArkApiClient to create ChatClient
70
+ const client = new ChatClient(arkApiClient);
71
+ chatClientRef.current = client;
72
+ const targets = await client.getQueryTargets();
73
+ setAvailableTargets(targets);
74
+ if (initialTargetId) {
75
+ // If initialTargetId is provided, find and set the target
76
+ const matchedTarget = targets.find((t) => t.id === initialTargetId);
77
+ const matchedIndex = targets.findIndex((t) => t.id === initialTargetId);
78
+ if (matchedTarget) {
79
+ setTarget(matchedTarget);
80
+ setTargetIndex(matchedIndex >= 0 ? matchedIndex : 0);
81
+ setChatConfig((prev) => ({ ...prev, currentTarget: matchedTarget }));
82
+ setMessages([]);
83
+ }
84
+ else {
85
+ // If target not found, show error and exit
86
+ console.error(chalk.red('Error:'), `Target "${initialTargetId}" not found`);
87
+ console.error(chalk.gray('Use "ark targets list" to see available targets'));
88
+ if (arkApiProxy) {
89
+ arkApiProxy.stop();
90
+ }
91
+ exit();
92
+ }
93
+ }
94
+ else if (targets.length > 0) {
95
+ // No initial target specified - auto-select first available
96
+ // Priority: agents > models > tools
97
+ const agents = targets.filter((t) => t.type === 'agent');
98
+ const models = targets.filter((t) => t.type === 'model');
99
+ const tools = targets.filter((t) => t.type === 'tool');
100
+ let selectedTarget = null;
101
+ let selectedIndex = 0;
102
+ if (agents.length > 0) {
103
+ selectedTarget = agents[0];
104
+ selectedIndex = targets.findIndex((t) => t.id === agents[0].id);
105
+ }
106
+ else if (models.length > 0) {
107
+ selectedTarget = models[0];
108
+ selectedIndex = targets.findIndex((t) => t.id === models[0].id);
109
+ }
110
+ else if (tools.length > 0) {
111
+ selectedTarget = tools[0];
112
+ selectedIndex = targets.findIndex((t) => t.id === tools[0].id);
113
+ }
114
+ if (selectedTarget) {
115
+ setTarget(selectedTarget);
116
+ setTargetIndex(selectedIndex);
117
+ setChatConfig((prev) => ({ ...prev, currentTarget: selectedTarget }));
118
+ setMessages([]);
119
+ }
120
+ else {
121
+ setError('No targets available');
122
+ }
123
+ }
124
+ else {
125
+ setError('No agents, models, or tools available');
126
+ }
127
+ setIsLoading(false);
128
+ }
129
+ catch (err) {
130
+ const errorMessage = err instanceof Error ? err.message : 'Failed to initialize chat';
131
+ console.error(chalk.red('Error:'), errorMessage);
132
+ if (arkApiProxy) {
133
+ arkApiProxy.stop();
134
+ }
135
+ exit();
136
+ }
137
+ };
138
+ initializeChat();
139
+ // Cleanup function to close port forward when component unmounts
140
+ return () => {
141
+ if (arkApiProxy) {
142
+ arkApiProxy.stop();
143
+ }
144
+ chatClientRef.current = undefined;
145
+ };
146
+ }, [initialTargetId]);
147
+ // Handle keyboard input
148
+ useInput((inputChar, key) => {
149
+ // Handle Ctrl+C to exit cleanly
150
+ if (inputChar === '\x03' || (key.ctrl && inputChar === 'c')) {
151
+ // Clean up resources
152
+ if (arkApiProxy) {
153
+ arkApiProxy.stop();
154
+ }
155
+ if (abortController) {
156
+ abortController.abort();
157
+ }
158
+ // Exit the app properly
159
+ exit();
160
+ return;
161
+ }
162
+ // Note: Ctrl+W for word deletion doesn't work reliably due to terminal/readline
163
+ // intercepting it before it reaches the app. Most terminals handle this at a lower level.
164
+ // Handle arrow keys for message history navigation
165
+ if (!showCommands && messageHistory.length > 0) {
166
+ if (key.upArrow && input === '') {
167
+ // Go back in history
168
+ const newIndex = historyIndex === -1
169
+ ? messageHistory.length - 1
170
+ : Math.max(0, historyIndex - 1);
171
+ if (newIndex >= 0 && newIndex < messageHistory.length) {
172
+ setHistoryIndex(newIndex);
173
+ setInput(messageHistory[newIndex]);
174
+ setInputKey((prev) => prev + 1); // Force re-mount to update cursor
175
+ }
176
+ return;
177
+ }
178
+ if (key.downArrow && input === '') {
179
+ // Go forward in history
180
+ if (historyIndex >= 0) {
181
+ const newIndex = Math.min(messageHistory.length - 1, historyIndex + 1);
182
+ if (newIndex === messageHistory.length - 1) {
183
+ // At the end of history, clear input
184
+ setHistoryIndex(-1);
185
+ setInput('');
186
+ }
187
+ else {
188
+ setHistoryIndex(newIndex);
189
+ setInput(messageHistory[newIndex]);
190
+ }
191
+ setInputKey((prev) => prev + 1); // Force re-mount to update cursor
192
+ }
193
+ return;
194
+ }
195
+ }
196
+ // Tab to autocomplete when there's a single matching command
197
+ if (key.tab &&
198
+ !key.shift &&
199
+ showCommands &&
200
+ filteredCommands.length === 1) {
201
+ // Set the completed command with a space at the end
202
+ const completedCommand = filteredCommands[0].command + ' ';
203
+ setInput(completedCommand);
204
+ // Keep the command hint visible but update to show only the completed command
205
+ setFilteredCommands([filteredCommands[0]]);
206
+ // Force re-mount of TextInput to reset cursor position
207
+ setInputKey((prev) => prev + 1);
208
+ return;
209
+ }
210
+ // Shift+Tab to cycle through targets
211
+ if (key.shift && key.tab && availableTargets.length > 0) {
212
+ // Cycle to next target
213
+ const nextIndex = (targetIndex + 1) % availableTargets.length;
214
+ const nextTarget = availableTargets[nextIndex];
215
+ setTargetIndex(nextIndex);
216
+ setTarget(nextTarget);
217
+ setChatConfig((prev) => ({ ...prev, currentTarget: nextTarget }));
218
+ }
219
+ // Esc to cancel current request
220
+ if (key.escape && isTyping && abortController) {
221
+ abortController.abort();
222
+ setAbortController(null);
223
+ setIsTyping(false);
224
+ // Mark the agent/team message as cancelled and add system message
225
+ setMessages((prev) => {
226
+ const newMessages = [...prev];
227
+ const lastMessage = newMessages[newMessages.length - 1];
228
+ if (lastMessage &&
229
+ (lastMessage.type === 'agent' || lastMessage.type === 'team')) {
230
+ lastMessage.cancelled = true;
231
+ // Remove the message if it has no content
232
+ if (lastMessage.type === 'agent' && !lastMessage.content) {
233
+ newMessages.pop();
234
+ }
235
+ else if (lastMessage.type === 'team' &&
236
+ lastMessage.members.length === 0) {
237
+ newMessages.pop();
238
+ }
239
+ }
240
+ // Add system message about interruption
241
+ const systemMessage = {
242
+ id: generateMessageId(),
243
+ type: 'system',
244
+ content: 'Interrupted by user',
245
+ timestamp: new Date(),
246
+ };
247
+ newMessages.push(systemMessage);
248
+ return newMessages;
249
+ });
250
+ }
251
+ });
252
+ const handleSubmit = async (value) => {
253
+ if (!value.trim())
254
+ return;
255
+ // Check for slash commands first (these work without a target)
256
+ if (value.startsWith('/output')) {
257
+ const parts = value.split(' ');
258
+ const arg = parts[1]?.toLowerCase();
259
+ if (arg === 'text' || arg === 'markdown') {
260
+ // Set output format
261
+ setOutputFormat(arg);
262
+ // Add system message to show the change
263
+ const systemMessage = {
264
+ id: generateMessageId(),
265
+ type: 'system',
266
+ content: `Output format set to ${arg}`,
267
+ timestamp: new Date(),
268
+ command: '/output',
269
+ };
270
+ setMessages((prev) => [...prev, systemMessage]);
271
+ }
272
+ else if (!arg) {
273
+ // Show current format
274
+ const systemMessage = {
275
+ id: generateMessageId(),
276
+ type: 'system',
277
+ content: `Current output format: ${outputFormat}`,
278
+ timestamp: new Date(),
279
+ command: '/output',
280
+ };
281
+ setMessages((prev) => [...prev, systemMessage]);
282
+ }
283
+ else {
284
+ // Show usage message
285
+ const systemMessage = {
286
+ id: generateMessageId(),
287
+ type: 'system',
288
+ content: `Use 'text' or 'markdown' e.g. /output markdown`,
289
+ timestamp: new Date(),
290
+ command: '/output',
291
+ };
292
+ setMessages((prev) => [...prev, systemMessage]);
293
+ }
294
+ setInput('');
295
+ setShowCommands(false);
296
+ setFilteredCommands([]);
297
+ return;
298
+ }
299
+ if (value.startsWith('/streaming')) {
300
+ const parts = value.split(' ');
301
+ const arg = parts[1]?.toLowerCase();
302
+ if (arg === 'on' || arg === 'off') {
303
+ // Set streaming based on argument
304
+ const newState = arg === 'on';
305
+ setChatConfig((prev) => ({ ...prev, streamingEnabled: newState }));
306
+ // Add system message to show the change
307
+ const systemMessage = {
308
+ id: generateMessageId(),
309
+ type: 'system',
310
+ content: `Streaming ${newState ? 'enabled' : 'disabled'}`,
311
+ timestamp: new Date(),
312
+ command: '/streaming',
313
+ };
314
+ setMessages((prev) => [...prev, systemMessage]);
315
+ }
316
+ else {
317
+ // Show usage message
318
+ const systemMessage = {
319
+ id: generateMessageId(),
320
+ type: 'system',
321
+ content: `Use either 'on' or 'off' e.g. /streaming on`,
322
+ timestamp: new Date(),
323
+ command: '/streaming',
324
+ };
325
+ setMessages((prev) => [...prev, systemMessage]);
326
+ }
327
+ setInput('');
328
+ setShowCommands(false);
329
+ setFilteredCommands([]);
330
+ return;
331
+ }
332
+ if (value.startsWith('/reset')) {
333
+ // Clear all messages
334
+ setMessages([]);
335
+ // Add system message to show the reset
336
+ const systemMessage = {
337
+ id: generateMessageId(),
338
+ type: 'system',
339
+ content: 'Message history cleared',
340
+ timestamp: new Date(),
341
+ command: '/reset',
342
+ };
343
+ setMessages([systemMessage]);
344
+ // Clear message history for arrow key navigation
345
+ setMessageHistory([]);
346
+ setHistoryIndex(-1);
347
+ setInput('');
348
+ setShowCommands(false);
349
+ setFilteredCommands([]);
350
+ return;
351
+ }
352
+ if (value.startsWith('/agents')) {
353
+ setShowAgentSelector(true);
354
+ setInput('');
355
+ setShowCommands(false);
356
+ setFilteredCommands([]);
357
+ return;
358
+ }
359
+ if (value.startsWith('/models')) {
360
+ setShowModelSelector(true);
361
+ setInput('');
362
+ setShowCommands(false);
363
+ setFilteredCommands([]);
364
+ return;
365
+ }
366
+ if (value.startsWith('/teams')) {
367
+ setShowTeamSelector(true);
368
+ setInput('');
369
+ setShowCommands(false);
370
+ setFilteredCommands([]);
371
+ return;
372
+ }
373
+ if (value.startsWith('/tools')) {
374
+ setShowToolSelector(true);
375
+ setInput('');
376
+ setShowCommands(false);
377
+ setFilteredCommands([]);
378
+ return;
379
+ }
380
+ // For regular messages, we need a target and client
381
+ if (!target || !chatClientRef.current) {
382
+ const systemMessage = {
383
+ id: generateMessageId(),
384
+ type: 'system',
385
+ content: 'No target selected. Use Shift+Tab to select a target.',
386
+ timestamp: new Date(),
387
+ };
388
+ setMessages((prev) => [...prev, systemMessage]);
389
+ setInput('');
390
+ setShowCommands(false);
391
+ setFilteredCommands([]);
392
+ return;
393
+ }
394
+ const userMessage = {
395
+ id: generateMessageId(),
396
+ type: 'user',
397
+ content: value,
398
+ timestamp: new Date(),
399
+ };
400
+ setMessages((prev) => [...prev, userMessage]);
401
+ // Add to message history
402
+ setMessageHistory((prev) => [...prev, value]);
403
+ setHistoryIndex(-1); // Reset history navigation
404
+ setInput('');
405
+ setIsTyping(true);
406
+ setError(null);
407
+ try {
408
+ // Create abort controller for this request
409
+ const controller = new AbortController();
410
+ setAbortController(controller);
411
+ // Convert messages to format expected by OpenAI API - only include user and agent messages
412
+ const apiMessages = messages
413
+ .filter((msg) => msg.type === 'user' || msg.type === 'agent' || msg.type === 'team')
414
+ .map((msg) => {
415
+ if (msg.type === 'user') {
416
+ return {
417
+ role: 'user',
418
+ content: msg.content,
419
+ };
420
+ }
421
+ else if (msg.type === 'agent') {
422
+ return {
423
+ role: 'assistant',
424
+ content: msg.content,
425
+ };
426
+ }
427
+ else if (msg.type === 'team') {
428
+ // For teams, concatenate all member responses
429
+ const content = msg.members.map((m) => m.content).join(' ');
430
+ return {
431
+ role: 'assistant',
432
+ content: content || '',
433
+ };
434
+ }
435
+ return { role: 'user', content: '' };
436
+ });
437
+ // Add the new user message
438
+ apiMessages.push({
439
+ role: 'user',
440
+ content: value,
441
+ });
442
+ // Add a placeholder message based on target type
443
+ const messageId = generateMessageId();
444
+ if (target.type === 'team') {
445
+ // For teams, create a TeamMessage
446
+ const teamMessage = {
447
+ id: messageId,
448
+ type: 'team',
449
+ targetName: target.name,
450
+ timestamp: new Date(),
451
+ members: [],
452
+ };
453
+ setMessages((prev) => [...prev, teamMessage]);
454
+ }
455
+ else {
456
+ // For agents/models, create an AgentMessage
457
+ const agentMessage = {
458
+ id: messageId,
459
+ type: 'agent',
460
+ targetName: target.name,
461
+ timestamp: new Date(),
462
+ content: '',
463
+ };
464
+ setMessages((prev) => [...prev, agentMessage]);
465
+ }
466
+ // Send message and get response with abort signal
467
+ const fullResponse = await chatClientRef.current.sendMessage(target.id, apiMessages, chatConfig, (chunk, toolCalls, arkMetadata) => {
468
+ // Update message progressively as chunks arrive
469
+ setMessages((prev) => {
470
+ const newMessages = [...prev];
471
+ const lastMessage = newMessages[newMessages.length - 1];
472
+ // Only update if not cancelled
473
+ if (lastMessage && !lastMessage.cancelled) {
474
+ if (lastMessage.type === 'team' && arkMetadata?.agent) {
475
+ // Handle team messages with agent metadata
476
+ const teamMsg = lastMessage;
477
+ // Find or create team member entry
478
+ let member = teamMsg.members.find((m) => m.agentName === arkMetadata.agent);
479
+ if (!member) {
480
+ member = {
481
+ agentName: arkMetadata.agent,
482
+ content: '',
483
+ color: 'blueBright',
484
+ };
485
+ teamMsg.members.push(member);
486
+ }
487
+ // Update member's content or tool calls
488
+ if (chunk) {
489
+ member.content += chunk;
490
+ }
491
+ if (toolCalls) {
492
+ member.toolCalls = toolCalls;
493
+ }
494
+ }
495
+ else if (lastMessage.type === 'agent') {
496
+ // Handle regular agent messages
497
+ const agentMsg = lastMessage;
498
+ if (chunk) {
499
+ agentMsg.content += chunk;
500
+ }
501
+ if (toolCalls) {
502
+ agentMsg.toolCalls = toolCalls;
503
+ }
504
+ }
505
+ }
506
+ return newMessages;
507
+ });
508
+ }, controller.signal);
509
+ // For non-streaming responses or final validation
510
+ setMessages((prev) => {
511
+ const newMessages = [...prev];
512
+ const lastMessage = newMessages[newMessages.length - 1];
513
+ // Only update if not cancelled
514
+ if (lastMessage && !lastMessage.cancelled) {
515
+ if (lastMessage.type === 'agent') {
516
+ const agentMsg = lastMessage;
517
+ // If content is empty (no streaming occurred), set the full response
518
+ if (!agentMsg.content && fullResponse) {
519
+ agentMsg.content = fullResponse;
520
+ }
521
+ // If no content at all, show a default message
522
+ if (!agentMsg.content && !agentMsg.toolCalls) {
523
+ agentMsg.content = 'No response received';
524
+ }
525
+ }
526
+ else if (lastMessage.type === 'team') {
527
+ const teamMsg = lastMessage;
528
+ // For teams in non-streaming mode, add the full response as a single member
529
+ if (!chatConfig.streamingEnabled &&
530
+ fullResponse &&
531
+ teamMsg.members.length === 0) {
532
+ teamMsg.members.push({
533
+ agentName: 'team',
534
+ content: fullResponse,
535
+ color: 'blueBright',
536
+ });
537
+ }
538
+ }
539
+ }
540
+ return newMessages;
541
+ });
542
+ setIsTyping(false);
543
+ setAbortController(null);
544
+ }
545
+ catch (err) {
546
+ // Check if this was cancelled by user
547
+ if (err instanceof Error && err.name === 'AbortError') {
548
+ // Request was cancelled, message already updated by Esc handler
549
+ return;
550
+ }
551
+ const errorMessage = err instanceof Error ? err.message : 'Failed to send message';
552
+ setError(errorMessage);
553
+ setIsTyping(false);
554
+ setAbortController(null);
555
+ // Update the agent/team message with the error
556
+ setMessages((prev) => {
557
+ const newMessages = [...prev];
558
+ const lastMessage = newMessages[newMessages.length - 1];
559
+ if (lastMessage && !lastMessage.cancelled) {
560
+ if (lastMessage.type === 'agent') {
561
+ lastMessage.content = `Error: ${errorMessage}`;
562
+ }
563
+ else if (lastMessage.type === 'team') {
564
+ // For team messages, add error as a single member
565
+ lastMessage.members = [
566
+ {
567
+ agentName: 'error',
568
+ content: `Error: ${errorMessage}`,
569
+ color: 'red',
570
+ },
571
+ ];
572
+ }
573
+ }
574
+ return newMessages;
575
+ });
576
+ }
577
+ };
578
+ const renderMessage = (msg, index) => {
579
+ const isLastMessage = index === messages.length - 1;
580
+ const isCurrentlyTyping = isTyping && isLastMessage;
581
+ const isCancelled = msg.cancelled === true;
582
+ // Handle different message types
583
+ switch (msg.type) {
584
+ case 'system':
585
+ return renderSystemMessage(msg, index);
586
+ case 'user':
587
+ return renderUserMessage(msg, index);
588
+ case 'agent':
589
+ return renderAgentMessage(msg, index, isCurrentlyTyping, isCancelled);
590
+ case 'team':
591
+ return renderTeamMessage(msg, index, isCurrentlyTyping, isCancelled);
592
+ }
593
+ };
594
+ const renderSystemMessage = (msg, index) => {
595
+ const isInterruption = msg.content === 'Interrupted by user';
596
+ // If it's a slash command response, show with special formatting
597
+ if (msg.command) {
598
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["\u203A ", msg.command] }) }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "gray", children: ["\u23BF ", msg.content] }) })] }, index));
599
+ }
600
+ // For other system messages (interruptions, errors, etc.)
601
+ const color = isInterruption ? 'yellow' : 'gray';
602
+ return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: color, children: ["\u2022 ", msg.content] }) }) }, index));
603
+ };
604
+ const renderUserMessage = (msg, index) => {
605
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "\u25CF" }), _jsx(Text, { children: " " }), _jsx(Text, { color: "cyan", bold: true, children: "You" }), _jsxs(Text, { color: "gray", children: [" ", msg.timestamp.toLocaleTimeString()] })] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { children: msg.content }) })] }, index));
606
+ };
607
+ const renderAgentMessage = (msg, index, isCurrentlyTyping, isCancelled) => {
608
+ const hasError = msg.content.startsWith('Error:') ||
609
+ msg.content === 'No response received';
610
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [!isCurrentlyTyping && !hasError && !isCancelled && (_jsx(Text, { color: "green", children: "\u25CF" })), isCurrentlyTyping && (_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) })), hasError && _jsx(Text, { color: "red", children: "\u25CF" }), isCancelled && _jsx(Text, { color: "gray", children: "\u25CF" }), _jsx(Text, { children: " " }), _jsx(Text, { color: isCurrentlyTyping
611
+ ? 'yellow'
612
+ : isCancelled
613
+ ? 'gray'
614
+ : hasError
615
+ ? 'red'
616
+ : 'green', bold: true, children: msg.targetName }), isCurrentlyTyping ? (_jsx(Text, { color: "gray", children: " (esc to interrupt)" })) : (_jsxs(Text, { color: "gray", children: [" ", msg.timestamp.toLocaleTimeString()] }))] }), msg.toolCalls &&
617
+ msg.toolCalls.length > 0 &&
618
+ renderToolCalls(msg.toolCalls), msg.content && (_jsx(Box, { marginLeft: 2, children: outputFormat === 'markdown' ? (_jsx(Text, { children: marked.parseInline(msg.content) })) : (_jsx(Text, { children: msg.content })) }))] }, index));
619
+ };
620
+ const renderTeamMessage = (msg, index, isCurrentlyTyping, isCancelled) => {
621
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [!isCurrentlyTyping && !isCancelled && _jsx(Text, { color: "green", children: "\u25CF" }), isCurrentlyTyping && (_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) })), isCancelled && _jsx(Text, { color: "gray", children: "\u25CF" }), _jsx(Text, { children: " " }), _jsx(Text, { color: isCurrentlyTyping ? 'yellow' : isCancelled ? 'gray' : 'green', bold: true, children: msg.targetName }), isCurrentlyTyping ? (_jsx(Text, { color: "gray", children: " (esc to interrupt)" })) : (_jsxs(Text, { color: "gray", children: [" ", msg.timestamp.toLocaleTimeString()] }))] }), msg.members.map((member, memberIndex) => (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: memberIndex > 0 ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: "blueBright", children: "\u2022" }), _jsx(Text, { children: " " }), _jsx(Text, { color: "blueBright", bold: true, children: member.agentName })] }), member.toolCalls && member.toolCalls.length > 0 && (_jsx(Box, { marginLeft: 2, children: renderToolCalls(member.toolCalls) })), member.content && (_jsx(Box, { marginLeft: 2, children: outputFormat === 'markdown' ? (_jsx(Text, { children: marked.parseInline(member.content) })) : (_jsx(Text, { children: member.content })) }))] }, memberIndex)))] }, index));
622
+ };
623
+ const renderToolCalls = (toolCalls) => {
624
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "magenta", bold: true, children: "Tool Calls:" }), toolCalls.map((toolCall, toolIndex) => (_jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: "magenta", children: ["\u2022 ", toolCall.function.name] }), toolCall.function.arguments && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", dimColor: true, children: (() => {
625
+ try {
626
+ const args = JSON.parse(toolCall.function.arguments);
627
+ return JSON.stringify(args, null, 2);
628
+ }
629
+ catch {
630
+ return toolCall.function.arguments;
631
+ }
632
+ })() }) }))] }, toolIndex)))] }));
633
+ };
634
+ // Show loading state
635
+ if (isLoading) {
636
+ return (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { color: "yellow", children: [_jsx(Spinner, { type: "dots" }), " Loading available targets..."] }) }));
637
+ }
638
+ // Show error if no targets available
639
+ if (!target && error) {
640
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: ["\u26A0 Error: ", error] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: "Please ensure ark-api is running and has available agents, models, or tools." }) })] }));
641
+ }
642
+ // Show agent selector if requested
643
+ if (showAgentSelector) {
644
+ return (_jsx(AgentSelector, { arkApiClient: arkApiClient, onSelect: (agent) => {
645
+ // Update the target to the selected agent
646
+ const agentTarget = {
647
+ id: `agent/${agent.name}`,
648
+ name: agent.name,
649
+ type: 'agent',
650
+ description: agent.description,
651
+ };
652
+ setTarget(agentTarget);
653
+ setChatConfig((prev) => ({ ...prev, currentTarget: agentTarget }));
654
+ setMessages([]);
655
+ setShowAgentSelector(false);
656
+ // Add system message about the selection
657
+ const systemMessage = {
658
+ id: generateMessageId(),
659
+ type: 'system',
660
+ content: `Switched to agent: ${agent.name}`,
661
+ timestamp: new Date(),
662
+ command: '/agents',
663
+ };
664
+ setMessages([systemMessage]);
665
+ }, onExit: () => setShowAgentSelector(false) }));
666
+ }
667
+ // Show model selector if requested
668
+ if (showModelSelector) {
669
+ return (_jsx(ModelSelector, { arkApiClient: arkApiClient, onSelect: (model) => {
670
+ // Update the target to the selected model
671
+ const modelTarget = {
672
+ id: `model/${model.name}`,
673
+ name: model.name,
674
+ type: 'model',
675
+ description: model.type,
676
+ };
677
+ setTarget(modelTarget);
678
+ setChatConfig((prev) => ({ ...prev, currentTarget: modelTarget }));
679
+ setMessages([]);
680
+ setShowModelSelector(false);
681
+ // Add system message about the selection
682
+ const systemMessage = {
683
+ id: generateMessageId(),
684
+ type: 'system',
685
+ content: `Switched to model: ${model.name}`,
686
+ timestamp: new Date(),
687
+ command: '/models',
688
+ };
689
+ setMessages([systemMessage]);
690
+ }, onExit: () => setShowModelSelector(false) }));
691
+ }
692
+ // Show team selector if requested
693
+ if (showTeamSelector) {
694
+ return (_jsx(TeamSelector, { arkApiClient: arkApiClient, onSelect: (team) => {
695
+ // Update the target to the selected team
696
+ const teamTarget = {
697
+ id: `team/${team.name}`,
698
+ name: team.name,
699
+ type: 'team',
700
+ description: team.strategy,
701
+ };
702
+ setTarget(teamTarget);
703
+ setChatConfig((prev) => ({ ...prev, currentTarget: teamTarget }));
704
+ setMessages([]);
705
+ setShowTeamSelector(false);
706
+ // Add system message about the selection
707
+ const systemMessage = {
708
+ id: generateMessageId(),
709
+ type: 'system',
710
+ content: `Switched to team: ${team.name}`,
711
+ timestamp: new Date(),
712
+ command: '/teams',
713
+ };
714
+ setMessages([systemMessage]);
715
+ }, onExit: () => setShowTeamSelector(false) }));
716
+ }
717
+ // Show tool selector if requested
718
+ if (showToolSelector) {
719
+ return (_jsx(ToolSelector, { arkApiClient: arkApiClient, onSelect: (tool) => {
720
+ // Update the target to the selected tool
721
+ const toolTarget = {
722
+ id: `tool/${tool.name}`,
723
+ name: tool.name,
724
+ type: 'tool',
725
+ description: tool.description,
726
+ };
727
+ setTarget(toolTarget);
728
+ setChatConfig((prev) => ({ ...prev, currentTarget: toolTarget }));
729
+ setMessages([]);
730
+ setShowToolSelector(false);
731
+ // Add system message about the selection
732
+ const systemMessage = {
733
+ id: generateMessageId(),
734
+ type: 'system',
735
+ content: `Switched to tool: ${tool.name}`,
736
+ timestamp: new Date(),
737
+ command: '/tools',
738
+ };
739
+ setMessages([systemMessage]);
740
+ }, onExit: () => setShowToolSelector(false) }));
741
+ }
742
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [messages.length === 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, paddingX: 2, children: [_jsx(Text, { bold: true, color: "green", children: "\u273B Welcome to ARK Chat!" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Type your message and press Enter to start" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: "Type '/' for available commands" }) })] })), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: messages.map(renderMessage) }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsxs(Box, { flexDirection: "row", width: "100%", children: [_jsx(Text, { color: "cyan", bold: true, children: "\u203A" }), _jsx(Box, { marginLeft: 1, flexGrow: 1, children: _jsx(TextInput, { value: input, onChange: (value) => {
743
+ setInput(value);
744
+ // Show commands menu only when input starts with '/'
745
+ const shouldShowCommands = value.startsWith('/');
746
+ setShowCommands(shouldShowCommands);
747
+ // Update filtered commands
748
+ if (shouldShowCommands) {
749
+ const inputLower = value.toLowerCase();
750
+ const commands = [
751
+ {
752
+ command: '/agents',
753
+ description: 'Select an agent to chat with',
754
+ },
755
+ {
756
+ command: '/models',
757
+ description: 'Select a model to chat with',
758
+ },
759
+ {
760
+ command: '/teams',
761
+ description: 'Select a team to chat with',
762
+ },
763
+ {
764
+ command: '/tools',
765
+ description: 'Select a tool to use',
766
+ },
767
+ {
768
+ command: '/output',
769
+ description: `Set output format (${outputFormat}) - use: /output text|markdown`,
770
+ },
771
+ {
772
+ command: '/streaming',
773
+ description: `Toggle streaming mode (${chatConfig.streamingEnabled ? 'on' : 'off'}) - use: /streaming on|off`,
774
+ },
775
+ {
776
+ command: '/reset',
777
+ description: 'Clear message history',
778
+ },
779
+ ];
780
+ // Check if user has typed a complete command (with space or at exact match)
781
+ const hasSpace = value.includes(' ');
782
+ const baseCommand = hasSpace ? value.split(' ')[0] : value;
783
+ // Filter commands - show matching commands or the current command if fully typed
784
+ const filtered = commands.filter((cmd) => {
785
+ if (hasSpace) {
786
+ // If there's a space, only show the exact matching command
787
+ return cmd.command === baseCommand;
788
+ }
789
+ else {
790
+ // Otherwise show all commands that start with the input
791
+ return cmd.command.toLowerCase().startsWith(inputLower);
792
+ }
793
+ });
794
+ setFilteredCommands(filtered);
795
+ }
796
+ else {
797
+ setFilteredCommands([]);
798
+ }
799
+ }, onSubmit: handleSubmit, placeholder: "Type your message..." }, inputKey) })] }) }), showCommands && filteredCommands.length > 0 && (_jsx(Box, { marginLeft: 1, marginTop: 1, flexDirection: "column", children: filteredCommands.map((cmd, index) => (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: cmd.command }), _jsxs(Text, { color: "gray", children: [" ", cmd.description] })] }, index))) })), !showCommands && (_jsx(Box, { marginLeft: 1, marginTop: 0, children: _jsxs(Box, { flexDirection: "row", children: [target && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: "Chatting with " }), _jsxs(Text, { color: "gray", children: [target.type, " "] }), _jsx(Text, { color: "green", children: target.name }), _jsx(Text, { color: "gray", children: " \u2022 Shift+Tab to cycle \u2022 " })] })), _jsx(Text, { color: "gray", children: "Ctrl+C to exit" })] }) }))] })] }));
800
+ };
801
+ export default ChatUI;