@workclaw/openclaw-workclaw 1.0.17 → 1.0.18

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.
Files changed (51) hide show
  1. package/README.md +21 -1
  2. package/index.ts +210 -210
  3. package/openclaw.plugin.json +1 -0
  4. package/package.json +11 -4
  5. package/setup-entry.ts +6 -0
  6. package/skills/openclaw-workclaw-cron/SKILL.md +45 -28
  7. package/src/accounts.ts +62 -37
  8. package/src/api/accounts-api.ts +88 -89
  9. package/src/api/prompts-api.ts +70 -77
  10. package/src/api/session-api.ts +99 -108
  11. package/src/api/skills-api.ts +35 -37
  12. package/src/api/workspace.ts +27 -29
  13. package/src/channel.ts +200 -202
  14. package/src/config-schema.ts +9 -9
  15. package/src/connection/workclaw-client.ts +554 -567
  16. package/src/gateway/agent-handlers.ts +392 -426
  17. package/src/gateway/config-writer.ts +228 -243
  18. package/src/gateway/message-context.ts +534 -362
  19. package/src/gateway/message-dispatcher.ts +529 -489
  20. package/src/gateway/reconnect.ts +217 -113
  21. package/src/gateway/skills-handler.ts +408 -472
  22. package/src/gateway/skills-list-handler.ts +9 -9
  23. package/src/gateway/tools-list-handler.ts +70 -72
  24. package/src/gateway/workclaw-gateway.ts +328 -486
  25. package/src/media/upload.ts +83 -94
  26. package/src/outbound/index.ts +57 -55
  27. package/src/outbound/workclaw-sender.ts +134 -133
  28. package/src/runtime.ts +291 -194
  29. package/src/send.ts +1 -1
  30. package/src/tools/openclaw-workclaw-cron/api/index.ts +6 -6
  31. package/src/tools/openclaw-workclaw-cron/src/add/params.ts +20 -19
  32. package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +2 -2
  33. package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +1 -1
  34. package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +3 -3
  35. package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +1 -1
  36. package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +3 -3
  37. package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +2 -2
  38. package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +1 -1
  39. package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +3 -3
  40. package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -197
  41. package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +4 -4
  42. package/src/tools/openclaw-workclaw-system/src/get/index.ts +2 -2
  43. package/src/tools/openclaw-workclaw-system/src/token/index.ts +4 -4
  44. package/src/types.ts +38 -40
  45. package/src/utils/content.ts +16 -21
  46. package/tests/accounts.test.ts +285 -0
  47. package/tests/message-context.test.ts +313 -0
  48. package/tests/reconnect.test.ts +257 -0
  49. package/tests/workclaw-client.test.ts +112 -0
  50. package/tsconfig.json +8 -5
  51. package/vitest.config.ts +8 -0
@@ -4,158 +4,156 @@
4
4
  * 通过 HTTP 回调返回结果
5
5
  */
6
6
 
7
- import { Buffer } from 'node:buffer'
8
- import { exec } from 'node:child_process'
9
- import fs from 'node:fs/promises'
10
- import http from 'node:http'
11
- import https from 'node:https'
12
- import os from 'node:os'
13
- import path from 'node:path'
14
- import process from 'node:process'
15
- import { promisify } from 'node:util'
16
-
17
- const execAsync = promisify(exec)
7
+ import { exec } from "node:child_process";
8
+ import { promisify } from "node:util";
9
+ import fs from "node:fs/promises";
10
+ import path from "node:path";
11
+ import os from "node:os";
12
+ import https from "node:https";
13
+ import http from "node:http";
14
+ import zlib from "node:zlib";
15
+ import stream from "node:stream";
16
+
17
+ const execAsync = promisify(exec);
18
18
 
19
19
  export interface SkillsEventData {
20
- topic: string // skills/create, skills/update, skills/delete, skills/list, skills/get, skills/invoke
21
- requestId: string
22
- callbackUrl: string
23
- authToken?: string
24
- data: any
20
+ topic: string; // skills/create, skills/update, skills/delete, skills/list, skills/get, skills/invoke
21
+ requestId: string;
22
+ callbackUrl: string;
23
+ authToken?: string;
24
+ data: any;
25
25
  }
26
26
 
27
27
  export interface CallbackResponse {
28
- userId?: string
29
- agentId?: string
30
- agentIds?: string[]
31
- appKey?: string
32
- dotype?: string
33
- doStatus?: boolean
34
- doErrorMsg?: string
28
+ userId?: string,
29
+ agentId?: string,
30
+ agentIds?: string[],
31
+ appKey?: string,
32
+ dotype?: string,
33
+ doStatus?: boolean,
34
+ doErrorMsg?: string,
35
35
  dataList?: any[]
36
36
  }
37
37
 
38
38
  // 回调 URL 映射
39
39
  const TOPIC_TO_CALLBACK_URL: Record<string, string> = {
40
- 'skills/add': '/open-apis/v1/claw/do/skills/result',
41
- 'skills/update': '/open-apis/v1/claw/do/skills/result',
42
- 'skills/remove': '/open-apis/v1/claw/do/skills/result',
43
- 'skills/list': '/open-apis/v1/claw/do/skills/result',
44
- 'skills/get': '/open-apis/v1/claw/do/skills/result',
45
- 'skills/invoke': '/open-apis/v1/claw/do/skills/result',
46
- }
40
+ "skills/add": "/open-apis/v1/claw/do/skills/result",
41
+ "skills/update": "/open-apis/v1/claw/do/skills/result",
42
+ "skills/remove": "/open-apis/v1/claw/do/skills/result",
43
+ "skills/list": "/open-apis/v1/claw/do/skills/result",
44
+ "skills/get": "/open-apis/v1/claw/do/skills/result",
45
+ "skills/invoke": "/open-apis/v1/claw/do/skills/result",
46
+ };
47
47
 
48
48
  /**
49
49
  * 处理 skills 事件
50
50
  */
