galaxy-code 0.1.2 → 0.1.5

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 (74) hide show
  1. package/dist/app.d.ts +1 -1
  2. package/dist/app.js +5 -5
  3. package/dist/cli.js +4 -4
  4. package/dist/connections/claude.d.ts +71 -0
  5. package/dist/connections/claude.js +303 -0
  6. package/dist/connections/gemini.d.ts +40 -0
  7. package/dist/connections/gemini.js +232 -0
  8. package/dist/connections/index.d.ts +11 -0
  9. package/dist/connections/index.js +11 -0
  10. package/dist/connections/ollama.d.ts +37 -0
  11. package/dist/connections/ollama.js +73 -0
  12. package/dist/connections/types.d.ts +22 -0
  13. package/dist/connections/types.js +7 -0
  14. package/dist/node_modules/@workspace/agent-core/providers/agent-selector.d.ts.map +1 -1
  15. package/dist/node_modules/@workspace/agent-core/providers/agent-selector.js +18 -18
  16. package/dist/node_modules/@workspace/agent-core/providers/agent-selector.js.map +1 -1
  17. package/dist/prompts/ba-it-analyzer.d.ts +1 -0
  18. package/dist/prompts/ba-it-analyzer.js +143 -0
  19. package/dist/prompts/index.d.ts +11 -0
  20. package/dist/prompts/index.js +11 -0
  21. package/dist/prompts/orchestrator.d.ts +8 -0
  22. package/dist/prompts/orchestrator.js +88 -0
  23. package/dist/prompts/planning-agent.d.ts +8 -0
  24. package/dist/prompts/planning-agent.js +195 -0
  25. package/dist/prompts/universal-agent.d.ts +7 -0
  26. package/dist/prompts/universal-agent.js +111 -0
  27. package/dist/providers/agent-selector.d.ts +29 -0
  28. package/dist/providers/agent-selector.js +84 -0
  29. package/dist/providers/claude-agent.d.ts +29 -0
  30. package/dist/providers/claude-agent.js +121 -0
  31. package/dist/providers/gemini-agent.d.ts +36 -0
  32. package/dist/providers/gemini-agent.js +168 -0
  33. package/dist/providers/index.d.ts +12 -0
  34. package/dist/providers/index.js +12 -0
  35. package/dist/providers/ollama-agent.d.ts +53 -0
  36. package/dist/providers/ollama-agent.js +276 -0
  37. package/dist/providers/orchestrator.d.ts +16 -0
  38. package/dist/providers/orchestrator.js +76 -0
  39. package/dist/tools/ba-it-analyzer.d.ts +66 -0
  40. package/dist/tools/ba-it-analyzer.js +90 -0
  41. package/dist/tools/code-generate-agent.d.ts +51 -0
  42. package/dist/tools/code-generate-agent.js +159 -0
  43. package/dist/tools/command-runner.d.ts +14 -0
  44. package/dist/tools/command-runner.js +120 -0
  45. package/dist/tools/document-parser.d.ts +11 -0
  46. package/dist/tools/document-parser.js +83 -0
  47. package/dist/tools/file-operations.d.ts +17 -0
  48. package/dist/tools/file-operations.js +127 -0
  49. package/dist/tools/galaxy-ui-integration.d.ts +94 -0
  50. package/dist/tools/galaxy-ui-integration.js +244 -0
  51. package/dist/tools/git-operations.d.ts +11 -0
  52. package/dist/tools/git-operations.js +114 -0
  53. package/dist/tools/index.d.ts +10 -0
  54. package/dist/tools/index.js +10 -0
  55. package/dist/tools/planning-agent.d.ts +29 -0
  56. package/dist/tools/planning-agent.js +134 -0
  57. package/dist/tools/progress-reporter.d.ts +19 -0
  58. package/dist/tools/progress-reporter.js +52 -0
  59. package/dist/tools/registry.d.ts +21 -0
  60. package/dist/tools/registry.js +695 -0
  61. package/dist/tools/tool-event-emitter.d.ts +24 -0
  62. package/dist/tools/tool-event-emitter.js +25 -0
  63. package/dist/tools/types.d.ts +31 -0
  64. package/dist/tools/types.js +1 -0
  65. package/dist/types.d.ts +1 -1
  66. package/dist/utils/config-manager.d.ts +102 -0
  67. package/dist/utils/config-manager.js +326 -0
  68. package/dist/utils/devtools.d.ts +21 -0
  69. package/dist/utils/devtools.js +61 -0
  70. package/dist/utils/message-formatters.d.ts +32 -0
  71. package/dist/utils/message-formatters.js +590 -0
  72. package/dist/utils/progress-tracker.d.ts +59 -0
  73. package/dist/utils/progress-tracker.js +213 -0
  74. package/package.json +3 -2
