@ynhcj/xiaoyi-channel 0.0.42-beta → 0.0.44-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
+ }
@@ -147,8 +147,8 @@ export const xyOutbound = {
147
147
  for (const pushId of pushIdList) {
148
148
  try {
149
149
  console.log(`[xyOutbound.sendText] Sending to pushId: ${pushId.substring(0, 20)}...`);
150
- // 传入 pushDataId,使用 kind="data" 格式
151
- await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId);
150
+ // 传入 pushId 和 pushDataId,使用 kind="data" 格式
151
+ await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId, pushId);
152
152
  successCount++;
153
153
  console.log(`[xyOutbound.sendText] ✅ Sent successfully to pushId: ${pushId.substring(0, 20)}...`);
154
154
  }
@@ -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
  */
@@ -20,8 +20,9 @@ export declare class XYPushService {
20
20
  * @param data - Optional additional data
21
21
  * @param sessionId - Optional session ID
22
22
  * @param pushDataId - Optional pushDataId for kind="data" format
23
+ * @param pushId - Push ID to use (required)
23
24
  */
24
- sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string, pushDataId?: string): Promise<void>;
25
+ sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string, pushDataId?: string, pushId?: string): Promise<void>;
25
26
  /**
26
27
  * Send a push message with file attachments.
27
28
  */
package/dist/src/push.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // Push message service for scheduled tasks
2
2
  import fetch from "node-fetch";
3
3
  import { randomUUID } from "crypto";
4
- import { configManager } from "./utils/config-manager.js";
5
4
  /**
6
5
  * Service for sending push messages to users.
7
6
  * Used for outbound messages and scheduled tasks.
@@ -27,27 +26,21 @@ export class XYPushService {
27
26
  * @param data - Optional additional data
28
27
  * @param sessionId - Optional session ID
29
28
  * @param pushDataId - Optional pushDataId for kind="data" format
29
+ * @param pushId - Push ID to use (required)
30
30
  */