51
51
  export async function handleSkillsEvent(
52
- eventData: SkillsEventData,
53
- token: string,
54
- baseUrl: string,
55
- appKey: string,
56
- log?: {
57
- info?: (msg: string) => void
58
- error?: (msg: string) => void
59
- },
52
+ eventData: SkillsEventData,
53
+ token: string,
54
+ baseUrl: string,
55
+ appKey: string,
56
+ log?: {
57
+ info?: (msg: string) => void;
58
+ error?: (msg: string) => void;
59
+ },
60
60
  ): Promise<void> {
61
- const { topic, data } = eventData
62
- log?.info?.(`[SkillsHandler] Processing ${topic}, data: ${JSON.stringify(data)}`)
61
+ const { topic, data } = eventData;
62
+ log?.info?.(`[SkillsHandler] Processing ${topic}, data: ${JSON.stringify(data)}`);
63
63
 
64
64
  try {
65
- let result: any
65
+ let result: any;
66
66
 
67
67
  // 根据 topic 路由到不同的处理函数
68
68
  switch (topic) {
69
- case 'skills/add':
70
- result = await handleCreateSkill(data, log)
71
- break
72
- case 'skills/update':
73
- result = await handleUpdateSkill(data, log)
74
- break
75
- case 'skills/remove':
76
- result = await handleDeleteSkill(data, log)
77
- break
78
- case 'skills/list':
79
- result = await handleListSkills(data, log)
80
- break
81
- case 'skills/get':
82
- result = await handleGetSkill(data, log)
83
- break
84
- case 'skills/invoke':
85
- result = await handleInvokeSkill(data, log)
86
- break
69
+ case "skills/add":
70
+ result = await handleCreateSkill(data, log);
71
+ break;
72
+ case "skills/update":
73
+ result = await handleUpdateSkill(data, log);
74
+ break;
75
+ case "skills/remove":
76
+ result = await handleDeleteSkill(data, log);
77
+ break;
78
+ case "skills/list":
79
+ result = await handleListSkills(data, log);
80
+ break;
81
+ case "skills/get":
82
+ result = await handleGetSkill(data, log);
83
+ break;
84
+ case "skills/invoke":
85
+ result = await handleInvokeSkill(data, log);
86
+ break;
87
87
  default:
88
- throw new Error(`Unknown topic: ${topic}`)
88
+ throw new Error(`Unknown topic: ${topic}`);
89
89
  }
90
90
 
91
91
  // 发送成功回调
92
92
  await sendCallback(
93
- baseUrl,
94
- topic,
95
- {
96
- userId: result.userId || '',
97
- agentId: '',
98
- appKey,
99
- agentIds: Array.isArray(result.agentIds) ? result.agentIds : (result.agentIds || 0),
100
- dotype: result.status || topic.split('/')[1],
101
- doStatus: true,
102
- doErrorMsg: '',
103
- dataList: [
104
- {
105
- typeStr: '',
106
- name: result.skillName || '',
107
- description: '',
108
- },
109
- ],
110
- },
111
- token,
112
- log,
113
- )
114
-
115
- log?.info?.(`[SkillsHandler] ${topic} completed, callback sent`)
93
+ baseUrl,
94
+ topic,
95
+ {
96
+ userId: result.userId || "",
97
+ agentId: "",
98
+ appKey,
99
+ agentIds: Array.isArray(result.agentIds) ? result.agentIds : (result.agentIds || 0),
100
+ dotype: result.status || topic.split('/')[1],
101
+ doStatus: true,
102
+ doErrorMsg: "",
103
+ dataList: [
104
+ {
105
+ typeStr: "",
106
+ name: result.skillName || "",
107
+ description: "",
108
+ }
109
+ ],
110
+ },
111
+ token,
112
+ log
113
+ );
114
+
115
+ log?.info?.(`[SkillsHandler] ${topic} completed, callback sent`);
116
116
 
117
117
  // 重启 OpenClaw 网关(仅在添加、更新、删除技能时)
118
118
  if (['skills/add', 'skills/update', 'skills/remove'].includes(topic)) {
119
119
  try {
120
- log?.info?.(`[SkillsHandler] rescan...`)
121
- await execAsync(`openclaw skills rescan`, { timeout: 2000 })
122
- await execAsync(`openclaw skills reload --force`, { timeout: 2000 })
123
- await execAsync(`openclaw skills list`, { timeout: 2000 })
124
- log?.info?.(`[SkillsHandler] OpenClaw skills rescaned successfully`)
125
- }
126
- catch (err: any) {
127
- log?.error?.(`[SkillsHandler] Failed to rescan OpenClaw skills: ${err.message}`)
120
+ log?.info?.(`[SkillsHandler] rescan...`);
121
+ await execAsync(`openclaw skills rescan`, { timeout: 2000 });
122
+ await execAsync(`openclaw skills reload --force`, { timeout: 2000 });
123
+ await execAsync(`openclaw skills list`, { timeout: 2000 });
124
+ log?.info?.(`[SkillsHandler] OpenClaw skills rescaned successfully`);
125
+ } catch (err: any) {
126
+ log?.error?.(`[SkillsHandler] Failed to rescan OpenClaw skills: ${err.message}`);
128
127
  // 重启 OpenClaw 网关
129
- await execAsync(`openclaw skills restart`, { timeout: 20000 })
128
+ await execAsync(`openclaw skills restart`, { timeout: 20000 });
130
129
  }
131
130
  }
132
- }
133
- catch (err: any) {
134
- log?.error?.(`[SkillsHandler] ${topic} failed: ${err.message}`)
131
+ } catch (err: any) {
132
+ log?.error?.(`[SkillsHandler] ${topic} failed: ${err.message}`);
135
133
 
136
134
  // 发送失败回调
137
135
  await sendCallback(
138
- baseUrl,
139
- topic,
140
- {
141
- userId: '',
142
- agentId: '',
143
- agentIds: [],
144
- appKey,
145
- dotype: topic.split('/')[1],
146
- doStatus: false,
147
- doErrorMsg: err.message,
148
- dataList: [
149
- {
150
- typeStr: '',
151
- name: '',
152
- description: '',
153
- },
154
- ],
155
- },
156
- token,
157
- log,
158
- )
136
+ baseUrl,
137
+ topic,
138
+ {
139
+ userId: "",
140
+ agentId: "",
141
+ agentIds: [],
142
+ appKey,
143
+ dotype: topic.split("/")[1],
144
+ doStatus: false,
145
+ doErrorMsg: err.message,
146
+ dataList: [
147
+ {
148
+ typeStr: "",
149
+ name: "",
150
+ description: "",
151
+ }
152
+ ],
153
+ },
154
+ token,
155
+ log
156
+ );
159
157
  }
160
158
  }
161
159
 
