@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.
- package/dist/index.d.mts +115 -0
- package/dist/index.d.ts +115 -0
- package/dist/index.js +313 -0
- package/dist/index.mjs +277 -0
- package/package.json +44 -0
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|