@workclaw/openclaw-workclaw 1.0.16 → 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 +12 -5
  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
@@ -1,123 +1,116 @@
1
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
2
- import { Buffer } from 'node:buffer'
3
- import { mkdir, readFile, stat, unlink, writeFile } from 'node:fs/promises'
4
- import path from 'node:path'
1
+ import { mkdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
+ import { resolveWorkspaceDir } from "./workspace.js";
5
4
 
6
- import { resolveWorkspaceDir } from './workspace.js'
7
-
8
- const promptNames = ['SOUL.md', 'IDENTITY.md'] as const
5
+ const promptNames = ["SOUL.md", "USER.md"] as const;
9
6
 
10
7
  function normalizePromptName(raw: string | null | undefined): (typeof promptNames)[number] | null {
11
- const name = String(raw ?? '').trim().toUpperCase()
12
- if (name === 'SOUL' || name === 'SOUL.MD')
13
- return 'SOUL.md'
14
- if (name === 'IDENTITY' || name === 'IDENTITY.MD')
15
- return 'IDENTITY.md'
16
- return null
8
+ const name = String(raw ?? "").trim().toUpperCase();
9
+ if (name === "SOUL" || name === "SOUL.MD") return "SOUL.md";
10
+ if (name === "USER" || name === "USER.MD") return "USER.md";
11
+ return null;
17
12
  }
18
13
 
14
+ import path from "node:path";
15
+
19
16
  function resolvePromptPath(api: OpenClawPluginApi, name: (typeof promptNames)[number]): string {
20
- return path.join(resolveWorkspaceDir(api), name)
17
+ return path.join(resolveWorkspaceDir(api), name);
21
18
  }
22
19
 
23
- function sendJson(res: any, statusCode: number, payload: unknown): void {
24
- res.statusCode = statusCode
25
- res.setHeader('Content-Type', 'application/json')
26
- res.end(JSON.stringify(payload))
20
+ function sendJson(res: any, statusCode: number, payload: unknown) {
21
+ res.statusCode = statusCode;
22
+ res.setHeader("Content-Type", "application/json");
23
+ res.end(JSON.stringify(payload));
27
24
  }
28
25
 
29
26
  export function createPromptsApiHandler(api: OpenClawPluginApi) {
30
27
  return async (req: any, res: any) => {
31
- const method = String(req.method ?? 'GET').toUpperCase()
32
- const url = new URL(req.url ?? '', 'http://localhost')
33
- const name = normalizePromptName(url.searchParams.get('name'))
28
+ const method = String(req.method ?? "GET").toUpperCase();
29
+ const url = new URL(req.url ?? "", "http://localhost");
30
+ const name = normalizePromptName(url.searchParams.get("name"));
34
31
 
35
- if (method === 'GET') {
32
+ if (method === "GET") {
36
33
  if (name) {
37
- const filePath = resolvePromptPath(api, name)
34
+ const filePath = resolvePromptPath(api, name);
38
35
  try {
39
- const content = await readFile(filePath, 'utf-8')
40
- sendJson(res, 200, { ok: true, name, content })
41
- return
42
- }
43
- catch {
44
- sendJson(res, 404, { ok: false, error: 'Not Found' })
45
- return
36
+ const content = await readFile(filePath, "utf-8");
37
+ sendJson(res, 200, { ok: true, name, content });
38
+ return;
39
+ } catch (error) {
40
+ sendJson(res, 404, { ok: false, error: "Not Found" });
41
+ return;
46
42
  }
47
43
  }
48
44
 
49
45
  const entries = await Promise.all(
50
46
  promptNames.map(async (promptName) => {
51
- const filePath = resolvePromptPath(api, promptName)
47
+ const filePath = resolvePromptPath(api, promptName);
52
48
  try {
53
- const info = await stat(filePath)
54
- return { name: promptName, exists: true, bytes: info.size }
55
- }
56
- catch {
57
- return { name: promptName, exists: false, bytes: 0 }
49
+ const info = await stat(filePath);
50
+ return { name: promptName, exists: true, bytes: info.size };
51
+ } catch {
52
+ return { name: promptName, exists: false, bytes: 0 };
58
53
  }
59
54
  }),
60
- )
61
- sendJson(res, 200, { ok: true, entries })
62
- return
55
+ );
56
+ sendJson(res, 200, { ok: true, entries });
57
+ return;
63
58
  }
64
59
 
65
- if (method === 'PUT' || method === 'POST') {
66
- const raw = await readRequestBody(req)
67
- let input: any = {}
60
+ if (method === "PUT" || method === "POST") {
61
+ const raw = await readRequestBody(req);
62
+ let input: any = {};
68
63
  try {
69
- input = raw ? JSON.parse(raw) : {}
70
- }
71
- catch {
72
- sendJson(res, 400, { ok: false, error: 'Invalid JSON' })
73
- return
64
+ input = raw ? JSON.parse(raw) : {};
65
+ } catch {
66
+ sendJson(res, 400, { ok: false, error: "Invalid JSON" });
67
+ return;
74
68
  }
75
69
 
76
- const targetName = normalizePromptName(input?.name)
77
- const content = typeof input?.content === 'string' ? input.content : null
70
+ const targetName = normalizePromptName(input?.name);
71
+ const content = typeof input?.content === "string" ? input.content : null;
78
72
 
79
73
  if (!targetName || content === null) {
80
- sendJson(res, 400, { ok: false, error: 'Missing name or content' })
81
- return
74
+ sendJson(res, 400, { ok: false, error: "Missing name or content" });
75
+ return;
82
76
  }
83
77
 
84
- const workspaceDir = resolveWorkspaceDir(api)
85
- await mkdir(workspaceDir, { recursive: true })
86
- await writeFile(resolvePromptPath(api, targetName), content, { encoding: 'utf-8' })
87
- sendJson(res, 200, { ok: true, name: targetName })
88
- return
78
+ const workspaceDir = resolveWorkspaceDir(api);
79
+ await mkdir(workspaceDir, { recursive: true });
80
+ await writeFile(resolvePromptPath(api, targetName), content, { encoding: "utf-8" });
81
+ sendJson(res, 200, { ok: true, name: targetName });
82
+ return;
89
83
  }
90
84
 
91
- if (method === 'DELETE') {
85
+ if (method === "DELETE") {
92
86
  if (!name) {
93
- sendJson(res, 400, { ok: false, error: 'Missing name' })
94
- return
87
+ sendJson(res, 400, { ok: false, error: "Missing name" });
88
+ return;
95
89
  }
96
90
 
97
- const filePath = resolvePromptPath(api, name)
91
+ const filePath = resolvePromptPath(api, name);
98
92
  try {
99
- await unlink(filePath)
100
- sendJson(res, 200, { ok: true, name, deleted: true })
101
- return
102
- }
103
- catch {
104
- sendJson(res, 200, { ok: true, name, deleted: false })
105
- return
93
+ await unlink(filePath);
94
+ sendJson(res, 200, { ok: true, name, deleted: true });
95
+ return;
96
+ } catch {
97
+ sendJson(res, 200, { ok: true, name, deleted: false });
98
+ return;
106
99
  }
107
100
  }
108
101
 
109
- sendJson(res, 405, { ok: false, error: 'Method Not Allowed' })
110
- }
102
+ sendJson(res, 405, { ok: false, error: "Method Not Allowed" });
103
+ };
111
104
  }
112
105
 
113
106
  async function readRequestBody(req: any): Promise<string> {
114
- const chunks: Buffer[] = []
107
+ const chunks: Buffer[] = [];
115
108
  await new Promise<void>((resolve, reject) => {
116
- req.on('data', (chunk: any) => {
117
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
118
- })
119
- req.on('end', () => resolve())
120
- req.on('error', (err: unknown) => reject(err))
121
- })
122
- return Buffer.concat(chunks).toString('utf-8')
109
+ req.on("data", (chunk: any) => {
110
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
111
+ });
112
+ req.on("end", () => resolve());
113
+ req.on("error", (err: unknown) => reject(err));
114
+ });
115
+ return Buffer.concat(chunks).toString("utf-8");
123
116
  }
@@ -1,23 +1,22 @@
1
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
2
- import { Buffer } from 'node:buffer'
3
- import { getOpenclawWorkclawRuntime } from '../runtime.js'
4
-
5
- function sendJson(res: any, statusCode: number, payload: unknown): void {
6
- res.statusCode = statusCode
7
- res.setHeader('Content-Type', 'application/json')
8
- res.end(JSON.stringify(payload))
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { getWorkclawRuntime } from "../runtime.js";
3
+
4
+ function sendJson(res: any, statusCode: number, payload: unknown) {
5
+ res.statusCode = statusCode;
6
+ res.setHeader("Content-Type", "application/json");
7
+ res.end(JSON.stringify(payload));
9
8
  }
10
9
 
11
10
  async function readRequestBody(req: any): Promise<string> {
12
- const chunks: Buffer[] = []
11
+ const chunks: Buffer[] = [];
13
12
  await new Promise<void>((resolve, reject) => {
14
- req.on('data', (chunk: any) => {
15
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
16
- })
17
- req.on('end', () => resolve())
18
- req.on('error', (err: unknown) => reject(err))
19
- })
20
- return Buffer.concat(chunks).toString('utf-8')
13
+ req.on("data", (chunk: any) => {
14
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
15
+ });
16
+ req.on("end", () => resolve());
17
+ req.on("error", (err: unknown) => reject(err));
18
+ });
19
+ return Buffer.concat(chunks).toString("utf-8");
21
20
  }