@@ -163,512 +161,452 @@ export async function handleSkillsEvent(
163
161
  * 发送 HTTP 回调
164
162
  */
165
163
  async function sendCallback(
166
- baseUrl: string,
167
- topic: string,
168
- response?: CallbackResponse,
169
- authToken?: string,
170
- log?: {
171
- info?: (msg: string) => void
172
- error?: (msg: string) => void
173
- },
164
+ baseUrl: string,
165
+ topic: string,
166
+ response?: CallbackResponse,
167
+ authToken?: string,
168
+ log?: {
169
+ info?: (msg: string) => void;
170
+ error?: (msg: string) => void;
171
+ }
174
172
  ): Promise<void> {
175
173
  // 获取回调 URL
176
- const callbackPath = TOPIC_TO_CALLBACK_URL[topic]
174
+ const callbackPath = TOPIC_TO_CALLBACK_URL[topic];
177
175
  if (!callbackPath) {
178
- throw new Error(`No callback URL defined for topic: ${topic}`)
176
+ throw new Error(`No callback URL defined for topic: ${topic}`);
179
177
  }
180
178
 
181
- const callbackUrl = `${baseUrl.replace(/\/$/, '')}${callbackPath}`
182
- const responseData = JSON.stringify(response)
179
+ const callbackUrl = `${baseUrl.replace(/\/$/, "")}${callbackPath}`;
180
+ const responseData = JSON.stringify(response);
183
181
 
184
- log?.info?.(`[SkillsHandler] Sending callback to ${callbackUrl}, data: ${responseData}`)
182
+ log?.info?.(`[SkillsHandler] Sending callback to ${callbackUrl}, data: ${responseData}`);
185
183
 
186
184
  // 准备请求头
187
185
  const headers: Record<string, string> = {
188
- 'Content-Type': 'application/json',
189
- }
186
+ "Content-Type": "application/json",
187
+ };
190
188
 
191
189
  if (authToken) {
192
- headers.Authorization = authToken.startsWith('Bearer ')
193
- ? authToken
194
- : `Bearer ${authToken}`
190
+ headers["Authorization"] = authToken.startsWith("Bearer ")
191
+ ? authToken
192
+ : `Bearer ${authToken}`;
195
193
  }
196
194
 
197
195
  // 使用 AbortController 实现超时
198
- const controller = new AbortController()
199
- const timeoutId = setTimeout(() => controller.abort(), 30000)
196
+ const controller = new AbortController();
197
+ const timeoutId = setTimeout(() => controller.abort(), 30000);
200
198
 
201
199
  try {
202
200
  const res = await fetch(callbackUrl, {
203
- method: 'POST',
201
+ method: "POST",
204
202
  headers,
205
203
  body: responseData,
206
204
  signal: controller.signal,
207
- })
205
+ });
208
206
 
209
- clearTimeout(timeoutId)
207
+ clearTimeout(timeoutId);
210
208
 
211
209
  if (!res.ok) {
212
- const errorText = await res.text()
213
- throw new Error(`HTTP ${res.status}: ${res.statusText || errorText}`)
210
+ const errorText = await res.text();
211
+ throw new Error(`HTTP ${res.status}: ${res.statusText || errorText}`);
214
212
  }
215
213
 
216
- log?.info?.(`[SkillsHandler] Callback sent successfully, status: ${res.status}`)
217
- }
218
- catch (err: any) {
219
- clearTimeout(timeoutId)
214
+ log?.info?.(`[SkillsHandler] Callback sent successfully, status: ${res.status}`);
215
+ } catch (err: any) {
216
+ clearTimeout(timeoutId);
220
217
 
221
- if (err.name === 'AbortError') {
222
- log?.error?.(`[SkillsHandler] Callback timeout after 30s: ${callbackUrl}`)
223
- throw new Error('Callback timeout')
218
+ if (err.name === "AbortError") {
219
+ log?.error?.(`[SkillsHandler] Callback timeout after 30s: ${callbackUrl}`);
220
+ throw new Error("Callback timeout");
224
221
  }
225
222
 
226
- log?.error?.(`[SkillsHandler] Failed to send callback: ${err.message}`)
227
- throw err
223
+ log?.error?.(`[SkillsHandler] Failed to send callback: ${err.message}`);
224
+ throw err;
228
225
  }
229
226
  }
230
227
 
231
228
  /**
232
229
  * 从 URL 下载文件内容
233
230
  */
234
- async function downloadFromUrl(url: string): Promise<Buffer> {
231
+ async function downloadFromUrl(url: string, log?: any): Promise<Buffer> {
235
232
  return new Promise((resolve, reject) => {
236
- const client = url.startsWith('https:') ? https : http
233
+ const client = url.startsWith("https:") ? https : http;
237
234
 
238
235
  const req = client.get(url, (res) => {
239
236
  if (res.statusCode !== 200) {
240
- reject(new Error(`Failed to download: HTTP ${res.statusCode}`))
241
- return
237
+ reject(new Error(`Failed to download: HTTP ${res.statusCode}`));
238
+ return;
242
239
  }
243
240
 
244
- const chunks: Buffer[] = []
245
- res.on('data', (chunk) => {
246
- chunks.push(chunk)
247
- })
248
- res.on('end', () => {
249
- const buffer = Buffer.concat(chunks)
250
- resolve(buffer)
251
- })
252
- })
241
+ const chunks: Buffer[] = [];
242
+ res.on("data", (chunk) => {
243
+ chunks.push(chunk);
244
+ });
245
+ res.on("end", () => {
246
+ const buffer = Buffer.concat(chunks);
247
+ resolve(buffer);
248
+ });
249
+ });
253
250
 
254
- req.on('error', (err) => {
255
- reject(new Error(`Download failed: ${err.message}`))
256
- })
251
+ req.on("error", (err) => {
252
+ reject(new Error(`Download failed: ${err.message}`));
253
+ });
257
254
 
258
255
  req.setTimeout(30000, () => {
259
- req.destroy()
260
- reject(new Error('Download timeout'))
261
- })
262
- })
256
+ req.destroy();
257
+ reject(new Error("Download timeout"));
258
+ });
259
+ });
263
260
  }
264
261
 
265
262
  /**
266
263
  * 获取 OpenClaw 配置目录
267
264
  */
268
265
  function getOpenClawConfigDir(): string {
269
- const homeDir = os.homedir()
270
- return path.join(homeDir, '.openclaw')
266
+ const homeDir = os.homedir();
267
+ return path.join(homeDir, ".openclaw");
271
268
  }
