@ynhcj/xiaoyi 2.5.5 → 2.5.7
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/auth.d.ts +1 -1
- package/dist/channel.d.ts +116 -14
- package/dist/channel.js +199 -665
- package/dist/config-schema.d.ts +8 -8
- package/dist/config-schema.js +5 -5
- package/dist/file-download.d.ts +17 -0
- package/dist/file-download.js +69 -0
- package/dist/heartbeat.d.ts +39 -0
- package/dist/heartbeat.js +102 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.js +7 -11
- package/dist/push.d.ts +28 -0
- package/dist/push.js +135 -0
- package/dist/runtime.d.ts +48 -2
- package/dist/runtime.js +117 -3
- package/dist/types.d.ts +95 -1
- package/dist/websocket.d.ts +49 -1
- package/dist/websocket.js +279 -20
- package/dist/xy-bot.d.ts +19 -0
- package/dist/xy-bot.js +277 -0
- package/dist/xy-client.d.ts +26 -0
- package/dist/xy-client.js +78 -0
- package/dist/xy-config.d.ts +18 -0
- package/dist/xy-config.js +37 -0
- package/dist/xy-formatter.d.ts +94 -0
- package/dist/xy-formatter.js +303 -0
- package/dist/xy-monitor.d.ts +17 -0
- package/dist/xy-monitor.js +187 -0
- package/dist/xy-parser.d.ts +49 -0
- package/dist/xy-parser.js +109 -0
- package/dist/xy-reply-dispatcher.d.ts +17 -0
- package/dist/xy-reply-dispatcher.js +308 -0
- package/dist/xy-tools/session-manager.d.ts +29 -0
- package/dist/xy-tools/session-manager.js +80 -0
- package/dist/xy-utils/config-manager.d.ts +26 -0
- package/dist/xy-utils/config-manager.js +61 -0
- package/dist/xy-utils/crypto.d.ts +8 -0
- package/dist/xy-utils/crypto.js +21 -0
- package/dist/xy-utils/logger.d.ts +6 -0
- package/dist/xy-utils/logger.js +37 -0
- package/dist/xy-utils/session.d.ts +34 -0
- package/dist/xy-utils/session.js +55 -0
- package/package.json +32 -16
package/dist/config-schema.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ export declare const XiaoYiConfigSchema: z.ZodObject<{
|
|
|
8
8
|
name: z.ZodOptional<z.ZodString>;
|
|
9
9
|
/** Whether this channel is enabled */
|
|
10
10
|
enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
11
|
-
/** WebSocket URL
|
|
12
|
-
|
|
11
|
+
/** First WebSocket server URL */
|
|
12
|
+
wsUrl1: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
13
|
+
/** Second WebSocket server URL */
|
|
14
|
+
wsUrl2: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
13
15
|
/** Access Key for authentication */
|
|
14
16
|
ak: z.ZodOptional<z.ZodString>;
|
|
15
17
|
/** Secret Key for authentication */
|
|
@@ -18,27 +20,25 @@ export declare const XiaoYiConfigSchema: z.ZodObject<{
|
|
|
18
20
|
agentId: z.ZodOptional<z.ZodString>;
|
|
19
21
|
/** Enable debug logging */
|
|
20
22
|
debug: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
21
|
-
/** Enable streaming responses (default: false) */
|
|
22
|
-
enableStreaming: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
23
23
|
/** Multi-account configuration */
|
|
24
24
|
accounts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
25
25
|
}, "strip", z.ZodTypeAny, {
|
|
26
26
|
enabled?: boolean;
|
|
27
|
-
|
|
27
|
+
wsUrl1?: string;
|
|
28
|
+
wsUrl2?: string;
|
|
28
29
|
ak?: string;
|
|
29
30
|
sk?: string;
|
|
30
31
|
agentId?: string;
|
|
31
|
-
enableStreaming?: boolean;
|
|
32
32
|
name?: string;
|
|
33
33
|
debug?: boolean;
|
|
34
34
|
accounts?: Record<string, unknown>;
|
|
35
35
|
}, {
|
|
36
36
|
enabled?: boolean;
|
|
37
|
-
|
|
37
|
+
wsUrl1?: string;
|
|
38
|
+
wsUrl2?: string;
|
|
38
39
|
ak?: string;
|
|
39
40
|
sk?: string;
|
|
40
41
|
agentId?: string;
|
|
41
|
-
enableStreaming?: boolean;
|
|
42
42
|
name?: string;
|
|
43
43
|
debug?: boolean;
|
|
44
44
|
accounts?: Record<string, unknown>;
|
package/dist/config-schema.js
CHANGED
|
@@ -10,9 +10,11 @@ exports.XiaoYiConfigSchema = zod_1.z.object({
|
|
|
10
10
|
/** Account name (optional display name) */
|
|
11
11
|
name: zod_1.z.string().optional(),
|
|
12
12
|
/** Whether this channel is enabled */
|
|
13
|
-
enabled: zod_1.z.boolean().optional().default(
|
|
14
|
-
/** WebSocket URL
|
|
15
|
-
|
|
13
|
+
enabled: zod_1.z.boolean().optional().default(false),
|
|
14
|
+
/** First WebSocket server URL */
|
|
15
|
+
wsUrl1: zod_1.z.string().optional().default("wss://hag.cloud.huawei.com/openclaw/v1/ws/link"),
|
|
16
|
+
/** Second WebSocket server URL */
|
|
17
|
+
wsUrl2: zod_1.z.string().optional().default("wss://116.63.174.231/openclaw/v1/ws/link"),
|
|
16
18
|
/** Access Key for authentication */
|
|
17
19
|
ak: zod_1.z.string().optional(),
|
|
18
20
|
/** Secret Key for authentication */
|
|
@@ -21,8 +23,6 @@ exports.XiaoYiConfigSchema = zod_1.z.object({
|
|
|
21
23
|
agentId: zod_1.z.string().optional(),
|
|
22
24
|
/** Enable debug logging */
|
|
23
25
|
debug: zod_1.z.boolean().optional().default(false),
|
|
24
|
-
/** Enable streaming responses (default: false) */
|
|
25
|
-
enableStreaming: zod_1.z.boolean().optional().default(false),
|
|
26
26
|
/** Multi-account configuration */
|
|
27
27
|
accounts: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
28
28
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download a file from URL to local path.
|
|
3
|
+
*/
|
|
4
|
+
export declare function downloadFile(url: string, destPath: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Download files from A2A file parts.
|
|
7
|
+
* Returns array of local file paths.
|
|
8
|
+
*/
|
|
9
|
+
export declare function downloadFilesFromParts(fileParts: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
uri: string;
|
|
13
|
+
}>, tempDir?: string): Promise<Array<{
|
|
14
|
+
path: string;
|
|
15
|
+
name: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
}>>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.downloadFile = downloadFile;
|
|
7
|
+
exports.downloadFilesFromParts = downloadFilesFromParts;
|
|
8
|
+
// File download utilities
|
|
9
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const logger_js_1 = require("./xy-utils/logger.js");
|
|
13
|
+
/**
|
|
14
|
+
* Download a file from URL to local path.
|
|
15
|
+
*/
|
|
16
|
+
async function downloadFile(url, destPath) {
|
|
17
|
+
logger_js_1.logger.debug(`Downloading file from ${url} to ${destPath}`);
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timeout = setTimeout(() => controller.abort(), 30000); // 30 seconds timeout
|
|
20
|
+
try {
|
|
21
|
+
const response = await (0, node_fetch_1.default)(url, { signal: controller.signal });
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
24
|
+
}
|
|
25
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
26
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
27
|
+
await promises_1.default.writeFile(destPath, buffer);
|
|
28
|
+
logger_js_1.logger.debug(`File downloaded successfully: ${destPath}`);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.name === 'AbortError') {
|
|
32
|
+
logger_js_1.logger.error(`Download timeout (30s) for ${url}`);
|
|
33
|
+
throw new Error(`Download timeout after 30 seconds`);
|
|
34
|
+
}
|
|
35
|
+
logger_js_1.logger.error(`Failed to download file from ${url}:`, error);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(timeout);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Download files from A2A file parts.
|
|
44
|
+
* Returns array of local file paths.
|
|
45
|
+
*/
|
|
46
|
+
async function downloadFilesFromParts(fileParts, tempDir = "/tmp/xy_channel") {
|
|
47
|
+
// Create temp directory if it doesn't exist
|
|
48
|
+
await promises_1.default.mkdir(tempDir, { recursive: true });
|
|
49
|
+
const downloadedFiles = [];
|
|
50
|
+
for (const filePart of fileParts) {
|
|
51
|
+
const { name, mimeType, uri } = filePart;
|
|
52
|
+
// Generate safe file name
|
|
53
|
+
const safeName = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
54
|
+
const destPath = path_1.default.join(tempDir, `${Date.now()}_${safeName}`);
|
|
55
|
+
try {
|
|
56
|
+
await downloadFile(uri, destPath);
|
|
57
|
+
downloadedFiles.push({
|
|
58
|
+
path: destPath,
|
|
59
|
+
name,
|
|
60
|
+
mimeType,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger_js_1.logger.error(`Failed to download file ${name}:`, error);
|
|
65
|
+
// Continue with other files
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return downloadedFiles;
|
|
69
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
export interface HeartbeatConfig {
|
|
3
|
+
interval: number;
|
|
4
|
+
timeout: number;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Manages heartbeat for a WebSocket connection.
|
|
9
|
+
* Supports both application-level and protocol-level heartbeats.
|
|
10
|
+
*/
|
|
11
|
+
export declare class HeartbeatManager {
|
|
12
|
+
private ws;
|
|
13
|
+
private config;
|
|
14
|
+
private onTimeout;
|
|
15
|
+
private serverName;
|
|
16
|
+
private onHeartbeatSuccess?;
|
|
17
|
+
private intervalTimer;
|
|
18
|
+
private timeoutTimer;
|
|
19
|
+
private lastPongTime;
|
|
20
|
+
private log;
|
|
21
|
+
private error;
|
|
22
|
+
constructor(ws: WebSocket, config: HeartbeatConfig, onTimeout: () => void, serverName?: string, logFn?: (msg: string, ...args: any[]) => void, errorFn?: (msg: string, ...args: any[]) => void, onHeartbeatSuccess?: () => void);
|
|
23
|
+
/**
|
|
24
|
+
* Start heartbeat monitoring.
|
|
25
|
+
*/
|
|
26
|
+
start(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Stop heartbeat monitoring.
|
|
29
|
+
*/
|
|
30
|
+
stop(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Send a heartbeat ping.
|
|
33
|
+
*/
|
|
34
|
+
private sendHeartbeat;
|
|
35
|
+
/**
|
|
36
|
+
* Check if connection is healthy based on last pong time.
|
|
37
|
+
*/
|
|
38
|
+
isHealthy(): boolean;
|
|
39
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HeartbeatManager = void 0;
|
|
7
|
+
// Heartbeat management for WebSocket connections
|
|
8
|
+
const ws_1 = __importDefault(require("ws"));
|
|
9
|
+
/**
|
|
10
|
+
* Manages heartbeat for a WebSocket connection.
|
|
11
|
+
* Supports both application-level and protocol-level heartbeats.
|
|
12
|
+
*/
|
|
13
|
+
class HeartbeatManager {
|
|
14
|
+
constructor(ws, config, onTimeout, serverName = "unknown", logFn, errorFn, onHeartbeatSuccess // ✅ 心跳成功回调,向 OpenClaw 报告健康状态
|
|
15
|
+
) {
|
|
16
|
+
this.ws = ws;
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.onTimeout = onTimeout;
|
|
19
|
+
this.serverName = serverName;
|
|
20
|
+
this.onHeartbeatSuccess = onHeartbeatSuccess;
|
|
21
|
+
this.intervalTimer = null;
|
|
22
|
+
this.timeoutTimer = null;
|
|
23
|
+
this.lastPongTime = 0;
|
|
24
|
+
this.log = logFn ?? console.log;
|
|
25
|
+
this.error = errorFn ?? console.error;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Start heartbeat monitoring.
|
|
29
|
+
*/
|
|
30
|
+
start() {
|
|
31
|
+
this.stop(); // Clear any existing timers
|
|
32
|
+
this.lastPongTime = Date.now();
|
|
33
|
+
// Setup ping/pong for protocol-level heartbeat
|
|
34
|
+
this.ws.on("pong", () => {
|
|
35
|
+
this.lastPongTime = Date.now();
|
|
36
|
+
if (this.timeoutTimer) {
|
|
37
|
+
clearTimeout(this.timeoutTimer);
|
|
38
|
+
this.timeoutTimer = null;
|
|
39
|
+
}
|
|
40
|
+
// ✅ Report health: heartbeat successful - notify OpenClaw framework
|
|
41
|
+
this.onHeartbeatSuccess?.();
|
|
42
|
+
this.log(`[${this.serverName}] Heartbeat pong received, health reported to OpenClaw`);
|
|
43
|
+
});
|
|
44
|
+
// Start interval timer
|
|
45
|
+
this.intervalTimer = setInterval(() => {
|
|
46
|
+
this.sendHeartbeat();
|
|
47
|
+
}, this.config.interval);
|
|
48
|
+
this.log(`[${this.serverName}] Heartbeat started: interval=${this.config.interval}ms, timeout=${this.config.timeout}ms`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Stop heartbeat monitoring.
|
|
52
|
+
*/
|
|
53
|
+
stop() {
|
|
54
|
+
if (this.intervalTimer) {
|
|
55
|
+
clearInterval(this.intervalTimer);
|
|
56
|
+
this.intervalTimer = null;
|
|
57
|
+
}
|
|
58
|
+
if (this.timeoutTimer) {
|
|
59
|
+
clearTimeout(this.timeoutTimer);
|
|
60
|
+
this.timeoutTimer = null;
|
|
61
|
+
}
|
|
62
|
+
// Remove pong listener
|
|
63
|
+
this.ws.off("pong", () => { });
|
|
64
|
+
this.log(`[${this.serverName}] Heartbeat stopped`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Send a heartbeat ping.
|
|
68
|
+
*/
|
|
69
|
+
sendHeartbeat() {
|
|
70
|
+
if (this.ws.readyState !== ws_1.default.OPEN) {
|
|
71
|
+
console.warn(`[${this.serverName}] Cannot send heartbeat: WebSocket not open`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
// Send application-level heartbeat message
|
|
76
|
+
this.log(`[${this.serverName}] Sending heartbeat frame:`, this.config.message.substring(0, 100));
|
|
77
|
+
this.ws.send(this.config.message);
|
|
78
|
+
// Send protocol-level ping
|
|
79
|
+
this.ws.ping();
|
|
80
|
+
this.log(`[${this.serverName}] Protocol-level ping sent`);
|
|
81
|
+
// Setup timeout timer
|
|
82
|
+
this.timeoutTimer = setTimeout(() => {
|
|
83
|
+
this.error(`[${this.serverName}] Heartbeat timeout - no pong received`);
|
|
84
|
+
this.onTimeout();
|
|
85
|
+
}, this.config.timeout);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
this.error(`[${this.serverName}] Failed to send heartbeat:`, error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if connection is healthy based on last pong time.
|
|
93
|
+
*/
|
|
94
|
+
isHealthy() {
|
|
95
|
+
if (this.lastPongTime === 0) {
|
|
96
|
+
return true; // Not started yet
|
|
97
|
+
}
|
|
98
|
+
const timeSinceLastPong = Date.now() - this.lastPongTime;
|
|
99
|
+
return timeSinceLastPong < this.config.interval + this.config.timeout;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.HeartbeatManager = HeartbeatManager;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,13 +14,10 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
|
14
14
|
* "wsUrl2": "ws://localhost:8766/ws/link",
|
|
15
15
|
* "ak": "test_ak",
|
|
16
16
|
* "sk": "test_sk",
|
|
17
|
-
* "agentId": "your-agent-id"
|
|
18
|
-
* "enableStreaming": true
|
|
17
|
+
* "agentId": "your-agent-id"
|
|
19
18
|
* }
|
|
20
19
|
* }
|
|
21
20
|
* }
|
|
22
|
-
*
|
|
23
|
-
* Backward compatibility: Can use "wsUrl" instead of "wsUrl1" (wsUrl2 will use default)
|
|
24
21
|
*/
|
|
25
22
|
declare const plugin: {
|
|
26
23
|
id: string;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const runtime_1 = require("./runtime");
|
|
3
|
+
const channel_js_1 = require("./channel.js");
|
|
4
|
+
const runtime_js_1 = require("./runtime.js");
|
|
6
5
|
/**
|
|
7
6
|
* XiaoYi Channel Plugin for OpenClaw
|
|
8
7
|
*
|
|
@@ -18,26 +17,23 @@ const runtime_1 = require("./runtime");
|
|
|
18
17
|
* "wsUrl2": "ws://localhost:8766/ws/link",
|
|
19
18
|
* "ak": "test_ak",
|
|
20
19
|
* "sk": "test_sk",
|
|
21
|
-
* "agentId": "your-agent-id"
|
|
22
|
-
* "enableStreaming": true
|
|
20
|
+
* "agentId": "your-agent-id"
|
|
23
21
|
* }
|
|
24
22
|
* }
|
|
25
23
|
* }
|
|
26
|
-
*
|
|
27
|
-
* Backward compatibility: Can use "wsUrl" instead of "wsUrl1" (wsUrl2 will use default)
|
|
28
24
|
*/
|
|
29
25
|
const plugin = {
|
|
30
26
|
id: "xiaoyi",
|
|
31
27
|
name: "XiaoYi Channel",
|
|
32
28
|
description: "XiaoYi channel plugin with A2A protocol support",
|
|
33
|
-
configSchema:
|
|
29
|
+
configSchema: undefined,
|
|
34
30
|
register(api) {
|
|
35
31
|
console.log("XiaoYi: register() called - START");
|
|
36
32
|
// Set runtime for managing WebSocket connections
|
|
37
|
-
(0,
|
|
33
|
+
(0, runtime_js_1.setXiaoYiRuntime)(api.runtime);
|
|
38
34
|
console.log("XiaoYi: setXiaoYiRuntime() completed");
|
|
39
35
|
// Clean up any existing connections from previous plugin loads
|
|
40
|
-
const runtime = require("./runtime").getXiaoYiRuntime();
|
|
36
|
+
const runtime = require("./runtime.js").getXiaoYiRuntime();
|
|
41
37
|
console.log(`XiaoYi: Got runtime instance: ${runtime.getInstanceId()}, isConnected: ${runtime.isConnected()}`);
|
|
42
38
|
if (runtime.isConnected()) {
|
|
43
39
|
console.log("XiaoYi: Cleaning up existing connection from previous load");
|
|
@@ -45,7 +41,7 @@ const plugin = {
|
|
|
45
41
|
}
|
|
46
42
|
// Register the channel plugin
|
|
47
43
|
console.log("XiaoYi: About to call registerChannel()");
|
|
48
|
-
api.registerChannel({ plugin:
|
|
44
|
+
api.registerChannel({ plugin: channel_js_1.xiaoyiPlugin });
|
|
49
45
|
console.log("XiaoYi: registerChannel() completed");
|
|
50
46
|
console.log("XiaoYi channel plugin registered - END");
|
|
51
47
|
},
|
package/dist/push.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { XiaoYiChannelConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Push message sending service
|
|
4
|
+
* Sends notifications to XiaoYi clients via webhook API
|
|
5
|
+
*/
|
|
6
|
+
export declare class XiaoYiPushService {
|
|
7
|
+
private config;
|
|
8
|
+
private readonly pushUrl;
|
|
9
|
+
constructor(config: XiaoYiChannelConfig);
|
|
10
|
+
/**
|
|
11
|
+
* Check if push functionality is configured
|
|
12
|
+
*/
|
|
13
|
+
isConfigured(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Generate HMAC-SHA256 signature
|
|
16
|
+
*/
|
|
17
|
+
private generateSignature;
|
|
18
|
+
/**
|
|
19
|
+
* Generate UUID
|
|
20
|
+
*/
|
|
21
|
+
private generateUUID;
|
|
22
|
+
/**
|
|
23
|
+
* Send push notification (with summary text)
|
|
24
|
+
* @param text - Summary text to send (e.g., first 30 characters)
|
|
25
|
+
* @param pushText - Push notification message (e.g., "任务已完成:xxx...")
|
|
26
|
+
*/
|
|
27
|
+
sendPush(text: string, pushText: string): Promise<boolean>;
|
|
28
|
+
}
|
package/dist/push.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.XiaoYiPushService = void 0;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
/**
|
|
39
|
+
* Push message sending service
|
|
40
|
+
* Sends notifications to XiaoYi clients via webhook API
|
|
41
|
+
*/
|
|
42
|
+
class XiaoYiPushService {
|
|
43
|
+
constructor(config) {
|
|
44
|
+
this.pushUrl = "https://hag.cloud.huawei.com/open-ability-agent/v1/agent-webhook";
|
|
45
|
+
this.config = config;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if push functionality is configured
|
|
49
|
+
*/
|
|
50
|
+
isConfigured() {
|
|
51
|
+
return Boolean(this.config.apiId?.trim() &&
|
|
52
|
+
this.config.pushId?.trim() &&
|
|
53
|
+
this.config.ak?.trim() &&
|
|
54
|
+
this.config.sk?.trim());
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Generate HMAC-SHA256 signature
|
|
58
|
+
*/
|
|
59
|
+
generateSignature(timestamp) {
|
|
60
|
+
const hmac = crypto.createHmac("sha256", this.config.sk);
|
|
61
|
+
hmac.update(timestamp);
|
|
62
|
+
return hmac.digest().toString("base64");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Generate UUID
|
|
66
|
+
*/
|
|
67
|
+
generateUUID() {
|
|
68
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
69
|
+
const r = (Math.random() * 16) | 0;
|
|
70
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
71
|
+
return v.toString(16);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Send push notification (with summary text)
|
|
76
|
+
* @param text - Summary text to send (e.g., first 30 characters)
|
|
77
|
+
* @param pushText - Push notification message (e.g., "任务已完成:xxx...")
|
|
78
|
+
*/
|
|
79
|
+
async sendPush(text, pushText) {
|
|
80
|
+
if (!this.isConfigured()) {
|
|
81
|
+
console.log("[PUSH] Push not configured, skipping");
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const timestamp = Date.now().toString();
|
|
86
|
+
const signature = this.generateSignature(timestamp);
|
|
87
|
+
const messageId = this.generateUUID();
|
|
88
|
+
const payload = {
|
|
89
|
+
jsonrpc: "2.0",
|
|
90
|
+
id: messageId,
|
|
91
|
+
result: {
|
|
92
|
+
id: this.generateUUID(),
|
|
93
|
+
apiId: this.config.apiId,
|
|
94
|
+
pushId: this.config.pushId,
|
|
95
|
+
pushText: pushText,
|
|
96
|
+
kind: "task",
|
|
97
|
+
artifacts: [{
|
|
98
|
+
artifactId: this.generateUUID(),
|
|
99
|
+
parts: [{
|
|
100
|
+
kind: "text",
|
|
101
|
+
text: text, // Summary text
|
|
102
|
+
}]
|
|
103
|
+
}],
|
|
104
|
+
status: { state: "completed" }
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
console.log(`[PUSH] Sending push notification: ${pushText}`);
|
|
108
|
+
const response = await fetch(this.pushUrl, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: {
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
"Accept": "application/json",
|
|
113
|
+
"x-hag-trace-id": this.generateUUID(),
|
|
114
|
+
"X-Access-Key": this.config.ak,
|
|
115
|
+
"X-Sign": signature,
|
|
116
|
+
"X-Ts": timestamp,
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify(payload),
|
|
119
|
+
});
|
|
120
|
+
if (response.ok) {
|
|
121
|
+
console.log("[PUSH] Push notification sent successfully");
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.error(`[PUSH] Failed: HTTP ${response.status}`);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.error("[PUSH] Error:", error);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.XiaoYiPushService = XiaoYiPushService;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { XiaoYiWebSocketManager } from "./websocket";
|
|
2
|
-
import { XiaoYiChannelConfig } from "./types";
|
|
1
|
+
import { XiaoYiWebSocketManager } from "./websocket.js";
|
|
2
|
+
import { XiaoYiChannelConfig } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Timeout configuration
|
|
5
5
|
*/
|
|
@@ -23,6 +23,11 @@ export declare class XiaoYiRuntime {
|
|
|
23
23
|
private timeoutConfig;
|
|
24
24
|
private sessionAbortControllerMap;
|
|
25
25
|
private sessionActiveRunMap;
|
|
26
|
+
private sessionStartTimeMap;
|
|
27
|
+
private static readonly SESSION_STALE_TIMEOUT_MS;
|
|
28
|
+
private sessionTaskTimeoutMap;
|
|
29
|
+
private sessionPushPendingMap;
|
|
30
|
+
private taskTimeoutMs;
|
|
26
31
|
constructor();
|
|
27
32
|
getInstanceId(): string;
|
|
28
33
|
/**
|
|
@@ -115,6 +120,7 @@ export declare class XiaoYiRuntime {
|
|
|
115
120
|
} | null;
|
|
116
121
|
/**
|
|
117
122
|
* Check if a session has an active agent run
|
|
123
|
+
* If session is active but stale (超过 SESSION_STALE_TIMEOUT_MS), automatically clean up
|
|
118
124
|
* @param sessionId - Session ID
|
|
119
125
|
* @returns true if session is busy
|
|
120
126
|
*/
|
|
@@ -140,6 +146,46 @@ export declare class XiaoYiRuntime {
|
|
|
140
146
|
* Clear all AbortControllers
|
|
141
147
|
*/
|
|
142
148
|
clearAllAbortControllers(): void;
|
|
149
|
+
/**
|
|
150
|
+
* Generate a composite key for session+task combination
|
|
151
|
+
* This ensures each task has its own push state, even within the same session
|
|
152
|
+
*/
|
|
153
|
+
private getPushStateKey;
|
|
154
|
+
/**
|
|
155
|
+
* Set task timeout time (from configuration)
|
|
156
|
+
*/
|
|
157
|
+
setTaskTimeout(timeoutMs: number): void;
|
|
158
|
+
/**
|
|
159
|
+
* Set a 1-hour task timeout timer for a session
|
|
160
|
+
* @returns timeout ID
|
|
161
|
+
*/
|
|
162
|
+
setTaskTimeoutForSession(sessionId: string, taskId: string, callback: (sessionId: string, taskId: string) => void): NodeJS.Timeout;
|
|
163
|
+
/**
|
|
164
|
+
* Clear the task timeout timer for a session
|
|
165
|
+
*/
|
|
166
|
+
clearTaskTimeoutForSession(sessionId: string): void;
|
|
167
|
+
/**
|
|
168
|
+
* Check if session+task is waiting for push notification
|
|
169
|
+
* @param sessionId - Session ID
|
|
170
|
+
* @param taskId - Task ID (optional, for per-task tracking)
|
|
171
|
+
*/
|
|
172
|
+
isSessionWaitingForPush(sessionId: string, taskId?: string): boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Mark session+task as waiting for push notification
|
|
175
|
+
* @param sessionId - Session ID
|
|
176
|
+
* @param taskId - Task ID (optional, for per-task tracking)
|
|
177
|
+
*/
|
|
178
|
+
markSessionWaitingForPush(sessionId: string, taskId?: string): void;
|
|
179
|
+
/**
|
|
180
|
+
* Clear the waiting push state for a session+task
|
|
181
|
+
* @param sessionId - Session ID
|
|
182
|
+
* @param taskId - Task ID (optional, for per-task tracking)
|
|
183
|
+
*/
|
|
184
|
+
clearSessionWaitingForPush(sessionId: string, taskId?: string): void;
|
|
185
|
+
/**
|
|
186
|
+
* Clear all task timeout related state for a session
|
|
187
|
+
*/
|
|
188
|
+
clearTaskTimeoutState(sessionId: string): void;
|
|
143
189
|
}
|
|
144
190
|
export declare function getXiaoYiRuntime(): XiaoYiRuntime;
|
|
145
191
|
export declare function setXiaoYiRuntime(runtime: any): void;
|