@workclaw/openclaw-workclaw 1.0.19 → 1.0.23
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 +363 -364
- package/api.ts +3 -0
- package/dist/index.js +2 -1
- package/dist/src/api/workspace.js +2 -0
- package/dist/src/channel.js +1 -0
- package/dist/src/gateway/agent-handlers.js +87 -87
- package/dist/src/gateway/config-writer.js +4 -14
- package/dist/src/gateway/cron-tasks-handler.d.ts +19 -0
- package/dist/src/gateway/cron-tasks-handler.js +188 -0
- package/dist/src/gateway/message-context.d.ts +8 -4
- package/dist/src/gateway/message-context.js +148 -138
- package/dist/src/gateway/message-dispatcher.d.ts +0 -1
- package/dist/src/gateway/message-dispatcher.js +383 -323
- package/dist/src/gateway/reconnect.js +4 -0
- package/dist/src/gateway/skills-handler.js +187 -148
- package/dist/src/gateway/workclaw-gateway.d.ts +1 -1
- package/dist/src/gateway/workclaw-gateway.js +47 -33
- package/dist/src/tools/openclaw-workclaw-cron/src/update/params.js +17 -17
- package/index.ts +325 -0
- package/package.json +45 -62
- package/setup-entry.ts +13 -0
- package/src/accounts.ts +360 -0
- package/src/api/accounts-api.ts +156 -0
- package/src/api/prompts-api.ts +122 -0
- package/src/api/session-api.ts +246 -0
- package/src/api/skills-api.ts +74 -0
- package/src/api/workspace.ts +45 -0
- package/src/channel.ts +226 -0
- package/src/config-schema.ts +60 -0
- package/src/connection/workclaw-client.ts +618 -0
- package/src/gateway/agent-handlers.ts +551 -0
- package/src/gateway/config-writer.ts +378 -0
- package/src/gateway/cron-tasks-handler.ts +230 -0
- package/src/gateway/message-context.ts +645 -0
- package/src/gateway/message-dispatcher.ts +688 -0
- package/src/gateway/reconnect.ts +260 -0
- package/src/gateway/skills-handler.ts +805 -0
- package/src/gateway/skills-list-handler.ts +332 -0
- package/src/gateway/tools-list-handler.ts +161 -0
- package/src/gateway/workclaw-gateway.ts +298 -0
- package/src/media/upload.ts +168 -0
- package/src/outbound/index.ts +191 -0
- package/src/outbound/workclaw-sender.ts +161 -0
- package/src/runtime.ts +520 -0
- package/src/secret-contract-api.ts +4 -0
- package/src/send.ts +1 -0
- package/src/setup-api.ts +3 -0
- package/src/setup-core.ts +25 -0
- package/src/setup-surface.ts +498 -0
- package/src/tools/openclaw-workclaw-cron/api/index.ts +326 -0
- package/src/tools/openclaw-workclaw-cron/index.ts +39 -0
- package/src/tools/openclaw-workclaw-cron/src/add/params.ts +177 -0
- package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +188 -0
- package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +100 -0
- package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +127 -0
- package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +100 -0
- package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +127 -0
- package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +148 -0
- package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +109 -0
- package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +127 -0
- package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -0
- package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +161 -0
- package/src/tools/openclaw-workclaw-cron/types/index.ts +55 -0
- package/src/tools/openclaw-workclaw-cron/utils/index.ts +141 -0
- package/src/tools/openclaw-workclaw-system/index.ts +17 -0
- package/src/tools/openclaw-workclaw-system/src/get/index.ts +77 -0
- package/src/tools/openclaw-workclaw-system/src/token/index.ts +93 -0
- package/src/types.ts +50 -0
- package/src/utils/content.ts +40 -0
- package/tsconfig.json +34 -0
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
* 处理云端下发的 skills 相关 EVENT 消息
|
|
4
4
|
* 通过 HTTP 回调返回结果
|
|
5
5
|
*/
|
|
6
|
-
import { exec } from
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import os from
|
|
11
|
-
import
|
|
12
|
-
import
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import fs from 'node:fs/promises';
|
|
8
|
+
import http from 'node:http';
|
|
9
|
+
import https from 'node:https';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { promisify } from 'node:util';
|
|
13
13
|
const execAsync = promisify(exec);
|
|
14
14
|
// 回调 URL 映射
|
|
15
15
|
const TOPIC_TO_CALLBACK_URL = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
'skills/add': '/open-apis/v1/claw/do/skills/result',
|
|
17
|
+
'skills/update': '/open-apis/v1/claw/do/skills/result',
|
|
18
|
+
'skills/remove': '/open-apis/v1/claw/do/skills/result',
|
|
19
|
+
'skills/list': '/open-apis/v1/claw/do/skills/result',
|
|
20
|
+
'skills/get': '/open-apis/v1/claw/do/skills/result',
|
|
21
|
+
'skills/invoke': '/open-apis/v1/claw/do/skills/result',
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
24
|
* 处理 skills 事件
|
|
@@ -30,22 +30,22 @@ export async function handleSkillsEvent(eventData, token, baseUrl, appKey, log)
|
|
|
30
30
|
let result;
|
|
31
31
|
// 根据 topic 路由到不同的处理函数
|
|
32
32
|
switch (topic) {
|
|
33
|
-
case
|
|
33
|
+
case 'skills/add':
|
|
34
34
|
result = await handleCreateSkill(data, log);
|
|
35
35
|
break;
|
|
36
|
-
case
|
|
36
|
+
case 'skills/update':
|
|
37
37
|
result = await handleUpdateSkill(data, log);
|
|
38
38
|
break;
|
|
39
|
-
case
|
|
39
|
+
case 'skills/remove':
|
|
40
40
|
result = await handleDeleteSkill(data, log);
|
|
41
41
|
break;
|
|
42
|
-
case
|
|
42
|
+
case 'skills/list':
|
|
43
43
|
result = await handleListSkills(data, log);
|
|
44
44
|
break;
|
|
45
|
-
case
|
|
45
|
+
case 'skills/get':
|
|
46
46
|
result = await handleGetSkill(data, log);
|
|
47
47
|
break;
|
|
48
|
-
case
|
|
48
|
+
case 'skills/invoke':
|
|
49
49
|
result = await handleInvokeSkill(data, log);
|
|
50
50
|
break;
|
|
51
51
|
default:
|
|
@@ -53,19 +53,19 @@ export async function handleSkillsEvent(eventData, token, baseUrl, appKey, log)
|
|
|
53
53
|
}
|
|
54
54
|
// 发送成功回调
|
|
55
55
|
await sendCallback(baseUrl, topic, {
|
|
56
|
-
userId: result.userId ||
|
|
57
|
-
agentId:
|
|
56
|
+
userId: result.userId || '',
|
|
57
|
+
agentId: '',
|
|
58
58
|
appKey,
|
|
59
59
|
agentIds: Array.isArray(result.agentIds) ? result.agentIds : (result.agentIds || 0),
|
|
60
60
|
dotype: result.status || topic.split('/')[1],
|
|
61
61
|
doStatus: true,
|
|
62
|
-
doErrorMsg:
|
|
62
|
+
doErrorMsg: '',
|
|
63
63
|
dataList: [
|
|
64
64
|
{
|
|
65
|
-
typeStr:
|
|
66
|
-
name: result.skillName ||
|
|
67
|
-
description:
|
|
68
|
-
}
|
|
65
|
+
typeStr: '',
|
|
66
|
+
name: result.skillName || '',
|
|
67
|
+
description: '',
|
|
68
|
+
},
|
|
69
69
|
],
|
|
70
70
|
}, token, log);
|
|
71
71
|
log?.info?.(`[SkillsHandler] ${topic} completed, callback sent`);
|
|
@@ -89,19 +89,19 @@ export async function handleSkillsEvent(eventData, token, baseUrl, appKey, log)
|
|
|
89
89
|
log?.error?.(`[SkillsHandler] ${topic} failed: ${err.message}`);
|
|
90
90
|
// 发送失败回调
|
|
91
91
|
await sendCallback(baseUrl, topic, {
|
|
92
|
-
userId:
|
|
93
|
-
agentId:
|
|
92
|
+
userId: '',
|
|
93
|
+
agentId: '',
|
|
94
94
|
agentIds: [],
|
|
95
95
|
appKey,
|
|
96
|
-
dotype: topic.split(
|
|
96
|
+
dotype: topic.split('/')[1],
|
|
97
97
|
doStatus: false,
|
|
98
98
|
doErrorMsg: err.message,
|
|
99
99
|
dataList: [
|
|
100
100
|
{
|
|
101
|
-
typeStr:
|
|
102
|
-
name:
|
|
103
|
-
description:
|
|
104
|
-
}
|
|
101
|
+
typeStr: '',
|
|
102
|
+
name: '',
|
|
103
|
+
description: '',
|
|
104
|
+
},
|
|
105
105
|
],
|
|
106
106
|
}, token, log);
|
|
107
107
|
}
|
|
@@ -115,15 +115,15 @@ async function sendCallback(baseUrl, topic, response, authToken, log) {
|
|
|
115
115
|
if (!callbackPath) {
|
|
116
116
|
throw new Error(`No callback URL defined for topic: ${topic}`);
|
|
117
117
|
}
|
|
118
|
-
const callbackUrl = `${baseUrl.replace(/\/$/,
|
|
118
|
+
const callbackUrl = `${baseUrl.replace(/\/$/, '')}${callbackPath}`;
|
|
119
119
|
const responseData = JSON.stringify(response);
|
|
120
120
|
log?.info?.(`[SkillsHandler] Sending callback to ${callbackUrl}, data: ${responseData}`);
|
|
121
121
|
// 准备请求头
|
|
122
122
|
const headers = {
|
|
123
|
-
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
124
|
};
|
|
125
125
|
if (authToken) {
|
|
126
|
-
headers
|
|
126
|
+
headers.Authorization = authToken.startsWith('Bearer ')
|
|
127
127
|
? authToken
|
|
128
128
|
: `Bearer ${authToken}`;
|
|
129
129
|
}
|
|
@@ -132,7 +132,7 @@ async function sendCallback(baseUrl, topic, response, authToken, log) {
|
|
|
132
132
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
133
133
|
try {
|
|
134
134
|
const res = await fetch(callbackUrl, {
|
|
135
|
-
method:
|
|
135
|
+
method: 'POST',
|
|
136
136
|
headers,
|
|
137
137
|
body: responseData,
|
|
138
138
|
signal: controller.signal,
|
|
@@ -146,9 +146,9 @@ async function sendCallback(baseUrl, topic, response, authToken, log) {
|
|
|
146
146
|
}
|
|
147
147
|
catch (err) {
|
|
148
148
|
clearTimeout(timeoutId);
|
|
149
|
-
if (err.name ===
|
|
149
|
+
if (err.name === 'AbortError') {
|
|
150
150
|
log?.error?.(`[SkillsHandler] Callback timeout after 30s: ${callbackUrl}`);
|
|
151
|
-
throw new Error(
|
|
151
|
+
throw new Error('Callback timeout');
|
|
152
152
|
}
|
|
153
153
|
log?.error?.(`[SkillsHandler] Failed to send callback: ${err.message}`);
|
|
154
154
|
throw err;
|
|
@@ -159,27 +159,27 @@ async function sendCallback(baseUrl, topic, response, authToken, log) {
|
|
|
159
159
|
*/
|
|
160
160
|
async function downloadFromUrl(url, log) {
|
|
161
161
|
return new Promise((resolve, reject) => {
|
|
162
|
-
const client = url.startsWith(
|
|
162
|
+
const client = url.startsWith('https:') ? https : http;
|
|
163
163
|
const req = client.get(url, (res) => {
|
|
164
164
|
if (res.statusCode !== 200) {
|
|
165
165
|
reject(new Error(`Failed to download: HTTP ${res.statusCode}`));
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
168
|
const chunks = [];
|
|
169
|
-
res.on(
|
|
169
|
+
res.on('data', (chunk) => {
|
|
170
170
|
chunks.push(chunk);
|
|
171
171
|
});
|
|
172
|
-
res.on(
|
|
172
|
+
res.on('end', () => {
|
|
173
173
|
const buffer = Buffer.concat(chunks);
|
|
174
174
|
resolve(buffer);
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
|
-
req.on(
|
|
177
|
+
req.on('error', (err) => {
|
|
178
178
|
reject(new Error(`Download failed: ${err.message}`));
|
|
179
179
|
});
|
|
180
180
|
req.setTimeout(30000, () => {
|
|
181
181
|
req.destroy();
|
|
182
|
-
reject(new Error(
|
|
182
|
+
reject(new Error('Download timeout'));
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
185
|
}
|
|
@@ -188,17 +188,17 @@ async function downloadFromUrl(url, log) {
|
|
|
188
188
|
*/
|
|
189
189
|
function getOpenClawConfigDir() {
|
|
190
190
|
const homeDir = os.homedir();
|
|
191
|
-
return path.join(homeDir,
|
|
191
|
+
return path.join(homeDir, '.openclaw');
|
|
192
192
|
}
|
|
193
193
|
/**
|
|
194
194
|
* 获取 skills 目录
|
|
195
195
|
*/
|
|
196
196
|
async function getSkillsDir(agentId, log) {
|
|
197
197
|
const openclawDir = getOpenClawConfigDir();
|
|
198
|
-
let skillsDir = path.join(openclawDir,
|
|
198
|
+
let skillsDir = path.join(openclawDir, 'workspace', 'skills');
|
|
199
199
|
// 如果有 agentId,添加到对应目录
|
|
200
200
|
if (agentId && agentId.trim()) {
|
|
201
|
-
skillsDir = path.join(openclawDir,
|
|
201
|
+
skillsDir = path.join(openclawDir, `workspace-${agentId}`, 'skills');
|
|
202
202
|
}
|
|
203
203
|
try {
|
|
204
204
|
await fs.mkdir(skillsDir, { recursive: true });
|
|
@@ -282,14 +282,14 @@ async function installSkillToClaw(skillName, skillContent, agentId, log) {
|
|
|
282
282
|
if (!skillData.id || !skillData.name) {
|
|
283
283
|
return {
|
|
284
284
|
success: false,
|
|
285
|
-
message:
|
|
285
|
+
message: 'Missing required fields in skill: id, name',
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
288
|
// 获取 skills 目录
|
|
289
289
|
const skillsDir = await getSkillsDir(agentId, log);
|
|
290
290
|
const skillFilePath = path.join(skillsDir, `${skillName}.json`);
|
|
291
291
|
// 写入 skill 文件
|
|
292
|
-
await fs.writeFile(skillFilePath, skillContent,
|
|
292
|
+
await fs.writeFile(skillFilePath, skillContent, 'utf-8');
|
|
293
293
|
log?.info?.(`[SkillsHandler] Skill saved to: ${skillFilePath}`);
|
|
294
294
|
return {
|
|
295
295
|
success: true,
|
|
@@ -307,17 +307,31 @@ async function installSkillToClaw(skillName, skillContent, agentId, log) {
|
|
|
307
307
|
/**
|
|
308
308
|
* 创建 Skill
|
|
309
309
|
*/
|
|
310
|
+
async function installSkillToLocations(skillName, skillContent, locations, log) {
|
|
311
|
+
const installResults = [];
|
|
312
|
+
for (const agentId of locations) {
|
|
313
|
+
const installResult = await installSkillToClaw(skillName, skillContent, agentId, log);
|
|
314
|
+
if (!installResult.success) {
|
|
315
|
+
const error = new Error(`Failed to install skill to ${agentId ? `agent ${agentId}` : 'default location'}: ${installResult.message}`);
|
|
316
|
+
error.code = 'INSTALL_FAILED';
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
installResults.push(installResult);
|
|
320
|
+
}
|
|
321
|
+
return installResults;
|
|
322
|
+
}
|
|
310
323
|
async function handleCreateSkill(data, log) {
|
|
311
|
-
const { userId, skillName, unloadUrl } = data;
|
|
324
|
+
const { userId, skillName, unloadUrl, mainId } = data;
|
|
312
325
|
const agentIds = Array.isArray(data?.agentIds) ? data.agentIds : (data?.agentIds ? [data.agentIds] : []);
|
|
313
326
|
if (!userId || !skillName) {
|
|
314
|
-
const error = new Error(
|
|
315
|
-
error.code =
|
|
327
|
+
const error = new Error('Missing required fields: userId, skillName');
|
|
328
|
+
error.code = 'MISSING_FIELDS';
|
|
316
329
|
throw error;
|
|
317
330
|
}
|
|
318
331
|
log?.info?.(`[SkillsHandler] Creating skill: ${skillName}`);
|
|
332
|
+
log?.info?.(`[SkillsHandler] mainId: ${mainId}`);
|
|
319
333
|
if (agentIds.length > 0) {
|
|
320
|
-
log?.info?.(`[SkillsHandler] Target agents: ${agentIds.join(
|
|
334
|
+
log?.info?.(`[SkillsHandler] Target agents: ${agentIds.join(', ')}`);
|
|
321
335
|
}
|
|
322
336
|
// 如果提供了 URL,从 URL 下载 skill 定义
|
|
323
337
|
if (unloadUrl) {
|
|
@@ -326,34 +340,43 @@ async function handleCreateSkill(data, log) {
|
|
|
326
340
|
// 下载 skill 定义
|
|
327
341
|
const skillContent = await downloadFromUrl(unloadUrl, log);
|
|
328
342
|
log?.info?.(`[SkillsHandler] Downloaded skill content, length: ${skillContent.length} bytes`);
|
|
329
|
-
//
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
343
|
+
// 确定安装位置
|
|
344
|
+
const installLocations = [];
|
|
345
|
+
// 检查 agentIds 是否只包含 mainId
|
|
346
|
+
const hasOnlyMainId = agentIds.length === 1 && agentIds[0] === mainId;
|
|
347
|
+
// 检查 agentIds 是否包含 mainId 和其他 id
|
|
348
|
+
const hasMainIdAndOthers = agentIds.includes(mainId) && agentIds.length > 1;
|
|
349
|
+
if (hasOnlyMainId) {
|
|
350
|
+
// 如果只有 mainId,安装到默认位置
|
|
351
|
+
log?.info?.(`[SkillsHandler] Only mainId found, installing to default location`);
|
|
352
|
+
installLocations.push(undefined);
|
|
339
353
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
354
|
+
else if (hasMainIdAndOthers) {
|
|
355
|
+
// 如果有 mainId 和其他 id,安装到默认位置和其他 id 位置
|
|
356
|
+
log?.info?.(`[SkillsHandler] mainId and other agents found, installing to default location and other agents`);
|
|
357
|
+
installLocations.push(undefined);
|
|
358
|
+
// 添加其他 id 位置(排除 mainId)
|
|
359
|
+
installLocations.push(...agentIds.filter(id => id !== mainId));
|
|
360
|
+
}
|
|
361
|
+
else if (agentIds.length > 0) {
|
|
362
|
+
// 如果没有 mainId,安装到所有指定的 agentId 位置
|
|
363
|
+
log?.info?.(`[SkillsHandler] No mainId found, installing to all specified agents`);
|
|
364
|
+
installLocations.push(...agentIds);
|
|
349
365
|
}
|
|
366
|
+
else {
|
|
367
|
+
// 如果没有指定智能体,安装到默认位置
|
|
368
|
+
log?.info?.(`[SkillsHandler] No agents specified, installing to default location`);
|
|
369
|
+
installLocations.push(undefined);
|
|
370
|
+
}
|
|
371
|
+
// 安装技能到所有位置
|
|
372
|
+
const installResults = await installSkillToLocations(skillName, skillContent, installLocations, log);
|
|
350
373
|
log?.info?.(`[SkillsHandler] Skill installed to ${installResults.length} locations`);
|
|
351
374
|
return {
|
|
352
375
|
userId,
|
|
353
376
|
skillName,
|
|
354
|
-
agentId:
|
|
377
|
+
agentId: '',
|
|
355
378
|
agentIds: agentIds.length > 0 ? agentIds : undefined,
|
|
356
|
-
status:
|
|
379
|
+
status: 'add',
|
|
357
380
|
installedAt: new Date().toISOString(),
|
|
358
381
|
};
|
|
359
382
|
}
|
|
@@ -365,9 +388,9 @@ async function handleCreateSkill(data, log) {
|
|
|
365
388
|
return {
|
|
366
389
|
userId,
|
|
367
390
|
skillName,
|
|
368
|
-
agentId:
|
|
391
|
+
agentId: '',
|
|
369
392
|
agentIds: agentIds.length > 0 ? agentIds : undefined,
|
|
370
|
-
status:
|
|
393
|
+
status: 'add',
|
|
371
394
|
createdAt: new Date().toISOString(),
|
|
372
395
|
};
|
|
373
396
|
}
|
|
@@ -377,98 +400,114 @@ async function handleCreateSkill(data, log) {
|
|
|
377
400
|
async function handleUpdateSkill(data, log) {
|
|
378
401
|
const { id, ...updates } = data;
|
|
379
402
|
if (!id) {
|
|
380
|
-
const error = new Error(
|
|
381
|
-
error.code =
|
|
403
|
+
const error = new Error('Missing required field: id');
|
|
404
|
+
error.code = 'MISSING_FIELDS';
|
|
382
405
|
throw error;
|
|
383
406
|
}
|
|
384
407
|
log?.info?.(`[SkillsHandler] Updating skill: ${id}`);
|
|
385
408
|
return {
|
|
386
409
|
id,
|
|
387
410
|
...updates,
|
|
388
|
-
status:
|
|
411
|
+
status: 'updated',
|
|
389
412
|
updatedAt: new Date().toISOString(),
|
|
390
413
|
};
|
|
391
414
|
}
|
|
392
415
|
/**
|
|
393
416
|
* 删除 Skill
|
|
394
417
|
*/
|
|
418
|
+
async function deleteSkillAtLocation(skillName, agentId, log) {
|
|
419
|
+
try {
|
|
420
|
+
const skillsDir = await getSkillsDir(agentId, log);
|
|
421
|
+
const skillFilePath = path.join(skillsDir, `${skillName}.json`);
|
|
422
|
+
const skillDirPath = path.join(skillsDir, skillName);
|
|
423
|
+
let deleted = false;
|
|
424
|
+
// 尝试删除技能文件
|
|
425
|
+
try {
|
|
426
|
+
await fs.unlink(skillFilePath);
|
|
427
|
+
log?.info?.(`[SkillsHandler] Skill file deleted: ${skillFilePath}`);
|
|
428
|
+
deleted = true;
|
|
429
|
+
}
|
|
430
|
+
catch (err) {
|
|
431
|
+
// 文件不存在,继续尝试删除目录
|
|
432
|
+
}
|
|
433
|
+
// 尝试删除技能目录(ZIP 安装的技能)
|
|
434
|
+
try {
|
|
435
|
+
await fs.rm(skillDirPath, { recursive: true, force: true });
|
|
436
|
+
log?.info?.(`[SkillsHandler] Skill directory deleted: ${skillDirPath}`);
|
|
437
|
+
deleted = true;
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
// 目录不存在,继续
|
|
441
|
+
}
|
|
442
|
+
return deleted;
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
log?.error?.(`[SkillsHandler] Failed to delete skill at location: ${err.message}`);
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
395
449
|
async function handleDeleteSkill(data, log) {
|
|
396
|
-
const { userId, skillName, agentIds } = data;
|
|
450
|
+
const { userId, skillName, agentIds, mainId } = data;
|
|
397
451
|
const agentIdsList = Array.isArray(agentIds) ? agentIds : (agentIds ? [agentIds] : []);
|
|
398
452
|
if (!skillName) {
|
|
399
|
-
const error = new Error(
|
|
400
|
-
error.code =
|
|
453
|
+
const error = new Error('Missing required field: skillName');
|
|
454
|
+
error.code = 'MISSING_FIELDS';
|
|
401
455
|
throw error;
|
|
402
456
|
}
|
|
403
457
|
log?.info?.(`[SkillsHandler] Deleting skill: ${skillName}`);
|
|
458
|
+
log?.info?.(`[SkillsHandler] mainId: ${mainId}`);
|
|
404
459
|
if (agentIdsList.length > 0) {
|
|
405
|
-
log?.info?.(`[SkillsHandler] Target agents: ${agentIdsList.join(
|
|
460
|
+
log?.info?.(`[SkillsHandler] Target agents: ${agentIdsList.join(', ')}`);
|
|
406
461
|
}
|
|
407
462
|
try {
|
|
408
463
|
let deleted = false;
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
await fs.unlink(skillFilePath);
|
|
419
|
-
log?.info?.(`[SkillsHandler] Skill file deleted: ${skillFilePath}`);
|
|
420
|
-
deleted = true;
|
|
421
|
-
}
|
|
422
|
-
catch (err) {
|
|
423
|
-
// 文件不存在,继续尝试删除目录
|
|
424
|
-
}
|
|
425
|
-
// 尝试删除技能目录(ZIP 安装的技能)
|
|
426
|
-
try {
|
|
427
|
-
await fs.rm(skillDirPath, { recursive: true, force: true });
|
|
428
|
-
log?.info?.(`[SkillsHandler] Skill directory deleted: ${skillDirPath}`);
|
|
429
|
-
deleted = true;
|
|
430
|
-
}
|
|
431
|
-
catch (err) {
|
|
432
|
-
// 目录不存在,继续
|
|
433
|
-
}
|
|
464
|
+
// 检查 agentIds 是否只包含 mainId
|
|
465
|
+
const hasOnlyMainId = agentIdsList.length === 1 && agentIdsList[0] === mainId;
|
|
466
|
+
// 检查 agentIds 是否包含 mainId 和其他 id
|
|
467
|
+
const hasMainIdAndOthers = agentIdsList.includes(mainId) && agentIdsList.length > 1;
|
|
468
|
+
if (hasOnlyMainId) {
|
|
469
|
+
// 如果只有 mainId,从默认位置删除
|
|
470
|
+
log?.info?.(`[SkillsHandler] Only mainId found, deleting from default location`);
|
|
471
|
+
deleted = await deleteSkillAtLocation(skillName, undefined, log);
|
|
434
472
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
catch (err) {
|
|
449
|
-
// 文件不存在,继续尝试删除目录
|
|
450
|
-
}
|
|
451
|
-
// 尝试删除技能目录(ZIP 安装的技能)
|
|
452
|
-
try {
|
|
453
|
-
await fs.rm(skillDirPath, { recursive: true, force: true });
|
|
454
|
-
log?.info?.(`[SkillsHandler] Skill directory deleted: ${skillDirPath}`);
|
|
455
|
-
deleted = true;
|
|
473
|
+
else if (hasMainIdAndOthers) {
|
|
474
|
+
// 如果有 mainId 和其他 id,从默认位置和其他 id 位置删除
|
|
475
|
+
log?.info?.(`[SkillsHandler] mainId and other agents found, deleting from default location and other agents`);
|
|
476
|
+
// 从默认位置删除
|
|
477
|
+
const defaultDeleted = await deleteSkillAtLocation(skillName, undefined, log);
|
|
478
|
+
deleted = deleted || defaultDeleted;
|
|
479
|
+
// 从其他 id 位置删除(排除 mainId)
|
|
480
|
+
for (const agentId of agentIdsList) {
|
|
481
|
+
if (agentId !== mainId) {
|
|
482
|
+
const agentDeleted = await deleteSkillAtLocation(skillName, agentId, log);
|
|
483
|
+
deleted = deleted || agentDeleted;
|
|
484
|
+
}
|
|
456
485
|
}
|
|
457
|
-
|
|
458
|
-
|
|
486
|
+
}
|
|
487
|
+
else if (agentIdsList.length > 0) {
|
|
488
|
+
// 如果没有 mainId,从所有指定的 agentId 位置删除
|
|
489
|
+
log?.info?.(`[SkillsHandler] No mainId found, deleting from all specified agents`);
|
|
490
|
+
for (const agentId of agentIdsList) {
|
|
491
|
+
const agentDeleted = await deleteSkillAtLocation(skillName, agentId, log);
|
|
492
|
+
deleted = deleted || agentDeleted;
|
|
459
493
|
}
|
|
460
494
|
}
|
|
495
|
+
else {
|
|
496
|
+
// 如果没有指定智能体,从默认位置删除
|
|
497
|
+
log?.info?.(`[SkillsHandler] No agents specified, deleting from default location`);
|
|
498
|
+
deleted = await deleteSkillAtLocation(skillName, undefined, log);
|
|
499
|
+
}
|
|
461
500
|
if (!deleted) {
|
|
462
501
|
const error = new Error(`Skill not found: ${skillName}`);
|
|
463
|
-
error.code =
|
|
502
|
+
error.code = 'SKILL_NOT_FOUND';
|
|
464
503
|
throw error;
|
|
465
504
|
}
|
|
466
505
|
return {
|
|
467
506
|
userId,
|
|
468
507
|
skillName,
|
|
469
|
-
agentId:
|
|
508
|
+
agentId: '',
|
|
470
509
|
agentIds: agentIdsList.length > 0 ? agentIdsList : undefined,
|
|
471
|
-
status:
|
|
510
|
+
status: 'remove',
|
|
472
511
|
deletedAt: new Date().toISOString(),
|
|
473
512
|
};
|
|
474
513
|
}
|
|
@@ -485,7 +524,7 @@ async function handleListSkills(data, log) {
|
|
|
485
524
|
log?.info?.(`[SkillsHandler] Listing skills, filter: ${filter}, limit: ${limit}`);
|
|
486
525
|
try {
|
|
487
526
|
// 调用 openclaw skills list 命令
|
|
488
|
-
const { stdout } = await execAsync(
|
|
527
|
+
const { stdout } = await execAsync('openclaw skills list --json', {
|
|
489
528
|
timeout: 10000,
|
|
490
529
|
});
|
|
491
530
|
let skills = [];
|
|
@@ -495,17 +534,17 @@ async function handleListSkills(data, log) {
|
|
|
495
534
|
}
|
|
496
535
|
catch {
|
|
497
536
|
// 解析文本输出
|
|
498
|
-
const lines = stdout.split(
|
|
537
|
+
const lines = stdout.split('\n');
|
|
499
538
|
skills = lines
|
|
500
|
-
.filter(
|
|
539
|
+
.filter(line => line.trim() && !line.includes('Skills'))
|
|
501
540
|
.map((line) => {
|
|
502
|
-
const parts = line.split(/\s{2,}/).map(
|
|
541
|
+
const parts = line.split(/\s{2,}/).map(p => p.trim());
|
|
503
542
|
if (parts.length >= 3) {
|
|
504
543
|
return {
|
|
505
|
-
status: parts[0]?.includes(
|
|
544
|
+
status: parts[0]?.includes('✓') ? 'ready' : 'missing',
|
|
506
545
|
name: parts[1],
|
|
507
546
|
description: parts[2],
|
|
508
|
-
source: parts[3] ||
|
|
547
|
+
source: parts[3] || 'openclaw-bundled',
|
|
509
548
|
};
|
|
510
549
|
}
|
|
511
550
|
return null;
|
|
@@ -514,7 +553,7 @@ async function handleListSkills(data, log) {
|
|
|
514
553
|
}
|
|
515
554
|
// 应用过滤
|
|
516
555
|
if (filter) {
|
|
517
|
-
skills = skills.filter(
|
|
556
|
+
skills = skills.filter(s => s.status === filter);
|
|
518
557
|
}
|
|
519
558
|
// 应用 limit
|
|
520
559
|
skills = skills.slice(0, limit);
|
|
@@ -538,8 +577,8 @@ async function handleListSkills(data, log) {
|
|
|
538
577
|
async function handleGetSkill(data, log) {
|
|
539
578
|
const { id } = data;
|
|
540
579
|
if (!id) {
|
|
541
|
-
const error = new Error(
|
|
542
|
-
error.code =
|
|
580
|
+
const error = new Error('Missing required field: id');
|
|
581
|
+
error.code = 'MISSING_FIELDS';
|
|
543
582
|
throw error;
|
|
544
583
|
}
|
|
545
584
|
log?.info?.(`[SkillsHandler] Getting skill: ${id}`);
|
|
@@ -547,8 +586,8 @@ async function handleGetSkill(data, log) {
|
|
|
547
586
|
return {
|
|
548
587
|
id,
|
|
549
588
|
name: `Skill ${id}`,
|
|
550
|
-
description:
|
|
551
|
-
status:
|
|
589
|
+
description: 'Skill description',
|
|
590
|
+
status: 'ready',
|
|
552
591
|
};
|
|
553
592
|
}
|
|
554
593
|
/**
|
|
@@ -557,15 +596,15 @@ async function handleGetSkill(data, log) {
|
|
|
557
596
|
async function handleInvokeSkill(data, log) {
|
|
558
597
|
const { id, input, context } = data;
|
|
559
598
|
if (!id) {
|
|
560
|
-
const error = new Error(
|
|
561
|
-
error.code =
|
|
599
|
+
const error = new Error('Missing required field: id');
|
|
600
|
+
error.code = 'MISSING_FIELDS';
|
|
562
601
|
throw error;
|
|
563
602
|
}
|
|
564
603
|
log?.info?.(`[SkillsHandler] Invoking skill: ${id}`);
|
|
565
604
|
// TODO: 实现实际的 skill 执行逻辑
|
|
566
605
|
return {
|
|
567
606
|
id,
|
|
568
|
-
status:
|
|
607
|
+
status: 'completed',
|
|
569
608
|
output: {
|
|
570
609
|
result: `Skill ${id} executed successfully`,
|
|
571
610
|
input,
|