272
269
 
273
270
  /**
274
271
  * 获取 skills 目录
275
272
  */
276
273
  async function getSkillsDir(agentId?: string, log?: any): Promise<string> {
277
- const openclawDir = getOpenClawConfigDir()
278
- let skillsDir = path.join(openclawDir, 'workspace', 'skills')
274
+ const openclawDir = getOpenClawConfigDir();
275
+ let skillsDir = path.join(openclawDir, "skills");
279
276
 
280
277
  // 如果有 agentId,添加到对应目录
281
278
  if (agentId && agentId.trim()) {
282
- skillsDir = path.join(openclawDir, `workspace-${agentId}`, 'skills')
279
+ skillsDir = path.join(openclawDir, "agents", `openclaw-workclaw-${agentId}`, "skills");
283
280
  }
284
281
 
285
282
  try {
286
- await fs.mkdir(skillsDir, { recursive: true })
287
- }
288
- catch (err: any) {
289
- log?.error?.(`[SkillsHandler] Failed to create skills directory: ${err.message}`)
290
- throw new Error(`Failed to create skills directory: ${err.message}`)
283
+ await fs.mkdir(skillsDir, { recursive: true });
284
+ } catch (err: any) {
285
+ log?.error?.(`[SkillsHandler] Failed to create skills directory: ${err.message}`);
286
+ throw new Error(`Failed to create skills directory: ${err.message}`);
291
287
  }
292
288
 
293
- return skillsDir
289
+ return skillsDir;
294
290
  }
295
291
 
296
292
  /**
297
293
  * 安装 skill 到 OpenClaw
298
294
  */
299
295
  async function installSkillToClaw(
300
- skillName: string,
301
- skillContent: Buffer,
302
- agentId?: string,
303
- log?: any,
304
- ): Promise<{ success: boolean, message: string, path?: string }> {
296
+ skillName: string,
297
+ skillContent: Buffer,
298
+ agentId?: string,
299
+ log?: any
300
+ ): Promise<{ success: boolean; message: string; path?: string }> {
305
301
  try {
306
302
  // 检测是否为 ZIP 文件(ZIP 文件以 PK 开头)
307
- const isZip = skillContent.toString('utf8', 0, 2) === 'PK'
303
+ const isZip = skillContent.toString('utf8', 0, 2) === 'PK';
308
304
 
309
305
  if (isZip) {
310
- log?.info?.(`[SkillsHandler] Detected ZIP file, extracting...`)
306
+ log?.info?.(`[SkillsHandler] Detected ZIP file, extracting...`);
311
307
 
312
308
  // 获取 skills 目录
313
- const skillsDir = await getSkillsDir(agentId, log)
309
+ const skillsDir = await getSkillsDir(agentId, log);
314
310
  // 创建以 skillName 命名的目录
315
- const skillDir = path.join(skillsDir, skillName)
316
- const tempZipPath = path.join(skillsDir, `${skillName}.zip`)
311
+ const skillDir = path.join(skillsDir, skillName);
312
+ const tempZipPath = path.join(skillsDir, `${skillName}.zip`);
317
313
 
318
314
  try {
319
315
  // 创建技能目录
320
- await fs.mkdir(skillDir, { recursive: true })
321
- log?.info?.(`[SkillsHandler] Skill directory created: ${skillDir}`)
316
+ await fs.mkdir(skillDir, { recursive: true });
317
+ log?.info?.(`[SkillsHandler] Skill directory created: ${skillDir}`);
322
318
 
323
319
  // 写入临时 ZIP 文件
324
- await fs.writeFile(tempZipPath, skillContent)
325
- log?.info?.(`[SkillsHandler] Temporary ZIP file created: ${tempZipPath}`)
320
+ await fs.writeFile(tempZipPath, skillContent);
321
+ log?.info?.(`[SkillsHandler] Temporary ZIP file created: ${tempZipPath}`);
326
322
 
327
323
  // 解压 ZIP 文件到技能目录
328
324
  if (process.platform === 'win32') {
329
325
  // Windows 系统使用 PowerShell 解压
330
- await execAsync(`powershell -Command "Expand-Archive -Path '${tempZipPath}' -DestinationPath '${skillDir}' -Force"`)
331
- }
332
- else {
326
+ await execAsync(`powershell -Command "Expand-Archive -Path '${tempZipPath}' -DestinationPath '${skillDir}' -Force"`);
327
+ } else {
333
328
  // Unix-like 系统使用 unzip 命令
334
- await execAsync(`unzip -o '${tempZipPath}' -d '${skillDir}'`)
329
+ await execAsync(`unzip -o '${tempZipPath}' -d '${skillDir}'`);
335
330
  }
336
331
 
337
- log?.info?.(`[SkillsHandler] ZIP file extracted successfully to: ${skillDir}`)
332
+ log?.info?.(`[SkillsHandler] ZIP file extracted successfully to: ${skillDir}`);
338
333
 
339
334
  // 删除临时 ZIP 文件
340
- await fs.unlink(tempZipPath)
341
- log?.info?.(`[SkillsHandler] Temporary ZIP file deleted`)
335
+ await fs.unlink(tempZipPath);
336
+ log?.info?.(`[SkillsHandler] Temporary ZIP file deleted`);
342
337
 
343
338
  return {
344
339
  success: true,
345
340
  message: `ZIP skill package ${skillName} installed successfully`,
346
341
  path: skillDir,
347
- }
348
- }
349
- catch (err: any) {
350
- log?.error?.(`[SkillsHandler] Failed to extract ZIP file: ${err.message}`)
342
+ };
343
+ } catch (err: any) {
344
+ log?.error?.(`[SkillsHandler] Failed to extract ZIP file: ${err.message}`);
351
345
  // 清理临时文件和目录
352
346
  try {
353
- await fs.unlink(tempZipPath)
354
- }
355
- catch {}
347
+ await fs.unlink(tempZipPath);
348
+ } catch {}
356
349
  try {
357
- await fs.rm(skillDir, { recursive: true, force: true })
358
- }
359
- catch {}
350
+ await fs.rm(skillDir, { recursive: true, force: true });
351
+ } catch {}
360
352
  return {
361
353
  success: false,
362
354
  message: `Failed to extract ZIP file: ${err.message}`,
363
- }
355
+ };
364
356
  }
