@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
@@ -1,343 +1,330 @@
1
- import { Agent, fetch as undiciFetch } from 'undici'
2
- import { getOpenclawWorkclawLogger } from '../runtime.js'
1
+ import { Agent, fetch as undiciFetch } from "undici";
2
+ import { getWorkclawLogger } from "../runtime.js";
3
3
 
4
- interface WorkClawTokenCache {
5
- token: string
6
- expiresAt: number
7
- pending?: Promise<string>
8
- }
4
+ type WorkClawTokenCache = {
5
+ token: string;
6
+ expiresAt: number;
7
+ };
9
8
 
10
- const tokenCache = new Map<string, WorkClawTokenCache>()
9
+ const tokenCache = new Map<string, WorkClawTokenCache>();
11
10
 
12
- export function clearOpenclawWorkclawTokenCache(cacheKey: string): void {
13
- tokenCache.delete(cacheKey)
11
+ export function clearWorkclawTokenCache(cacheKey: string): void {
12
+ tokenCache.delete(cacheKey);
14
13
  }
15
14
 
16
15
  export function normalizeBaseUrl(value: string | undefined): string {
17
- const raw = (value || 'https://open.workbrain.cn/open-apis').trim()
18
- return raw.endsWith('/') ? raw.slice(0, -1) : raw
16
+ const raw = (value || "https://open.workbrain.cn/open-apis").trim();
17
+ return raw.endsWith("/") ? raw.slice(0, -1) : raw;
19
18
  }
20
19
 
21
20
  function createWsEndpoint(baseUrl: string): string {
22
- const wsBase = baseUrl.replace(/^https?:\/\//i, match =>
23
- match.toLowerCase() === 'https://' ? 'wss://' : 'ws://')
24
- return `${wsBase}/v1/ws/connect`
21
+ const wsBase = baseUrl.replace(/^https?:\/\//i, (match) =>
22
+ match.toLowerCase() === "https://" ? "wss://" : "ws://",
23
+ );
24
+ return `${wsBase}/v1/ws/connect`;
25
25
  }
26
26
 
27
27
  export async function doFetchJson(
28
- url: string,
29
- init: RequestInit,
30
- allowInsecureTls?: boolean,
31
- requestTimeout?: number,
28
+ url: string,
29
+ init: RequestInit,
30
+ allowInsecureTls?: boolean,
31
+ requestTimeout?: number,
32
32
  ): Promise<any> {
33
- const dispatcher = allowInsecureTls
34
- ? new Agent({ connect: { rejectUnauthorized: false } })
35
- : undefined
36
- const fetcher = allowInsecureTls ? undiciFetch : globalThis.fetch
37
- const controller = new AbortController()
38
- const timeoutMs = typeof requestTimeout === 'number' && requestTimeout > 0 ? requestTimeout : 30000
39
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
40
-
41
- let response: any
42
- let text = ''
43
- try {
44
- response = await fetcher(url, {
45
- ...init,
46
- signal: controller.signal,
47
- ...(dispatcher ? { dispatcher } : {}),
48
- } as any)
49
- text = await response.text().catch(() => '')
50
- }
51
- finally {
52
- clearTimeout(timeoutId)
53
- }
54
-
55
- let data: any = {}
56
- if (text) {
33
+ const dispatcher = allowInsecureTls
34
+ ? new Agent({ connect: { rejectUnauthorized: false } })
35
+ : undefined;
36
+ const fetcher = allowInsecureTls ? undiciFetch : globalThis.fetch;
37
+ const controller = new AbortController();
38
+ const timeoutMs = typeof requestTimeout === "number" && requestTimeout > 0 ? requestTimeout : 30000;
39
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
40
+
41
+ let response: any;
42
+ let text = "";
57
43
  try {
58
- data = JSON.parse(text)
44
+ response = await fetcher(url, {
45
+ ...init,
46
+ signal: controller.signal,
47
+ ...(dispatcher ? { dispatcher } : {}),
48
+ } as any);
49
+ text = await response.text().catch(() => "");
50
+ } finally {
51
+ clearTimeout(timeoutId);
59
52
  }
60
- catch {
61
- data = {}
53
+
54
+ let data: any = {};
55
+ if (text) {
56
+ try {
57
+ data = JSON.parse(text);
58
+ } catch {
59
+ data = {};
60
+ }
62
61
  }
63
- }
64
62
 
65
- if (!response.ok) {
66
- const message = data?.message || text || `HTTP ${response.status}`
67
- throw new Error(message)
68
- }
69
- return data
63
+ if (!response.ok) {
64
+ const message = data?.message || text || `HTTP ${response.status}`;
65
+ throw new Error(message);
66
+ }
67
+ return data;
70
68
  }
71
69
 
72
- export interface OpenclawWorkclawConnectionConfig {
73
- baseUrl?: string
74
- websocketUrl?: string
75
- appKey?: string
76
- appSecret?: string
77
- localIp?: string
78
- allowInsecureTls?: boolean
79
- requestTimeout?: number
80
- }
70
+ export type WorkclawConnectionConfig = {
71
+ baseUrl?: string;
72
+ websocketUrl?: string;
73
+ appKey?: string;
74
+ appSecret?: string;
75
+ localIp?: string;
76
+ allowInsecureTls?: boolean;
77
+ requestTimeout?: number;
78
+ };
79
+
80
+ async function fetchAccessTokenFromServer(
81
+ requestUrl: string,
82
+ config: WorkclawConnectionConfig,
83
+ ): Promise<{ token: string; expiresAt: number }> {
84
+ getWorkclawLogger().info(
85
+ `Requesting access token url=${requestUrl} appKeyPrefix=${String(config.appKey)}... appSecretLen=${String(config.appSecret)}`,
86
+ );
87
+
88
+ const data = await doFetchJson(
89
+ requestUrl,
90
+ {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify({
94
+ app_key: config.appKey,
95
+ app_secret: config.appSecret,
96
+ }),
97
+ },
98
+ config.allowInsecureTls,
99
+ config.requestTimeout,
100
+ );
81
101
 
82
- export async function getOpenclawWorkclawAccessToken(
83
- cacheKey: string,
84
- config: OpenclawWorkclawConnectionConfig,
85
- ): Promise<string> {
86
- const startedAt = Date.now()
87
- const baseUrl = normalizeBaseUrl(config.baseUrl)
88
- const requestUrl = `${baseUrl}/authen/v1/access_token/internal`
89
- const timeoutMs
90
- = typeof config.requestTimeout === 'number' && config.requestTimeout > 0
91
- ? config.requestTimeout
92
- : 30000
93
-
94
- getOpenclawWorkclawLogger().info(
95
- `getOpenclawWorkclawAccessToken start cacheKey=${cacheKey} requestUrl=${requestUrl} baseUrl=${baseUrl} allowInsecureTls=${Boolean(config.allowInsecureTls)} timeoutMs=${timeoutMs}`,
96
- )
97
-
98
- try {
99
- const cached = tokenCache.get(cacheKey)
100
-
101
- if (cached && cached.token) {
102
- const now = Date.now()
103
- const refreshAt = cached.expiresAt - 5 * 60 * 1000
104
- const remainingMs = cached.expiresAt - now
105
- getOpenclawWorkclawLogger().info(
106
- `Token cache found cacheKey=${cacheKey} expiresAt=${new Date(cached.expiresAt).toISOString()} remainingMs=${remainingMs}`,
107
- )
108
-
109
- if (now < refreshAt) {
110
- getOpenclawWorkclawLogger().info(
111
- `Using cached token cacheKey=${cacheKey} elapsedMs=${now - startedAt}`,
112
- )
113
- return cached.token
114
- }
115
-
116
- getOpenclawWorkclawLogger().info(
117
- `Cached token near expiry/expired, refreshing cacheKey=${cacheKey} elapsedMs=${now - startedAt}`,
118
- )
119
- }
102
+ getWorkclawLogger().info(
103
+ `Access token response code=${String(data?.code ?? "")} errCode=${String(data?.errCode ?? "")} errMsg=${String(data?.errMsg ?? data?.message ?? "")}`,
104
+ );
120
105
 
121
- if (cached?.pending) {
122
- getOpenclawWorkclawLogger().info(
123
- `Using pending token request cacheKey=${cacheKey} elapsedMs=${Date.now() - startedAt}`,
124
- )
125
- return cached.pending
106
+ if (data?.code !== 200 || !data?.data?.accessToken) {
107
+ throw new Error(
108
+ String(data?.message || data?.errMsg || "Failed to acquire access token"),
109
+ );
126
110
  }
127
111
 
128
- if (!config.appKey || !config.appSecret) {
129
- getOpenclawWorkclawLogger().error(
130
- `Missing appKey/appSecret cacheKey=${cacheKey} appKey=${String(config.appKey || '') ? 'present' : 'missing'} appSecret=${String(config.appSecret || '') ? 'present' : 'missing'}`,
131
- )
132
- throw new Error('Missing appKey/appSecret')
133
- }
112
+ const token = String(data.data.accessToken);
113
+ const expiresIn = Number(data.data.expiresIn || 0);
114
+ const expiresAt = Date.now() + (expiresIn > 0 ? expiresIn * 1000 : 0);
115
+
116
+ getWorkclawLogger().info(
117
+ `Access token acquired tokenLen=${token.length} expiresInSec=${expiresIn} expiresAt=${new Date(expiresAt).toISOString()}`,
118
+ );
134
119
 
135
- const pending = (async (): Promise<string> => {
136
- getOpenclawWorkclawLogger().info(
137
- `Requesting access token url=${requestUrl} appKeyPrefix=${String(config.appKey).slice(0, 8)}... appSecretLen=${String(config.appSecret).length}`,
138
- )
120
+ return { token, expiresAt };
121
+ }
122
+
123
+ export async function getWorkclawAccessToken(
124
+ cacheKey: string,
125
+ config: WorkclawConnectionConfig,
126
+ ): Promise<string> {
127
+ const startedAt = Date.now();
128
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
129
+ const requestUrl = `${baseUrl}/authen/v1/access_token/internal`;
130
+ const timeoutMs =
131
+ typeof config.requestTimeout === "number" && config.requestTimeout > 0
132
+ ? config.requestTimeout
133
+ : 30000;
134
+
135
+ getWorkclawLogger().info(
136
+ `getWorkclawAccessToken start cacheKey=${cacheKey} requestUrl=${requestUrl} baseUrl=${baseUrl} allowInsecureTls=${Boolean(config.allowInsecureTls)} timeoutMs=${timeoutMs}`,
137
+ );
139
138
 
140
- const data = await doFetchJson(
139
+ try {
140
+ const cached = tokenCache.get(cacheKey);
141
+
142
+ if (cached && cached.token) {
143
+ const now = Date.now();
144
+ const refreshAt = cached.expiresAt - 5 * 60 * 1000;
145
+ const remainingMs = cached.expiresAt - now;
146
+ getWorkclawLogger().info(
147
+ `Token cache found cacheKey=${cacheKey} expiresAt=${new Date(cached.expiresAt).toISOString()} remainingMs=${remainingMs}`,
148
+ );
149
+
150
+ if (now < refreshAt) {
151
+ getWorkclawLogger().info(
152
+ `Using cached token cacheKey=${cacheKey} elapsedMs=${now - startedAt}`,
153
+ );
154
+ return cached.token;
155
+ }
156
+
157
+ getWorkclawLogger().info(
158
+ `Cached token near expiry/expired, refreshing cacheKey=${cacheKey} elapsedMs=${now - startedAt}`,
159
+ );
160
+ }
161
+
162
+ if (!config.appKey || !config.appSecret) {
163
+ getWorkclawLogger().error(
164
+ `Missing appKey/appSecret cacheKey=${cacheKey} appKey=${String(config.appKey || "") ? "present" : "missing"} appSecret=${String(config.appSecret || "") ? "present" : "missing"}`,
165
+ );
166
+ throw new Error("Missing appKey/appSecret");
167
+ }
168
+
169
+ const result = await fetchAccessTokenFromServer(requestUrl, config);
170
+ tokenCache.set(cacheKey, { token: result.token, expiresAt: result.expiresAt });
171
+ getWorkclawLogger().info(
172
+ `getWorkclawAccessToken success cacheKey=${cacheKey} elapsedMs=${Date.now() - startedAt}`,
173
+ );
174
+ return result.token;
175
+ } catch (error) {
176
+ getWorkclawLogger().error(
177
+ `getWorkclawAccessToken failed cacheKey=${cacheKey} requestUrl=${requestUrl} baseUrl=${baseUrl} elapsedMs=${Date.now() - startedAt} error=${String(error)}`,
178
+ );
179
+ throw error;
180
+ }
181
+ }
182
+
183
+ export async function openWorkclawConnection(
184
+ cacheKey: string,
185
+ config: WorkclawConnectionConfig,
186
+ ): Promise<{ endpoint: string; ticket: string }> {
187
+ const startedAt = Date.now();
188
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
189
+ const requestUrl = `${baseUrl}/open-apis/v1/connections/open`;
190
+ const timeoutMs =
191
+ typeof config.requestTimeout === "number" && config.requestTimeout > 0
192
+ ? config.requestTimeout
193
+ : 30000;
194
+
195
+ const appKeyRaw = String(config.appKey ?? "");
196
+ const appSecretRaw = String(config.appSecret ?? "");
197
+
198
+ getWorkclawLogger().info(
199
+ `openWorkclawConnection start cacheKey=${cacheKey} baseUrl=${baseUrl} requestUrl=${requestUrl} allowInsecureTls=${Boolean(config.allowInsecureTls)} timeoutMs=${timeoutMs} localIp=${String(config.localIp ?? "")} websocketUrl=${String(config.websocketUrl ?? "")} appKeyPrefix=${appKeyRaw ? `${appKeyRaw.slice(0, 8)}...` : "missing"} appSecretLen=${appSecretRaw.length}`,
200
+ );
201
+
202
+ const tokenStartedAt = Date.now();
203
+ const token = await getWorkclawAccessToken(cacheKey, config);
204
+ getWorkclawLogger().info(
205
+ `openWorkclawConnection got access token cacheKey=${cacheKey} tokenLen=${token.length} elapsedMs=${Date.now() - tokenStartedAt}`,
206
+ );
207
+
208
+ const payload = {
209
+ appKey: config.appKey,
210
+ appSecret: config.appSecret,
211
+ subscriptions: [
212
+ { type: "CALLBACK", topic: "AGENT_MESSAGE" },
213
+ { type: "EVENT", topic: "*" },
214
+ { type: "SYSTEM", topic: "*" },
215
+ ],
216
+ ...(config.localIp ? { localIp: config.localIp } : {}),
217
+ };
218
+
219
+ getWorkclawLogger().info(
220
+ `Opening connection url=${requestUrl} cacheKey=${cacheKey} subscriptions=${payload.subscriptions.length}`,
221
+ );
222
+
223
+ const data = await doFetchJson(
141
224
  requestUrl,
142
225
  {
143
- method: 'POST',
144
- headers: { 'Content-Type': 'application/json' },
145
- body: JSON.stringify({
146
- app_key: config.appKey,
147
- app_secret: config.appSecret,
148
- }),
226
+ method: "POST",
227
+ headers: {
228
+ "Content-Type": "application/json",
229
+ "Authorization": `Bearer ${token}`,
230
+ },
231
+ body: JSON.stringify(payload),
149
232
  },
150
233
  config.allowInsecureTls,
151
234
  config.requestTimeout,
152
- )
235
+ );
153
236
 
154
- getOpenclawWorkclawLogger().info(
155
- `Access token response code=${String(data?.code ?? '')} errCode=${String(data?.errCode ?? '')} errMsg=${String(data?.errMsg ?? data?.message ?? '')}`,
156
- )
237
+ getWorkclawLogger().info(
238
+ `Open connection response code=${String(data?.code ?? "")} errCode=${String(data?.errCode ?? "")} errMsg=${String(data?.errMsg ?? data?.message ?? "")} hasTicket=${Boolean(data?.data?.ticket)} hasEndpoint=${Boolean(data?.data?.endpoint)}`,
239
+ );
157
240
 
158
- if (data?.code !== 200 || !data?.data?.accessToken) {
159
- throw new Error(
160
- String(data?.message || data?.errMsg || 'Failed to acquire access token'),
161
- )
162
- }
241
+ if (data?.code !== 200 || !data?.data?.ticket) {
242
+ throw new Error(String(data?.message || data?.errMsg || "Failed to open connection"));
243
+ }
163
244
 
164
- const token = String(data.data.accessToken)
165
- const expiresIn = Number(data.data.expiresIn || 0)
166
- const expiresAt = Date.now() + (expiresIn > 0 ? expiresIn * 1000 : 0)
245
+ const ticket = String(data.data.ticket);
167
246
 
168
- getOpenclawWorkclawLogger().info(
169
- `Access token acquired tokenLen=${token.length} expiresInSec=${expiresIn} expiresAt=${new Date(expiresAt).toISOString()}`,
170
- )
171
-
172
- tokenCache.set(cacheKey, { token, expiresAt })
173
- return token
174
- })()
175
-
176
- tokenCache.set(cacheKey, {
177
- token: cached?.token || '',
178
- expiresAt: cached?.expiresAt || 0,
179
- pending,
180
- })
181
-
182
- const token = await pending
183
- getOpenclawWorkclawLogger().info(
184
- `getOpenclawWorkclawAccessToken success cacheKey=${cacheKey} elapsedMs=${Date.now() - startedAt}`,
185
- )
186
- return token
187
- }
188
- catch (error) {
189
- getOpenclawWorkclawLogger().error(
190
- `getOpenclawWorkclawAccessToken failed cacheKey=${cacheKey} requestUrl=${requestUrl} baseUrl=${baseUrl} elapsedMs=${Date.now() - startedAt} error=${String(error)}`,
191
- )
192
- throw error
193
- }
194
- }
247
+ let endpointSource: "config" | "api" | "derived" = "derived";
195
248
 
196
- export async function openOpenclawWorkclawConnection(
197
- cacheKey: string,
198
- config: OpenclawWorkclawConnectionConfig,
199
- ): Promise<{ endpoint: string, ticket: string }> {
200
- const startedAt = Date.now()
201
- const baseUrl = normalizeBaseUrl(config.baseUrl)
202
- const requestUrl = `${baseUrl}/open-apis/v1/connections/open`
203
- const timeoutMs
204
- = typeof config.requestTimeout === 'number' && config.requestTimeout > 0
205
- ? config.requestTimeout
206
- : 30000
207
-
208
- const appKeyRaw = String(config.appKey ?? '')
209
- const appSecretRaw = String(config.appSecret ?? '')
210
-
211
- getOpenclawWorkclawLogger().info(
212
- `openOpenclawWorkclawConnection start cacheKey=${cacheKey} baseUrl=${baseUrl} requestUrl=${requestUrl} allowInsecureTls=${Boolean(config.allowInsecureTls)} timeoutMs=${timeoutMs} localIp=${String(config.localIp ?? '')} websocketUrl=${String(config.websocketUrl ?? '')} appKeyPrefix=${appKeyRaw ? `${appKeyRaw.slice(0, 8)}...` : 'missing'} appSecretLen=${appSecretRaw.length}`,
213
- )
214
-
215
- const tokenStartedAt = Date.now()
216
- const token = await getOpenclawWorkclawAccessToken(cacheKey, config)
217
- getOpenclawWorkclawLogger().info(
218
- `openOpenclawWorkclawConnection got access token cacheKey=${cacheKey} tokenLen=${token.length} elapsedMs=${Date.now() - tokenStartedAt}`,
219
- )
220
-
221
- const payload = {
222
- appKey: config.appKey,
223
- appSecret: config.appSecret,
224
- subscriptions: [
225
- { type: 'CALLBACK', topic: 'AGENT_MESSAGE' },
226
- { type: 'EVENT', topic: '*' },
227
- { type: 'SYSTEM', topic: '*' },
228
- ],
229
- ...(config.localIp ? { localIp: config.localIp } : {}),
230
- }
231
-
232
- getOpenclawWorkclawLogger().info(
233
- `Opening connection url=${requestUrl} cacheKey=${cacheKey} subscriptions=${payload.subscriptions.length}`,
234
- )
235
-
236
- const data = await doFetchJson(
237
- requestUrl,
238
- {
239
- method: 'POST',
240
- headers: {
241
- 'Content-Type': 'application/json',
242
- 'Authorization': `Bearer ${token}`,
243
- },
244
- body: JSON.stringify(payload),
245
- },
246
- config.allowInsecureTls,
247
- config.requestTimeout,
248
- )
249
-
250
- getOpenclawWorkclawLogger().info(
251
- `Open connection response code=${String(data?.code ?? '')} errCode=${String(data?.errCode ?? '')} errMsg=${String(data?.errMsg ?? data?.message ?? '')} hasTicket=${Boolean(data?.data?.ticket)} hasEndpoint=${Boolean(data?.data?.endpoint)}`,
252
- )
253
-
254
- if (data?.code !== 200 || !data?.data?.ticket) {
255
- throw new Error(String(data?.message || data?.errMsg || 'Failed to open connection'))
256
- }
257
-
258
- const ticket = String(data.data.ticket)
259
-
260
- let endpointSource: 'config' | 'api' | 'derived' = 'derived'
261
-
262
- // 优先使用配置中的 websocketUrl,其次使用 API 返回的 endpoint,最后使用默认构建的 endpoint
263
- const endpoint = config.websocketUrl
264
- ? ((endpointSource = 'config'), String(config.websocketUrl))
265
- : data?.data?.endpoint
266
- ? ((endpointSource = 'api'), String(data.data.endpoint))
267
- : ((endpointSource = 'derived'), createWsEndpoint(baseUrl))
268
-
269
- getOpenclawWorkclawLogger().info(
270
- `openOpenclawWorkclawConnection success cacheKey=${cacheKey} endpoint=${endpoint} endpointSource=${endpointSource} ticketLen=${ticket.length} elapsedMs=${Date.now() - startedAt}`,
271
- )
272
-
273
- return { endpoint, ticket }
249
+ // 优先使用配置中的 websocketUrl,其次使用 API 返回的 endpoint,最后使用默认构建的 endpoint
250
+ const endpoint = config.websocketUrl
251
+ ? ((endpointSource = "config"), String(config.websocketUrl))
252
+ : data?.data?.endpoint
253
+ ? ((endpointSource = "api"), String(data.data.endpoint))
254
+ : ((endpointSource = "derived"), createWsEndpoint(baseUrl));
255
+
256
+ getWorkclawLogger().info(
257
+ `openWorkclawConnection success cacheKey=${cacheKey} endpoint=${endpoint} endpointSource=${endpointSource} ticketLen=${ticket.length} elapsedMs=${Date.now() - startedAt}`,
258
+ );
259
+
260
+ return { endpoint, ticket };
274
261
  }
275
262
 
276
263
  export interface AgentInstance {
277
- id: string
278
- createTime: string
279
- updateTime: string
280
- futureId: string
281
- phone: string
282
- nickName: string
283
- imageId: string
284
- level: number
285
- identity: string
286
- type: string
287
- name: string
288
- gender: string
289
- age: number | null
290
- tcId: string | null
291
- city: string | null
292
- loginType: string | null
293
- status: string
294
- publicScope: string
295
- ip: string | null
296
- phoneName: string | null
297
- tip: string | null
298
- email: string | null
299
- featureId: string | null
300
- job: string | null
301
- newUser: boolean
302
- imageKnBase: string | null
303
- recommend: string | null
304
- comment: string | null
305
- treeKnbase: string | null
306
- preferredLanguage: string
307
- timezone: string
308
- introduction: string
309
- backgroundId: string
310
- voiceId: string
311
- characterSettings: string | null
312
- personFeatures: string | null
313
- learningFeatures: string | null
314
- workFeatures: string | null
315
- socializeFeatures: string | null
316
- useKnowledge: boolean
317
- roomOnlyKnowledge: boolean
318
- roomWebSearch: boolean
319
- abilityId: string | null
320
- baiduCensoringStrategy: string | null
321
- knowledgeCheckStrategy: string | null
322
- interestTags: string | null
323
- temporaryId: string | null
324
- treeBatchNo: string | null
325
- allowJoinSpace: boolean
326
- modelId: string
327
- orgId: string | null
328
- qrCodeUrl: string | null
329
- locationPoint: {
330
- x: number
331
- y: number
332
- } | null
333
- hot: string
334
- score: number
335
- professionalUser: string | null
336
- autoCreateFace: boolean
337
- liveId: string | null
338
- liveStartTime: string | null
339
- isIpAgent: number
340
- isAdopted: number
264
+ id: string;
265
+ createTime: string;
266
+ updateTime: string;
267
+ futureId: string;
268
+ phone: string;
269
+ nickName: string;
270
+ imageId: string;
271
+ level: number;
272
+ identity: string;
273
+ type: string;
274
+ name: string;
275
+ gender: string;
276
+ age: number | null;
277
+ tcId: string | null;
278
+ city: string | null;
279
+ loginType: string | null;
280
+ status: string;
281
+ publicScope: string;
282
+ ip: string | null;
283
+ phoneName: string | null;
284
+ tip: string | null;
285
+ email: string | null;
286
+ featureId: string | null;
287
+ job: string | null;
288
+ newUser: boolean;
289
+ imageKnBase: string | null;
290
+ recommend: string | null;
291
+ comment: string | null;
292
+ treeKnbase: string | null;
293
+ preferredLanguage: string;
294
+ timezone: string;
295
+ introduction: string;
296
+ backgroundId: string;
297
+ voiceId: string;
298
+ characterSettings: string | null;
299
+ personFeatures: string | null;
300
+ learningFeatures: string | null;
301
+ workFeatures: string | null;
302
+ socializeFeatures: string | null;
303
+ useKnowledge: boolean;
304
+ roomOnlyKnowledge: boolean;
305
+ roomWebSearch: boolean;
306
+ abilityId: string | null;
307
+ baiduCensoringStrategy: string | null;
308
+ knowledgeCheckStrategy: string | null;
309
+ interestTags: string | null;
310
+ temporaryId: string | null;
311
+ treeBatchNo: string | null;
312
+ allowJoinSpace: boolean;
313
+ modelId: string;
314
+ orgId: string | null;
315
+ qrCodeUrl: string | null;
316
+ locationPoint: {
317
+ x: number;
318
+ y: number;
319
+ } | null;
320
+ hot: string;
321
+ score: number;
322
+ professionalUser: string | null;
323
+ autoCreateFace: boolean;
324
+ liveId: string | null;
325
+ liveStartTime: string | null;
326
+ isIpAgent: number;
327
+ isAdopted: number;
341
328
  }
342
329
 
343
330
  /**
@@ -345,312 +332,312 @@ export interface AgentInstance {
345
332
  * POST /open-apis/instance/list
346
333
  */
347
334
  export async function getAgentInstances(
348
- cacheKey: string,
349
- config: OpenclawWorkclawConnectionConfig,
335
+ cacheKey: string,
336
+ config: WorkclawConnectionConfig
350
337
  ): Promise<AgentInstance[]> {
351
- const baseUrl = normalizeBaseUrl(config.baseUrl)
352
- const token = await getOpenclawWorkclawAccessToken(cacheKey, config)
353
-
354
- const payload = {
355
- appKey: config.appKey,
356
- }
357
-
358
- getOpenclawWorkclawLogger().info(`Getting agent instances from ${baseUrl}/open-apis/instance/list`)
359
- getOpenclawWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2))
360
-
361
- const data = await doFetchJson(
362
- `${baseUrl}/open-apis/instance/list`,
363
- {
364
- method: 'POST',
365
- headers: {
366
- 'Content-Type': 'application/json',
367
- 'Authorization': `Bearer ${token}`,
368
- },
369
- body: JSON.stringify(payload),
370
- },
371
- config.allowInsecureTls,
372
- config.requestTimeout,
373
- )
374
- getOpenclawWorkclawLogger().info(`Agent instances response:`, JSON.stringify(data, null, 2))
375
-
376
- if (data?.code !== 200 || !Array.isArray(data?.data)) {
377
- throw new Error(data?.message || 'Failed to get agent instances')
378
- }
379
-
380
- return data.data as AgentInstance[]
381
- }
338
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
339
+ const token = await getWorkclawAccessToken(cacheKey, config);
382
340
 
383
- export interface WorkClawMessageParams {
384
- cacheKey: string
385
- config: OpenclawWorkclawConnectionConfig
386
- agentId: string | number
387
- receiveId: string | number
388
- msgType: string
389
- content: string
390
- openConversationId?: string
391
- replayMsgId?: string
392
- endpoint?: string
393
- last?: boolean
341
+ const payload = {
342
+ appKey: config.appKey,
343
+ };
344
+
345
+ getWorkclawLogger().info(`Getting agent instances from ${baseUrl}/open-apis/instance/list`);
346
+ getWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2));
347
+
348
+ const data = await doFetchJson(
349
+ `${baseUrl}/open-apis/instance/list`,
350
+ {
351
+ method: "POST",
352
+ headers: {
353
+ "Content-Type": "application/json",
354
+ "Authorization": `Bearer ${token}`,
355
+ },
356
+ body: JSON.stringify(payload),
357
+ },
358
+ config.allowInsecureTls,
359
+ config.requestTimeout,
360
+ );
361
+ getWorkclawLogger().info(`Agent instances response:`, JSON.stringify(data, null, 2));
362
+
363
+ if (data?.code !== 200 || !Array.isArray(data?.data)) {
364
+ throw new Error(data?.message || "Failed to get agent instances");
365
+ }
366
+
367
+ return data.data as AgentInstance[];
394
368
  }
