@ynhcj/xiaoyi-channel 1.0.0 â 1.0.2
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 +15 -2
- package/dist/src/channel.js +2 -1
- package/dist/src/outbound.js +2 -2
- package/dist/src/parser.d.ts +5 -0
- package/dist/src/parser.js +15 -0
- package/dist/src/push.d.ts +7 -1
- package/dist/src/push.js +78 -18
- package/dist/src/tools/calendar-tool.d.ts +6 -0
- package/dist/src/tools/calendar-tool.js +167 -0
- package/dist/src/tools/location-tool.js +2 -2
- package/dist/src/tools/note-tool.js +2 -2
- package/dist/src/tools/search-note-tool.js +2 -2
- package/dist/src/tools/session-manager.js +7 -0
- package/dist/src/types.d.ts +0 -8
- package/dist/src/utils/config-manager.d.ts +26 -0
- package/dist/src/utils/config-manager.js +56 -0
- package/dist/src/websocket.js +34 -26
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getXYRuntime } from "./runtime.js";
|
|
2
2
|
import { createXYReplyDispatcher } from "./reply-dispatcher.js";
|
|
3
|
-
import { parseA2AMessage, extractTextFromParts, extractFileParts } from "./parser.js";
|
|
3
|
+
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId } from "./parser.js";
|
|
4
4
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
5
5
|
import { resolveXYConfig } from "./config.js";
|
|
6
6
|
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse } from "./formatter.js";
|
|
7
7
|
import { registerSession, unregisterSession } from "./tools/session-manager.js";
|
|
8
|
+
import { configManager } from "./utils/config-manager.js";
|
|
8
9
|
/**
|
|
9
10
|
* Handle an incoming A2A message.
|
|
10
11
|
* This is the main entry point for message processing.
|
|
@@ -55,6 +56,18 @@ export async function handleXYMessage(params) {
|
|
|
55
56
|
}
|
|
56
57
|
// Parse the A2A message (for regular messages)
|
|
57
58
|
const parsed = parseA2AMessage(message);
|
|
59
|
+
// Extract and update push_id if present
|
|
60
|
+
const pushId = extractPushId(parsed.parts);
|
|
61
|
+
if (pushId) {
|
|
62
|
+
log(`[BOT] đ Extracted push_id from user message`);
|
|
63
|
+
log(`[BOT] - Session ID: ${parsed.sessionId}`);
|
|
64
|
+
log(`[BOT] - Push ID preview: ${pushId.substring(0, 20)}...`);
|
|
65
|
+
log(`[BOT] - Full push_id: ${pushId}`);
|
|
66
|
+
configManager.updatePushId(parsed.sessionId, pushId);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
log(`[BOT] âšī¸ No push_id found in message, will use config default`);
|
|
70
|
+
}
|
|
58
71
|
// Resolve configuration (needed for status updates)
|
|
59
72
|
const config = resolveXYConfig(cfg);
|
|
60
73
|
// â
Resolve agent route (following feishu pattern)
|
|
@@ -99,7 +112,7 @@ export async function handleXYMessage(params) {
|
|
|
99
112
|
messageBody = `${speaker}: ${messageBody}`;
|
|
100
113
|
// Format agent envelope (following feishu pattern)
|
|
101
114
|
const body = core.channel.reply.formatAgentEnvelope({
|
|
102
|
-
channel: "
|
|
115
|
+
channel: "xiaoyi-channel",
|
|
103
116
|
from: speaker,
|
|
104
117
|
timestamp: new Date(),
|
|
105
118
|
envelope: envelopeOptions,
|
package/dist/src/channel.js
CHANGED
|
@@ -5,6 +5,7 @@ import { xyOnboardingAdapter } from "./onboarding.js";
|
|
|
5
5
|
import { locationTool } from "./tools/location-tool.js";
|
|
6
6
|
import { noteTool } from "./tools/note-tool.js";
|
|
7
7
|
import { searchNoteTool } from "./tools/search-note-tool.js";
|
|
8
|
+
import { calendarTool } from "./tools/calendar-tool.js";
|
|
8
9
|
/**
|
|
9
10
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
10
11
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -43,7 +44,7 @@ export const xyPlugin = {
|
|
|
43
44
|
},
|
|
44
45
|
outbound: xyOutbound,
|
|
45
46
|
onboarding: xyOnboardingAdapter,
|
|
46
|
-
agentTools: [locationTool, noteTool, searchNoteTool],
|
|
47
|
+
agentTools: [locationTool, noteTool, searchNoteTool, calendarTool],
|
|
47
48
|
messaging: {
|
|
48
49
|
normalizeTarget: (raw) => {
|
|
49
50
|
const trimmed = raw.trim();
|
package/dist/src/outbound.js
CHANGED
|
@@ -76,8 +76,8 @@ export const xyOutbound = {
|
|
|
76
76
|
const pushService = new XYPushService(config);
|
|
77
77
|
// Extract title (first 57 chars or first line)
|
|
78
78
|
const title = text.split("\n")[0].slice(0, 57);
|
|
79
|
-
// Send push message
|
|
80
|
-
await pushService.sendPush(text, title, actualTo);
|
|
79
|
+
// Send push message (content, title, data, sessionId)
|
|
80
|
+
await pushService.sendPush(text, title, undefined, actualTo);
|
|
81
81
|
console.log(`[xyOutbound.sendText] Completed successfully`);
|
|
82
82
|
// Return message info
|
|
83
83
|
return {
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -38,6 +38,11 @@ export declare function isClearContextMessage(method: string): boolean;
|
|
|
38
38
|
* Check if message is a tasks/cancel request.
|
|
39
39
|
*/
|
|
40
40
|
export declare function isTasksCancelMessage(method: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Extract push_id from message parts.
|
|
43
|
+
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
41
46
|
/**
|
|
42
47
|
* Validate A2A request structure.
|
|
43
48
|
*/
|
package/dist/src/parser.js
CHANGED
|
@@ -57,6 +57,21 @@ export function isClearContextMessage(method) {
|
|
|
57
57
|
export function isTasksCancelMessage(method) {
|
|
58
58
|
return method === "tasks/cancel" || method === "tasks_cancel";
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Extract push_id from message parts.
|
|
62
|
+
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
63
|
+
*/
|
|
64
|
+
export function extractPushId(parts) {
|
|
65
|
+
for (const part of parts) {
|
|
66
|
+
if (part.kind === "data" && part.data) {
|
|
67
|
+
const pushId = part.data.variables?.systemVariables?.push_id;
|
|
68
|
+
if (pushId && typeof pushId === "string") {
|
|
69
|
+
return pushId;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
60
75
|
/**
|
|
61
76
|
* Validate A2A request structure.
|
|
62
77
|
*/
|
package/dist/src/push.d.ts
CHANGED
|
@@ -5,11 +5,17 @@ import type { XYChannelConfig } from "./types.js";
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class XYPushService {
|
|
7
7
|
private config;
|
|
8
|
+
private readonly DEFAULT_PUSH_URL;
|
|
9
|
+
private readonly REQUEST_FROM;
|
|
8
10
|
constructor(config: XYChannelConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Generate a random trace ID for request tracking.
|
|
13
|
+
*/
|
|
14
|
+
private generateTraceId;
|
|
9
15
|
/**
|
|
10
16
|
* Send a push message to a user session.
|
|
11
17
|
*/
|
|
12
|
-
sendPush(content: string, title: string, sessionId?: string): Promise<void>;
|
|
18
|
+
sendPush(content: string, title: string, data?: Record<string, any>, sessionId?: string): Promise<void>;
|
|
13
19
|
/**
|
|
14
20
|
* Send a push message with file attachments.
|
|
15
21
|
*/
|
package/dist/src/push.js
CHANGED
|
@@ -1,46 +1,104 @@
|
|
|
1
1
|
// Push message service for scheduled tasks
|
|
2
2
|
import fetch from "node-fetch";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
3
4
|
import { logger } from "./utils/logger.js";
|
|
5
|
+
import { configManager } from "./utils/config-manager.js";
|
|
4
6
|
/**
|
|
5
7
|
* Service for sending push messages to users.
|
|
6
8
|
* Used for outbound messages and scheduled tasks.
|
|
7
9
|
*/
|
|
8
10
|
export class XYPushService {
|
|
9
11
|
config;
|
|
12
|
+
DEFAULT_PUSH_URL = "https://hag.cloud.huawei.com/open-ability-agent/v1/agent-webhook";
|
|
13
|
+
REQUEST_FROM = "openclaw";
|
|
10
14
|
constructor(config) {
|
|
11
15
|
this.config = config;
|
|
12
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate a random trace ID for request tracking.
|
|
19
|
+
*/
|
|
20
|
+
generateTraceId() {
|
|
21
|
+
return `trace-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
22
|
+
}
|
|
13
23
|
/**
|
|
14
24
|
* Send a push message to a user session.
|
|
15
25
|
*/
|
|
16
|
-
async sendPush(content, title, sessionId) {
|
|
17
|
-
const pushUrl = this.config.pushUrl ||
|
|
18
|
-
|
|
26
|
+
async sendPush(content, title, data, sessionId) {
|
|
27
|
+
const pushUrl = this.config.pushUrl || this.DEFAULT_PUSH_URL;
|
|
28
|
+
const traceId = this.generateTraceId();
|
|
29
|
+
// Get dynamic pushId for the session (falls back to config pushId)
|
|
30
|
+
const dynamicPushId = configManager.getPushId(sessionId);
|
|
31
|
+
const pushId = dynamicPushId || this.config.pushId;
|
|
32
|
+
logger.log(`[PUSH] đ¤ Preparing to send push message`);
|
|
33
|
+
logger.log(`[PUSH] - Title: "${title}"`);
|
|
34
|
+
logger.log(`[PUSH] - Content length: ${content.length} chars`);
|
|
35
|
+
logger.log(`[PUSH] - Session ID: ${sessionId || 'none'}`);
|
|
36
|
+
logger.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
37
|
+
logger.log(`[PUSH] - Push URL: ${pushUrl}`);
|
|
38
|
+
if (dynamicPushId) {
|
|
39
|
+
logger.log(`[PUSH] - Using dynamic pushId (from session): ${pushId.substring(0, 20)}...`);
|
|
40
|
+
logger.log(`[PUSH] - Full dynamic pushId: ${pushId}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
logger.log(`[PUSH] - Using config pushId (fallback): ${pushId.substring(0, 20)}...`);
|
|
44
|
+
logger.log(`[PUSH] - Full config pushId: ${pushId}`);
|
|
45
|
+
}
|
|
46
|
+
logger.log(`[PUSH] - API ID: ${this.config.apiId}`);
|
|
47
|
+
logger.log(`[PUSH] - UID: ${this.config.uid}`);
|
|
19
48
|
try {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
const requestBody = {
|
|
50
|
+
jsonrpc: "2.0",
|
|
51
|
+
id: randomUUID(),
|
|
52
|
+
result: {
|
|
53
|
+
id: randomUUID(),
|
|
54
|
+
apiId: this.config.apiId,
|
|
55
|
+
pushId: pushId, // Use dynamic pushId
|
|
56
|
+
pushText: title,
|
|
57
|
+
kind: "task",
|
|
58
|
+
artifacts: [
|
|
59
|
+
{
|
|
60
|
+
artifactId: randomUUID(),
|
|
61
|
+
parts: [
|
|
62
|
+
{
|
|
63
|
+
kind: "data",
|
|
64
|
+
data: data || { content },
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
},
|
|
27
70
|
};
|
|
71
|
+
logger.debug(`[PUSH] Full request body:`, JSON.stringify(requestBody, null, 2));
|
|
28
72
|
const response = await fetch(pushUrl, {
|
|
29
73
|
method: "POST",
|
|
30
74
|
headers: {
|
|
31
75
|
"Content-Type": "application/json",
|
|
76
|
+
"Accept": "application/json",
|
|
77
|
+
"x-hag-trace-id": traceId,
|
|
78
|
+
"x-uid": this.config.uid,
|
|
32
79
|
"x-api-key": this.config.apiKey,
|
|
33
|
-
"x-request-from":
|
|
80
|
+
"x-request-from": this.REQUEST_FROM,
|
|
34
81
|
},
|
|
35
|
-
body: JSON.stringify(
|
|
82
|
+
body: JSON.stringify(requestBody),
|
|
36
83
|
});
|
|
37
84
|
if (!response.ok) {
|
|
38
|
-
|
|
85
|
+
const errorText = await response.text();
|
|
86
|
+
logger.error(`[PUSH] â Push request failed`);
|
|
87
|
+
logger.error(`[PUSH] - HTTP Status: ${response.status}`);
|
|
88
|
+
logger.error(`[PUSH] - Error: ${errorText}`);
|
|
89
|
+
throw new Error(`Push failed: HTTP ${response.status} - ${errorText}`);
|
|
39
90
|
}
|
|
40
|
-
|
|
91
|
+
const result = await response.json();
|
|
92
|
+
logger.log(`[PUSH] â
Push message sent successfully`);
|
|
93
|
+
logger.log(`[PUSH] - Title: "${title}"`);
|
|
94
|
+
logger.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
95
|
+
logger.log(`[PUSH] - Used pushId: ${pushId.substring(0, 20)}...`);
|
|
96
|
+
logger.debug(`[PUSH] - Response:`, result);
|
|
41
97
|
}
|
|
42
98
|
catch (error) {
|
|
43
|
-
logger.error(
|
|
99
|
+
logger.error(`[PUSH] â Failed to send push message`);
|
|
100
|
+
logger.error(`[PUSH] - Trace ID: ${traceId}`);
|
|
101
|
+
logger.error(`[PUSH] - Error:`, error);
|
|
44
102
|
throw error;
|
|
45
103
|
}
|
|
46
104
|
}
|
|
@@ -48,8 +106,10 @@ export class XYPushService {
|
|
|
48
106
|
* Send a push message with file attachments.
|
|
49
107
|
*/
|
|
50
108
|
async sendPushWithFiles(content, title, fileIds, sessionId) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
109
|
+
const data = {
|
|
110
|
+
content,
|
|
111
|
+
fileIds,
|
|
112
|
+
};
|
|
113
|
+
await this.sendPush(content, title, data, sessionId);
|
|
54
114
|
}
|
|
55
115
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { getXYWebSocketManager } from "../client.js";
|
|
2
|
+
import { sendCommand } from "../formatter.js";
|
|
3
|
+
import { getLatestSessionContext } from "./session-manager.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* XY calendar event tool - creates a calendar event on user's device.
|
|
7
|
+
* Requires title, dtStart (start time), and dtEnd (end time) parameters.
|
|
8
|
+
* Time format must be: yyyy-mm-dd hh:mm:ss
|
|
9
|
+
*/
|
|
10
|
+
export const calendarTool = {
|
|
11
|
+
name: "create_calendar_event",
|
|
12
|
+
label: "Create Calendar Event",
|
|
13
|
+
description: "å¨į¨æˇčŽžå¤ä¸ååģēæĨį¨ãéčĻæäžæĨ፿ éĸãåŧå§æļé´åįģææļé´ãæļé´æ ŧåŧåŋ
éĄģä¸ēīŧyyyy-mm-dd hh:mm:ssīŧäžåĻīŧ2024-01-15 14:30:00īŧ",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
title: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "æĨ፿ éĸ/åį§°",
|
|
20
|
+
},
|
|
21
|
+
dtStart: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "æĨį¨åŧå§æļé´īŧæ ŧåŧåŋ
éĄģä¸ēīŧyyyy-mm-dd hh:mm:ssīŧäžåĻīŧ2024-01-15 14:30:00īŧ",
|
|
24
|
+
},
|
|
25
|
+
dtEnd: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "æĨį¨įģææļé´īŧæ ŧåŧåŋ
éĄģä¸ēīŧyyyy-mm-dd hh:mm:ssīŧäžåĻīŧ2024-01-15 17:30:00īŧ",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ["title", "dtStart", "dtEnd"],
|
|
31
|
+
},
|
|
32
|
+
async execute(toolCallId, params) {
|
|
33
|
+
logger.log(`[CALENDAR_TOOL] đ Starting execution`);
|
|
34
|
+
logger.log(`[CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
|
|
35
|
+
logger.log(`[CALENDAR_TOOL] - params:`, JSON.stringify(params));
|
|
36
|
+
logger.log(`[CALENDAR_TOOL] - timestamp: ${new Date().toISOString()}`);
|
|
37
|
+
// Validate parameters
|
|
38
|
+
if (!params.title || !params.dtStart || !params.dtEnd) {
|
|
39
|
+
logger.error(`[CALENDAR_TOOL] â Missing required parameters`);
|
|
40
|
+
throw new Error("Missing required parameters: title, dtStart, and dtEnd are required");
|
|
41
|
+
}
|
|
42
|
+
// Convert time strings to millisecond timestamps
|
|
43
|
+
logger.log(`[CALENDAR_TOOL] đ Converting time strings to timestamps...`);
|
|
44
|
+
logger.log(`[CALENDAR_TOOL] - dtStart input: ${params.dtStart}`);
|
|
45
|
+
logger.log(`[CALENDAR_TOOL] - dtEnd input: ${params.dtEnd}`);
|
|
46
|
+
const dtStartMs = new Date(params.dtStart).getTime();
|
|
47
|
+
const dtEndMs = new Date(params.dtEnd).getTime();
|
|
48
|
+
if (isNaN(dtStartMs) || isNaN(dtEndMs)) {
|
|
49
|
+
logger.error(`[CALENDAR_TOOL] â Invalid time format`);
|
|
50
|
+
throw new Error("Invalid time format. Required format: yyyy-mm-dd hh:mm:ss (e.g., 2024-01-15 14:30:00)");
|
|
51
|
+
}
|
|
52
|
+
logger.log(`[CALENDAR_TOOL] â
Time conversion successful`);
|
|
53
|
+
logger.log(`[CALENDAR_TOOL] - dtStart timestamp: ${dtStartMs}`);
|
|
54
|
+
logger.log(`[CALENDAR_TOOL] - dtEnd timestamp: ${dtEndMs}`);
|
|
55
|
+
// Get session context
|
|
56
|
+
logger.log(`[CALENDAR_TOOL] đ Attempting to get session context...`);
|
|
57
|
+
const sessionContext = getLatestSessionContext();
|
|
58
|
+
if (!sessionContext) {
|
|
59
|
+
logger.error(`[CALENDAR_TOOL] â FAILED: No active session found!`);
|
|
60
|
+
logger.error(`[CALENDAR_TOOL] - toolCallId: ${toolCallId}`);
|
|
61
|
+
throw new Error("No active XY session found. Calendar tool can only be used during an active conversation.");
|
|
62
|
+
}
|
|
63
|
+
logger.log(`[CALENDAR_TOOL] â
Session context found`);
|
|
64
|
+
logger.log(`[CALENDAR_TOOL] - sessionId: ${sessionContext.sessionId}`);
|
|
65
|
+
logger.log(`[CALENDAR_TOOL] - taskId: ${sessionContext.taskId}`);
|
|
66
|
+
logger.log(`[CALENDAR_TOOL] - messageId: ${sessionContext.messageId}`);
|
|
67
|
+
const { config, sessionId, taskId, messageId } = sessionContext;
|
|
68
|
+
// Get WebSocket manager
|
|
69
|
+
logger.log(`[CALENDAR_TOOL] đ Getting WebSocket manager...`);
|
|
70
|
+
const wsManager = getXYWebSocketManager(config);
|
|
71
|
+
logger.log(`[CALENDAR_TOOL] â
WebSocket manager obtained`);
|
|
72
|
+
// Build CreateCalendarEvent command
|
|
73
|
+
logger.log(`[CALENDAR_TOOL] đĻ Building CreateCalendarEvent command...`);
|
|
74
|
+
const command = {
|
|
75
|
+
header: {
|
|
76
|
+
namespace: "Common",
|
|
77
|
+
name: "Action",
|
|
78
|
+
},
|
|
79
|
+
payload: {
|
|
80
|
+
cardParam: {},
|
|
81
|
+
executeParam: {
|
|
82
|
+
executeMode: "background",
|
|
83
|
+
intentName: "CreateCalendarEvent",
|
|
84
|
+
bundleName: "com.huawei.hmos.calendardata",
|
|
85
|
+
dimension: "",
|
|
86
|
+
needUnlock: true,
|
|
87
|
+
actionResponse: true,
|
|
88
|
+
timeOut: 5,
|
|
89
|
+
intentParam: {
|
|
90
|
+
title: params.title,
|
|
91
|
+
dtStart: dtStartMs,
|
|
92
|
+
dtEnd: dtEndMs,
|
|
93
|
+
},
|
|
94
|
+
achieveType: "INTENT",
|
|
95
|
+
},
|
|
96
|
+
responses: [
|
|
97
|
+
{
|
|
98
|
+
resultCode: "",
|
|
99
|
+
displayText: "",
|
|
100
|
+
ttsText: "",
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
needUploadResult: true,
|
|
104
|
+
noHalfPage: false,
|
|
105
|
+
pageControlRelated: false,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
// Send command and wait for response (5 second timeout)
|
|
109
|
+
logger.log(`[CALENDAR_TOOL] âŗ Setting up promise to wait for calendar event response...`);
|
|
110
|
+
logger.log(`[CALENDAR_TOOL] - Timeout: 5 seconds`);
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const timeout = setTimeout(() => {
|
|
113
|
+
logger.error(`[CALENDAR_TOOL] â° Timeout: No response received within 5 seconds`);
|
|
114
|
+
wsManager.off("data-event", handler);
|
|
115
|
+
reject(new Error("ååģēæĨį¨čļ
æļīŧ15į§īŧ"));
|
|
116
|
+
}, 15000);
|
|
117
|
+
// Listen for data events from WebSocket
|
|
118
|
+
const handler = (event) => {
|
|
119
|
+
logger.log(`[CALENDAR_TOOL] đ¨ Received data event:`, JSON.stringify(event));
|
|
120
|
+
if (event.intentName === "CreateCalendarEvent") {
|
|
121
|
+
logger.log(`[CALENDAR_TOOL] đ¯ CreateCalendarEvent event received`);
|
|
122
|
+
logger.log(`[CALENDAR_TOOL] - status: ${event.status}`);
|
|
123
|
+
clearTimeout(timeout);
|
|
124
|
+
wsManager.off("data-event", handler);
|
|
125
|
+
if (event.status === "success" && event.outputs) {
|
|
126
|
+
logger.log(`[CALENDAR_TOOL] â
Calendar event created successfully`);
|
|
127
|
+
logger.log(`[CALENDAR_TOOL] - outputs:`, JSON.stringify(event.outputs));
|
|
128
|
+
resolve({
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: JSON.stringify(event.outputs),
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
logger.error(`[CALENDAR_TOOL] â Calendar event creation failed`);
|
|
139
|
+
logger.error(`[CALENDAR_TOOL] - status: ${event.status}`);
|
|
140
|
+
reject(new Error(`ååģēæĨį¨å¤ąč´Ĩ: ${event.status}`));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
// Register event handler
|
|
145
|
+
logger.log(`[CALENDAR_TOOL] đĄ Registering data-event handler on WebSocket manager`);
|
|
146
|
+
wsManager.on("data-event", handler);
|
|
147
|
+
// Send the command
|
|
148
|
+
logger.log(`[CALENDAR_TOOL] đ¤ Sending CreateCalendarEvent command...`);
|
|
149
|
+
sendCommand({
|
|
150
|
+
config,
|
|
151
|
+
sessionId,
|
|
152
|
+
taskId,
|
|
153
|
+
messageId,
|
|
154
|
+
command,
|
|
155
|
+
})
|
|
156
|
+
.then(() => {
|
|
157
|
+
logger.log(`[CALENDAR_TOOL] â
Command sent successfully, waiting for response...`);
|
|
158
|
+
})
|
|
159
|
+
.catch((error) => {
|
|
160
|
+
logger.error(`[CALENDAR_TOOL] â Failed to send command:`, error);
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
wsManager.off("data-event", handler);
|
|
163
|
+
reject(error);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
};
|
|
@@ -76,8 +76,8 @@ export const locationTool = {
|
|
|
76
76
|
const timeout = setTimeout(() => {
|
|
77
77
|
logger.error(`[LOCATION_TOOL] â° Timeout: No response received within 5 seconds`);
|
|
78
78
|
wsManager.off("data-event", handler);
|
|
79
|
-
reject(new Error("čˇåäŊįŊŽčļ
æļīŧ
|
|
80
|
-
},
|
|
79
|
+
reject(new Error("čˇåäŊįŊŽčļ
æļīŧ10į§īŧ"));
|
|
80
|
+
}, 10000);
|
|
81
81
|
// Listen for data events from WebSocket
|
|
82
82
|
const handler = (event) => {
|
|
83
83
|
logger.log(`[LOCATION_TOOL] đ¨ Received data event:`, JSON.stringify(event));
|
|
@@ -76,8 +76,8 @@ export const noteTool = {
|
|
|
76
76
|
return new Promise((resolve, reject) => {
|
|
77
77
|
const timeout = setTimeout(() => {
|
|
78
78
|
wsManager.off("data-event", handler);
|
|
79
|
-
reject(new Error("ååģēå¤åŋåŊčļ
æļīŧ
|
|
80
|
-
},
|
|
79
|
+
reject(new Error("ååģēå¤åŋåŊčļ
æļīŧ10į§īŧ"));
|
|
80
|
+
}, 10000);
|
|
81
81
|
// Listen for data events from WebSocket
|
|
82
82
|
const handler = (event) => {
|
|
83
83
|
logger.debug("Received data event:", event);
|
|
@@ -71,8 +71,8 @@ export const searchNoteTool = {
|
|
|
71
71
|
return new Promise((resolve, reject) => {
|
|
72
72
|
const timeout = setTimeout(() => {
|
|
73
73
|
wsManager.off("data-event", handler);
|
|
74
|
-
reject(new Error("æį´ĸå¤åŋåŊčļ
æļīŧ
|
|
75
|
-
},
|
|
74
|
+
reject(new Error("æį´ĸå¤åŋåŊčļ
æļīŧ15į§īŧ"));
|
|
75
|
+
}, 15000);
|
|
76
76
|
// Listen for data events from WebSocket
|
|
77
77
|
const handler = (event) => {
|
|
78
78
|
logger.debug("Received data event:", event);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.js";
|
|
2
|
+
import { configManager } from "../utils/config-manager.js";
|
|
2
3
|
// Map of sessionKey -> SessionContext
|
|
3
4
|
const activeSessions = new Map();
|
|
4
5
|
/**
|
|
@@ -24,7 +25,13 @@ export function unregisterSession(sessionKey) {
|
|
|
24
25
|
logger.log(`[SESSION_MANAGER] đī¸ Unregistering session: ${sessionKey}`);
|
|
25
26
|
logger.log(`[SESSION_MANAGER] - Active sessions before: ${activeSessions.size}`);
|
|
26
27
|
logger.log(`[SESSION_MANAGER] - Session existed: ${activeSessions.has(sessionKey)}`);
|
|
28
|
+
// Get session context before deleting to clear associated pushId
|
|
29
|
+
const context = activeSessions.get(sessionKey);
|
|
27
30
|
const existed = activeSessions.delete(sessionKey);
|
|
31
|
+
// Clear cached pushId for this session
|
|
32
|
+
if (context) {
|
|
33
|
+
configManager.clearSession(context.sessionId);
|
|
34
|
+
}
|
|
28
35
|
logger.log(`[SESSION_MANAGER] - Deleted: ${existed}`);
|
|
29
36
|
logger.log(`[SESSION_MANAGER] - Active sessions after: ${activeSessions.size}`);
|
|
30
37
|
logger.log(`[SESSION_MANAGER] - Remaining session keys: [${Array.from(activeSessions.keys()).join(", ")}]`);
|
package/dist/src/types.d.ts
CHANGED
|
@@ -155,14 +155,6 @@ export interface FileUploadCompleteRequest {
|
|
|
155
155
|
export interface FileUploadCompleteResponse {
|
|
156
156
|
fileId: string;
|
|
157
157
|
}
|
|
158
|
-
export interface PushMessageRequest {
|
|
159
|
-
apiKey: string;
|
|
160
|
-
apiId: string;
|
|
161
|
-
pushId: string;
|
|
162
|
-
sessionId: string;
|
|
163
|
-
title: string;
|
|
164
|
-
content: string;
|
|
165
|
-
}
|
|
166
158
|
export type ServerIdentifier = "server1" | "server2";
|
|
167
159
|
export interface SessionBinding {
|
|
168
160
|
sessionId: string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages dynamic configuration updates that can change at runtime.
|
|
3
|
+
* Specifically handles pushId which can be updated per-session.
|
|
4
|
+
*/
|
|
5
|
+
declare class ConfigManager {
|
|
6
|
+
private sessionPushIds;
|
|
7
|
+
private globalPushId;
|
|
8
|
+
/**
|
|
9
|
+
* Update push ID for a specific session.
|
|
10
|
+
*/
|
|
11
|
+
updatePushId(sessionId: string, pushId: string): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get push ID for a session (falls back to global if not found).
|
|
14
|
+
*/
|
|
15
|
+
getPushId(sessionId?: string): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Clear push ID for a session.
|
|
18
|
+
*/
|
|
19
|
+
clearSession(sessionId: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Clear all cached push IDs.
|
|
22
|
+
*/
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare const configManager: ConfigManager;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Dynamic configuration manager for runtime updates
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* Manages dynamic configuration updates that can change at runtime.
|
|
5
|
+
* Specifically handles pushId which can be updated per-session.
|
|
6
|
+
*/
|
|
7
|
+
class ConfigManager {
|
|
8
|
+
sessionPushIds = new Map();
|
|
9
|
+
globalPushId = null;
|
|
10
|
+
/**
|
|
11
|
+
* Update push ID for a specific session.
|
|
12
|
+
*/
|
|
13
|
+
updatePushId(sessionId, pushId) {
|
|
14
|
+
if (!pushId) {
|
|
15
|
+
logger.warn(`[ConfigManager] Attempted to set empty pushId for session ${sessionId}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const previous = this.sessionPushIds.get(sessionId);
|
|
19
|
+
if (previous !== pushId) {
|
|
20
|
+
logger.log(`[ConfigManager] ⨠Updated pushId for session ${sessionId}`);
|
|
21
|
+
logger.log(`[ConfigManager] - Previous: ${previous ? previous.substring(0, 20) + '...' : 'none'}`);
|
|
22
|
+
logger.log(`[ConfigManager] - New: ${pushId.substring(0, 20)}...`);
|
|
23
|
+
logger.log(`[ConfigManager] - Full new pushId: ${pushId}`);
|
|
24
|
+
this.sessionPushIds.set(sessionId, pushId);
|
|
25
|
+
this.globalPushId = pushId; // Also update global for backward compatibility
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get push ID for a session (falls back to global if not found).
|
|
30
|
+
*/
|
|
31
|
+
getPushId(sessionId) {
|
|
32
|
+
if (sessionId) {
|
|
33
|
+
const sessionPushId = this.sessionPushIds.get(sessionId);
|
|
34
|
+
if (sessionPushId) {
|
|
35
|
+
return sessionPushId;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this.globalPushId;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Clear push ID for a session.
|
|
42
|
+
*/
|
|
43
|
+
clearSession(sessionId) {
|
|
44
|
+
this.sessionPushIds.delete(sessionId);
|
|
45
|
+
logger.debug(`[ConfigManager] Cleared pushId for session ${sessionId}`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Clear all cached push IDs.
|
|
49
|
+
*/
|
|
50
|
+
clear() {
|
|
51
|
+
this.sessionPushIds.clear();
|
|
52
|
+
this.globalPushId = null;
|
|
53
|
+
logger.debug(`[ConfigManager] Cleared all cached pushIds`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export const configManager = new ConfigManager();
|
package/dist/src/websocket.js
CHANGED
|
@@ -317,19 +317,23 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
317
317
|
// Only emit data-event, don't send to openclaw
|
|
318
318
|
console.log(`[XY-${serverId}] Message contains only data parts, processing as tool result`);
|
|
319
319
|
for (const dataPart of dataParts) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
320
|
+
// Data format: {events: [{header, payload}, ...]}
|
|
321
|
+
const events = dataPart.data?.events;
|
|
322
|
+
if (!Array.isArray(events)) {
|
|
323
|
+
console.warn(`[XY-${serverId}] dataPart.data.events is not an array, skipping`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
console.log(`[XY-${serverId}] Processing ${events.length} events from data.events`);
|
|
327
|
+
for (const item of events) {
|
|
328
|
+
// Check if it's an UploadExeResult (intent execution result)
|
|
329
|
+
if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
|
|
330
|
+
const dataEvent = {
|
|
331
|
+
intentName: item.payload.intentName,
|
|
332
|
+
outputs: item.payload.outputs || {},
|
|
333
|
+
status: "success",
|
|
334
|
+
};
|
|
335
|
+
console.log(`[XY-${serverId}] Emitting data-event:`, dataEvent);
|
|
336
|
+
this.emit("data-event", dataEvent);
|
|
333
337
|
}
|
|
334
338
|
}
|
|
335
339
|
}
|
|
@@ -356,19 +360,23 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
356
360
|
const dataParts = a2aRequest.params?.message?.parts?.filter((p) => p.kind === "data");
|
|
357
361
|
if (dataParts && dataParts.length > 0) {
|
|
358
362
|
for (const dataPart of dataParts) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
363
|
+
// Data format: {events: [{header, payload}, ...]}
|
|
364
|
+
const events = dataPart.data?.events;
|
|
365
|
+
if (!Array.isArray(events)) {
|
|
366
|
+
console.warn(`[XY-${serverId}] dataPart.data.events is not an array, skipping`);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
console.log(`[XY-${serverId}] Processing ${events.length} events from data.events`);
|
|
370
|
+
for (const item of events) {
|
|
371
|
+
// Check if it's an UploadExeResult (intent execution result)
|
|
372
|
+
if (item.header?.name === "UploadExeResult" && item.payload?.intentName) {
|
|
373
|
+
const dataEvent = {
|
|
374
|
+
intentName: item.payload.intentName,
|
|
375
|
+
outputs: item.payload.outputs || {},
|
|
376
|
+
status: "success",
|
|
377
|
+
};
|
|
378
|
+
console.log(`[XY-${serverId}] Emitting data-event:`, dataEvent);
|
|
379
|
+
this.emit("data-event", dataEvent);
|
|
372
380
|
}
|
|
373
381
|
}
|
|
374
382
|
}
|