@@ -0,0 +1,590 @@
1
+ /**
2
+ * @author Bùi Trọng Hiếu
3
+ * @email kevinbui210191@gmail.com
4
+ * @create 2025-01-10
5
+ * @desc Message formatters for rich tool output display
6
+ */
7
+ import React from 'react';
8
+ import { Box, Text } from 'ink';
9
+ /**
10
+ * Get terminal width with fallback
11
+ */
12
+ const getTerminalWidth = () => {
13
+ const width = process.stdout?.columns;
14
+ return typeof width === 'number' && Number.isFinite(width) ? width : 80;
15
+ };
16
+ /**
17
+ * Truncate long lines to fit terminal width
18
+ */
19
+ const truncateLine = (line, maxWidth) => {
20
+ if (line.length <= maxWidth)
21
+ return line;
22
+ return line.slice(0, maxWidth - 3) + '...';
23
+ };
24
+ /**
25
+ * Parse tool result to detect format type
26
+ */
27
+ export function detectMessageType(content, toolName) {
28
+ // Plan task outputs todos
29
+ if (toolName === 'plan_task') {
30
+ return 'todos';
31
+ }
32
+ // File operations show diffs
33
+ if (toolName === 'file_write' || toolName === 'code_generate') {
34
+ // Check if content has diff markers
35
+ if (content.includes('---') ||
36
+ content.includes('+++') ||
37
+ content.includes('@@')) {
38
+ return 'diff';
39
+ }
40
+ }
41
+ // Code blocks
42
+ if (content.includes('```')) {
43
+ return 'code';
44
+ }
45
+ return 'plain';
46
+ }
47
+ /**
48
+ * Generate simple diff between two texts
49
+ */
50
+ function generateDiff(oldText, newText) {
51
+ const oldLines = oldText.split('\n');
52
+ const newLines = newText.split('\n');
53
+ const diffLines = [];
54
+ let oldIdx = 0;
55
+ let newIdx = 0;
56
+ // Simple line-by-line comparison
57
+ while (oldIdx < oldLines.length || newIdx < newLines.length) {
58
+ const oldLine = oldLines[oldIdx];
59
+ const newLine = newLines[newIdx];
60
+ if (oldLine === newLine) {
61
+ // Context line
62
+ diffLines.push({
63
+ type: 'context',
64
+ oldLineNum: oldIdx + 1,
65
+ newLineNum: newIdx + 1,
66
+ content: oldLine || '',
67
+ });
68
+ oldIdx++;
69
+ newIdx++;
70
+ }
71
+ else if (oldIdx < oldLines.length && oldLine !== undefined && !newLines.includes(oldLine)) {
72
+ // Line removed
73
+ diffLines.push({
74
+ type: 'remove',
75
+ oldLineNum: oldIdx + 1,
76
+ content: oldLine || '',
77
+ });
78
+ oldIdx++;
79
+ }
80
+ else if (newIdx < newLines.length && newLine !== undefined && !oldLines.includes(newLine)) {
81
+ // Line added
82
+ diffLines.push({
83
+ type: 'add',
84
+ newLineNum: newIdx + 1,
85
+ content: newLine || '',
86
+ });
87
+ newIdx++;
88
+ }
89
+ else {
90
+ // Lines differ but exist in both - treat as remove + add
91
+ if (oldIdx < oldLines.length) {
92
+ diffLines.push({
93
+ type: 'remove',
94
+ oldLineNum: oldIdx + 1,
95
+ content: oldLine || '',
96
+ });
97
+ oldIdx++;
98
+ }
99
+ if (newIdx < newLines.length) {
100
+ diffLines.push({
101
+ type: 'add',
102
+ newLineNum: newIdx + 1,
103
+ content: newLine || '',
104
+ });
105
+ newIdx++;
106
+ }
107
+ }
108
+ }
109
+ return diffLines;
110
+ }
111
+ /**
112
+ * Compress diff by grouping context lines
113
+ */
114
+ function compressDiff(diffLines, contextLines = 2) {
115
+ const compressed = [];
116
+ let i = 0;
117
+ while (i < diffLines.length) {
118
+ const line = diffLines[i];
119
+ if (line && line.type !== 'context') {
120
+ // Add changed line
121
+ compressed.push(line);
122
+ i++;
123
+ }
124
+ else {
125
+ // Look for runs of context lines
126
+ const contextStart = i;
127
+ let contextEnd = i;
128
+ while (contextEnd < diffLines.length &&
129
+ diffLines[contextEnd]?.type === 'context') {
130
+ contextEnd++;
131
+ }
132
+ const contextLength = contextEnd - contextStart;
133
+ if (contextLength <= contextLines * 2) {
134
+ // Keep all context if small
135
+ for (let j = contextStart; j < contextEnd; j++) {
136
+ if (diffLines[j])
137
+ compressed.push(diffLines[j]);
138
+ }
139
+ }
140
+ else {
141
+ // Keep only beginning and end context
142
+ for (let j = contextStart; j < contextStart + contextLines; j++) {
143
+ if (diffLines[j])
144
+ compressed.push(diffLines[j]);
145
+ }
146
+ // Add placeholder for skipped lines
147
+ const skipped = contextLength - contextLines * 2;
148
+ compressed.push({
149
+ type: 'context',
150
+ content: `... ${skipped} unchanged lines ...`,
151
+ });
152
+ for (let j = contextEnd - contextLines; j < contextEnd; j++) {
153
+ if (diffLines[j])
154
+ compressed.push(diffLines[j]);
155
+ }
156
+ }
157
+ i = contextEnd;
158
+ }
159
+ }
160
+ return compressed;
161
+ }
162
+ /**
163
+ * Format todo list output from plan_task
164
+ */
165
+ export function formatTodos(content) {
166
+ try {
167
+ // Try to parse as JSON first (if plan_task returns structured data)
168
+ const data = JSON.parse(content);
169
+ if (data.tasks && Array.isArray(data.tasks)) {
170
+ return (React.createElement(Box, { flexDirection: "column" },
171
+ React.createElement(Text, { bold: true, color: "cyan" }, "\uD83D\uDCCB Todos"),
172
+ data.tasks.map((task, idx) => (React.createElement(Box, { key: idx, marginLeft: 2 },
173
+ React.createElement(Text, { color: task.completed ? 'green' : 'gray' },
174
+ task.completed ? '☒' : '☐',
175
+ ' ',
176
+ task.content || task.title || task.description))))));
177
+ }
178
+ }
179
+ catch {
180
+ // Not JSON, parse as plain text todo list
181
+ const lines = content.split('\n');
182
+ const todoLines = [];
183
+ for (const line of lines) {
184
+ const trimmed = line.trim();
185
+ // Match patterns like: "☒ Task name" or "- [x] Task name" or "✓ Task name"
186
+ if (trimmed.match(/^[☒✓✅-]\s*\[x\]\s*/i)) {
187
+ todoLines.push({
188
+ completed: true,
189
+ text: trimmed.replace(/^[☒✓✅-]\s*\[x\]\s*/i, ''),
190
+ });
191
+ }
192
+ else if (trimmed.match(/^[☐-]\s*\[\s*\]\s*/)) {
193
+ todoLines.push({
194
+ completed: false,
195
+ text: trimmed.replace(/^[☐-]\s*\[\s*\]\s*/, ''),
196
+ });
197
+ }
198
+ else if (trimmed.match(/^[-*•]\s+/)) {
199
+ todoLines.push({
200
+ completed: false,
201
+ text: trimmed.replace(/^[-*•]\s+/, ''),
202
+ });
203
+ }
204
+ }
205
+ if (todoLines.length > 0) {
206
+ return (React.createElement(Box, { flexDirection: "column" },
207
+ React.createElement(Text, { bold: true, color: "cyan" }, "\uD83D\uDCCB Todos"),
208
+ todoLines.map((todo, idx) => (React.createElement(Box, { key: idx, marginLeft: 2 },
209
+ React.createElement(Text, { color: todo.completed ? 'green' : 'gray' },
210
+ todo.completed ? '☒' : '☐',
211
+ " ",
212
+ todo.text))))));
213
+ }
214
+ }
215
+ // Fallback to plain text
216
+ return React.createElement(Text, null, content);
217
+ }
218
+ /**
219
+ * Format git-style diff output for file operations
220
+ */
221
+ export function formatDiff(content, filePath) {
222
+ const lines = content.split('\n');
223
+ const diffLines = [];
224
+ let lineNumber = 1;
225
+ for (let i = 0; i < lines.length; i++) {
226
+ const line = lines[i] || '';
227
+ // File header
228
+ if (line.startsWith('---') || line.startsWith('+++')) {
229
+ diffLines.push(React.createElement(Box, { key: i },
230
+ React.createElement(Text, { bold: true, color: "cyan" }, line)));
231
+ continue;
232
+ }
233
+ // Hunk header (e.g., @@ -1,4 +1,6 @@)
234
+ if (line.startsWith('@@')) {
235
+ diffLines.push(React.createElement(Box, { key: i },
236
+ React.createElement(Text, { bold: true, color: "magenta" }, line)));
237
+ continue;
238
+ }
239
+ // Deleted line
240
+ if (line.startsWith('-')) {
241
+ diffLines.push(React.createElement(Box, { key: i },
242
+ React.createElement(Text, { dimColor: true }, String(lineNumber).padStart(4, ' ')),
243
+ React.createElement(Text, { color: "red" }, ` ${line}`)));
244
+ lineNumber++;
245
+ continue;
246
+ }
247
+ // Added line
248
+ if (line.startsWith('+')) {
249
+ diffLines.push(React.createElement(Box, { key: i },
250
+ React.createElement(Text, { dimColor: true }, String(lineNumber).padStart(4, ' ')),
251
+ React.createElement(Text, { color: "green" }, ` ${line}`)));
252
+ lineNumber++;
253
+ continue;
254
+ }
255
+ // Context line (unchanged)
256
+ diffLines.push(React.createElement(Box, { key: i },
257
+ React.createElement(Text, { dimColor: true }, String(lineNumber).padStart(4, ' ')),
258
+ React.createElement(Text, null, ` ${line}`)));
259
+ lineNumber++;
260
+ }
261
+ return (React.createElement(Box, { flexDirection: "column" },
262
+ filePath && React.createElement(Text, { bold: true, color: "yellow" }, `📝 ${filePath}`),
263
+ diffLines));
264
+ }
265
+ /**
266
+ * Format code blocks
267
+ */
268
+ export function formatCode(content) {
269
+ const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
270
+ const parts = [];
271
+ let lastIndex = 0;
272
+ let match;
273
+ let idx = 0;
274
+ while ((match = codeBlockRegex.exec(content)) !== null) {
275
+ // Text before code block
276
+ if (match.index > lastIndex) {
277
+ parts.push(React.createElement(Text, { key: `text-${idx}` }, content.slice(lastIndex, match.index)));
278
+ }
279
+ // Code block
280
+ const language = match[1] || 'text';
281
+ const code = match[2];
282
+ parts.push(React.createElement(Box, { key: `code-${idx}`, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1 },
283
+ React.createElement(Text, { dimColor: true }, language),
284
+ React.createElement(Text, { color: "cyan" }, code)));
285
+ lastIndex = match.index + match[0].length;
286
+ idx++;
287
+ }
288
+ // Remaining text
289
+ if (lastIndex < content.length) {
290
+ parts.push(React.createElement(Text, { key: `text-${idx}` }, content.slice(lastIndex)));
291
+ }
292
+ return React.createElement(Box, { flexDirection: "column" }, parts);
293
+ }
294
+ /**
295
+ * Format file tree output
296
+ * Pattern: 📁 LIST DIRECTORY (path)
297
+ * ↳ Listed X items.
298
+ */
299
+ const formatFileTree = (content, toolInfo) => {
300
+ let itemCount = 0;
301
+ let directory = 'current directory';
302
+ // Get directory from toolInfo args
303
+ if (toolInfo?.args?.path) {
304
+ directory = toolInfo?.args.path;
305
+ }
306
+ // Count items from tree output
307
+ if (content) {
308
+ const lines = content.split('\n').filter(line => {
309
+ const trimmed = line.trim();
310
+ return trimmed && !trimmed.startsWith('Failed');
311
+ });
312
+ itemCount = lines.length;
313
+ }
314
+ return (React.createElement(Box, { flexDirection: "column" },
315
+ React.createElement(Text, { bold: true, color: "cyan" },
316
+ "\uD83D\uDCC1 LIST DIRECTORY ",
317
+ React.createElement(Text, { color: "gray" },
318
+ "(",
319
+ directory,
320
+ ")")),
321
+ React.createElement(Box, { marginLeft: 1 },
322
+ React.createElement(Text, { color: "green" },
323
+ "\u21B3 Listed ",
324
+ itemCount,
325
+ " items."))));
326
+ };
327
+ /**
328
+ * Format file read output
329
+ * Pattern: 📄 READ (filename)
330
+ * ↳ Read X lines.
331
+ */
332
+ const formatFileRead = (content, toolInfo) => {
333
+ let lineCount = 0;
334
+ let fileName = 'file';
335
+ // Get file name from toolInfo args
336
+ if (toolInfo?.args?.path) {
337
+ const parts = toolInfo.args.path.split('/');
338
+ fileName = parts[parts.length - 1] || toolInfo.args.path;
339
+ }
340
+ // Count lines from content
341
+ if (content) {
342
+ lineCount = content.split('\n').length;
343
+ }
344
+ return (React.createElement(Box, { flexDirection: "column" },
345
+ React.createElement(Text, { bold: true, color: "cyan" },
346
+ "\uD83D\uDCC4 READ ",
347
+ React.createElement(Text, { color: "gray" },
348
+ "(",
349
+ fileName,
350
+ ")")),
351
+ React.createElement(Box, { marginLeft: 1 },
352
+ React.createElement(Text, { color: "green" },
353
+ "\u21B3 Read ",
354
+ lineCount,
355
+ " lines."))));
356
+ };
357
+ /**
358
+ * Format file write output with diff view
359
+ * Handles both new file creation and file updates
360
+ */
361
+ const formatFileWrite = (content, toolInfo) => {
362
+ let fileName = 'file';
363
+ let writeResult = null;
364
+ // Try to parse FileWriteResult from content
365
+ try {
366
+ writeResult = JSON.parse(content);
367
+ }
368
+ catch {
369
+ // Fallback: treat as new file with content from args
370
+ if (toolInfo?.args?.content) {
371
+ writeResult = {
372
+ isNewFile: true,
373
+ newContent: toolInfo.args.content,
374
+ linesAdded: toolInfo.args.content.split('\n').length,
375
+ linesRemoved: 0,
376
+ path: toolInfo.args.path,
377
+ };
378
+ }
379
+ }
380
+ if (!writeResult) {
381
+ return React.createElement(Text, { wrap: "wrap" }, content);
382
+ }
383
+ // Get file name
384
+ if (writeResult.path) {
385
+ const parts = writeResult.path.split('/');
386
+ fileName = parts[parts.length - 1] || writeResult.path;
387
+ }
388
+ const { isNewFile, oldContent, newContent, linesAdded, linesRemoved } = writeResult;
389
+ // Header and status
390
+ const statusText = isNewFile
391
+ ? `Succeeded. File created. (+${linesAdded} added)`
392
+ : `Succeeded. File edited. (+${linesAdded} added, -${linesRemoved} removed)`;
393
+ // Calculate max content width (terminal width - border - padding - line numbers)
394
+ const terminalWidth = getTerminalWidth();
395
+ const maxContentWidth = terminalWidth - 15; // Reserve space for borders, padding, line numbers
396
+ if (isNewFile) {
397
+ // New file: show only first 10 lines
398
+ const diffLines = [];
399
+ const contentLines = newContent.split('\n');
400
+ const MAX_DISPLAY_LINES = 10;
401
+ // Chỉ hiển thị 10 dòng đầu nếu file có nhiều hơn 10 dòng
402
+ const linesToShow = contentLines.length > MAX_DISPLAY_LINES
403
+ ? contentLines.slice(0, MAX_DISPLAY_LINES)
404
+ : contentLines;
405
+ linesToShow.forEach((line, idx) => {
406
+ const truncated = truncateLine(line, maxContentWidth);
407
+ diffLines.push(React.createElement(Box, { key: idx },
408
+ React.createElement(Text, { dimColor: true }, String(idx + 1).padStart(4, ' ')),
409
+ React.createElement(Text, { color: "green" }, ` + ${truncated}`)));
410
+ });
411
+ // Thêm dòng "..." nếu có nhiều hơn 10 dòng
412
+ if (contentLines.length > MAX_DISPLAY_LINES) {
413
+ diffLines.push(React.createElement(Box, { key: "truncated" },
414
+ React.createElement(Text, { dimColor: true },
415
+ "... (",
416
+ contentLines.length - MAX_DISPLAY_LINES,
417
+ " more lines)")));
418
+ }
419
+ return (React.createElement(Box, { flexDirection: "column", width: terminalWidth - 2 },
420
+ React.createElement(Text, { bold: true, color: "cyan" },
421
+ "\uD83D\uDCDD APPLY PATCH ",
422
+ React.createElement(Text, { color: "gray" },
423
+ "(",
424
+ fileName,
425
+ ")")),
426
+ React.createElement(Box, { marginLeft: 1, marginBottom: 1 },
427
+ React.createElement(Text, { color: "green" },
428
+ "\u21B3 ",
429
+ statusText)),
430
+ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, paddingY: 0, width: terminalWidth - 4 }, diffLines)));
431
+ }
432
+ else {
433
+ // File edited: show full diff
434
+ const diffLines = generateDiff(oldContent || '', newContent);
435
+ const compressedDiff = compressDiff(diffLines, 2);
436
+ const diffElements = [];
437
+ compressedDiff.forEach((line, idx) => {
438
+ if (line.content.startsWith('...') &&
439
+ line.content.includes('unchanged')) {
440
+ // Placeholder for skipped lines
441
+ diffElements.push(React.createElement(Box, { key: idx },
442
+ React.createElement(Text, { dimColor: true }, line.content)));
443
+ }
444
+ else if (line.type === 'add') {
445
+ const truncated = truncateLine(line.content, maxContentWidth);
446
+ diffElements.push(React.createElement(Box, { key: idx },
447
+ React.createElement(Text, { dimColor: true }, ' '.padStart(4, ' ')),
448
+ React.createElement(Text, { dimColor: true }, "|"),
449
+ React.createElement(Text, { dimColor: true }, String(line.newLineNum).padStart(4, ' ')),
450
+ React.createElement(Text, { color: "green" }, ` + ${truncated}`)));
451
+ }
452
+ else if (line.type === 'remove') {
453
+ const truncated = truncateLine(line.content, maxContentWidth);
454
+ diffElements.push(React.createElement(Box, { key: idx },
455
+ React.createElement(Text, { dimColor: true }, String(line.oldLineNum).padStart(4, ' ')),
456
+ React.createElement(Text, { dimColor: true }, "|"),
457
+ React.createElement(Text, { dimColor: true }, ' '.padStart(4, ' ')),
458
+ React.createElement(Text, { color: "red" }, ` - ${truncated}`)));
459
+ }
460
+ else {
461
+ // Context line
462
+ const truncated = truncateLine(line.content, maxContentWidth);
463
+ diffElements.push(React.createElement(Box, { key: idx },
464
+ React.createElement(Text, { dimColor: true }, String(line.oldLineNum).padStart(4, ' ')),
465
+ React.createElement(Text, { dimColor: true }, "|"),
466
+ React.createElement(Text, { dimColor: true }, String(line.newLineNum).padStart(4, ' ')),
467
+ React.createElement(Text, null, ` ${truncated}`)));
468
+ }
469
+ });
470
+ // Add truncation notice if diff is too long
471
+ if (diffElements.length > 30) {
472
+ diffElements.splice(30);
473
+ diffElements.push(React.createElement(Box, { key: "truncated" },
474
+ React.createElement(Text, { dimColor: true }, "... (diff truncated for display)")));
475
+ }
476
+ return (React.createElement(Box, { flexDirection: "column", width: terminalWidth - 2 },
477
+ React.createElement(Text, { bold: true, color: "cyan" },
478
+ "\uD83D\uDCDD APPLY PATCH ",
479
+ React.createElement(Text, { color: "gray" },
480
+ "(",
481
+ fileName,
482
+ ")")),
483
+ React.createElement(Box, { marginLeft: 1, marginBottom: 1 },
484
+ React.createElement(Text, { color: "green" },
485
+ "\u21B3 ",
486
+ statusText)),
487
+ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, paddingY: 0, width: terminalWidth - 4 }, diffElements)));
488
+ }
489
+ };
490
+ /**
491
+ * Format command execution output
492
+ * Pattern: ⚡ EXECUTE (command)
493
+ * ↳ ✓ Success. Output: X lines. (hoặc ✗ Failed)
494
+ */
495
+ const formatCommandRun = (content, toolInfo) => {
496
+ let isSuccess = true;
497
+ let outputLines = 0;
498
+ let command = 'command';
499
+ // Get command from toolInfo args
500
+ if (toolInfo?.args?.command) {
501
+ command = toolInfo.args.command;
502
+ }
503
+ // Parse content to determine success/failure
504
+ if (content) {
505
+ // Format: stdout:\n...\n\nstderr:\n...
506
+ const stdoutMatch = content.match(/stdout:\n([\s\S]*?)\n\nstderr:/);
507
+ const stderrMatch = content.match(/stderr:\n([\s\S]*)/);
508
+ if (stdoutMatch && stdoutMatch[1]) {
509
+ const lines = stdoutMatch[1].split('\n').filter(line => line.trim());
510
+ outputLines = lines.length;
511
+ }
512
+ // Check if there's meaningful error content in stderr
513
+ if (stderrMatch && stderrMatch[1]) {
514
+ const stderrContent = stderrMatch[1].trim();
515
+ // Some commands output warnings to stderr but still succeed
516
+ // Only mark as failed if there's actual error content
517
+ if (stderrContent && stderrContent.toLowerCase().includes('error')) {
518
+ isSuccess = false;
519
+ }
520
+ }
521
+ // Check for common error indicators
522
+ if (content.toLowerCase().includes('command not found') ||
523
+ content.toLowerCase().includes('no such file') ||
524
+ content.toLowerCase().includes('permission denied')) {
525
+ isSuccess = false;
526
+ }
527
+ }
528
+ const statusIcon = isSuccess ? '✓' : '✗';
529
+ const statusText = isSuccess ? 'Success' : 'Failed';
530
+ const statusColor = isSuccess ? 'green' : 'red';
531
+ return (React.createElement(Box, { flexDirection: "column" },
532
+ React.createElement(Text, { bold: true, color: "cyan" },
533
+ "\u26A1 EXECUTE ",
534
+ React.createElement(Text, { color: "gray" },
535
+ "(",
536
+ command,
537
+ ")")),
538
+ React.createElement(Box, { marginLeft: 1 },
539
+ React.createElement(Text, { color: statusColor },
540
+ "\u21B3 ",
541
+ statusIcon,
542
+ " ",
543
+ statusText,
544
+ ". Output: ",
545
+ outputLines,
546
+ " lines."))));
547
+ };
548
+ /**
549
+ * Normalize tool name by removing "galaxy:" prefix if present
550
+ */
551
+ const normalizeToolName = (toolName) => {
552
+ if (!toolName)
553
+ return '';
554
+ return toolName.replace(/^galaxy:/, '');
555
+ };
556
+ /**
557
+ * Main formatter dispatcher
558
+ */
559
+ export function formatMessage(content, toolName, toolInfo) {
560
+ // Normalize tool name
561
+ const normalizedToolName = normalizeToolName(toolName);
562
+ // Convert to string if needed
563
+ const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
564
+ // Match tool-specific formatters
565
+ switch (normalizedToolName) {
566
+ case 'file_tree':
567
+ return formatFileTree(contentStr, toolInfo);
568
+ case 'file_read':
569
+ return formatFileRead(contentStr, toolInfo);
570
+ case 'file_write':
571
+ return formatFileWrite(contentStr, toolInfo);
572
+ case 'command_run':
573
+ return formatCommandRun(contentStr, toolInfo);
574
+ case 'plan_task':
575
+ // Plan is already displayed in planning list, don't show tool result
576
+ return null;
577
+ case 'progress_reporter':
578
+ // Progress updates are shown in planning list status, don't show tool result
579
+ return null;
580
+ case 'todos':
581
+ return formatTodos(contentStr);
582
+ case 'diff':
583
+ return formatDiff(contentStr, toolName);
584
+ case 'code':
585
+ return formatCode(contentStr);
586
+ default:
587
+ // Full content display with word wrap
588
+ return React.createElement(Text, { wrap: "wrap" }, contentStr);
589
+ }
590
+ }
@@ -0,0 +1,59 @@
1
+ export interface ProgressStep {
2
+ step: number;
3
+ action: string;
4
+ status: 'pending' | 'in-progress' | 'completed' | 'failed';
5
+ startTime?: Date;
6
+ endTime?: Date;
7
+ error?: string;
8
+ filesCreated?: string[];
9
+ }
10
+ export interface ProjectProgress {
11
+ projectName: string;
12
+ startTime: Date;
13
+ lastUpdated: Date;
14
+ currentStep: number;
15
+ totalSteps: number;
16
+ steps: ProgressStep[];
17
+ }
18
+ export declare class ProgressTracker {
19
+ private progressFilePath;
20
+ private progress;
21
+ constructor(workingDir?: string);
22
+ /**
23
+ * Initialize new progress tracking
24
+ */
25
+ initializeProgress(projectName: string, steps: ProgressStep[]): void;
26
+ /**
27
+ * Load existing progress
28
+ */
29
+ loadProgress(): ProjectProgress | null;
30
+ /**
31
+ * Update step status
32
+ */
33
+ updateStepStatus(stepNum: number, status: ProgressStep['status'], error?: string, filesCreated?: string[]): void;
34
+ /**
35
+ * Generate markdown content
36
+ */
37
+ private generateMarkdown;
38
+ /**
39
+ * Save progress to markdown file
40
+ */
41
+ saveProgress(): void;
42
+ /**
43
+ * Get current progress
44
+ */
45
+ getProgress(): ProjectProgress | null;
46
+ /**
47
+ * Check if progress file exists
48
+ */
49
+ hasProgress(): boolean;
50
+ /**
51
+ * Clear progress (delete file)
52
+ */
53
+ clearProgress(): void;
54
+ /**
55
+ * Get progress file path
56
+ */
57
+ getProgressFilePath(): string;
58
+ }
59
+ export declare const progressTracker: ProgressTracker;