@lobehub/lobehub 2.0.0-next.73 → 2.0.0-next.75
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/.github/workflows/desktop-pr-build.yml +7 -3
- package/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +1 -0
- package/apps/desktop/src/main/controllers/LocalFileCtr.ts +55 -11
- package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +153 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/chat.json +5 -0
- package/locales/ar/models.json +15 -0
- package/locales/ar/tool.json +12 -1
- package/locales/bg-BG/chat.json +5 -0
- package/locales/bg-BG/models.json +15 -0
- package/locales/bg-BG/tool.json +12 -1
- package/locales/de-DE/chat.json +5 -0
- package/locales/de-DE/models.json +15 -0
- package/locales/de-DE/tool.json +12 -1
- package/locales/en-US/models.json +15 -0
- package/locales/en-US/tool.json +12 -1
- package/locales/es-ES/chat.json +5 -0
- package/locales/es-ES/models.json +15 -0
- package/locales/es-ES/tool.json +12 -1
- package/locales/fa-IR/chat.json +5 -0
- package/locales/fa-IR/models.json +15 -0
- package/locales/fa-IR/tool.json +12 -1
- package/locales/fr-FR/chat.json +5 -0
- package/locales/fr-FR/models.json +15 -0
- package/locales/fr-FR/tool.json +12 -1
- package/locales/it-IT/chat.json +5 -0
- package/locales/it-IT/models.json +15 -0
- package/locales/it-IT/tool.json +12 -1
- package/locales/ja-JP/chat.json +5 -0
- package/locales/ja-JP/models.json +15 -0
- package/locales/ja-JP/tool.json +12 -1
- package/locales/ko-KR/chat.json +5 -0
- package/locales/ko-KR/models.json +15 -0
- package/locales/ko-KR/tool.json +12 -1
- package/locales/nl-NL/chat.json +5 -0
- package/locales/nl-NL/models.json +15 -0
- package/locales/nl-NL/tool.json +12 -1
- package/locales/pl-PL/chat.json +5 -0
- package/locales/pl-PL/models.json +15 -0
- package/locales/pl-PL/tool.json +12 -1
- package/locales/pt-BR/chat.json +5 -0
- package/locales/pt-BR/models.json +15 -0
- package/locales/pt-BR/tool.json +12 -1
- package/locales/ru-RU/chat.json +5 -0
- package/locales/ru-RU/models.json +15 -0
- package/locales/ru-RU/tool.json +12 -1
- package/locales/tr-TR/chat.json +5 -0
- package/locales/tr-TR/models.json +15 -0
- package/locales/tr-TR/tool.json +12 -1
- package/locales/vi-VN/chat.json +5 -0
- package/locales/vi-VN/models.json +15 -0
- package/locales/vi-VN/tool.json +12 -1
- package/locales/zh-CN/models.json +15 -0
- package/locales/zh-CN/tool.json +12 -1
- package/locales/zh-TW/chat.json +5 -0
- package/locales/zh-TW/models.json +15 -0
- package/locales/zh-TW/tool.json +12 -1
- package/package.json +2 -1
- package/packages/electron-client-ipc/src/types/localSystem.ts +4 -0
- package/scripts/prebuild.mts +15 -5
- package/src/app/[variants]/desktopRouter.config.tsx +0 -17
- package/src/app/[variants]/mobileRouter.config.tsx +0 -16
- package/src/app/[variants]/page.tsx +5 -4
- package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +23 -4
- package/src/locales/default/tool.ts +11 -0
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +5 -6
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +45 -182
- package/src/tools/executionRuntimes.ts +3 -0
- package/src/tools/local-system/ExecutionRuntime/index.ts +407 -0
- package/src/tools/local-system/Intervention/EditLocalFile/index.tsx +89 -0
- package/src/tools/local-system/Intervention/WriteFile/index.tsx +72 -0
- package/src/tools/local-system/Intervention/index.ts +4 -0
- package/src/tools/local-system/Render/EditLocalFile/index.tsx +67 -0
- package/src/tools/local-system/Render/ReadLocalFile/ReadFileView.tsx +53 -78
- package/src/tools/local-system/Render/index.ts +2 -0
- package/src/tools/local-system/index.ts +1 -0
- package/src/tools/local-system/type.ts +4 -3
package/scripts/prebuild.mts
CHANGED
|
@@ -20,11 +20,11 @@ const partialBuildPages = [
|
|
|
20
20
|
disabled: isDesktop,
|
|
21
21
|
paths: ['src/app/[variants]/(auth)'],
|
|
22
22
|
},
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
},
|
|
23
|
+
// {
|
|
24
|
+
// name: 'mobile',
|
|
25
|
+
// disabled: isDesktop,
|
|
26
|
+
// paths: ['src/app/[variants]/(main)/(mobile)'],
|
|
27
|
+
// },
|
|
28
28
|
{
|
|
29
29
|
name: 'oauth',
|
|
30
30
|
disabled: isDesktop,
|
|
@@ -35,6 +35,16 @@ const partialBuildPages = [
|
|
|
35
35
|
disabled: isDesktop,
|
|
36
36
|
paths: ['src/app/(backend)/api/webhooks'],
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
name: 'market-auth',
|
|
40
|
+
disabled: isDesktop,
|
|
41
|
+
paths: ['src/app/market-auth-callback'],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'pwa',
|
|
45
|
+
disabled: isDesktop,
|
|
46
|
+
paths: ['src/manifest.ts', 'src/sitemap.tsx', 'src/robots.tsx', 'src/sw'],
|
|
47
|
+
},
|
|
38
48
|
// no need for web
|
|
39
49
|
{
|
|
40
50
|
name: 'desktop-devtools',
|
|
@@ -7,7 +7,6 @@ import Loading from '@/components/Loading/BrandTextLoading';
|
|
|
7
7
|
import { useGlobalStore } from '@/store/global';
|
|
8
8
|
import type { Locales } from '@/types/locale';
|
|
9
9
|
|
|
10
|
-
import DesktopChangelogLayout from './(main)/changelog/_layout/Desktop';
|
|
11
10
|
import DesktopMainLayout from './(main)/layouts/desktop';
|
|
12
11
|
import { idLoader, slugLoader } from './loaders/routeParams';
|
|
13
12
|
|
|
@@ -300,22 +299,6 @@ export const createDesktopRouter = (locale: Locales) =>
|
|
|
300
299
|
path: 'labs',
|
|
301
300
|
},
|
|
302
301
|
|
|
303
|
-
// Changelog routes
|
|
304
|
-
{
|
|
305
|
-
children: [
|
|
306
|
-
{
|
|
307
|
-
index: true,
|
|
308
|
-
lazy: () =>
|
|
309
|
-
import('./(main)/changelog').then((m) => ({
|
|
310
|
-
Component: m.DesktopPage,
|
|
311
|
-
})),
|
|
312
|
-
path: '*',
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
element: <DesktopChangelogLayout locale={locale} />,
|
|
316
|
-
path: 'changelog',
|
|
317
|
-
},
|
|
318
|
-
|
|
319
302
|
// Profile routes
|
|
320
303
|
{
|
|
321
304
|
children: [
|
|
@@ -7,7 +7,6 @@ import Loading from '@/components/Loading/BrandTextLoading';
|
|
|
7
7
|
import { useGlobalStore } from '@/store/global';
|
|
8
8
|
import type { Locales } from '@/types/locale';
|
|
9
9
|
|
|
10
|
-
import MobileChangelogLayout from './(main)/changelog/_layout/Mobile';
|
|
11
10
|
import { MobileMainLayout } from './(main)/layouts/mobile';
|
|
12
11
|
import { idLoader, slugLoader } from './loaders/routeParams';
|
|
13
12
|
|
|
@@ -287,21 +286,6 @@ export const createMobileRouter = (locale: Locales) =>
|
|
|
287
286
|
path: 'labs',
|
|
288
287
|
},
|
|
289
288
|
|
|
290
|
-
// Changelog routes
|
|
291
|
-
{
|
|
292
|
-
children: [
|
|
293
|
-
{
|
|
294
|
-
index: true,
|
|
295
|
-
lazy: () =>
|
|
296
|
-
import('./(main)/changelog').then((m) => ({
|
|
297
|
-
Component: m.MobilePage,
|
|
298
|
-
})),
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
element: <MobileChangelogLayout locale={locale} />,
|
|
302
|
-
path: 'changelog',
|
|
303
|
-
},
|
|
304
|
-
|
|
305
289
|
// Profile routes
|
|
306
290
|
{
|
|
307
291
|
children: [
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { DynamicLayoutProps } from '@/types/next';
|
|
2
2
|
import { RouteVariants } from '@/utils/server/routeVariants';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import DesktopRouter from './DesktopRouter';
|
|
5
|
+
import MobileRouter from './MobileRouter';
|
|
6
|
+
|
|
7
|
+
export default async (props: DynamicLayoutProps) => {
|
|
5
8
|
// Get isMobile from variants parameter on server side
|
|
6
9
|
const isMobile = await RouteVariants.getIsMobile(props);
|
|
7
10
|
const { locale } = await RouteVariants.getVariantsFromProps(props);
|
|
@@ -10,10 +13,8 @@ export default async function Page(props: DynamicLayoutProps) {
|
|
|
10
13
|
// Using native dynamic import ensures complete code splitting
|
|
11
14
|
// Mobile and Desktop bundles will be completely separate
|
|
12
15
|
if (isMobile) {
|
|
13
|
-
const { default: MobileRouter } = await import('./MobileRouter');
|
|
14
16
|
return <MobileRouter locale={locale} />;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
const { default: DesktopRouter } = await import('./DesktopRouter');
|
|
18
19
|
return <DesktopRouter locale={locale} />;
|
|
19
|
-
}
|
|
20
|
+
};
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
Trash2,
|
|
12
12
|
X,
|
|
13
13
|
} from 'lucide-react';
|
|
14
|
-
import { CSSProperties, memo, useState } from 'react';
|
|
14
|
+
import { CSSProperties, memo, useEffect, useState } from 'react';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
import { Flexbox } from 'react-layout-kit';
|
|
17
17
|
|
|
@@ -97,7 +97,6 @@ const Inspectors = memo<InspectorProps>(
|
|
|
97
97
|
apiName,
|
|
98
98
|
id,
|
|
99
99
|
arguments: requestArgs,
|
|
100
|
-
showRender,
|
|
101
100
|
result,
|
|
102
101
|
setShowRender,
|
|
103
102
|
showPluginRender,
|
|
@@ -109,6 +108,8 @@ const Inspectors = memo<InspectorProps>(
|
|
|
109
108
|
const { styles, theme } = useStyles();
|
|
110
109
|
|
|
111
110
|
const [showDebug, setShowDebug] = useState(false);
|
|
111
|
+
const [isPinned, setIsPinned] = useState(false);
|
|
112
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
112
113
|
|
|
113
114
|
const [deleteAssistantMessage] = useChatStore((s) => [s.deleteAssistantMessage]);
|
|
114
115
|
|
|
@@ -121,7 +122,15 @@ const Inspectors = memo<InspectorProps>(
|
|
|
121
122
|
const isReject = intervention?.status === 'rejected';
|
|
122
123
|
const isTitleLoading = !hasResult && !isPending;
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
// Compute actual render state based on pinned or hovered
|
|
126
|
+
const shouldShowRender = isPinned || isHovered;
|
|
127
|
+
|
|
128
|
+
// Sync with parent state
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
setShowRender(shouldShowRender);
|
|
131
|
+
}, [shouldShowRender, setShowRender]);
|
|
132
|
+
|
|
133
|
+
const showCustomPluginRender = shouldShowRender && !isPending && !isReject;
|
|
125
134
|
return (
|
|
126
135
|
<Flexbox className={styles.container} gap={4}>
|
|
127
136
|
<Flexbox align={'center'} distribution={'space-between'} gap={8} horizontal>
|
|
@@ -131,7 +140,17 @@ const Inspectors = memo<InspectorProps>(
|
|
|
131
140
|
gap={8}
|
|
132
141
|
horizontal
|
|
133
142
|
onClick={() => {
|
|
134
|
-
|
|
143
|
+
setIsPinned(!isPinned);
|
|
144
|
+
}}
|
|
145
|
+
onMouseEnter={() => {
|
|
146
|
+
if (!isPinned) {
|
|
147
|
+
setIsHovered(true);
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
onMouseLeave={() => {
|
|
151
|
+
if (!isPinned) {
|
|
152
|
+
setIsHovered(false);
|
|
153
|
+
}
|
|
135
154
|
}}
|
|
136
155
|
paddingInline={4}
|
|
137
156
|
>
|
|
@@ -15,6 +15,12 @@ export default {
|
|
|
15
15
|
prompt: '提示词',
|
|
16
16
|
},
|
|
17
17
|
localFiles: {
|
|
18
|
+
editFile: {
|
|
19
|
+
newString: '替换为',
|
|
20
|
+
oldString: '查找内容',
|
|
21
|
+
replaceAll: '替换全部匹配项',
|
|
22
|
+
replaceFirst: '仅替换第一个匹配项',
|
|
23
|
+
},
|
|
18
24
|
file: '文件',
|
|
19
25
|
folder: '文件夹',
|
|
20
26
|
moveFiles: {
|
|
@@ -35,6 +41,11 @@ export default {
|
|
|
35
41
|
readFileError: '读取文件失败,请检查文件路径是否正确',
|
|
36
42
|
readFiles: '读取文件',
|
|
37
43
|
readFilesError: '读取文件失败,请检查文件路径是否正确',
|
|
44
|
+
writeFile: {
|
|
45
|
+
characters: '字符',
|
|
46
|
+
preview: '内容预览',
|
|
47
|
+
truncated: '已截断',
|
|
48
|
+
},
|
|
38
49
|
},
|
|
39
50
|
search: {
|
|
40
51
|
createNewSearch: '创建新的搜索记录',
|
|
@@ -50,18 +50,17 @@ describe('localFileSlice', () => {
|
|
|
50
50
|
|
|
51
51
|
describe('internal_triggerLocalFileToolCalling', () => {
|
|
52
52
|
it('should handle successful calling', async () => {
|
|
53
|
-
const mockContent =
|
|
53
|
+
const mockContent = 'result content';
|
|
54
54
|
const mockState = { state: 'test' };
|
|
55
|
-
const mockService = vi
|
|
55
|
+
const mockService = vi
|
|
56
|
+
.fn()
|
|
57
|
+
.mockResolvedValue({ content: mockContent, state: mockState, success: true });
|
|
56
58
|
|
|
57
59
|
await store.internal_triggerLocalFileToolCalling('test-id', mockService);
|
|
58
60
|
|
|
59
61
|
expect(mockStore.toggleLocalFileLoading).toBeCalledWith('test-id', true);
|
|
60
62
|
expect(mockStore.optimisticUpdatePluginState).toBeCalledWith('test-id', mockState);
|
|
61
|
-
expect(mockStore.optimisticUpdateMessageContent).toBeCalledWith(
|
|
62
|
-
'test-id',
|
|
63
|
-
JSON.stringify(mockContent),
|
|
64
|
-
);
|
|
63
|
+
expect(mockStore.optimisticUpdateMessageContent).toBeCalledWith('test-id', mockContent);
|
|
65
64
|
expect(mockStore.toggleLocalFileLoading).toBeCalledWith('test-id', false);
|
|
66
65
|
});
|
|
67
66
|
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
GrepContentParams,
|
|
6
6
|
KillCommandParams,
|
|
7
7
|
ListLocalFileParams,
|
|
8
|
-
LocalMoveFilesResultItem,
|
|
9
8
|
LocalReadFileParams,
|
|
10
9
|
LocalReadFilesParams,
|
|
11
10
|
LocalSearchFilesParams,
|
|
@@ -16,28 +15,14 @@ import {
|
|
|
16
15
|
} from '@lobechat/electron-client-ipc';
|
|
17
16
|
import { StateCreator } from 'zustand/vanilla';
|
|
18
17
|
|
|
19
|
-
import { localFileService } from '@/services/electron/localFileService';
|
|
20
18
|
import { ChatStore } from '@/store/chat/store';
|
|
21
|
-
import {
|
|
22
|
-
EditLocalFileState,
|
|
23
|
-
GetCommandOutputState,
|
|
24
|
-
GlobFilesState,
|
|
25
|
-
GrepContentState,
|
|
26
|
-
KillCommandState,
|
|
27
|
-
LocalFileListState,
|
|
28
|
-
LocalFileSearchState,
|
|
29
|
-
LocalMoveFilesState,
|
|
30
|
-
LocalReadFileState,
|
|
31
|
-
LocalReadFilesState,
|
|
32
|
-
LocalRenameFileState,
|
|
33
|
-
RunCommandState,
|
|
34
|
-
} from '@/tools/local-system/type';
|
|
19
|
+
import { LocalSystemExecutionRuntime } from '@/tools/local-system/ExecutionRuntime';
|
|
35
20
|
|
|
36
21
|
/* eslint-disable typescript-sort-keys/interface */
|
|
37
22
|
export interface LocalFileAction {
|
|
38
|
-
internal_triggerLocalFileToolCalling:
|
|
23
|
+
internal_triggerLocalFileToolCalling: (
|
|
39
24
|
id: string,
|
|
40
|
-
callingService: () => Promise<{ content: any; state?:
|
|
25
|
+
callingService: () => Promise<{ content: string; error?: any; state?: any; success: boolean }>,
|
|
41
26
|
) => Promise<boolean>;
|
|
42
27
|
|
|
43
28
|
// File Operations
|
|
@@ -63,6 +48,8 @@ export interface LocalFileAction {
|
|
|
63
48
|
}
|
|
64
49
|
/* eslint-enable typescript-sort-keys/interface */
|
|
65
50
|
|
|
51
|
+
const runtime = new LocalSystemExecutionRuntime();
|
|
52
|
+
|
|
66
53
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
67
54
|
export const localSystemSlice: StateCreator<
|
|
68
55
|
ChatStore,
|
|
@@ -72,148 +59,49 @@ export const localSystemSlice: StateCreator<
|
|
|
72
59
|
> = (set, get) => ({
|
|
73
60
|
// ==================== File Editing ====================
|
|
74
61
|
editLocalFile: async (id, params) => {
|
|
75
|
-
return get().internal_triggerLocalFileToolCalling
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const message = result.success
|
|
79
|
-
? `Successfully replaced ${result.replacements} occurrence(s) in ${params.file_path}`
|
|
80
|
-
: `Edit failed: ${result.error}`;
|
|
81
|
-
|
|
82
|
-
const state: EditLocalFileState = { message, result };
|
|
83
|
-
|
|
84
|
-
return { content: result, state };
|
|
62
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
63
|
+
return await runtime.editLocalFile(params);
|
|
85
64
|
});
|
|
86
65
|
},
|
|
87
66
|
|
|
88
67
|
writeLocalFile: async (id, params) => {
|
|
89
68
|
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let content: { message: string; success: boolean };
|
|
93
|
-
|
|
94
|
-
if (result.success) {
|
|
95
|
-
content = {
|
|
96
|
-
message: `成功写入文件 ${params.path}`,
|
|
97
|
-
success: true,
|
|
98
|
-
};
|
|
99
|
-
} else {
|
|
100
|
-
const errorMessage = result.error;
|
|
101
|
-
|
|
102
|
-
content = { message: errorMessage || '写入文件失败', success: false };
|
|
103
|
-
}
|
|
104
|
-
return { content };
|
|
69
|
+
return await runtime.writeLocalFile(params);
|
|
105
70
|
});
|
|
106
71
|
},
|
|
107
72
|
moveLocalFiles: async (id, params) => {
|
|
108
|
-
return get().internal_triggerLocalFileToolCalling
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// 检查所有文件是否成功移动以更新消息内容
|
|
112
|
-
const allSucceeded = results.every((r) => r.success);
|
|
113
|
-
const someFailed = results.some((r) => !r.success);
|
|
114
|
-
const successCount = results.filter((r) => r.success).length;
|
|
115
|
-
const failedCount = results.length - successCount;
|
|
116
|
-
|
|
117
|
-
let message = '';
|
|
118
|
-
|
|
119
|
-
if (allSucceeded) {
|
|
120
|
-
message = `Successfully moved ${results.length} item(s).`;
|
|
121
|
-
} else if (someFailed) {
|
|
122
|
-
message = `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
|
|
123
|
-
} else {
|
|
124
|
-
// 所有都失败了?
|
|
125
|
-
message = `Failed to move all ${results.length} item(s).`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const state: LocalMoveFilesState = { results, successCount, totalCount: results.length };
|
|
129
|
-
|
|
130
|
-
return { content: { message, results }, state };
|
|
73
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
74
|
+
return await runtime.moveLocalFiles(params);
|
|
131
75
|
});
|
|
132
76
|
},
|
|
133
77
|
renameLocalFile: async (id, params) => {
|
|
134
|
-
return get().internal_triggerLocalFileToolCalling
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Basic validation for newName (can be done here or backend, maybe better in backend)
|
|
138
|
-
if (
|
|
139
|
-
!newName ||
|
|
140
|
-
newName.includes('/') ||
|
|
141
|
-
newName.includes('\\') ||
|
|
142
|
-
newName === '.' ||
|
|
143
|
-
newName === '..' ||
|
|
144
|
-
/["*/:<>?\\|]/.test(newName)
|
|
145
|
-
) {
|
|
146
|
-
throw new Error(
|
|
147
|
-
'Invalid new name provided. It cannot be empty, contain path separators, or invalid characters.',
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const result = await localFileService.renameLocalFile({ newName, path: currentPath }); // Call the specific service
|
|
152
|
-
|
|
153
|
-
let state: LocalRenameFileState;
|
|
154
|
-
let content: { message: string; success: boolean };
|
|
155
|
-
|
|
156
|
-
if (result.success) {
|
|
157
|
-
state = { newPath: result.newPath!, oldPath: currentPath, success: true };
|
|
158
|
-
// Simplified message
|
|
159
|
-
content = {
|
|
160
|
-
message: `Successfully renamed file ${currentPath} to ${newName}.`,
|
|
161
|
-
success: true,
|
|
162
|
-
};
|
|
163
|
-
} else {
|
|
164
|
-
const errorMessage = result.error;
|
|
165
|
-
state = {
|
|
166
|
-
error: errorMessage,
|
|
167
|
-
newPath: '',
|
|
168
|
-
oldPath: params.path,
|
|
169
|
-
success: false,
|
|
170
|
-
};
|
|
171
|
-
content = { message: errorMessage, success: false };
|
|
172
|
-
}
|
|
173
|
-
return { content, state };
|
|
78
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
79
|
+
return await runtime.renameLocalFile(params);
|
|
174
80
|
});
|
|
175
81
|
},
|
|
176
82
|
|
|
177
83
|
// ==================== Search & Find ====================
|
|
178
84
|
grepContent: async (id, params) => {
|
|
179
|
-
return get().internal_triggerLocalFileToolCalling
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const message = result.success
|
|
183
|
-
? `Found ${result.total_matches} matches in ${result.matches.length} locations`
|
|
184
|
-
: 'Search failed';
|
|
185
|
-
|
|
186
|
-
const state: GrepContentState = { message, result };
|
|
187
|
-
|
|
188
|
-
return { content: result, state };
|
|
85
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
86
|
+
return await runtime.grepContent(params);
|
|
189
87
|
});
|
|
190
88
|
},
|
|
191
89
|
|
|
192
90
|
globLocalFiles: async (id, params) => {
|
|
193
|
-
return get().internal_triggerLocalFileToolCalling
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const message = result.success ? `Found ${result.total_files} files` : 'Glob search failed';
|
|
197
|
-
|
|
198
|
-
const state: GlobFilesState = { message, result };
|
|
199
|
-
|
|
200
|
-
return { content: result, state };
|
|
91
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
92
|
+
return await runtime.globLocalFiles(params);
|
|
201
93
|
});
|
|
202
94
|
},
|
|
203
95
|
|
|
204
96
|
searchLocalFiles: async (id, params) => {
|
|
205
|
-
return get().internal_triggerLocalFileToolCalling
|
|
206
|
-
|
|
207
|
-
const state: LocalFileSearchState = { searchResults: result };
|
|
208
|
-
return { content: result, state };
|
|
97
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
98
|
+
return await runtime.searchLocalFiles(params);
|
|
209
99
|
});
|
|
210
100
|
},
|
|
211
101
|
|
|
212
102
|
listLocalFiles: async (id, params) => {
|
|
213
|
-
return get().internal_triggerLocalFileToolCalling
|
|
214
|
-
|
|
215
|
-
const state: LocalFileListState = { listResults: result };
|
|
216
|
-
return { content: result, state };
|
|
103
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
104
|
+
return await runtime.listLocalFiles(params);
|
|
217
105
|
});
|
|
218
106
|
},
|
|
219
107
|
|
|
@@ -226,67 +114,31 @@ export const localSystemSlice: StateCreator<
|
|
|
226
114
|
},
|
|
227
115
|
|
|
228
116
|
readLocalFile: async (id, params) => {
|
|
229
|
-
return get().internal_triggerLocalFileToolCalling
|
|
230
|
-
|
|
231
|
-
const state: LocalReadFileState = { fileContent: result };
|
|
232
|
-
return { content: result, state };
|
|
117
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
118
|
+
return await runtime.readLocalFile(params);
|
|
233
119
|
});
|
|
234
120
|
},
|
|
235
121
|
|
|
236
122
|
readLocalFiles: async (id, params) => {
|
|
237
|
-
return get().internal_triggerLocalFileToolCalling
|
|
238
|
-
|
|
239
|
-
const state: LocalReadFilesState = { filesContent: results };
|
|
240
|
-
return { content: results, state };
|
|
123
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
124
|
+
return await runtime.readLocalFiles(params);
|
|
241
125
|
});
|
|
242
126
|
},
|
|
243
127
|
|
|
244
128
|
// ==================== Shell Commands ====================
|
|
245
129
|
runCommand: async (id, params) => {
|
|
246
|
-
return get().internal_triggerLocalFileToolCalling
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
let message: string;
|
|
250
|
-
|
|
251
|
-
if (result.success) {
|
|
252
|
-
if (result.shell_id) {
|
|
253
|
-
message = `Command started in background with shell_id: ${result.shell_id}`;
|
|
254
|
-
} else {
|
|
255
|
-
message = `Command completed successfully.`;
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
message = `Command failed: ${result.error}`;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const state: RunCommandState = { message, result };
|
|
262
|
-
|
|
263
|
-
return { content: result, state };
|
|
130
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
131
|
+
return await runtime.runCommand(params);
|
|
264
132
|
});
|
|
265
133
|
},
|
|
266
134
|
killCommand: async (id, params) => {
|
|
267
|
-
return get().internal_triggerLocalFileToolCalling
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const message = result.success
|
|
271
|
-
? `Successfully killed shell: ${params.shell_id}`
|
|
272
|
-
: `Failed to kill shell: ${result.error}`;
|
|
273
|
-
|
|
274
|
-
const state: KillCommandState = { message, result };
|
|
275
|
-
|
|
276
|
-
return { content: result, state };
|
|
135
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
136
|
+
return await runtime.killCommand(params);
|
|
277
137
|
});
|
|
278
138
|
},
|
|
279
139
|
getCommandOutput: async (id, params) => {
|
|
280
|
-
return get().internal_triggerLocalFileToolCalling
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const message = result.success
|
|
284
|
-
? `Output retrieved. Running: ${result.running}`
|
|
285
|
-
: `Failed: ${result.error}`;
|
|
286
|
-
|
|
287
|
-
const state: GetCommandOutputState = { message, result };
|
|
288
|
-
|
|
289
|
-
return { content: result, state };
|
|
140
|
+
return get().internal_triggerLocalFileToolCalling(id, async () => {
|
|
141
|
+
return await runtime.getCommandOutput(params);
|
|
290
142
|
});
|
|
291
143
|
},
|
|
292
144
|
|
|
@@ -305,11 +157,22 @@ export const localSystemSlice: StateCreator<
|
|
|
305
157
|
internal_triggerLocalFileToolCalling: async (id, callingService) => {
|
|
306
158
|
get().toggleLocalFileLoading(id, true);
|
|
307
159
|
try {
|
|
308
|
-
const { state, content } = await callingService();
|
|
309
|
-
|
|
310
|
-
|
|
160
|
+
const { state, content, success, error } = await callingService();
|
|
161
|
+
|
|
162
|
+
if (success) {
|
|
163
|
+
if (state) {
|
|
164
|
+
await get().optimisticUpdatePluginState(id, state);
|
|
165
|
+
}
|
|
166
|
+
await get().optimisticUpdateMessageContent(id, content);
|
|
167
|
+
} else {
|
|
168
|
+
await get().optimisticUpdateMessagePluginError(id, {
|
|
169
|
+
body: error,
|
|
170
|
+
message: error?.message || 'Operation failed',
|
|
171
|
+
type: 'PluginServerError',
|
|
172
|
+
});
|
|
173
|
+
// Still update content even if failed, to show error message
|
|
174
|
+
await get().optimisticUpdateMessageContent(id, content);
|
|
311
175
|
}
|
|
312
|
-
await get().optimisticUpdateMessageContent(id, JSON.stringify(content));
|
|
313
176
|
} catch (error) {
|
|
314
177
|
await get().optimisticUpdateMessagePluginError(id, {
|
|
315
178
|
body: error,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { LocalSystemManifest } from './local-system';
|
|
2
|
+
import { LocalSystemExecutionRuntime } from './local-system/ExecutionRuntime';
|
|
1
3
|
import { WebBrowsingManifest } from './web-browsing';
|
|
2
4
|
import { WebBrowsingExecutionRuntime } from './web-browsing/ExecutionRuntime';
|
|
3
5
|
|
|
4
6
|
export const BuiltinToolServerRuntimes: Record<string, any> = {
|
|
7
|
+
[LocalSystemManifest.identifier]: LocalSystemExecutionRuntime,
|
|
5
8
|
[WebBrowsingManifest.identifier]: WebBrowsingExecutionRuntime,
|
|
6
9
|
};
|