@myassis/gateway 1.0.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/README.md +194 -0
- package/dist/.env +6 -0
- package/dist/api/index.js +182 -0
- package/dist/config/index.js +41 -0
- package/dist/index.js +183 -0
- package/dist/middleware/auth.js +53 -0
- package/dist/middleware/errorHandler.js +20 -0
- package/dist/routes/agent.js +513 -0
- package/dist/routes/auth.js +172 -0
- package/dist/routes/chat.js +45 -0
- package/dist/routes/config.js +21 -0
- package/dist/routes/models.js +123 -0
- package/dist/routes/service.js +240 -0
- package/dist/routes/settings.js +101 -0
- package/dist/routes/skillHub.js +126 -0
- package/dist/routes/skills.js +159 -0
- package/dist/routes/tasks.js +149 -0
- package/dist/routes/upload.js +129 -0
- package/dist/routes/version.js +66 -0
- package/dist/services/HMSPushService.js +24 -0
- package/dist/services/LocalTaskService.js +223 -0
- package/dist/services/NotificationService.js +242 -0
- package/dist/services/ServiceManager.js +348 -0
- package/dist/services/TaskSchedulerService.js +195 -0
- package/dist/services/TaskService.js +240 -0
- package/dist/services/WebSocketService.js +236 -0
- package/dist/services/agent/Agent.js +120 -0
- package/dist/services/agent/AgentManager.js +265 -0
- package/dist/services/agent/AgentStore.js +73 -0
- package/dist/services/dataService.js +293 -0
- package/dist/services/index.js +15 -0
- package/dist/services/llm/LLMClient.js +724 -0
- package/dist/services/memory/MemoryManager.js +117 -0
- package/dist/services/model/ModelCapabilities.js +141 -0
- package/dist/services/model/index.js +4 -0
- package/dist/services/models.js +16 -0
- package/dist/services/session/MigrationManager.js +176 -0
- package/dist/services/session/Session.js +733 -0
- package/dist/services/session/SessionManager.js +255 -0
- package/dist/services/session/SessionStore.js +186 -0
- package/dist/services/session/index.js +3 -0
- package/dist/services/skills.js +34 -0
- package/dist/services/systemPrompt.js +150 -0
- package/dist/services/task/PushTokenStore.js +124 -0
- package/dist/services/task/TaskStore.js +143 -0
- package/dist/services/tools/calculator.js +27 -0
- package/dist/services/tools/edit.js +318 -0
- package/dist/services/tools/exec.js +119 -0
- package/dist/services/tools/fetch.js +155 -0
- package/dist/services/tools/file.js +315 -0
- package/dist/services/tools/index.js +48 -0
- package/dist/services/tools/keyboard.js +145 -0
- package/dist/services/tools/model.js +86 -0
- package/dist/services/tools/mouse.js +55 -0
- package/dist/services/tools/screenshot.js +19 -0
- package/dist/services/tools/search.js +53 -0
- package/dist/services/tools/skill.js +108 -0
- package/dist/services/tools/task.js +110 -0
- package/dist/services/tools/types.js +1 -0
- package/dist/services/tools/webFetch.js +34 -0
- package/dist/stores/authStore.js +178 -0
- package/dist/stores/index.js +6 -0
- package/dist/stores/memoryStore.js +191 -0
- package/dist/stores/persistStore.js +317 -0
- package/package.json +94 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { keyboard, Key } from '@nut-tree/nut-js';
|
|
2
|
+
// 可打印 ASCII 字符范围(空格到 ~)
|
|
3
|
+
const PRINTABLE_ASCII_START = 32;
|
|
4
|
+
const PRINTABLE_ASCII_END = 126;
|
|
5
|
+
// 校验文本中的所有字符是否都是键盘可输入的字符
|
|
6
|
+
function validateText(text) {
|
|
7
|
+
for (let i = 0; i < text.length; i++) {
|
|
8
|
+
const charCode = text.charCodeAt(i);
|
|
9
|
+
// 检查是否在可打印 ASCII 范围内
|
|
10
|
+
if (charCode < PRINTABLE_ASCII_START || charCode > PRINTABLE_ASCII_END) {
|
|
11
|
+
return { valid: false, invalidChar: text[i], charCode };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return { valid: true };
|
|
15
|
+
}
|
|
16
|
+
// 按键名称到 nut-js Key 的映射
|
|
17
|
+
const keyMap = {
|
|
18
|
+
// 常用键
|
|
19
|
+
enter: Key.Enter,
|
|
20
|
+
return: Key.Enter,
|
|
21
|
+
escape: Key.Escape,
|
|
22
|
+
esc: Key.Escape,
|
|
23
|
+
tab: Key.Tab,
|
|
24
|
+
space: Key.Space,
|
|
25
|
+
backspace: Key.Backspace,
|
|
26
|
+
delete: Key.Delete,
|
|
27
|
+
del: Key.Delete,
|
|
28
|
+
// 方向键
|
|
29
|
+
up: Key.Up,
|
|
30
|
+
down: Key.Down,
|
|
31
|
+
left: Key.Left,
|
|
32
|
+
right: Key.Right,
|
|
33
|
+
// 功能键
|
|
34
|
+
f1: Key.F1,
|
|
35
|
+
f2: Key.F2,
|
|
36
|
+
f3: Key.F3,
|
|
37
|
+
f4: Key.F4,
|
|
38
|
+
f5: Key.F5,
|
|
39
|
+
f6: Key.F6,
|
|
40
|
+
f7: Key.F7,
|
|
41
|
+
f8: Key.F8,
|
|
42
|
+
f9: Key.F9,
|
|
43
|
+
f10: Key.F10,
|
|
44
|
+
f11: Key.F11,
|
|
45
|
+
f12: Key.F12,
|
|
46
|
+
// 修饰键
|
|
47
|
+
ctrl: Key.LeftControl,
|
|
48
|
+
control: Key.LeftControl,
|
|
49
|
+
alt: Key.LeftAlt,
|
|
50
|
+
shift: Key.LeftShift,
|
|
51
|
+
meta: Key.LeftSuper,
|
|
52
|
+
win: Key.LeftSuper,
|
|
53
|
+
cmd: Key.LeftSuper,
|
|
54
|
+
command: Key.LeftSuper,
|
|
55
|
+
};
|
|
56
|
+
// 解析组合键,如 "Ctrl+C" 或 "Alt+Shift+F4"
|
|
57
|
+
function parseHotkey(keysStr) {
|
|
58
|
+
const parts = keysStr.split(/[+\-]/).map(s => s.trim().toLowerCase());
|
|
59
|
+
const keys = [];
|
|
60
|
+
for (const part of parts) {
|
|
61
|
+
const mappedKey = keyMap[part];
|
|
62
|
+
if (mappedKey) {
|
|
63
|
+
keys.push(mappedKey);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// 处理单个字符
|
|
67
|
+
if (part.length === 1) {
|
|
68
|
+
keys.push(part);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return keys;
|
|
73
|
+
}
|
|
74
|
+
// 解析单个按键
|
|
75
|
+
function parseKey(keyStr) {
|
|
76
|
+
const lowerKey = keyStr.toLowerCase();
|
|
77
|
+
if (keyMap[lowerKey]) {
|
|
78
|
+
return keyMap[lowerKey];
|
|
79
|
+
}
|
|
80
|
+
// 单个字符直接返回
|
|
81
|
+
if (keyStr.length === 1) {
|
|
82
|
+
return keyStr;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`未知按键: ${keyStr}`);
|
|
85
|
+
}
|
|
86
|
+
export const keyboardTool = {
|
|
87
|
+
name: 'keyboard',
|
|
88
|
+
description: '控制键盘输入和按键操作',
|
|
89
|
+
parameters: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
action: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
description: '操作类型:type(输入文本)、press(按压键)、hotkey(组合键)',
|
|
95
|
+
enum: ['type', 'press', 'hotkey'],
|
|
96
|
+
},
|
|
97
|
+
text: { type: 'string', description: '要输入的文本(type 必填)' },
|
|
98
|
+
key: { type: 'string', description: '按键名称(press 必填),如 Enter、Escape、Tab' },
|
|
99
|
+
keys: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
description: '组合键,如 Ctrl+C、Alt+F4(hotkey 必填)',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
required: ['action'],
|
|
105
|
+
},
|
|
106
|
+
handler: async (args) => {
|
|
107
|
+
try {
|
|
108
|
+
const { action, text, key, keys } = args;
|
|
109
|
+
switch (action) {
|
|
110
|
+
case 'type':
|
|
111
|
+
if (typeof text !== 'string') {
|
|
112
|
+
return { success: false, errorMessage: 'type 操作需要 text 参数' };
|
|
113
|
+
}
|
|
114
|
+
const validation = validateText(text);
|
|
115
|
+
if (!validation.valid) {
|
|
116
|
+
return { success: false, errorMessage: `文本包含不可输入字符: "${validation.invalidChar}" (字符码: ${validation.charCode})` };
|
|
117
|
+
}
|
|
118
|
+
await keyboard.type(text);
|
|
119
|
+
return { success: true, output: `已输入文本: "${text}"` };
|
|
120
|
+
case 'press':
|
|
121
|
+
if (typeof key !== 'string') {
|
|
122
|
+
return { success: false, errorMessage: 'press 操作需要 key 参数' };
|
|
123
|
+
}
|
|
124
|
+
const parsedKey = parseKey(key);
|
|
125
|
+
await keyboard.pressKey(parsedKey);
|
|
126
|
+
return { success: true, output: `已按下按键: ${key}` };
|
|
127
|
+
case 'hotkey':
|
|
128
|
+
if (typeof keys !== 'string') {
|
|
129
|
+
return { success: false, errorMessage: 'hotkey 操作需要 keys 参数' };
|
|
130
|
+
}
|
|
131
|
+
const hotkeys = parseHotkey(keys);
|
|
132
|
+
if (hotkeys.length === 0) {
|
|
133
|
+
return { success: false, errorMessage: '无法解析组合键' };
|
|
134
|
+
}
|
|
135
|
+
await keyboard.pressKey(...hotkeys);
|
|
136
|
+
return { success: true, output: `已执行组合键: ${keys}` };
|
|
137
|
+
default:
|
|
138
|
+
return { success: false, errorMessage: `未知的键盘操作: ${action}` };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return { success: false, errorMessage: `键盘操作失败: ${error?.message || error}` };
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { modelsService } from '../dataService';
|
|
2
|
+
export const modelTool = {
|
|
3
|
+
name: 'model',
|
|
4
|
+
description: '模型管理工具',
|
|
5
|
+
parameters: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
action: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
description: '操作类型:list、add、update、delete、setPrimary',
|
|
11
|
+
enum: ['list', 'add', 'update', 'delete', 'setPrimary'],
|
|
12
|
+
},
|
|
13
|
+
modelId: { type: 'string', description: '模型 ID(update/delete/setPrimary)' },
|
|
14
|
+
modelName: { type: 'string', description: '模型名称(add/update)' },
|
|
15
|
+
modelIdName: { type: 'string', description: '模型 ID 名称(add)' },
|
|
16
|
+
baseUrl: { type: 'string', description: 'API 地址(add/update)' },
|
|
17
|
+
apiKey: { type: 'string', description: 'API Key(add)' },
|
|
18
|
+
provider: { type: 'string', description: '提供商', default: 'doubao' },
|
|
19
|
+
isPrimary: { type: 'boolean', description: '设为默认', default: false },
|
|
20
|
+
supportsToolCall: { type: 'boolean', description: '支持工具调用', default: true },
|
|
21
|
+
},
|
|
22
|
+
required: ['action'],
|
|
23
|
+
},
|
|
24
|
+
handler: async (args) => {
|
|
25
|
+
try {
|
|
26
|
+
const { action, modelId, modelName, modelIdName, baseUrl, apiKey, provider = 'doubao', isPrimary = false, supportsToolCall = true } = args;
|
|
27
|
+
switch (action) {
|
|
28
|
+
case 'list': {
|
|
29
|
+
const response = await modelsService.list();
|
|
30
|
+
if (!response.success)
|
|
31
|
+
return { success: false, errorMessage: response.error || '获取列表失败' };
|
|
32
|
+
const models = response.data || [];
|
|
33
|
+
return { success: true, output: JSON.stringify({ total: models.length, models: models.map((m) => ({ id: m.id, name: m.modelName, provider: m.provider, baseUrl: m.baseUrl, isPrimary: m.isPrimary, supportsToolCall: m.supportsToolCall })) }) };
|
|
34
|
+
}
|
|
35
|
+
case 'add': {
|
|
36
|
+
if (!modelName || !baseUrl || !modelIdName)
|
|
37
|
+
return { success: false, errorMessage: '添加模型需要提供模型名称、模型 ID 和 API 地址' };
|
|
38
|
+
const response = await modelsService.create({ modelName, modelId: modelIdName, baseUrl, apiKey, provider, isPrimary, supportsToolCall });
|
|
39
|
+
if (!response.success)
|
|
40
|
+
return { success: false, errorMessage: response.error || '添加失败' };
|
|
41
|
+
return { success: true, output: JSON.stringify({ model: response.data }) };
|
|
42
|
+
}
|
|
43
|
+
case 'update': {
|
|
44
|
+
if (!modelId)
|
|
45
|
+
return { success: false, errorMessage: '修改模型需要提供模型 ID' };
|
|
46
|
+
const updateData = {};
|
|
47
|
+
if (modelName !== undefined)
|
|
48
|
+
updateData.modelName = modelName;
|
|
49
|
+
if (baseUrl !== undefined)
|
|
50
|
+
updateData.baseUrl = baseUrl;
|
|
51
|
+
if (provider !== undefined)
|
|
52
|
+
updateData.provider = provider;
|
|
53
|
+
if (isPrimary !== undefined)
|
|
54
|
+
updateData.isPrimary = isPrimary;
|
|
55
|
+
if (supportsToolCall !== undefined)
|
|
56
|
+
updateData.supportsToolCall = supportsToolCall;
|
|
57
|
+
const response = await modelsService.update(modelId, updateData);
|
|
58
|
+
if (!response.success)
|
|
59
|
+
return { success: false, errorMessage: response.error || '更新失败' };
|
|
60
|
+
return { success: true, output: JSON.stringify({ model: response.data }) };
|
|
61
|
+
}
|
|
62
|
+
case 'delete': {
|
|
63
|
+
if (!modelId)
|
|
64
|
+
return { success: false, errorMessage: '删除模型需要提供模型 ID' };
|
|
65
|
+
const response = await modelsService.delete(modelId);
|
|
66
|
+
if (!response.success)
|
|
67
|
+
return { success: false, errorMessage: response.error || '删除失败' };
|
|
68
|
+
return { success: true };
|
|
69
|
+
}
|
|
70
|
+
case 'setPrimary': {
|
|
71
|
+
if (!modelId)
|
|
72
|
+
return { success: false, errorMessage: '设为默认需要提供模型 ID' };
|
|
73
|
+
const response = await modelsService.setPrimary(modelId);
|
|
74
|
+
if (!response.success)
|
|
75
|
+
return { success: false, errorMessage: response.error || '设置失败' };
|
|
76
|
+
return { success: true };
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
return { success: false, errorMessage: `未知的模型操作: ${action}` };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return { success: false, errorMessage: `模型操作失败: ${error?.message}` };
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mouse, Button } from '@nut-tree/nut-js';
|
|
2
|
+
export const mouseTool = {
|
|
3
|
+
name: 'mouse',
|
|
4
|
+
description: '控制鼠标移动、点击和滚轮操作',
|
|
5
|
+
parameters: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
action: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
description: '鼠标操作类型:move、click、rightClick、doubleClick、scroll、position',
|
|
11
|
+
},
|
|
12
|
+
x: { type: 'number', description: '目标 X 坐标(move 必填)' },
|
|
13
|
+
y: { type: 'number', description: '目标 Y 坐标(move 必填)' },
|
|
14
|
+
button: { type: 'string', description: '鼠标按钮:left、right、middle,默认 left', default: 'left' },
|
|
15
|
+
scrollX: { type: 'number', description: '水平滚动量', default: 0 },
|
|
16
|
+
scrollY: { type: 'number', description: '垂直滚动量', default: 0 },
|
|
17
|
+
},
|
|
18
|
+
required: ['action'],
|
|
19
|
+
},
|
|
20
|
+
handler: async (args) => {
|
|
21
|
+
try {
|
|
22
|
+
const { action, x, y, button = 'left', scrollX = 0, scrollY = 0 } = args;
|
|
23
|
+
switch (action) {
|
|
24
|
+
case 'move':
|
|
25
|
+
if (typeof x !== 'number' || typeof y !== 'number') {
|
|
26
|
+
return { success: false, errorMessage: 'move 操作需要 x 和 y 坐标' };
|
|
27
|
+
}
|
|
28
|
+
await mouse.setPosition({ x, y });
|
|
29
|
+
return { success: true, output: `鼠标移动到 (${x}, ${y})` };
|
|
30
|
+
case 'click':
|
|
31
|
+
case 'rightClick':
|
|
32
|
+
case 'doubleClick':
|
|
33
|
+
if (action === 'doubleClick') {
|
|
34
|
+
await mouse.click(Button.LEFT);
|
|
35
|
+
await new Promise(r => setTimeout(r, 100));
|
|
36
|
+
}
|
|
37
|
+
const btn = button === 'right' ? Button.RIGHT : button === 'middle' ? Button.MIDDLE : Button.LEFT;
|
|
38
|
+
await mouse.click(btn);
|
|
39
|
+
return { success: true, output: `${button} 键${action === 'doubleClick' ? '双' : ''}击成功` };
|
|
40
|
+
case 'scroll':
|
|
41
|
+
await mouse.scrollDown(scrollY);
|
|
42
|
+
await mouse.scrollRight(scrollX);
|
|
43
|
+
return { success: true, output: `滚动 (${scrollX}, ${scrollY})` };
|
|
44
|
+
case 'position':
|
|
45
|
+
const pos = await mouse.getPosition();
|
|
46
|
+
return { success: true, output: `当前鼠标位置 (${pos.x}, ${pos.y})` };
|
|
47
|
+
default:
|
|
48
|
+
return { success: false, errorMessage: `未知的鼠标操作: ${action}` };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return { success: false, errorMessage: `鼠标操作失败: ${error?.message || error}` };
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import screenshot from 'screenshot-desktop';
|
|
2
|
+
export const screenshotTool = {
|
|
3
|
+
name: 'screenshot',
|
|
4
|
+
description: '截取当前电脑屏幕的截图,这个操作属于危险操作,没有用户的主动允许禁止使用',
|
|
5
|
+
parameters: { type: 'object', properties: {}, required: [] },
|
|
6
|
+
handler: async () => {
|
|
7
|
+
try {
|
|
8
|
+
const imgBuffer = await screenshot({ format: 'jpeg', quality: 20 });
|
|
9
|
+
const base64 = imgBuffer.toString('base64');
|
|
10
|
+
return {
|
|
11
|
+
success: true,
|
|
12
|
+
output: JSON.stringify({ image: `data:image/jpeg;base64,${base64}`, size: imgBuffer.length }),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return { success: false, errorMessage: `截图失败: ${error?.message || error}` };
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import got from 'got';
|
|
2
|
+
import * as cheerio from 'cheerio';
|
|
3
|
+
export const searchTool = {
|
|
4
|
+
name: 'search',
|
|
5
|
+
description: '执行网络搜索',
|
|
6
|
+
parameters: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
query: { type: 'string', description: '搜索关键词' },
|
|
10
|
+
limit: { type: 'number', description: '返回结果数量上限', default: 5 },
|
|
11
|
+
},
|
|
12
|
+
required: ['query'],
|
|
13
|
+
},
|
|
14
|
+
handler: async (args) => {
|
|
15
|
+
try {
|
|
16
|
+
const { query, limit = 5 } = args;
|
|
17
|
+
const response = await got(`https://cn.bing.com/search?q=${encodeURIComponent(query)}`, {
|
|
18
|
+
headers: {
|
|
19
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
20
|
+
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
21
|
+
},
|
|
22
|
+
timeout: { request: 10000 },
|
|
23
|
+
});
|
|
24
|
+
const $ = cheerio.load(response.body);
|
|
25
|
+
const results = [];
|
|
26
|
+
$('li.b_algo').each((_, el) => {
|
|
27
|
+
if (results.length >= limit)
|
|
28
|
+
return false;
|
|
29
|
+
const titleEl = $(el).find('h2 a');
|
|
30
|
+
const title = titleEl.text().trim();
|
|
31
|
+
const url = titleEl.attr('href') || '';
|
|
32
|
+
const snippet = $(el).find('div.b_caption p').text().trim();
|
|
33
|
+
if (title)
|
|
34
|
+
results.push({ title, url, snippet });
|
|
35
|
+
});
|
|
36
|
+
if (results.length === 0) {
|
|
37
|
+
$('div.b_listnav_bing').find('li').each((_, el) => {
|
|
38
|
+
if (results.length >= limit)
|
|
39
|
+
return false;
|
|
40
|
+
const titleEl = $(el).find('a');
|
|
41
|
+
const title = titleEl.text().trim();
|
|
42
|
+
const url = titleEl.attr('href') || '';
|
|
43
|
+
if (title && url)
|
|
44
|
+
results.push({ title, url, snippet: '' });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return { success: true, output: JSON.stringify({ query, total: results.length, results }) };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return { success: false, errorMessage: `搜索失败: ${error.message}` };
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { skillsService, skillHubService } from '../dataService';
|
|
2
|
+
import { persistStore } from '@/stores';
|
|
3
|
+
import { getLogger } from '@pocketclaw/shared';
|
|
4
|
+
const logger = getLogger('skill');
|
|
5
|
+
export const skillTool = {
|
|
6
|
+
name: 'skill',
|
|
7
|
+
description: '技能管理工具',
|
|
8
|
+
parameters: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
action: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: '操作类型:listInstalled、search、install、uninstall、setApiKey、rate、detail、getApiKey',
|
|
14
|
+
enum: ['listInstalled', 'search', 'install', 'uninstall', 'setApiKey', 'rate', 'detail', 'getApiKey'],
|
|
15
|
+
},
|
|
16
|
+
query: { type: 'string', description: '搜索关键词' },
|
|
17
|
+
skillId: { type: 'string', description: '技能 ID(uninstall/detail/setApiKey/getApiKey)' },
|
|
18
|
+
hubId: { type: 'string', description: '技能市场 ID(search/rate)' },
|
|
19
|
+
apiKey: { type: 'string', description: 'API Key(setApiKey)' },
|
|
20
|
+
apiBaseUrl: { type: 'string', description: 'API 地址(setApiKey)' },
|
|
21
|
+
rating: { type: 'number', description: '评分 1-5(rate)' },
|
|
22
|
+
},
|
|
23
|
+
required: ['action'],
|
|
24
|
+
},
|
|
25
|
+
handler: async (args) => {
|
|
26
|
+
try {
|
|
27
|
+
const { action, query, skillId, hubId, apiKey, apiBaseUrl, rating } = args;
|
|
28
|
+
switch (action) {
|
|
29
|
+
case 'listInstalled': {
|
|
30
|
+
const response = await skillsService.list();
|
|
31
|
+
if (!response.success)
|
|
32
|
+
return { success: false, errorMessage: response.error || '获取列表失败' };
|
|
33
|
+
const skills = response.data || [];
|
|
34
|
+
return { success: true, output: JSON.stringify({ total: skills.length, skills: skills.map((s) => ({ id: s.id, name: s.name, description: s.description, version: s.version, status: s.status })) }) };
|
|
35
|
+
}
|
|
36
|
+
case 'search': {
|
|
37
|
+
const response = await skillHubService.list({ keyword: query, pageSize: 10 });
|
|
38
|
+
if (!response.success)
|
|
39
|
+
return { success: false, errorMessage: response.error || '搜索失败' };
|
|
40
|
+
const skills = response.data?.items || response.data || [];
|
|
41
|
+
return { success: true, output: JSON.stringify({ query, total: skills.length, skills: skills.map((s) => ({ id: s.id, hubId: s.hubId, name: s.name, description: s.description, author: s.author, tags: s.tags, rating: s.rating })) }) };
|
|
42
|
+
}
|
|
43
|
+
case 'install': {
|
|
44
|
+
const installId = hubId || query;
|
|
45
|
+
if (!installId)
|
|
46
|
+
return { success: false, errorMessage: '安装需要提供 hubId 或 query' };
|
|
47
|
+
const response = await skillsService.install(installId);
|
|
48
|
+
if (!response.success)
|
|
49
|
+
return { success: false, errorMessage: response.error || '安装失败' };
|
|
50
|
+
return { success: true };
|
|
51
|
+
}
|
|
52
|
+
case 'uninstall': {
|
|
53
|
+
if (!skillId)
|
|
54
|
+
return { success: false, errorMessage: '卸载需要提供技能 ID' };
|
|
55
|
+
const response = await skillsService.uninstall(skillId);
|
|
56
|
+
if (!response.success)
|
|
57
|
+
return { success: false, errorMessage: response.error || '卸载失败' };
|
|
58
|
+
return { success: true };
|
|
59
|
+
}
|
|
60
|
+
case 'detail': {
|
|
61
|
+
if (!skillId)
|
|
62
|
+
return { success: false, errorMessage: '查看详情需要提供技能 ID' };
|
|
63
|
+
const response = await skillsService.get(skillId);
|
|
64
|
+
if (!response.success)
|
|
65
|
+
return { success: false, errorMessage: response.error || '获取详情失败' };
|
|
66
|
+
// 表明该技能已被使用
|
|
67
|
+
const detailHubId = response.data?.templateId;
|
|
68
|
+
if (detailHubId) {
|
|
69
|
+
skillHubService.use(detailHubId).catch((err) => logger.warn('技能使用记录失败:', err?.message));
|
|
70
|
+
}
|
|
71
|
+
const apikey = await skillsService.getApiKey(skillId);
|
|
72
|
+
return { success: true, output: JSON.stringify({ skill: { ...response.data, apiKey: apikey.apiKey } }) };
|
|
73
|
+
}
|
|
74
|
+
case 'setApiKey': {
|
|
75
|
+
if (!skillId || !apiKey)
|
|
76
|
+
return { success: false, errorMessage: '设置 API Key 需要提供技能 ID 和 API Key' };
|
|
77
|
+
const response = await skillsService.setApiKey(skillId, apiKey);
|
|
78
|
+
if (!response)
|
|
79
|
+
return { success: false, errorMessage: '设置失败' };
|
|
80
|
+
return { success: true };
|
|
81
|
+
}
|
|
82
|
+
case 'rate': {
|
|
83
|
+
if (!hubId || rating === undefined)
|
|
84
|
+
return { success: false, errorMessage: '评分需要提供 hubId 和评分(1-5)' };
|
|
85
|
+
if (rating < 1 || rating > 5)
|
|
86
|
+
return { success: false, errorMessage: '评分必须是 1-5 之间的整数' };
|
|
87
|
+
const response = await skillHubService.rate(hubId, rating);
|
|
88
|
+
if (!response.success)
|
|
89
|
+
return { success: false, errorMessage: response.error || '评分失败' };
|
|
90
|
+
return { success: true };
|
|
91
|
+
}
|
|
92
|
+
case 'getApiKey': {
|
|
93
|
+
if (!skillId)
|
|
94
|
+
return { success: false, errorMessage: '获取 API Key 需要提供技能 ID' };
|
|
95
|
+
const apikey = persistStore.getSkillApiKey(skillId);
|
|
96
|
+
if (apikey)
|
|
97
|
+
return { success: true, output: JSON.stringify({ apiKey: apikey.apiKey }) };
|
|
98
|
+
return { success: false, errorMessage: '未找到 API Key' };
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
return { success: false, errorMessage: `未知的技能操作: ${action}` };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return { success: false, errorMessage: `技能操作失败: ${error?.message}` };
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { tasksService } from '../dataService';
|
|
2
|
+
import { authStore } from '@/stores';
|
|
3
|
+
import { validPlatformApplies, getPlatform } from '@pocketclaw/shared/dist/utils/system.js';
|
|
4
|
+
export const taskTool = {
|
|
5
|
+
name: 'task',
|
|
6
|
+
description: '任务管理工具',
|
|
7
|
+
parameters: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
action: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: '操作类型:create、list、update、delete',
|
|
13
|
+
enum: ['create', 'list', 'update', 'delete'],
|
|
14
|
+
},
|
|
15
|
+
title: { type: 'string', description: '任务标题(create)' },
|
|
16
|
+
description: { type: 'string', description: '任务描述(create/update)' },
|
|
17
|
+
taskType: { type: 'string', description: '任务类型:one_time、recurring', enum: ['one_time', 'recurring'], default: 'one_time' },
|
|
18
|
+
recurrenceRule: { type: 'string', description: '重复规则', enum: ['daily', 'weekly', 'monthly', 'trading_day', 'work_day', 'interval'] },
|
|
19
|
+
intervalValue: { type: 'number', description: '间隔值(interval)' },
|
|
20
|
+
intervalUnit: { type: 'string', description: '间隔单位', enum: ['minutes', 'hours', 'days'] },
|
|
21
|
+
startTime: { type: 'string', description: '开始时间' },
|
|
22
|
+
endTime: { type: 'string', description: '结束时间' },
|
|
23
|
+
scheduledAt: { type: 'string', description: '计划执行时间' },
|
|
24
|
+
platformApply: { type: 'string', description: '适用平台', enum: validPlatformApplies, default: getPlatform() },
|
|
25
|
+
status: { type: 'string', description: '任务状态筛选', enum: ['pending', 'doing', 'done', 'cancelled'] },
|
|
26
|
+
taskId: { type: 'string', description: '任务 ID(update/delete)' },
|
|
27
|
+
newTitle: { type: 'string', description: '新标题(update)' },
|
|
28
|
+
newDescription: { type: 'string', description: '新描述(update)' },
|
|
29
|
+
newStatus: { type: 'string', description: '新状态(update)', enum: ['pending', 'doing', 'done', 'cancelled'] },
|
|
30
|
+
newRecurrenceRule: { type: 'string', description: '新重复规则(update)' },
|
|
31
|
+
newScheduledAt: { type: 'string', description: '新计划执行时间(update)' },
|
|
32
|
+
},
|
|
33
|
+
required: ['action'],
|
|
34
|
+
},
|
|
35
|
+
handler: async (args) => {
|
|
36
|
+
try {
|
|
37
|
+
const { action, title, description, taskType, recurrenceRule, intervalValue, intervalUnit, startTime, endTime, scheduledAt, platformApply, status, taskId, newTitle, newDescription, newStatus, newRecurrenceRule, newScheduledAt } = args;
|
|
38
|
+
switch (action) {
|
|
39
|
+
case 'create': {
|
|
40
|
+
if (!title)
|
|
41
|
+
return { success: false, errorMessage: '创建任务需要提供标题(title)' };
|
|
42
|
+
const createData = { title, taskType: taskType || 'one_time' };
|
|
43
|
+
if (description)
|
|
44
|
+
createData.description = description;
|
|
45
|
+
if (recurrenceRule)
|
|
46
|
+
createData.recurrenceRule = recurrenceRule;
|
|
47
|
+
if (intervalValue !== undefined)
|
|
48
|
+
createData.intervalValue = intervalValue;
|
|
49
|
+
if (intervalUnit)
|
|
50
|
+
createData.intervalUnit = intervalUnit;
|
|
51
|
+
if (startTime)
|
|
52
|
+
createData.startTime = startTime;
|
|
53
|
+
if (endTime)
|
|
54
|
+
createData.endTime = endTime;
|
|
55
|
+
if (scheduledAt)
|
|
56
|
+
createData.scheduledAt = scheduledAt;
|
|
57
|
+
const userId = authStore.getUserId();
|
|
58
|
+
if (!userId) {
|
|
59
|
+
return { success: false, errorMessage: '未登录' };
|
|
60
|
+
}
|
|
61
|
+
createData.platformApply = platformApply || getPlatform();
|
|
62
|
+
const response = await tasksService.create(createData, String(userId));
|
|
63
|
+
if (!response.success)
|
|
64
|
+
return { success: false, errorMessage: response.error || '创建任务失败' };
|
|
65
|
+
return { success: true, output: JSON.stringify({ task: response.data }) };
|
|
66
|
+
}
|
|
67
|
+
case 'list': {
|
|
68
|
+
const response = await tasksService.list({ status });
|
|
69
|
+
if (!response.success)
|
|
70
|
+
return { success: false, errorMessage: '获取任务列表失败' };
|
|
71
|
+
const tasks = response.data || [];
|
|
72
|
+
const stats = { total: tasks.length, pending: tasks.filter((t) => t.status === 'pending').length, doing: tasks.filter((t) => t.status === 'doing').length, done: tasks.filter((t) => t.status === 'done').length, cancelled: tasks.filter((t) => t.status === 'cancelled').length };
|
|
73
|
+
return { success: true, output: JSON.stringify({ stats, total: tasks.length, tasks: tasks.map((t) => ({ id: t.id, title: t.title, taskType: t.taskType, recurrenceRule: t.recurrenceRule, scheduledAt: t.scheduledAt, status: t.status })) }) };
|
|
74
|
+
}
|
|
75
|
+
case 'update': {
|
|
76
|
+
if (!taskId)
|
|
77
|
+
return { success: false, errorMessage: '修改任务需要提供任务 ID(taskId)' };
|
|
78
|
+
const updateData = {};
|
|
79
|
+
if (newTitle !== undefined)
|
|
80
|
+
updateData.title = newTitle;
|
|
81
|
+
if (newDescription !== undefined)
|
|
82
|
+
updateData.description = newDescription;
|
|
83
|
+
if (newStatus !== undefined)
|
|
84
|
+
updateData.status = newStatus;
|
|
85
|
+
if (newRecurrenceRule !== undefined)
|
|
86
|
+
updateData.recurrenceRule = newRecurrenceRule;
|
|
87
|
+
if (newScheduledAt !== undefined)
|
|
88
|
+
updateData.scheduledAt = newScheduledAt;
|
|
89
|
+
const response = await tasksService.update(taskId, updateData);
|
|
90
|
+
if (!response.success)
|
|
91
|
+
return { success: false, errorMessage: response.error || '更新任务失败' };
|
|
92
|
+
return { success: true };
|
|
93
|
+
}
|
|
94
|
+
case 'delete': {
|
|
95
|
+
if (!taskId)
|
|
96
|
+
return { success: false, errorMessage: '删除任务需要提供任务 ID(taskId)' };
|
|
97
|
+
const response = await tasksService.delete(taskId);
|
|
98
|
+
if (!response.success)
|
|
99
|
+
return { success: false, errorMessage: response.error || '删除任务失败' };
|
|
100
|
+
return { success: true };
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
return { success: false, errorMessage: `未知的任务操作: ${action}` };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return { success: false, errorMessage: `任务操作失败: ${error?.message}` };
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import got from 'got';
|
|
2
|
+
import * as cheerio from 'cheerio';
|
|
3
|
+
export const webFetchTool = {
|
|
4
|
+
name: 'webFetch',
|
|
5
|
+
description: '获取网页内容,支持 JavaScript 渲染',
|
|
6
|
+
parameters: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
url: { type: 'string', description: '目标网页 URL' },
|
|
10
|
+
query: { type: 'string', description: '要提取的内容选择器(CSS 选择器)' },
|
|
11
|
+
},
|
|
12
|
+
required: ['url'],
|
|
13
|
+
},
|
|
14
|
+
handler: async (args) => {
|
|
15
|
+
try {
|
|
16
|
+
const { url, query } = args;
|
|
17
|
+
const response = await got(url, {
|
|
18
|
+
headers: {
|
|
19
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
20
|
+
},
|
|
21
|
+
timeout: { request: 15000 },
|
|
22
|
+
});
|
|
23
|
+
const $ = cheerio.load(response.body);
|
|
24
|
+
let content = $.html();
|
|
25
|
+
if (query) {
|
|
26
|
+
content = $(query).text().trim() || `选择器 ${query} 未找到匹配内容`;
|
|
27
|
+
}
|
|
28
|
+
return { success: true, output: content.substring(0, 50000) };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return { success: false, errorMessage: `网页抓取失败: ${error.message}` };
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|