codemini-cli 0.2.0 → 0.2.2

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.
@@ -63,6 +63,7 @@ const TUI_COPY = {
63
63
  waitingForInput: '等待输入',
64
64
  ready: '就绪',
65
65
  noMessagesYet: '还没有消息',
66
+ taskCompleted: '已完成任务',
66
67
  code: '代码',
67
68
  codeActivity: '代码活动',
68
69
  textNotes: '说明文本',
@@ -140,6 +141,10 @@ const TUI_COPY = {
140
141
  doingCodeGeneration: '正在生成代码',
141
142
  doneSkill: '已完成技能',
142
143
  doingSkill: '正在执行技能',
144
+ doneProjectIndex: '已初始化项目索引',
145
+ doingProjectIndex: '正在初始化项目索引',
146
+ doneFileIndex: '已刷新文件索引',
147
+ doingFileIndex: '正在刷新文件索引',
143
148
  toolFailed: (name) => `工具执行失败: ${name}`,
144
149
  waitingModelContinue: (detail) => `${detail},等待模型继续`,
145
150
  waitingModelAdjust: (detail) => `${detail},等待模型调整`
@@ -189,6 +194,7 @@ const TUI_COPY = {
189
194
  waitingForInput: 'waiting for input',
190
195
  ready: 'ready',
191
196
  noMessagesYet: 'No messages yet',
197
+ taskCompleted: 'Task completed',
192
198
  code: 'code',
193
199
  codeActivity: 'CODE ACTIVITY',
194
200
  textNotes: 'NOTES',
@@ -266,6 +272,10 @@ const TUI_COPY = {
266
272
  doingCodeGeneration: 'Generating code',
267
273
  doneSkill: 'Completed skill',
268
274
  doingSkill: 'Running skill',
275
+ doneProjectIndex: 'Project index initialized',
276
+ doingProjectIndex: 'Initializing project index',
277
+ doneFileIndex: 'File index refreshed',
278
+ doingFileIndex: 'Refreshing file index',
269
279
  toolFailed: (name) => `Tool failed: ${name}`,
270
280
  waitingModelContinue: (detail) => `${detail}, waiting for model to continue`,
271
281
  waitingModelAdjust: (detail) => `${detail}, waiting for model to adjust`
@@ -431,6 +441,12 @@ function getActivityDisplayParts(activity) {
431
441
  secondary: `(${activity?.name || 'unknown'})`
432
442
  };
433
443
  }
444
+ if ((activity?.type || 'tool') === 'system_tool') {
445
+ return {
446
+ primary: 'Index',
447
+ secondary: parsed.target ? `(${parsed.target})` : parsed.base ? `(${parsed.base})` : ''
448
+ };
449
+ }
434
450
  const labels = {
435
451
  read: 'Read',
436
452
  edit: 'Edit',
@@ -455,8 +471,32 @@ function getActivityDisplayParts(activity) {
455
471
  };
456
472
  }
457
473
 
474
+ export function isIndexSystemToolName(name) {
475
+ const parsed = parseToolDisplayName(name);
476
+ return parsed.base === 'project_index' || parsed.base === 'file_index';
477
+ }
478
+
479
+ export function shouldShowCompletionFooter(msg) {
480
+ return Boolean(msg && msg.label === 'coder' && !msg.loading && !(msg.phase || '').trim());
481
+ }
482
+
458
483
  function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
459
484
  const parsed = parseToolDisplayName(name);
485
+ if (parsed.base === 'project_index') {
486
+ return blocked
487
+ ? `${copy.toolActivity.blocked}: project index`
488
+ : done
489
+ ? copy.toolActivity.doneProjectIndex
490
+ : copy.toolActivity.doingProjectIndex;
491
+ }
492
+ if (parsed.base === 'file_index') {
493
+ const safeTarget = trimText(parsed.target || '.codemini-project/file-index.json', 72);
494
+ return blocked
495
+ ? `${copy.toolActivity.blocked}: ${safeTarget}`
496
+ : done
497
+ ? `${copy.toolActivity.doneFileIndex}: ${safeTarget}`
498
+ : `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
499
+ }
460
500
  if (parsed.base === 'run' || parsed.base === 'start_service') {
461
501
  const intent = classifyCommandIntent(parsed.target);
462
502
  const target = parsed.target || intent.kind || 'command';
@@ -765,8 +805,11 @@ function PlanStrip({ planState, copy }) {
765
805
  );
766
806
  }
767
807
 
768
- function Header({ sessionId, model, shellName }) {
808
+ function Header({ sessionId, model, shellName, safeMode = true }) {
769
809
  const shortSession = String(sessionId || '').slice(-12) || '-';
810
+ const modeValue = safeMode ? 'SAFE' : 'OPEN';
811
+ const modeColor = safeMode ? 'greenBright' : 'redBright';
812
+ const modeTextColor = safeMode ? 'black' : 'white';
770
813
  return h(
771
814
  Box,
772
815
  { width: '100%', justifyContent: 'center', marginTop: 1, marginBottom: 2 },
@@ -781,12 +824,6 @@ function Header({ sessionId, model, shellName }) {
781
824
  alignItems: 'center',
782
825
  minWidth: 88
783
826
  },
784
- h(
785
- Box,
786
- { width: '100%', justifyContent: 'space-between', marginBottom: 1 },
787
- h(Text, { color: 'cyan' }, 'CLI'),
788
- h(Text, { color: 'greenBright' }, 'SAFE')
789
- ),
790
827
  ...BANNER.map((line, idx) =>
791
828
  h(
792
829
  Box,
@@ -801,8 +838,9 @@ function Header({ sessionId, model, shellName }) {
801
838
  Box,
802
839
  { flexDirection: 'row', justifyContent: 'center' },
803
840
  h(StatusPill, { label: 'MODEL', value: model, color: 'cyanBright', textColor: 'black' }),
804
- h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: 'greenBright', textColor: 'black' }),
805
- h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' })
841
+ h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: 'yellowBright', textColor: 'black' }),
842
+ h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' }),
843
+ h(StatusPill, { label: 'MODE', value: modeValue, color: modeColor, textColor: modeTextColor })
806
844
  )
807
845
  )
808
846
  );
@@ -878,9 +916,16 @@ export function parsePlanProgressLine(text) {
878
916
  };
879
917
  }
880
918
 
881
- function getTailPreviewLines(text, maxLines = 3) {
919
+ function getTailPreviewWindow(text, maxLines = 3) {
882
920
  const source = String(text || '');
883
- if (!source.trim()) return [];
921
+ if (!source.trim()) return { lines: [], startLine: 1 };
922
+ const sliceTail = (lines) => {
923
+ const tail = lines.slice(-Math.max(1, maxLines));
924
+ return {
925
+ lines: tail,
926
+ startLine: Math.max(1, lines.length - tail.length + 1)
927
+ };
928
+ };
884
929
 
885
930
  const lines = source.split('\n').map((line) => line.replace(/\r$/, ''));
886
931
  let insideFence = false;
@@ -908,21 +953,21 @@ function getTailPreviewLines(text, maxLines = 3) {
908
953
  if (insideFence) {
909
954
  const codeLines = fenceLines.filter((line) => line.trim().length > 0);
910
955
  if (codeLines.length > 0) {
911
- return codeLines.slice(-Math.max(1, maxLines));
956
+ return sliceTail(codeLines);
912
957
  }
913
958
  }
914
959
 
915
960
  const closedFenceLines = latestClosedFenceLines.filter((line) => line.trim().length > 0);
916
961
  if (closedFenceLines.length > 0) {
917
- return closedFenceLines.slice(-Math.max(1, maxLines));
962
+ return sliceTail(closedFenceLines);
918
963
  }
919
964
 
920
965
  const tailLines = source
921
966
  .split('\n')
922
967
  .map((line) => line.replace(/\r$/, ''))
923
968
  .filter((line) => line.trim().length > 0);
924
- if (tailLines.length === 0) return [];
925
- return tailLines.slice(-Math.max(1, maxLines));
969
+ if (tailLines.length === 0) return { lines: [], startLine: 1 };
970
+ return sliceTail(tailLines);
926
971
  }
927
972
 
928
973
  function collectPreviewStrings(value, out = []) {
@@ -954,7 +999,6 @@ function collectPreviewStrings(value, out = []) {
954
999
  function extractPreviewTextFromRawArguments(raw) {
955
1000
  const source = String(raw || '');
956
1001
  if (!source.trim()) return '';
957
-
958
1002
  const contentMatch = source.match(/"(content|new_content|new_text|patch|code|body|script|source|value)"\s*:\s*"([\s\S]*)$/);
959
1003
  if (!contentMatch) return '';
960
1004
 
@@ -962,7 +1006,7 @@ function extractPreviewTextFromRawArguments(raw) {
962
1006
  .replace(/\\n/g, '\n')
963
1007
  .replace(/\\"/g, '"')
964
1008
  .replace(/\\\\/g, '\\')
965
- .replace(/",?\s*$/g, '')
1009
+ .replace(/"\s*[,}\]]*\s*$/g, '')
966
1010
  .trim();
967
1011
  }
968
1012
 
@@ -973,40 +1017,76 @@ function compactPreviewLine(line, maxChars = 56) {
973
1017
  return `${text.slice(0, Math.max(1, maxChars - 3))}...`;
974
1018
  }
975
1019
 
1020
+ function extractPreviewLinesFromTool(tool, maxLines = 3) {
1021
+ const previewSource =
1022
+ typeof tool?.arguments === 'string'
1023
+ ? (() => {
1024
+ const parsedArguments = safeJsonParse(tool.arguments);
1025
+ if (parsedArguments) {
1026
+ const parsedPreview = collectPreviewStrings(parsedArguments);
1027
+ if (parsedPreview.length > 0) return parsedPreview;
1028
+ }
1029
+ const rawArgumentPreview = extractPreviewTextFromRawArguments(tool.arguments);
1030
+ return rawArgumentPreview ? [rawArgumentPreview] : [];
1031
+ })()
1032
+ : collectPreviewStrings(tool?.arguments || tool?.content || tool?.summary || []);
1033
+
1034
+ if (previewSource.length === 0) return { lines: [], startLine: 1 };
1035
+ const combined = previewSource.join('\n');
1036
+ const previewWindow = getTailPreviewWindow(combined, maxLines);
1037
+ return {
1038
+ lines: previewWindow.lines.map((line) => compactPreviewLine(line)),
1039
+ startLine: previewWindow.startLine
1040
+ };
1041
+ }
1042
+
976
1043
  function getLatestToolPreviewLines(msg, maxLines = 3) {
977
- const toolCalls = [
978
- ...(Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : []),
979
- ...(Array.isArray(msg?.toolCalls) ? msg.toolCalls : [])
980
- ];
981
1044
  const codeTools = new Set(['edit', 'write', 'patch', 'generate_diff']);
982
- for (let index = toolCalls.length - 1; index >= 0; index -= 1) {
983
- const tool = toolCalls[index];
984
- const parsed = parseToolDisplayName(tool?.name);
985
- if (!codeTools.has(parsed.base)) continue;
986
- const rawArgumentPreview =
987
- typeof tool?.arguments === 'string' ? extractPreviewTextFromRawArguments(tool.arguments) : '';
988
- const previewSource = rawArgumentPreview
989
- ? [rawArgumentPreview]
990
- : collectPreviewStrings(tool?.arguments || tool?.content || tool?.summary || []);
991
- if (previewSource.length === 0) continue;
992
- const combined = previewSource.join('\n');
993
- const previewLines = getTailPreviewLines(combined, maxLines);
994
- if (previewLines.length > 0) return previewLines.map((line) => compactPreviewLine(line));
1045
+ const extractFromCalls = (calls) => {
1046
+ for (let index = calls.length - 1; index >= 0; index -= 1) {
1047
+ const tool = calls[index];
1048
+ const parsed = parseToolDisplayName(tool?.name);
1049
+ if (!codeTools.has(parsed.base)) continue;
1050
+ const previewWindow = extractPreviewLinesFromTool(tool, maxLines);
1051
+ if (previewWindow.lines.length > 0) return previewWindow;
1052
+ }
1053
+ return { lines: [], startLine: 1 };
1054
+ };
1055
+
1056
+ const pendingCodeCalls = (Array.isArray(msg?.pendingToolCalls) ? msg.pendingToolCalls : []).filter((tool) =>
1057
+ codeTools.has(parseToolDisplayName(tool?.name).base)
1058
+ );
1059
+ if (pendingCodeCalls.length > 0) {
1060
+ const latestPendingPreview = extractPreviewLinesFromTool(pendingCodeCalls.at(-1), maxLines);
1061
+ if (latestPendingPreview.lines.length > 0) return latestPendingPreview;
1062
+ return { lines: [], startLine: 1 };
995
1063
  }
996
- return [];
1064
+
1065
+ const toolCalls = (Array.isArray(msg?.toolCalls) ? msg.toolCalls : []).filter(
1066
+ (tool) => tool?.status === 'running' && codeTools.has(parseToolDisplayName(tool?.name).base)
1067
+ );
1068
+ if (toolCalls.length > 0) {
1069
+ return extractFromCalls(toolCalls);
1070
+ }
1071
+
1072
+ return { lines: [], startLine: 1 };
997
1073
  }
998
1074
 
999
1075
  export function getGeneratingCodePlaceholderRows(msg, copy, contentWidth = 72) {
1000
1076
  const liveStatus = String(msg?.liveStatus || '').trim();
1001
1077
  if (!msg?.loading || (msg?.phase !== 'generating' && msg?.phase !== 'tooling')) return [];
1002
- if (liveStatus !== String(copy?.runtime?.generatingCode || '').trim()) return [];
1003
1078
 
1004
- const previewLines = getLatestToolPreviewLines(msg, 3);
1005
- if (previewLines.length === 0) return [];
1079
+ const previewWindow = getLatestToolPreviewLines(msg, 3);
1080
+ if (previewWindow.lines.length === 0) return [];
1081
+ const hasRunningCodeTool = (Array.isArray(msg?.toolCalls) ? msg.toolCalls : []).some(
1082
+ (tool) => tool?.status === 'running' && new Set(['edit', 'write', 'patch', 'generate_diff']).has(parseToolDisplayName(tool?.name).base)
1083
+ );
1084
+ const isCodeGenerationStatus = liveStatus === String(copy?.runtime?.generatingCode || '').trim();
1085
+ if (!isCodeGenerationStatus && !(msg?.phase === 'tooling' && hasRunningCodeTool)) return [];
1006
1086
 
1007
- return previewLines.map((line, idx) => ({
1087
+ return previewWindow.lines.map((line, idx) => ({
1008
1088
  kind: 'code-placeholder',
1009
- lineNo: idx + 1,
1089
+ lineNo: previewWindow.startLine + idx,
1010
1090
  text: line,
1011
1091
  color: 'gray'
1012
1092
  }));
@@ -1321,7 +1401,7 @@ export function insertRowsAfterLastCodeRow(rows, extraRows) {
1321
1401
 
1322
1402
  let insertIndex = -1;
1323
1403
  for (let index = source.length - 1; index >= 0; index -= 1) {
1324
- if (source[index]?.kind === 'code') {
1404
+ if (source[index]?.kind === 'code' || source[index]?.kind === 'code-placeholder') {
1325
1405
  insertIndex = index + 1;
1326
1406
  break;
1327
1407
  }
@@ -1498,10 +1578,18 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
1498
1578
  };
1499
1579
 
1500
1580
  if (Array.isArray(msg?.segments) && msg.segments.length > 0) {
1501
- const totalTools = msg.segments.filter((segment) => segment.type === 'tool' || segment.type === 'skill').length;
1581
+ const totalTools = msg.segments.filter(
1582
+ (segment) =>
1583
+ segment.type === 'tool' ||
1584
+ segment.type === 'skill' ||
1585
+ (segment.type === 'system_tool' && (showToolDetails || !isIndexSystemToolName(segment.name)))
1586
+ ).length;
1502
1587
  let toolIndex = 0;
1503
1588
  for (const segment of msg.segments) {
1504
- if (segment.type === 'tool' || segment.type === 'skill') {
1589
+ if (segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool') {
1590
+ if (segment.type === 'system_tool' && !showToolDetails && isIndexSystemToolName(segment.name)) {
1591
+ continue;
1592
+ }
1505
1593
  pushActivityRows(segment, toolIndex, totalTools);
1506
1594
  toolIndex += 1;
1507
1595
  } else {
@@ -1548,6 +1636,10 @@ function renderMessageRow(msg, row, idx, loaderTick) {
1548
1636
  ? row.status === 'error'
1549
1637
  ? 'redBright'
1550
1638
  : 'cyanBright'
1639
+ : activity.type === 'system_tool'
1640
+ ? row.status === 'error' || row.status === 'blocked'
1641
+ ? 'redBright'
1642
+ : 'blueBright'
1551
1643
  : row.status === 'error' || row.status === 'blocked'
1552
1644
  ? 'redBright'
1553
1645
  : 'cyanBright';
@@ -1783,7 +1875,14 @@ function MessageBubble({ msg, loaderTick, showToolDetails, rowWindow = null, con
1783
1875
  ? h(Text, { color: 'blueBright' }, autoSkillBadge)
1784
1876
  : h(Text, { color: theme.chrome }, ' ')
1785
1877
  ),
1786
- ...rendered
1878
+ ...rendered,
1879
+ shouldShowCompletionFooter(msg)
1880
+ ? h(
1881
+ Box,
1882
+ { marginTop: 1, marginLeft: 1, key: `row-completion-${msg.id}` },
1883
+ h(Text, { color: 'gray', dimColor: true }, copy.generic.taskCompleted)
1884
+ )
1885
+ : null
1787
1886
  )
1788
1887
  );
1789
1888
  }
@@ -2017,7 +2116,7 @@ function makeIdleStatus(copy, snapshot, variant = 'ready') {
2017
2116
  );
2018
2117
  }
2019
2118
 
2020
- export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '' }) {
2119
+ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '', safeMode = true }) {
2021
2120
  const copy = getCopy(language);
2022
2121
  const stdoutCols = Number(process.stdout?.columns || 120);
2023
2122
  const [inputValue, setInputValue] = useState('');
@@ -2060,6 +2159,23 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2060
2159
  const messagesRef = useRef([]);
2061
2160
  const pendingQueueRef = useRef([]);
2062
2161
  const deltaBufferRef = useRef('');
2162
+
2163
+ useEffect(() => {
2164
+ const rawStartupActivities = runtime.consumeStartupEvents?.();
2165
+ const startupActivities = Array.isArray(rawStartupActivities) ? rawStartupActivities : [];
2166
+ if (startupActivities.length === 0) return;
2167
+ setMessages((prev) => [
2168
+ ...prev,
2169
+ {
2170
+ id: nextId(),
2171
+ label: 'system',
2172
+ text: '',
2173
+ color: 'yellowBright',
2174
+ toolCalls: startupActivities,
2175
+ segments: startupActivities
2176
+ }
2177
+ ]);
2178
+ }, [runtime]);
2063
2179
  const deltaFlushTimerRef = useRef(null);
2064
2180
  const escSeqRef = useRef('');
2065
2181
  const planTextBufferRef = useRef('');
@@ -2191,6 +2307,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2191
2307
  prev.map((m) => {
2192
2308
  if (m.id !== targetId) return m;
2193
2309
  const toolCalls = Array.isArray(m.toolCalls) ? [...m.toolCalls] : [];
2310
+ const pendingToolCalls = Array.isArray(m.pendingToolCalls) ? [...m.pendingToolCalls] : [];
2194
2311
  const activityType = toolEvent.type || 'tool';
2195
2312
  const idx = findActivityUpdateIndex(toolCalls, toolEvent);
2196
2313
  const startedAt = toolEvent.status === 'running' ? Date.now() : undefined;
@@ -2243,7 +2360,16 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2243
2360
  : {})
2244
2361
  };
2245
2362
  }
2246
- return { ...m, toolCalls, segments };
2363
+ const shouldClearPending =
2364
+ activityType === 'tool' && ['running', 'done', 'blocked', 'error'].includes(toolEvent.status);
2365
+ const nextPendingToolCalls = shouldClearPending
2366
+ ? pendingToolCalls.filter((entry) => {
2367
+ if (!entry) return false;
2368
+ if (toolEvent.id && entry.id) return entry.id !== toolEvent.id;
2369
+ return parseToolDisplayName(entry.name).base !== parseToolDisplayName(toolEvent.name).base;
2370
+ })
2371
+ : pendingToolCalls;
2372
+ return { ...m, toolCalls, segments, pendingToolCalls: nextPendingToolCalls };
2247
2373
  })
2248
2374
  );
2249
2375
  };
@@ -2582,6 +2708,54 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2582
2708
  arguments: event.arguments
2583
2709
  });
2584
2710
  }
2711
+ if (event?.type === 'system_tool:start') {
2712
+ ensureActiveAssistant();
2713
+ const detail = describeToolActivity(event.name, copy);
2714
+ setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'blueBright'));
2715
+ setInputStage('tooling');
2716
+ updateActivityStatusOnActiveAssistant({
2717
+ type: 'system_tool',
2718
+ id: event.id,
2719
+ name: event.name,
2720
+ status: 'running',
2721
+ summary: event.summary
2722
+ });
2723
+ setActiveAssistantMeta({ loading: true, phase: 'tooling', liveStatus: detail });
2724
+ }
2725
+ if (event?.type === 'system_tool:end') {
2726
+ const detail = describeToolActivity(event.name, copy, { done: true });
2727
+ setRuntimeStatus(makeStatus(copy.runtime.toolCompleted, copy.toolActivity.waitingModelContinue(detail), 'blueBright'));
2728
+ setInputStage('thinking');
2729
+ updateActivityStatusOnActiveAssistant({
2730
+ type: 'system_tool',
2731
+ id: event.id,
2732
+ name: event.name,
2733
+ status: 'done',
2734
+ summary: event.summary
2735
+ });
2736
+ setActiveAssistantMeta({
2737
+ loading: true,
2738
+ phase: 'thinking',
2739
+ liveStatus: copy.toolActivity.waitingModelContinue(detail)
2740
+ });
2741
+ }
2742
+ if (event?.type === 'system_tool:error') {
2743
+ const detail = copy.toolActivity.toolFailed(event.name);
2744
+ setRuntimeStatus(makeStatus(copy.runtime.toolFailed, event.summary || detail, 'redBright'));
2745
+ setInputStage('thinking');
2746
+ updateActivityStatusOnActiveAssistant({
2747
+ type: 'system_tool',
2748
+ id: event.id,
2749
+ name: event.name,
2750
+ status: 'error',
2751
+ summary: event.summary
2752
+ });
2753
+ setActiveAssistantMeta({
2754
+ loading: true,
2755
+ phase: 'thinking',
2756
+ liveStatus: copy.toolActivity.waitingModelAdjust(detail)
2757
+ });
2758
+ }
2585
2759
  if (event?.type === 'skill:start') {
2586
2760
  ensureActiveAssistant();
2587
2761
  const detail = describeSkillActivity(event.name, copy);
@@ -3087,7 +3261,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3087
3261
  return h(
3088
3262
  Box,
3089
3263
  { flexDirection: 'column' },
3090
- h(Header, { sessionId: displaySessionId, model: displayModel, shellName }),
3264
+ h(Header, { sessionId: displaySessionId, model: displayModel, shellName, safeMode }),
3091
3265
  h(MessageList, {
3092
3266
  messages: visibleMessages,
3093
3267
  loaderTick,