@workclaw/openclaw-workclaw 1.0.0

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 (48) hide show
  1. package/README.md +325 -0
  2. package/index.ts +298 -0
  3. package/openclaw.plugin.json +10 -0
  4. package/package.json +43 -0
  5. package/skills/openclaw-workclaw-cron/SKILL.md +458 -0
  6. package/src/accounts.ts +287 -0
  7. package/src/api/accounts-api.ts +157 -0
  8. package/src/api/prompts-api.ts +123 -0
  9. package/src/api/session-api.ts +247 -0
  10. package/src/api/skills-api.ts +74 -0
  11. package/src/api/workspace.ts +43 -0
  12. package/src/channel.ts +227 -0
  13. package/src/config-schema.ts +110 -0
  14. package/src/connection/workclaw-client.ts +656 -0
  15. package/src/gateway/agent-handlers.ts +557 -0
  16. package/src/gateway/config-writer.ts +311 -0
  17. package/src/gateway/message-context.ts +422 -0
  18. package/src/gateway/message-dispatcher.ts +601 -0
  19. package/src/gateway/reconnect.ts +149 -0
  20. package/src/gateway/skills-handler.ts +759 -0
  21. package/src/gateway/skills-list-handler.ts +332 -0
  22. package/src/gateway/tools-list-handler.ts +162 -0
  23. package/src/gateway/workclaw-gateway.ts +521 -0
  24. package/src/media/upload.ts +168 -0
  25. package/src/outbound/index.ts +183 -0
  26. package/src/outbound/workclaw-sender.ts +157 -0
  27. package/src/runtime.ts +400 -0
  28. package/src/send.ts +1 -0
  29. package/src/tools/openclaw-workclaw-cron/api/index.ts +326 -0
  30. package/src/tools/openclaw-workclaw-cron/index.ts +39 -0
  31. package/src/tools/openclaw-workclaw-cron/src/add/params.ts +176 -0
  32. package/src/tools/openclaw-workclaw-cron/src/add/sync.ts +188 -0
  33. package/src/tools/openclaw-workclaw-cron/src/disable/params.ts +100 -0
  34. package/src/tools/openclaw-workclaw-cron/src/disable/sync.ts +127 -0
  35. package/src/tools/openclaw-workclaw-cron/src/enable/params.ts +100 -0
  36. package/src/tools/openclaw-workclaw-cron/src/enable/sync.ts +127 -0
  37. package/src/tools/openclaw-workclaw-cron/src/notify/sync.ts +148 -0
  38. package/src/tools/openclaw-workclaw-cron/src/remove/params.ts +109 -0
  39. package/src/tools/openclaw-workclaw-cron/src/remove/sync.ts +127 -0
  40. package/src/tools/openclaw-workclaw-cron/src/update/params.ts +197 -0
  41. package/src/tools/openclaw-workclaw-cron/src/update/sync.ts +161 -0
  42. package/src/tools/openclaw-workclaw-cron/types/index.ts +55 -0
  43. package/src/tools/openclaw-workclaw-cron/utils/index.ts +141 -0
  44. package/src/types.ts +60 -0
  45. package/src/utils/content.ts +40 -0
  46. package/templates/IDENTITY.md +14 -0
  47. package/templates/SOUL.md +0 -0
  48. package/tsconfig.json +11 -0
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Message Dispatcher - handles all WorkClaw WebSocket message types
3
+ */
4
+
5
+ import type { ResolvedOpenclawWorkclawAccount, WorkClawAccountConfig, WorkClawBaseConfig } from '../types.js'
6
+ import type { InboundMessage, ParseWorkClawResult } from './message-context.js'
7
+
8
+ import { resolveAccountByUserIdAndAgentId, resolveOpenclawWorkclawAccount } from '../accounts.js'
9
+ import { getOpenclawWorkclawAccessToken } from '../connection/workclaw-client.js'
10
+
11
+ import { sendMessageOpenclawWorkclaw } from '../outbound/index.js'
12
+ import { getOpenclawWorkclawRuntime, getOpenclawWorkclawWsConnection, setToolContext } from '../runtime.js'
13
+ import { handleAgentCreated, handleAgentDeleted, handleAgentUpdated } from './agent-handlers.js'
14
+ import { saveOpenConversationId, saveWorkClawAgentId, saveWorkClawApiKey, saveWorkClawUserId } from './config-writer.js'
15
+ import { buildInboundContext, parseWorkClawMessage } from './message-context.js'
16
+
17
+ export interface MessageDispatcherContext {
18
+ accountId: string
19
+ account: ResolvedOpenclawWorkclawAccount
20
+ cfg: any
21
+ baseConfig: WorkClawBaseConfig
22
+ accountConfig: WorkClawAccountConfig
23
+ log?: {
24
+ info?: (msg: string) => void
25
+ warn?: (msg: string) => void
26
+ error?: (msg: string) => void
27
+ debug?: (msg: string) => void
28
+ }
29
+ scheduleReconnect: () => void
30
+ }
31
+
32
+ /** Log an error safely */
33
+ function logError(log: MessageDispatcherContext['log'], context: string, err: unknown): void {
34
+ const errAny = err as { stack?: string }
35
+ const stack = typeof errAny?.stack === 'string' ? `\nStack: ${errAny.stack}` : ''
36
+ log?.error?.(`Dispatcher ${context}: ${String(err)}${stack}`)
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Individual message handlers
41
+ // ---------------------------------------------------------------------------
42
+
43
+ async function handlePing(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
44
+ const { accountId, log } = ctx
45
+ const ws = getOpenclawWorkclawWsConnection(accountId)
46
+ if (!ws)
47
+ return
48
+
49
+ try {
50
+ const pongResponse = {
51
+ code: 200,
52
+ message: 'pong',
53
+ metadata: { contentType: 'application/json' },
54
+ data: JSON.stringify(result.pongData),
55
+ }
56
+ ws.send(JSON.stringify(pongResponse))
57
+ log?.debug?.(`pong`)
58
+ }
59
+ catch (err) {
60
+ logError(log, 'Failed to send pong', err)
61
+ }
62
+ }
63
+
64
+ async function handleDisconnect(ctx: MessageDispatcherContext): Promise<void> {
65
+ const { accountId, log, scheduleReconnect } = ctx
66
+ const ws = getOpenclawWorkclawWsConnection(accountId)
67
+ if (ws) {
68
+ try {
69
+ ws.close()
70
+ log?.info?.(`Closed WebSocket due to disconnect command`)
71
+ }
72
+ catch (err) {
73
+ logError(log, 'Failed to close WebSocket', err)
74
+ }
75
+ }
76
+ else {
77
+ log?.info?.(`WebSocket already cleared, triggering reconnect...`)
78
+ scheduleReconnect()
79
+ }
80
+ }
81
+
82
+ async function handleAgentEvent(
83
+ ctx: MessageDispatcherContext,
84
+ result: ParseWorkClawResult,
85
+ ): Promise<void> {
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
+ }
134
+
135
+ async function handleSkillsProcessingEvent(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
136
+ const { accountId, baseConfig, log } = ctx
137
+ if (!result.skillsEvent)
138
+ return
139
+ try {
140
+ const { handleSkillsEvent } = await import('./skills-handler.js')
141
+ const connConfig = {
142
+ baseUrl: baseConfig.baseUrl || '',
143
+ websocketUrl: baseConfig.websocketUrl,
144
+ appKey: baseConfig.appKey,
145
+ appSecret: baseConfig.appSecret,
146
+ localIp: baseConfig.localIp,
147
+ allowInsecureTls: baseConfig.allowInsecureTls,
148
+ requestTimeout: baseConfig.requestTimeout,
149
+ }
150
+ const tokenCacheKey = baseConfig.appKey || accountId
151
+ const token = await getOpenclawWorkclawAccessToken(tokenCacheKey, connConfig)
152
+ await handleSkillsEvent(result.skillsEvent, token, connConfig.baseUrl, connConfig.appKey || accountId, log)
153
+ }
154
+ catch (err) {
155
+ logError(log, 'handleSkillsEvent failed', err)
156
+ }
157
+ }
158
+
159
+ async function handleToolsListEvent(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
160
+ const { accountId, baseConfig, log } = ctx
161
+ if (!result.eventData)
162
+ return
163
+ try {
164
+ const { handleToolsListEvent } = await import('./tools-list-handler.js')
165
+ const connConfig = {
166
+ baseUrl: baseConfig.baseUrl || '',
167
+ websocketUrl: baseConfig.websocketUrl,
168
+ appKey: baseConfig.appKey,
169
+ appSecret: baseConfig.appSecret,
170
+ localIp: baseConfig.localIp,
171
+ allowInsecureTls: baseConfig.allowInsecureTls,
172
+ requestTimeout: baseConfig.requestTimeout,
173
+ }
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
+ }
182
+
183
+ async function handleSkillsListEvent(ctx: MessageDispatcherContext, result: ParseWorkClawResult): Promise<void> {
184
+ const { accountId, baseConfig, log } = ctx
185
+ if (!result.eventData)
186
+ return
187
+ try {
188
+ const { handleSkillsListEvent } = await import('./skills-list-handler.js')
189
+ const connConfig = {
190
+ baseUrl: baseConfig.baseUrl || '',
191
+ websocketUrl: baseConfig.websocketUrl,
192
+ appKey: baseConfig.appKey,
193
+ appSecret: baseConfig.appSecret,
194
+ localIp: baseConfig.localIp,
195
+ allowInsecureTls: baseConfig.allowInsecureTls,
196
+ requestTimeout: baseConfig.requestTimeout,
197
+ }
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
+ }
206
+
207
+ async function handleInitAgentEvent(
208
+ ctx: MessageDispatcherContext,
209
+ result: ParseWorkClawResult,
210
+ ): Promise<void> {
211
+ const { accountId, cfg, baseConfig, log } = ctx
212
+ const initStartedAt = Date.now()
213
+ const eventData = result.eventData
214
+
215
+ if (!eventData) {
216
+ log?.warn?.(`init_agent received but no data`)
217
+ return
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)
259
+ }
260
+ }
261
+
262
+ // Save default userId
263
+ const userId = String(eventData?.futureId || eventData?.userId || '')
264
+ if (userId) {
265
+ try {
266
+ await saveWorkClawUserId('default', userId, cfg, log)
267
+ log?.info?.(`init_agent saved default userId=${userId}`)
268
+ }
269
+ catch (err) {
270
+ logError(log, 'init_agent userId save failed', err)
271
+ }
272
+ }
273
+
274
+ log?.info?.(`init_agent handled elapsedMs=${Date.now() - initStartedAt}`)
275
+ }
276
+
277
+ async function handleAgentMessage(
278
+ ctx: MessageDispatcherContext,
279
+ data: string,
280
+ result: ParseWorkClawResult,
281
+ ): Promise<void> {
282
+ const { accountId, account, cfg, accountConfig, log } = ctx
283
+ const { message } = result
284
+ if (!message)
285
+ return
286
+
287
+ // 这里的msgAgentId是云平台的agentId
288
+ const { text, userId, openConversationId, messageId: parsedMessageId, agentId: msgAgentId } = message
289
+
290
+ // Resolve account from message
291
+ let messageAccountId = accountId
292
+ let messageAccount = account
293
+
294
+ if (userId && msgAgentId) {
295
+ const resolvedAccountId = resolveAccountByUserIdAndAgentId(cfg, String(userId), String(msgAgentId))
296
+ if (resolvedAccountId) {
297
+ const resolved = resolveOpenclawWorkclawAccount({ cfg, accountId: resolvedAccountId })
298
+ messageAccountId = resolvedAccountId
299
+ messageAccount = resolved
300
+ log?.info?.(`Resolved account ${resolvedAccountId} from message (userId=${userId}, agentId=${msgAgentId})`)
301
+ }
302
+ else if (String(msgAgentId) !== String(accountConfig.agentId)) {
303
+ log?.warn?.(`Account not found for userId=${userId}, agentId=${msgAgentId}, ignoring`)
304
+ return
305
+ }
306
+ }
307
+
308
+ const resolvedAccountConfig = messageAccount.config as unknown as WorkClawAccountConfig
309
+ const msgAgentIdResolved = msgAgentId ?? accountConfig.agentId
310
+
311
+ // Save agentId if not already set
312
+ if (msgAgentId && !resolvedAccountConfig.agentId) {
313
+ try {
314
+ await saveWorkClawAgentId(messageAccountId, msgAgentId, cfg, log)
315
+ log?.info?.(`Saved agentId ${msgAgentId} to account ${messageAccountId}`)
316
+ }
317
+ catch (err) {
318
+ logError(log, 'saveWorkClawAgentId failed', err)
319
+ }
320
+ }
321
+
322
+ // Save openConversationId if new
323
+ if (openConversationId && openConversationId !== resolvedAccountConfig.openConversationId) {
324
+ try {
325
+ await saveOpenConversationId(messageAccountId, openConversationId, cfg, log)
326
+ log?.info?.(`Saved openConversationId ${openConversationId} to account ${messageAccountId}`)
327
+ }
328
+ catch (err) {
329
+ logError(log, 'saveOpenConversationId failed', err)
330
+ }
331
+ }
332
+
333
+ // Save userId if not already set (directly to openclawWorkclaw level)
334
+ const openclawWorkclawConfig = cfg?.channels?.['openclaw-workclaw'] || {}
335
+ if (userId && !openclawWorkclawConfig.userId) {
336
+ try {
337
+ await saveWorkClawUserId(messageAccountId, userId, cfg, log)
338
+ log?.info?.(`Saved userId ${userId} to openclawWorkclaw config`)
339
+ }
340
+ catch (err) {
341
+ logError(log, 'saveWorkClawUserId failed', err)
342
+ }
343
+ }
344
+
345
+ const resolvedOpenConversationId = openConversationId || resolvedAccountConfig.openConversationId || ''
346
+ const to = userId
347
+ const bodyText = text || data
348
+
349
+ if (!bodyText) {
350
+ log?.warn?.(`Empty body, ignoring`)
351
+ return
352
+ }
353
+
354
+ // Route via bindings
355
+ const bindings = cfg?.bindings ?? []
356
+ let targetAgentId = 'default'
357
+ const matchedBinding = bindings.find((binding: any) => {
358
+ const match = binding?.match
359
+ if (!match || match.channel !== 'openclaw-workclaw')
360
+ return false
361
+ return match.accountId === messageAccountId || match.accountId === '*'
362
+ })
363
+ if (matchedBinding) {
364
+ targetAgentId = matchedBinding.agentId
365
+ log?.info?.(`Routing to agent ${targetAgentId} via binding (account: ${messageAccountId})`)
366
+ }
367
+
368
+ if (!targetAgentId) {
369
+ log?.warn?.(`Missing target, ignoring`)
370
+ return
371
+ }
372
+
373
+ // 构建用于解析 sessionKey 的消息对象
374
+ // to = agentId (智能体/机器人), from = userId (用户)
375
+ const inboundMessage: InboundMessage = {
376
+ text: bodyText,
377
+ to,
378
+ from: userId,
379
+ chatType: 'dm',
380
+ messageId: parsedMessageId,
381
+ rawPayload: data,
382
+ timestamp: Date.now(),
383
+ }
384
+
385
+ const pluginRuntime = getOpenclawWorkclawRuntime()
386
+
387
+ // 使用扩展后的 buildInboundContext,它会处理完整的 session 流程
388
+ const finalized = await buildInboundContext(
389
+ inboundMessage,
390
+ messageAccountId,
391
+ messageAccount.config,
392
+ targetAgentId,
393
+ pluginRuntime,
394
+ true,
395
+ log,
396
+ )
397
+
398
+ await pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
399
+ ctx: finalized,
400
+ cfg,
401
+ replyOptions: {
402
+ onAgentRunStart(runId: string) {
403
+ // 保存 runId 和 ctx 的关联,供 hook 使用
404
+ setToolContext(runId, {
405
+ target: targetAgentId,
406
+ replyToMessageId: parsedMessageId,
407
+ openConversationId: resolvedOpenConversationId,
408
+ accountId: messageAccountId,
409
+ agentId: String(msgAgentIdResolved),
410
+ sessionKey: (finalized.SessionKey as string) || '',
411
+ })
412
+ log?.info?.(`[AGENT] onAgentRunStart: runId=${runId} target=${targetAgentId}`)
413
+ },
414
+ /* onToolStart(payload) {
415
+ log?.info?.(`[AGENT] onToolStart: ${JSON.stringify(payload)}`);
416
+ if (payload.phase === "start") {
417
+ currentToolName = payload.name ?? null;
418
+ const hint = getToolStartHint(currentToolName);
419
+ log?.info?.(`[TOOL] Starting tool: ${currentToolName}, sending hint: ${hint}`);
420
+ sendToolMessage(hint, true).catch((err) => {
421
+ logError(log, "Tool start hint send failed", err);
422
+ });
423
+ }
424
+ } */
425
+ },
426
+ dispatcherOptions: {
427
+ deliver: async (payload: any, info?: any) => {
428
+ // 记录每次 deliver 调用,帮助调试 info.kind 问题
429
+ log?.info?.(`[DELIVER] deliver called: info.kind=${info?.kind}, payload.text=${String(payload?.text ?? '').substring(0, 100)}`)
430
+ const resolvedReplyToId = payload?.replyToId || parsedMessageId
431
+ const textOut = String(payload.text ?? payload.body ?? '')
432
+ const target = to || String(payload.to ?? '')
433
+
434
+ if (!target) {
435
+ log?.warn?.(`Skipping reply: no target`)
436
+ return
437
+ }
438
+
439
+ if (!textOut.trim()) {
440
+ log?.warn?.(`Empty reply text, skipping`)
441
+ return
442
+ }
443
+
444
+ if (info?.kind === 'tool')
445
+ return
446
+
447
+ // 处理工具类型的 deliver
448
+ /* if (info?.kind === "tool") {
449
+ // log?.info?.(`[DELIVER] tool reply: ${JSON.stringify(payload)}`);
450
+ const toolText = payload.text ?? "";
451
+ if (toolText.trim()) {
452
+ // 使用 formattedToolOutput 格式发送
453
+ try {
454
+ await sendMessageOpenclawWorkclaw({
455
+ cfg,
456
+ to: targetAgentId,
457
+ text: JSON.stringify({
458
+ name: currentToolName || "Unknown",
459
+ content: toolText,
460
+ state: "result",
461
+ }),
462
+ msgType: "26",
463
+ accountId: messageAccountId,
464
+ openConversationId: resolvedOpenConversationId,
465
+ agentId: msgAgentIdResolved,
466
+ replyToMessageId: parsedMessageId,
467
+ });
468
+ } catch (err) {
469
+ logError(log, "Tool result send failed", err);
470
+ }
471
+ }
472
+ return;
473
+ } */
474
+
475
+ log?.info?.(`[DELIVER] is last: ${info?.kind === 'final'}`)
476
+
477
+ try {
478
+ const result = await sendMessageOpenclawWorkclaw({
479
+ cfg,
480
+ to: target,
481
+ text: textOut,
482
+ accountId: messageAccountId,
483
+ openConversationId: resolvedOpenConversationId,
484
+ agentId: msgAgentIdResolved,
485
+ replyToMessageId: resolvedReplyToId,
486
+ last: info?.kind === 'final',
487
+ })
488
+ log?.info?.(`Reply sent messageId=${result.messageId}`)
489
+ }
490
+ catch (err) {
491
+ logError(log, 'Reply send failed', err)
492
+ throw err
493
+ }
494
+ },
495
+ },
496
+ })
497
+ }
498
+
499
+ // ---------------------------------------------------------------------------
500
+ // Main dispatcher
501
+ // ---------------------------------------------------------------------------
502
+
503
+ export async function dispatchOpenclawWorkclawMessage(
504
+ data: string,
505
+ ctx: MessageDispatcherContext,
506
+ ): Promise<void> {
507
+ const { log } = ctx
508
+
509
+ const rawHasAuthToken
510
+ = data.includes('authToken')
511
+ || data.toLowerCase().includes('authorization')
512
+ || data.toLowerCase().includes('bearer ')
513
+ const rawPreview = data.length > 300 ? `${data.slice(0, 300)}...` : data
514
+ log?.info?.(
515
+ `Raw message bytes=${data.length} hasAuthToken=${rawHasAuthToken} preview=${rawPreview}`,
516
+ )
517
+
518
+ const result = parseWorkClawMessage(data, log)
519
+ if (!result) {
520
+ log?.warn?.(`Failed to parse message`)
521
+ return
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
+ }
569
+
570
+ // 创建成功回调
571
+ async function handleAgentCreatedMessage(baseConfig: WorkClawBaseConfig, agentId: string, accountId: string, log?: any): Promise<void> {
572
+ // 通知
573
+ try {
574
+ const callbackUrl = `${baseConfig.baseUrl}/open-apis/instance/agent-created/status`
575
+ const tokenCacheKey = baseConfig.appKey || accountId
576
+ const callbackData = {
577
+ agentId,
578
+ success: true,
579
+ }
580
+
581
+ log?.info?.(`[AgentCreated] Calling callback API: ${callbackUrl}`)
582
+ const token = await getOpenclawWorkclawAccessToken(tokenCacheKey, baseConfig)
583
+
584
+ const { request } = await import('undici')
585
+ const response = await request(callbackUrl, {
586
+ method: 'POST',
587
+ headers: {
588
+ 'Content-Type': 'application/json',
589
+ 'Authorization': `Bearer ${token}`,
590
+ },
591
+ body: JSON.stringify(callbackData),
592
+ })
593
+
594
+ const responseBody = await response.body.text()
595
+ log?.info?.(`[AgentCreated] Callback API response: status=${response.statusCode}, body=${responseBody}`)
596
+ }
597
+ catch (callbackErr) {
598
+ // 接口调用失败不影响主流程,只记录错误
599
+ log?.error?.(`[AgentCreated] Callback API failed: ${String(callbackErr)}`)
600
+ }
601
+ }