@ynhcj/xiaoyi-channel 0.0.41-beta → 0.0.43-beta

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/dist/src/bot.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { getXYRuntime } from "./runtime.js";
2
2
  import { createXYReplyDispatcher } from "./reply-dispatcher.js";
3
- import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId } from "./parser.js";
3
+ import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractTriggerData } from "./parser.js";
4
4
  import { resolveXYConfig } from "./config.js";
5
- import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse } from "./formatter.js";
5
+ import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
6
6
  import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
7
7
  import { configManager } from "./utils/config-manager.js";
8
8
  import { addPushId } from "./utils/pushid-manager.js";
9
+ import { getPushDataById } from "./utils/pushdata-manager.js";
9
10
  import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
10
11
  /**
11
12
  * Handle an incoming A2A message.
@@ -57,6 +58,44 @@ export async function handleXYMessage(params) {
57
58
  }
58
59
  // Parse the A2A message (for regular messages)
59
60
  const parsed = parseA2AMessage(message);
61
+ // ========== 检测 Trigger 消息 ==========
62
+ // 如果消息中包含 Trigger 事件数据,直接返回 pushData 内容,不走正常流程
63
+ const triggerData = extractTriggerData(parsed.parts);
64
+ if (triggerData) {
65
+ log(`[BOT] 📌 Detected Trigger message with pushDataId: ${triggerData.pushDataId}`);
66
+ log(`[BOT] - Session ID: ${parsed.sessionId}`);
67
+ log(`[BOT] - Task ID: ${parsed.taskId}`);
68
+ try {
69
+ // 读取 pushData
70
+ const pushDataItem = await getPushDataById(triggerData.pushDataId);
71
+ if (!pushDataItem) {
72
+ error(`[BOT] ❌ pushData not found for ID: ${triggerData.pushDataId}`);
73
+ return;
74
+ }
75
+ log(`[BOT] ✅ Found pushData, sending direct response`);
76
+ log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
77
+ log(`[BOT] - time: ${pushDataItem.time}`);
78
+ log(`[BOT] - content length: ${pushDataItem.dataDetail.length} chars`);
79
+ const config = resolveXYConfig(cfg);
80
+ // 直接发送响应(final=true,不走 openclaw 流程)
81
+ await sendA2AResponse({
82
+ config,
83
+ sessionId: parsed.sessionId,
84
+ taskId: parsed.taskId,
85
+ messageId: parsed.messageId,
86
+ text: pushDataItem.dataDetail,
87
+ append: false,
88
+ final: true,
89
+ });
90
+ log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
91
+ return; // 提前返回,不继续处理
92
+ }
93
+ catch (err) {
94
+ error(`[BOT] ❌ Failed to handle Trigger message:`, err);
95
+ return;
96
+ }
97
+ }
98
+ // ========================================
60
99
  // 🔑 检测steer模式和是否是第二条消息
61
100
  const isSteerMode = cfg.messages?.queue?.mode === "steer";
62
101
  const isSecondMessage = isSteerMode && hasActiveTask(parsed.sessionId);
@@ -92,3 +92,17 @@ export interface SendTasksCancelResponseParams {
92
92
  * Send a tasks/cancel response.
93
93
  */
94
94
  export declare function sendTasksCancelResponse(params: SendTasksCancelResponseParams): Promise<void>;
95
+ /**
96
+ * Parameters for sending a Trigger response.
97
+ */
98
+ export interface SendTriggerResponseParams {
99
+ config: XYChannelConfig;
100
+ sessionId: string;
101
+ taskId: string;
102
+ messageId: string;
103
+ content: string;
104
+ }
105
+ /**
106
+ * Send a Trigger response with pushData content.
107
+ */
108
+ export declare function sendTriggerResponse(params: SendTriggerResponseParams): Promise<void>;
@@ -293,3 +293,49 @@ export async function sendTasksCancelResponse(params) {
293
293
  await wsManager.sendMessage(sessionId, outboundMessage);
294
294
  log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
295
295
  }
