codemini-cli 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/core/tools.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  } from './shell.js';
15
15
  import { evaluateCommandPolicy } from './command-policy.js';
16
16
  import { queryAst, readAstNode, resolveAstTarget } from './ast.js';
17
- import { initializeProjectIndex, refreshIndexedFile } from './project-index.js';
17
+ import { initializeProjectIndex, queryProjectIndex, refreshIndexedFile } from './project-index.js';
18
18
  import { checkReadDedup } from './agent-loop.js';
19
19
 
20
20
  const SKIP_DIRS = new Set(['.git', 'node_modules', '.codemini', '.codemini-global', 'dist', 'coverage']);
@@ -1928,6 +1928,24 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
1928
1928
  }
1929
1929
  }
1930
1930
  },
1931
+ {
1932
+ type: 'function',
1933
+ function: {
1934
+ name: 'query_project_index',
1935
+ description:
1936
+ 'Query the lightweight project index before broad file reads. Uses both project-map metadata and file-index symbols to suggest the most relevant files for the current task.',
1937
+ parameters: {
1938
+ type: 'object',
1939
+ properties: {
1940
+ query: { type: 'string', description: 'Task or code search phrase such as "login auth" or "tui presenters"' },
1941
+ path: { type: 'string', description: 'Optional path prefix like src or src/auth to narrow results' },
1942
+ path_prefix: { type: 'string', description: 'Alias for path' },
1943
+ language: { type: 'string', description: 'Optional language filter such as ts, js, python, or go' },
1944
+ max_results: { type: 'number', description: 'Max result files to return' }
1945
+ }
1946
+ }
1947
+ }
1948
+ },
1931
1949
  {
1932
1950
  type: 'function',
1933
1951
  function: {
@@ -2182,6 +2200,10 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2182
2200
  if (readPath) lastReadPath = readPath;
2183
2201
  return result;
2184
2202
  }),
2203
+ query_project_index: async (args) => {
2204
+ await ensureProjectIndex();
2205
+ return queryProjectIndex(workspaceRoot, args);
2206
+ },
2185
2207
  grep: (args) => grep(workspaceRoot, args),
2186
2208
  glob: (args) => glob(workspaceRoot, args),
2187
2209
  list: (args) => list(workspaceRoot, args),
@@ -2311,6 +2333,33 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
2311
2333
  return `${header}\n${dirs.join('\n')}${dirs.length && files.length ? '\n' : ''}${files.join('\n')}`;
2312
2334
  },
2313
2335
 
2336
+ query_project_index(result) {
2337
+ if (!result || typeof result !== 'object') return String(result);
2338
+ const lines = [];
2339
+ if (result.query) lines.push(`[project_index: "${result.query}"]`);
2340
+ if (result.project_root) lines.push(`project_root: ${result.project_root}`);
2341
+ const projectMap = result.project_map;
2342
+ if (projectMap) {
2343
+ lines.push(`languages: ${(projectMap.languages || []).join(', ') || 'unknown'}`);
2344
+ lines.push(`source_roots: ${(projectMap.source_roots || []).join(', ') || 'none'}`);
2345
+ lines.push(`test_roots: ${(projectMap.test_roots || []).join(', ') || 'none'}`);
2346
+ lines.push(`entry_candidates: ${(projectMap.entry_candidates || []).join(', ') || 'none'}`);
2347
+ lines.push(`framework_hints: ${(projectMap.framework_hints || []).join(', ') || 'none'}`);
2348
+ }
2349
+ const matches = Array.isArray(result.matches) ? result.matches : [];
2350
+ if (matches.length === 0) {
2351
+ lines.push('No indexed file matches found.');
2352
+ return lines.join('\n');
2353
+ }
2354
+ lines.push('matches:');
2355
+ for (const item of matches) {
2356
+ lines.push(
2357
+ `- ${item.file} [score=${item.score}] exports=[${(item.exports || []).join(', ')}] functions=[${(item.functions || []).join(', ')}] classes=[${(item.classes || []).join(', ')}]`
2358
+ );
2359
+ }
2360
+ return lines.join('\n');
2361
+ },
2362
+
2314
2363
  edit(result) {
2315
2364
  if (!result || typeof result !== 'object') return String(result);
2316
2365
  const p = result.path || '';
@@ -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;
@@ -675,33 +689,8 @@ function parseToolDisplayName(name) {
675
689
  };
676
690
  }
