codemini-cli 0.2.7 → 0.2.9
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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/core/agent-loop.js +101 -3
- package/src/core/default-system-prompt.js +38 -0
- package/src/core/provider/openai-compatible.js +34 -4
- package/src/core/shell-profile.js +14 -3
- package/src/core/tools.js +163 -55
- package/src/tui/chat-app.js +515 -193
- package/src/tui/skill-activity/index.js +20 -0
- package/src/tui/tool-activity/common.js +29 -0
- package/src/tui/tool-activity/index.js +17 -0
- package/src/tui/tool-activity/presenters/command.js +29 -0
- package/src/tui/tool-activity/presenters/files.js +26 -0
- package/src/tui/tool-activity/presenters/misc.js +19 -0
- package/src/tui/tool-activity/presenters/system.js +14 -0
- package/src/tui/tool-narration/common.js +37 -0
- package/src/tui/tool-narration/presenters/change.js +109 -0
- package/src/tui/tool-narration/presenters/edit.js +3 -0
- package/src/tui/tool-narration/presenters/generic.js +10 -0
- package/src/tui/tool-narration/presenters/glob.js +11 -0
- package/src/tui/tool-narration/presenters/grep.js +11 -0
- package/src/tui/tool-narration/presenters/list.js +11 -0
- package/src/tui/tool-narration/presenters/patch.js +3 -0
- package/src/tui/tool-narration/presenters/read.js +11 -0
- package/src/tui/tool-narration/presenters/run.js +29 -0
- package/src/tui/tool-narration/presenters/write.js +3 -0
- package/src/tui/tool-narration.js +67 -0
package/src/tui/chat-app.js
CHANGED
|
@@ -2,6 +2,20 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
3
|
import { shouldCaptureEscapeSequence } from './input-escape.js';
|
|
4
4
|
import { classifyCommandIntent } from '../core/shell.js';
|
|
5
|
+
import {
|
|
6
|
+
buildInterToolNotice as buildRegisteredInterToolNotice,
|
|
7
|
+
buildPreToolNotice as buildRegisteredPreToolNotice,
|
|
8
|
+
buildSyntheticCompletionText as buildRegisteredSyntheticCompletionText
|
|
9
|
+
} from './tool-narration.js';
|
|
10
|
+
import {
|
|
11
|
+
describeToolActivity as describeRegisteredToolActivity,
|
|
12
|
+
isCodeGenerationActivityName
|
|
13
|
+
} from './tool-activity/index.js';
|
|
14
|
+
import {
|
|
15
|
+
describeAutoSkillActivity as describeRegisteredAutoSkillActivity,
|
|
16
|
+
describeSkillActivity as describeRegisteredSkillActivity,
|
|
17
|
+
formatAutoSkillBadge as formatRegisteredAutoSkillBadge
|
|
18
|
+
} from './skill-activity/index.js';
|
|
5
19
|
|
|
6
20
|
const h = React.createElement;
|
|
7
21
|
const SUGGESTION_PAGE_SIZE = 8;
|
|
@@ -88,6 +102,7 @@ const TUI_COPY = {
|
|
|
88
102
|
startupHint: '使用 /help、/commands、/compact、/exit、!<shell>。Tab 可自动补全 slash 命令。',
|
|
89
103
|
toolSummaryExpanded: '工具摘要:已展开',
|
|
90
104
|
toolSummaryCollapsed: '工具摘要:已收起',
|
|
105
|
+
toolChainCollapsed: (count) => `已折叠更早的 ${count} 个工具调用,按 Ctrl+T 展开全部`,
|
|
91
106
|
toggleToolSummary: 'Ctrl+T 切换',
|
|
92
107
|
scrollHint: '使用终端自己的滚动条或 scrollback',
|
|
93
108
|
keyboardDebugEnabled: '键盘调试已开启',
|
|
@@ -219,6 +234,7 @@ const TUI_COPY = {
|
|
|
219
234
|
startupHint: 'Use /help, /commands, /compact, /exit, !<shell>. Tab for slash autocomplete.',
|
|
220
235
|
toolSummaryExpanded: 'Tool summary: expanded',
|
|
221
236
|
toolSummaryCollapsed: 'Tool summary: collapsed',
|
|
237
|
+
toolChainCollapsed: (count) => `${count} earlier tool calls hidden, press Ctrl+T to expand`,
|
|
222
238
|
toggleToolSummary: 'Ctrl+T to toggle',
|
|
223
239
|
scrollHint: 'Scroll with your terminal scrollbar or scrollback',
|
|
224
240
|
keyboardDebugEnabled: 'Keyboard debug enabled',
|
|
@@ -353,6 +369,308 @@ function trimText(value, maxLen = 88) {
|
|
|
353
369
|
return `${text.slice(0, maxLen - 3)}...`;
|
|
354
370
|
}
|
|
355
371
|
|
|
372
|
+
export function splitMarkdownTableCells(line) {
|
|
373
|
+
const text = String(line || '').trim();
|
|
374
|
+
if (!text.includes('|')) return [];
|
|
375
|
+
return text
|
|
376
|
+
.replace(/^\|/, '')
|
|
377
|
+
.replace(/\|$/, '')
|
|
378
|
+
.split('|')
|
|
379
|
+
.map((cell) => String(cell || '').trim());
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function isMarkdownTableSeparator(line) {
|
|
383
|
+
const cells = splitMarkdownTableCells(line);
|
|
384
|
+
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function isMarkdownTableHeader(line, nextLine) {
|
|
388
|
+
const cells = splitMarkdownTableCells(line);
|
|
389
|
+
return cells.length > 1 && isMarkdownTableSeparator(nextLine);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function getMarkdownTableAlignments(separatorLine, columnCount) {
|
|
393
|
+
const cells = splitMarkdownTableCells(separatorLine);
|
|
394
|
+
return Array.from({ length: columnCount }, (_, index) => {
|
|
395
|
+
const cell = String(cells[index] || '').trim();
|
|
396
|
+
if (/^:-{3,}:$/.test(cell)) return 'center';
|
|
397
|
+
if (/^-{3,}:$/.test(cell)) return 'right';
|
|
398
|
+
return 'left';
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function stringWidthLite(value) {
|
|
403
|
+
return Array.from(String(value || '')).reduce((sum, ch) => sum + charDisplayWidth(ch), 0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function splitTableWrapUnits(text) {
|
|
407
|
+
return String(text || '')
|
|
408
|
+
.split(/([\s,.;:!?/\\|()[\]{}<>,。;:!?、()【】《》]+)/)
|
|
409
|
+
.filter(Boolean);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function wrapPlainText(text, width, hard = false) {
|
|
413
|
+
const normalized = String(text || '').replace(/\s+/g, ' ').trim();
|
|
414
|
+
if (!normalized) return [''];
|
|
415
|
+
if (width <= 1) return [normalized];
|
|
416
|
+
|
|
417
|
+
const words = splitTableWrapUnits(normalized);
|
|
418
|
+
const lines = [];
|
|
419
|
+
let current = '';
|
|
420
|
+
|
|
421
|
+
const pushWord = (word) => {
|
|
422
|
+
if (stringWidthLite(word) <= width) {
|
|
423
|
+
if (!current) {
|
|
424
|
+
current = word;
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const needsSpacer =
|
|
428
|
+
!/\s$/.test(current) &&
|
|
429
|
+
!/^\s/.test(word) &&
|
|
430
|
+
!/^[,.;:!?/\\|)\]},。;:!?、】【》]/.test(word);
|
|
431
|
+
const next = needsSpacer ? `${current} ${word}` : `${current}${word}`;
|
|
432
|
+
if (stringWidthLite(next) <= width) {
|
|
433
|
+
current = next;
|
|
434
|
+
} else {
|
|
435
|
+
lines.push(current);
|
|
436
|
+
current = word;
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!hard) {
|
|
442
|
+
if (current) {
|
|
443
|
+
lines.push(current);
|
|
444
|
+
current = '';
|
|
445
|
+
}
|
|
446
|
+
lines.push(word);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (current) {
|
|
451
|
+
lines.push(current);
|
|
452
|
+
current = '';
|
|
453
|
+
}
|
|
454
|
+
let rest = word;
|
|
455
|
+
while (stringWidthLite(rest) > width) {
|
|
456
|
+
lines.push(Array.from(rest).slice(0, width).join(''));
|
|
457
|
+
rest = Array.from(rest).slice(width).join('');
|
|
458
|
+
}
|
|
459
|
+
current = rest;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
for (const word of words) pushWord(word);
|
|
463
|
+
if (current) lines.push(current);
|
|
464
|
+
return lines.length > 0 ? lines : [''];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function padAlignedText(text, width, align = 'left') {
|
|
468
|
+
const value = String(text || '');
|
|
469
|
+
const visible = stringWidthLite(value);
|
|
470
|
+
if (visible >= width) return value;
|
|
471
|
+
const gap = width - visible;
|
|
472
|
+
if (align === 'right') return `${' '.repeat(gap)}${value}`;
|
|
473
|
+
if (align === 'center') {
|
|
474
|
+
const left = Math.floor(gap / 2);
|
|
475
|
+
const right = gap - left;
|
|
476
|
+
return `${' '.repeat(left)}${value}${' '.repeat(right)}`;
|
|
477
|
+
}
|
|
478
|
+
return `${value}${' '.repeat(gap)}`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function normalizeTableCellText(value) {
|
|
482
|
+
return String(value || '')
|
|
483
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
484
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
485
|
+
.replace(/\*([^*]+)\*/g, '$1')
|
|
486
|
+
.trim();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function formatMarkdownTableBlock(lines, contentWidth = 72) {
|
|
490
|
+
const sourceLines = Array.isArray(lines) ? lines : [];
|
|
491
|
+
if (sourceLines.length < 2) return [];
|
|
492
|
+
|
|
493
|
+
const headerCells = splitMarkdownTableCells(sourceLines[0]);
|
|
494
|
+
const separatorLine = sourceLines[1];
|
|
495
|
+
const bodyRows = sourceLines.slice(2).map(splitMarkdownTableCells).filter((cells) => cells.length > 0);
|
|
496
|
+
if (headerCells.length === 0) return [];
|
|
497
|
+
|
|
498
|
+
const columnCount = Math.max(headerCells.length, ...bodyRows.map((cells) => cells.length));
|
|
499
|
+
const headers = Array.from({ length: columnCount }, (_, index) => normalizeTableCellText(headerCells[index] || ''));
|
|
500
|
+
const rows = bodyRows.map((cells) =>
|
|
501
|
+
Array.from({ length: columnCount }, (_, index) => normalizeTableCellText(cells[index] || ''))
|
|
502
|
+
);
|
|
503
|
+
const alignments = getMarkdownTableAlignments(separatorLine, columnCount);
|
|
504
|
+
|
|
505
|
+
const minColumnWidth = 3;
|
|
506
|
+
const maxRowLines = 6;
|
|
507
|
+
const safetyMargin = 4;
|
|
508
|
+
const borderOverhead = 1 + columnCount * 3;
|
|
509
|
+
const availableWidth = Math.max(contentWidth - borderOverhead - safetyMargin, columnCount * minColumnWidth);
|
|
510
|
+
|
|
511
|
+
const getMinWidth = (text) => {
|
|
512
|
+
const words = splitTableWrapUnits(String(text || '')).filter((word) => !/^\s+$/.test(word));
|
|
513
|
+
if (words.length === 0) return minColumnWidth;
|
|
514
|
+
return Math.max(...words.map((word) => stringWidthLite(word)), minColumnWidth);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const getIdealWidth = (text) => Math.max(stringWidthLite(String(text || '').trim()), minColumnWidth);
|
|
518
|
+
|
|
519
|
+
const minWidths = headers.map((header, index) =>
|
|
520
|
+
Math.max(getMinWidth(header), ...rows.map((row) => getMinWidth(row[index])))
|
|
521
|
+
);
|
|
522
|
+
const idealWidths = headers.map((header, index) =>
|
|
523
|
+
Math.max(getIdealWidth(header), ...rows.map((row) => getIdealWidth(row[index])))
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const totalMin = minWidths.reduce((sum, width) => sum + width, 0);
|
|
527
|
+
const totalIdeal = idealWidths.reduce((sum, width) => sum + width, 0);
|
|
528
|
+
let needsHardWrap = false;
|
|
529
|
+
let columnWidths;
|
|
530
|
+
|
|
531
|
+
if (totalIdeal <= availableWidth) {
|
|
532
|
+
columnWidths = idealWidths.slice();
|
|
533
|
+
} else if (totalMin <= availableWidth) {
|
|
534
|
+
const extraSpace = availableWidth - totalMin;
|
|
535
|
+
const overflows = idealWidths.map((ideal, index) => ideal - minWidths[index]);
|
|
536
|
+
const totalOverflow = overflows.reduce((sum, width) => sum + width, 0);
|
|
537
|
+
columnWidths = minWidths.map((min, index) => {
|
|
538
|
+
if (totalOverflow === 0) return min;
|
|
539
|
+
return min + Math.floor((overflows[index] / totalOverflow) * extraSpace);
|
|
540
|
+
});
|
|
541
|
+
} else {
|
|
542
|
+
needsHardWrap = true;
|
|
543
|
+
const scale = availableWidth / Math.max(totalMin, 1);
|
|
544
|
+
columnWidths = minWidths.map((width) => Math.max(Math.floor(width * scale), minColumnWidth));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const wrapCell = (text, width) => wrapPlainText(text, width, needsHardWrap);
|
|
548
|
+
|
|
549
|
+
const computeMaxWrappedLines = () => {
|
|
550
|
+
let maxLines = 1;
|
|
551
|
+
for (let index = 0; index < headers.length; index += 1) {
|
|
552
|
+
maxLines = Math.max(maxLines, wrapCell(headers[index], columnWidths[index]).length);
|
|
553
|
+
}
|
|
554
|
+
for (const row of rows) {
|
|
555
|
+
for (let index = 0; index < columnCount; index += 1) {
|
|
556
|
+
maxLines = Math.max(maxLines, wrapCell(row[index], columnWidths[index]).length);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return maxLines;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const renderVerticalRows = () => {
|
|
563
|
+
const rendered = [];
|
|
564
|
+
const separatorWidth = Math.min(Math.max(contentWidth - 2, 12), 40);
|
|
565
|
+
const separator = '─'.repeat(separatorWidth);
|
|
566
|
+
rows.forEach((row, rowIndex) => {
|
|
567
|
+
if (rowIndex > 0) rendered.push({ kind: 'table-vertical-separator', text: separator });
|
|
568
|
+
row.forEach((cell, cellIndex) => {
|
|
569
|
+
const label = headers[cellIndex] || `Column ${cellIndex + 1}`;
|
|
570
|
+
const firstWidth = Math.max(contentWidth - stringWidthLite(label) - 3, 10);
|
|
571
|
+
const nextWidth = Math.max(contentWidth - 3, 10);
|
|
572
|
+
const firstPass = wrapPlainText(cell, firstWidth, true);
|
|
573
|
+
const firstLine = firstPass[0] || '';
|
|
574
|
+
const remaining = firstPass.slice(1).join(' ');
|
|
575
|
+
const rest = remaining ? wrapPlainText(remaining, nextWidth, true) : [];
|
|
576
|
+
const wrapped = [firstLine, ...rest].filter((line, idx) => idx === 0 || line.trim());
|
|
577
|
+
rendered.push({
|
|
578
|
+
kind: 'table-vertical',
|
|
579
|
+
label,
|
|
580
|
+
text: wrapped[0] || ''
|
|
581
|
+
});
|
|
582
|
+
for (const line of wrapped.slice(1)) {
|
|
583
|
+
rendered.push({
|
|
584
|
+
kind: 'table-vertical-continuation',
|
|
585
|
+
text: line
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
return rendered;
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
if (computeMaxWrappedLines() > maxRowLines && contentWidth < 80) {
|
|
594
|
+
return renderVerticalRows();
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const renderBorder = (type) => {
|
|
598
|
+
const chars = {
|
|
599
|
+
top: ['┌', '─', '┬', '┐'],
|
|
600
|
+
middle: ['├', '─', '┼', '┤'],
|
|
601
|
+
bottom: ['└', '─', '┴', '┘']
|
|
602
|
+
}[type];
|
|
603
|
+
let line = chars[0];
|
|
604
|
+
columnWidths.forEach((width, index) => {
|
|
605
|
+
line += chars[1].repeat(width + 2);
|
|
606
|
+
line += index < columnWidths.length - 1 ? chars[2] : chars[3];
|
|
607
|
+
});
|
|
608
|
+
return line;
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
const renderRowLines = (cells, isHeader = false) => {
|
|
612
|
+
const wrappedColumns = cells.map((cell, index) => wrapCell(cell, columnWidths[index]));
|
|
613
|
+
const maxLines = Math.max(...wrappedColumns.map((entry) => entry.length), 1);
|
|
614
|
+
const verticalOffsets = wrappedColumns.map((entry) => Math.floor((maxLines - entry.length) / 2));
|
|
615
|
+
const rendered = [];
|
|
616
|
+
for (let lineIndex = 0; lineIndex < maxLines; lineIndex += 1) {
|
|
617
|
+
let line = '│';
|
|
618
|
+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
|
|
619
|
+
const wrapped = wrappedColumns[columnIndex];
|
|
620
|
+
const offset = verticalOffsets[columnIndex];
|
|
621
|
+
const contentIndex = lineIndex - offset;
|
|
622
|
+
const text = contentIndex >= 0 && contentIndex < wrapped.length ? wrapped[contentIndex] : '';
|
|
623
|
+
const align = isHeader ? 'center' : alignments[columnIndex];
|
|
624
|
+
line += ` ${padAlignedText(text, columnWidths[columnIndex], align)} │`;
|
|
625
|
+
}
|
|
626
|
+
rendered.push({
|
|
627
|
+
kind: 'table',
|
|
628
|
+
text: line,
|
|
629
|
+
isHeader
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return rendered;
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const tableLines = [
|
|
636
|
+
{ kind: 'table-separator', text: renderBorder('top') },
|
|
637
|
+
...renderRowLines(headers, true),
|
|
638
|
+
{ kind: 'table-separator', text: renderBorder('middle') }
|
|
639
|
+
];
|
|
640
|
+
|
|
641
|
+
rows.forEach((row, index) => {
|
|
642
|
+
tableLines.push(...renderRowLines(row, false));
|
|
643
|
+
if (index < rows.length - 1) {
|
|
644
|
+
tableLines.push({ kind: 'table-separator', text: renderBorder('middle') });
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
tableLines.push({ kind: 'table-separator', text: renderBorder('bottom') });
|
|
648
|
+
|
|
649
|
+
const maxLineWidth = Math.max(...tableLines.map((entry) => stringWidthLite(entry.text)));
|
|
650
|
+
if (maxLineWidth > contentWidth - safetyMargin) {
|
|
651
|
+
return renderVerticalRows();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return tableLines;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function parseRichTextSegments(line, baseColor) {
|
|
658
|
+
const parts = String(line || '').split(/(`[^`]+`|\*\*[^*]+\*\*)/g);
|
|
659
|
+
return parts.map((part, idx) => {
|
|
660
|
+
if (part.startsWith('`') && part.endsWith('`') && part.length >= 2) {
|
|
661
|
+
return h(
|
|
662
|
+
Text,
|
|
663
|
+
{ key: `ic-${idx}`, color: 'black', backgroundColor: 'yellow' },
|
|
664
|
+
part.slice(1, -1)
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
if (part.startsWith('**') && part.endsWith('**') && part.length >= 4) {
|
|
668
|
+
return h(Text, { key: `bd-${idx}`, color: 'cyanBright', bold: true }, part.slice(2, -2));
|
|
669
|
+
}
|
|
670
|
+
return h(Text, { key: `tx-${idx}`, color: baseColor }, part);
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
356
674
|
function safeJsonParse(raw) {
|
|
357
675
|
try {
|
|
358
676
|
return JSON.parse(String(raw || '{}'));
|
|
@@ -371,33 +689,8 @@ function parseToolDisplayName(name) {
|
|
|
371
689
|
};
|
|
372
690
|
}
|
|
373
691
|
|
|
374
|
-
function isCodeGenerationActivityName(name) {
|
|
375
|
-
return String(name || '').trim() === 'Code generation';
|
|
376
|
-
}
|
|
377
|
-
|
|
378
692
|
export function buildPreToolNotice(name, copy) {
|
|
379
|
-
|
|
380
|
-
const base = parsed.base;
|
|
381
|
-
const target = parsed.target ? trimText(parsed.target, 48) : '';
|
|
382
|
-
const isEnglish = String(copy?.roleLabels?.coder || '').trim() === 'CODER' && String(copy?.roleLabels?.you || '').trim() === 'YOU';
|
|
383
|
-
|
|
384
|
-
if (isEnglish) {
|
|
385
|
-
if (base === 'read') return target ? `I'll inspect ${target} first.` : `I'll inspect the relevant file first.`;
|
|
386
|
-
if (base === 'list' || base === 'glob') return target ? `I'll inspect the ${target} directory first.` : `I'll inspect the relevant directory first.`;
|
|
387
|
-
if (base === 'grep') return `I'll search the relevant code first.`;
|
|
388
|
-
if (base === 'edit' || base === 'write' || base === 'patch' || base === 'generate_diff') {
|
|
389
|
-
return `I'll inspect the current code first, then make the change.`;
|
|
390
|
-
}
|
|
391
|
-
if (base === 'run') return `I'll verify the current project state first.`;
|
|
392
|
-
return `I'll check the relevant project context first.`;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (base === 'read') return target ? `我先查看 ${target} 的内容。` : '我先查看相关文件内容。';
|
|
396
|
-
if (base === 'list' || base === 'glob') return target ? `我先查看 ${target} 目录里的内容。` : '我先查看相关目录内容。';
|
|
397
|
-
if (base === 'grep') return '我先搜索相关代码位置。';
|
|
398
|
-
if (base === 'edit' || base === 'write' || base === 'patch' || base === 'generate_diff') return '我先确认当前代码上下文,再动手修改。';
|
|
399
|
-
if (base === 'run') return '我先检查当前项目状态。';
|
|
400
|
-
return '我先查看相关上下文。';
|
|
693
|
+
return buildRegisteredPreToolNotice(name, copy);
|
|
401
694
|
}
|
|
402
695
|
|
|
403
696
|
export function shouldInjectPreToolNotice(msg) {
|
|
@@ -408,6 +701,33 @@ export function shouldInjectPreToolNotice(msg) {
|
|
|
408
701
|
return !text && !hasTextSegment;
|
|
409
702
|
}
|
|
410
703
|
|
|
704
|
+
function getLastToolActivity(msg, statuses = []) {
|
|
705
|
+
const allowed = new Set((Array.isArray(statuses) ? statuses : []).map((status) => String(status)));
|
|
706
|
+
const segments = Array.isArray(msg?.segments) ? msg.segments : [];
|
|
707
|
+
for (let idx = segments.length - 1; idx >= 0; idx -= 1) {
|
|
708
|
+
const segment = segments[idx];
|
|
709
|
+
if (segment?.type !== 'tool' && segment?.type !== 'system_tool') continue;
|
|
710
|
+
if (allowed.size === 0 || allowed.has(String(segment.status || ''))) return segment;
|
|
711
|
+
}
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function hasOnlySyntheticNarration(msg) {
|
|
716
|
+
if (!msg?.syntheticPrelude) return false;
|
|
717
|
+
const segments = Array.isArray(msg?.segments) ? msg.segments : [];
|
|
718
|
+
const hasToolRows = segments.some((segment) => segment?.type === 'tool' || segment?.type === 'system_tool');
|
|
719
|
+
const textSegments = segments.filter((segment) => segment?.type === 'text' && String(segment.text || '').trim());
|
|
720
|
+
return hasToolRows && textSegments.length <= 1;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
export function buildInterToolNotice(previousActivity, nextToolName, copy) {
|
|
724
|
+
return buildRegisteredInterToolNotice(previousActivity, nextToolName, copy);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function buildSyntheticCompletionText(msg, copy) {
|
|
728
|
+
return buildRegisteredSyntheticCompletionText(msg, copy);
|
|
729
|
+
}
|
|
730
|
+
|
|
411
731
|
function formatDurationMs(ms) {
|
|
412
732
|
const safeMs = Math.max(0, Number(ms) || 0);
|
|
413
733
|
return `${(safeMs / 1000).toFixed(1)}s`;
|
|
@@ -514,168 +834,19 @@ export function shouldShowCompletionFooter(msg) {
|
|
|
514
834
|
}
|
|
515
835
|
|
|
516
836
|
function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
|
|
517
|
-
|
|
518
|
-
if (parsed.base === 'project_index') {
|
|
519
|
-
return blocked
|
|
520
|
-
? `${copy.toolActivity.blocked}: project index`
|
|
521
|
-
: done
|
|
522
|
-
? copy.toolActivity.doneProjectIndex
|
|
523
|
-
: copy.toolActivity.doingProjectIndex;
|
|
524
|
-
}
|
|
525
|
-
if (parsed.base === 'file_index') {
|
|
526
|
-
const safeTarget = trimText(parsed.target || '.codemini-project/file-index.json', 72);
|
|
527
|
-
return blocked
|
|
528
|
-
? `${copy.toolActivity.blocked}: ${safeTarget}`
|
|
529
|
-
: done
|
|
530
|
-
? `${copy.toolActivity.doneFileIndex}: ${safeTarget}`
|
|
531
|
-
: `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
|
|
532
|
-
}
|
|
533
|
-
if (parsed.base === 'run' || parsed.base === 'start_service') {
|
|
534
|
-
const intent = classifyCommandIntent(parsed.target);
|
|
535
|
-
const target = parsed.target || intent.kind || 'command';
|
|
536
|
-
if (intent.kind === 'install') {
|
|
537
|
-
return blocked
|
|
538
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
539
|
-
: done
|
|
540
|
-
? `${copy.toolActivity.doneInstall}: ${target}`
|
|
541
|
-
: `${copy.toolActivity.doingInstall}: ${target}`;
|
|
542
|
-
}
|
|
543
|
-
if (intent.kind === 'build') {
|
|
544
|
-
return blocked
|
|
545
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
546
|
-
: done
|
|
547
|
-
? `${copy.toolActivity.doneBuild}: ${target}`
|
|
548
|
-
: `${copy.toolActivity.doingBuild}: ${target}`;
|
|
549
|
-
}
|
|
550
|
-
if (intent.kind === 'test') {
|
|
551
|
-
return blocked
|
|
552
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
553
|
-
: done
|
|
554
|
-
? `${copy.toolActivity.doneTest}: ${target}`
|
|
555
|
-
: `${copy.toolActivity.doingTest}: ${target}`;
|
|
556
|
-
}
|
|
557
|
-
if (intent.kind === 'frontend-service') {
|
|
558
|
-
return blocked
|
|
559
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
560
|
-
: done
|
|
561
|
-
? `${copy.toolActivity.doneFrontend}: ${target}`
|
|
562
|
-
: `${copy.toolActivity.doingFrontend}: ${target}`;
|
|
563
|
-
}
|
|
564
|
-
if (intent.kind === 'backend-service') {
|
|
565
|
-
return blocked
|
|
566
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
567
|
-
: done
|
|
568
|
-
? `${copy.toolActivity.doneBackend}: ${target}`
|
|
569
|
-
: `${copy.toolActivity.doingBackend}: ${target}`;
|
|
570
|
-
}
|
|
571
|
-
if (intent.kind === 'database-service') {
|
|
572
|
-
return blocked
|
|
573
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
574
|
-
: done
|
|
575
|
-
? `${copy.toolActivity.doneDatabase}: ${target}`
|
|
576
|
-
: `${copy.toolActivity.doingDatabase}: ${target}`;
|
|
577
|
-
}
|
|
578
|
-
if (intent.kind === 'docker-service') {
|
|
579
|
-
return blocked
|
|
580
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
581
|
-
: done
|
|
582
|
-
? `${copy.toolActivity.doneDocker}: ${target}`
|
|
583
|
-
: `${copy.toolActivity.doingDocker}: ${target}`;
|
|
584
|
-
}
|
|
585
|
-
if (intent.kind === 'service') {
|
|
586
|
-
return blocked
|
|
587
|
-
? `${copy.toolActivity.blocked}: ${target}`
|
|
588
|
-
: done
|
|
589
|
-
? `${copy.toolActivity.doneGeneric}: ${target}`
|
|
590
|
-
: `${copy.toolActivity.doingGeneric}: ${target}`;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
if (isCodeGenerationActivityName(name)) {
|
|
594
|
-
return blocked
|
|
595
|
-
? `${copy.toolActivity.blocked}: code generation`
|
|
596
|
-
: done
|
|
597
|
-
? copy.toolActivity.doneCodeGeneration
|
|
598
|
-
: copy.toolActivity.doingCodeGeneration;
|
|
599
|
-
}
|
|
600
|
-
const { raw, base, target } = parseToolDisplayName(name);
|
|
601
|
-
const safeTarget = trimText(target, 72);
|
|
602
|
-
if (base === 'read') {
|
|
603
|
-
return blocked
|
|
604
|
-
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
605
|
-
: done
|
|
606
|
-
? `${copy.toolActivity.doneRead}: ${safeTarget || '.'}`
|
|
607
|
-
: `${copy.toolActivity.doingRead}: ${safeTarget || '.'}`;
|
|
608
|
-
}
|
|
609
|
-
if (base === 'edit') {
|
|
610
|
-
return blocked
|
|
611
|
-
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
612
|
-
: done
|
|
613
|
-
? `${copy.toolActivity.doneEdit}: ${safeTarget || '.'}`
|
|
614
|
-
: `${copy.toolActivity.doingEdit}: ${safeTarget || '.'}`;
|
|
615
|
-
}
|
|
616
|
-
if (base === 'write') {
|
|
617
|
-
return blocked
|
|
618
|
-
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
619
|
-
: done
|
|
620
|
-
? `${copy.toolActivity.doneWrite}: ${safeTarget || '.'}`
|
|
621
|
-
: `${copy.toolActivity.doingWrite}: ${safeTarget || '.'}`;
|
|
622
|
-
}
|
|
623
|
-
if (base === 'patch') {
|
|
624
|
-
return blocked
|
|
625
|
-
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
626
|
-
: done
|
|
627
|
-
? `${copy.toolActivity.donePatch}: ${safeTarget || '.'}`
|
|
628
|
-
: `${copy.toolActivity.doingPatch}: ${safeTarget || '.'}`;
|
|
629
|
-
}
|
|
630
|
-
if (base === 'list' || base === 'glob' || base === 'grep') {
|
|
631
|
-
return blocked
|
|
632
|
-
? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
|
|
633
|
-
: done
|
|
634
|
-
? `${copy.toolActivity.doneList}: ${safeTarget || '.'}`
|
|
635
|
-
: `${copy.toolActivity.doingList}: ${safeTarget || '.'}`;
|
|
636
|
-
}
|
|
637
|
-
if (base === 'run') {
|
|
638
|
-
return blocked
|
|
639
|
-
? `${copy.toolActivity.blocked}: ${safeTarget || base}`
|
|
640
|
-
: done
|
|
641
|
-
? `${copy.toolActivity.doneCommand}: ${safeTarget || base}`
|
|
642
|
-
: `${copy.toolActivity.doingCommand}: ${safeTarget || base}`;
|
|
643
|
-
}
|
|
644
|
-
if (base === 'start_service' || base === 'list_services' || base === 'get_service_status' || base === 'get_service_logs' || base === 'stop_service') {
|
|
645
|
-
return blocked
|
|
646
|
-
? `${copy.toolActivity.blocked}: ${safeTarget || base}`
|
|
647
|
-
: done
|
|
648
|
-
? `${copy.toolActivity.doneGeneric}: ${safeTarget || base}`
|
|
649
|
-
: `${copy.toolActivity.doingGeneric}: ${safeTarget || base}`;
|
|
650
|
-
}
|
|
651
|
-
if (base === 'create_task') {
|
|
652
|
-
return blocked ? `${copy.toolActivity.blocked}: create_task` : done ? copy.toolActivity.doneCreateTask : copy.toolActivity.doingCreateTask;
|
|
653
|
-
}
|
|
654
|
-
if (base === 'update_task') {
|
|
655
|
-
return blocked ? `${copy.toolActivity.blocked}: update_task` : done ? copy.toolActivity.doneUpdateTask : copy.toolActivity.doingUpdateTask;
|
|
656
|
-
}
|
|
657
|
-
return blocked ? `${copy.toolActivity.blocked}: ${raw}` : done ? `${copy.toolActivity.doneGeneric}: ${raw}` : `${copy.toolActivity.doingGeneric}: ${raw}`;
|
|
837
|
+
return describeRegisteredToolActivity(copy, name, { done, blocked });
|
|
658
838
|
}
|
|
659
839
|
|
|
660
840
|
function describeSkillActivity(name, copy, { done = false, failed = false } = {}) {
|
|
661
|
-
|
|
662
|
-
if (done) return `${copy.toolActivity.doneSkill}: /${name}`;
|
|
663
|
-
return `${copy.toolActivity.doingSkill}: /${name}`;
|
|
841
|
+
return describeRegisteredSkillActivity(copy, name, { done, failed });
|
|
664
842
|
}
|
|
665
843
|
|
|
666
844
|
function describeAutoSkillActivity(names, copy) {
|
|
667
|
-
|
|
668
|
-
if (safeNames.length === 0) return '';
|
|
669
|
-
return copy.runtime.autoSkillInjected(safeNames);
|
|
845
|
+
return describeRegisteredAutoSkillActivity(copy, names);
|
|
670
846
|
}
|
|
671
847
|
|
|
672
848
|
function formatAutoSkillBadge(names, copy) {
|
|
673
|
-
|
|
674
|
-
if (safeNames.length === 0) return '';
|
|
675
|
-
const [first, ...rest] = safeNames;
|
|
676
|
-
const suffix = rest.length > 0 ? ` +${rest.length}` : '';
|
|
677
|
-
const prefix = copy?.roleLabels?.system === 'SYSTEM' ? 'AUTO' : '自动';
|
|
678
|
-
return `${prefix} /${first}${suffix}`;
|
|
849
|
+
return formatRegisteredAutoSkillBadge(copy, names);
|
|
679
850
|
}
|
|
680
851
|
|
|
681
852
|
function normalizeRuntimeStatus(status, copy) {
|
|
@@ -880,20 +1051,31 @@ function Header({ sessionId, model, shellName, safeMode = true }) {
|
|
|
880
1051
|
}
|
|
881
1052
|
|
|
882
1053
|
function renderInlineCode(line, baseColor) {
|
|
883
|
-
|
|
884
|
-
return parts.map((part, idx) => {
|
|
885
|
-
if (part.startsWith('`') && part.endsWith('`') && part.length >= 2) {
|
|
886
|
-
return h(
|
|
887
|
-
Text,
|
|
888
|
-
{ key: `ic-${idx}`, color: 'black', backgroundColor: 'yellow' },
|
|
889
|
-
part.slice(1, -1)
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
return h(Text, { key: `tx-${idx}`, color: baseColor }, part);
|
|
893
|
-
});
|
|
1054
|
+
return parseRichTextSegments(line, baseColor);
|
|
894
1055
|
}
|
|
895
1056
|
|
|
896
1057
|
function renderTextLine(msg, line, idx, color) {
|
|
1058
|
+
const headingMatch = String(line || '').match(/^\s{0,3}(#{1,3})\s+(.*)$/);
|
|
1059
|
+
if (headingMatch) {
|
|
1060
|
+
const level = headingMatch[1].length;
|
|
1061
|
+
const title = headingMatch[2].trim();
|
|
1062
|
+
const accent = level === 1 ? 'cyanBright' : level === 2 ? 'greenBright' : 'yellowBright';
|
|
1063
|
+
return h(
|
|
1064
|
+
Box,
|
|
1065
|
+
{ key: `ln-wrap-${msg.id}-${idx}` },
|
|
1066
|
+
h(Text, { color: accent, bold: true }, title)
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const boldTitleMatch = String(line || '').match(/^\s*\*\*(.+)\*\*\s*$/);
|
|
1071
|
+
if (boldTitleMatch) {
|
|
1072
|
+
return h(
|
|
1073
|
+
Box,
|
|
1074
|
+
{ key: `ln-wrap-${msg.id}-${idx}` },
|
|
1075
|
+
h(Text, { key: `ln-${msg.id}-${idx}`, color: 'cyanBright', bold: true }, boldTitleMatch[1].trim())
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
897
1079
|
return h(
|
|
898
1080
|
Box,
|
|
899
1081
|
{ key: `ln-wrap-${msg.id}-${idx}` },
|
|
@@ -1544,12 +1726,70 @@ export function mergeActivitySummary(previousSummary, nextSummary, activityName)
|
|
|
1544
1726
|
return lines.join('\n');
|
|
1545
1727
|
}
|
|
1546
1728
|
|
|
1729
|
+
export function collapseActivityChainRows(inputRows, showToolDetails, copy, maxVisibleActivities = 3) {
|
|
1730
|
+
const rows = Array.isArray(inputRows) ? inputRows : [];
|
|
1731
|
+
if (showToolDetails) return rows.slice();
|
|
1732
|
+
const maxVisible = Math.max(1, Number(maxVisibleActivities) || 3);
|
|
1733
|
+
const collapsed = [];
|
|
1734
|
+
|
|
1735
|
+
const isCollapsibleActivity = (row) =>
|
|
1736
|
+
row?.kind === 'activity' &&
|
|
1737
|
+
['tool', 'skill', 'system_tool'].includes(String(row?.activityType || 'tool'));
|
|
1738
|
+
|
|
1739
|
+
let index = 0;
|
|
1740
|
+
while (index < rows.length) {
|
|
1741
|
+
const row = rows[index];
|
|
1742
|
+
if (!isCollapsibleActivity(row)) {
|
|
1743
|
+
collapsed.push(row);
|
|
1744
|
+
index += 1;
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
const group = [];
|
|
1749
|
+
while (index < rows.length) {
|
|
1750
|
+
const next = rows[index];
|
|
1751
|
+
if (isCollapsibleActivity(next)) {
|
|
1752
|
+
group.push([next]);
|
|
1753
|
+
index += 1;
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
if (next?.kind === 'activity-summary' && group.length > 0) {
|
|
1757
|
+
group[group.length - 1].push(next);
|
|
1758
|
+
index += 1;
|
|
1759
|
+
continue;
|
|
1760
|
+
}
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
if (group.length <= maxVisible) {
|
|
1765
|
+
for (const item of group) collapsed.push(...item);
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
const hiddenCount = group.length - maxVisible;
|
|
1770
|
+
collapsed.push({
|
|
1771
|
+
kind: 'activity-collapsed',
|
|
1772
|
+
hiddenCount,
|
|
1773
|
+
text:
|
|
1774
|
+
copy?.generic?.toolChainCollapsed != null
|
|
1775
|
+
? copy.generic.toolChainCollapsed(hiddenCount)
|
|
1776
|
+
: `${hiddenCount} earlier tool calls hidden`
|
|
1777
|
+
});
|
|
1778
|
+
for (const item of group.slice(-maxVisible)) {
|
|
1779
|
+
collapsed.push(...item);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
return collapsed;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1547
1786
|
function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
1548
1787
|
const rows = [];
|
|
1549
1788
|
const pushTextRows = (text) => {
|
|
1550
1789
|
const lines = String(text || '').split('\n');
|
|
1551
1790
|
let codeFence = false;
|
|
1552
|
-
for (
|
|
1791
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
1792
|
+
const line = lines[lineIndex];
|
|
1553
1793
|
const trimmed = line.trim();
|
|
1554
1794
|
const planProgress = parsePlanProgressLine(trimmed);
|
|
1555
1795
|
if (planProgress) {
|
|
@@ -1570,6 +1810,16 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
|
1570
1810
|
pushWrappedRow(rows, { kind: 'code', text: line || ' ', color: 'gray' }, contentWidth);
|
|
1571
1811
|
continue;
|
|
1572
1812
|
}
|
|
1813
|
+
if (isMarkdownTableHeader(line, lines[lineIndex + 1])) {
|
|
1814
|
+
const tableLines = [line];
|
|
1815
|
+
lineIndex += 1; // skip separator
|
|
1816
|
+
while (lineIndex + 1 < lines.length && splitMarkdownTableCells(lines[lineIndex + 1]).length > 1) {
|
|
1817
|
+
tableLines.push(lines[lineIndex + 1]);
|
|
1818
|
+
lineIndex += 1;
|
|
1819
|
+
}
|
|
1820
|
+
rows.push(...formatMarkdownTableBlock(tableLines, contentWidth));
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1573
1823
|
let color = msg.color || roleStyle(msg.label).text || 'white';
|
|
1574
1824
|
if (line.startsWith('#')) color = 'cyanBright';
|
|
1575
1825
|
else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) color = 'magentaBright';
|
|
@@ -1651,7 +1901,9 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
|
1651
1901
|
syntheticRows.push(...statusRows);
|
|
1652
1902
|
}
|
|
1653
1903
|
|
|
1654
|
-
return normalizeActivitySpacingRows(
|
|
1904
|
+
return normalizeActivitySpacingRows(
|
|
1905
|
+
insertRowsAfterLastCodeRow(collapseActivityChainRows(rows, showToolDetails, copy), syntheticRows)
|
|
1906
|
+
);
|
|
1655
1907
|
}
|
|
1656
1908
|
|
|
1657
1909
|
function renderMessageRow(msg, row, idx, loaderTick) {
|
|
@@ -1695,6 +1947,49 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
1695
1947
|
h(Text, { color: 'gray' }, `└ ${row.text}`)
|
|
1696
1948
|
);
|
|
1697
1949
|
}
|
|
1950
|
+
if (row.kind === 'table') {
|
|
1951
|
+
return h(
|
|
1952
|
+
Box,
|
|
1953
|
+
{ key: `row-table-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1954
|
+
h(Text, { color: row.isHeader ? 'cyanBright' : 'gray', bold: Boolean(row.isHeader) }, row.text)
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
if (row.kind === 'table-separator') {
|
|
1958
|
+
return h(
|
|
1959
|
+
Box,
|
|
1960
|
+
{ key: `row-table-sep-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1961
|
+
h(Text, { color: 'gray' }, row.text)
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1964
|
+
if (row.kind === 'table-vertical') {
|
|
1965
|
+
return h(
|
|
1966
|
+
Box,
|
|
1967
|
+
{ key: `row-table-v-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1968
|
+
h(Text, { color: 'cyanBright', bold: true }, `${row.label}:`),
|
|
1969
|
+
h(Text, { color: 'gray' }, row.text ? ` ${row.text}` : '')
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
if (row.kind === 'table-vertical-continuation') {
|
|
1973
|
+
return h(
|
|
1974
|
+
Box,
|
|
1975
|
+
{ key: `row-table-vc-${msg.id}-${idx}`, marginLeft: 3 },
|
|
1976
|
+
h(Text, { color: 'gray' }, row.text)
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
if (row.kind === 'table-vertical-separator') {
|
|
1980
|
+
return h(
|
|
1981
|
+
Box,
|
|
1982
|
+
{ key: `row-table-vs-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1983
|
+
h(Text, { color: 'gray' }, row.text)
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
if (row.kind === 'activity-collapsed') {
|
|
1987
|
+
return h(
|
|
1988
|
+
Box,
|
|
1989
|
+
{ key: `row-tool-collapsed-${msg.id}-${idx}`, marginLeft: 1 },
|
|
1990
|
+
h(Text, { color: 'gray' }, `└ ${row.text}`)
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1698
1993
|
if (row.kind === 'plan-progress') {
|
|
1699
1994
|
return h(
|
|
1700
1995
|
Box,
|
|
@@ -2539,6 +2834,26 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2539
2834
|
return aid;
|
|
2540
2835
|
};
|
|
2541
2836
|
|
|
2837
|
+
const maybeRefreshSyntheticNarration = (nextToolName) => {
|
|
2838
|
+
const targetId = activeAssistantIdRef.current;
|
|
2839
|
+
if (!targetId || !nextToolName) return;
|
|
2840
|
+
setMessages((prev) =>
|
|
2841
|
+
prev.map((m) => {
|
|
2842
|
+
if (m.id !== targetId) return m;
|
|
2843
|
+
if (!hasOnlySyntheticNarration(m)) return m;
|
|
2844
|
+
const previousActivity = getLastToolActivity(m, ['done', 'running']);
|
|
2845
|
+
if (!previousActivity) return m;
|
|
2846
|
+
const bridge = buildInterToolNotice(previousActivity, nextToolName, copy);
|
|
2847
|
+
if (!bridge) return m;
|
|
2848
|
+
return {
|
|
2849
|
+
...m,
|
|
2850
|
+
text: bridge,
|
|
2851
|
+
syntheticPrelude: true
|
|
2852
|
+
};
|
|
2853
|
+
})
|
|
2854
|
+
);
|
|
2855
|
+
};
|
|
2856
|
+
|
|
2542
2857
|
const runSubmission = (line, userMessageId = null) => {
|
|
2543
2858
|
inFlightRef.current = true;
|
|
2544
2859
|
activeUserMessageIdRef.current = userMessageId;
|
|
@@ -2632,9 +2947,15 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2632
2947
|
setMessages((prev) =>
|
|
2633
2948
|
prev.map((m) => {
|
|
2634
2949
|
if (m.id !== targetId) return m;
|
|
2950
|
+
const responseText = typeof event.text === 'string' ? event.text.trim() : '';
|
|
2951
|
+
const shouldSynthesizeCompletion = !responseText && m.syntheticPrelude;
|
|
2635
2952
|
return {
|
|
2636
2953
|
...m,
|
|
2637
|
-
...(
|
|
2954
|
+
...(responseText
|
|
2955
|
+
? { text: event.text, syntheticPrelude: false }
|
|
2956
|
+
: shouldSynthesizeCompletion
|
|
2957
|
+
? { text: buildSyntheticCompletionText(m, copy), syntheticPrelude: false }
|
|
2958
|
+
: {}),
|
|
2638
2959
|
loading: false,
|
|
2639
2960
|
phase: undefined,
|
|
2640
2961
|
liveStatus: undefined,
|
|
@@ -2658,6 +2979,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2658
2979
|
}
|
|
2659
2980
|
if (event?.type === 'tool:start') {
|
|
2660
2981
|
ensureActiveAssistant();
|
|
2982
|
+
maybeRefreshSyntheticNarration(event.name);
|
|
2661
2983
|
const detail = describeToolActivity(event.name, copy);
|
|
2662
2984
|
setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'magentaBright'));
|
|
2663
2985
|
setInputStage('tooling');
|