@kirosnn/mosaic 0.71.0 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/package.json +4 -2
- package/src/agent/Agent.ts +353 -131
- package/src/agent/context.ts +4 -4
- package/src/agent/prompts/systemPrompt.ts +15 -6
- package/src/agent/prompts/toolsPrompt.ts +136 -10
- package/src/agent/provider/anthropic.ts +100 -100
- package/src/agent/provider/google.ts +102 -102
- package/src/agent/provider/mistral.ts +95 -95
- package/src/agent/provider/ollama.ts +77 -60
- package/src/agent/provider/openai.ts +42 -38
- package/src/agent/provider/rateLimit.ts +178 -0
- package/src/agent/provider/xai.ts +99 -99
- package/src/agent/tools/definitions.ts +19 -9
- package/src/agent/tools/executor.ts +95 -85
- package/src/agent/tools/exploreExecutor.ts +8 -10
- package/src/agent/tools/grep.ts +30 -29
- package/src/agent/tools/question.ts +7 -1
- package/src/agent/types.ts +9 -8
- package/src/components/App.tsx +45 -45
- package/src/components/CustomInput.tsx +214 -36
- package/src/components/Main.tsx +552 -339
- package/src/components/Setup.tsx +1 -1
- package/src/components/Welcome.tsx +1 -1
- package/src/components/main/ApprovalPanel.tsx +4 -3
- package/src/components/main/ChatPage.tsx +858 -675
- package/src/components/main/HomePage.tsx +53 -38
- package/src/components/main/QuestionPanel.tsx +52 -7
- package/src/components/main/ThinkingIndicator.tsx +2 -1
- package/src/index.tsx +50 -20
- package/src/mcp/approvalPolicy.ts +156 -0
- package/src/mcp/cli/add.ts +185 -0
- package/src/mcp/cli/doctor.ts +74 -0
- package/src/mcp/cli/index.ts +85 -0
- package/src/mcp/cli/list.ts +50 -0
- package/src/mcp/cli/logs.ts +24 -0
- package/src/mcp/cli/manage.ts +99 -0
- package/src/mcp/cli/show.ts +53 -0
- package/src/mcp/cli/tools.ts +77 -0
- package/src/mcp/config.ts +234 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/processManager.ts +304 -0
- package/src/mcp/rateLimiter.ts +50 -0
- package/src/mcp/registry.ts +151 -0
- package/src/mcp/schemaConverter.ts +100 -0
- package/src/mcp/servers/navigation/browser.ts +151 -0
- package/src/mcp/servers/navigation/index.ts +23 -0
- package/src/mcp/servers/navigation/tools.ts +263 -0
- package/src/mcp/servers/navigation/types.ts +17 -0
- package/src/mcp/servers/navigation/utils.ts +20 -0
- package/src/mcp/toolCatalog.ts +182 -0
- package/src/mcp/types.ts +116 -0
- package/src/utils/approvalBridge.ts +17 -5
- package/src/utils/commands/compact.ts +30 -0
- package/src/utils/commands/echo.ts +1 -1
- package/src/utils/commands/index.ts +4 -6
- package/src/utils/commands/new.ts +15 -0
- package/src/utils/commands/types.ts +3 -0
- package/src/utils/config.ts +3 -1
- package/src/utils/diffRendering.tsx +1 -3
- package/src/utils/exploreBridge.ts +10 -0
- package/src/utils/markdown.tsx +220 -122
- package/src/utils/models.ts +31 -9
- package/src/utils/questionBridge.ts +36 -1
- package/src/utils/tokenEstimator.ts +32 -0
- package/src/utils/toolFormatting.ts +317 -7
- package/src/web/app.tsx +72 -72
- package/src/web/components/HomePage.tsx +7 -7
- package/src/web/components/MessageItem.tsx +66 -35
- package/src/web/components/QuestionPanel.tsx +72 -12
- package/src/web/components/Sidebar.tsx +0 -2
- package/src/web/components/ThinkingIndicator.tsx +1 -0
- package/src/web/server.tsx +767 -683
- package/src/utils/commands/redo.ts +0 -74
- package/src/utils/commands/sessions.ts +0 -129
- package/src/utils/commands/undo.ts +0 -75
- package/src/utils/undoRedo.ts +0 -429
- package/src/utils/undoRedoBridge.ts +0 -45
- package/src/utils/undoRedoDb.ts +0 -338
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatWriteToolResult, formatEditToolResult } from './diff';
|
|
2
|
+
import { isNativeMcpTool, getNativeMcpToolName } from '../mcp/types';
|
|
2
3
|
|
|
3
4
|
const TOOL_BODY_INDENT = 2;
|
|
4
5
|
|
|
@@ -18,7 +19,39 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
|
|
18
19
|
plan: 'Plan',
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
function parseMcpSafeId(toolName: string): { serverId: string; tool: string } | null {
|
|
23
|
+
if (!toolName.startsWith('mcp__')) return null;
|
|
24
|
+
const parts = toolName.slice(5).split('__');
|
|
25
|
+
if (parts.length < 2) return null;
|
|
26
|
+
const tool = parts.pop()!;
|
|
27
|
+
const serverId = parts.join('__');
|
|
28
|
+
return { serverId, tool };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getMcpToolDisplayName(tool: string): string {
|
|
32
|
+
const words = tool.replace(/[-_]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
33
|
+
return words.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getNativeToolDisplayName(safeId: string): string | null {
|
|
37
|
+
const toolName = getNativeMcpToolName(safeId);
|
|
38
|
+
if (!toolName) return null;
|
|
39
|
+
const mcp = parseMcpSafeId(safeId);
|
|
40
|
+
if (!mcp) return null;
|
|
41
|
+
const serverPrefix = mcp.serverId + '_';
|
|
42
|
+
const stripped = toolName.startsWith(serverPrefix) ? toolName.slice(serverPrefix.length) : toolName;
|
|
43
|
+
return getMcpToolDisplayName(stripped);
|
|
44
|
+
}
|
|
45
|
+
|
|
21
46
|
function getToolDisplayName(toolName: string): string {
|
|
47
|
+
if (isNativeMcpTool(toolName)) {
|
|
48
|
+
const nativeName = getNativeToolDisplayName(toolName);
|
|
49
|
+
if (nativeName) return nativeName;
|
|
50
|
+
}
|
|
51
|
+
const mcp = parseMcpSafeId(toolName);
|
|
52
|
+
if (mcp) {
|
|
53
|
+
return getMcpToolDisplayName(mcp.tool);
|
|
54
|
+
}
|
|
22
55
|
return TOOL_DISPLAY_NAMES[toolName] || toolName;
|
|
23
56
|
}
|
|
24
57
|
|
|
@@ -43,6 +76,50 @@ export function formatToolResult(result: unknown): string {
|
|
|
43
76
|
}
|
|
44
77
|
}
|
|
45
78
|
|
|
79
|
+
function getMcpHeaderInfo(_tool: string, args: Record<string, unknown>): string {
|
|
80
|
+
const query = args.query ?? args.search ?? args.prompt ?? args.input ?? args.text ?? args.q;
|
|
81
|
+
if (typeof query === 'string' && query.trim()) {
|
|
82
|
+
const clean = query.replace(/[\r\n]+/g, ' ').trim();
|
|
83
|
+
return clean.length > 50 ? clean.slice(0, 50) + '...' : clean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const url = args.url ?? args.uri ?? args.href;
|
|
87
|
+
if (typeof url === 'string' && url.trim()) {
|
|
88
|
+
try {
|
|
89
|
+
const u = new URL(url);
|
|
90
|
+
return u.hostname + (u.pathname !== '/' ? u.pathname : '');
|
|
91
|
+
} catch {
|
|
92
|
+
return url.length > 50 ? url.slice(0, 50) + '...' : url;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const urls = args.urls;
|
|
97
|
+
if (Array.isArray(urls) && urls.length > 0) {
|
|
98
|
+
const first = typeof urls[0] === 'string' ? urls[0] : '';
|
|
99
|
+
if (urls.length === 1) {
|
|
100
|
+
try {
|
|
101
|
+
return new URL(first).hostname;
|
|
102
|
+
} catch {
|
|
103
|
+
return first.length > 40 ? first.slice(0, 40) + '...' : first;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return `${urls.length} URLs`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const path = args.path ?? args.file ?? args.filename ?? args.filepath ?? args.name;
|
|
110
|
+
if (typeof path === 'string' && path.trim()) {
|
|
111
|
+
return path.length > 50 ? path.slice(0, 50) + '...' : path;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const command = args.command ?? args.cmd;
|
|
115
|
+
if (typeof command === 'string' && command.trim()) {
|
|
116
|
+
const clean = command.replace(/[\r\n]+/g, ' ').trim();
|
|
117
|
+
return clean.length > 50 ? clean.slice(0, 50) + '...' : clean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
|
|
46
123
|
function formatKnownToolArgs(toolName: string, args: Record<string, unknown>): string | null {
|
|
47
124
|
switch (toolName) {
|
|
48
125
|
case 'read':
|
|
@@ -67,6 +144,9 @@ function formatKnownToolArgs(toolName: string, args: Record<string, unknown>): s
|
|
|
67
144
|
}
|
|
68
145
|
|
|
69
146
|
default: {
|
|
147
|
+
if (toolName.startsWith('mcp__')) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
70
150
|
const keys = Object.keys(args);
|
|
71
151
|
if (keys.length === 0) return null;
|
|
72
152
|
try {
|
|
@@ -78,6 +158,15 @@ function formatKnownToolArgs(toolName: string, args: Record<string, unknown>): s
|
|
|
78
158
|
}
|
|
79
159
|
}
|
|
80
160
|
|
|
161
|
+
function formatNativeMcpError(errorText: string): string[] {
|
|
162
|
+
const statusMatch = errorText.match(/status code (\d+)/i);
|
|
163
|
+
if (statusMatch) {
|
|
164
|
+
return [`Error ${statusMatch[1]}`];
|
|
165
|
+
}
|
|
166
|
+
const short = errorText.length > 80 ? errorText.slice(0, 80) + '...' : errorText;
|
|
167
|
+
return [`Error: ${short}`];
|
|
168
|
+
}
|
|
169
|
+
|
|
81
170
|
export function isToolSuccess(result: unknown): boolean {
|
|
82
171
|
if (result === null || result === undefined) return false;
|
|
83
172
|
|
|
@@ -143,8 +232,16 @@ function formatToolHeader(toolName: string, args: Record<string, unknown>): stri
|
|
|
143
232
|
}
|
|
144
233
|
case 'plan':
|
|
145
234
|
return displayName;
|
|
146
|
-
default:
|
|
235
|
+
default: {
|
|
236
|
+
if (toolName.startsWith('mcp__')) {
|
|
237
|
+
const info = getMcpHeaderInfo('', args);
|
|
238
|
+
if (isNativeMcpTool(toolName)) {
|
|
239
|
+
return info ? `${displayName} (${info})` : displayName;
|
|
240
|
+
}
|
|
241
|
+
return info ? `${displayName} ("${info}")` : displayName;
|
|
242
|
+
}
|
|
147
243
|
return displayName;
|
|
244
|
+
}
|
|
148
245
|
}
|
|
149
246
|
}
|
|
150
247
|
|
|
@@ -217,11 +314,20 @@ export function parseToolHeader(toolName: string, args: Record<string, unknown>)
|
|
|
217
314
|
}
|
|
218
315
|
case 'plan':
|
|
219
316
|
return { name: displayName, info: null };
|
|
220
|
-
default:
|
|
317
|
+
default: {
|
|
318
|
+
if (toolName.startsWith('mcp__')) {
|
|
319
|
+
const info = getMcpHeaderInfo('', args);
|
|
320
|
+
return { name: displayName, info: info || null };
|
|
321
|
+
}
|
|
221
322
|
return { name: displayName, info: null };
|
|
323
|
+
}
|
|
222
324
|
}
|
|
223
325
|
}
|
|
224
326
|
|
|
327
|
+
export function isNativeMcpToolName(toolName: string): boolean {
|
|
328
|
+
return isNativeMcpTool(toolName);
|
|
329
|
+
}
|
|
330
|
+
|
|
225
331
|
function getLineCount(text: string): number {
|
|
226
332
|
if (!text) return 0;
|
|
227
333
|
return text.split(/\r?\n/).length;
|
|
@@ -230,10 +336,25 @@ function getLineCount(text: string): number {
|
|
|
230
336
|
function formatListTree(result: unknown): string[] {
|
|
231
337
|
if (typeof result !== 'string') return [];
|
|
232
338
|
try {
|
|
233
|
-
const parsed = JSON.parse(result)
|
|
234
|
-
|
|
339
|
+
const parsed = JSON.parse(result);
|
|
340
|
+
|
|
341
|
+
let items: Array<{ name?: string; path?: string; type?: string }>;
|
|
342
|
+
let errors: string[] = [];
|
|
343
|
+
|
|
344
|
+
if (Array.isArray(parsed)) {
|
|
345
|
+
items = parsed;
|
|
346
|
+
} else if (parsed && typeof parsed === 'object' && Array.isArray(parsed.files)) {
|
|
347
|
+
items = parsed.files;
|
|
348
|
+
if (Array.isArray(parsed.errors)) {
|
|
349
|
+
errors = parsed.errors
|
|
350
|
+
.map((e: unknown) => (typeof e === 'string' ? e : ''))
|
|
351
|
+
.filter((e: string) => e);
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
235
356
|
|
|
236
|
-
const entries =
|
|
357
|
+
const entries = items
|
|
237
358
|
.map((e) => ({
|
|
238
359
|
name: typeof e.name === 'string' ? e.name : (typeof e.path === 'string' ? e.path : ''),
|
|
239
360
|
type: typeof e.type === 'string' ? e.type : '',
|
|
@@ -250,7 +371,16 @@ function formatListTree(result: unknown): string[] {
|
|
|
250
371
|
.map((e) => e.name)
|
|
251
372
|
.sort((a, b) => a.localeCompare(b));
|
|
252
373
|
|
|
253
|
-
|
|
374
|
+
const lines = [...dirs, ...files];
|
|
375
|
+
|
|
376
|
+
if (errors.length > 0) {
|
|
377
|
+
lines.push('', `Errors (${errors.length}):`);
|
|
378
|
+
for (const err of errors) {
|
|
379
|
+
lines.push(` ${err}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return lines;
|
|
254
384
|
} catch {
|
|
255
385
|
return [];
|
|
256
386
|
}
|
|
@@ -285,6 +415,175 @@ function getToolErrorText(result: unknown): string | null {
|
|
|
285
415
|
return typeof error === 'string' && error.trim() ? error.trim() : null;
|
|
286
416
|
}
|
|
287
417
|
|
|
418
|
+
function formatSearchResultBody(result: unknown): string[] {
|
|
419
|
+
if (typeof result !== 'string') return [];
|
|
420
|
+
try {
|
|
421
|
+
const parsed = JSON.parse(result);
|
|
422
|
+
if (typeof parsed !== 'object' || parsed === null) return [];
|
|
423
|
+
|
|
424
|
+
if (typeof parsed.error === 'string') {
|
|
425
|
+
return [`Error: ${parsed.error}`];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const count = typeof parsed.resultCount === 'number' ? parsed.resultCount : 0;
|
|
429
|
+
if (count === 0) return ['No results'];
|
|
430
|
+
return [`${count} results`];
|
|
431
|
+
} catch {
|
|
432
|
+
return [];
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function formatMcpResultBody(result: unknown): string[] {
|
|
437
|
+
if (typeof result !== 'string') {
|
|
438
|
+
if (result && typeof result === 'object') {
|
|
439
|
+
const obj = result as Record<string, unknown>;
|
|
440
|
+
if (typeof obj.error === 'string') {
|
|
441
|
+
return [`Error: ${obj.error}`];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return ['Completed'];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const text = result.trim();
|
|
448
|
+
if (!text) return ['(empty result)'];
|
|
449
|
+
|
|
450
|
+
try {
|
|
451
|
+
const parsed = JSON.parse(text);
|
|
452
|
+
|
|
453
|
+
if (Array.isArray(parsed)) {
|
|
454
|
+
return formatMcpArray(parsed);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
458
|
+
return formatMcpObject(parsed);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return [String(parsed)];
|
|
462
|
+
} catch {
|
|
463
|
+
const lines = text.split(/\r?\n/).filter(l => l.trim());
|
|
464
|
+
return lines.length > 0 ? lines : ['Completed'];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function formatMcpArray(arr: unknown[]): string[] {
|
|
469
|
+
if (arr.length === 0) return ['(no results)'];
|
|
470
|
+
|
|
471
|
+
const lines: string[] = [];
|
|
472
|
+
|
|
473
|
+
for (const item of arr) {
|
|
474
|
+
if (typeof item === 'string') {
|
|
475
|
+
lines.push(` ${item}`);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (item && typeof item === 'object') {
|
|
480
|
+
const obj = item as Record<string, unknown>;
|
|
481
|
+
|
|
482
|
+
if (typeof obj.error === 'string') {
|
|
483
|
+
const url = typeof obj.url === 'string' ? obj.url : '';
|
|
484
|
+
const detail = obj.details && typeof obj.details === 'object'
|
|
485
|
+
? (obj.details as Record<string, unknown>).detail
|
|
486
|
+
: '';
|
|
487
|
+
const errMsg = typeof detail === 'string' && detail ? detail : obj.error;
|
|
488
|
+
lines.push(url ? ` ${url} - ${errMsg}` : ` Error: ${errMsg}`);
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const title = obj.title ?? obj.name ?? obj.label;
|
|
493
|
+
const desc = obj.description ?? obj.summary ?? obj.snippet ?? obj.text ?? obj.content;
|
|
494
|
+
const url = obj.url ?? obj.link ?? obj.href;
|
|
495
|
+
|
|
496
|
+
if (typeof title === 'string' && title) {
|
|
497
|
+
let line = ` ${title}`;
|
|
498
|
+
if (typeof url === 'string' && url) {
|
|
499
|
+
try {
|
|
500
|
+
line += ` (${new URL(url).hostname})`;
|
|
501
|
+
} catch {
|
|
502
|
+
line += ` (${url.length > 40 ? url.slice(0, 40) + '...' : url})`;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
lines.push(line);
|
|
506
|
+
if (typeof desc === 'string' && desc.trim()) {
|
|
507
|
+
const short = desc.trim().replace(/[\r\n]+/g, ' ');
|
|
508
|
+
lines.push(` ${short.length > 80 ? short.slice(0, 80) + '...' : short}`);
|
|
509
|
+
}
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (typeof url === 'string' && url) {
|
|
514
|
+
let line = ` ${url}`;
|
|
515
|
+
if (typeof desc === 'string' && desc.trim()) {
|
|
516
|
+
const short = desc.trim().replace(/[\r\n]+/g, ' ');
|
|
517
|
+
line += ` - ${short.length > 60 ? short.slice(0, 60) + '...' : short}`;
|
|
518
|
+
}
|
|
519
|
+
lines.push(line);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const keys = Object.keys(obj);
|
|
524
|
+
const summary = keys.slice(0, 3).map(k => {
|
|
525
|
+
const v = obj[k];
|
|
526
|
+
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
527
|
+
const short = s && s.length > 30 ? s.slice(0, 30) + '...' : s;
|
|
528
|
+
return `${k}: ${short}`;
|
|
529
|
+
}).join(', ');
|
|
530
|
+
lines.push(` ${summary}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (arr.length > 1) {
|
|
535
|
+
lines.unshift(`${arr.length} results:`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return lines.length > 0 ? lines : ['Completed'];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function formatMcpObject(obj: Record<string, unknown>): string[] {
|
|
542
|
+
const lines: string[] = [];
|
|
543
|
+
|
|
544
|
+
if (typeof obj.error === 'string') {
|
|
545
|
+
const detail = obj.details && typeof obj.details === 'object'
|
|
546
|
+
? (obj.details as Record<string, unknown>).detail
|
|
547
|
+
: '';
|
|
548
|
+
const errMsg = typeof detail === 'string' && detail ? detail : obj.error;
|
|
549
|
+
return [`Error: ${errMsg}`];
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const status = obj.status ?? obj.statusCode ?? obj.code;
|
|
553
|
+
const message = obj.message ?? obj.result ?? obj.output ?? obj.text ?? obj.content ?? obj.data;
|
|
554
|
+
|
|
555
|
+
if (typeof status === 'number' || typeof status === 'string') {
|
|
556
|
+
lines.push(`Status: ${status}`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (typeof message === 'string' && message.trim()) {
|
|
560
|
+
const msgLines = message.trim().split(/\r?\n/);
|
|
561
|
+
lines.push(...msgLines);
|
|
562
|
+
} else if (message && typeof message === 'object') {
|
|
563
|
+
if (Array.isArray(message)) {
|
|
564
|
+
lines.push(...formatMcpArray(message));
|
|
565
|
+
} else {
|
|
566
|
+
const entries = Object.entries(message as Record<string, unknown>).slice(0, 5);
|
|
567
|
+
for (const [k, v] of entries) {
|
|
568
|
+
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
569
|
+
const short = s && s.length > 60 ? s.slice(0, 60) + '...' : s;
|
|
570
|
+
lines.push(` ${k}: ${short}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (lines.length === 0) {
|
|
576
|
+
const entries = Object.entries(obj).slice(0, 5);
|
|
577
|
+
for (const [k, v] of entries) {
|
|
578
|
+
const s = typeof v === 'string' ? v : JSON.stringify(v);
|
|
579
|
+
const short = s && s.length > 60 ? s.slice(0, 60) + '...' : s;
|
|
580
|
+
lines.push(` ${k}: ${short}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return lines.length > 0 ? lines : ['Completed'];
|
|
585
|
+
}
|
|
586
|
+
|
|
288
587
|
function formatToolBodyLines(toolName: string, args: Record<string, unknown>, result: unknown): string[] {
|
|
289
588
|
const errorText = getToolErrorText(result);
|
|
290
589
|
if (errorText) {
|
|
@@ -294,6 +593,9 @@ function formatToolBodyLines(toolName: string, args: Record<string, unknown>, re
|
|
|
294
593
|
return [`${statusMatch[1]} - Failed to fetch`];
|
|
295
594
|
}
|
|
296
595
|
}
|
|
596
|
+
if (toolName.startsWith('mcp__')) {
|
|
597
|
+
return formatNativeMcpError(errorText);
|
|
598
|
+
}
|
|
297
599
|
return [`Tool error: ${errorText}`];
|
|
298
600
|
}
|
|
299
601
|
|
|
@@ -437,6 +739,14 @@ function formatToolBodyLines(toolName: string, args: Record<string, unknown>, re
|
|
|
437
739
|
}
|
|
438
740
|
|
|
439
741
|
default: {
|
|
742
|
+
if (toolName.startsWith('mcp__')) {
|
|
743
|
+
const nativeName = getNativeMcpToolName(toolName);
|
|
744
|
+
if (nativeName === 'navigation_search') {
|
|
745
|
+
const searchLines = formatSearchResultBody(result);
|
|
746
|
+
if (searchLines.length > 0) return searchLines;
|
|
747
|
+
}
|
|
748
|
+
return formatMcpResultBody(result);
|
|
749
|
+
}
|
|
440
750
|
const toolResultText = formatToolResult(result);
|
|
441
751
|
if (!toolResultText) return [];
|
|
442
752
|
return toolResultText.split(/\r?\n/);
|
|
@@ -500,4 +810,4 @@ export function getToolWrapWidth(maxWidth: number, paragraphIndex: number): numb
|
|
|
500
810
|
|
|
501
811
|
export function formatErrorMessage(errorType: 'API' | 'Mosaic' | 'Tool', errorMessage: string): string {
|
|
502
812
|
return `${errorType} Error\n${errorMessage}`;
|
|
503
|
-
}
|
|
813
|
+
}
|
package/src/web/app.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @jsxImportSource react */
|
|
2
|
-
import { useState, useEffect,
|
|
2
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import ReactDOM from 'react-dom/client';
|
|
4
4
|
import { HomePage } from './components/HomePage';
|
|
5
5
|
import { ChatPage } from './components/ChatPage';
|
|
@@ -42,9 +42,9 @@ function App() {
|
|
|
42
42
|
const [currentConversation, setCurrentConversation] = useState<Conversation | null>(null);
|
|
43
43
|
const [conversations, setConversations] = useState<Conversation[]>([]);
|
|
44
44
|
const [workspace, setWorkspace] = useState<string | null>(null);
|
|
45
|
-
const [questionRequest, setQuestionRequest] = useState<QuestionRequest | null>(null);
|
|
46
|
-
const [approvalRequest, setApprovalRequest] = useState<ApprovalRequest | null>(null);
|
|
47
|
-
const [requireApprovals, setRequireApprovals] = useState(true);
|
|
45
|
+
const [questionRequest, setQuestionRequest] = useState<QuestionRequest | null>(null);
|
|
46
|
+
const [approvalRequest, setApprovalRequest] = useState<ApprovalRequest | null>(null);
|
|
47
|
+
const [requireApprovals, setRequireApprovals] = useState(true);
|
|
48
48
|
|
|
49
49
|
const refreshConversations = useCallback(() => {
|
|
50
50
|
setConversations(getAllConversations());
|
|
@@ -79,27 +79,27 @@ function App() {
|
|
|
79
79
|
.then(data => setWorkspace(data.workspace))
|
|
80
80
|
.catch(() => { });
|
|
81
81
|
|
|
82
|
-
fetch('/api/tui-conversations')
|
|
83
|
-
.then(res => res.ok ? res.json() : [])
|
|
84
|
-
.then((data: Conversation[]) => {
|
|
82
|
+
fetch('/api/tui-conversations')
|
|
83
|
+
.then(res => res.ok ? res.json() : [])
|
|
84
|
+
.then((data: Conversation[]) => {
|
|
85
85
|
if (Array.isArray(data) && data.length > 0) {
|
|
86
86
|
const changed = mergeConversations(data);
|
|
87
87
|
if (changed) {
|
|
88
88
|
refreshConversations();
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
})
|
|
92
|
-
.catch(() => { });
|
|
93
|
-
|
|
94
|
-
fetch('/api/approvals')
|
|
95
|
-
.then(res => res.ok ? res.json() : null)
|
|
96
|
-
.then((data) => {
|
|
97
|
-
if (data && typeof data.requireApprovals === 'boolean') {
|
|
98
|
-
setRequireApprovals(data.requireApprovals);
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
.catch(() => { });
|
|
102
|
-
}, [refreshConversations]);
|
|
91
|
+
})
|
|
92
|
+
.catch(() => { });
|
|
93
|
+
|
|
94
|
+
fetch('/api/approvals')
|
|
95
|
+
.then(res => res.ok ? res.json() : null)
|
|
96
|
+
.then((data) => {
|
|
97
|
+
if (data && typeof data.requireApprovals === 'boolean') {
|
|
98
|
+
setRequireApprovals(data.requireApprovals);
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
.catch(() => { });
|
|
102
|
+
}, [refreshConversations]);
|
|
103
103
|
|
|
104
104
|
useEffect(() => {
|
|
105
105
|
if (route.page === 'chat' && route.conversationId) {
|
|
@@ -157,15 +157,15 @@ function App() {
|
|
|
157
157
|
}
|
|
158
158
|
}, [messages, currentTitle]);
|
|
159
159
|
|
|
160
|
-
const handleSendMessage = async (content: string, images: Message['images'] = []) => {
|
|
161
|
-
if ((!content.trim() && images.length === 0) || isProcessing) return;
|
|
160
|
+
const handleSendMessage = async (content: string, images: Message['images'] = []) => {
|
|
161
|
+
if ((!content.trim() && images.length === 0) || isProcessing) return;
|
|
162
162
|
|
|
163
|
-
const userMessage: Message = {
|
|
164
|
-
id: createId(),
|
|
165
|
-
role: 'user',
|
|
166
|
-
content: content,
|
|
167
|
-
images: images.length > 0 ? images : undefined,
|
|
168
|
-
};
|
|
163
|
+
const userMessage: Message = {
|
|
164
|
+
id: createId(),
|
|
165
|
+
role: 'user',
|
|
166
|
+
content: content,
|
|
167
|
+
images: images.length > 0 ? images : undefined,
|
|
168
|
+
};
|
|
169
169
|
|
|
170
170
|
let conversation = currentConversation;
|
|
171
171
|
if (!conversation) {
|
|
@@ -190,13 +190,13 @@ function App() {
|
|
|
190
190
|
method: 'POST',
|
|
191
191
|
headers: { 'Content-Type': 'application/json' },
|
|
192
192
|
body: JSON.stringify({
|
|
193
|
-
message: userMessage.content,
|
|
194
|
-
images: userMessage.images || [],
|
|
195
|
-
history: messages
|
|
196
|
-
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
197
|
-
.map((m) => ({ role: m.role, content: m.content, images: m.images || [] })),
|
|
198
|
-
}),
|
|
199
|
-
});
|
|
193
|
+
message: userMessage.content,
|
|
194
|
+
images: userMessage.images || [],
|
|
195
|
+
history: messages
|
|
196
|
+
.filter((m) => m.role === 'user' || m.role === 'assistant')
|
|
197
|
+
.map((m) => ({ role: m.role, content: m.content, images: m.images || [] })),
|
|
198
|
+
}),
|
|
199
|
+
});
|
|
200
200
|
|
|
201
201
|
if (!response.ok) {
|
|
202
202
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
@@ -509,7 +509,7 @@ function App() {
|
|
|
509
509
|
}
|
|
510
510
|
};
|
|
511
511
|
|
|
512
|
-
const handleRenameConversation = (conversationId: string, newTitle: string) => {
|
|
512
|
+
const handleRenameConversation = (conversationId: string, newTitle: string) => {
|
|
513
513
|
const conversation = getConversation(conversationId);
|
|
514
514
|
if (conversation) {
|
|
515
515
|
const updated: Conversation = {
|
|
@@ -534,26 +534,26 @@ function App() {
|
|
|
534
534
|
}).catch(() => { });
|
|
535
535
|
}
|
|
536
536
|
}
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
const handleToggleApprovals = async () => {
|
|
540
|
-
try {
|
|
541
|
-
const next = !requireApprovals;
|
|
542
|
-
const res = await fetch('/api/approvals', {
|
|
543
|
-
method: 'POST',
|
|
544
|
-
headers: { 'Content-Type': 'application/json' },
|
|
545
|
-
body: JSON.stringify({ requireApprovals: next }),
|
|
546
|
-
});
|
|
547
|
-
if (res.ok) {
|
|
548
|
-
setRequireApprovals(next);
|
|
549
|
-
if (!next) {
|
|
550
|
-
setApprovalRequest(null);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
} catch (error) {
|
|
554
|
-
console.error('Failed to toggle approvals:', error);
|
|
555
|
-
}
|
|
556
|
-
};
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const handleToggleApprovals = async () => {
|
|
540
|
+
try {
|
|
541
|
+
const next = !requireApprovals;
|
|
542
|
+
const res = await fetch('/api/approvals', {
|
|
543
|
+
method: 'POST',
|
|
544
|
+
headers: { 'Content-Type': 'application/json' },
|
|
545
|
+
body: JSON.stringify({ requireApprovals: next }),
|
|
546
|
+
});
|
|
547
|
+
if (res.ok) {
|
|
548
|
+
setRequireApprovals(next);
|
|
549
|
+
if (!next) {
|
|
550
|
+
setApprovalRequest(null);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
} catch (error) {
|
|
554
|
+
console.error('Failed to toggle approvals:', error);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
557
|
|
|
558
558
|
const handleNavigateHome = () => {
|
|
559
559
|
navigateTo({ page: 'home' });
|
|
@@ -605,22 +605,22 @@ function App() {
|
|
|
605
605
|
sidebarProps={sidebarProps}
|
|
606
606
|
/>
|
|
607
607
|
) : (
|
|
608
|
-
<ChatPage
|
|
609
|
-
messages={messages}
|
|
610
|
-
isProcessing={isProcessing}
|
|
611
|
-
processingStartTime={processingStartTime}
|
|
612
|
-
currentTokens={currentTokens}
|
|
613
|
-
onSendMessage={handleSendMessage}
|
|
614
|
-
onStopAgent={handleStopAgent}
|
|
615
|
-
sidebarProps={sidebarProps}
|
|
616
|
-
currentTitle={currentTitle}
|
|
617
|
-
workspace={workspace}
|
|
618
|
-
questionRequest={questionRequest}
|
|
619
|
-
approvalRequest={approvalRequest}
|
|
620
|
-
requireApprovals={requireApprovals}
|
|
621
|
-
onToggleApprovals={handleToggleApprovals}
|
|
622
|
-
/>
|
|
623
|
-
)}
|
|
608
|
+
<ChatPage
|
|
609
|
+
messages={messages}
|
|
610
|
+
isProcessing={isProcessing}
|
|
611
|
+
processingStartTime={processingStartTime}
|
|
612
|
+
currentTokens={currentTokens}
|
|
613
|
+
onSendMessage={handleSendMessage}
|
|
614
|
+
onStopAgent={handleStopAgent}
|
|
615
|
+
sidebarProps={sidebarProps}
|
|
616
|
+
currentTitle={currentTitle}
|
|
617
|
+
workspace={workspace}
|
|
618
|
+
questionRequest={questionRequest}
|
|
619
|
+
approvalRequest={approvalRequest}
|
|
620
|
+
requireApprovals={requireApprovals}
|
|
621
|
+
onToggleApprovals={handleToggleApprovals}
|
|
622
|
+
/>
|
|
623
|
+
)}
|
|
624
624
|
|
|
625
625
|
<Modal
|
|
626
626
|
isOpen={activeModal === 'settings'}
|
|
@@ -663,4 +663,4 @@ function App() {
|
|
|
663
663
|
|
|
664
664
|
|
|
665
665
|
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
|
666
|
-
root.render(<App />);
|
|
666
|
+
root.render(<App />);
|
|
@@ -11,11 +11,11 @@ interface RecentProject {
|
|
|
11
11
|
lastOpened: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
interface HomePageProps {
|
|
15
|
-
onStartChat: (message: string, images?: import("../../utils/images").ImageAttachment[]) => void;
|
|
16
|
-
onOpenProject: (path: string) => void;
|
|
17
|
-
sidebarProps: SidebarProps;
|
|
18
|
-
}
|
|
14
|
+
interface HomePageProps {
|
|
15
|
+
onStartChat: (message: string, images?: import("../../utils/images").ImageAttachment[]) => void;
|
|
16
|
+
onOpenProject: (path: string) => void;
|
|
17
|
+
sidebarProps: SidebarProps;
|
|
18
|
+
}
|
|
19
19
|
|
|
20
20
|
function formatRelativeTime(timestamp: number): string {
|
|
21
21
|
const now = Date.now();
|
|
@@ -31,7 +31,7 @@ function formatRelativeTime(timestamp: number): string {
|
|
|
31
31
|
return `${days} day${days !== 1 ? 's' : ''} ago`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function HomePage({ onStartChat, onOpenProject, sidebarProps }: HomePageProps) {
|
|
34
|
+
export function HomePage({ onStartChat: _onStartChat, onOpenProject, sidebarProps }: HomePageProps) {
|
|
35
35
|
const [recentProjects, setRecentProjects] = useState<RecentProject[]>([]);
|
|
36
36
|
const [isLoading, setIsLoading] = useState(true);
|
|
37
37
|
const [showFileExplorer, setShowFileExplorer] = useState(false);
|
|
@@ -118,4 +118,4 @@ export function HomePage({ onStartChat, onOpenProject, sidebarProps }: HomePageP
|
|
|
118
118
|
</Modal>
|
|
119
119
|
</div>
|
|
120
120
|
);
|
|
121
|
-
}
|
|
121
|
+
}
|