@ynhcj/xiaoyi-channel 0.0.1-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.
Files changed (60) hide show
  1. package/dist/index.d.ts +16 -0
  2. package/dist/index.js +21 -0
  3. package/dist/src/bot.d.ts +17 -0
  4. package/dist/src/bot.js +260 -0
  5. package/dist/src/channel.d.ts +6 -0
  6. package/dist/src/channel.js +87 -0
  7. package/dist/src/client.d.ts +35 -0
  8. package/dist/src/client.js +147 -0
  9. package/dist/src/config-schema.d.ts +54 -0
  10. package/dist/src/config-schema.js +55 -0
  11. package/dist/src/config.d.ts +17 -0
  12. package/dist/src/config.js +45 -0
  13. package/dist/src/file-download.d.ts +17 -0
  14. package/dist/src/file-download.js +53 -0
  15. package/dist/src/file-upload.d.ts +23 -0
  16. package/dist/src/file-upload.js +129 -0
  17. package/dist/src/formatter.d.ts +77 -0
  18. package/dist/src/formatter.js +252 -0
  19. package/dist/src/heartbeat.d.ts +39 -0
  20. package/dist/src/heartbeat.js +102 -0
  21. package/dist/src/monitor.d.ts +17 -0
  22. package/dist/src/monitor.js +191 -0
  23. package/dist/src/onboarding.d.ts +6 -0
  24. package/dist/src/onboarding.js +173 -0
  25. package/dist/src/outbound.d.ts +6 -0
  26. package/dist/src/outbound.js +208 -0
  27. package/dist/src/parser.d.ts +49 -0
  28. package/dist/src/parser.js +99 -0
  29. package/dist/src/push.d.ts +23 -0
  30. package/dist/src/push.js +146 -0
  31. package/dist/src/reply-dispatcher.d.ts +15 -0
  32. package/dist/src/reply-dispatcher.js +160 -0
  33. package/dist/src/runtime.d.ts +11 -0
  34. package/dist/src/runtime.js +18 -0
  35. package/dist/src/tools/calendar-tool.d.ts +6 -0
  36. package/dist/src/tools/calendar-tool.js +167 -0
  37. package/dist/src/tools/location-tool.d.ts +5 -0
  38. package/dist/src/tools/location-tool.js +136 -0
  39. package/dist/src/tools/note-tool.d.ts +5 -0
  40. package/dist/src/tools/note-tool.js +130 -0
  41. package/dist/src/tools/search-note-tool.d.ts +5 -0
  42. package/dist/src/tools/search-note-tool.js +130 -0
  43. package/dist/src/tools/session-manager.d.ts +29 -0
  44. package/dist/src/tools/session-manager.js +74 -0
  45. package/dist/src/tools/tool-context.d.ts +16 -0
  46. package/dist/src/tools/tool-context.js +7 -0
  47. package/dist/src/types.d.ts +163 -0
  48. package/dist/src/types.js +2 -0
  49. package/dist/src/utils/config-manager.d.ts +26 -0
  50. package/dist/src/utils/config-manager.js +56 -0
  51. package/dist/src/utils/crypto.d.ts +8 -0
  52. package/dist/src/utils/crypto.js +14 -0
  53. package/dist/src/utils/logger.d.ts +6 -0
  54. package/dist/src/utils/logger.js +34 -0
  55. package/dist/src/utils/session.d.ts +34 -0
  56. package/dist/src/utils/session.js +50 -0
  57. package/dist/src/websocket.d.ts +123 -0
  58. package/dist/src/websocket.js +547 -0
  59. package/openclaw.plugin.json +10 -0
  60. package/package.json +71 -0
