@umimoney/clawdbot-relay-plugin 0.1.0

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.
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @umimoney/clawdbot-relay-plugin
3
+ *
4
+ * Clawdbot plugin for connecting to UMI relay server.
5
+ * Enables real-time chat visibility in UMI web UI.
6
+ */
7
+ interface RelayConfig {
8
+ /** UMI relay server URL (default: wss://server.umi.app/ws/relay) */
9
+ relayUrl?: string;
10
+ /** UMI API key (starts with umi_) */
11
+ apiKey: string;
12
+ /** Agent version string */
13
+ agentVersion?: string;
14
+ /** Agent capabilities */
15
+ capabilities?: string[];
16
+ /** Reconnect on disconnect (default: true) */
17
+ autoReconnect?: boolean;
18
+ /** Max reconnect attempts (default: 10) */
19
+ maxReconnectAttempts?: number;
20
+ /** Initial reconnect delay in ms (default: 1000) */
21
+ reconnectDelayMs?: number;
22
+ /** Max reconnect delay in ms (default: 30000) */
23
+ maxReconnectDelayMs?: number;
24
+ }
25
+ interface RelayMessage {
26
+ type: string;
27
+ id: string;
28
+ timestamp: number;
29
+ agentId?: string;
30
+ [key: string]: any;
31
+ }
32
+ interface MessageHandler {
33
+ (message: RelayMessage): void;
34
+ }
35
+ interface StateUpdate {
36
+ state: 'idle' | 'thinking' | 'executing' | 'waiting_approval';
37
+ speechBubble?: string | null;
38
+ currentTask?: {
39
+ type: string;
40
+ chain?: string;
41
+ description: string;
42
+ } | null;
43
+ }
44
+ declare class UmiRelayClient {
45
+ private ws;
46
+ private config;
47
+ private reconnectAttempts;
48
+ private reconnectTimeout;
49
+ private pingInterval;
50
+ private messageHandlers;
51
+ private agentId;
52
+ private isAuthenticated;
53
+ constructor(config: RelayConfig);
54
+ /**
55
+ * Connect to the relay server
56
+ */
57
+ connect(): Promise<void>;
58
+ /**
59
+ * Disconnect from the relay server
60
+ */
61
+ disconnect(): void;
62
+ /**
63
+ * Send a chat message through the relay
64
+ */
65
+ sendMessage(content: string, metadata?: Record<string, any>): void;
66
+ /**
67
+ * Update agent state (shown in UMI Town)
68
+ */
69
+ updateState(state: StateUpdate): void;
70
+ /**
71
+ * Request approval for a transaction
72
+ */
73
+ requestApproval(params: {
74
+ transactionId: string;
75
+ chain: string;
76
+ transactionType: string;
77
+ amountUsd: number;
78
+ description: string;
79
+ expiresAt: string;
80
+ transactionData: any;
81
+ }): void;
82
+ /**
83
+ * Register a message handler
84
+ */
85
+ onMessage(handler: MessageHandler): () => void;
86
+ /**
87
+ * Check if connected and authenticated
88
+ */
89
+ get isConnected(): boolean;
90
+ private authenticate;
91
+ private handleMessage;
92
+ private notifyHandlers;
93
+ private sendPong;
94
+ private startPing;
95
+ private cleanup;
96
+ private scheduleReconnect;
97
+ private generateId;
98
+ }
99
+ interface ClawdbotContext {
100
+ injectMessage: (params: {
101
+ channel: string;
102
+ text: string;
103
+ metadata?: any;
104
+ }) => void;
105
+ registerSend: (channel: string, handler: (response: any) => void) => void;
106
+ }
107
+ interface ClawdbotPlugin {
108
+ id: string;
109
+ type: 'channel';
110
+ start: (ctx: ClawdbotContext, config: RelayConfig) => Promise<void>;
111
+ stop?: () => void;
112
+ }
113
+ declare const plugin: ClawdbotPlugin;
114
+
115
+ export { type ClawdbotContext, type ClawdbotPlugin, type MessageHandler, type RelayConfig, type RelayMessage, type StateUpdate, UmiRelayClient, plugin as default, plugin as umiRelayPlugin };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @umimoney/clawdbot-relay-plugin
3
+ *
4
+ * Clawdbot plugin for connecting to UMI relay server.
5
+ * Enables real-time chat visibility in UMI web UI.
6
+ */
7
+ interface RelayConfig {
8
+ /** UMI relay server URL (default: wss://server.umi.app/ws/relay) */
9
+ relayUrl?: string;
10
+ /** UMI API key (starts with umi_) */
11
+ apiKey: string;
12
+ /** Agent version string */
13
+ agentVersion?: string;
14
+ /** Agent capabilities */
15
+ capabilities?: string[];
16
+ /** Reconnect on disconnect (default: true) */
17
+ autoReconnect?: boolean;
18
+ /** Max reconnect attempts (default: 10) */
19
+ maxReconnectAttempts?: number;
20
+ /** Initial reconnect delay in ms (default: 1000) */
21
+ reconnectDelayMs?: number;
22
+ /** Max reconnect delay in ms (default: 30000) */
23
+ maxReconnectDelayMs?: number;
24
+ }
25
+ interface RelayMessage {
26
+ type: string;
27
+ id: string;
28
+ timestamp: number;
29
+ agentId?: string;
30
+ [key: string]: any;
31
+ }
32
+ interface MessageHandler {
33
+ (message: RelayMessage): void;
34
+ }
35
+ interface StateUpdate {
36
+ state: 'idle' | 'thinking' | 'executing' | 'waiting_approval';
37
+ speechBubble?: string | null;
38
+ currentTask?: {
39
+ type: string;
40
+ chain?: string;
41
+ description: string;
42
+ } | null;
43
+ }
44
+ declare class UmiRelayClient {
45
+ private ws;
46
+ private config;
47
+ private reconnectAttempts;
48
+ private reconnectTimeout;
49
+ private pingInterval;
50
+ private messageHandlers;
51
+ private agentId;
52
+ private isAuthenticated;
53
+ constructor(config: RelayConfig);
54
+ /**
55
+ * Connect to the relay server
56
+ */
57
+ connect(): Promise<void>;
58
+ /**
59
+ * Disconnect from the relay server
60
+ */
61
+ disconnect(): void;
62
+ /**
63
+ * Send a chat message through the relay
64
+ */
65
+ sendMessage(content: string, metadata?: Record<string, any>): void;
66
+ /**
67
+ * Update agent state (shown in UMI Town)
68
+ */
69
+ updateState(state: StateUpdate): void;
70
+ /**
71
+ * Request approval for a transaction
72
+ */
73
+ requestApproval(params: {
74
+ transactionId: string;
75
+ chain: string;
76
+ transactionType: string;
77
+ amountUsd: number;
78
+ description: string;
79
+ expiresAt: string;
80
+ transactionData: any;
81
+ }): void;
82
+ /**
83
+ * Register a message handler
84
+ */
85
+ onMessage(handler: MessageHandler): () => void;
86
+ /**
87
+ * Check if connected and authenticated
88
+ */
89
+ get isConnected(): boolean;
90
+ private authenticate;
91
+ private handleMessage;
92
+ private notifyHandlers;
93
+ private sendPong;
94
+ private startPing;
95
+ private cleanup;
96
+ private scheduleReconnect;
97
+ private generateId;
98
+ }
99
+ interface ClawdbotContext {
100
+ injectMessage: (params: {
101
+ channel: string;
102
+ text: string;
103
+ metadata?: any;
104
+ }) => void;
105
+ registerSend: (channel: string, handler: (response: any) => void) => void;
106
+ }
107
+ interface ClawdbotPlugin {
108
+ id: string;
109
+ type: 'channel';
110
+ start: (ctx: ClawdbotContext, config: RelayConfig) => Promise<void>;
111
+ stop?: () => void;
112
+ }
113
+ declare const plugin: ClawdbotPlugin;
114
+
115
+ export { type ClawdbotContext, type ClawdbotPlugin, type MessageHandler, type RelayConfig, type RelayMessage, type StateUpdate, UmiRelayClient, plugin as default, plugin as umiRelayPlugin };
package/dist/index.js ADDED
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ UmiRelayClient: () => UmiRelayClient,
34
+ default: () => index_default,
35
+ umiRelayPlugin: () => plugin
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+ var import_ws = __toESM(require("ws"));
39
+ var UmiRelayClient = class {
40
+ constructor(config) {
41
+ this.ws = null;
42
+ this.reconnectAttempts = 0;
43
+ this.reconnectTimeout = null;
44
+ this.pingInterval = null;
45
+ this.messageHandlers = /* @__PURE__ */ new Set();
46
+ this.agentId = null;
47
+ this.isAuthenticated = false;
48
+ this.config = {
49
+ relayUrl: config.relayUrl || "wss://server.umi.app/ws/relay",
50
+ apiKey: config.apiKey,
51
+ agentVersion: config.agentVersion || "1.0.0",
52
+ capabilities: config.capabilities || ["chat"],
53
+ autoReconnect: config.autoReconnect ?? true,
54
+ maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
55
+ reconnectDelayMs: config.reconnectDelayMs ?? 1e3,
56
+ maxReconnectDelayMs: config.maxReconnectDelayMs ?? 3e4
57
+ };
58
+ }
59
+ /**
60
+ * Connect to the relay server
61
+ */
62
+ connect() {
63
+ return new Promise((resolve, reject) => {
64
+ try {
65
+ const url = `${this.config.relayUrl}?type=agent`;
66
+ console.log(`[UmiRelay] Connecting to ${url}`);
67
+ this.ws = new import_ws.default(url);
68
+ this.ws.on("open", () => {
69
+ console.log("[UmiRelay] Connected, authenticating...");
70
+ this.authenticate();
71
+ });
72
+ this.ws.on("message", (data) => {
73
+ try {
74
+ const message = JSON.parse(data.toString());
75
+ this.handleMessage(message, resolve, reject);
76
+ } catch (error) {
77
+ console.error("[UmiRelay] Failed to parse message:", error);
78
+ }
79
+ });
80
+ this.ws.on("close", () => {
81
+ console.log("[UmiRelay] Disconnected");
82
+ this.cleanup();
83
+ this.scheduleReconnect();
84
+ });
85
+ this.ws.on("error", (error) => {
86
+ console.error("[UmiRelay] WebSocket error:", error);
87
+ if (!this.isAuthenticated) {
88
+ reject(error);
89
+ }
90
+ });
91
+ } catch (error) {
92
+ reject(error);
93
+ }
94
+ });
95
+ }
96
+ /**
97
+ * Disconnect from the relay server
98
+ */
99
+ disconnect() {
100
+ this.config.autoReconnect = false;
101
+ this.cleanup();
102
+ if (this.ws) {
103
+ this.ws.close();
104
+ this.ws = null;
105
+ }
106
+ }
107
+ /**
108
+ * Send a chat message through the relay
109
+ */
110
+ sendMessage(content, metadata) {
111
+ if (!this.ws || this.ws.readyState !== import_ws.default.OPEN || !this.agentId) {
112
+ console.error("[UmiRelay] Cannot send message - not connected");
113
+ return;
114
+ }
115
+ const message = {
116
+ type: "agent:message",
117
+ id: this.generateId(),
118
+ timestamp: Date.now(),
119
+ agentId: this.agentId,
120
+ content,
121
+ metadata
122
+ };
123
+ this.ws.send(JSON.stringify(message));
124
+ }
125
+ /**
126
+ * Update agent state (shown in UMI Town)
127
+ */
128
+ updateState(state) {
129
+ if (!this.ws || this.ws.readyState !== import_ws.default.OPEN || !this.agentId) {
130
+ console.error("[UmiRelay] Cannot update state - not connected");
131
+ return;
132
+ }
133
+ const message = {
134
+ type: "agent:state",
135
+ id: this.generateId(),
136
+ timestamp: Date.now(),
137
+ agentId: this.agentId,
138
+ ...state
139
+ };
140
+ this.ws.send(JSON.stringify(message));
141
+ }
142
+ /**
143
+ * Request approval for a transaction
144
+ */
145
+ requestApproval(params) {
146
+ if (!this.ws || this.ws.readyState !== import_ws.default.OPEN || !this.agentId) {
147
+ console.error("[UmiRelay] Cannot request approval - not connected");
148
+ return;
149
+ }
150
+ const message = {
151
+ type: "agent:approval_request",
152
+ id: this.generateId(),
153
+ timestamp: Date.now(),
154
+ agentId: this.agentId,
155
+ ...params
156
+ };
157
+ this.ws.send(JSON.stringify(message));
158
+ }
159
+ /**
160
+ * Register a message handler
161
+ */
162
+ onMessage(handler) {
163
+ this.messageHandlers.add(handler);
164
+ return () => this.messageHandlers.delete(handler);
165
+ }
166
+ /**
167
+ * Check if connected and authenticated
168
+ */
169
+ get isConnected() {
170
+ return this.ws !== null && this.ws.readyState === import_ws.default.OPEN && this.isAuthenticated;
171
+ }
172
+ // ============================================
173
+ // Private Methods
174
+ // ============================================
175
+ authenticate() {
176
+ if (!this.ws) return;
177
+ const authMessage = {
178
+ type: "agent:auth",
179
+ id: this.generateId(),
180
+ timestamp: Date.now(),
181
+ apiKey: this.config.apiKey,
182
+ agentVersion: this.config.agentVersion,
183
+ capabilities: this.config.capabilities
184
+ };
185
+ this.ws.send(JSON.stringify(authMessage));
186
+ }
187
+ handleMessage(message, resolveConnect, rejectConnect) {
188
+ switch (message.type) {
189
+ case "platform:auth_success":
190
+ this.isAuthenticated = true;
191
+ this.agentId = message.agentId ?? null;
192
+ this.reconnectAttempts = 0;
193
+ console.log(`[UmiRelay] Authenticated as ${message.agentName} (${message.agentId})`);
194
+ this.startPing();
195
+ resolveConnect?.();
196
+ break;
197
+ case "platform:auth_failure":
198
+ console.error(`[UmiRelay] Authentication failed: ${message.error}`);
199
+ this.config.autoReconnect = false;
200
+ rejectConnect?.(new Error(message.error));
201
+ break;
202
+ case "platform:message":
203
+ console.log(`[UmiRelay] Received message: ${message.content}`);
204
+ this.notifyHandlers(message);
205
+ break;
206
+ case "platform:task_approve":
207
+ console.log(`[UmiRelay] Task approved: ${message.transactionId}`);
208
+ this.notifyHandlers(message);
209
+ break;
210
+ case "platform:task_reject":
211
+ console.log(`[UmiRelay] Task rejected: ${message.transactionId}`);
212
+ this.notifyHandlers(message);
213
+ break;
214
+ case "platform:ping":
215
+ this.sendPong();
216
+ break;
217
+ default:
218
+ this.notifyHandlers(message);
219
+ }
220
+ }
221
+ notifyHandlers(message) {
222
+ this.messageHandlers.forEach((handler) => {
223
+ try {
224
+ handler(message);
225
+ } catch (error) {
226
+ console.error("[UmiRelay] Handler error:", error);
227
+ }
228
+ });
229
+ }
230
+ sendPong() {
231
+ if (!this.ws || this.ws.readyState !== import_ws.default.OPEN) return;
232
+ this.ws.send(JSON.stringify({
233
+ type: "agent:pong",
234
+ id: this.generateId(),
235
+ timestamp: Date.now(),
236
+ agentId: this.agentId
237
+ }));
238
+ }
239
+ startPing() {
240
+ }
241
+ cleanup() {
242
+ this.isAuthenticated = false;
243
+ if (this.pingInterval) {
244
+ clearInterval(this.pingInterval);
245
+ this.pingInterval = null;
246
+ }
247
+ if (this.reconnectTimeout) {
248
+ clearTimeout(this.reconnectTimeout);
249
+ this.reconnectTimeout = null;
250
+ }
251
+ }
252
+ scheduleReconnect() {
253
+ if (!this.config.autoReconnect) return;
254
+ if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
255
+ console.error("[UmiRelay] Max reconnect attempts reached");
256
+ return;
257
+ }
258
+ this.reconnectAttempts++;
259
+ const delay = Math.min(
260
+ this.config.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1),
261
+ this.config.maxReconnectDelayMs
262
+ );
263
+ console.log(`[UmiRelay] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
264
+ this.reconnectTimeout = setTimeout(() => {
265
+ this.connect().catch((error) => {
266
+ console.error("[UmiRelay] Reconnect failed:", error);
267
+ });
268
+ }, delay);
269
+ }
270
+ generateId() {
271
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
272
+ }
273
+ };
274
+ var relayClient = null;
275
+ var plugin = {
276
+ id: "umi-relay",
277
+ type: "channel",
278
+ async start(ctx, config) {
279
+ relayClient = new UmiRelayClient(config);
280
+ relayClient.onMessage((message) => {
281
+ if (message.type === "platform:message") {
282
+ ctx.injectMessage({
283
+ channel: "umi",
284
+ text: message.content,
285
+ metadata: {
286
+ userId: message.userId,
287
+ agentId: message.agentId
288
+ }
289
+ });
290
+ }
291
+ });
292
+ ctx.registerSend("umi", (response) => {
293
+ if (relayClient?.isConnected) {
294
+ relayClient.sendMessage(response.text || response.content, response.metadata);
295
+ }
296
+ });
297
+ await relayClient.connect();
298
+ console.log("[UmiRelay] Plugin started");
299
+ },
300
+ stop() {
301
+ if (relayClient) {
302
+ relayClient.disconnect();
303
+ relayClient = null;
304
+ }
305
+ console.log("[UmiRelay] Plugin stopped");
306
+ }
307
+ };
308
+ var index_default = plugin;
309
+ // Annotate the CommonJS export names for ESM import in node:
310
+ 0 && (module.exports = {
311
+ UmiRelayClient,
312
+ umiRelayPlugin
313
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,277 @@
1
+ // src/index.ts
2
+ import WebSocket from "ws";
3
+ var UmiRelayClient = class {
4
+ constructor(config) {
5
+ this.ws = null;
6
+ this.reconnectAttempts = 0;
7
+ this.reconnectTimeout = null;
8
+ this.pingInterval = null;
9
+ this.messageHandlers = /* @__PURE__ */ new Set();
10
+ this.agentId = null;
11
+ this.isAuthenticated = false;
12
+ this.config = {
13
+ relayUrl: config.relayUrl || "wss://server.umi.app/ws/relay",
14
+ apiKey: config.apiKey,
15
+ agentVersion: config.agentVersion || "1.0.0",
16
+ capabilities: config.capabilities || ["chat"],
17
+ autoReconnect: config.autoReconnect ?? true,
18
+ maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
19
+ reconnectDelayMs: config.reconnectDelayMs ?? 1e3,
20
+ maxReconnectDelayMs: config.maxReconnectDelayMs ?? 3e4
21
+ };
22
+ }
23
+ /**
24
+ * Connect to the relay server
25
+ */
26
+ connect() {
27
+ return new Promise((resolve, reject) => {
28
+ try {
29
+ const url = `${this.config.relayUrl}?type=agent`;
30
+ console.log(`[UmiRelay] Connecting to ${url}`);
31
+ this.ws = new WebSocket(url);
32
+ this.ws.on("open", () => {
33
+ console.log("[UmiRelay] Connected, authenticating...");
34
+ this.authenticate();
35
+ });
36
+ this.ws.on("message", (data) => {
37
+ try {
38
+ const message = JSON.parse(data.toString());
39
+ this.handleMessage(message, resolve, reject);
40
+ } catch (error) {
41
+ console.error("[UmiRelay] Failed to parse message:", error);
42
+ }
43
+ });
44
+ this.ws.on("close", () => {
45
+ console.log("[UmiRelay] Disconnected");
46
+ this.cleanup();
47
+ this.scheduleReconnect();
48
+ });
49
+ this.ws.on("error", (error) => {
50
+ console.error("[UmiRelay] WebSocket error:", error);
51
+ if (!this.isAuthenticated) {
52
+ reject(error);
53
+ }
54
+ });
55
+ } catch (error) {
56
+ reject(error);
57
+ }
58
+ });
59
+ }
60
+ /**
61
+ * Disconnect from the relay server
62
+ */
63
+ disconnect() {
64
+ this.config.autoReconnect = false;
65
+ this.cleanup();
66
+ if (this.ws) {
67
+ this.ws.close();
68
+ this.ws = null;
69
+ }
70
+ }
71
+ /**
72
+ * Send a chat message through the relay
73
+ */
74
+ sendMessage(content, metadata) {
75
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.agentId) {
76
+ console.error("[UmiRelay] Cannot send message - not connected");
77
+ return;
78
+ }
79
+ const message = {
80
+ type: "agent:message",
81
+ id: this.generateId(),
82
+ timestamp: Date.now(),
83
+ agentId: this.agentId,
84
+ content,
85
+ metadata
86
+ };
87
+ this.ws.send(JSON.stringify(message));
88
+ }
89
+ /**
90
+ * Update agent state (shown in UMI Town)
91
+ */
92
+ updateState(state) {
93
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.agentId) {
94
+ console.error("[UmiRelay] Cannot update state - not connected");
95
+ return;
96
+ }
97
+ const message = {
98
+ type: "agent:state",
99
+ id: this.generateId(),
100
+ timestamp: Date.now(),
101
+ agentId: this.agentId,
102
+ ...state
103
+ };
104
+ this.ws.send(JSON.stringify(message));
105
+ }
106
+ /**
107
+ * Request approval for a transaction
108
+ */
109
+ requestApproval(params) {
110
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.agentId) {
111
+ console.error("[UmiRelay] Cannot request approval - not connected");
112
+ return;
113
+ }
114
+ const message = {
115
+ type: "agent:approval_request",
116
+ id: this.generateId(),
117
+ timestamp: Date.now(),
118
+ agentId: this.agentId,
119
+ ...params
120
+ };
121
+ this.ws.send(JSON.stringify(message));
122
+ }
123
+ /**
124
+ * Register a message handler
125
+ */
126
+ onMessage(handler) {
127
+ this.messageHandlers.add(handler);
128
+ return () => this.messageHandlers.delete(handler);
129
+ }
130
+ /**
131
+ * Check if connected and authenticated
132
+ */
133
+ get isConnected() {
134
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.isAuthenticated;
135
+ }
136
+ // ============================================
137
+ // Private Methods
138
+ // ============================================
139
+ authenticate() {
140
+ if (!this.ws) return;
141
+ const authMessage = {
142
+ type: "agent:auth",
143
+ id: this.generateId(),
144
+ timestamp: Date.now(),
145
+ apiKey: this.config.apiKey,
146
+ agentVersion: this.config.agentVersion,
147
+ capabilities: this.config.capabilities
148
+ };
149
+ this.ws.send(JSON.stringify(authMessage));
150
+ }
151
+ handleMessage(message, resolveConnect, rejectConnect) {
152
+ switch (message.type) {
153
+ case "platform:auth_success":
154
+ this.isAuthenticated = true;
155
+ this.agentId = message.agentId ?? null;
156
+ this.reconnectAttempts = 0;
157
+ console.log(`[UmiRelay] Authenticated as ${message.agentName} (${message.agentId})`);
158
+ this.startPing();
159
+ resolveConnect?.();
160
+ break;
161
+ case "platform:auth_failure":
162
+ console.error(`[UmiRelay] Authentication failed: ${message.error}`);
163
+ this.config.autoReconnect = false;
164
+ rejectConnect?.(new Error(message.error));
165
+ break;
166
+ case "platform:message":
167
+ console.log(`[UmiRelay] Received message: ${message.content}`);
168
+ this.notifyHandlers(message);
169
+ break;
170
+ case "platform:task_approve":
171
+ console.log(`[UmiRelay] Task approved: ${message.transactionId}`);
172
+ this.notifyHandlers(message);
173
+ break;
174
+ case "platform:task_reject":
175
+ console.log(`[UmiRelay] Task rejected: ${message.transactionId}`);
176
+ this.notifyHandlers(message);
177
+ break;
178
+ case "platform:ping":
179
+ this.sendPong();
180
+ break;
181
+ default:
182
+ this.notifyHandlers(message);
183
+ }
184
+ }
185
+ notifyHandlers(message) {
186
+ this.messageHandlers.forEach((handler) => {
187
+ try {
188
+ handler(message);
189
+ } catch (error) {
190
+ console.error("[UmiRelay] Handler error:", error);
191
+ }
192
+ });
193
+ }
194
+ sendPong() {
195
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
196
+ this.ws.send(JSON.stringify({
197
+ type: "agent:pong",
198
+ id: this.generateId(),
199
+ timestamp: Date.now(),
200
+ agentId: this.agentId
201
+ }));
202
+ }
203
+ startPing() {
204
+ }
205
+ cleanup() {
206
+ this.isAuthenticated = false;
207
+ if (this.pingInterval) {
208
+ clearInterval(this.pingInterval);
209
+ this.pingInterval = null;
210
+ }
211
+ if (this.reconnectTimeout) {
212
+ clearTimeout(this.reconnectTimeout);
213
+ this.reconnectTimeout = null;
214
+ }
215
+ }
216
+ scheduleReconnect() {
217
+ if (!this.config.autoReconnect) return;
218
+ if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
219
+ console.error("[UmiRelay] Max reconnect attempts reached");
220
+ return;
221
+ }
222
+ this.reconnectAttempts++;
223
+ const delay = Math.min(
224
+ this.config.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1),
225
+ this.config.maxReconnectDelayMs
226
+ );
227
+ console.log(`[UmiRelay] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
228
+ this.reconnectTimeout = setTimeout(() => {
229
+ this.connect().catch((error) => {
230
+ console.error("[UmiRelay] Reconnect failed:", error);
231
+ });
232
+ }, delay);
233
+ }
234
+ generateId() {
235
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
236
+ }
237
+ };
238
+ var relayClient = null;
239
+ var plugin = {
240
+ id: "umi-relay",
241
+ type: "channel",
242
+ async start(ctx, config) {
243
+ relayClient = new UmiRelayClient(config);
244
+ relayClient.onMessage((message) => {
245
+ if (message.type === "platform:message") {
246
+ ctx.injectMessage({
247
+ channel: "umi",
248
+ text: message.content,
249
+ metadata: {
250
+ userId: message.userId,
251
+ agentId: message.agentId
252
+ }
253
+ });
254
+ }
255
+ });
256
+ ctx.registerSend("umi", (response) => {
257
+ if (relayClient?.isConnected) {
258
+ relayClient.sendMessage(response.text || response.content, response.metadata);
259
+ }
260
+ });
261
+ await relayClient.connect();
262
+ console.log("[UmiRelay] Plugin started");
263
+ },
264
+ stop() {
265
+ if (relayClient) {
266
+ relayClient.disconnect();
267
+ relayClient = null;
268
+ }
269
+ console.log("[UmiRelay] Plugin stopped");
270
+ }
271
+ };
272
+ var index_default = plugin;
273
+ export {
274
+ UmiRelayClient,
275
+ index_default as default,
276
+ plugin as umiRelayPlugin
277
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@umimoney/clawdbot-relay-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Clawdbot plugin for connecting to UMI relay server",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "clawdbot",
17
+ "umi",
18
+ "relay",
19
+ "agent",
20
+ "plugin"
21
+ ],
22
+ "author": "UMI Labs",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/anthropics/claude-code"
27
+ },
28
+ "dependencies": {
29
+ "ws": "^8.18.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/ws": "^8.5.10",
33
+ "tsup": "^8.0.0",
34
+ "typescript": "^5.3.0"
35
+ },
36
+ "peerDependencies": {
37
+ "@anthropic-ai/claude-code": "*"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@anthropic-ai/claude-code": {
41
+ "optional": true
42
+ }
43
+ }
44
+ }