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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/core/agent-loop.js +196 -0
- package/src/core/chat-runtime.js +18 -3
- package/src/core/default-system-prompt.js +2 -2
- package/src/core/project-index.js +140 -13
- package/src/core/provider/openai-compatible.js +16 -4
- package/src/core/shell-profile.js +4 -0
- package/src/core/tools.js +50 -1
- package/src/tui/chat-app.js +74 -180
- package/src/tui/skill-activity/index.js +20 -0
- package/src/tui/tool-activity/common.js +29 -0
- package/src/tui/tool-activity/index.js +17 -0
- package/src/tui/tool-activity/presenters/command.js +29 -0
- package/src/tui/tool-activity/presenters/files.js +26 -0
- package/src/tui/tool-activity/presenters/misc.js +19 -0
- package/src/tui/tool-activity/presenters/system.js +14 -0
- package/src/tui/tool-narration/common.js +37 -0
- package/src/tui/tool-narration/presenters/change.js +109 -0
- package/src/tui/tool-narration/presenters/edit.js +3 -0
- package/src/tui/tool-narration/presenters/generic.js +10 -0
- package/src/tui/tool-narration/presenters/glob.js +11 -0
- package/src/tui/tool-narration/presenters/grep.js +11 -0
- package/src/tui/tool-narration/presenters/list.js +11 -0
- package/src/tui/tool-narration/presenters/patch.js +3 -0
- package/src/tui/tool-narration/presenters/read.js +11 -0
- package/src/tui/tool-narration/presenters/run.js +29 -0
- package/src/tui/tool-narration/presenters/write.js +3 -0
- package/src/tui/tool-narration.js +67 -0
package/src/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 || '';
|
package/src/tui/chat-app.js
CHANGED
|
@@ -2,6 +2,20 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
3
|
import { shouldCaptureEscapeSequence } from './input-escape.js';
|
|
4
4
|
import { classifyCommandIntent } from '../core/shell.js';
|
|
5
|
+
import {
|
|
6
|
+
buildInterToolNotice as buildRegisteredInterToolNotice,
|
|
7
|
+
buildPreToolNotice as buildRegisteredPreToolNotice,
|
|
8
|
+
buildSyntheticCompletionText as buildRegisteredSyntheticCompletionText
|
|
9
|
+
} from './tool-narration.js';
|
|
10
|
+
import {
|
|
11
|
+
describeToolActivity as describeRegisteredToolActivity,
|
|
12
|
+
isCodeGenerationActivityName
|
|
13
|
+
} from './tool-activity/index.js';
|
|
14
|
+
import {
|
|
15
|
+
describeAutoSkillActivity as describeRegisteredAutoSkillActivity,
|
|
16
|
+
describeSkillActivity as describeRegisteredSkillActivity,
|
|
17
|
+
formatAutoSkillBadge as formatRegisteredAutoSkillBadge
|
|
18
|
+
} from './skill-activity/index.js';
|
|
5
19
|
|
|
6
20
|
const h = React.createElement;
|
|
7
21
|
const SUGGESTION_PAGE_SIZE = 8;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...(
|
|
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
|
+
}
|