31
- async sendPush(content, title, data, sessionId, pushDataId) {
31
+ async sendPush(content, title, data, sessionId, pushDataId, pushId) {
32
32
  const pushUrl = this.config.pushUrl || this.DEFAULT_PUSH_URL;
33
33
  const traceId = this.generateTraceId();
34
- // Get dynamic pushId for the session (falls back to config pushId)
35
- const dynamicPushId = configManager.getPushId(sessionId);
36
- const pushId = dynamicPushId || this.config.pushId;
34
+ // Use provided pushId or fall back to config pushId
35
+ const actualPushId = pushId || this.config.pushId;
37
36
  console.log(`[PUSH] 📤 Preparing to send push message`);
38
37
  console.log(`[PUSH] - Title: "${title}"`);
39
38
  console.log(`[PUSH] - Content length: ${content.length} chars`);
40
39
  console.log(`[PUSH] - Session ID: ${sessionId || 'none'}`);
41
40
  console.log(`[PUSH] - Trace ID: ${traceId}`);
42
41
  console.log(`[PUSH] - Push URL: ${pushUrl}`);
43
- if (dynamicPushId) {
44
- console.log(`[PUSH] - Using dynamic pushId (from session): ${pushId.substring(0, 20)}...`);
45
- console.log(`[PUSH] - Full dynamic pushId: ${pushId}`);
46
- }
47
- else {
48
- console.log(`[PUSH] - Using config pushId (fallback): ${pushId.substring(0, 20)}...`);
49
- console.log(`[PUSH] - Full config pushId: ${pushId}`);
50
- }
42
+ console.log(`[PUSH] - Using pushId: ${actualPushId.substring(0, 20)}...`);
43
+ console.log(`[PUSH] - Full pushId: ${actualPushId}`);
51
44
  console.log(`[PUSH] - API ID: ${this.config.apiId}`);
52
45
  console.log(`[PUSH] - UID: ${this.config.uid}`);
53
46
  try {
@@ -57,7 +50,7 @@ export class XYPushService {
57
50
  result: {
58
51
  id: randomUUID(),
59
52
  apiId: this.config.apiId,
60
- pushId: pushId, // Use dynamic pushId
53
+ pushId: actualPushId,
61
54
  pushText: title,
62
55
  kind: "task",
63
56
  artifacts: [
@@ -129,14 +122,14 @@ export class XYPushService {
129
122
  console.log(`[PUSH] ✅ Push message sent successfully`);
130
123
  console.log(`[PUSH] - Title: "${title}"`);
131
124
  console.log(`[PUSH] - Trace ID: ${traceId}`);
132
- console.log(`[PUSH] - Used pushId: ${pushId.substring(0, 20)}...`);
125
+ console.log(`[PUSH] - Used pushId: ${actualPushId.substring(0, 20)}...`);
133
126
  console.log(`[PUSH] - Response:`, result);
134
127
  }
135
128
  catch (error) {
136
129
  console.log(`[PUSH] ❌ Failed to send push message`);
137
130
  console.log(`[PUSH] - Trace ID: ${traceId}`);
138
131
  console.log(`[PUSH] - Target URL: ${pushUrl}`);
139
- console.log(`[PUSH] - Push ID: ${pushId.substring(0, 20)}...`);
132
+ console.log(`[PUSH] - Push ID: ${actualPushId.substring(0, 20)}...`);
140
133
  if (error instanceof Error) {
141
134
  console.log(`[PUSH] - Error name: ${error.name}`);
142
135
  console.log(`[PUSH] - Error message: ${error.message}`);
@@ -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,12 +1,11 @@
1
- // Trigger 事件处理器
2
- import { randomUUID } from "crypto";
3
- import { getPushDataById } from "./utils/pushdata-manager.js";
4
- import { resolveXYConfig } from "./config.js";
5
- import { getXYWebSocketManager } from "./client.js";
1
+ import { handleXYMessage } from "./bot.js";
6
2
  /**
7
3
  * 处理 Trigger 事件
8
4
  * 当用户在手机侧点击推送消息时触发
9
5
  *
6
+ * 策略:构造一个包含 Trigger 事件的 A2A 消息,通过 handleXYMessage 处理
7
+ * 这样可以复用现有的消息链路和 runtime 初始化
8
+ *
10
9
  * @param context - Trigger 事件上下文(包含 event, sessionId, taskId)
11
10
  * @param cfg - OpenClaw 配置
12
11
  * @param runtime - 运行时环境
@@ -18,77 +17,41 @@ export async function handleTriggerEvent(context, cfg, runtime, accountId) {
18
17
  try {
19
18
  const { event, sessionId, taskId } = context;
20
19
  log(`[TRIGGER_HANDLER] 📌 Received Trigger event`);
21
- log(`[TRIGGER_HANDLER] Event:`, JSON.stringify(event, null, 2));
22
- log(`[TRIGGER_HANDLER] Context - sessionId: ${sessionId}, taskId: ${taskId}`);
23
- if (!sessionId) {
24
- error(`[TRIGGER_HANDLER] Missing sessionId in context`);
25
- return;
26
- }
27
- if (!taskId) {
28
- error(`[TRIGGER_HANDLER] ❌ Missing taskId in context`);
29
- return;
30
- }
31
- // 从 event.payload.dataMap 中提取 pushDataId
32
- const pushDataId = event.payload?.dataMap?.pushDataId;
33
- if (!pushDataId) {
34
- error(`[TRIGGER_HANDLER] ❌ Missing pushDataId in Trigger event payload`);
35
- return;
36
- }
37
- log(`[TRIGGER_HANDLER] 🔍 Looking up pushDataId: ${pushDataId}`);
38
- // 根据 pushDataId 查询原始数据
39
- const pushDataItem = await getPushDataById(pushDataId);
40
- if (!pushDataItem) {
41
- error(`[TRIGGER_HANDLER] ❌ pushData not found for ID: ${pushDataId}`);
42
- return;
43
- }
44
- log(`[TRIGGER_HANDLER] ✅ Found pushData`);
45
- log(`[TRIGGER_HANDLER] - pushDataId: ${pushDataItem.pushDataId}`);
46
- log(`[TRIGGER_HANDLER] - time: ${pushDataItem.time}`);
47
- log(`[TRIGGER_HANDLER] - dataDetail length: ${pushDataItem.dataDetail.length} chars`);
48
- // ==================== 新逻辑:直接返回结果 ====================
49
- log(`[TRIGGER_HANDLER] 📤 Directly responding with pushData content`);
50
- // 获取配置
51
- const config = resolveXYConfig(cfg);
52
- // 构造 A2A Response(final=true,直接结束)
53
- const messageId = randomUUID();
54
- const response = {
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 检测并处理
25
+ const a2aMessage = {
55
26
  jsonrpc: "2.0",
56
- id: messageId,
57
- result: {
58
- taskId: taskId,
59
- kind: "artifact-update",
60
- append: false,
61
- lastChunk: true,
62
- final: true, // 直接结束
63
- artifact: {
64
- artifactId: randomUUID(),
27
+ method: "sendMessage",
28
+ id: taskId,
29
+ params: {
30
+ id: taskId,
31
+ sessionId: sessionId,
32
+ agentLoginSessionId: "",
33
+ message: {
34
+ role: "user",
65
35
  parts: [
66
36
  {
67
- kind: "text",
68
- text: pushDataItem.dataDetail, // 直接返回原始内容
37
+ kind: "data",
38
+ data: {
39
+ events: [event], // 包含 Trigger 事件
40
+ },
69
41
  },
70
42
  ],
71
43
  },
72
44
  },
73
- error: { code: 0 },
74
45
  };
75
- // 构造 WebSocket 消息
76
- const outboundMessage = {
77
- msgType: "agent_response",
78
- agentId: config.agentId,
79
- sessionId: sessionId,
80
- taskId: taskId,
81
- msgDetail: JSON.stringify(response),
82
- };
83
- log(`[TRIGGER_HANDLER] 📦 Sending direct response:`);
84
- log(`[TRIGGER_HANDLER] - sessionId: ${sessionId}`);
85
- log(`[TRIGGER_HANDLER] - taskId: ${taskId}`);
86
- log(`[TRIGGER_HANDLER] - messageId: ${messageId}`);
87
- log(`[TRIGGER_HANDLER] - content length: ${pushDataItem.dataDetail.length} chars`);
88
- // 发送消息
89
- const wsManager = getXYWebSocketManager(config);
90
- await wsManager.sendMessage(sessionId, outboundMessage);
91
- log(`[TRIGGER_HANDLER] ✅ Direct response sent successfully`);
46
+ log(`[TRIGGER_HANDLER] 🚀 Dispatching to handleXYMessage for processing`);
47
+ // 通过 handleXYMessage 处理(复用现有链路)
48
+ await handleXYMessage({
49
+ cfg,
50
+ runtime,
51
+ message: a2aMessage,
52
+ accountId,
53
+ });
54
+ log(`[TRIGGER_HANDLER] Trigger event dispatched successfully`);
92
55
  }
93
56
  catch (err) {
94
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.42-beta",
3
+ "version": "0.0.44-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",