@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
|
@@ -2,38 +2,42 @@
|
|
|
2
2
|
* Message Dispatcher - handles all WorkClaw WebSocket message types
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
5
|
+
import type { ResolvedWorkclawAccount, WorkClawBaseConfig, WorkClawAccountConfig } from "../types.js";
|
|
6
|
+
import { parseWorkClawMessage, type ParseWorkClawResult } from "./message-context.js";
|
|
7
|
+
import { buildInboundContext, type InboundMessage } from "./message-context.js";
|
|
8
|
+
import { handleAgentCreated, handleAgentUpdated, handleAgentDeleted } from "./agent-handlers.js";
|
|
9
|
+
import { saveOpenConversationId, saveWorkClawAgentId, saveWorkClawApiKey, saveWorkClawUserId } from "./config-writer.js";
|
|
10
|
+
import { getWorkclawWsConnection, setLastInboundAt } from "../runtime.js";
|
|
11
|
+
import { getWorkclawRuntime } from "../runtime.js";
|
|
12
|
+
import { sendMessageWorkclaw } from "../outbound/index.js";
|
|
13
|
+
import { resolveAccountByUserIdAndAgentId, resolveWorkclawAccountWithCache } from "../accounts.js";
|
|
14
|
+
import { getWorkclawAccessToken, getWorkclawModelConfigByAppKey } from "../connection/workclaw-client.js";
|
|
15
|
+
import { setToolContext } from "../runtime.js";
|
|
16
|
+
import { handleSkillsEvent } from "./skills-handler.js";
|
|
17
|
+
import { handleToolsListEvent as doHandleToolsListEvent } from "./tools-list-handler.js";
|
|
18
|
+
import { handleSkillsListEvent as doHandleSkillsListEvent } from "./skills-list-handler.js";
|
|
19
|
+
import { request } from "undici";
|
|
16
20
|
|
|
17
21
|
export interface MessageDispatcherContext {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
accountId: string;
|
|
23
|
+
account: ResolvedWorkclawAccount;
|
|
24
|
+
cfg: any;
|
|
25
|
+
baseConfig: WorkClawBaseConfig;
|
|
26
|
+
accountConfig: WorkClawAccountConfig;
|
|
27
|
+
log?: {
|
|
28
|
+
info?: (msg: string) => void;
|
|
29
|
+
warn?: (msg: string) => void;
|
|
30
|
+
error?: (msg: string) => void;
|
|
31
|
+
debug?: (msg: string) => void;
|
|
32
|
+
};
|
|
33
|
+
scheduleReconnect: () => void;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
/** Log an error safely */
|
|
33
|
-
function logError(log: MessageDispatcherContext[
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
function logError(log: MessageDispatcherContext["log"], context: string, err: unknown): void {
|
|
38
|
+
const errAny = err as { stack?: string };
|
|
39
|
+
const stack = typeof errAny?.stack === "string" ? `\nStack: ${errAny.stack}` : "";
|
|
40
|
+
log?.error?.(`Dispatcher ${context}: ${String(err)}${stack}`);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
// ---------------------------------------------------------------------------
|
|
@@ -41,377 +45,380 @@ function logError(log: MessageDispatcherContext['log'], context: string, err: un
|
|
|
41
45
|
// ---------------------------------------------------------------------------
|
|
42
46
|
|
|
43
47
|
async function handlePing(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const { accountId, log } = ctx;
|
|
49
|
+
const ws = getWorkclawWsConnection(accountId);
|
|
50
|
+
if (!ws) return;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const pongResponse = {
|
|
54
|
+
code: 200,
|
|
55
|
+
message: "pong",
|
|
56
|
+
metadata: { contentType: "application/json" },
|
|
57
|
+
data: JSON.stringify(result.pongData),
|
|
58
|
+
};
|
|
59
|
+
ws.send(JSON.stringify(pongResponse));
|
|
60
|
+
log?.debug?.(`pong`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
logError(log, "Failed to send pong", err);
|
|
55
63
|
}
|
|
56
|
-
ws.send(JSON.stringify(pongResponse))
|
|
57
|
-
log?.debug?.(`pong`)
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
logError(log, 'Failed to send pong', err)
|
|
61
|
-
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
async function handleDisconnect(ctx: MessageDispatcherContext): Promise<void> {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
const { accountId, log, scheduleReconnect } = ctx;
|
|
68
|
+
const ws = getWorkclawWsConnection(accountId);
|
|
69
|
+
if (ws) {
|
|
70
|
+
try {
|
|
71
|
+
ws.close();
|
|
72
|
+
log?.info?.(`Closed WebSocket due to disconnect command`);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logError(log, "Failed to close WebSocket", err);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
log?.info?.(`WebSocket already cleared, triggering reconnect...`);
|
|
78
|
+
scheduleReconnect();
|
|
74
79
|
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
log?.info?.(`WebSocket already cleared, triggering reconnect...`)
|
|
78
|
-
scheduleReconnect()
|
|
79
|
-
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
async function handleAgentEvent(
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
ctx: MessageDispatcherContext,
|
|
84
|
+
result: ParseWorkClawResult,
|
|
85
85
|
): Promise<void> {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
86
|
+
const { cfg, log, baseConfig, accountId } = ctx;
|
|
87
|
+
const eventData = result.eventData;
|
|
88
|
+
if (!eventData) {
|
|
89
|
+
log?.warn?.(`Received ${result.type} event but no data`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const agentId = String(eventData.id || "");
|
|
94
|
+
const userId = String(eventData.futureId || "");
|
|
95
|
+
|
|
96
|
+
if (!agentId) {
|
|
97
|
+
log?.warn?.(`Received ${result.type} event but missing agent id`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
log?.info?.(`Processing ${result.type} event for agent ${agentId}, userId: ${userId}`);
|
|
102
|
+
|
|
103
|
+
const accountData = {
|
|
104
|
+
agentId,
|
|
105
|
+
userId,
|
|
106
|
+
nickName: eventData.nickName,
|
|
107
|
+
phone: eventData.phone,
|
|
108
|
+
status: eventData.status,
|
|
109
|
+
introduction: eventData.introduction,
|
|
110
|
+
name: eventData.name,
|
|
111
|
+
tip: eventData.tip,
|
|
112
|
+
characterSettings: eventData.characterSettings,
|
|
113
|
+
personFeatures: eventData.personFeatures,
|
|
114
|
+
workFeatures: eventData.workFeatures,
|
|
115
|
+
learningFeatures: eventData.learningFeatures,
|
|
116
|
+
socializeFeatures: eventData.socializeFeatures,
|
|
117
|
+
job: eventData.job,
|
|
118
|
+
city: eventData.city,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
switch (result.type) {
|
|
122
|
+
case "agent_created":
|
|
123
|
+
await handleAgentCreated(accountData, cfg, log);
|
|
124
|
+
handleAgentCreatedMessage(baseConfig, agentId, accountId, log)
|
|
125
|
+
break;
|
|
126
|
+
case "agent_updated":
|
|
127
|
+
await handleAgentUpdated(accountData, cfg, log);
|
|
128
|
+
break;
|
|
129
|
+
case "agent_deleted":
|
|
130
|
+
await handleAgentDeleted(accountData, cfg, log);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
async function handleSkillsProcessingEvent(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
136
|
+
const { accountId, baseConfig, log } = ctx;
|
|
137
|
+
if (!result.eventData) return;
|
|
138
|
+
try {
|
|
139
|
+
const connConfig = {
|
|
140
|
+
baseUrl: baseConfig.baseUrl || "",
|
|
141
|
+
websocketUrl: baseConfig.websocketUrl,
|
|
142
|
+
appKey: baseConfig.appKey,
|
|
143
|
+
appSecret: baseConfig.appSecret,
|
|
144
|
+
localIp: baseConfig.localIp,
|
|
145
|
+
allowInsecureTls: baseConfig.allowInsecureTls,
|
|
146
|
+
requestTimeout: baseConfig.requestTimeout,
|
|
147
|
+
};
|
|
148
|
+
const tokenCacheKey = baseConfig.appKey || accountId;
|
|
149
|
+
const token = await getWorkclawAccessToken(tokenCacheKey, connConfig);
|
|
150
|
+
await handleSkillsEvent(result.eventData, token, connConfig.baseUrl, connConfig.appKey || accountId, log);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
logError(log, "handleSkillsEvent failed", err);
|
|
149
153
|
}
|
|
150
|
-
const tokenCacheKey = baseConfig.appKey || accountId
|
|
151
|
-
const token = await getOpenclawWorkclawAccessToken(tokenCacheKey, connConfig)
|
|
152
|
-
await handleSkillsEvent(result.eventData, token, connConfig.baseUrl, connConfig.appKey || accountId, log)
|
|
153
|
-
}
|
|
154
|
-
catch (err) {
|
|
155
|
-
logError(log, 'handleSkillsEvent failed', err)
|
|
156
|
-
}
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
async function handleToolsListEvent(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
157
|
+
const { accountId, baseConfig, log } = ctx;
|
|
158
|
+
if (!result.eventData) return;
|
|
159
|
+
try {
|
|
160
|
+
const connConfig = {
|
|
161
|
+
baseUrl: baseConfig.baseUrl || "",
|
|
162
|
+
websocketUrl: baseConfig.websocketUrl,
|
|
163
|
+
appKey: baseConfig.appKey,
|
|
164
|
+
appSecret: baseConfig.appSecret,
|
|
165
|
+
localIp: baseConfig.localIp,
|
|
166
|
+
allowInsecureTls: baseConfig.allowInsecureTls,
|
|
167
|
+
requestTimeout: baseConfig.requestTimeout,
|
|
168
|
+
};
|
|
169
|
+
const tokenCacheKey = baseConfig.appKey || accountId;
|
|
170
|
+
const token = await getWorkclawAccessToken(tokenCacheKey, connConfig);
|
|
171
|
+
await doHandleToolsListEvent(result.eventData, connConfig.baseUrl, token, log);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
logError(log, "handleToolsListEvent failed", err);
|
|
173
174
|
}
|
|
174
|
-
const tokenCacheKey = baseConfig.appKey || accountId
|
|
175
|
-
const token = await getOpenclawWorkclawAccessToken(tokenCacheKey, connConfig)
|
|
176
|
-
await handleToolsListEvent(result.eventData, connConfig.baseUrl, token, log)
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
logError(log, 'handleToolsListEvent failed', err)
|
|
180
|
-
}
|
|
181
175
|
}
|
|
182
176
|
|
|
183
177
|
async function handleSkillsListEvent(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
178
|
+
const { accountId, baseConfig, log } = ctx;
|
|
179
|
+
if (!result.eventData) return;
|
|
180
|
+
try {
|
|
181
|
+
const connConfig = {
|
|
182
|
+
baseUrl: baseConfig.baseUrl || "",
|
|
183
|
+
websocketUrl: baseConfig.websocketUrl,
|
|
184
|
+
appKey: baseConfig.appKey,
|
|
185
|
+
appSecret: baseConfig.appSecret,
|
|
186
|
+
localIp: baseConfig.localIp,
|
|
187
|
+
allowInsecureTls: baseConfig.allowInsecureTls,
|
|
188
|
+
requestTimeout: baseConfig.requestTimeout,
|
|
189
|
+
};
|
|
190
|
+
const tokenCacheKey = baseConfig.appKey || accountId;
|
|
191
|
+
const token = await getWorkclawAccessToken(tokenCacheKey, connConfig);
|
|
192
|
+
await doHandleSkillsListEvent(result.eventData, connConfig.baseUrl, token, log);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
logError(log, "handleSkillsListEvent failed", err);
|
|
197
195
|
}
|
|
198
|
-
const tokenCacheKey = baseConfig.appKey || accountId
|
|
199
|
-
const token = await getOpenclawWorkclawAccessToken(tokenCacheKey, connConfig)
|
|
200
|
-
await handleSkillsListEvent(result.eventData, connConfig.baseUrl, token, log)
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
logError(log, 'handleSkillsListEvent failed', err)
|
|
204
|
-
}
|
|
205
196
|
}
|
|
206
197
|
|
|
207
198
|
async function handleInitAgentEvent(
|
|
208
|
-
|
|
209
|
-
|
|
199
|
+
ctx: MessageDispatcherContext,
|
|
200
|
+
result: ParseWorkClawResult,
|
|
210
201
|
): Promise<void> {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
log?.info?.(
|
|
221
|
-
`init_agent accountId=${accountId} eventDataType=${typeof eventData} preview=${JSON.stringify(eventData).slice(0, 200)}`,
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
const connConfig = {
|
|
225
|
-
baseUrl: baseConfig.baseUrl || '',
|
|
226
|
-
websocketUrl: baseConfig.websocketUrl,
|
|
227
|
-
appKey: baseConfig.appKey,
|
|
228
|
-
appSecret: baseConfig.appSecret,
|
|
229
|
-
localIp: baseConfig.localIp,
|
|
230
|
-
allowInsecureTls: baseConfig.allowInsecureTls,
|
|
231
|
-
requestTimeout: baseConfig.requestTimeout,
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Fetch and save apiKey
|
|
235
|
-
try {
|
|
236
|
-
const { getOpenclawWorkclawModelConfigByAppKey } = await import('../connection/workclaw-client.js')
|
|
237
|
-
const tokenCacheKey = baseConfig.appKey || accountId
|
|
238
|
-
const { baseUrl: modelBaseUrl, apiKey } = await getOpenclawWorkclawModelConfigByAppKey(tokenCacheKey, connConfig)
|
|
239
|
-
const maskedKey = apiKey ? `${String(apiKey).slice(0, 6)}...(${String(apiKey).length})` : 'missing'
|
|
240
|
-
|
|
241
|
-
log?.info?.(`init_agent model fetched baseUrl=${modelBaseUrl} apiKey=${maskedKey}`)
|
|
242
|
-
|
|
243
|
-
await saveWorkClawApiKey(apiKey, cfg, log)
|
|
244
|
-
log?.info?.(`init_agent model config saved elapsedMs=${Date.now() - initStartedAt}`)
|
|
245
|
-
}
|
|
246
|
-
catch (err) {
|
|
247
|
-
logError(log, 'init_agent model fetch/save failed', err)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Save default agentId
|
|
251
|
-
const agentId = String(eventData?.id || eventData?.agentId || '')
|
|
252
|
-
if (agentId) {
|
|
253
|
-
try {
|
|
254
|
-
await saveWorkClawAgentId('default', agentId, cfg, log)
|
|
255
|
-
log?.info?.(`init_agent saved default agentId=${agentId}`)
|
|
256
|
-
}
|
|
257
|
-
catch (err) {
|
|
258
|
-
logError(log, 'init_agent agentId save failed', err)
|
|
202
|
+
const { accountId, cfg, baseConfig, log } = ctx;
|
|
203
|
+
const initStartedAt = Date.now();
|
|
204
|
+
const eventData = result.eventData;
|
|
205
|
+
|
|
206
|
+
if (!eventData) {
|
|
207
|
+
log?.warn?.(`init_agent received but no data`);
|
|
208
|
+
return;
|
|
259
209
|
}
|
|
260
|
-
}
|
|
261
210
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
211
|
+
log?.info?.(
|
|
212
|
+
`init_agent accountId=${accountId} eventDataType=${typeof eventData} preview=${JSON.stringify(eventData).slice(0, 200)}`,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const connConfig = {
|
|
216
|
+
baseUrl: baseConfig.baseUrl || "",
|
|
217
|
+
websocketUrl: baseConfig.websocketUrl,
|
|
218
|
+
appKey: baseConfig.appKey,
|
|
219
|
+
appSecret: baseConfig.appSecret,
|
|
220
|
+
localIp: baseConfig.localIp,
|
|
221
|
+
allowInsecureTls: baseConfig.allowInsecureTls,
|
|
222
|
+
requestTimeout: baseConfig.requestTimeout,
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Fetch and save apiKey
|
|
265
226
|
try {
|
|
266
|
-
|
|
267
|
-
|
|
227
|
+
const tokenCacheKey = baseConfig.appKey || accountId;
|
|
228
|
+
const { baseUrl: modelBaseUrl, apiKey } = await getWorkclawModelConfigByAppKey(tokenCacheKey, connConfig);
|
|
229
|
+
const maskedKey = apiKey ? `${String(apiKey).slice(0, 6)}...(${String(apiKey).length})` : "missing";
|
|
230
|
+
|
|
231
|
+
log?.info?.(`init_agent model fetched baseUrl=${modelBaseUrl} apiKey=${maskedKey}`);
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
await saveWorkClawApiKey(apiKey, cfg, log);
|
|
235
|
+
log?.info?.(`init_agent model config saved elapsedMs=${Date.now() - initStartedAt}`);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
logError(log, "init_agent model fetch/save failed", err);
|
|
268
238
|
}
|
|
269
|
-
|
|
270
|
-
|
|
239
|
+
|
|
240
|
+
// Save default agentId
|
|
241
|
+
const agentId = String(eventData?.id || eventData?.agentId || "");
|
|
242
|
+
if (agentId) {
|
|
243
|
+
try {
|
|
244
|
+
await saveWorkClawAgentId("default", agentId, cfg, log);
|
|
245
|
+
log?.info?.(`init_agent saved default agentId=${agentId}`);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
logError(log, "init_agent agentId save failed", err);
|
|
248
|
+
}
|
|
271
249
|
}
|
|
272
|
-
}
|
|
273
250
|
|
|
274
|
-
|
|
251
|
+
// Save default userId
|
|
252
|
+
const userId = String(eventData?.futureId || eventData?.userId || "");
|
|
253
|
+
if (userId) {
|
|
254
|
+
try {
|
|
255
|
+
await saveWorkClawUserId("default", userId, cfg, log);
|
|
256
|
+
log?.info?.(`init_agent saved default userId=${userId}`);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
logError(log, "init_agent userId save failed", err);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
log?.info?.(`init_agent handled elapsedMs=${Date.now() - initStartedAt}`);
|
|
275
263
|
}
|
|
276
264
|
|
|
277
265
|
async function handleAgentMessage(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
266
|
+
ctx: MessageDispatcherContext,
|
|
267
|
+
data: string,
|
|
268
|
+
result: ParseWorkClawResult,
|
|
281
269
|
): Promise<void> {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return
|
|
270
|
+
const { accountId, account, cfg, accountConfig, log } = ctx;
|
|
271
|
+
const { message } = result;
|
|
272
|
+
if (!message) return;
|
|
273
|
+
|
|
274
|
+
// 这里的msgAgentId是云平台的agentId
|
|
275
|
+
const { text, userId, openConversationId, messageId: parsedMessageId, agentId: msgAgentId } = message;
|
|
276
|
+
|
|
277
|
+
// Resolve account from message
|
|
278
|
+
let messageAccountId = accountId;
|
|
279
|
+
let messageAccount = account;
|
|
280
|
+
|
|
281
|
+
if (userId && msgAgentId) {
|
|
282
|
+
const resolvedAccountId = resolveAccountByUserIdAndAgentId(cfg, String(userId), String(msgAgentId));
|
|
283
|
+
if (resolvedAccountId) {
|
|
284
|
+
const resolved = resolveWorkclawAccountWithCache({ cfg, accountId: resolvedAccountId });
|
|
285
|
+
messageAccountId = resolvedAccountId;
|
|
286
|
+
messageAccount = resolved;
|
|
287
|
+
log?.info?.(`Resolved account ${resolvedAccountId} from message (userId=${userId}, agentId=${msgAgentId})`);
|
|
288
|
+
} else if (String(msgAgentId) !== String(accountConfig.agentId)) {
|
|
289
|
+
log?.warn?.(`Account not found for userId=${userId}, agentId=${msgAgentId}, ignoring`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
305
292
|
}
|
|
306
|
-
}
|
|
307
293
|
|
|
308
|
-
|
|
309
|
-
|
|
294
|
+
const resolvedAccountConfig = messageAccount.config as unknown as WorkClawAccountConfig;
|
|
295
|
+
const msgAgentIdResolved = msgAgentId ?? accountConfig.agentId;
|
|
310
296
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
297
|
+
// Save agentId if not already set
|
|
298
|
+
if (msgAgentId && !resolvedAccountConfig.agentId) {
|
|
299
|
+
try {
|
|
300
|
+
await saveWorkClawAgentId(messageAccountId, msgAgentId, cfg, log);
|
|
301
|
+
log?.info?.(`Saved agentId ${msgAgentId} to account ${messageAccountId}`);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
logError(log, "saveWorkClawAgentId failed", err);
|
|
304
|
+
}
|
|
316
305
|
}
|
|
317
|
-
|
|
318
|
-
|
|
306
|
+
|
|
307
|
+
// Save openConversationId if new
|
|
308
|
+
if (openConversationId && openConversationId !== resolvedAccountConfig.openConversationId) {
|
|
309
|
+
try {
|
|
310
|
+
await saveOpenConversationId(messageAccountId, openConversationId, cfg, log);
|
|
311
|
+
log?.info?.(`Saved openConversationId ${openConversationId} to account ${messageAccountId}`);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
logError(log, "saveOpenConversationId failed", err);
|
|
314
|
+
}
|
|
319
315
|
}
|
|
320
|
-
}
|
|
321
316
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
317
|
+
// Save userId if not already set (directly to openclawWorkclaw level)
|
|
318
|
+
const workclawConfig = cfg?.channels?.['openclaw-workclaw'] || {};
|
|
319
|
+
if (userId && !workclawConfig.userId) {
|
|
320
|
+
try {
|
|
321
|
+
await saveWorkClawUserId(messageAccountId, userId, cfg, log);
|
|
322
|
+
log?.info?.(`Saved userId ${userId} to workclaw config`);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
logError(log, "saveWorkClawUserId failed", err);
|
|
325
|
+
}
|
|
327
326
|
}
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
|
|
328
|
+
const resolvedOpenConversationId = openConversationId || resolvedAccountConfig.openConversationId || "";
|
|
329
|
+
const to = userId;
|
|
330
|
+
const bodyText = text || data;
|
|
331
|
+
|
|
332
|
+
if (!bodyText) {
|
|
333
|
+
log?.warn?.(`Empty body, ignoring`);
|
|
334
|
+
return;
|
|
330
335
|
}
|
|
331
|
-
}
|
|
332
336
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
337
|
+
// Route via bindings
|
|
338
|
+
const bindings = cfg?.bindings ?? [];
|
|
339
|
+
let targetAgentId = "default";
|
|
340
|
+
const matchedBinding = bindings.find((binding: any) => {
|
|
341
|
+
const match = binding?.match;
|
|
342
|
+
if (!match || match.channel !== "openclaw-workclaw") return false;
|
|
343
|
+
return match.accountId === messageAccountId || match.accountId === "*";
|
|
344
|
+
});
|
|
345
|
+
if (matchedBinding) {
|
|
346
|
+
targetAgentId = matchedBinding.agentId;
|
|
347
|
+
log?.info?.(`Routing to agent ${targetAgentId} via binding (account: ${messageAccountId})`);
|
|
339
348
|
}
|
|
340
|
-
|
|
341
|
-
|
|
349
|
+
|
|
350
|
+
if (!targetAgentId) {
|
|
351
|
+
log?.warn?.(`Missing target, ignoring`);
|
|
352
|
+
return;
|
|
342
353
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
})
|
|
412
|
-
log?.info?.(`[AGENT] onAgentRunStart: runId=${runId} target=${targetAgentId}`)
|
|
413
|
-
},
|
|
414
|
-
/* onToolStart(payload) {
|
|
354
|
+
|
|
355
|
+
// 构建用于解析 sessionKey 的消息对象
|
|
356
|
+
// to = agentId (智能体/机器人), from = userId (用户)
|
|
357
|
+
const attachments = message.attachments
|
|
358
|
+
const inboundMessage: InboundMessage = {
|
|
359
|
+
text: bodyText,
|
|
360
|
+
to: to,
|
|
361
|
+
from: userId,
|
|
362
|
+
chatType: "dm",
|
|
363
|
+
messageId: parsedMessageId,
|
|
364
|
+
rawPayload: data,
|
|
365
|
+
timestamp: Date.now(),
|
|
366
|
+
attachments: attachments,
|
|
367
|
+
};
|
|
368
|
+
log?.info?.(`handleAgentMessage: text=${bodyText?.substring(0, 50)}, attachments=${attachments?.length || 0}`)
|
|
369
|
+
const pluginRuntime = getWorkclawRuntime();
|
|
370
|
+
|
|
371
|
+
// 使用扩展后的 buildInboundContext,它会处理完整的 session 流程
|
|
372
|
+
const finalized = await buildInboundContext(
|
|
373
|
+
inboundMessage,
|
|
374
|
+
messageAccountId,
|
|
375
|
+
messageAccount.config,
|
|
376
|
+
targetAgentId,
|
|
377
|
+
pluginRuntime,
|
|
378
|
+
true,
|
|
379
|
+
log,
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// 维护当前工具名称状态
|
|
383
|
+
let currentToolName: string | null = null;
|
|
384
|
+
// 追踪 final 是否已投递(框架在流式输出时会 drop final,需要手动补发 last: true)
|
|
385
|
+
let finalDelivered = false;
|
|
386
|
+
|
|
387
|
+
// 发送工具消息的辅助函数
|
|
388
|
+
const sendToolMessage = async (text: string, isStart: boolean = false) => {
|
|
389
|
+
try {
|
|
390
|
+
await sendMessageWorkclaw({
|
|
391
|
+
cfg,
|
|
392
|
+
to: to,
|
|
393
|
+
text,
|
|
394
|
+
accountId: messageAccountId,
|
|
395
|
+
openConversationId: resolvedOpenConversationId,
|
|
396
|
+
agentId: msgAgentIdResolved,
|
|
397
|
+
replyToMessageId: parsedMessageId,
|
|
398
|
+
last: false,
|
|
399
|
+
});
|
|
400
|
+
} catch (err) {
|
|
401
|
+
logError(log, isStart ? "Tool start message send failed" : "Tool result message send failed", err);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
406
|
+
ctx: finalized,
|
|
407
|
+
cfg,
|
|
408
|
+
replyOptions: {
|
|
409
|
+
onAgentRunStart(runId: string) {
|
|
410
|
+
// 保存 runId 和 ctx 的关联,供 hook 使用
|
|
411
|
+
setToolContext(runId, {
|
|
412
|
+
target: targetAgentId,
|
|
413
|
+
replyToMessageId: parsedMessageId,
|
|
414
|
+
openConversationId: resolvedOpenConversationId,
|
|
415
|
+
accountId: messageAccountId,
|
|
416
|
+
agentId: String(msgAgentIdResolved),
|
|
417
|
+
sessionKey: (finalized.SessionKey as string) || "",
|
|
418
|
+
});
|
|
419
|
+
log?.info?.(`[AGENT] onAgentRunStart: runId=${runId} target=${targetAgentId}`);
|
|
420
|
+
},
|
|
421
|
+
/*onToolStart(payload) {
|
|
415
422
|
log?.info?.(`[AGENT] onToolStart: ${JSON.stringify(payload)}`);
|
|
416
423
|
if (payload.phase === "start") {
|
|
417
424
|
currentToolName = payload.name ?? null;
|
|
@@ -421,37 +428,36 @@ async function handleAgentMessage(
|
|
|
421
428
|
logError(log, "Tool start hint send failed", err);
|
|
422
429
|
});
|
|
423
430
|
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
431
|
+
}*/
|
|
432
|
+
},
|
|
433
|
+
dispatcherOptions: {
|
|
434
|
+
deliver: async (payload: any, info?: any) => {
|
|
435
|
+
// 记录每次 deliver 调用,帮助调试 info.kind 问题
|
|
436
|
+
log?.info?.(`[DELIVER] deliver called: info.kind=${info?.kind}, payload.text=${String(payload?.text ?? "").substring(0, 100)}`);
|
|
437
|
+
const resolvedReplyToId = payload?.replyToId || parsedMessageId;
|
|
438
|
+
let textOut = String(payload.text ?? payload.body ?? "");
|
|
439
|
+
const target = to || String(payload.to ?? "");
|
|
440
|
+
|
|
441
|
+
if (!target) {
|
|
442
|
+
log?.warn?.(`Skipping reply: no target`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
438
445
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
446
|
+
if (!textOut.trim()) {
|
|
447
|
+
log?.warn?.(`Empty reply text, skipping`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
443
450
|
|
|
444
|
-
|
|
445
|
-
return
|
|
451
|
+
if (info?.kind === "tool") return;
|
|
446
452
|
|
|
447
|
-
|
|
448
|
-
|
|
453
|
+
// 处理工具类型的 deliver
|
|
454
|
+
/*if (info?.kind === "tool") {
|
|
449
455
|
// log?.info?.(`[DELIVER] tool reply: ${JSON.stringify(payload)}`);
|
|
450
456
|
const toolText = payload.text ?? "";
|
|
451
457
|
if (toolText.trim()) {
|
|
452
458
|
// 使用 formattedToolOutput 格式发送
|
|
453
459
|
try {
|
|
454
|
-
await
|
|
460
|
+
await sendMessageWorkclaw({
|
|
455
461
|
cfg,
|
|
456
462
|
to: targetAgentId,
|
|
457
463
|
text: JSON.stringify({
|
|
@@ -470,132 +476,166 @@ async function handleAgentMessage(
|
|
|
470
476
|
}
|
|
471
477
|
}
|
|
472
478
|
return;
|
|
473
|
-
}
|
|
479
|
+
}*/
|
|
480
|
+
|
|
481
|
+
// final 时清空工具状态
|
|
482
|
+
if (info?.kind === "final") {
|
|
483
|
+
currentToolName = null;
|
|
484
|
+
finalDelivered = true;
|
|
485
|
+
log?.info?.(`[DELIVER] final received, cleared currentToolName`);
|
|
486
|
+
}
|
|
474
487
|
|
|
475
|
-
|
|
488
|
+
// 明确设置 last:final 时为 true,否则为 false(显式转换,避免 undefined)
|
|
489
|
+
const isFinal = info?.kind === "final";
|
|
490
|
+
const isLast = isFinal ? true : false;
|
|
491
|
+
log?.info?.(`[DELIVER] is last: ${isLast} (info.kind=${info?.kind})`);
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const result = await sendMessageWorkclaw({
|
|
495
|
+
cfg, to: target, text: textOut,
|
|
496
|
+
accountId: messageAccountId, openConversationId: resolvedOpenConversationId,
|
|
497
|
+
agentId: msgAgentIdResolved, replyToMessageId: resolvedReplyToId,
|
|
498
|
+
last: isLast,
|
|
499
|
+
});
|
|
500
|
+
log?.info?.(`Reply sent messageId=${result.messageId}`);
|
|
501
|
+
} catch (err) {
|
|
502
|
+
logError(log, "Reply send failed", err);
|
|
503
|
+
throw err;
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
});
|
|
476
508
|
|
|
509
|
+
// 框架在流式输出时会 drop final payload,检测到时应手动补发 last: true
|
|
510
|
+
if (!finalDelivered) {
|
|
511
|
+
log?.warn?.(`[DELIVER] final was dropped by framework, sending last: true`);
|
|
477
512
|
try {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
513
|
+
// 发送一个空格作为占位内容,确保 last: true 能被服务端接受
|
|
514
|
+
await sendMessageWorkclaw({
|
|
515
|
+
cfg, to: to || String(userId || ""),
|
|
516
|
+
text: "",
|
|
517
|
+
accountId: messageAccountId, openConversationId: resolvedOpenConversationId,
|
|
518
|
+
agentId: msgAgentIdResolved, replyToMessageId: parsedMessageId,
|
|
519
|
+
last: true,
|
|
520
|
+
});
|
|
521
|
+
} catch (err) {
|
|
522
|
+
// 服务端可能拒绝空内容的 last 信号,忽略即可
|
|
523
|
+
const errStr = String(err);
|
|
524
|
+
if (errStr.includes("content can't be empty")) {
|
|
525
|
+
log?.info?.(`[DELIVER] last signal skipped (empty content not supported)`);
|
|
526
|
+
} else {
|
|
527
|
+
logError(log, "Final/last signal send failed", err);
|
|
528
|
+
}
|
|
489
529
|
}
|
|
490
|
-
|
|
491
|
-
logError(log, 'Reply send failed', err)
|
|
492
|
-
throw err
|
|
493
|
-
}
|
|
494
|
-
},
|
|
495
|
-
},
|
|
496
|
-
})
|
|
530
|
+
}
|
|
497
531
|
}
|
|
498
532
|
|
|
499
533
|
// ---------------------------------------------------------------------------
|
|
500
534
|
// Main dispatcher
|
|
501
535
|
// ---------------------------------------------------------------------------
|
|
502
536
|
|
|
503
|
-
export async function
|
|
504
|
-
|
|
505
|
-
|
|
537
|
+
export async function dispatchWorkclawMessage(
|
|
538
|
+
data: string,
|
|
539
|
+
ctx: MessageDispatcherContext,
|
|
506
540
|
): Promise<void> {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
log?.info?.(`Message type=${result.type}`)
|
|
525
|
-
|
|
526
|
-
switch (result.type) {
|
|
527
|
-
case 'ping':
|
|
528
|
-
await handlePing(ctx, result)
|
|
529
|
-
return
|
|
530
|
-
|
|
531
|
-
case 'disconnect':
|
|
532
|
-
await handleDisconnect(ctx)
|
|
533
|
-
return
|
|
534
|
-
|
|
535
|
-
case 'agent_created':
|
|
536
|
-
case 'agent_updated':
|
|
537
|
-
case 'agent_deleted':
|
|
538
|
-
await handleAgentEvent(ctx, result)
|
|
539
|
-
return
|
|
540
|
-
|
|
541
|
-
case 'skills_event':
|
|
542
|
-
await handleSkillsProcessingEvent(ctx, result)
|
|
543
|
-
return
|
|
544
|
-
|
|
545
|
-
case 'tools_list':
|
|
546
|
-
await handleToolsListEvent(ctx, result)
|
|
547
|
-
return
|
|
548
|
-
|
|
549
|
-
case 'skills_list':
|
|
550
|
-
await handleSkillsListEvent(ctx, result)
|
|
551
|
-
return
|
|
552
|
-
|
|
553
|
-
case 'init_agent':
|
|
554
|
-
await handleInitAgentEvent(ctx, result)
|
|
555
|
-
return
|
|
556
|
-
|
|
557
|
-
case 'ignored':
|
|
558
|
-
log?.info?.(`Ignoring message type=${result.type}`)
|
|
559
|
-
return
|
|
560
|
-
|
|
561
|
-
case 'agent_message':
|
|
562
|
-
await handleAgentMessage(ctx, data, result)
|
|
563
|
-
return
|
|
564
|
-
|
|
565
|
-
default:
|
|
566
|
-
log?.info?.(`Unknown message type: ${result.type}, ignoring`)
|
|
567
|
-
}
|
|
568
|
-
}
|
|
541
|
+
const { log } = ctx;
|
|
542
|
+
|
|
543
|
+
const rawHasAuthToken =
|
|
544
|
+
data.includes("authToken") ||
|
|
545
|
+
data.toLowerCase().includes("authorization") ||
|
|
546
|
+
data.toLowerCase().includes("bearer ");
|
|
547
|
+
const rawPreview = data.length > 300 ? `${data.slice(0, 300)}...` : data;
|
|
548
|
+
/*log?.info?.(
|
|
549
|
+
`Raw message bytes=${data.length} hasAuthToken=${rawHasAuthToken} preview=${rawPreview}`,
|
|
550
|
+
);*/
|
|
551
|
+
|
|
552
|
+
const result = parseWorkClawMessage(data, log);
|
|
553
|
+
if (!result) {
|
|
554
|
+
log?.warn?.(`Failed to parse message`);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
569
557
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const callbackData = {
|
|
577
|
-
agentId,
|
|
578
|
-
success: true,
|
|
558
|
+
log?.info?.(`Message type=${result.type}`);
|
|
559
|
+
|
|
560
|
+
// 更新上次消息时间(始终用 accountId,因为每个账号的入站时间需要独立追踪)
|
|
561
|
+
const { accountId } = ctx;
|
|
562
|
+
if (accountId) {
|
|
563
|
+
setLastInboundAt(accountId, Date.now());
|
|
579
564
|
}
|
|
580
565
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
566
|
+
switch (result.type) {
|
|
567
|
+
case "ping":
|
|
568
|
+
await handlePing(ctx, result);
|
|
569
|
+
return;
|
|
570
|
+
|
|
571
|
+
case "disconnect":
|
|
572
|
+
await handleDisconnect(ctx);
|
|
573
|
+
return;
|
|
574
|
+
|
|
575
|
+
case "agent_created":
|
|
576
|
+
case "agent_updated":
|
|
577
|
+
case "agent_deleted":
|
|
578
|
+
await handleAgentEvent(ctx, result);
|
|
579
|
+
return;
|
|
580
|
+
|
|
581
|
+
case "skills_event":
|
|
582
|
+
await handleSkillsProcessingEvent(ctx, result);
|
|
583
|
+
return;
|
|
584
|
+
|
|
585
|
+
case "tools_list":
|
|
586
|
+
await handleToolsListEvent(ctx, result);
|
|
587
|
+
return;
|
|
588
|
+
|
|
589
|
+
case "skills_list":
|
|
590
|
+
await handleSkillsListEvent(ctx, result);
|
|
591
|
+
return;
|
|
592
|
+
|
|
593
|
+
case "init_agent":
|
|
594
|
+
await handleInitAgentEvent(ctx, result);
|
|
595
|
+
return;
|
|
596
|
+
|
|
597
|
+
case "ignored":
|
|
598
|
+
log?.info?.(`Ignoring message type=${result.type}`);
|
|
599
|
+
return;
|
|
600
|
+
|
|
601
|
+
case "agent_message":
|
|
602
|
+
await handleAgentMessage(ctx, data, result);
|
|
603
|
+
return;
|
|
604
|
+
|
|
605
|
+
default:
|
|
606
|
+
log?.info?.(`Unknown message type: ${result.type}, ignoring`);
|
|
607
|
+
}
|
|
601
608
|
}
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
// 创建成功回调
|
|
612
|
+
async function handleAgentCreatedMessage(baseConfig: WorkClawBaseConfig, agentId: string, accountId: string, log?: any) {
|
|
613
|
+
// 通知
|
|
614
|
+
try {
|
|
615
|
+
const callbackUrl = `${baseConfig.baseUrl}/open-apis/instance/agent-created/status`;
|
|
616
|
+
const tokenCacheKey = baseConfig.appKey || accountId;
|
|
617
|
+
const callbackData = {
|
|
618
|
+
"agentId": agentId,
|
|
619
|
+
"success": true
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
log?.info?.(`[AgentCreated] Calling callback API: ${callbackUrl}`);
|
|
623
|
+
const token = await getWorkclawAccessToken(tokenCacheKey, baseConfig);
|
|
624
|
+
|
|
625
|
+
const response = await request(callbackUrl, {
|
|
626
|
+
method: "POST",
|
|
627
|
+
headers: {
|
|
628
|
+
"Content-Type": "application/json",
|
|
629
|
+
"Authorization": `Bearer ${token}`,
|
|
630
|
+
},
|
|
631
|
+
body: JSON.stringify(callbackData),
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const responseBody = await response.body.text();
|
|
635
|
+
log?.info?.(`[AgentCreated] Callback API response: status=${response.statusCode}, body=${responseBody}`);
|
|
636
|
+
|
|
637
|
+
} catch (callbackErr) {
|
|
638
|
+
// 接口调用失败不影响主流程,只记录错误
|
|
639
|
+
log?.error?.(`[AgentCreated] Callback API failed: ${String(callbackErr)}`);
|
|
640
|
+
}
|
|
641
|
+
}
|