677
691
 
678
- function isCodeGenerationActivityName(name) {
679
- return String(name || '').trim() === 'Code generation';
680
- }
681
-
682
692
  export function buildPreToolNotice(name, copy) {
683
- const parsed = parseToolDisplayName(name);
684
- const base = parsed.base;
685
- const target = parsed.target ? trimText(parsed.target, 48) : '';
686
- const isEnglish = String(copy?.roleLabels?.coder || '').trim() === 'CODER' && String(copy?.roleLabels?.you || '').trim() === 'YOU';
687
-
688
- if (isEnglish) {
689
- if (base === 'read') return target ? `I'll inspect ${target} first.` : `I'll inspect the relevant file first.`;
690
- if (base === 'list' || base === 'glob') return target ? `I'll inspect the ${target} directory first.` : `I'll inspect the relevant directory first.`;
691
- if (base === 'grep') return `I'll search the relevant code first.`;
692
- if (base === 'edit' || base === 'write' || base === 'patch' || base === 'generate_diff') {
693
- return `I'll inspect the current code first, then make the change.`;
694
- }
695
- if (base === 'run') return `I'll verify the current project state first.`;
696
- return `I'll check the relevant project context first.`;
697
- }
698
-
699
- if (base === 'read') return target ? `我先查看 ${target} 的内容。` : '我先查看相关文件内容。';
700
- if (base === 'list' || base === 'glob') return target ? `我先查看 ${target} 目录里的内容。` : '我先查看相关目录内容。';
701
- if (base === 'grep') return '我先搜索相关代码位置。';
702
- if (base === 'edit' || base === 'write' || base === 'patch' || base === 'generate_diff') return '我先确认当前代码上下文,再动手修改。';
703
- if (base === 'run') return '我先检查当前项目状态。';
704
- return '我先查看相关上下文。';
693
+ return buildRegisteredPreToolNotice(name, copy);
705
694
  }
706
695
 