22
21
 
23
22
  /**
@@ -25,26 +24,26 @@ async function readRequestBody(req: any): Promise<string> {
25
24
  * 格式: openclaw-workclaw:{accountId}:{userId}
26
25
  */
27
26
  function buildSessionKey(accountId: string, userId: string): string {
28
- return `openclaw-workclaw:${accountId}:${userId}`
27
+ return `openclaw-workclaw:${accountId}:${userId}`;
29
28
  }
30
29
 
31
30
  /**
32
31
  * 解析会话 key
33
32
  */
34
33
  function parseSessionKey(sessionKey: string): {
35
- channel: string
36
- accountId: string
37
- userId: string
34
+ channel: string;
35
+ accountId: string;
36
+ userId: string;
38
37
  } | null {
39
- const parts = sessionKey.split(':')
40
- if (parts.length !== 3 || parts[0] !== 'openclaw-workclaw') {
41
- return null
38
+ const parts = sessionKey.split(":");
39
+ if (parts.length !== 3 || parts[0] !== "openclaw-workclaw") {
40
+ return null;
42
41
  }
43
42
  return {
44
43
  channel: parts[0],
45
44
  accountId: parts[1],
46
45
  userId: parts[2],
47
- }
46
+ };
48
47
  }
49
48
 
50
49
  /**
@@ -53,28 +52,27 @@ function parseSessionKey(sessionKey: string): {
53
52
  */