296
+ /**
297
+ * Send a Trigger response with pushData content.
298
+ */
299
+ export async function sendTriggerResponse(params) {
300
+ const { config, sessionId, taskId, messageId, content } = params;
301
+ const runtime = getXYRuntime();
302
+ const log = runtime?.log ?? console.log;
303
+ const error = runtime?.error ?? console.error;
304
+ // Build JSON-RPC response for Trigger
305
+ const jsonRpcResponse = {
306
+ jsonrpc: "2.0",
307
+ id: messageId,
308
+ result: {
309
+ taskId: taskId,
310
+ kind: "artifact-update",
311
+ append: false,
312
+ lastChunk: true,
313
+ final: true,
314
+ artifact: {
315
+ artifactId: uuidv4(),
316
+ parts: [
317
+ {
318
+ kind: "text",
319
+ text: content,
320
+ },
321
+ ],
322
+ },
323
+ },
324
+ error: {
325
+ code: 0,
326
+ message: "",
327
+ },
328
+ };
329
+ // Send via WebSocket
330
+ const wsManager = getXYWebSocketManager(config);
331
+ const outboundMessage = {
332
+ msgType: "agent_response",
333
+ agentId: config.agentId,
334
+ sessionId,
335
+ taskId,
336
+ msgDetail: JSON.stringify(jsonRpcResponse),
337
+ };
338
+ log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
339
+ await wsManager.sendMessage(sessionId, outboundMessage);
340
+ log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
341
+ }
@@ -43,6 +43,13 @@ export declare function isTasksCancelMessage(method: string): boolean;
43
43
  * Looks for push_id in data parts under variables.systemVariables.push_id
44
44
  */
45
45
  export declare function extractPushId(parts: A2AMessagePart[]): string | null;
46
+ /**
47
+ * Extract Trigger event data from message parts.
48
+ * Looks for Trigger events with pushDataId in data parts.
49
+ */
50
+ export declare function extractTriggerData(parts: A2AMessagePart[]): {
51
+ pushDataId: string;
52
+ } | null;
46
53
  /**
47
54
  * Validate A2A request structure.
48
55
  */
@@ -72,6 +72,28 @@ export function extractPushId(parts) {
72
72
  }
73
73
  return null;
74
74
  }
