rol-websocket-channel 1.8.4 → 1.8.6

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 (3) hide show
  1. package/dist/index.js +45 -4
  2. package/index.ts +50 -4
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11,6 +11,10 @@ import { registerArtifactsTools } from "./src/admin/tools/artifacts-tools.js";
11
11
  // ============================================
12
12
  import { getContext, initializeContext } from "./src/shared/context.js";
13
13
  let pluginRuntime = null;
14
+ // 记录最近活跃的 session 上下文,便于发信时注入 traceId 和 meta
15
+ const sessionContextMap = new Map();
16
+ // 以 senderId 为键的上下文缓存,供 sendText/sendMedia 反查 sessionKey
17
+ const senderContextMap = new Map();
14
18
  function isRecord(value) {
15
19
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
16
20
  }
@@ -274,15 +278,27 @@ const WebSocketChannel = {
274
278
  // MQTT 是 topic-based 路由,不依赖 to 做地址解析
275
279
  // 使用 "direct" 模式:AI reply 走 deliver 回调;message 工具走 sendText
276
280
  deliveryMode: "direct",
277
- sendText: async ({ to, text, sessionKey }) => {
281
+ // sendText:框架传入字段见 ChannelMessageSendTextContext(to = senderId,无 sessionKey/trace_id)
282
+ sendText: async ({ to, text, accountId }) => {
278
283
  const conn = ConnectionManager.getGlobalConnection();
279
284
  if (!conn || !conn.ws || !conn.ws.connected) {
280
285
  return { ok: false, error: "No MQTT connection" };
281
286
  }
287
+ // 通过 to(senderId)反查 session 上下文
288
+ const senderCtx = to ? senderContextMap.get(to) : undefined;
289
+ const finalTraceId = senderCtx?.traceId;
290
+ const finalAgentId = accountId || senderCtx?.agentId || "unknown";
291
+ const finalSessionKey = senderCtx?.sessionKey;
282
292
  // 与 deliver 回调保持一致,使用 receiver 格式发布消息
283
293
  const message = JSON.stringify({
284
294
  type: "receiver",
295
+ trace_id: finalTraceId,
285
296
  source: "ai",
297
+ meta: {
298
+ 'type': "sendText",
299
+ agentId: finalAgentId,
300
+ sessionKey: finalSessionKey,
301
+ },
286
302
  data: { text },
287
303
  timestamp: Date.now(),
288
304
  });
@@ -291,17 +307,29 @@ const WebSocketChannel = {
291
307
  if (targetTopic.endsWith("#")) {
292
308
  targetTopic = targetTopic.slice(0, -1) + "bot";
293
309
  }
294
- conn.ws.publish(targetTopic, message, { retain: true });
310
+ conn.ws.publish(targetTopic, message, { retain: false });
295
311
  return { ok: true };
296
312
  },
297
- sendMedia: async ({ to, text, mediaUrl }) => {
313
+ // sendMedia:框架传入字段见 ChannelMessageSendMediaContext(to = senderId,无 sessionKey/trace_id)
314
+ sendMedia: async ({ to, text, mediaUrl, accountId }) => {
298
315
  const conn = ConnectionManager.getGlobalConnection();
299
316
  if (!conn || !conn.ws || !conn.ws.connected) {
300
317
  return { ok: false, error: "No MQTT connection" };
301
318
  }
319
+ // 通过 to(senderId)反查 session 上下文
320
+ const senderCtx = to ? senderContextMap.get(to) : undefined;
321
+ const finalTraceId = senderCtx?.traceId;
322
+ const finalAgentId = accountId || senderCtx?.agentId || "unknown";
323
+ const finalSessionKey = senderCtx?.sessionKey;
302
324
  const message = JSON.stringify({
303
325
  type: "receiver",
326
+ trace_id: finalTraceId,
304
327
  source: "ai",
328
+ meta: {
329
+ 'type': "sendMedia",
330
+ agentId: finalAgentId,
331
+ sessionKey: finalSessionKey,
332
+ },
305
333
  data: { text, mediaUrl },
306
334
  timestamp: Date.now(),
307
335
  });
@@ -309,7 +337,7 @@ const WebSocketChannel = {
309
337
  if (targetTopic.endsWith("#")) {
310
338
  targetTopic = targetTopic.slice(0, -1) + "bot";
311
339
  }
312
- conn.ws.publish(targetTopic, message, { retain: true });
340
+ conn.ws.publish(targetTopic, message, { retain: false });
313
341
  return { ok: true };
314
342
  },
315
343
  },
@@ -345,6 +373,10 @@ const WebSocketChannel = {
345
373
  const text = typeof params.message === "string" ? params.message
346
374
  : typeof params.text === "string" ? params.text
347
375
  : "";
376
+ const sessionKey = ctx.sessionKey;
377
+ const sessionCtx = sessionKey ? sessionContextMap.get(sessionKey) : undefined;
378
+ const trace_id = params.trace_id || params.traceId || ctx.traceId || sessionCtx?.traceId || ctx.runId;
379
+ const agentId = ctx.accountId || ctx.agentId || sessionCtx?.agentId || "unknown";
348
380
  const conn = ConnectionManager.getGlobalConnection();
349
381
  if (!conn || !conn.ws || !conn.ws.connected) {
350
382
  return {
@@ -354,7 +386,13 @@ const WebSocketChannel = {
354
386
  }
355
387
  const replyMessage = JSON.stringify({
356
388
  type: "receiver",
389
+ trace_id: trace_id,
357
390
  source: "ai",
391
+ meta: {
392
+ 'type': "handleAction",
393
+ agentId: agentId,
394
+ sessionKey: sessionKey
395
+ },
358
396
  data: { text },
359
397
  timestamp: Date.now(),
360
398
  });
@@ -536,6 +574,9 @@ async function handleIncomingMessage(payload, account, cfg, runtime, log, mqttTo
536
574
  // 用户传参覆盖自动路由结果
537
575
  const resolvedAccountId = targetAgentId ?? route.accountId;
538
576
  const resolvedSessionKey = targetSessionId ?? route.sessionKey;
577
+ // 缓存最新请求上下文(双键:sessionKey 和 senderId)
578
+ sessionContextMap.set(resolvedSessionKey, { traceId, agentId: resolvedAccountId });
579
+ senderContextMap.set(normalizedMessage.senderId, { sessionKey: resolvedSessionKey, traceId, agentId: resolvedAccountId });
539
580
  // 构建消息上下文
540
581
  // To 必须设置为 senderId:框架用它确定 message 工具的回复目标
541
582
  // From 是发送方;To 是「这条对话的对端」(即 bot 应回复给谁)
package/index.ts CHANGED
@@ -40,6 +40,11 @@ import { getContext, initializeContext } from "./src/shared/context.js";
40
40
 
41
41
  let pluginRuntime: any = null;
42
42
 
43
+ // 记录最近活跃的 session 上下文,便于发信时注入 traceId 和 meta
44
+ const sessionContextMap = new Map<string, { traceId: string; agentId: string; }>();
45
+ // 以 senderId 为键的上下文缓存,供 sendText/sendMedia 反查 sessionKey
46
+ const senderContextMap = new Map<string, { sessionKey: string; traceId: string; agentId: string; }>();
47
+
43
48
  function isRecord(value: unknown): value is Record<string, any> {
44
49
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
45
50
  }
@@ -337,16 +342,29 @@ const WebSocketChannel: any = {
337
342
  // 使用 "direct" 模式:AI reply 走 deliver 回调;message 工具走 sendText
338
343
  deliveryMode: "direct",
339
344
 
340
- sendText: async ({ to, text, sessionKey }: any) => {
345
+ // sendText:框架传入字段见 ChannelMessageSendTextContext(to = senderId,无 sessionKey/trace_id)
346
+ sendText: async ({ to, text, accountId }: any) => {
341
347
  const conn = ConnectionManager.getGlobalConnection();
342
348
  if (!conn || !conn.ws || !conn.ws.connected) {
343
349
  return { ok: false, error: "No MQTT connection" };
344
350
  }
345
351
 
352
+ // 通过 to(senderId)反查 session 上下文
353
+ const senderCtx = to ? senderContextMap.get(to) : undefined;
354
+ const finalTraceId = senderCtx?.traceId;
355
+ const finalAgentId = accountId || senderCtx?.agentId || "unknown";
356
+ const finalSessionKey = senderCtx?.sessionKey;
357
+
346
358
  // 与 deliver 回调保持一致,使用 receiver 格式发布消息
347
359
  const message = JSON.stringify({
348
360
  type: "receiver",
361
+ trace_id: finalTraceId,
349
362
  source: "ai",
363
+ meta: {
364
+ 'type': "sendText",
365
+ agentId: finalAgentId,
366
+ sessionKey: finalSessionKey,
367
+ },
350
368
  data: { text },
351
369
  timestamp: Date.now(),
352
370
  });
@@ -357,19 +375,32 @@ const WebSocketChannel: any = {
357
375
  targetTopic = targetTopic.slice(0, -1) + "bot";
358
376
  }
359
377
 
360
- conn.ws.publish(targetTopic, message, { retain: true });
378
+ conn.ws.publish(targetTopic, message, { retain: false });
361
379
  return { ok: true };
362
380
  },
363
381
 
364
- sendMedia: async ({ to, text, mediaUrl }: any) => {
382
+ // sendMedia:框架传入字段见 ChannelMessageSendMediaContext(to = senderId,无 sessionKey/trace_id)
383
+ sendMedia: async ({ to, text, mediaUrl, accountId }: any) => {
365
384
  const conn = ConnectionManager.getGlobalConnection();
366
385
  if (!conn || !conn.ws || !conn.ws.connected) {
367
386
  return { ok: false, error: "No MQTT connection" };
368
387
  }
369
388
 
389
+ // 通过 to(senderId)反查 session 上下文
390
+ const senderCtx = to ? senderContextMap.get(to) : undefined;
391
+ const finalTraceId = senderCtx?.traceId;
392
+ const finalAgentId = accountId || senderCtx?.agentId || "unknown";
393
+ const finalSessionKey = senderCtx?.sessionKey;
394
+
370
395
  const message = JSON.stringify({
371
396
  type: "receiver",
397
+ trace_id: finalTraceId,
372
398
  source: "ai",
399
+ meta: {
400
+ 'type': "sendMedia",
401
+ agentId: finalAgentId,
402
+ sessionKey: finalSessionKey,
403
+ },
373
404
  data: { text, mediaUrl },
374
405
  timestamp: Date.now(),
375
406
  });
@@ -379,7 +410,7 @@ const WebSocketChannel: any = {
379
410
  targetTopic = targetTopic.slice(0, -1) + "bot";
380
411
  }
381
412
 
382
- conn.ws.publish(targetTopic, message, { retain: true });
413
+ conn.ws.publish(targetTopic, message, { retain: false });
383
414
  return { ok: true };
384
415
  },
385
416
  },
@@ -419,6 +450,11 @@ const WebSocketChannel: any = {
419
450
  : typeof params.text === "string" ? params.text
420
451
  : "";
421
452
 
453
+ const sessionKey = ctx.sessionKey;
454
+ const sessionCtx = sessionKey ? sessionContextMap.get(sessionKey) : undefined;
455
+ const trace_id = params.trace_id || params.traceId || ctx.traceId || sessionCtx?.traceId || ctx.runId;
456
+ const agentId = ctx.accountId || ctx.agentId || sessionCtx?.agentId || "unknown";
457
+
422
458
  const conn = ConnectionManager.getGlobalConnection();
423
459
  if (!conn || !conn.ws || !conn.ws.connected) {
424
460
  return {
@@ -429,7 +465,13 @@ const WebSocketChannel: any = {
429
465
 
430
466
  const replyMessage = JSON.stringify({
431
467
  type: "receiver",
468
+ trace_id: trace_id,
432
469
  source: "ai",
470
+ meta: {
471
+ 'type': "handleAction",
472
+ agentId: agentId,
473
+ sessionKey: sessionKey
474
+ },
433
475
  data: { text },
434
476
  timestamp: Date.now(),
435
477
  });
@@ -685,6 +727,10 @@ async function handleIncomingMessage(
685
727
  const resolvedAccountId = targetAgentId ?? route.accountId;
686
728
  const resolvedSessionKey = targetSessionId ?? route.sessionKey;
687
729
 
730
+ // 缓存最新请求上下文(双键:sessionKey 和 senderId)
731
+ sessionContextMap.set(resolvedSessionKey, { traceId, agentId: resolvedAccountId });
732
+ senderContextMap.set(normalizedMessage.senderId, { sessionKey: resolvedSessionKey, traceId, agentId: resolvedAccountId });
733
+
688
734
  // 构建消息上下文
689
735
  // To 必须设置为 senderId:框架用它确定 message 工具的回复目标
690
736
  // From 是发送方;To 是「这条对话的对端」(即 bot 应回复给谁)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rol-websocket-channel",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "Unified OpenClaw plugin: MQTT Channel + Admin Bridge for remote management",
5
5
  "license": "MIT",
6
6
  "author": "nixgnehc",