395
369
 
370
+ export type WorkClawMessageParams = {
371
+ cacheKey: string;
372
+ config: WorkclawConnectionConfig;
373
+ agentId: string | number;
374
+ receiveId: string | number;
375
+ msgType: string;
376
+ content: string;
377
+ openConversationId?: string;
378
+ replayMsgId?: string;
379
+ endpoint?: string;
380
+ last?: boolean;
381
+ };
382
+
396
383
  /**
397
384
  * 发送主动消息(非回复)
398
385
  * POST /im/v1/messages
399
386
  */
400
- export async function sendOpenclawWorkclawMessage(params: WorkClawMessageParams): Promise<string> {
401
- const baseUrl = normalizeBaseUrl(params.config.baseUrl)
402
- const token = await getOpenclawWorkclawAccessToken(params.cacheKey, params.config)
403
-
404
- // 主动消息参数
405
- const payload: any = {
406
- agentId: params.agentId,
407
- receiveId: params.receiveId,
408
- msgType: params.msgType,
409
- content: params.content,
410
- }
411
-
412
- // 添加 last 字段(仅当为 true 时)
413
- if (params.last === true) {
414
- payload.is_last = true
415
- }
416
-
417
- // openConversationId 非必填,API 字段名为 conversationId
418
- if (params.openConversationId) {
419
- payload.conversationId = params.openConversationId
420
- }
421
-
422
- const endpoint = params.endpoint || '/open-apis/im/v1/messages'
423
- getOpenclawWorkclawLogger().info(`Sending proactive message to ${baseUrl}${endpoint}`)
424
- getOpenclawWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2))
425
-
426
- const data = await doFetchJson(
427
- `${baseUrl}${endpoint}`,
428
- {
429
- method: 'POST',
430
- headers: {
431
- 'Content-Type': 'application/json',
432
- 'Authorization': `Bearer ${token}`,
433
- },
434
- body: JSON.stringify(payload),
435
- },
436
- params.config.allowInsecureTls,
437
- params.config.requestTimeout,
438
- )
439
- getOpenclawWorkclawLogger().info(`Send message response:`, JSON.stringify(data, null, 2))
440
-
441
- if (data?.code !== 200 || !data?.data?.msgId) {
442
- throw new Error(data?.message || 'Failed to send message')
443
- }
444
- return String(data.data.msgId)
387
+ export async function sendWorkclawMessage(params: WorkClawMessageParams): Promise<string> {
388
+ const baseUrl = normalizeBaseUrl(params.config.baseUrl);
389
+ const token = await getWorkclawAccessToken(params.cacheKey, params.config);
390
+
391
+ // 主动消息参数
392
+ const payload: any = {
393
+ agentId: params.agentId,
394
+ receiveId: params.receiveId,
395
+ msgType: params.msgType,
396
+ content: params.content,
397
+ };
398
+
399
+ // 添加 last 字段(仅当为 true 时)
400
+ if (params.last === true) {
401
+ payload.is_last = true;
402
+ }
403
+
404
+ // openConversationId 非必填,API 字段名为 conversationId
405
+ if (params.openConversationId) {
406
+ payload.conversationId = params.openConversationId;
407
+ }
408
+
409
+ const endpoint = params.endpoint || "/open-apis/im/v1/messages";
410
+ getWorkclawLogger().info(`Sending proactive message to ${baseUrl}${endpoint}`);
411
+ getWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2));
412
+
413
+ const data = await doFetchJson(
414
+ `${baseUrl}${endpoint}`,
415
+ {
416
+ method: "POST",
417
+ headers: {
418
+ "Content-Type": "application/json",
419
+ "Authorization": `Bearer ${token}`,
420
+ },
421
+ body: JSON.stringify(payload),
422
+ },
423
+ params.config.allowInsecureTls,
424
+ params.config.requestTimeout,
425
+ );
426
+ getWorkclawLogger().info(`Send message response:`, JSON.stringify(data, null, 2));
427
+
428
+ if (data?.code !== 200 || !data?.data?.msgId) {
429
+ throw new Error(data?.message || "Failed to send message");
430
+ }
431
+ return String(data.data.msgId);
445
432
  }