54
53
  async function resetSession(
55
54
  sessionKey: string,
56
- log?: { info?: (msg: string) => void, error?: (msg: string) => void },
57
- ): Promise<{ success: boolean, message: string }> {
55
+ log?: { info?: (msg: string) => void; error?: (msg: string) => void }
56
+ ): Promise<{ success: boolean; message: string }> {
58
57
  try {
59
- const runtime = getOpenclawWorkclawRuntime()
58
+ const runtime = getWorkclawRuntime();
60
59
 
61
60
  // 使用 system.enqueueSystemEvent 发送 /new 命令
62
- runtime.system.enqueueSystemEvent('/new', {
61
+ runtime.system.enqueueSystemEvent("/new", {
63
62
  sessionKey,
64
63
  contextKey: null,
65
- })
64
+ });
66
65
 
67
- log?.info?.(`[SessionAPI] Sent /new command to session: ${sessionKey}`)
66
+ log?.info?.(`[SessionAPI] Sent /new command to session: ${sessionKey}`);
68
67
 
69
68
  return {
70
69
  success: true,
71
70
  message: `Session reset command sent to ${sessionKey}`,
72
- }
73
- }
74
- catch (err) {
75
- const errorMsg = `Failed to reset session: ${String(err)}`
76
- log?.error?.(`[SessionAPI] ${errorMsg}`)
77
- return { success: false, message: errorMsg }
71
+ };
72
+ } catch (err) {
73
+ const errorMsg = `Failed to reset session: ${String(err)}`;
74
+ log?.error?.(`[SessionAPI] ${errorMsg}`);
75
+ return { success: false, message: errorMsg };
78
76
  }
79
77
  }
80
78
 