365
357
  }
366
358
 
367
359
  // 尝试解析为 JSON
368
- let skillData: any
360
+ let skillData: any;
369
361
  try {
370
- const jsonString = skillContent.toString('utf8')
371
- skillData = JSON.parse(jsonString)
372
- }
373
- catch (err: any) {
362
+ const jsonString = skillContent.toString('utf8');
363
+ skillData = JSON.parse(jsonString);
364
+ } catch (err: any) {
374
365
  return {
375
366
  success: false,
376
367
  message: `Invalid skill JSON: ${err.message}`,
377
- }
368
+ };
378
369
  }
379
370
 
380
371
  // 验证必要的字段
381
372
  if (!skillData.id || !skillData.name) {
382
373
  return {
383
374
  success: false,
384
- message: 'Missing required fields in skill: id, name',
385
- }
375
+ message: "Missing required fields in skill: id, name",
376
+ };
386
377
  }
387
378
 
388
379
  // 获取 skills 目录
389
- const skillsDir = await getSkillsDir(agentId, log)
390
- const skillFilePath = path.join(skillsDir, `${skillName}.json`)
380
+ const skillsDir = await getSkillsDir(agentId, log);
381
+ const skillFilePath = path.join(skillsDir, `${skillName}.json`);
391
382
 
392
383
  // 写入 skill 文件
393
- await fs.writeFile(skillFilePath, skillContent, 'utf-8')
394
- log?.info?.(`[SkillsHandler] Skill saved to: ${skillFilePath}`)
384
+ await fs.writeFile(skillFilePath, skillContent, "utf-8");
385
+ log?.info?.(`[SkillsHandler] Skill saved to: ${skillFilePath}`);
395
386
 
396
387
  return {
397
388
  success: true,
398
389
  message: `Skill ${skillName} installed successfully`,
399
390
  path: skillFilePath,
400
- }
401
- }
402
- catch (err: any) {
391
+ };
392
+ } catch (err: any) {
403
393
  return {
404
394
  success: false,
405
395
  message: `Failed to install skill: ${err.message}`,
406
- }
396
+ };
407
397
  }
408
398
  }
409
399
 
410
400
  /**
411
401
  * 创建 Skill
412
402
  */
413
- async function installSkillToLocations(skillName: string, skillContent: Buffer, locations: (string | undefined)[], log?: any): Promise<any[]> {
414
- const installResults = []
415
-
416
- for (const agentId of locations) {
417
- const installResult = await installSkillToClaw(skillName, skillContent, agentId, log)
418
- if (!installResult.success) {
419
- const error: any = new Error(`Failed to install skill to ${agentId ? `agent ${agentId}` : 'default location'}: ${installResult.message}`)
420
- error.code = 'INSTALL_FAILED'
421
- throw error
422
- }
423
- installResults.push(installResult)
424
- }
425
-
426
- return installResults
427
- }
428
-
429
403
  async function handleCreateSkill(
430
- data: any,
431
- log?: {
432
- info?: (msg: string) => void
433
- error?: (msg: string) => void
434
- },
404
+ data: any,
405
+ log?: {
406
+ info?: (msg: string) => void;
407
+ error?: (msg: string) => void;
408
+ }
435
409
  ): Promise<any> {
436
- const { userId, skillName, unloadUrl, mainId } = data
437
- const agentIds = Array.isArray(data?.agentIds) ? data.agentIds : (data?.agentIds ? [data.agentIds] : [])
410
+ const { userId, skillName, unloadUrl } = data;
411
+ const agentIds = Array.isArray(data?.agentIds) ? data.agentIds : (data?.agentIds ? [data.agentIds] : []);
438
412
 
439
413
  if (!userId || !skillName) {
440
- const error: any = new Error('Missing required fields: userId, skillName')
441
- error.code = 'MISSING_FIELDS'
442
- throw error
414
+ const error: any = new Error("Missing required fields: userId, skillName");
415
+ error.code = "MISSING_FIELDS";
416
+ throw error;
443
417
  }
444
418
 
445
- log?.info?.(`[SkillsHandler] Creating skill: ${skillName}`)
446
- log?.info?.(`[SkillsHandler] mainId: ${mainId}`)
419
+ log?.info?.(`[SkillsHandler] Creating skill: ${skillName}`);
447
420
  if (agentIds.length > 0) {
448
- log?.info?.(`[SkillsHandler] Target agents: ${agentIds.join(', ')}`)
421
+ log?.info?.(`[SkillsHandler] Target agents: ${agentIds.join(", ")}`);
449
422
  }
450
423
 
451
424
  // 如果提供了 URL,从 URL 下载 skill 定义
452
425
  if (unloadUrl) {
453
- log?.info?.(`[SkillsHandler] Downloading skill from URL: ${unloadUrl}`)
426
+ log?.info?.(`[SkillsHandler] Downloading skill from URL: ${unloadUrl}`);
454
427
 
455
428
  try {
456
429
  // 下载 skill 定义
457
- const skillContent = await downloadFromUrl(unloadUrl)
458
- log?.info?.(`[SkillsHandler] Downloaded skill content, length: ${skillContent.length} bytes`)
459
-
460
- // 确定安装位置
461
- const installLocations: (string | undefined)[] = []
462
-
463
- // 检查 agentIds 是否只包含 mainId
464
- const hasOnlyMainId = agentIds.length === 1 && agentIds[0] === mainId
465
- // 检查 agentIds 是否包含 mainId 和其他 id
466
- const hasMainIdAndOthers = agentIds.includes(mainId) && agentIds.length > 1
467
-
468
- if (hasOnlyMainId) {
469
- // 如果只有 mainId,安装到默认位置
470
- log?.info?.(`[SkillsHandler] Only mainId found, installing to default location`)
471
- installLocations.push(undefined)
472
- }
473
- else if (hasMainIdAndOthers) {
474
- // 如果有 mainId 和其他 id,安装到默认位置和其他 id 位置
475
- log?.info?.(`[SkillsHandler] mainId and other agents found, installing to default location and other agents`)
476
- installLocations.push(undefined)
477
- // 添加其他 id 位置(排除 mainId)
478
- installLocations.push(...agentIds.filter((id: any) => id !== mainId))
479
- }
480
- else if (agentIds.length > 0) {
481
- // 如果没有 mainId,安装到所有指定的 agentId 位置
482
- log?.info?.(`[SkillsHandler] No mainId found, installing to all specified agents`)
483
- installLocations.push(...agentIds)
484
- }
485
- else {
486
- // 如果没有指定智能体,安装到默认位置
487
- log?.info?.(`[SkillsHandler] No agents specified, installing to default location`)
488
- installLocations.push(undefined)
430
+ const skillContent = await downloadFromUrl(unloadUrl, log);
431
+ log?.info?.(`[SkillsHandler] Downloaded skill content, length: ${skillContent.length} bytes`);
432
+
433
+ // 安装 skill 到多个智能体
434
+ const installResults = [];
435
+ for (const agentId of agentIds) {
436
+ const installResult = await installSkillToClaw(skillName, skillContent, agentId, log);
437
+ if (!installResult.success) {
438
+ const error: any = new Error(`Failed to install skill to agent ${agentId}: ${installResult.message}`);
439
+ error.code = "INSTALL_FAILED";
440
+ throw error;
441
+ }
442
+ installResults.push(installResult);
489
443
  }
490
444
 
491
- // 安装技能到所有位置
492
- const installResults = await installSkillToLocations(skillName, skillContent, installLocations, log)
445
+ // 如果没有指定智能体,安装到默认位置
446
+ if (agentIds.length === 0) {
447
+ const installResult = await installSkillToClaw(skillName, skillContent, undefined, log);
448
+ if (!installResult.success) {
449
+ const error: any = new Error(installResult.message);
450
+ error.code = "INSTALL_FAILED";
451
+ throw error;
452
+ }
453
+ installResults.push(installResult);
454
+ }
493
455
 
494
- log?.info?.(`[SkillsHandler] Skill installed to ${installResults.length} locations`)
456
+ log?.info?.(`[SkillsHandler] Skill installed to ${installResults.length} locations`);
495
457
 
496
458
  return {
497
459
  userId,
498
460
  skillName,
499
- agentId: '',
461
+ agentId: "",
500
462
  agentIds: agentIds.length > 0 ? agentIds : undefined,
501
- status: 'add',
463
+ status: "add",
502
464
  installedAt: new Date().toISOString(),
503
- }
504
- }
505
- catch (err: any) {
506
- log?.error?.(`[SkillsHandler] Failed to create skill from URL: ${err.message}`)
507
- throw err
465
+ };
466
+ } catch (err: any) {
467
+ log?.error?.(`[SkillsHandler] Failed to create skill from URL: ${err.message}`);
468
+ throw err;
508
469
  }
509
470
  }
510
471
 
511
472
  return {
512
473
  userId,
513
474
  skillName,
514
- agentId: '',
475
+ agentId: "",
515
476
  agentIds: agentIds.length > 0 ? agentIds : undefined,
516
- status: 'add',
477
+ status: "add",
517
478
  createdAt: new Date().toISOString(),
518
- }
479
+ };
519
480
  }
