@lobehub/lobehub 2.0.0-next.305 → 2.0.0-next.307
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/.vscode/settings.json +18 -3
- package/CHANGELOG.md +53 -0
- package/changelog/v1.json +18 -0
- package/e2e/src/steps/community/detail-pages.steps.ts +3 -1
- package/e2e/src/steps/community/interactions.steps.ts +4 -4
- package/package.json +2 -2
- package/packages/builtin-agents/src/agents/group-supervisor/index.ts +1 -7
- package/packages/builtin-tool-group-agent-builder/src/ExecutionRuntime/index.ts +29 -0
- package/packages/builtin-tool-group-agent-builder/src/executor.ts +18 -0
- package/packages/builtin-tool-group-agent-builder/src/manifest.ts +17 -0
- package/packages/builtin-tool-group-agent-builder/src/types.ts +10 -0
- package/packages/builtin-tool-group-management/src/executor.test.ts +0 -12
- package/packages/builtin-tool-group-management/src/executor.ts +8 -47
- package/packages/builtin-tool-group-management/src/manifest.ts +0 -17
- package/packages/builtin-tool-group-management/src/systemRole.ts +1 -8
- package/packages/builtin-tool-group-management/src/types.ts +0 -10
- package/packages/builtin-tool-local-system/src/ExecutionRuntime/index.ts +70 -31
- package/packages/builtin-tool-local-system/src/executor/index.ts +94 -60
- package/packages/context-engine/src/processors/GroupMessageFlatten.ts +9 -6
- package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +103 -0
- package/packages/context-engine/src/providers/GroupAgentBuilderContextInjector.ts +18 -31
- package/packages/context-engine/src/providers/__tests__/GroupAgentBuilderContextInjector.test.ts +307 -0
- package/packages/database/src/repositories/agentGroup/index.ts +23 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.test.ts +61 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandOutput.ts +21 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandResult.test.ts +87 -0
- package/packages/prompts/src/prompts/fileSystem/formatCommandResult.ts +35 -0
- package/packages/prompts/src/prompts/fileSystem/formatEditResult.test.ts +57 -0
- package/packages/prompts/src/prompts/fileSystem/formatEditResult.ts +17 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileContent.test.ts +59 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileContent.ts +14 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileList.test.ts +62 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileList.ts +13 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.test.ts +34 -0
- package/packages/prompts/src/prompts/fileSystem/formatFileSearchResults.ts +12 -0
- package/packages/prompts/src/prompts/fileSystem/formatGlobResults.test.ts +64 -0
- package/packages/prompts/src/prompts/fileSystem/formatGlobResults.ts +23 -0
- package/packages/prompts/src/prompts/fileSystem/formatGrepResults.test.ts +85 -0
- package/packages/prompts/src/prompts/fileSystem/formatGrepResults.ts +24 -0
- package/packages/prompts/src/prompts/fileSystem/formatKillResult.test.ts +30 -0
- package/packages/prompts/src/prompts/fileSystem/formatKillResult.ts +9 -0
- package/packages/prompts/src/prompts/fileSystem/formatMoveResults.test.ts +37 -0
- package/packages/prompts/src/prompts/fileSystem/formatMoveResults.ts +20 -0
- package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.test.ts +54 -0
- package/packages/prompts/src/prompts/fileSystem/formatMultipleFiles.ts +9 -0
- package/packages/prompts/src/prompts/fileSystem/formatRenameResult.test.ts +35 -0
- package/packages/prompts/src/prompts/fileSystem/formatRenameResult.ts +17 -0
- package/packages/prompts/src/prompts/fileSystem/formatWriteResult.test.ts +30 -0
- package/packages/prompts/src/prompts/fileSystem/formatWriteResult.ts +11 -0
- package/packages/prompts/src/prompts/fileSystem/index.ts +13 -0
- package/packages/prompts/src/prompts/index.ts +1 -0
- package/packages/prompts/src/prompts/userMemory/__snapshots__/index.test.ts.snap +14 -38
- package/packages/prompts/src/prompts/userMemory/index.ts +5 -24
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/Actions.tsx +4 -3
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
- package/src/app/[variants]/(main)/community/(detail)/assistant/index.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/AddGroupAgent.tsx +69 -17
- package/src/app/[variants]/(main)/community/(detail)/mcp/index.tsx +1 -1
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/Actions.tsx +4 -3
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/useDropdownMenu.tsx +12 -2
- package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/index.tsx +2 -2
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +13 -3
- package/src/features/ChatInput/ActionBar/components/ActionDropdown.tsx +26 -3
- package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -1
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +8 -2
- package/src/features/ResourceManager/components/Header/AddButton.tsx +20 -3
- package/src/server/routers/lambda/__tests__/agentGroup.test.ts +1 -0
- package/src/server/routers/lambda/agentGroup.ts +22 -0
- package/src/services/chat/index.ts +1 -0
- package/src/services/chat/mecha/agentConfigResolver.test.ts +62 -45
- package/src/services/chat/mecha/agentConfigResolver.ts +77 -10
- package/src/services/chat/mecha/modelParamsResolver.test.ts +211 -0
- package/src/services/chatGroup/index.ts +14 -0
- package/src/store/agentGroup/action.ts +30 -0
- package/src/store/agentGroup/slices/lifecycle.test.ts +77 -18
- package/src/store/agentGroup/slices/lifecycle.ts +7 -9
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +124 -0
- package/src/store/chat/slices/operation/selectors.ts +22 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatEditResult } from './formatEditResult';
|
|
4
|
+
|
|
5
|
+
describe('formatEditResult', () => {
|
|
6
|
+
it('should format edit result without line stats', () => {
|
|
7
|
+
const result = formatEditResult({
|
|
8
|
+
filePath: '/src/index.ts',
|
|
9
|
+
replacements: 3,
|
|
10
|
+
});
|
|
11
|
+
expect(result).toMatchInlineSnapshot(
|
|
12
|
+
`"Successfully replaced 3 occurrence(s) in /src/index.ts"`,
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should format edit result with lines added', () => {
|
|
17
|
+
const result = formatEditResult({
|
|
18
|
+
filePath: '/src/index.ts',
|
|
19
|
+
linesAdded: 5,
|
|
20
|
+
replacements: 1,
|
|
21
|
+
});
|
|
22
|
+
expect(result).toMatchInlineSnapshot(
|
|
23
|
+
`"Successfully replaced 1 occurrence(s) in /src/index.ts (+5 -0)"`,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should format edit result with lines deleted', () => {
|
|
28
|
+
const result = formatEditResult({
|
|
29
|
+
filePath: '/src/index.ts',
|
|
30
|
+
linesDeleted: 3,
|
|
31
|
+
replacements: 2,
|
|
32
|
+
});
|
|
33
|
+
expect(result).toMatchInlineSnapshot(
|
|
34
|
+
`"Successfully replaced 2 occurrence(s) in /src/index.ts (+0 -3)"`,
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should format edit result with both lines added and deleted', () => {
|
|
39
|
+
const result = formatEditResult({
|
|
40
|
+
filePath: '/src/utils.ts',
|
|
41
|
+
linesAdded: 10,
|
|
42
|
+
linesDeleted: 5,
|
|
43
|
+
replacements: 4,
|
|
44
|
+
});
|
|
45
|
+
expect(result).toMatchInlineSnapshot(
|
|
46
|
+
`"Successfully replaced 4 occurrence(s) in /src/utils.ts (+10 -5)"`,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle zero replacements', () => {
|
|
51
|
+
const result = formatEditResult({
|
|
52
|
+
filePath: '/test.txt',
|
|
53
|
+
replacements: 0,
|
|
54
|
+
});
|
|
55
|
+
expect(result).toMatchInlineSnapshot(`"Successfully replaced 0 occurrence(s) in /test.txt"`);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface FormatEditResultParams {
|
|
2
|
+
filePath: string;
|
|
3
|
+
linesAdded?: number;
|
|
4
|
+
linesDeleted?: number;
|
|
5
|
+
replacements: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const formatEditResult = ({
|
|
9
|
+
replacements,
|
|
10
|
+
filePath,
|
|
11
|
+
linesAdded,
|
|
12
|
+
linesDeleted,
|
|
13
|
+
}: FormatEditResultParams): string => {
|
|
14
|
+
const statsText =
|
|
15
|
+
linesAdded || linesDeleted ? ` (+${linesAdded || 0} -${linesDeleted || 0})` : '';
|
|
16
|
+
return `Successfully replaced ${replacements} occurrence(s) in ${filePath}${statsText}`;
|
|
17
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatFileContent } from './formatFileContent';
|
|
4
|
+
|
|
5
|
+
describe('formatFileContent', () => {
|
|
6
|
+
it('should format file content without line range', () => {
|
|
7
|
+
const result = formatFileContent({
|
|
8
|
+
content: 'console.log("hello");',
|
|
9
|
+
path: '/src/index.ts',
|
|
10
|
+
});
|
|
11
|
+
expect(result).toMatchInlineSnapshot(`
|
|
12
|
+
"File: /src/index.ts
|
|
13
|
+
|
|
14
|
+
console.log("hello");"
|
|
15
|
+
`);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should format file content with line range', () => {
|
|
19
|
+
const result = formatFileContent({
|
|
20
|
+
content: 'function test() {\n return true;\n}',
|
|
21
|
+
lineRange: [10, 12],
|
|
22
|
+
path: '/src/utils.ts',
|
|
23
|
+
});
|
|
24
|
+
expect(result).toMatchInlineSnapshot(`
|
|
25
|
+
"File: /src/utils.ts (lines 10-12)
|
|
26
|
+
|
|
27
|
+
function test() {
|
|
28
|
+
return true;
|
|
29
|
+
}"
|
|
30
|
+
`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should handle empty content', () => {
|
|
34
|
+
const result = formatFileContent({
|
|
35
|
+
content: '',
|
|
36
|
+
path: '/empty.txt',
|
|
37
|
+
});
|
|
38
|
+
expect(result).toMatchInlineSnapshot(`
|
|
39
|
+
"File: /empty.txt
|
|
40
|
+
|
|
41
|
+
"
|
|
42
|
+
`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle multiline content', () => {
|
|
46
|
+
const content = 'line 1\nline 2\nline 3';
|
|
47
|
+
const result = formatFileContent({
|
|
48
|
+
content,
|
|
49
|
+
path: '/test.txt',
|
|
50
|
+
});
|
|
51
|
+
expect(result).toMatchInlineSnapshot(`
|
|
52
|
+
"File: /test.txt
|
|
53
|
+
|
|
54
|
+
line 1
|
|
55
|
+
line 2
|
|
56
|
+
line 3"
|
|
57
|
+
`);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface FormatFileContentParams {
|
|
2
|
+
content: string;
|
|
3
|
+
lineRange?: [number, number];
|
|
4
|
+
path: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const formatFileContent = ({
|
|
8
|
+
path,
|
|
9
|
+
content,
|
|
10
|
+
lineRange,
|
|
11
|
+
}: FormatFileContentParams): string => {
|
|
12
|
+
const lineInfo = lineRange ? ` (lines ${lineRange[0]}-${lineRange[1]})` : '';
|
|
13
|
+
return `File: ${path}${lineInfo}\n\n${content}`;
|
|
14
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatFileList } from './formatFileList';
|
|
4
|
+
|
|
5
|
+
describe('formatFileList', () => {
|
|
6
|
+
it('should format empty directory', () => {
|
|
7
|
+
const result = formatFileList([], '/home/user');
|
|
8
|
+
expect(result).toMatchInlineSnapshot(`"Directory /home/user is empty"`);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should format directory with files only', () => {
|
|
12
|
+
const files = [
|
|
13
|
+
{ isDirectory: false, name: 'file1.txt' },
|
|
14
|
+
{ isDirectory: false, name: 'file2.js' },
|
|
15
|
+
];
|
|
16
|
+
const result = formatFileList(files, '/home/user');
|
|
17
|
+
expect(result).toMatchInlineSnapshot(`
|
|
18
|
+
"Found 2 item(s) in /home/user:
|
|
19
|
+
[F] file1.txt
|
|
20
|
+
[F] file2.js"
|
|
21
|
+
`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should format directory with directories only', () => {
|
|
25
|
+
const files = [
|
|
26
|
+
{ isDirectory: true, name: 'src' },
|
|
27
|
+
{ isDirectory: true, name: 'dist' },
|
|
28
|
+
];
|
|
29
|
+
const result = formatFileList(files, '/project');
|
|
30
|
+
expect(result).toMatchInlineSnapshot(`
|
|
31
|
+
"Found 2 item(s) in /project:
|
|
32
|
+
[D] src
|
|
33
|
+
[D] dist"
|
|
34
|
+
`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should format directory with mixed content', () => {
|
|
38
|
+
const files = [
|
|
39
|
+
{ isDirectory: true, name: 'src' },
|
|
40
|
+
{ isDirectory: false, name: 'package.json' },
|
|
41
|
+
{ isDirectory: true, name: 'node_modules' },
|
|
42
|
+
{ isDirectory: false, name: 'README.md' },
|
|
43
|
+
];
|
|
44
|
+
const result = formatFileList(files, '/project');
|
|
45
|
+
expect(result).toMatchInlineSnapshot(`
|
|
46
|
+
"Found 4 item(s) in /project:
|
|
47
|
+
[D] src
|
|
48
|
+
[F] package.json
|
|
49
|
+
[D] node_modules
|
|
50
|
+
[F] README.md"
|
|
51
|
+
`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should format single item', () => {
|
|
55
|
+
const files = [{ isDirectory: false, name: 'index.ts' }];
|
|
56
|
+
const result = formatFileList(files, '/src');
|
|
57
|
+
expect(result).toMatchInlineSnapshot(`
|
|
58
|
+
"Found 1 item(s) in /src:
|
|
59
|
+
[F] index.ts"
|
|
60
|
+
`);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface FileListItem {
|
|
2
|
+
isDirectory: boolean;
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const formatFileList = (files: FileListItem[], directory: string): string => {
|
|
7
|
+
if (files.length === 0) {
|
|
8
|
+
return `Directory ${directory} is empty`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const fileList = files.map((f) => ` ${f.isDirectory ? '[D]' : '[F]'} ${f.name}`).join('\n');
|
|
12
|
+
return `Found ${files.length} item(s) in ${directory}:\n${fileList}`;
|
|
13
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatFileSearchResults } from './formatFileSearchResults';
|
|
4
|
+
|
|
5
|
+
describe('formatFileSearchResults', () => {
|
|
6
|
+
it('should format empty results', () => {
|
|
7
|
+
const result = formatFileSearchResults([]);
|
|
8
|
+
expect(result).toMatchInlineSnapshot(`"No files found"`);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should format single result', () => {
|
|
12
|
+
const results = [{ path: '/src/index.ts' }];
|
|
13
|
+
const result = formatFileSearchResults(results);
|
|
14
|
+
expect(result).toMatchInlineSnapshot(`
|
|
15
|
+
"Found 1 file(s):
|
|
16
|
+
/src/index.ts"
|
|
17
|
+
`);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should format multiple results', () => {
|
|
21
|
+
const results = [
|
|
22
|
+
{ path: '/src/index.ts' },
|
|
23
|
+
{ path: '/src/utils.ts' },
|
|
24
|
+
{ path: '/src/types.ts' },
|
|
25
|
+
];
|
|
26
|
+
const result = formatFileSearchResults(results);
|
|
27
|
+
expect(result).toMatchInlineSnapshot(`
|
|
28
|
+
"Found 3 file(s):
|
|
29
|
+
/src/index.ts
|
|
30
|
+
/src/utils.ts
|
|
31
|
+
/src/types.ts"
|
|
32
|
+
`);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface FileSearchResultItem {
|
|
2
|
+
path: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const formatFileSearchResults = (results: FileSearchResultItem[]): string => {
|
|
6
|
+
if (results.length === 0) {
|
|
7
|
+
return 'No files found';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const fileList = results.map((f) => ` ${f.path}`).join('\n');
|
|
11
|
+
return `Found ${results.length} file(s):\n${fileList}`;
|
|
12
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatGlobResults } from './formatGlobResults';
|
|
4
|
+
|
|
5
|
+
describe('formatGlobResults', () => {
|
|
6
|
+
it('should format empty results', () => {
|
|
7
|
+
const result = formatGlobResults({
|
|
8
|
+
files: [],
|
|
9
|
+
totalFiles: 0,
|
|
10
|
+
});
|
|
11
|
+
expect(result).toMatchInlineSnapshot(`"Found 0 files"`);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should format single file', () => {
|
|
15
|
+
const result = formatGlobResults({
|
|
16
|
+
files: ['/src/index.ts'],
|
|
17
|
+
totalFiles: 1,
|
|
18
|
+
});
|
|
19
|
+
expect(result).toMatchInlineSnapshot(`
|
|
20
|
+
"Found 1 files:
|
|
21
|
+
/src/index.ts"
|
|
22
|
+
`);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should format multiple files', () => {
|
|
26
|
+
const result = formatGlobResults({
|
|
27
|
+
files: ['/src/index.ts', '/src/utils.ts', '/src/types.ts'],
|
|
28
|
+
totalFiles: 3,
|
|
29
|
+
});
|
|
30
|
+
expect(result).toMatchInlineSnapshot(`
|
|
31
|
+
"Found 3 files:
|
|
32
|
+
/src/index.ts
|
|
33
|
+
/src/utils.ts
|
|
34
|
+
/src/types.ts"
|
|
35
|
+
`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should truncate files exceeding maxDisplay (default 50)', () => {
|
|
39
|
+
const files = Array.from({ length: 55 }, (_, i) => `/file${i + 1}.ts`);
|
|
40
|
+
const result = formatGlobResults({
|
|
41
|
+
files,
|
|
42
|
+
totalFiles: 55,
|
|
43
|
+
});
|
|
44
|
+
expect(result).toContain('Found 55 files:');
|
|
45
|
+
expect(result).toContain('... and 5 more');
|
|
46
|
+
expect(result).not.toContain('file51');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should respect custom maxDisplay', () => {
|
|
50
|
+
const files = ['/a.ts', '/b.ts', '/c.ts', '/d.ts', '/e.ts'];
|
|
51
|
+
const result = formatGlobResults({
|
|
52
|
+
files,
|
|
53
|
+
maxDisplay: 3,
|
|
54
|
+
totalFiles: 5,
|
|
55
|
+
});
|
|
56
|
+
expect(result).toMatchInlineSnapshot(`
|
|
57
|
+
"Found 5 files:
|
|
58
|
+
/a.ts
|
|
59
|
+
/b.ts
|
|
60
|
+
/c.ts
|
|
61
|
+
... and 2 more"
|
|
62
|
+
`);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface FormatGlobResultsParams {
|
|
2
|
+
files: string[];
|
|
3
|
+
maxDisplay?: number;
|
|
4
|
+
totalFiles: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const formatGlobResults = ({
|
|
8
|
+
totalFiles,
|
|
9
|
+
files,
|
|
10
|
+
maxDisplay = 50,
|
|
11
|
+
}: FormatGlobResultsParams): string => {
|
|
12
|
+
const message = `Found ${totalFiles} files`;
|
|
13
|
+
|
|
14
|
+
if (files.length === 0) {
|
|
15
|
+
return message;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const displayFiles = files.slice(0, maxDisplay);
|
|
19
|
+
const fileList = displayFiles.map((f) => ` ${f}`).join('\n');
|
|
20
|
+
const moreInfo = files.length > maxDisplay ? `\n ... and ${files.length - maxDisplay} more` : '';
|
|
21
|
+
|
|
22
|
+
return `${message}:\n${fileList}${moreInfo}`;
|
|
23
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatGrepResults } from './formatGrepResults';
|
|
4
|
+
|
|
5
|
+
describe('formatGrepResults', () => {
|
|
6
|
+
it('should format empty results', () => {
|
|
7
|
+
const result = formatGrepResults({
|
|
8
|
+
matches: [],
|
|
9
|
+
totalMatches: 0,
|
|
10
|
+
});
|
|
11
|
+
expect(result).toMatchInlineSnapshot(`"Found 0 matches in 0 locations"`);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should format single match', () => {
|
|
15
|
+
const result = formatGrepResults({
|
|
16
|
+
matches: ['/src/index.ts:10: const foo = "bar"'],
|
|
17
|
+
totalMatches: 1,
|
|
18
|
+
});
|
|
19
|
+
expect(result).toMatchInlineSnapshot(`
|
|
20
|
+
"Found 1 matches in 1 locations:
|
|
21
|
+
/src/index.ts:10: const foo = "bar""
|
|
22
|
+
`);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should format multiple matches', () => {
|
|
26
|
+
const result = formatGrepResults({
|
|
27
|
+
matches: ['/src/index.ts:10: match1', '/src/utils.ts:20: match2', '/src/types.ts:30: match3'],
|
|
28
|
+
totalMatches: 5,
|
|
29
|
+
});
|
|
30
|
+
expect(result).toMatchInlineSnapshot(`
|
|
31
|
+
"Found 5 matches in 3 locations:
|
|
32
|
+
/src/index.ts:10: match1
|
|
33
|
+
/src/utils.ts:20: match2
|
|
34
|
+
/src/types.ts:30: match3"
|
|
35
|
+
`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should truncate matches exceeding maxDisplay', () => {
|
|
39
|
+
const matches = Array.from({ length: 25 }, (_, i) => `match${i + 1}`);
|
|
40
|
+
const result = formatGrepResults({
|
|
41
|
+
matches,
|
|
42
|
+
totalMatches: 100,
|
|
43
|
+
});
|
|
44
|
+
expect(result).toMatchInlineSnapshot(`
|
|
45
|
+
"Found 100 matches in 25 locations:
|
|
46
|
+
match1
|
|
47
|
+
match2
|
|
48
|
+
match3
|
|
49
|
+
match4
|
|
50
|
+
match5
|
|
51
|
+
match6
|
|
52
|
+
match7
|
|
53
|
+
match8
|
|
54
|
+
match9
|
|
55
|
+
match10
|
|
56
|
+
match11
|
|
57
|
+
match12
|
|
58
|
+
match13
|
|
59
|
+
match14
|
|
60
|
+
match15
|
|
61
|
+
match16
|
|
62
|
+
match17
|
|
63
|
+
match18
|
|
64
|
+
match19
|
|
65
|
+
match20
|
|
66
|
+
... and 5 more"
|
|
67
|
+
`);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should respect custom maxDisplay', () => {
|
|
71
|
+
const matches = ['match1', 'match2', 'match3', 'match4', 'match5'];
|
|
72
|
+
const result = formatGrepResults({
|
|
73
|
+
matches,
|
|
74
|
+
maxDisplay: 3,
|
|
75
|
+
totalMatches: 10,
|
|
76
|
+
});
|
|
77
|
+
expect(result).toMatchInlineSnapshot(`
|
|
78
|
+
"Found 10 matches in 5 locations:
|
|
79
|
+
match1
|
|
80
|
+
match2
|
|
81
|
+
match3
|
|
82
|
+
... and 2 more"
|
|
83
|
+
`);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface FormatGrepResultsParams {
|
|
2
|
+
matches: string[];
|
|
3
|
+
maxDisplay?: number;
|
|
4
|
+
totalMatches: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const formatGrepResults = ({
|
|
8
|
+
totalMatches,
|
|
9
|
+
matches,
|
|
10
|
+
maxDisplay = 20,
|
|
11
|
+
}: FormatGrepResultsParams): string => {
|
|
12
|
+
const message = `Found ${totalMatches} matches in ${matches.length} locations`;
|
|
13
|
+
|
|
14
|
+
if (matches.length === 0) {
|
|
15
|
+
return message;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const displayMatches = matches.slice(0, maxDisplay);
|
|
19
|
+
const matchList = displayMatches.map((m) => ` ${m}`).join('\n');
|
|
20
|
+
const moreInfo =
|
|
21
|
+
matches.length > maxDisplay ? `\n ... and ${matches.length - maxDisplay} more` : '';
|
|
22
|
+
|
|
23
|
+
return `${message}:\n${matchList}${moreInfo}`;
|
|
24
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatKillResult } from './formatKillResult';
|
|
4
|
+
|
|
5
|
+
describe('formatKillResult', () => {
|
|
6
|
+
it('should format successful kill', () => {
|
|
7
|
+
const result = formatKillResult({
|
|
8
|
+
shellId: 'shell-123',
|
|
9
|
+
success: true,
|
|
10
|
+
});
|
|
11
|
+
expect(result).toMatchInlineSnapshot(`"Successfully killed shell: shell-123"`);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should format failed kill', () => {
|
|
15
|
+
const result = formatKillResult({
|
|
16
|
+
error: 'Process not found',
|
|
17
|
+
shellId: 'shell-456',
|
|
18
|
+
success: false,
|
|
19
|
+
});
|
|
20
|
+
expect(result).toMatchInlineSnapshot(`"Failed to kill shell: Process not found"`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should format failed kill without error message', () => {
|
|
24
|
+
const result = formatKillResult({
|
|
25
|
+
shellId: 'shell-789',
|
|
26
|
+
success: false,
|
|
27
|
+
});
|
|
28
|
+
expect(result).toMatchInlineSnapshot(`"Failed to kill shell: undefined"`);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface FormatKillResultParams {
|
|
2
|
+
error?: string;
|
|
3
|
+
shellId: string;
|
|
4
|
+
success: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const formatKillResult = ({ success, shellId, error }: FormatKillResultParams): string => {
|
|
8
|
+
return success ? `Successfully killed shell: ${shellId}` : `Failed to kill shell: ${error}`;
|
|
9
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatMoveResults } from './formatMoveResults';
|
|
4
|
+
|
|
5
|
+
describe('formatMoveResults', () => {
|
|
6
|
+
it('should format all successful moves', () => {
|
|
7
|
+
const results = [{ success: true }, { success: true }, { success: true }];
|
|
8
|
+
const result = formatMoveResults(results);
|
|
9
|
+
expect(result).toMatchInlineSnapshot(`"Successfully moved 3 item(s)."`);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should format all failed moves', () => {
|
|
13
|
+
const results = [{ success: false }, { success: false }];
|
|
14
|
+
const result = formatMoveResults(results);
|
|
15
|
+
expect(result).toMatchInlineSnapshot(`"Failed to move all 2 item(s)."`);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should format partial success', () => {
|
|
19
|
+
const results = [{ success: true }, { success: false }, { success: true }];
|
|
20
|
+
const result = formatMoveResults(results);
|
|
21
|
+
expect(result).toMatchInlineSnapshot(
|
|
22
|
+
`"Moved 2 item(s) successfully. Failed to move 1 item(s)."`,
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should handle single item success', () => {
|
|
27
|
+
const results = [{ success: true }];
|
|
28
|
+
const result = formatMoveResults(results);
|
|
29
|
+
expect(result).toMatchInlineSnapshot(`"Successfully moved 1 item(s)."`);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should handle single item failure', () => {
|
|
33
|
+
const results = [{ success: false }];
|
|
34
|
+
const result = formatMoveResults(results);
|
|
35
|
+
expect(result).toMatchInlineSnapshot(`"Failed to move all 1 item(s)."`);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface MoveResultItem {
|
|
2
|
+
success: boolean;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export const formatMoveResults = (results: MoveResultItem[]): string => {
|
|
6
|
+
const successCount = results.filter((r) => r.success).length;
|
|
7
|
+
const failedCount = results.length - successCount;
|
|
8
|
+
const allSucceeded = failedCount === 0;
|
|
9
|
+
const allFailed = successCount === 0;
|
|
10
|
+
|
|
11
|
+
if (allSucceeded) {
|
|
12
|
+
return `Successfully moved ${results.length} item(s).`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (allFailed) {
|
|
16
|
+
return `Failed to move all ${results.length} item(s).`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
|
|
20
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatMultipleFiles } from './formatMultipleFiles';
|
|
4
|
+
|
|
5
|
+
describe('formatMultipleFiles', () => {
|
|
6
|
+
it('should format single file', () => {
|
|
7
|
+
const files = [{ content: 'hello world', filename: 'test.txt' }];
|
|
8
|
+
const result = formatMultipleFiles(files);
|
|
9
|
+
expect(result).toMatchInlineSnapshot(`
|
|
10
|
+
"Read 1 file(s):
|
|
11
|
+
|
|
12
|
+
=== test.txt ===
|
|
13
|
+
hello world"
|
|
14
|
+
`);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should format multiple files', () => {
|
|
18
|
+
const files = [
|
|
19
|
+
{ content: 'content 1', filename: 'file1.txt' },
|
|
20
|
+
{ content: 'content 2', filename: 'file2.txt' },
|
|
21
|
+
];
|
|
22
|
+
const result = formatMultipleFiles(files);
|
|
23
|
+
expect(result).toMatchInlineSnapshot(`
|
|
24
|
+
"Read 2 file(s):
|
|
25
|
+
|
|
26
|
+
=== file1.txt ===
|
|
27
|
+
content 1
|
|
28
|
+
|
|
29
|
+
=== file2.txt ===
|
|
30
|
+
content 2"
|
|
31
|
+
`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle empty files array', () => {
|
|
35
|
+
const result = formatMultipleFiles([]);
|
|
36
|
+
expect(result).toMatchInlineSnapshot(`
|
|
37
|
+
"Read 0 file(s):
|
|
38
|
+
|
|
39
|
+
"
|
|
40
|
+
`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should handle files with multiline content', () => {
|
|
44
|
+
const files = [{ content: 'line 1\nline 2', filename: 'multi.txt' }];
|
|
45
|
+
const result = formatMultipleFiles(files);
|
|
46
|
+
expect(result).toMatchInlineSnapshot(`
|
|
47
|
+
"Read 1 file(s):
|
|
48
|
+
|
|
49
|
+
=== multi.txt ===
|
|
50
|
+
line 1
|
|
51
|
+
line 2"
|
|
52
|
+
`);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface FileContentItem {
|
|
2
|
+
content: string;
|
|
3
|
+
filename: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const formatMultipleFiles = (files: FileContentItem[]): string => {
|
|
7
|
+
const fileContents = files.map((f) => `=== ${f.filename} ===\n${f.content}`).join('\n\n');
|
|
8
|
+
return `Read ${files.length} file(s):\n\n${fileContents}`;
|
|
9
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { formatRenameResult } from './formatRenameResult';
|
|
4
|
+
|
|
5
|
+
describe('formatRenameResult', () => {
|
|
6
|
+
it('should format successful rename', () => {
|
|
7
|
+
const result = formatRenameResult({
|
|
8
|
+
newName: 'newFile.ts',
|
|
9
|
+
oldPath: '/src/oldFile.ts',
|
|
10
|
+
success: true,
|
|
11
|
+
});
|
|
12
|
+
expect(result).toMatchInlineSnapshot(
|
|
13
|
+
`"Successfully renamed file /src/oldFile.ts to newFile.ts"`,
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should format failed rename', () => {
|
|
18
|
+
const result = formatRenameResult({
|
|
19
|
+
error: 'File already exists',
|
|
20
|
+
newName: 'existing.ts',
|
|
21
|
+
oldPath: '/src/file.ts',
|
|
22
|
+
success: false,
|
|
23
|
+
});
|
|
24
|
+
expect(result).toMatchInlineSnapshot(`"Failed to rename file: File already exists"`);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should format failed rename without error message', () => {
|
|
28
|
+
const result = formatRenameResult({
|
|
29
|
+
newName: 'newName.ts',
|
|
30
|
+
oldPath: '/src/file.ts',
|
|
31
|
+
success: false,
|
|
32
|
+
});
|
|
33
|
+
expect(result).toMatchInlineSnapshot(`"Failed to rename file: undefined"`);
|
|
34
|
+
});
|
|
35
|
+
});
|