@@ -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 (30s) and protocol-level (20s) 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
+ // Heartbeat management for WebSocket connections
2
+ import WebSocket from "ws";
3
+ /**
4
+ * Manages heartbeat for a WebSocket connection.
5
+ * Supports both application-level (30s) and protocol-level (20s) heartbeats.
6
+ */
7
+ export class HeartbeatManager {
8
+ ws;
9
+ config;
10
+ onTimeout;
11
+ serverName;
12
+ onHeartbeatSuccess;
13
+ intervalTimer = null;
14
+ timeoutTimer = null;
15
+ lastPongTime = 0;
16
+ // Logging functions following feishu pattern
17
+ log;
18
+ error;
19
+ constructor(ws, config, onTimeout, serverName = "unknown", logFn, errorFn, onHeartbeatSuccess // ✅ 新增:心跳成功回调
20
+ ) {
21
+ this.ws = ws;
22
+ this.config = config;
23
+ this.onTimeout = onTimeout;
24
+ this.serverName = serverName;
25
+ this.onHeartbeatSuccess = onHeartbeatSuccess;
26
+ this.log = logFn ?? console.log;
27
+ this.error = errorFn ?? console.error;
28
+ }
29
+ /**
30
+ * Start heartbeat monitoring.
31
+ */
32
+ start() {
33
+ this.stop(); // Clear any existing timers
34
+ this.lastPongTime = Date.now();
35
+ // Setup ping/pong for protocol-level heartbeat
36
+ this.ws.on("pong", () => {
37
+ this.lastPongTime = Date.now();
38
+ if (this.timeoutTimer) {
39
+ clearTimeout(this.timeoutTimer);
40
+ this.timeoutTimer = null;
41
+ }
42
+ // ✅ Report health: heartbeat successful
43
+ this.onHeartbeatSuccess?.();
44
+ });
45
+ // Start interval timer
46
+ this.intervalTimer = setInterval(() => {
47
+ this.sendHeartbeat();
48
+ }, this.config.interval);
49
+ this.log(`[DEBUG] Heartbeat started for ${this.serverName}: interval=${this.config.interval}ms`);
50
+ }
51
+ /**
52
+ * Stop heartbeat monitoring.
53
+ */
54
+ stop() {
55
+ if (this.intervalTimer) {
56
+ clearInterval(this.intervalTimer);
57
+ this.intervalTimer = null;
58
+ }
59
+ if (this.timeoutTimer) {
60
+ clearTimeout(this.timeoutTimer);
61
+ this.timeoutTimer = null;
62
+ }
63
+ this.log(`[DEBUG] Heartbeat stopped for ${this.serverName}`);
64
+ }
65
+ /**
66
+ * Send a heartbeat ping.
67
+ */
68
+ sendHeartbeat() {
69
+ if (this.ws.readyState !== WebSocket.OPEN) {
70
+ console.warn(`Cannot send heartbeat for ${this.serverName}: WebSocket not open`);
71
+ return;
72
+ }
73
+ try {
74
+ // Send application-level heartbeat message
75
+ console.log(`[WS-${this.serverName}-SEND] Sending heartbeat frame:`, this.config.message);
76
+ this.ws.send(this.config.message);
77
+ console.log(`[WS-${this.serverName}-SEND] Heartbeat message sent, size: ${this.config.message.length} bytes`);
78
+ // Send protocol-level ping
79
+ this.ws.ping();
80
+ console.log(`[WS-${this.serverName}-SEND] Protocol-level ping sent`);
81
+ // Setup timeout timer
82
+ this.timeoutTimer = setTimeout(() => {
83
+ this.error(`Heartbeat timeout for ${this.serverName}`);
84
+ this.onTimeout();
85
+ }, this.config.timeout);
86
+ this.log(`[DEBUG] Heartbeat sent for ${this.serverName}`);
87
+ }
88
+ catch (error) {
89
+ this.error(`Failed to send heartbeat for ${this.serverName}:`, error);
90
+ }
91
+ }
92
+ /**
93
+ * Check if connection is healthy based on last pong time.
94
+ */
95
+ isHealthy() {
96
+ if (this.lastPongTime === 0) {
97
+ return true; // Not started yet
98
+ }
99
+ const timeSinceLastPong = Date.now() - this.lastPongTime;
100
+ return timeSinceLastPong < this.config.interval + this.config.timeout;
101
+ }
102
+ }
@@ -0,0 +1,17 @@
1
+ import type { RuntimeEnv } from "openclaw/plugin-sdk";
2
+ export type MonitorXYOpts = {
3
+ config?: any;
4
+ runtime?: RuntimeEnv;
5
+ abortSignal?: AbortSignal;
6
+ accountId?: string;
7
+ setStatus?: (status: {
8
+ lastEventAt?: number;
9
+ lastInboundAt?: number;
10
+ connected?: boolean;
11
+ }) => void;
12
+ };
13
+ /**
14
+ * Monitor XY channel WebSocket connections.
15
+ * Keeps the connection alive until abortSignal is triggered.
16
+ */
17
+ export declare function monitorXYProvider(opts?: MonitorXYOpts): Promise<void>;
@@ -0,0 +1,191 @@
1
+ import { resolveXYConfig } from "./config.js";
2
+ import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
3
+ import { handleXYMessage } from "./bot.js";
4
+ /**
5
+ * Per-session serial queue that ensures messages from the same session are processed
6
+ * in arrival order while allowing different sessions to run concurrently.
7
+ * Following feishu/monitor.account.ts pattern.
8
+ */
9
+ function createSessionQueue() {
10
+ const queues = new Map();
11
+ return (sessionId, task) => {
12
+ const prev = queues.get(sessionId) ?? Promise.resolve();
13
+ const next = prev.then(task, task);
14
+ queues.set(sessionId, next);
15
+ void next.finally(() => {
16
+ if (queues.get(sessionId) === next) {
17
+ queues.delete(sessionId);
18
+ }
19
+ });
20
+ return next;
21
+ };
22
+ }
23
+ /**
24
+ * Monitor XY channel WebSocket connections.
25
+ * Keeps the connection alive until abortSignal is triggered.
26
+ */
27
+ export async function monitorXYProvider(opts = {}) {
28
+ const cfg = opts.config;
29
+ if (!cfg) {
30
+ throw new Error("Config is required for XY monitor");
31
+ }
32
+ const runtime = opts.runtime;
33
+ const log = runtime?.log ?? console.log;
34
+ const error = runtime?.error ?? console.error;
35
+ const account = resolveXYConfig(cfg);
36
+ if (!account.enabled) {
37
+ throw new Error(`XY account is disabled`);
38
+ }
39
+ const accountId = opts.accountId ?? "default";
40
+ // Create trackEvent function to report health to OpenClaw framework
41
+ const trackEvent = opts.setStatus
42
+ ? () => {
43
+ opts.setStatus({ lastEventAt: Date.now(), lastInboundAt: Date.now() });
44
+ }
45
+ : undefined;
46
+ // 🔍 Diagnose WebSocket managers before gateway start
47
+ console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
48
+ diagnoseAllManagers();
49
+ // Get WebSocket manager (cached)
50
+ const wsManager = getXYWebSocketManager(account);
51
+ // ✅ Set health event callback for heartbeat reporting
52
+ if (trackEvent) {
53
+ wsManager.setHealthEventCallback(trackEvent);
54
+ }
55
+ // Track logged servers to avoid duplicate logs
56
+ const loggedServers = new Set();
57
+ // Track active message processing to detect duplicates
58
+ const activeMessages = new Set();
59
+ // Create session queue for ordered message processing
60
+ const enqueue = createSessionQueue();
61
+ // Health check interval
62
+ let healthCheckInterval = null;
63
+ return new Promise((resolve, reject) => {
64
+ // Event handlers (defined early so they can be referenced in cleanup)
65
+ const messageHandler = (message, sessionId, serverId) => {
66
+ const messageKey = `${sessionId}::${message.id}`;
67
+ log(`[MONITOR-HANDLER] ####### messageHandler triggered: serverId=${serverId}, sessionId=${sessionId}, messageId=${message.id} #######`);
68
+ // ✅ Report health: received a message
69
+ trackEvent?.();
70
+ // Check for duplicate message handling
71
+ if (activeMessages.has(messageKey)) {
72
+ error(`[MONITOR-HANDLER] ⚠️ WARNING: Duplicate message detected! messageKey=${messageKey}, this may cause duplicate dispatchers!`);
73
+ }
74
+ activeMessages.add(messageKey);
75
+ log(`[MONITOR-HANDLER] 📝 Active messages count: ${activeMessages.size}, messageKey: ${messageKey}`);
76
+ const task = async () => {
77
+ try {
78
+ log(`[MONITOR-HANDLER] 🚀 Starting handleXYMessage for messageKey=${messageKey}`);
79
+ await handleXYMessage({
80
+ cfg,
81
+ runtime,
82
+ message,
83
+ accountId, // ✅ Pass accountId ("default")
84
+ });
85
+ log(`[MONITOR-HANDLER] ✅ Completed handleXYMessage for messageKey=${messageKey}`);
86
+ }
87
+ catch (err) {
88
+ // ✅ Only log error, don't re-throw to prevent gateway restart
89
+ error(`XY gateway: error handling message from ${serverId}: ${String(err)}`);
90
+ }
91
+ finally {
92
+ // Remove from active messages when done
93
+ activeMessages.delete(messageKey);
94
+ log(`[MONITOR-HANDLER] 🧹 Cleaned up messageKey=${messageKey}, remaining active: ${activeMessages.size}`);
95
+ }
96
+ };
97
+ void enqueue(sessionId, task).catch((err) => {
98
+ // Error already logged in task, this is for queue failures
99
+ error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
100
+ activeMessages.delete(messageKey);
101
+ });
102
+ };
103
+ const connectedHandler = (serverId) => {
104
+ if (!loggedServers.has(serverId)) {
105
+ log(`XY gateway: ${serverId} connected`);
106
+ loggedServers.add(serverId);
107
+ }
108
+ // ✅ Report health: connection established
109
+ trackEvent?.();
110
+ opts.setStatus?.({ connected: true });
111
+ };
112
+ const disconnectedHandler = (serverId) => {
113
+ console.warn(`XY gateway: ${serverId} disconnected`);
114
+ loggedServers.delete(serverId);
115
+ // ✅ Report disconnection status (only if all servers disconnected)
116
+ if (loggedServers.size === 0) {
117
+ opts.setStatus?.({ connected: false });
118
+ }
119
+ };
120
+ const errorHandler = (err, serverId) => {
121
+ error(`XY gateway: ${serverId} error: ${String(err)}`);
122
+ };
123
+ const cleanup = () => {
124
+ log("XY gateway: cleaning up...");
125
+ // 🔍 Diagnose before cleanup
126
+ console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers before cleanup...");
127
+ diagnoseAllManagers();
128
+ // Stop health check interval
129
+ if (healthCheckInterval) {
130
+ clearInterval(healthCheckInterval);
131
+ healthCheckInterval = null;
132
+ console.log("⏸️ Stopped periodic health check");
133
+ }
134
+ // Remove event handlers to prevent duplicate calls on gateway restart
135
+ wsManager.off("message", messageHandler);
136
+ wsManager.off("connected", connectedHandler);
137
+ wsManager.off("disconnected", disconnectedHandler);
138
+ wsManager.off("error", errorHandler);
139
+ // ✅ Disconnect the wsManager to prevent connection leaks
140
+ // This is safe because each gateway lifecycle should have clean connections
141
+ wsManager.disconnect();
142
+ // ✅ Remove manager from cache to prevent reusing dirty state
143
+ removeXYWebSocketManager(account);
144
+ loggedServers.clear();
145
+ activeMessages.clear();
146
+ log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages`);
147
+ // 🔍 Diagnose after cleanup
148
+ console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
149
+ diagnoseAllManagers();
150
+ };
151
+ const handleAbort = () => {
152
+ log("XY gateway: abort signal received, stopping");
153
+ // cleanup();
154
+ // log("XY gateway stopped");
155
+ resolve();
156
+ };
157
+ if (opts.abortSignal?.aborted) {
158
+ // cleanup();
159
+ resolve();
160
+ return;
161
+ }
162
+ opts.abortSignal?.addEventListener("abort", handleAbort, { once: true });
163
+ // Register event handlers (handlers are defined above in cleanup scope)
164
+ wsManager.on("message", messageHandler);
165
+ wsManager.on("connected", connectedHandler);
166
+ wsManager.on("disconnected", disconnectedHandler);
167
+ wsManager.on("error", errorHandler);
168
+ // Start periodic health check (every 5 minutes)
169
+ console.log("🏥 Starting periodic health check (every 5 minutes)...");
170
+ healthCheckInterval = setInterval(() => {
171
+ console.log("🏥 [HEALTH CHECK] Periodic WebSocket diagnostics...");
172
+ diagnoseAllManagers();
173
+ // Auto-cleanup orphan connections
174
+ const cleaned = cleanupOrphanConnections();
175
+ if (cleaned > 0) {
176
+ console.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
177
+ }
178
+ }, 5 * 60 * 1000); // 5 minutes
179
+ // Connect to WebSocket servers
180
+ wsManager.connect()
181
+ .then(() => {
182
+ log("XY gateway: started successfully");
183
+ })
184
+ .catch((err) => {
185
+ // Connection failed but don't reject - continue monitoring for reconnection
186
+ error(`XY gateway: initial connection failed: ${String(err)}`);
187
+ // Still resolve successfully so plugin starts
188
+ resolve();
189
+ });
190
+ });
191
+ }
@@ -0,0 +1,6 @@
1
+ import type { ChannelOnboardingAdapter } from "openclaw/plugin-sdk";
2
+ /**
3
+ * XY Channel Onboarding Adapter
4
+ * Implements the ChannelOnboardingAdapter interface for OpenClaw's onboarding system
5
+ */
6
+ export declare const xyOnboardingAdapter: ChannelOnboardingAdapter;
@@ -0,0 +1,173 @@
1
+ const channel = "xiaoyi-channel";
2
+ /**
3
+ * Check if XY channel is properly configured with required fields
4
+ */
5
+ function isXYConfigured(cfg) {
6
+ try {
7
+ const xyConfig = cfg.channels?.["xiaoyi-channel"];
8
+ if (!xyConfig) {
9
+ return false;
10
+ }
11
+ // Check required fields
12
+ const requiredFields = ["apiKey", "agentId", "uid", "apiId", "pushId"];
13
+ for (const field of requiredFields) {
14
+ if (!xyConfig[field] || (typeof xyConfig[field] === "string" && !xyConfig[field].trim())) {
15
+ return false;
16
+ }
17
+ }
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ /**
25
+ * Get current status of XY channel configuration
26
+ */
27
+ async function getStatus({ cfg }) {
28
+ const configured = isXYConfigured(cfg);
29
+ const xyConfig = cfg.channels?.["xiaoyi-channel"];
30
+ const statusLines = [];
31
+ if (configured) {
32
+ const wsUrl1 = xyConfig?.wsUrl1 || "ws://localhost:8765/ws/link";
33
+ const wsUrl2 = xyConfig?.wsUrl2 || "ws://localhost:8768/ws/link";
34
+ statusLines.push(`XY: configured (双 WebSocket: ${wsUrl1}, ${wsUrl2})`);
35
+ }
36
+ else {
37
+ statusLines.push("XY: 需要配置 (需要 apiKey, agentId, uid, apiId, pushId)");
38
+ }
39
+ return {
40
+ channel,
41
+ configured,
42
+ statusLines,
43
+ selectionHint: configured ? "configured" : "需要配置",
44
+ quickstartScore: configured ? 5 : 0,
45
+ };
46
+ }
47
+ /**
48
+ * Configure XY channel through interactive prompts
49
+ */
50
+ async function configure({ cfg, prompter, }) {
51
+ // Note current configuration status
52
+ const currentConfig = cfg.channels?.["xiaoyi-channel"];
53
+ const isUpdate = Boolean(currentConfig);
54
+ await prompter.note([
55
+ "XY Channel - 小艺 A2A 协议配置",
56
+ "",
57
+ "XY 是小艺智能助手的 A2A (Agent-to-Agent) 协议集成,",
58
+ "需要配置双 WebSocket 连接和相关认证信息。",
59
+ "",
60
+ isUpdate ? "当前配置将被更新。" : "首次配置 XY channel。",
61
+ ].join("\n"), "XY Channel 配置");
62
+ // Prompt for WebSocket URLs
63
+ const wsUrl1 = await prompter.text({
64
+ message: "WebSocket URL 1 (主连接)",
65
+ initialValue: currentConfig?.wsUrl1 || "ws://localhost:8765/ws/link",
66
+ placeholder: "ws://localhost:8765/ws/link",
67
+ });
68
+ const wsUrl2 = await prompter.text({
69
+ message: "WebSocket URL 2 (辅助连接)",
70
+ initialValue: currentConfig?.wsUrl2 || "ws://localhost:8768/ws/link",
71
+ placeholder: "ws://localhost:8768/ws/link",
72
+ });
73
+ // Prompt for required authentication fields
74
+ const apiKey = await prompter.text({
75
+ message: "API Key (必需)",
76
+ initialValue: currentConfig?.apiKey || "",
77
+ placeholder: "输入小艺 API Key",
78
+ validate: (value) => (value.trim() ? undefined : "API Key 不能为空"),
79
+ });
80
+ const uid = await prompter.text({
81
+ message: "UID - 用户ID (必需)",
82
+ initialValue: currentConfig?.uid || "",
83
+ placeholder: "输入用户 ID",
84
+ validate: (value) => (value.trim() ? undefined : "UID 不能为空"),
85
+ });
86
+ const agentId = await prompter.text({
87
+ message: "Agent ID - 智能体ID (必需)",
88
+ initialValue: currentConfig?.agentId || "",
89
+ placeholder: "agent5336cca603f941ee9b112f711805e866",
90
+ validate: (value) => (value.trim() ? undefined : "Agent ID 不能为空"),
91
+ });
92
+ const apiId = await prompter.text({
93
+ message: "API ID (必需)",
94
+ initialValue: currentConfig?.apiId || "",
95
+ placeholder: "输入 API ID",
96
+ validate: (value) => (value.trim() ? undefined : "API ID 不能为空"),
97
+ });
98
+ const pushId = await prompter.text({
99
+ message: "Push ID (必需)",
100
+ initialValue: currentConfig?.pushId || "",
101
+ placeholder: "输入 Push ID",
102
+ validate: (value) => (value.trim() ? undefined : "Push ID 不能为空"),
103
+ });
104
+ // Optional fields
105
+ const fileUploadUrl = await prompter.text({
106
+ message: "File Upload URL (文件上传服务)",
107
+ initialValue: currentConfig?.fileUploadUrl || "http://localhost:8767",
108
+ placeholder: "http://localhost:8767",
109
+ });
110
+ const pushUrl = await prompter.text({
111
+ message: "Push URL (推送服务,可选)",
112
+ initialValue: currentConfig?.pushUrl || "",
113
+ placeholder: "留空使用默认值",
114
+ });
115
+ // Update configuration
116
+ const updatedConfig = {
117
+ ...cfg,
118
+ channels: {
119
+ ...cfg.channels,
120
+ "xiaoyi-channel": {
121
+ enabled: true,
122
+ wsUrl1: wsUrl1.trim(),
123
+ wsUrl2: wsUrl2.trim(),
124
+ apiKey: apiKey.trim(),
125
+ uid: uid.trim(),
126
+ agentId: agentId.trim(),
127
+ apiId: apiId.trim(),
128
+ pushId: pushId.trim(),
129
+ fileUploadUrl: fileUploadUrl.trim(),
130
+ ...(pushUrl?.trim() ? { pushUrl: pushUrl.trim() } : {}),
131
+ },
132
+ },
133
+ };
134
+ // Show confirmation
135
+ await prompter.note([
136
+ "✅ XY Channel 配置完成",
137
+ "",
138
+ `主连接: ${wsUrl1}`,
139
+ `辅助连接: ${wsUrl2}`,
140
+ `Agent ID: ${agentId}`,
141
+ `UID: ${uid}`,
142
+ "",
143
+ "运行以下命令启动 gateway:",
144
+ " openclaw gateway restart",
145
+ "",
146
+ "查看日志:",
147
+ " openclaw logs --follow",
148
+ ].join("\n"), "配置成功");
149
+ return {
150
+ cfg: updatedConfig,
151
+ accountId: "default",
152
+ };
153
+ }
154
+ /**
155
+ * XY Channel Onboarding Adapter
156
+ * Implements the ChannelOnboardingAdapter interface for OpenClaw's onboarding system
157
+ */
158
+ export const xyOnboardingAdapter = {
159
+ channel,
160
+ getStatus,
161
+ configure,
162
+ // Optional: disable the channel
163
+ disable: (cfg) => ({
164
+ ...cfg,
165
+ channels: {
166
+ ...cfg.channels,
167
+ "xiaoyi-channel": {
168
+ ...(cfg.channels?.["xiaoyi-channel"] || {}),
169
+ enabled: false,
170
+ },
171
+ },
172
+ }),
173
+ };
@@ -0,0 +1,6 @@
1
+ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
2
+ /**
3
+ * Outbound adapter for sending messages from OpenClaw to XY.
4
+ * Uses Push service for direct message delivery.
5
+ */
6
+ export declare const xyOutbound: ChannelOutboundAdapter;