520
481
 
521
482
  /**
522
483
  * 更新 Skill
523
484
  */
524
485
  async function handleUpdateSkill(
525
- data: any,
526
- log?: {
527
- info?: (msg: string) => void
528
- error?: (msg: string) => void
529
- },
486
+ data: any,
487
+ log?: {
488
+ info?: (msg: string) => void;
489
+ error?: (msg: string) => void;
490
+ }
530
491
  ): Promise<any> {
531
- const { id, ...updates } = data
492
+ const { id, ...updates } = data;
532
493
 
533
494
  if (!id) {
534
- const error: any = new Error('Missing required field: id')
535
- error.code = 'MISSING_FIELDS'
536
- throw error
495
+ const error: any = new Error("Missing required field: id");
496
+ error.code = "MISSING_FIELDS";
497
+ throw error;
537
498
  }
538
499
 
539
- log?.info?.(`[SkillsHandler] Updating skill: ${id}`)
500
+ log?.info?.(`[SkillsHandler] Updating skill: ${id}`);
540
501
 
541
502
  return {
542
503
  id,
543
504
  ...updates,
544
- status: 'updated',
505
+ status: "updated",
545
506
  updatedAt: new Date().toISOString(),
546
- }
507
+ };
547
508
  }
548
509
 
549
510
  /**
550
511
  * 删除 Skill
551
512
  */