446
433
 
447
434
  /**
448
435
  * 发送回复消息
449
436
  * POST /im/v1/messages/{messageId}/reply
450
437
  */
451
- export async function sendOpenclawWorkclawReplyMessage(params: WorkClawMessageParams & { messageId: string }): Promise<string> {
452
- const baseUrl = normalizeBaseUrl(params.config.baseUrl)
453
- const token = await getOpenclawWorkclawAccessToken(params.cacheKey, params.config)
454
-
455
- // 回复消息参数
456
- // 注意:虽然 API 文档说不需要 openConversationId,但为了保险起见还是会传递
457
- const payload: any = {
458
- agentId: params.agentId,
459
- receiveId: params.receiveId,
460
- msgType: params.msgType,
461
- content: params.content,
462
- }
463
-
464
- getOpenclawWorkclawLogger().info(`Reply payload is last : ${params.last} `)
465
-
466
- // 添加 last 字段(仅当为 true 时)
467
- if (params.last === true) {
468
- payload.is_last = true
469
- }
470
-
471
- // 如果提供了 openConversationId,也添加到 payload 中(以防 API 需要)
472
- if (params.openConversationId) {
473
- payload.conversationId = params.openConversationId
474
- getOpenclawWorkclawLogger().info(`Reply payload includes conversationId: ${params.openConversationId}`)
475
- }
476
- else {
477
- getOpenclawWorkclawLogger().info(`Reply payload does NOT include conversationId (openConversationId is empty)`)
478
- }
479
-
480
- const endpoint = params.endpoint || `/open-apis/im/v1/messages/${params.messageId}/reply`
481
- getOpenclawWorkclawLogger().info(`Sending reply message to ${baseUrl}${endpoint}`)
482
- getOpenclawWorkclawLogger().info(`Reply Payload:`, JSON.stringify(payload, null, 2))
483
-
484
- const data = await doFetchJson(
485
- `${baseUrl}${endpoint}`,
486
- {
487
- method: 'POST',
488
- headers: {
489
- 'Content-Type': 'application/json',
490
- 'Authorization': `Bearer ${token}`,
491
- },
492
- body: JSON.stringify(payload),
493
- },
494
- params.config.allowInsecureTls,
495
- params.config.requestTimeout,
496
- )
497
- getOpenclawWorkclawLogger().info(`Send reply response:`, JSON.stringify(data, null, 2))
498
-
499
- if (data?.code !== 200 || !data?.data?.msgId) {
500
- throw new Error(data?.message || 'Failed to send reply')
501
- }
502
- return String(data.data.msgId)
438
+ export async function sendWorkclawReplyMessage(params: WorkClawMessageParams & { messageId: string }): Promise<string> {
439
+ const baseUrl = normalizeBaseUrl(params.config.baseUrl);
440
+ const token = await getWorkclawAccessToken(params.cacheKey, params.config);
441
+
442
+ // 回复消息参数
443
+ // 注意:虽然 API 文档说不需要 openConversationId,但为了保险起见还是会传递
444
+ const payload: any = {
445
+ agentId: params.agentId,
446
+ receiveId: params.receiveId,
447
+ msgType: params.msgType,
448
+ content: params.content,
449
+ };
450
+
451
+ getWorkclawLogger().info(`Reply payload is last : ${params.last} `);
452
+
453
+ // 添加 last 字段(仅当为 true 时)
454
+ if (params.last === true) {
455
+ payload.is_last = true;
456
+ }
457
+
458
+ // 如果提供了 openConversationId,也添加到 payload 中(以防 API 需要)
459
+ if (params.openConversationId) {
460
+ payload.conversationId = params.openConversationId;
461
+ getWorkclawLogger().info(`Reply payload includes conversationId: ${params.openConversationId}`);
462
+ } else {
463
+ getWorkclawLogger().info(`Reply payload does NOT include conversationId (openConversationId is empty)`);
464
+ }
465
+
466
+ const endpoint = params.endpoint || `/open-apis/im/v1/messages/${params.messageId}/reply`;
467
+ getWorkclawLogger().info(`Sending reply message to ${baseUrl}${endpoint}`);
468
+ getWorkclawLogger().info(`Reply Payload:`, JSON.stringify(payload, null, 2));
469
+
470
+ const data = await doFetchJson(
471
+ `${baseUrl}${endpoint}`,
472
+ {
473
+ method: "POST",
474
+ headers: {
475
+ "Content-Type": "application/json",
476
+ "Authorization": `Bearer ${token}`,
477
+ },
478
+ body: JSON.stringify(payload),
479
+ },
480
+ params.config.allowInsecureTls,
481
+ params.config.requestTimeout,
482
+ );
483
+ getWorkclawLogger().info(`Send reply response:`, JSON.stringify(data, null, 2));
484
+
485
+ if (data?.code !== 200 || !data?.data?.msgId) {
486
+ throw new Error(data?.message || "Failed to send reply");
487
+ }
488
+ return String(data.data.msgId);
503
489
  }
504
490
 
505
- export function resolveOpenclawWorkclawMessage(
506
- text: string,
507
- mediaUrl?: string,
508
- ): { msgType: string, content: string } {
509
- if (!mediaUrl || text.trim()) {
510
- return { msgType: 'text', content: text }
511
- }
512
- const url = mediaUrl.trim()
513
- const lower = url.toLowerCase()
514
- const isAudio
515
- = lower.endsWith('.mp3')
516
- || lower.endsWith('.wav')
517
- || lower.endsWith('.aac')
518
- || lower.endsWith('.m4a')
519
- || lower.endsWith('.ogg')
520
- const payload = JSON.stringify({ url })
521
- return { msgType: isAudio ? 'audio' : 'image', content: payload }
491
+ export function resolveWorkclawMessage(
492
+ text: string,
493
+ mediaUrl?: string,
494
+ ): { msgType: string; content: string } {
495
+ if (!mediaUrl || text.trim()) {
496
+ return { msgType: "text", content: text };
497
+ }
498
+ const url = mediaUrl.trim();
499
+ const lower = url.toLowerCase();
500
+ const isAudio =
501
+ lower.endsWith(".mp3") ||
502
+ lower.endsWith(".wav") ||
503
+ lower.endsWith(".aac") ||
504
+ lower.endsWith(".m4a") ||
505
+ lower.endsWith(".ogg");
506
+ const payload = JSON.stringify({ url });
507
+ return { msgType: isAudio ? "audio" : "image", content: payload };
522
508
  }
523
509
 
510
+
524
511
  export interface CronJobPayload {
525
- messageId: number | string
526
- planId?: number | string
527
- clawJobId: string
528
- name: string
529
- kind: string // 'at' | 'every' | 'cron'
530
- expr: string
531
- message?: string
512
+ messageId: number | string;
513
+ planId?: number | string;
514
+ clawJobId: string;
515
+ name: string;
516
+ kind: string; // 'at' | 'every' | 'cron'
517
+ expr: string;
518
+ message?: string;
532
519
  }
533
520
 
534
521
  /**
535
522
  * 同步 openclaw 创建的定时任务至后端
536
523
  * POST /cron/job/add
537
524
  */
538
- export async function syncOpenclawWorkclawCronJobToBackend(
539
- cacheKey: string,
540
- config: OpenclawWorkclawConnectionConfig,
541
- payload: CronJobPayload,
525
+ export async function syncWorkclawCronJobToBackend(
526
+ cacheKey: string,
527
+ config: WorkclawConnectionConfig,
528
+ payload: CronJobPayload
542
529
  ): Promise<any> {
543
- const baseUrl = normalizeBaseUrl(config.baseUrl)
544
- const token = await getOpenclawWorkclawAccessToken(cacheKey, config)
545
-
546
- // 尝试添加 /open-apis 前缀,以匹配其他 API 的模式 (如 getAgentInstances)
547
- const requestUrl = `${baseUrl}/open-apis/cron/job/add`
548
- getOpenclawWorkclawLogger().info(`Syncing cron job to ${requestUrl}`)
549
- getOpenclawWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2))
550
-
551
- const data = await doFetchJson(
552
- requestUrl,
553
- {
554
- method: 'POST',
555
- headers: {
556
- 'Content-Type': 'application/json',
557
- 'Authorization': `Bearer ${token}`,
558
- },
559
- body: JSON.stringify(payload),
560
- },
561
- config.allowInsecureTls,
562
- config.requestTimeout,
563
- )
564
- getOpenclawWorkclawLogger().info(`Sync cron job response:`, JSON.stringify(data, null, 2))
565
-
566
- if (data?.code !== 200 && data?.code !== 0) {
567
- throw new Error(data?.message || data?.msg || 'Failed to sync cron job')
568
- }
569
- return data
530
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
531
+ const token = await getWorkclawAccessToken(cacheKey, config);
532
+
533
+ // 尝试添加 /open-apis 前缀,以匹配其他 API 的模式 (如 getAgentInstances)
534
+ const requestUrl = `${baseUrl}/open-apis/cron/job/add`;
535
+ getWorkclawLogger().info(`Syncing cron job to ${requestUrl}`);
536
+ getWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2));
537
+
538
+ const data = await doFetchJson(
539
+ requestUrl,
540
+ {
541
+ method: "POST",
542
+ headers: {
543
+ "Content-Type": "application/json",
544
+ "Authorization": `Bearer ${token}`,
545
+ },
546
+ body: JSON.stringify(payload),
547
+ },
548
+ config.allowInsecureTls,
549
+ config.requestTimeout,
550
+ );
551
+ getWorkclawLogger().info(`Sync cron job response:`, JSON.stringify(data, null, 2));
552
+
553
+ if (data?.code !== 200 && data?.code !== 0) {
554
+ throw new Error(data?.message || data?.msg || "Failed to sync cron job");
555
+ }
556
+ return data;
570
557
  }
