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.
@@ -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 保持切换模式,↑↓选择,Enter 填入',
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 切换候选,↑↓选择,Enter 填入 (${count} 项)`,
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' ? 'white' : step.status === 'failed' ? 'redBright' : 'gray' }, `${step.index}/${step.total} ${step.role}: ${step.title}`)
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 grouped = groupCommandSuggestions(commandSuggestions);
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
- ? (runtime.getCompletionOptions(inputValue) || []).slice(0, 8)
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 byId = toolEvent.id
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 ? { summary: 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 bySegmentId = toolEvent.id
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) => Math.max(0, prev - 1));
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) => Math.min(commandSuggestions.length - 1, prev + 1));
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 visibleMessages = hasConversationStarted
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,