@@ -83,18 +81,17 @@ async function resetSession(
83
81
  */
84
82
  function getSessionStorePath(
85
83
  agentId?: string,
86
- log?: { info?: (msg: string) => void, error?: (msg: string) => void },
84
+ log?: { info?: (msg: string) => void; error?: (msg: string) => void }
87
85
  ): string | null {
88
86
  try {
89
- const runtime = getOpenclawWorkclawRuntime()
87
+ const runtime = getWorkclawRuntime();
90
88
  const storePath = runtime.channel.session.resolveStorePath(undefined, {
91
89
  agentId,
92
- })
93
- return storePath
94
- }
95
- catch (err) {
96
- log?.error?.(`[SessionAPI] Failed to resolve store path: ${String(err)}`)
97
- return null
90
+ });
91
+ return storePath;
92
+ } catch (err) {
93
+ log?.error?.(`[SessionAPI] Failed to resolve store path: ${String(err)}`);
94
+ return null;
98
95
  }
99
96
  }
100
97
 
@@ -103,47 +100,46 @@ function getSessionStorePath(
103
100
  */
104
101
  async function getSessionLastUpdated(
105
102
  sessionKey: string,
106
- log?: { info?: (msg: string) => void, error?: (msg: string) => void },
103
+ log?: { info?: (msg: string) => void; error?: (msg: string) => void }
107
104
  ): Promise<number | null> {
108
105
  try {
109
- const runtime = getOpenclawWorkclawRuntime()
110
- const storePath = runtime.channel.session.resolveStorePath()
106
+ const runtime = getWorkclawRuntime();
107
+ const storePath = runtime.channel.session.resolveStorePath();
111
108
  const updatedAt = runtime.channel.session.readSessionUpdatedAt({
112
109
  storePath,
113
110
  sessionKey,
114
- })
115
- return updatedAt ?? null
116
- }
117
- catch (err) {
118
- log?.error?.(`[SessionAPI] Failed to read session updated at: ${String(err)}`)
119
- return null
111
+ });
112
+ return updatedAt ?? null;
113
+ } catch (err) {
114
+ log?.error?.(`[SessionAPI] Failed to read session updated at: ${String(err)}`);
115
+ return null;
120
116
  }
121
117
  }
122
118
 
123
119
  export function createSessionApiHandler(api: OpenClawPluginApi) {
124
120
  return async (req: any, res: any) => {
125
- const method = String(req.method ?? 'GET').toUpperCase()
126
- const url = new URL(req.url ?? '', 'http://localhost')
121
+ const method = String(req.method ?? "GET").toUpperCase();
122
+ const url = new URL(req.url ?? "", "http://localhost");
127
123
  // 移除尾部斜杠并获取路径部分
128
- const fullPath = url.pathname.replace(/\/+$/, '')
124
+ const fullPath = url.pathname.replace(/\/+$/, "");
129
125
  // 提取子路径(去掉 /openclaw-workclaw/sessions 前缀)
130
- const subPath = fullPath.replace(/^\/openclaw-workclaw\/sessions/, '').replace(/^\//, '') || '/'
126
+ const subPath = fullPath.replace(/^\/openclaw-workclaw\/sessions/, "").replace(/^\//, "") || "/";
131
127
 
132
128
  const log = {
133
129
  info: (msg: string) => api.logger?.info?.(`[SessionAPI] ${msg}`),
134
130
  error: (msg: string) => api.logger?.error?.(`[SessionAPI] ${msg}`),
135
- }
131
+ };
136
132
 
137
- log?.info?.(`[SessionAPI] ${method} ${fullPath} (subPath: ${subPath})`)
133
+ log?.info?.(`[SessionAPI] ${method} ${fullPath} (subPath: ${subPath})`);
138
134
 
139
135
  // GET /sessions 或 /sessions/ - 列出会话信息
140
- if (method === 'GET' && (subPath === '/' || subPath === '')) {
141
- const sessionKey = url.searchParams.get('sessionKey')
136
+ if (method === "GET" && (subPath === "/" || subPath === "")) {
137
+ const sessionKey = url.searchParams.get("sessionKey");
142
138
 
143
139
  if (sessionKey) {
144
140
  // 获取特定会话信息
145
- const parsed = parseSessionKey(sessionKey)
146
- const updatedAt = await getSessionLastUpdated(sessionKey, log)
141
+ const parsed = parseSessionKey(sessionKey);
142
+ const updatedAt = await getSessionLastUpdated(sessionKey, log);
147
143
 
148
144
  sendJson(res, 200, {
149
145
  ok: true,
@@ -155,93 +151,88 @@ export function createSessionApiHandler(api: OpenClawPluginApi) {
155
151
  ? new Date(updatedAt).toISOString()
156
152
  : null,
157
153
  },
158
- })
159
- return
154
+ });
155
+ return;
160
156
  }
161
157
 
162
158
  // 返回 API 信息
163
159
  sendJson(res, 200, {
164
160
  ok: true,
165
- message: 'Session Management API',
161
+ message: "Session Management API",
166
162
  endpoints: {
167
- 'GET /sessions': '获取会话信息 (可选参数: sessionKey)',
168
- 'POST /sessions/reset': '重置/开启新会话 (参数: accountId + userId 或直接提供 sessionKey)',
169
- 'GET /sessions/store-path': '获取会话存储路径 (可选参数: agentId)',
163
+ "GET /sessions": "获取会话信息 (可选参数: sessionKey)",
164
+ "POST /sessions/reset": "重置/开启新会话 (参数: accountId + userId 或直接提供 sessionKey)",
165
+ "GET /sessions/store-path": "获取会话存储路径 (可选参数: agentId)",
170
166
  },
171
- })
172
- return
167
+ });
168
+ return;
173
169
  }