552
- async function deleteSkillAtLocation(skillName: string, agentId: string | undefined, log?: any): Promise<boolean> {
553
- try {
554
- const skillsDir = await getSkillsDir(agentId, log)
555
-
556
- const skillFilePath = path.join(skillsDir, `${skillName}.json`)
557
- const skillDirPath = path.join(skillsDir, skillName)
558
-
559
- let deleted = false
560
-
561
- // 尝试删除技能文件
562
- try {
563
- await fs.unlink(skillFilePath)
564
- log?.info?.(`[SkillsHandler] Skill file deleted: ${skillFilePath}`)
565
- deleted = true
566
- }
567
- catch {
568
- // 文件不存在,继续尝试删除目录
569
- }
570
-
571
- // 尝试删除技能目录(ZIP 安装的技能)
572
- try {
573
- await fs.rm(skillDirPath, { recursive: true, force: true })
574
- log?.info?.(`[SkillsHandler] Skill directory deleted: ${skillDirPath}`)
575
- deleted = true
576
- }
577
- catch {
578
- // 目录不存在,继续
579
- }
580
-
581
- return deleted
582
- }
583
- catch (err: any) {
584
- log?.error?.(`[SkillsHandler] Failed to delete skill at location: ${err.message}`)
585
- return false
586
- }
587
- }
588
-
589
513
  async function handleDeleteSkill(
590
- data: any,
591
- log?: {
592
- info?: (msg: string) => void
593
- error?: (msg: string) => void
594
- },
514
+ data: any,
515
+ log?: {
516
+ info?: (msg: string) => void;
517
+ error?: (msg: string) => void;
518
+ }
595
519
  ): Promise<any> {
596
- const { userId, skillName, agentIds, mainId } = data
597
- const agentIdsList = Array.isArray(agentIds) ? agentIds : (agentIds ? [agentIds] : [])
520
+ const { userId, skillName, agentIds } = data;
521
+ const agentIdsList = Array.isArray(agentIds) ? agentIds : (agentIds ? [agentIds] : []);
598
522
 
599
523
  if (!skillName) {
600
- const error: any = new Error('Missing required field: skillName')
601
- error.code = 'MISSING_FIELDS'
602
- throw error
524
+ const error: any = new Error("Missing required field: skillName");
525
+ error.code = "MISSING_FIELDS";
526
+ throw error;
603
527
  }
604
528
 
605
- log?.info?.(`[SkillsHandler] Deleting skill: ${skillName}`)
606
- log?.info?.(`[SkillsHandler] mainId: ${mainId}`)
529
+ log?.info?.(`[SkillsHandler] Deleting skill: ${skillName}`);
607
530
  if (agentIdsList.length > 0) {
608
- log?.info?.(`[SkillsHandler] Target agents: ${agentIdsList.join(', ')}`)
531
+ log?.info?.(`[SkillsHandler] Target agents: ${agentIdsList.join(", ")}`);
609
532
  }
610
533
 
611
534
  try {
612
- let deleted = false
535
+ let deleted = false;
613
536
 
614
- // 检查 agentIds 是否只包含 mainId
615
- const hasOnlyMainId = agentIdsList.length === 1 && agentIdsList[0] === mainId
616
- // 检查 agentIds 是否包含 mainId 和其他 id
617
- const hasMainIdAndOthers = agentIdsList.includes(mainId) && agentIdsList.length > 1
537
+ // 从多个智能体中删除技能
538
+ for (const agentId of agentIdsList) {
539
+ // 获取技能目录
540
+ const skillsDir = await getSkillsDir(agentId, log);
618
541
 
619
- if (hasOnlyMainId) {
620
- // 如果只有 mainId,从默认位置删除
621
- log?.info?.(`[SkillsHandler] Only mainId found, deleting from default location`)
622
- deleted = await deleteSkillAtLocation(skillName, undefined, log)
623
- }
624
- else if (hasMainIdAndOthers) {
625
- // 如果有 mainId 和其他 id,从默认位置和其他 id 位置删除
626
- log?.info?.(`[SkillsHandler] mainId and other agents found, deleting from default location and other agents`)
627
-
628
- // 从默认位置删除
629
- const defaultDeleted = await deleteSkillAtLocation(skillName, undefined, log)
630
- deleted = deleted || defaultDeleted
631
-
632
- // 从其他 id 位置删除(排除 mainId)
633
- for (const agentId of agentIdsList) {
634
- if (agentId !== mainId) {
635
- const agentDeleted = await deleteSkillAtLocation(skillName, agentId, log)
636
- deleted = deleted || agentDeleted
637
- }
542
+ // 检查技能文件和目录
543
+ const skillFilePath = path.join(skillsDir, `${skillName}.json`);
544
+ const skillDirPath = path.join(skillsDir, skillName);
545
+
546
+ // 尝试删除技能文件
547
+ try {
548
+ await fs.unlink(skillFilePath);
549
+ log?.info?.(`[SkillsHandler] Skill file deleted: ${skillFilePath}`);
550
+ deleted = true;
551
+ } catch (err: any) {
552
+ // 文件不存在,继续尝试删除目录
638
553
  }
639
- }
640
- else if (agentIdsList.length > 0) {
641
- // 如果没有 mainId,从所有指定的 agentId 位置删除
642
- log?.info?.(`[SkillsHandler] No mainId found, deleting from all specified agents`)
643
- for (const agentId of agentIdsList) {
644
- const agentDeleted = await deleteSkillAtLocation(skillName, agentId, log)
645
- deleted = deleted || agentDeleted
554
+
555
+ // 尝试删除技能目录(ZIP 安装的技能)
556
+ try {
557
+ await fs.rm(skillDirPath, { recursive: true, force: true });
558
+ log?.info?.(`[SkillsHandler] Skill directory deleted: ${skillDirPath}`);
559
+ deleted = true;
560
+ } catch (err: any) {
561
+ // 目录不存在,继续
646
562
  }
647
563
  }
648
- else {
649
- // 如果没有指定智能体,从默认位置删除
650
- log?.info?.(`[SkillsHandler] No agents specified, deleting from default location`)
651
- deleted = await deleteSkillAtLocation(skillName, undefined, log)
564
+
565
+ // 如果没有指定智能体,从默认位置删除
566
+ if (agentIdsList.length === 0) {
567
+ // 获取默认技能目录
568
+ const skillsDir = await getSkillsDir(undefined, log);
569
+
570
+ // 检查技能文件和目录
571
+ const skillFilePath = path.join(skillsDir, `${skillName}.json`);
572
+ const skillDirPath = path.join(skillsDir, skillName);
573
+
574
+ // 尝试删除技能文件
575
+ try {
576
+ await fs.unlink(skillFilePath);
577
+ log?.info?.(`[SkillsHandler] Skill file deleted: ${skillFilePath}`);
578
+ deleted = true;
579
+ } catch (err: any) {
580
+ // 文件不存在,继续尝试删除目录
581
+ }
582
+
583
+ // 尝试删除技能目录(ZIP 安装的技能)
584
+ try {
585
+ await fs.rm(skillDirPath, { recursive: true, force: true });
586
+ log?.info?.(`[SkillsHandler] Skill directory deleted: ${skillDirPath}`);
587
+ deleted = true;
588
+ } catch (err: any) {
589
+ // 目录不存在,继续
590
+ }
652
591
  }
653
592
 
654
593
  if (!deleted) {
655
- const error: any = new Error(`Skill not found: ${skillName}`)
656
- error.code = 'SKILL_NOT_FOUND'
657
- throw error
594
+ const error: any = new Error(`Skill not found: ${skillName}`);
595
+ error.code = "SKILL_NOT_FOUND";
596
+ throw error;
658
597
  }
659
598
 
660
599
  return {
661
600
  userId,
662
601
  skillName,
663
- agentId: '',
602
+ agentId: "",
664
603
  agentIds: agentIdsList.length > 0 ? agentIdsList : undefined,
665
- status: 'remove',
604
+ status: "remove",
666
605
  deletedAt: new Date().toISOString(),
667
- }
668
- }
669
- catch (err: any) {
670
- log?.error?.(`[SkillsHandler] Failed to delete skill: ${err.message}`)
671
- throw err
606
+ };
607
+ } catch (err: any) {
608
+ log?.error?.(`[SkillsHandler] Failed to delete skill: ${err.message}`);
609
+ throw err;
672
610
  }
673
611
  }
674
612
 
@@ -676,67 +614,65 @@ async function handleDeleteSkill(
676
614
  * 查询 Skills 列表
677
615
  */
678
616
  async function handleListSkills(
679
- data: any,
680
- log?: {
681
- info?: (msg: string) => void
682
- error?: (msg: string) => void
683
- },
617
+ data: any,
618
+ log?: {
619
+ info?: (msg: string) => void;
620
+ error?: (msg: string) => void;
621
+ }
684
622
  ): Promise<any> {
685
- const { filter, limit = 100 } = data
623
+ const { filter, limit = 100 } = data;
686
624
 
687
- log?.info?.(`[SkillsHandler] Listing skills, filter: ${filter}, limit: ${limit}`)
625
+ log?.info?.(`[SkillsHandler] Listing skills, filter: ${filter}, limit: ${limit}`);
688
626
 
689
627
  try {
690
628
  // 调用 openclaw skills list 命令
691
- const { stdout } = await execAsync('openclaw skills list --json', {
629
+ const { stdout } = await execAsync("openclaw skills list --json", {
692
630
  timeout: 10000,
693
- })
631
+ });
694
632
 
695
- let skills: any[] = []
633
+ let skills: any[] = [];
696
634
  try {
697
- const parsed = JSON.parse(stdout)
698
- skills = parsed.skills || parsed
699
- }
700
- catch {
635
+ const parsed = JSON.parse(stdout);
636
+ skills = parsed.skills || parsed;
637
+ } catch {
701
638
  // 解析文本输出
702
- const lines = stdout.split('\n')
639
+ const lines = stdout.split("\n");
703
640
  skills = lines
704
- .filter(line => line.trim() && !line.includes('Skills'))
705
- .map((line) => {
706
- const parts = line.split(/\s{2,}/).map(p => p.trim())
707
- if (parts.length >= 3) {
708
- return {
709
- status: parts[0]?.includes('') ? 'ready' : 'missing',
710
- name: parts[1],
711
- description: parts[2],
712
- source: parts[3] || 'openclaw-bundled',
641
+ .filter((line) => line.trim() && !line.includes("Skills"))
642
+ .map((line) => {
643
+ const parts = line.split(/\s{2,}/).map((p) => p.trim());
644
+ if (parts.length >= 3) {
645
+ return {
646
+ status: parts[0]?.includes("") ? "ready" : "missing",
647
+ name: parts[1],
648
+ description: parts[2],
649
+ source: parts[3] || "openclaw-bundled",
650
+ };
713
651
  }
714
- }
715
- return null
716
- })
717
- .filter((s): s is any => s !== null)
652
+ return null;
653
+ })
654
+ .filter((s): s is any => s !== null);
718
655
  }
719
656
 
720
657
  // 应用过滤
721
658
  if (filter) {
722
- skills = skills.filter(s => s.status === filter)
659
+ skills = skills.filter((s) => s.status === filter);
723
660
  }
724
661
 
725
662
  // 应用 limit
726
- skills = skills.slice(0, limit)
663
+ skills = skills.slice(0, limit);
727
664
 
728
665
  return {
729
666
  total: skills.length,
730
667
  skills,
731
- }
732
- }
733
- catch (err: any) {
734
- log?.error?.(`[SkillsHandler] Failed to list skills: ${err.message}`)
668
+ };
669
+ } catch (err: any) {
670
+ log?.error?.(`[SkillsHandler] Failed to list skills: ${err.message}`);
735
671
  // 返回空列表而不是报错
736
672
  return {
737
673
  total: 0,
738
674
  skills: [],
739
- }
675
+ };
740
676
  }
741
677
  }
742
678
 
@@ -744,62 +680,62 @@ async function handleListSkills(
744
680
  * 查询单个 Skill
745
681
  */
746
682
  async function handleGetSkill(
747
- data: any,
748
- log?: {
749
- info?: (msg: string) => void
750
- error?: (msg: string) => void
751
- },
683
+ data: any,
684
+ log?: {
685
+ info?: (msg: string) => void;
686
+ error?: (msg: string) => void;
687
+ }
752
688
  ): Promise<any> {
753
- const { id } = data
689
+ const { id } = data;
754
690
 
755
691
  if (!id) {
756
- const error: any = new Error('Missing required field: id')
757
- error.code = 'MISSING_FIELDS'
758
- throw error
692
+ const error: any = new Error("Missing required field: id");
693
+ error.code = "MISSING_FIELDS";
694
+ throw error;
759
695
  }
760
696
 
761
- log?.info?.(`[SkillsHandler] Getting skill: ${id}`)
697
+ log?.info?.(`[SkillsHandler] Getting skill: ${id}`);
762
698
 
763
699
  // TODO: 实现实际的 skill 查询逻辑
764
700
 
765
701
  return {
766
702
  id,
767
703
  name: `Skill ${id}`,
768
- description: 'Skill description',
769
- status: 'ready',
770
- }
704
+ description: "Skill description",
705
+ status: "ready",
706
+ };
771
707
  }
772
708
 
773
709
  /**
774
710
  * 执行 Skill
775
711
  */
776
712
  async function handleInvokeSkill(
777
- data: any,
778
- log?: {
779
- info?: (msg: string) => void
780
- error?: (msg: string) => void
781
- },
713
+ data: any,
714
+ log?: {
715
+ info?: (msg: string) => void;
716
+ error?: (msg: string) => void;
717
+ }
782
718
  ): Promise<any> {
783
- const { id, input, context } = data
719
+ const { id, input, context } = data;
784
720
 
785
721
  if (!id) {
786
- const error: any = new Error('Missing required field: id')
787
- error.code = 'MISSING_FIELDS'
788
- throw error
722
+ const error: any = new Error("Missing required field: id");
723
+ error.code = "MISSING_FIELDS";
724
+ throw error;
789
725
  }
790
726
 
791
- log?.info?.(`[SkillsHandler] Invoking skill: ${id}`)
727
+ log?.info?.(`[SkillsHandler] Invoking skill: ${id}`);
792
728
 
793
729
  // TODO: 实现实际的 skill 执行逻辑
794
730
 
795
731
  return {
796
732
  id,
797
- status: 'completed',
733
+ status: "completed",
798
734
  output: {
799
735
  result: `Skill ${id} executed successfully`,
800
736
  input,
801
737
  context,
802
738
  },
803
739
  executedAt: new Date().toISOString(),
804
- }
805
- }
740
+ };
741
+ }