571
558
 
572
559
  /**
573
560
  * 获取AppKey所对应的模型配置
574
561
  * POST /open-apis/instance/apiKey/{appKey}
575
562
  */
576
- export async function getOpenclawWorkclawModelConfigByAppKey(
577
- cacheKey: string,
578
- config: OpenclawWorkclawConnectionConfig,
563
+ export async function getWorkclawModelConfigByAppKey(
564
+ cacheKey: string,
565
+ config: WorkclawConnectionConfig
579
566
  ): Promise<{
580
- baseUrl: string
581
- apiKey: string
567
+ baseUrl: string;
568
+ apiKey: string;
582
569
  }> {
583
- const baseUrl = normalizeBaseUrl(config.baseUrl)
584
- const token = await getOpenclawWorkclawAccessToken(cacheKey, config)
585
- const appKey = config.appKey
586
-
587
- if (!appKey) {
588
- throw new Error('Missing appKey for getModelKeyByAppKey')
589
- }
590
-
591
- const requestUrl = `${baseUrl}/open-apis/instance/apiKey/${appKey}`
592
- getOpenclawWorkclawLogger().info(`Getting model key from ${requestUrl}`)
593
-
594
- const data = await doFetchJson(
595
- requestUrl,
596
- {
597
- method: 'GET',
598
- headers: {
599
- 'Content-Type': 'x-www-form-urlencoded',
600
- 'Authorization': `Bearer ${token}`,
601
- },
602
- },
603
- config.allowInsecureTls,
604
- config.requestTimeout,
605
- )
606
-
607
- getOpenclawWorkclawLogger().info(`Model key response:`, JSON.stringify(data, null, 2))
608
-
609
- if (data?.code !== 200 || !data?.data) {
610
- throw new Error(data?.message || data?.msg || 'Failed to get model key')
611
- }
612
-
613
- return data.data
570
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
571
+ const token = await getWorkclawAccessToken(cacheKey, config);
572
+ const appKey = config.appKey;
573
+
574
+ if (!appKey) {
575
+ throw new Error("Missing appKey for getModelKeyByAppKey");
576
+ }
577
+
578
+ const requestUrl = `${baseUrl}/open-apis/instance/apiKey/${appKey}`;
579
+ getWorkclawLogger().info(`Getting model key from ${requestUrl}`);
580
+
581
+ const data = await doFetchJson(
582
+ requestUrl,
583
+ {
584
+ method: "GET",
585
+ headers: {
586
+ "Content-Type": "x-www-form-urlencoded",
587
+ "Authorization": `Bearer ${token}`,
588
+ }
589
+ },
590
+ config.allowInsecureTls,
591
+ config.requestTimeout,
592
+ );
593
+
594
+ getWorkclawLogger().info(`Model key response:`, JSON.stringify(data, null, 2));
595
+
596
+ if (data?.code !== 200 || !data?.data) {
597
+ throw new Error(data?.message || data?.msg || "Failed to get model key");
598
+ }
599
+
600
+ return data.data
614
601
  }
615
602
 
616
603
  export interface CronJobMessagePayload {
617
- clawJobId: string
618
- message: string
604
+ clawJobId: string;
605
+ message: string;
619
606
  }
620
607
 
621
608
  /**
622
609
  * 此时定时任务触发消息至后端
623
610
  * POST /open-apis/cron/job/message
624
611
  */
625
- export async function sendOpenclawWorkclawCronJobMessageToBackend(
626
- cacheKey: string,
627
- config: OpenclawWorkclawConnectionConfig,
628
- payload: CronJobMessagePayload,
612
+ export async function sendWorkclawCronJobMessageToBackend(
613
+ cacheKey: string,
614
+ config: WorkclawConnectionConfig,
615
+ payload: CronJobMessagePayload
629
616
  ): Promise<any> {
630
- const baseUrl = normalizeBaseUrl(config.baseUrl)
631
- const token = await getOpenclawWorkclawAccessToken(cacheKey, config)
632
-
633
- const requestUrl = `${baseUrl}/open-apis/cron/job/message`
634
- getOpenclawWorkclawLogger().info(`Sending cron job message to ${requestUrl}`)
635
- getOpenclawWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2))
636
-
637
- const data = await doFetchJson(
638
- requestUrl,
639
- {
640
- method: 'POST',
641
- headers: {
642
- 'Content-Type': 'application/json',
643
- 'Authorization': `Bearer ${token}`,
644
- },
645
- body: JSON.stringify(payload),
646
- },
647
- config.allowInsecureTls,
648
- config.requestTimeout,
649
- )
650
- getOpenclawWorkclawLogger().info(`Send cron job message response:`, JSON.stringify(data, null, 2))
651
-
652
- if (data?.code !== 200 && data?.code !== 0) {
653
- throw new Error(data?.message || data?.msg || 'Failed to send cron job message')
654
- }
655
- return data
656
- }
617
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
618
+ const token = await getWorkclawAccessToken(cacheKey, config);
619
+
620
+ const requestUrl = `${baseUrl}/open-apis/cron/job/message`;
621
+ getWorkclawLogger().info(`Sending cron job message to ${requestUrl}`);
622
+ getWorkclawLogger().info(`Payload:`, JSON.stringify(payload, null, 2));
623
+
624
+ const data = await doFetchJson(
625
+ requestUrl,
626
+ {
627
+ method: "POST",
628
+ headers: {
629
+ "Content-Type": "application/json",
630
+ "Authorization": `Bearer ${token}`,
631
+ },
632
+ body: JSON.stringify(payload),
633
+ },
634
+ config.allowInsecureTls,
635
+ config.requestTimeout,
636
+ );
637
+ getWorkclawLogger().info(`Send cron job message response:`, JSON.stringify(data, null, 2));
638
+
639
+ if (data?.code !== 200 && data?.code !== 0) {
640
+ throw new Error(data?.message || data?.msg || "Failed to send cron job message");
641
+ }
642
+ return data;
643
+ }