174
170
 
175
171
  // POST /sessions/reset - 重置会话
176
- if (method === 'POST' && subPath === 'reset') {
177
- const raw = await readRequestBody(req)
178
- let input: any = {}
172
+ if (method === "POST" && subPath === "reset") {
173
+ const raw = await readRequestBody(req);
174
+ let input: any = {};
179
175
  try {
180
- input = raw ? JSON.parse(raw) : {}
181
- }
182
- catch {
183
- sendJson(res, 400, { ok: false, error: 'Invalid JSON' })
184
- return
176
+ input = raw ? JSON.parse(raw) : {};
177
+ } catch {
178
+ sendJson(res, 400, { ok: false, error: "Invalid JSON" });
179
+ return;
185
180
  }
186
181
 
187
- const { accountId, userId, sessionKey: directSessionKey } = input
182
+ const { accountId, userId, sessionKey: directSessionKey } = input;
188
183
 
189
- let sessionKey: string
184
+ let sessionKey: string;
190
185
  if (directSessionKey) {
191
- sessionKey = directSessionKey
192
- }
193
- else if (accountId && userId) {
194
- sessionKey = buildSessionKey(accountId, userId)
195
- }
196
- else {
186
+ sessionKey = directSessionKey;
187
+ } else if (accountId && userId) {
188
+ sessionKey = buildSessionKey(accountId, userId);
189
+ } else {
197
190
  sendJson(res, 400, {
198
191
  ok: false,
199
- error: 'Missing required fields: either provide \'sessionKey\' or both \'accountId\' and \'userId\'',
200
- })
201
- return
192
+ error: "Missing required fields: either provide 'sessionKey' or both 'accountId' and 'userId'",
193
+ });
194
+ return;
202
195
  }
203
196
 
204
- const result = await resetSession(sessionKey, log)
197
+ const result = await resetSession(sessionKey, log);
205
198
 
206
199
  if (result.success) {
207
200
  sendJson(res, 200, {
208
201
  ok: true,
209
202
  message: result.message,
210
203
  sessionKey,
211
- })
212
- }
213
- else {
204
+ });
205
+ } else {
214
206
  sendJson(res, 500, {
215
207
  ok: false,
216
208
  error: result.message,
217
209
  sessionKey,
218
- })
210
+ });
219
211
  }
220
- return
212
+ return;
221
213
  }
222
214
 
223
215
  // GET /sessions/store-path - 获取会话存储路径
224
- if (method === 'GET' && subPath === 'store-path') {
225
- const agentId = url.searchParams.get('agentId') ?? undefined
226
- const storePath = getSessionStorePath(agentId, log)
216
+ if (method === "GET" && subPath === "store-path") {
217
+ const agentId = url.searchParams.get("agentId") ?? undefined;
218
+ const storePath = getSessionStorePath(agentId, log);
227
219
 
228
220
  if (storePath) {
229
221
  sendJson(res, 200, {
230
222
  ok: true,
231
223
  storePath,
232
- agentId: agentId ?? 'default',
233
- })
234
- }
235
- else {
224
+ agentId: agentId ?? "default",
225
+ });
226
+ } else {
236
227
  sendJson(res, 500, {
237
228
  ok: false,
238
- error: 'Failed to resolve session store path',
239
- })
229
+ error: "Failed to resolve session store path",
230
+ });
240
231
  }
241
- return
232
+ return;
242
233
  }
243
234
 
244
235
  // 404
245
- sendJson(res, 404, { ok: false, error: 'Not Found' })
246
- }
236
+ sendJson(res, 404, { ok: false, error: "Not Found" });
237
+ };
247
238
  }