707
696
  export function shouldInjectPreToolNotice(msg) {
@@ -712,6 +701,33 @@ export function shouldInjectPreToolNotice(msg) {
712
701
  return !text && !hasTextSegment;
713
702
  }
714
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
+
715
731
  function formatDurationMs(ms) {
716
732
  const safeMs = Math.max(0, Number(ms) || 0);
717
733
  return `${(safeMs / 1000).toFixed(1)}s`;
@@ -818,168 +834,19 @@ export function shouldShowCompletionFooter(msg) {
818
834
  }
819
835
 
820
836
  function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
821
- const parsed = parseToolDisplayName(name);
822
- if (parsed.base === 'project_index') {
823
- return blocked
824
- ? `${copy.toolActivity.blocked}: project index`
825
- : done
826
- ? copy.toolActivity.doneProjectIndex
827
- : copy.toolActivity.doingProjectIndex;
828
- }
829
- if (parsed.base === 'file_index') {
830
- const safeTarget = trimText(parsed.target || '.codemini-project/file-index.json', 72);
831
- return blocked
832
- ? `${copy.toolActivity.blocked}: ${safeTarget}`
833
- : done
834
- ? `${copy.toolActivity.doneFileIndex}: ${safeTarget}`
835
- : `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
836
- }
837
- if (parsed.base === 'run' || parsed.base === 'start_service') {
838
- const intent = classifyCommandIntent(parsed.target);
839
- const target = parsed.target || intent.kind || 'command';
840
- if (intent.kind === 'install') {
841
- return blocked
842
- ? `${copy.toolActivity.blocked}: ${target}`
843
- : done
844
- ? `${copy.toolActivity.doneInstall}: ${target}`
845
- : `${copy.toolActivity.doingInstall}: ${target}`;
846
- }
847
- if (intent.kind === 'build') {
848
- return blocked
849
- ? `${copy.toolActivity.blocked}: ${target}`
850
- : done
851
- ? `${copy.toolActivity.doneBuild}: ${target}`
852
- : `${copy.toolActivity.doingBuild}: ${target}`;
853
- }
854
- if (intent.kind === 'test') {
855
- return blocked
856
- ? `${copy.toolActivity.blocked}: ${target}`
857
- : done
858
- ? `${copy.toolActivity.doneTest}: ${target}`
859
- : `${copy.toolActivity.doingTest}: ${target}`;
860
- }
861
- if (intent.kind === 'frontend-service') {
862
- return blocked
863
- ? `${copy.toolActivity.blocked}: ${target}`
864
- : done
865
- ? `${copy.toolActivity.doneFrontend}: ${target}`
866
- : `${copy.toolActivity.doingFrontend}: ${target}`;
867
- }
868
- if (intent.kind === 'backend-service') {
869
- return blocked
870
- ? `${copy.toolActivity.blocked}: ${target}`
871
- : done
872
- ? `${copy.toolActivity.doneBackend}: ${target}`
873
- : `${copy.toolActivity.doingBackend}: ${target}`;
874
- }
875
- if (intent.kind === 'database-service') {
876
- return blocked
877
- ? `${copy.toolActivity.blocked}: ${target}`
878
- : done
879
- ? `${copy.toolActivity.doneDatabase}: ${target}`
880
- : `${copy.toolActivity.doingDatabase}: ${target}`;
881
- }
882
- if (intent.kind === 'docker-service') {
883
- return blocked
884
- ? `${copy.toolActivity.blocked}: ${target}`
885
- : done
886
- ? `${copy.toolActivity.doneDocker}: ${target}`
887
- : `${copy.toolActivity.doingDocker}: ${target}`;
888
- }
889
- if (intent.kind === 'service') {
890
- return blocked
891
- ? `${copy.toolActivity.blocked}: ${target}`
892
- : done
893
- ? `${copy.toolActivity.doneGeneric}: ${target}`
894
- : `${copy.toolActivity.doingGeneric}: ${target}`;
895
- }
896
- }
897
- if (isCodeGenerationActivityName(name)) {
898
- return blocked
899
- ? `${copy.toolActivity.blocked}: code generation`
900
- : done
901
- ? copy.toolActivity.doneCodeGeneration
902
- : copy.toolActivity.doingCodeGeneration;
903
- }
904
- const { raw, base, target } = parseToolDisplayName(name);
905
- const safeTarget = trimText(target, 72);
906
- if (base === 'read') {
907
- return blocked
908
- ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
909
- : done
910
- ? `${copy.toolActivity.doneRead}: ${safeTarget || '.'}`
911
- : `${copy.toolActivity.doingRead}: ${safeTarget || '.'}`;
912
- }
913
- if (base === 'edit') {
914
- return blocked
915
- ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
916
- : done
917
- ? `${copy.toolActivity.doneEdit}: ${safeTarget || '.'}`
918
- : `${copy.toolActivity.doingEdit}: ${safeTarget || '.'}`;
919
- }
920
- if (base === 'write') {
921
- return blocked
922
- ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
923
- : done
924
- ? `${copy.toolActivity.doneWrite}: ${safeTarget || '.'}`
925
- : `${copy.toolActivity.doingWrite}: ${safeTarget || '.'}`;
926
- }
927
- if (base === 'patch') {
928
- return blocked
929
- ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
930
- : done
931
- ? `${copy.toolActivity.donePatch}: ${safeTarget || '.'}`
932
- : `${copy.toolActivity.doingPatch}: ${safeTarget || '.'}`;
933
- }
934
- if (base === 'list' || base === 'glob' || base === 'grep') {
935
- return blocked
936
- ? `${copy.toolActivity.blocked}: ${base}(${safeTarget || '.'})`
937
- : done
938
- ? `${copy.toolActivity.doneList}: ${safeTarget || '.'}`
939
- : `${copy.toolActivity.doingList}: ${safeTarget || '.'}`;
940
- }
941
- if (base === 'run') {
942
- return blocked
943
- ? `${copy.toolActivity.blocked}: ${safeTarget || base}`
944
- : done
945
- ? `${copy.toolActivity.doneCommand}: ${safeTarget || base}`
946
- : `${copy.toolActivity.doingCommand}: ${safeTarget || base}`;
947
- }
948
- if (base === 'start_service' || base === 'list_services' || base === 'get_service_status' || base === 'get_service_logs' || base === 'stop_service') {
949
- return blocked
950
- ? `${copy.toolActivity.blocked}: ${safeTarget || base}`
951
- : done
952
- ? `${copy.toolActivity.doneGeneric}: ${safeTarget || base}`
953
- : `${copy.toolActivity.doingGeneric}: ${safeTarget || base}`;
954
- }
955
- if (base === 'create_task') {
956
- return blocked ? `${copy.toolActivity.blocked}: create_task` : done ? copy.toolActivity.doneCreateTask : copy.toolActivity.doingCreateTask;
957
- }
958
- if (base === 'update_task') {
959
- return blocked ? `${copy.toolActivity.blocked}: update_task` : done ? copy.toolActivity.doneUpdateTask : copy.toolActivity.doingUpdateTask;
960
- }
961
- return blocked ? `${copy.toolActivity.blocked}: ${raw}` : done ? `${copy.toolActivity.doneGeneric}: ${raw}` : `${copy.toolActivity.doingGeneric}: ${raw}`;
837
+ return describeRegisteredToolActivity(copy, name, { done, blocked });
962
838
  }
963
839
 
964
840
  function describeSkillActivity(name, copy, { done = false, failed = false } = {}) {
965
- if (failed) return `${copy.runtime.skillFailed}: /${name}`;
966
- if (done) return `${copy.toolActivity.doneSkill}: /${name}`;
967
- return `${copy.toolActivity.doingSkill}: /${name}`;
841
+ return describeRegisteredSkillActivity(copy, name, { done, failed });
968
842
  }
969
843
 
970
844
  function describeAutoSkillActivity(names, copy) {
971
- const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
972
- if (safeNames.length === 0) return '';
973
- return copy.runtime.autoSkillInjected(safeNames);
845
+ return describeRegisteredAutoSkillActivity(copy, names);
974
846
  }
975
847
 
976
848
  function formatAutoSkillBadge(names, copy) {
977
- const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
978
- if (safeNames.length === 0) return '';
979
- const [first, ...rest] = safeNames;
980
- const suffix = rest.length > 0 ? ` +${rest.length}` : '';
981
- const prefix = copy?.roleLabels?.system === 'SYSTEM' ? 'AUTO' : '自动';
982
- return `${prefix} /${first}${suffix}`;
849
+ return formatRegisteredAutoSkillBadge(copy, names);
983
850
  }
984
851
 
985
852
  function normalizeRuntimeStatus(status, copy) {
@@ -2967,6 +2834,26 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
2967
2834
  return aid;
2968
2835
  };
2969
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
+
2970
2857
  const runSubmission = (line, userMessageId = null) => {
2971
2858
  inFlightRef.current = true;
2972
2859
  activeUserMessageIdRef.current = userMessageId;
@@ -3060,9 +2947,15 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3060
2947
  setMessages((prev) =>
3061
2948
  prev.map((m) => {
3062
2949
  if (m.id !== targetId) return m;
2950
+ const responseText = typeof event.text === 'string' ? event.text.trim() : '';
2951
+ const shouldSynthesizeCompletion = !responseText && m.syntheticPrelude;
3063
2952
  return {
3064
2953
  ...m,
3065
- ...(typeof event.text === 'string' && event.text.length > 0 ? { text: event.text } : {}),
2954
+ ...(responseText
2955
+ ? { text: event.text, syntheticPrelude: false }
2956
+ : shouldSynthesizeCompletion
2957
+ ? { text: buildSyntheticCompletionText(m, copy), syntheticPrelude: false }
2958
+ : {}),
3066
2959
  loading: false,
3067
2960
  phase: undefined,
3068
2961
  liveStatus: undefined,
@@ -3086,6 +2979,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
3086
2979
  }
3087
2980
  if (event?.type === 'tool:start') {
3088
2981
  ensureActiveAssistant();
2982
+ maybeRefreshSyntheticNarration(event.name);
3089
2983
  const detail = describeToolActivity(event.name, copy);
3090
2984
  setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'magentaBright'));
3091
2985
  setInputStage('tooling');
@@ -0,0 +1,20 @@
1
+ export function describeSkillActivity(copy, name, { done = false, failed = false } = {}) {
2
+ if (failed) return `${copy.runtime.skillFailed}: /${name}`;
3
+ if (done) return `${copy.toolActivity.doneSkill}: /${name}`;
4
+ return `${copy.toolActivity.doingSkill}: /${name}`;
5
+ }
6
+
7
+ export function describeAutoSkillActivity(copy, names) {
8
+ const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
9
+ if (safeNames.length === 0) return '';
10
+ return copy.runtime.autoSkillInjected(safeNames);
11
+ }
12
+
13
+ export function formatAutoSkillBadge(copy, names) {
14
+ const safeNames = Array.isArray(names) ? names.filter(Boolean) : [];
15
+ if (safeNames.length === 0) return '';
16
+ const [first, ...rest] = safeNames;
17
+ const suffix = rest.length > 0 ? ` +${rest.length}` : '';
18
+ const prefix = copy?.roleLabels?.system === 'SYSTEM' ? 'AUTO' : '自动';
19
+ return `${prefix} /${first}${suffix}`;
20
+ }
@@ -0,0 +1,29 @@
1
+ import { classifyCommandIntent } from '../../core/shell.js';
2
+
3
+ export function parseToolDisplayName(name) {
4
+ const raw = String(name || '').trim();
5
+ const match = raw.match(/^([^(]+)\((.*)\)$/);
6
+ return {
7
+ raw,
8
+ base: match ? match[1] : raw,
9
+ target: match ? match[2] : ''
10
+ };
11
+ }
12
+
13
+ export function trimText(text, max = 72) {
14
+ const value = String(text || '').trim();
15
+ if (!value) return '';
16
+ return value.length > max ? `${value.slice(0, Math.max(0, max - 3)).trimEnd()}...` : value;
17
+ }
18
+
19
+ export function makeBlocked(copy, target) {
20
+ return `${copy.toolActivity.blocked}: ${target}`;
21
+ }
22
+
23
+ export function makePhase(copy, doneLabel, doingLabel, target) {
24
+ return target ? `${doneLabel}: ${target}` : doneLabel || doingLabel;
25
+ }
26
+
27
+ export function classifyRunIntent(target) {
28
+ return classifyCommandIntent(target);
29
+ }
@@ -0,0 +1,17 @@
1
+ import { parseToolDisplayName } from './common.js';
2
+ import { describeCommandToolActivity } from './presenters/command.js';
3
+ import { describeFileToolActivity } from './presenters/files.js';
4
+ import { describeMiscToolActivity } from './presenters/misc.js';
5
+ import { describeSystemToolActivity } from './presenters/system.js';
6
+
7
+ export { isCodeGenerationActivityName } from './presenters/misc.js';
8
+
9
+ export function describeToolActivity(copy, name, options = {}) {
10
+ const parsed = parseToolDisplayName(name);
11
+ return (
12
+ describeSystemToolActivity(copy, parsed, options) ||
13
+ describeCommandToolActivity(copy, parsed, options) ||
14
+ describeFileToolActivity(copy, parsed, options) ||
15
+ describeMiscToolActivity(copy, parsed, name, options)
16
+ );
17
+ }
@@ -0,0 +1,29 @@
1
+ import { classifyRunIntent, makeBlocked, trimText } from '../common.js';
2
+
3
+ function phaseText(copy, blocked, done, target, doingLabel, doneLabel) {
4
+ if (blocked) return makeBlocked(copy, target);
5
+ return done ? `${doneLabel}: ${target}` : `${doingLabel}: ${target}`;
6
+ }
7
+
8
+ export function describeCommandToolActivity(copy, parsed, { done = false, blocked = false } = {}) {
9
+ const target = parsed.target || 'command';
10
+ const intent = classifyRunIntent(parsed.target);
11
+
12
+ if (parsed.base === 'run' || parsed.base === 'start_service') {
13
+ if (intent.kind === 'install') return phaseText(copy, blocked, done, target, copy.toolActivity.doingInstall, copy.toolActivity.doneInstall);
14
+ if (intent.kind === 'build') return phaseText(copy, blocked, done, target, copy.toolActivity.doingBuild, copy.toolActivity.doneBuild);
15
+ if (intent.kind === 'test') return phaseText(copy, blocked, done, target, copy.toolActivity.doingTest, copy.toolActivity.doneTest);
16
+ if (intent.kind === 'frontend-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingFrontend, copy.toolActivity.doneFrontend);
17
+ if (intent.kind === 'backend-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingBackend, copy.toolActivity.doneBackend);
18
+ if (intent.kind === 'database-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingDatabase, copy.toolActivity.doneDatabase);
19
+ if (intent.kind === 'docker-service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingDocker, copy.toolActivity.doneDocker);
20
+ if (intent.kind === 'service') return phaseText(copy, blocked, done, target, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
21
+ if (parsed.base === 'run') return phaseText(copy, blocked, done, trimText(target, 72) || parsed.base, copy.toolActivity.doingCommand, copy.toolActivity.doneCommand);
22
+ }
23
+
24
+ if (parsed.base === 'start_service' || parsed.base === 'list_services' || parsed.base === 'get_service_status' || parsed.base === 'get_service_logs' || parsed.base === 'stop_service') {
25
+ return phaseText(copy, blocked, done, trimText(parsed.target, 72) || parsed.base, copy.toolActivity.doingGeneric, copy.toolActivity.doneGeneric);
26
+ }
27
+
28
+ return '';
29
+ }
@@ -0,0 +1,26 @@
1
+ import { makeBlocked, trimText } from '../common.js';
2
+
3
+ function describePathTool(copy, parsed, labels, { done = false, blocked = false } = {}) {
4
+ const safeTarget = trimText(parsed.target, 72) || '.';
5
+ if (blocked) return makeBlocked(copy, `${parsed.base}(${safeTarget})`);
6
+ return done ? `${labels.done}: ${safeTarget}` : `${labels.doing}: ${safeTarget}`;
7
+ }
8
+
9
+ export function describeFileToolActivity(copy, parsed, options = {}) {
10
+ if (parsed.base === 'read') {
11
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneRead, doing: copy.toolActivity.doingRead }, options);
12
+ }
13
+ if (parsed.base === 'edit') {
14
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneEdit, doing: copy.toolActivity.doingEdit }, options);
15
+ }
16
+ if (parsed.base === 'write') {
17
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneWrite, doing: copy.toolActivity.doingWrite }, options);
18
+ }
19
+ if (parsed.base === 'patch') {
20
+ return describePathTool(copy, parsed, { done: copy.toolActivity.donePatch, doing: copy.toolActivity.doingPatch }, options);
21
+ }
22
+ if (parsed.base === 'list' || parsed.base === 'glob' || parsed.base === 'grep') {
23
+ return describePathTool(copy, parsed, { done: copy.toolActivity.doneList, doing: copy.toolActivity.doingList }, options);
24
+ }
25
+ return '';
26
+ }
@@ -0,0 +1,19 @@
1
+ import { makeBlocked } from '../common.js';
2
+
3
+ export function isCodeGenerationActivityName(name) {
4
+ return String(name || '').trim() === 'Code generation';
5
+ }
6
+
7
+ export function describeMiscToolActivity(copy, parsed, rawName, { done = false, blocked = false } = {}) {
8
+ if (isCodeGenerationActivityName(rawName)) {
9
+ if (blocked) return `${copy.toolActivity.blocked}: code generation`;
10
+ return done ? copy.toolActivity.doneCodeGeneration : copy.toolActivity.doingCodeGeneration;
11
+ }
12
+ if (parsed.base === 'create_task') {
13
+ return blocked ? makeBlocked(copy, 'create_task') : done ? copy.toolActivity.doneCreateTask : copy.toolActivity.doingCreateTask;
14
+ }
15
+ if (parsed.base === 'update_task') {
16
+ return blocked ? makeBlocked(copy, 'update_task') : done ? copy.toolActivity.doneUpdateTask : copy.toolActivity.doingUpdateTask;
17
+ }
18
+ return blocked ? `${copy.toolActivity.blocked}: ${parsed.raw}` : done ? `${copy.toolActivity.doneGeneric}: ${parsed.raw}` : `${copy.toolActivity.doingGeneric}: ${parsed.raw}`;
19
+ }
@@ -0,0 +1,14 @@
1
+ import { makeBlocked, trimText } from '../common.js';
2
+
3
+ export function describeSystemToolActivity(copy, parsed, { done = false, blocked = false } = {}) {
4
+ if (parsed.base === 'project_index') {
5
+ if (blocked) return `${copy.toolActivity.blocked}: project index`;
6
+ return done ? copy.toolActivity.doneProjectIndex : copy.toolActivity.doingProjectIndex;
7
+ }
8
+ if (parsed.base === 'file_index') {
9
+ const safeTarget = trimText(parsed.target || '.codemini-project/file-index.json', 72);
10
+ if (blocked) return makeBlocked(copy, safeTarget);
11
+ return done ? `${copy.toolActivity.doneFileIndex}: ${safeTarget}` : `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
12
+ }
13
+ return '';
14
+ }
@@ -0,0 +1,37 @@
1
+ export function trimText(text, max = 48) {
2
+ const value = String(text || '').trim();
3
+ if (!value) return '';
4
+ return value.length > max ? `${value.slice(0, Math.max(0, max - 3)).trimEnd()}...` : value;
5
+ }
6
+
7
+ export function parseToolDisplayName(name) {
8
+ const raw = String(name || '').trim();
9
+ const match = raw.match(/^([^(]+)\((.*)\)$/);
10
+ return {
11
+ raw,
12
+ base: match ? match[1] : raw,
13
+ target: match ? match[2] : ''
14
+ };
15
+ }
16
+
17
+ export function isEnglishCopy(copy) {
18
+ return String(copy?.roleLabels?.coder || '').trim() === 'CODER' && String(copy?.roleLabels?.you || '').trim() === 'YOU';
19
+ }
20
+
21
+ export function renderLocalizedEntry(entry, copy, context) {
22
+ if (!entry) return '';
23
+ const locale = isEnglishCopy(copy) ? 'en' : 'zh';
24
+ const renderer = entry[locale];
25
+ return typeof renderer === 'function' ? renderer(context) : '';
26
+ }
27
+
28
+ export function getLastToolActivity(msg, statuses = []) {
29
+ const allowed = new Set((Array.isArray(statuses) ? statuses : []).map((status) => String(status)));
30
+ const segments = Array.isArray(msg?.segments) ? msg.segments : [];
31
+ for (let idx = segments.length - 1; idx >= 0; idx -= 1) {
32
+ const segment = segments[idx];
33
+ if (segment?.type !== 'tool' && segment?.type !== 'system_tool') continue;
34
+ if (allowed.size === 0 || allowed.has(String(segment.status || ''))) return segment;
35
+ }
36
+ return null;
37
+ }