75
+ /**
76
+ * Extract Trigger event data from message parts.
77
+ * Looks for Trigger events with pushDataId in data parts.
78
+ */
79
+ export function extractTriggerData(parts) {
80
+ for (const part of parts) {
81
+ if (part.kind === "data" && part.data) {
82
+ const events = part.data.events;
83
+ if (Array.isArray(events)) {
84
+ for (const event of events) {
85
+ if (event.header?.namespace === "Common" && event.header?.name === "Trigger") {
86
+ const pushDataId = event.payload?.dataMap?.pushDataId;
87
+ if (pushDataId && typeof pushDataId === "string") {
88
+ return { pushDataId };
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ return null;
96
+ }
75
97
  /**
76
98
  * Validate A2A request structure.
77
99
  */
@@ -11,6 +11,9 @@ export interface TriggerEventContext {
11
11
  * 处理 Trigger 事件
12
12
  * 当用户在手机侧点击推送消息时触发
13
13
  *
14
+ * 策略:构造一个包含 Trigger 事件的 A2A 消息,通过 handleXYMessage 处理
15
+ * 这样可以复用现有的消息链路和 runtime 初始化
16
+ *
14
17
  * @param context - Trigger 事件上下文(包含 event, sessionId, taskId)
15
18
  * @param cfg - OpenClaw 配置
16
19
  * @param runtime - 运行时环境
@@ -1,11 +1,11 @@
1
- // Trigger 事件处理器
2
- import { randomUUID } from "crypto";
3
- import { getPushDataById } from "./utils/pushdata-manager.js";
4
1
  import { handleXYMessage } from "./bot.js";
5
2
  /**
6
3
  * 处理 Trigger 事件
7
4
  * 当用户在手机侧点击推送消息时触发
8
5
  *
6
+ * 策略:构造一个包含 Trigger 事件的 A2A 消息,通过 handleXYMessage 处理
7
+ * 这样可以复用现有的消息链路和 runtime 初始化
8
+ *
9
9
  * @param context - Trigger 事件上下文(包含 event, sessionId, taskId)
10
10
  * @param cfg - OpenClaw 配置
11
11
  * @param runtime - 运行时环境
@@ -17,73 +17,41 @@ export async function handleTriggerEvent(context, cfg, runtime, accountId) {
17
17
  try {
18
18
  const { event, sessionId, taskId } = context;
19
19
  log(`[TRIGGER_HANDLER] 📌 Received Trigger event`);
20
- log(`[TRIGGER_HANDLER] Event:`, JSON.stringify(event, null, 2));
21
- log(`[TRIGGER_HANDLER] Context - sessionId: ${sessionId}, taskId: ${taskId}`);
22
- if (!sessionId) {
23
- error(`[TRIGGER_HANDLER] Missing sessionId in context`);
24
- return;
25
- }
26
- if (!taskId) {
27
- error(`[TRIGGER_HANDLER] ❌ Missing taskId in context`);
28
- return;
29
- }
30
- // 从 event.payload.dataMap 中提取 pushDataId
31
- const pushDataId = event.payload?.dataMap?.pushDataId;
32
- if (!pushDataId) {
33
- error(`[TRIGGER_HANDLER] ❌ Missing pushDataId in Trigger event payload`);
34
- return;
35
- }
36
- log(`[TRIGGER_HANDLER] 🔍 Looking up pushDataId: ${pushDataId}`);
37
- // 根据 pushDataId 查询原始数据
38
- const pushDataItem = await getPushDataById(pushDataId);
39
- if (!pushDataItem) {
40
- error(`[TRIGGER_HANDLER] ❌ pushData not found for ID: ${pushDataId}`);
41
- return;
42
- }
43
- log(`[TRIGGER_HANDLER] ✅ Found pushData`);
44
- log(`[TRIGGER_HANDLER] - pushDataId: ${pushDataItem.pushDataId}`);
45
- log(`[TRIGGER_HANDLER] - time: ${pushDataItem.time}`);
46
- log(`[TRIGGER_HANDLER] - dataDetail length: ${pushDataItem.dataDetail.length} chars`);
47
- // 拼接消息
48
- const prefixMessage = "这是定时任务的执行结果,你检查一下是否依赖手机侧的工具执行,如果依赖则在本轮任务中完成手机侧任务执行,如果不依赖,则直接把完整的执行结果返回给用户";
49
- const fullMessage = `${prefixMessage}\n\n${pushDataItem.dataDetail}`;
50
- log(`[TRIGGER_HANDLER] 📝 Constructed message (length: ${fullMessage.length} chars)`);
51
- // 构造 A2A 消息
52
- // 使用从 WebSocket 传递过来的 sessionId 和 taskId(新的 taskId)
53
- // 这样可以触发 steer 模式,覆盖之前正在执行的任务
54
- const messageId = randomUUID();
20
+ log(`[TRIGGER_HANDLER] - sessionId: ${sessionId}`);
21
+ log(`[TRIGGER_HANDLER] - taskId: ${taskId}`);
22
+ log(`[TRIGGER_HANDLER] - pushDataId: ${event.payload?.dataMap?.pushDataId}`);
23
+ // 构造包含 Trigger 事件的 A2A 消息
24
+ // 将原始 event 放入 message.parts 中,让 handleXYMessage 检测并处理
55
25
  const a2aMessage = {
56
26
  jsonrpc: "2.0",
57
27
  method: "sendMessage",
58
- id: messageId,
28
+ id: taskId,
59
29
  params: {
60
- id: taskId, // 使用新的 taskId(点击推送时生成)
61
- sessionId: sessionId, // 使用原始会话 sessionId
30
+ id: taskId,
31
+ sessionId: sessionId,
62
32
  agentLoginSessionId: "",
63
33
  message: {
64
34
  role: "user",
65
35
  parts: [
66
36
  {
67
- kind: "text",
68
- text: fullMessage,
37
+ kind: "data",
38
+ data: {
39
+ events: [event], // 包含 Trigger 事件
40
+ },
69
41
  },
70
42
  ],
71
43
  },
72
44
  },
73
45
  };
74
- log(`[TRIGGER_HANDLER] 🚀 Dispatching A2A message to bot handler`);
75
- log(`[TRIGGER_HANDLER] - sessionId: ${sessionId}`);
76
- log(`[TRIGGER_HANDLER] - taskId: ${taskId} (NEW - will trigger steer mode if configured)`);
77
- log(`[TRIGGER_HANDLER] - messageId: ${messageId}`);
78
- // 透传给 openclaw 处理
79
- // 如果配置了 steer 模式,且该 sessionId 有活跃任务,这个新的 taskId 会覆盖旧的
46
+ log(`[TRIGGER_HANDLER] 🚀 Dispatching to handleXYMessage for processing`);
47
+ // 通过 handleXYMessage 处理(复用现有链路)
80
48
  await handleXYMessage({
81
49
  cfg,
82
50
  runtime,
83
51
  message: a2aMessage,
84
52
  accountId,
85
53
  });
86
- log(`[TRIGGER_HANDLER] ✅ Trigger event handled successfully`);
54
+ log(`[TRIGGER_HANDLER] ✅ Trigger event dispatched successfully`);
87
55
  }
88
56
  catch (err) {
89
57
  error(`[TRIGGER_HANDLER] ❌ Failed to handle Trigger event:`, err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.41-beta",
3
+ "version": "0.0.43-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",