@@ -1,74 +1,72 @@
1
- import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
2
- import { exec } from 'node:child_process'
3
- import { promisify } from 'node:util'
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { exec } from "node:child_process";
3
+ import { promisify } from "node:util";
4
4
 
5
- const execAsync = promisify(exec)
5
+ const execAsync = promisify(exec);
6
6
 
7
- function sendJson(res: any, statusCode: number, payload: unknown): void {
8
- res.statusCode = statusCode
9
- res.setHeader('Content-Type', 'application/json')
10
- res.end(JSON.stringify(payload))
7
+ function sendJson(res: any, statusCode: number, payload: unknown) {
8
+ res.statusCode = statusCode;
9
+ res.setHeader("Content-Type", "application/json");
10
+ res.end(JSON.stringify(payload));
11
11
  }
12
12
 
13
13
  export function createSkillsApiHandler(api: OpenClawPluginApi) {
14
14
  return async (req: any, res: any) => {
15
- const method = String(req.method ?? 'GET').toUpperCase()
15
+ const method = String(req.method ?? "GET").toUpperCase();
16
16
 
17
- if (method === 'GET') {
17
+ if (method === "GET") {
18
18
  try {
19
19
  // 调用 openclaw skills list 命令
20
- const { stdout, stderr } = await execAsync('openclaw skills list --json', {
20
+ const { stdout, stderr } = await execAsync("openclaw skills list --json", {
21
21
  timeout: 10000, // 10秒超时
22
- })
22
+ });
23
23
 
24
24
  if (stderr && !stdout) {
25
- api.logger.error(`Skills list error: ${stderr}`)
26
- sendJson(res, 500, { ok: false, error: 'Failed to list skills' })
27
- return
25
+ api.logger.error(`Skills list error: ${stderr}`);
26
+ sendJson(res, 500, { ok: false, error: "Failed to list skills" });
27
+ return;
28
28
  }
29
29
 
30
30
  // 尝试解析JSON输出
31
- let skillsData: any
31
+ let skillsData: any;
32
32
  try {
33
- skillsData = JSON.parse(stdout)
34
- }
35
- catch {
33
+ skillsData = JSON.parse(stdout);
34
+ } catch {
36
35
  // 如果不支持--json输出,尝试解析文本输出
37
- const lines = stdout.split('\n')
36
+ const lines = stdout.split('\n');
38
37
  const skills = lines
39
38
  .filter((line: string) => line.trim() && !line.includes('Skills'))
40
39
  .map((line: string) => {
41
- const parts = line.split(/\s{2,}/).map((p: string) => p.trim())
40
+ const parts = line.split(/\s{2,}/).map((p: string) => p.trim());
42
41
  if (parts.length >= 3) {
43
42
  return {
44
43
  status: parts[0]?.includes('✓') ? 'ready' : 'missing',
45
44
  name: parts[1],
46
45
  description: parts[2],
47
- source: parts[3] || 'openclaw-bundled',
48
- }
46
+ source: parts[3] || 'openclaw-bundled'
47
+ };
49
48
  }
50
- return null
49
+ return null;
51
50
  })
52
- .filter((s: any) => s !== null)
53
-
51
+ .filter((s: any) => s !== null);
52
+
54
53
  skillsData = {
55
54
  total: skills.length,
56
55
  ready: skills.filter((s: any) => s.status === 'ready').length,
57
56
  missing: skills.filter((s: any) => s.status === 'missing').length,
58
- skills,
59
- }
57
+ skills
58
+ };
60
59
  }
61
60
 
62
- sendJson(res, 200, { ok: true, ...skillsData })
63
- return
64
- }
65
- catch (error: any) {
66
- api.logger.error(`Skills API error: ${error.message}`)
67
- sendJson(res, 500, { ok: false, error: error.message })
68
- return
61
+ sendJson(res, 200, { ok: true, ...skillsData });
62
+ return;
63
+ } catch (error: any) {
64
+ api.logger.error(`Skills API error: ${error.message}`);
65
+ sendJson(res, 500, { ok: false, error: error.message });
66
+ return;
69
67
  }
70
68
  }
71
69
 
72
- sendJson(res, 405, { ok: false, error: 'Method Not Allowed' })
73
- }
70
+ sendJson(res, 405, { ok: false, error: "Method Not Allowed" });
71
+ };
74
72
  }