codemini-cli 0.1.12 → 0.1.14
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 +58 -0
- package/package.json +1 -1
- package/src/core/agent-loop.js +46 -0
- package/src/core/chat-runtime.js +364 -44
- package/src/core/config-store.js +49 -3
- package/src/core/reply-language.js +25 -0
- package/src/core/shell-profile.js +1 -1
- package/src/core/shell.js +135 -9
- package/src/core/soul.js +3 -1
- package/src/core/tools.js +1284 -10
- package/src/tui/chat-app.js +295 -36
package/src/tui/chat-app.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Box, Text, useApp, useInput } from 'ink';
|
|
|
3
3
|
import { shouldCaptureEscapeSequence } from './input-escape.js';
|
|
4
4
|
|
|
5
5
|
const h = React.createElement;
|
|
6
|
+
const SUGGESTION_PAGE_SIZE = 8;
|
|
6
7
|
const BANNER = [
|
|
7
8
|
' ██████ ██████ ██████ ███████ ███ ███ ██ ███ ██ ██ ',
|
|
8
9
|
'██ ██ ██ ██ ██ ██ ████ ████ ██ ████ ██ ██ ',
|
|
@@ -122,13 +123,13 @@ const TUI_COPY = {
|
|
|
122
123
|
},
|
|
123
124
|
suggestion: {
|
|
124
125
|
singleTab: 'Tab 补全当前命令',
|
|
125
|
-
navFill: 'Tab
|
|
126
|
-
navEnter: 'Tab 进入切换模式,再用 ↑↓
|
|
126
|
+
navFill: 'Tab 保持切换模式,↑↓选择,←→翻页,Enter 填入',
|
|
127
|
+
navEnter: 'Tab 进入切换模式,再用 ↑↓ 选择,←→翻页',
|
|
127
128
|
noSuggestions: '/ 查看命令,Tab 自动补全,↑↓ 历史,Ctrl+T 展开工具',
|
|
128
129
|
oneNav: 'Tab 或 Enter 填入当前命令,↑↓ 历史',
|
|
129
130
|
oneIdle: 'Tab 补全当前唯一候选,Enter 直接发送,↑↓ 历史',
|
|
130
|
-
manyNav: (count) => `Tab
|
|
131
|
-
manyIdle: (count) => `Tab 进入候选切换 (${count} 项),↑↓
|
|
131
|
+
manyNav: (count) => `Tab 切换候选,↑↓选择,←→翻页,Enter 填入 (${count} 项)`,
|
|
132
|
+
manyIdle: (count) => `Tab 进入候选切换 (${count} 项),↑↓ 历史,←→翻页`
|
|
132
133
|
},
|
|
133
134
|
runtime: {
|
|
134
135
|
sendingToGateway: '正在发送到网关',
|
|
@@ -224,13 +225,13 @@ const TUI_COPY = {
|
|
|
224
225
|
},
|
|
225
226
|
suggestion: {
|
|
226
227
|
singleTab: 'Tab completes the current command',
|
|
227
|
-
navFill: 'Tab stays in pick mode, ↑↓ select, Enter applies',
|
|
228
|
-
navEnter: 'Tab enters pick mode, then use ↑↓ to choose',
|
|
228
|
+
navFill: 'Tab stays in pick mode, ↑↓ select, ←→ page, Enter applies',
|
|
229
|
+
navEnter: 'Tab enters pick mode, then use ↑↓ to choose, ←→ page',
|
|
229
230
|
noSuggestions: '/ shows commands, Tab autocompletes, ↑↓ history, Ctrl+T tools',
|
|
230
231
|
oneNav: 'Tab or Enter applies the current command, ↑↓ history',
|
|
231
232
|
oneIdle: 'Tab completes the only candidate, Enter sends, ↑↓ history',
|
|
232
|
-
manyNav: (count) => `Tab cycles candidates, ↑↓ select, Enter applies (${count} items)`,
|
|
233
|
-
manyIdle: (count) => `Tab enters candidate mode (${count} items), ↑↓ history`
|
|
233
|
+
manyNav: (count) => `Tab cycles candidates, ↑↓ select, ←→ page, Enter applies (${count} items)`,
|
|
234
|
+
manyIdle: (count) => `Tab enters candidate mode (${count} items), ↑↓ history, ←→ page`
|
|
234
235
|
},
|
|
235
236
|
runtime: {
|
|
236
237
|
sendingToGateway: 'sending to gateway',
|
|
@@ -315,6 +316,11 @@ function getActivityDisplayParts(activity) {
|
|
|
315
316
|
read_file: 'Read',
|
|
316
317
|
write_file: 'Write',
|
|
317
318
|
run_command: 'Command',
|
|
319
|
+
start_service: 'Service',
|
|
320
|
+
list_services: 'Service',
|
|
321
|
+
get_service_status: 'Service',
|
|
322
|
+
get_service_logs: 'Service',
|
|
323
|
+
stop_service: 'Service',
|
|
318
324
|
list_files: 'Glob',
|
|
319
325
|
create_task: 'Task',
|
|
320
326
|
update_task: 'Task'
|
|
@@ -356,6 +362,13 @@ function describeToolActivity(name, copy, { done = false, blocked = false } = {}
|
|
|
356
362
|
? `${copy.toolActivity.doneCommand}: ${safeTarget || 'run_command'}`
|
|
357
363
|
: `${copy.toolActivity.doingCommand}: ${safeTarget || 'run_command'}`;
|
|
358
364
|
}
|
|
365
|
+
if (base === 'start_service' || base === 'list_services' || base === 'get_service_status' || base === 'get_service_logs' || base === 'stop_service') {
|
|
366
|
+
return blocked
|
|
367
|
+
? `${copy.toolActivity.blocked}: ${safeTarget || base}`
|
|
368
|
+
: done
|
|
369
|
+
? `${copy.toolActivity.doneGeneric}: ${safeTarget || base}`
|
|
370
|
+
: `${copy.toolActivity.doingGeneric}: ${safeTarget || base}`;
|
|
371
|
+
}
|
|
359
372
|
if (base === 'create_task') {
|
|
360
373
|
return blocked ? `${copy.toolActivity.blocked}: create_task` : done ? copy.toolActivity.doneCreateTask : copy.toolActivity.doingCreateTask;
|
|
361
374
|
}
|
|
@@ -471,9 +484,13 @@ function PlanStrip({ planState, copy }) {
|
|
|
471
484
|
...planState.steps.slice(-4).map((step, idx) =>
|
|
472
485
|
h(
|
|
473
486
|
Box,
|
|
474
|
-
{ key: `plan-step-${idx}
|
|
487
|
+
{ key: `plan-step-${idx}`, marginTop: idx === 0 ? 0 : 1 },
|
|
475
488
|
h(Text, { color: step.status === 'active' ? 'cyanBright' : step.status === 'failed' ? 'redBright' : 'gray' }, `${step.status === 'active' ? '>' : step.status === 'failed' ? 'x' : '·'} `),
|
|
476
|
-
h(Text, { color: step.status === 'active' ? '
|
|
489
|
+
h(Text, { color: step.status === 'active' ? 'yellowBright' : step.status === 'failed' ? 'redBright' : 'gray' }, `${step.index}/${step.total}`),
|
|
490
|
+
h(Text, { color: 'gray' }, ' '),
|
|
491
|
+
h(Text, { color: step.status === 'active' ? 'magentaBright' : step.status === 'failed' ? 'redBright' : 'gray' }, String(step.role || 'agent').toUpperCase()),
|
|
492
|
+
h(Text, { color: 'gray' }, ' '),
|
|
493
|
+
h(Text, { color: step.status === 'active' ? 'white' : step.status === 'failed' ? 'redBright' : 'gray' }, step.title)
|
|
477
494
|
)
|
|
478
495
|
)
|
|
479
496
|
)
|
|
@@ -582,6 +599,39 @@ export function parseAutoPlanSummaryMessage(text) {
|
|
|
582
599
|
return parsed;
|
|
583
600
|
}
|
|
584
601
|
|
|
602
|
+
export function parsePlanProgressLine(text) {
|
|
603
|
+
const raw = String(text || '').trim();
|
|
604
|
+
const match = raw.match(/^\[plan\]\s+Step\s+(\d+)\/(\d+)\s+->\s+([^:]+):\s+(.+)$/i);
|
|
605
|
+
if (!match) return null;
|
|
606
|
+
return {
|
|
607
|
+
current: Number(match[1]),
|
|
608
|
+
total: Number(match[2]),
|
|
609
|
+
role: String(match[3] || '').trim(),
|
|
610
|
+
title: String(match[4] || '').trim()
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function injectPlanStateMessage(messages, planState, activeUserMessageId, activeAssistantId) {
|
|
615
|
+
const source = Array.isArray(messages) ? messages : [];
|
|
616
|
+
if (!planState || !planState.total) return source;
|
|
617
|
+
const synthetic = {
|
|
618
|
+
id: `plan-state-${planState.current}-${planState.total}-${planState.role || 'agent'}`,
|
|
619
|
+
label: 'system',
|
|
620
|
+
planStrip: true,
|
|
621
|
+
planState
|
|
622
|
+
};
|
|
623
|
+
const withNoPlanStrip = source.filter((message) => !message?.planStrip);
|
|
624
|
+
const userIdx = withNoPlanStrip.findIndex((message) => message.id === activeUserMessageId);
|
|
625
|
+
if (userIdx !== -1) {
|
|
626
|
+
return [...withNoPlanStrip.slice(0, userIdx + 1), synthetic, ...withNoPlanStrip.slice(userIdx + 1)];
|
|
627
|
+
}
|
|
628
|
+
const assistantIdx = withNoPlanStrip.findIndex((message) => message.id === activeAssistantId);
|
|
629
|
+
if (assistantIdx !== -1) {
|
|
630
|
+
return [...withNoPlanStrip.slice(0, assistantIdx), synthetic, ...withNoPlanStrip.slice(assistantIdx)];
|
|
631
|
+
}
|
|
632
|
+
return [...withNoPlanStrip, synthetic];
|
|
633
|
+
}
|
|
634
|
+
|
|
585
635
|
function PlanSummaryBubble({ msg, copy }) {
|
|
586
636
|
const theme = roleStyle(msg.label);
|
|
587
637
|
const summary = msg.planSummary || parseAutoPlanSummaryMessage(msg.text);
|
|
@@ -759,6 +809,114 @@ function pushWrappedRow(rows, baseRow, contentWidth) {
|
|
|
759
809
|
});
|
|
760
810
|
}
|
|
761
811
|
|
|
812
|
+
function isActivityRow(row) {
|
|
813
|
+
return row?.kind === 'activity' || row?.kind === 'activity-summary';
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function isBlankTextRow(row) {
|
|
817
|
+
return row?.kind === 'text' && String(row?.text || '').trim() === '';
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export function normalizeActivitySpacingRows(inputRows) {
|
|
821
|
+
const rows = Array.isArray(inputRows) ? inputRows : [];
|
|
822
|
+
const normalized = [];
|
|
823
|
+
|
|
824
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
825
|
+
const row = rows[index];
|
|
826
|
+
const prev = normalized.at(-1);
|
|
827
|
+
const next = rows[index + 1];
|
|
828
|
+
|
|
829
|
+
if (isBlankTextRow(row)) {
|
|
830
|
+
let lookahead = index + 1;
|
|
831
|
+
while (lookahead < rows.length && isBlankTextRow(rows[lookahead])) {
|
|
832
|
+
lookahead += 1;
|
|
833
|
+
}
|
|
834
|
+
if (isActivityRow(rows[lookahead])) {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (isBlankTextRow(row) && isActivityRow(next)) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
normalized.push(row);
|
|
844
|
+
|
|
845
|
+
if (isActivityRow(row) && !isActivityRow(next) && next) {
|
|
846
|
+
const last = normalized.at(-1);
|
|
847
|
+
if (!isBlankTextRow(last) && !(next.kind === 'status')) {
|
|
848
|
+
normalized.push({
|
|
849
|
+
kind: 'text',
|
|
850
|
+
text: ' ',
|
|
851
|
+
color: 'white'
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (isBlankTextRow(row) && isBlankTextRow(prev)) {
|
|
857
|
+
normalized.pop();
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return normalized;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function isReadActivityName(name) {
|
|
865
|
+
const parsed = parseToolDisplayName(name);
|
|
866
|
+
return parsed.base === 'read_file' || parsed.base === 'Read';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function isIgnorableSegmentAfterRead(item, activityType, activityName) {
|
|
870
|
+
if (!item) return true;
|
|
871
|
+
if (item.type === 'text') {
|
|
872
|
+
return String(item.text || '').trim() === '';
|
|
873
|
+
}
|
|
874
|
+
return (item.type || 'tool') === activityType && item.name === activityName;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
export function findActivityUpdateIndex(items, toolEvent) {
|
|
878
|
+
const source = Array.isArray(items) ? items : [];
|
|
879
|
+
const activityType = toolEvent?.type || 'tool';
|
|
880
|
+
const byId = toolEvent?.id
|
|
881
|
+
? source.findIndex((item) => item.type === activityType && item.id && item.id === toolEvent.id)
|
|
882
|
+
: -1;
|
|
883
|
+
if (byId !== -1) return byId;
|
|
884
|
+
|
|
885
|
+
const byNameRunning = source.findIndex(
|
|
886
|
+
(item) => (item.type || 'tool') === activityType && item.name === toolEvent?.name && item.status !== 'done'
|
|
887
|
+
);
|
|
888
|
+
if (byNameRunning !== -1) return byNameRunning;
|
|
889
|
+
|
|
890
|
+
if (isReadActivityName(toolEvent?.name)) {
|
|
891
|
+
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
892
|
+
const item = source[index];
|
|
893
|
+
if ((item?.type || 'tool') !== activityType || item?.name !== toolEvent?.name) continue;
|
|
894
|
+
const trailing = source.slice(index + 1);
|
|
895
|
+
if (trailing.every((entry) => isIgnorableSegmentAfterRead(entry, activityType, toolEvent?.name))) {
|
|
896
|
+
return index;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
return -1;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
export function mergeActivitySummary(previousSummary, nextSummary, activityName) {
|
|
905
|
+
const prev = String(previousSummary || '').trim();
|
|
906
|
+
const next = String(nextSummary || '').trim();
|
|
907
|
+
if (!next) return prev;
|
|
908
|
+
if (!prev) return next;
|
|
909
|
+
if (!isReadActivityName(activityName) || prev === next) return next;
|
|
910
|
+
|
|
911
|
+
const lines = [];
|
|
912
|
+
for (const line of `${prev}\n${next}`.split('\n')) {
|
|
913
|
+
const trimmed = String(line || '').trim();
|
|
914
|
+
if (!trimmed) continue;
|
|
915
|
+
if (!lines.includes(trimmed)) lines.push(trimmed);
|
|
916
|
+
}
|
|
917
|
+
return lines.join('\n');
|
|
918
|
+
}
|
|
919
|
+
|
|
762
920
|
function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
|
|
763
921
|
const rows = [];
|
|
764
922
|
const pushTextRows = (text) => {
|
|
@@ -766,6 +924,17 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
|
|
|
766
924
|
let codeFence = false;
|
|
767
925
|
for (const line of lines) {
|
|
768
926
|
const trimmed = line.trim();
|
|
927
|
+
const planProgress = parsePlanProgressLine(trimmed);
|
|
928
|
+
if (planProgress) {
|
|
929
|
+
rows.push({
|
|
930
|
+
kind: 'plan-progress',
|
|
931
|
+
current: planProgress.current,
|
|
932
|
+
total: planProgress.total,
|
|
933
|
+
role: planProgress.role,
|
|
934
|
+
title: trimText(planProgress.title, Math.max(12, contentWidth - 18))
|
|
935
|
+
});
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
769
938
|
if (trimmed.startsWith('```')) {
|
|
770
939
|
codeFence = !codeFence;
|
|
771
940
|
continue;
|
|
@@ -842,7 +1011,7 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72) {
|
|
|
842
1011
|
);
|
|
843
1012
|
}
|
|
844
1013
|
|
|
845
|
-
return rows;
|
|
1014
|
+
return normalizeActivitySpacingRows(rows);
|
|
846
1015
|
}
|
|
847
1016
|
|
|
848
1017
|
|
|
@@ -883,7 +1052,74 @@ function getSuggestionDisplay(item) {
|
|
|
883
1052
|
return typeof item === 'string' ? item : String(item?.display || item?.value || '');
|
|
884
1053
|
}
|
|
885
1054
|
|
|
1055
|
+
function getSuggestionDescription(item) {
|
|
1056
|
+
return typeof item === 'string' ? '' : String(item?.description || '');
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
export function formatSuggestionDescription(text, maxChars = 40) {
|
|
1060
|
+
const value = String(text || '').replace(/\s+/g, ' ').trim();
|
|
1061
|
+
if (!value) return '';
|
|
1062
|
+
const limit = Math.max(4, Number(maxChars) || 40);
|
|
1063
|
+
if (value.length <= limit) return value;
|
|
1064
|
+
return `${value.slice(0, limit - 3)}...`;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
export function getSuggestionPageState(commandSuggestions, menuIndex, pageSize = SUGGESTION_PAGE_SIZE) {
|
|
1068
|
+
const items = Array.isArray(commandSuggestions) ? commandSuggestions : [];
|
|
1069
|
+
const normalizedPageSize = Math.max(1, Number(pageSize) || SUGGESTION_PAGE_SIZE);
|
|
1070
|
+
const safeIndex = items.length === 0 ? 0 : Math.max(0, Math.min(Number(menuIndex) || 0, items.length - 1));
|
|
1071
|
+
const pageIndex = Math.floor(safeIndex / normalizedPageSize);
|
|
1072
|
+
const pageCount = Math.max(1, Math.ceil(items.length / normalizedPageSize));
|
|
1073
|
+
const pageStart = pageIndex * normalizedPageSize;
|
|
1074
|
+
const pageEnd = Math.min(items.length, pageStart + normalizedPageSize);
|
|
1075
|
+
return {
|
|
1076
|
+
pageSize: normalizedPageSize,
|
|
1077
|
+
safeIndex,
|
|
1078
|
+
pageIndex,
|
|
1079
|
+
pageCount,
|
|
1080
|
+
pageStart,
|
|
1081
|
+
pageEnd,
|
|
1082
|
+
pageItems: items.slice(pageStart, pageEnd)
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
export function moveSuggestionSelection(currentIndex, itemCount, direction, pageSize = SUGGESTION_PAGE_SIZE) {
|
|
1087
|
+
const total = Math.max(0, Number(itemCount) || 0);
|
|
1088
|
+
if (total <= 0) return 0;
|
|
1089
|
+
const normalizedPageSize = Math.max(1, Number(pageSize) || SUGGESTION_PAGE_SIZE);
|
|
1090
|
+
const safeIndex = Math.max(0, Math.min(Number(currentIndex) || 0, total - 1));
|
|
1091
|
+
|
|
1092
|
+
if (direction === 'left') {
|
|
1093
|
+
if (safeIndex < normalizedPageSize) return 0;
|
|
1094
|
+
return Math.max(0, safeIndex - normalizedPageSize);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (direction === 'right') {
|
|
1098
|
+
const currentPageStart = Math.floor(safeIndex / normalizedPageSize) * normalizedPageSize;
|
|
1099
|
+
const currentPageEnd = Math.min(total, currentPageStart + normalizedPageSize);
|
|
1100
|
+
if (currentPageEnd >= total) return safeIndex;
|
|
1101
|
+
return Math.min(total - 1, safeIndex + normalizedPageSize);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (direction === 'up') {
|
|
1105
|
+
return Math.max(0, safeIndex - 1);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (direction === 'down') {
|
|
1109
|
+
return Math.min(total - 1, safeIndex + 1);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
return safeIndex;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
886
1115
|
function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, contentWidth = 72, copy }) {
|
|
1116
|
+
if (msg?.planStrip) {
|
|
1117
|
+
return h(
|
|
1118
|
+
Box,
|
|
1119
|
+
{ marginBottom: 1 },
|
|
1120
|
+
h(PlanStrip, { planState: msg.planState, copy })
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
887
1123
|
if (msg?.planSummary || parseAutoPlanSummaryMessage(msg?.text)) {
|
|
888
1124
|
return h(PlanSummaryBubble, { msg, copy });
|
|
889
1125
|
}
|
|
@@ -930,6 +1166,18 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
|
|
|
930
1166
|
h(Text, { color: 'gray' }, `└ ${row.text}`)
|
|
931
1167
|
);
|
|
932
1168
|
}
|
|
1169
|
+
if (row.kind === 'plan-progress') {
|
|
1170
|
+
return h(
|
|
1171
|
+
Box,
|
|
1172
|
+
{ key: `row-plan-progress-${msg.id}-${idx}`, marginTop: 1, marginBottom: 1 },
|
|
1173
|
+
h(Text, { color: 'cyanBright' }, '[plan] '),
|
|
1174
|
+
h(Text, { color: 'yellowBright' }, `Step ${row.current}/${row.total}`),
|
|
1175
|
+
h(Text, { color: 'gray' }, ' -> '),
|
|
1176
|
+
h(Text, { color: 'magentaBright' }, String(row.role || 'agent').toUpperCase()),
|
|
1177
|
+
h(Text, { color: 'gray' }, ': '),
|
|
1178
|
+
h(Text, { color: 'white' }, row.title)
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
933
1181
|
if (row.kind === 'status') {
|
|
934
1182
|
const dots = '.'.repeat((loaderTick % 3) + 1);
|
|
935
1183
|
const phase = msg.phase;
|
|
@@ -1028,7 +1276,8 @@ function MessageList({ messages, loaderTick, showToolDetails, contentWidth = 72,
|
|
|
1028
1276
|
|
|
1029
1277
|
function SuggestionPanel({ commandSuggestions, suggestionNav, menuIndex, copy }) {
|
|
1030
1278
|
if (commandSuggestions.length === 0) return null;
|
|
1031
|
-
const
|
|
1279
|
+
const pageState = getSuggestionPageState(commandSuggestions, menuIndex);
|
|
1280
|
+
const grouped = groupCommandSuggestions(pageState.pageItems);
|
|
1032
1281
|
let flatIndex = -1;
|
|
1033
1282
|
const panelHint =
|
|
1034
1283
|
commandSuggestions.length === 1
|
|
@@ -1050,7 +1299,7 @@ function SuggestionPanel({ commandSuggestions, suggestionNav, menuIndex, copy })
|
|
|
1050
1299
|
Box,
|
|
1051
1300
|
{ marginBottom: 1 },
|
|
1052
1301
|
h(Text, { color: 'magentaBright' }, suggestionNav ? copy.generic.commandPaletteGroupedSelect : copy.generic.commandPaletteGroupedSuggestions),
|
|
1053
|
-
h(Text, { color: 'gray' }, ` ${panelHint}`)
|
|
1302
|
+
h(Text, { color: 'gray' }, ` ${panelHint} · ${pageState.pageIndex + 1}/${pageState.pageCount}`)
|
|
1054
1303
|
),
|
|
1055
1304
|
...grouped.flatMap(([group, items]) => {
|
|
1056
1305
|
const nodes = [
|
|
@@ -1063,13 +1312,17 @@ function SuggestionPanel({ commandSuggestions, suggestionNav, menuIndex, copy })
|
|
|
1063
1312
|
];
|
|
1064
1313
|
items.forEach((c) => {
|
|
1065
1314
|
flatIndex += 1;
|
|
1066
|
-
const active = suggestionNav && menuIndex === flatIndex;
|
|
1315
|
+
const active = suggestionNav && menuIndex === pageState.pageStart + flatIndex;
|
|
1067
1316
|
const label = getSuggestionDisplay(c);
|
|
1317
|
+
const description = formatSuggestionDescription(getSuggestionDescription(c), 42);
|
|
1068
1318
|
nodes.push(
|
|
1069
1319
|
h(
|
|
1070
1320
|
Box,
|
|
1071
1321
|
{ key: `opt-${group}-${getSuggestionValue(c)}` },
|
|
1072
|
-
h(Text, { color: active ? 'black' : 'magenta', backgroundColor: active ? 'magentaBright' : undefined }, `${active ? ' > ' : ' '}${label}`)
|
|
1322
|
+
h(Text, { color: active ? 'black' : 'magenta', backgroundColor: active ? 'magentaBright' : undefined }, `${active ? ' > ' : ' '}${label}`),
|
|
1323
|
+
description
|
|
1324
|
+
? h(Text, { color: active ? 'black' : 'gray', backgroundColor: active ? 'magentaBright' : undefined }, ` ${description}`)
|
|
1325
|
+
: null
|
|
1073
1326
|
)
|
|
1074
1327
|
);
|
|
1075
1328
|
});
|
|
@@ -1301,7 +1554,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1301
1554
|
|
|
1302
1555
|
const commandSuggestions =
|
|
1303
1556
|
inputValue.startsWith('/')
|
|
1304
|
-
?
|
|
1557
|
+
? runtime.getCompletionOptions(inputValue) || []
|
|
1305
1558
|
: [];
|
|
1306
1559
|
const hasTransientPanels =
|
|
1307
1560
|
commandSuggestions.length > 0 || pendingQueue.length > 0 || debugKeys || Boolean(planState?.total);
|
|
@@ -1390,13 +1643,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1390
1643
|
if (m.id !== targetId) return m;
|
|
1391
1644
|
const toolCalls = Array.isArray(m.toolCalls) ? [...m.toolCalls] : [];
|
|
1392
1645
|
const activityType = toolEvent.type || 'tool';
|
|
1393
|
-
const
|
|
1394
|
-
? toolCalls.findIndex((t) => t.type === activityType && t.id && t.id === toolEvent.id)
|
|
1395
|
-
: -1;
|
|
1396
|
-
const byNameRunning = toolCalls.findIndex(
|
|
1397
|
-
(t) => (t.type || 'tool') === activityType && t.name === toolEvent.name && t.status !== 'done'
|
|
1398
|
-
);
|
|
1399
|
-
const idx = byId !== -1 ? byId : byNameRunning;
|
|
1646
|
+
const idx = findActivityUpdateIndex(toolCalls, toolEvent);
|
|
1400
1647
|
|
|
1401
1648
|
if (idx === -1) {
|
|
1402
1649
|
toolCalls.push({
|
|
@@ -1414,17 +1661,13 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1414
1661
|
id: toolEvent.id || toolCalls[idx].id,
|
|
1415
1662
|
status: toolEvent.status,
|
|
1416
1663
|
...(toolEvent.durationMs !== undefined ? { durationMs: toolEvent.durationMs } : {}),
|
|
1417
|
-
...(toolEvent.summary
|
|
1664
|
+
...(toolEvent.summary
|
|
1665
|
+
? { summary: mergeActivitySummary(toolCalls[idx].summary, toolEvent.summary, toolEvent.name) }
|
|
1666
|
+
: {})
|
|
1418
1667
|
};
|
|
1419
1668
|
}
|
|
1420
1669
|
const segments = Array.isArray(m.segments) ? [...m.segments] : [];
|
|
1421
|
-
const
|
|
1422
|
-
? segments.findIndex((segment) => segment.type === activityType && segment.id === toolEvent.id)
|
|
1423
|
-
: -1;
|
|
1424
|
-
const bySegmentName = segments.findIndex(
|
|
1425
|
-
(segment) => segment.type === activityType && segment.name === toolEvent.name && segment.status !== 'done'
|
|
1426
|
-
);
|
|
1427
|
-
const segmentIdx = bySegmentId !== -1 ? bySegmentId : bySegmentName;
|
|
1670
|
+
const segmentIdx = findActivityUpdateIndex(segments, toolEvent);
|
|
1428
1671
|
const patch = {
|
|
1429
1672
|
type: activityType,
|
|
1430
1673
|
id: toolEvent.id || '',
|
|
@@ -1438,7 +1681,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1438
1681
|
} else {
|
|
1439
1682
|
segments[segmentIdx] = {
|
|
1440
1683
|
...segments[segmentIdx],
|
|
1441
|
-
...patch
|
|
1684
|
+
...patch,
|
|
1685
|
+
...(toolEvent.summary
|
|
1686
|
+
? { summary: mergeActivitySummary(segments[segmentIdx].summary, toolEvent.summary, toolEvent.name) }
|
|
1687
|
+
: {})
|
|
1442
1688
|
};
|
|
1443
1689
|
}
|
|
1444
1690
|
return { ...m, toolCalls, segments };
|
|
@@ -1853,7 +2099,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1853
2099
|
|
|
1854
2100
|
if (key.upArrow) {
|
|
1855
2101
|
if (suggestionNav && commandSuggestions.length > 0) {
|
|
1856
|
-
setMenuIndex((prev) =>
|
|
2102
|
+
setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'up'));
|
|
1857
2103
|
return;
|
|
1858
2104
|
}
|
|
1859
2105
|
if (history.length === 0) return;
|
|
@@ -1879,7 +2125,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1879
2125
|
|
|
1880
2126
|
if (key.downArrow) {
|
|
1881
2127
|
if (suggestionNav && commandSuggestions.length > 0) {
|
|
1882
|
-
setMenuIndex((prev) =>
|
|
2128
|
+
setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'down'));
|
|
1883
2129
|
return;
|
|
1884
2130
|
}
|
|
1885
2131
|
if (history.length === 0 || historyIndex === null) return;
|
|
@@ -1900,6 +2146,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1900
2146
|
return;
|
|
1901
2147
|
}
|
|
1902
2148
|
if (key.leftArrow) {
|
|
2149
|
+
if (suggestionNav && commandSuggestions.length > 0) {
|
|
2150
|
+
setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'left'));
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
1903
2153
|
setSuggestionNav(false);
|
|
1904
2154
|
const next = Math.max(0, cursorIndexRef.current - 1);
|
|
1905
2155
|
cursorIndexRef.current = next;
|
|
@@ -1907,6 +2157,10 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
1907
2157
|
return;
|
|
1908
2158
|
}
|
|
1909
2159
|
if (key.rightArrow) {
|
|
2160
|
+
if (suggestionNav && commandSuggestions.length > 0) {
|
|
2161
|
+
setMenuIndex((prev) => moveSuggestionSelection(prev, commandSuggestions.length, 'right'));
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
1910
2164
|
setSuggestionNav(false);
|
|
1911
2165
|
const next = Math.min(inputValue.length, cursorIndexRef.current + 1);
|
|
1912
2166
|
cursorIndexRef.current = next;
|
|
@@ -2118,16 +2372,21 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2118
2372
|
const hasConversationStarted = messages.some((m) =>
|
|
2119
2373
|
['you', 'coder', 'pending', 'error'].includes(m.label)
|
|
2120
2374
|
);
|
|
2121
|
-
const
|
|
2375
|
+
const baseVisibleMessages = hasConversationStarted
|
|
2122
2376
|
? messages.filter((m) => !(m.label === 'system' && m.text === startupHint))
|
|
2123
2377
|
: messages;
|
|
2378
|
+
const visibleMessages = injectPlanStateMessage(
|
|
2379
|
+
baseVisibleMessages,
|
|
2380
|
+
planState,
|
|
2381
|
+
activeUserMessageIdRef.current,
|
|
2382
|
+
activeAssistantIdRef.current
|
|
2383
|
+
);
|
|
2124
2384
|
|
|
2125
2385
|
return h(
|
|
2126
2386
|
Box,
|
|
2127
2387
|
{ flexDirection: 'column' },
|
|
2128
2388
|
h(Header, { sessionId: displaySessionId, model: displayModel, shellName }),
|
|
2129
2389
|
h(RuntimeStrip, { busy, runtimeStatus, loaderTick, copy }),
|
|
2130
|
-
h(PlanStrip, { planState, copy }),
|
|
2131
2390
|
h(MessageList, {
|
|
2132
2391
|
messages: visibleMessages,
|
|
2133
2392
|
loaderTick,
|