@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.
- package/README.md +21 -1
- package/index.ts +210 -210
- package/openclaw.plugin.json +1 -0
- package/package.json +11 -4
- package/setup-entry.ts +6 -0
- package/skills/openclaw-workclaw-cron/SKILL.md +45 -28
- package/src/accounts.ts +62 -37
- package/src/api/accounts-api.ts +88 -89
- package/src/api/prompts-api.ts +70 -77
- package/src/api/session-api.ts +99 -108
- package/src/api/skills-api.ts +35 -37
- package/src/api/workspace.ts +27 -29
- package/src/channel.ts +200 -202
- package/src/config-schema.ts +9 -9
- package/src/connection/workclaw-client.ts +554 -567
- package/src/gateway/agent-handlers.ts +392 -426
- package/src/gateway/config-writer.ts +228 -243
- package/src/gateway/message-context.ts +534 -362
- package/src/gateway/message-dispatcher.ts +529 -489
- package/src/gateway/reconnect.ts +217 -113
- package/src/gateway/skills-handler.ts +408 -472
- package/src/gateway/skills-list-handler.ts +9 -9
- package/src/gateway/tools-list-handler.ts +70 -72
- package/src/gateway/workclaw-gateway.ts +328 -486
- package/src/media/upload.ts +83 -94
- package/src/outbound/index.ts +57 -55
- package/src/outbound/workclaw-sender.ts +134 -133
- package/src/runtime.ts +291 -194
- package/src/send.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/api/index.ts +6 -6
- package/src/tools/openclaw-workclaw-cron/src/add/params.ts +20 -19
- package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +2 -2
- package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +1 -1
- package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +3 -3
- package/src/tools/openclaw-workclaw-cron/src/update/params.ts +195 -197
- package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +4 -4
- package/src/tools/openclaw-workclaw-system/src/get/index.ts +2 -2
- package/src/tools/openclaw-workclaw-system/src/token/index.ts +4 -4
- package/src/types.ts +38 -40
- package/src/utils/content.ts +16 -21
- package/tests/accounts.test.ts +285 -0
- package/tests/message-context.test.ts +313 -0
- package/tests/reconnect.test.ts +257 -0
- package/tests/workclaw-client.test.ts +112 -0
- package/tsconfig.json +8 -5
- package/vitest.config.ts +8 -0
|
@@ -1,343 +1,330 @@
|
|
|
1
|
-
import { Agent, fetch as undiciFetch } from
|
|
2
|
-
import {
|
|
1
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
2
|
+
import { getWorkclawLogger } from "../runtime.js";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
13
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
url: string,
|
|
29
|
+
init: RequestInit,
|
|
30
|
+
allowInsecureTls?: boolean,
|
|
31
|
+
requestTimeout?: number,
|
|
32
32
|
): Promise<any> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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 (
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
155
|
-
`
|
|
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
|
-
|
|
159
|
-
throw new Error(
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
349
|
-
|
|
335
|
+
cacheKey: string,
|
|
336
|
+
config: WorkclawConnectionConfig
|
|
350
337
|
): Promise<AgentInstance[]> {
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
): { msgType: string
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
525
|
+
export async function syncWorkclawCronJobToBackend(
|
|
526
|
+
cacheKey: string,
|
|
527
|
+
config: WorkclawConnectionConfig,
|
|
528
|
+
payload: CronJobPayload
|
|
542
529
|
): Promise<any> {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
|
577
|
-
|
|
578
|
-
|
|
563
|
+
export async function getWorkclawModelConfigByAppKey(
|
|
564
|
+
cacheKey: string,
|
|
565
|
+
config: WorkclawConnectionConfig
|
|
579
566
|
): Promise<{
|
|
580
|
-
|
|
581
|
-
|
|
567
|
+
baseUrl: string;
|
|
568
|
+
apiKey: string;
|
|
582
569
|
}> {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
618
|
-
|
|
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
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
612
|
+
export async function sendWorkclawCronJobMessageToBackend(
|
|
613
|
+
cacheKey: string,
|
|
614
|
+
config: WorkclawConnectionConfig,
|
|
615
|
+
payload: CronJobMessagePayload
|
|
629
616
|
): Promise<any> {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
+
}
|