@insta-dev01/intclaw 1.0.9 → 1.0.10
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/openclaw.plugin.json +5 -5
- package/package.json +3 -2
- package/src/channel/intclaw_channel.js +155 -0
- package/src/index.js +10 -27
- package/src/channel/IntClawChannel.js +0 -309
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "intclaw",
|
|
3
3
|
"name": "IntClaw Plugin",
|
|
4
4
|
"description": "OpenClaw plugin for IntClaw services - WebSocket-based channel integration for Community Platform, Message Channel, and Agent Collaboration Engine",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.10",
|
|
6
6
|
"kind": "channel",
|
|
7
7
|
"channels": ["intclaw"],
|
|
8
8
|
"configSchema": {
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"description": "Enable/disable the IntClaw channel",
|
|
15
15
|
"default": true
|
|
16
16
|
},
|
|
17
|
-
"
|
|
17
|
+
"appKey": {
|
|
18
18
|
"type": "string",
|
|
19
|
-
"description": "
|
|
19
|
+
"description": "APP key for authentication with IntClaw server"
|
|
20
20
|
},
|
|
21
|
-
"
|
|
21
|
+
"appSecret": {
|
|
22
22
|
"type": "string",
|
|
23
|
-
"description": "
|
|
23
|
+
"description": "APP secret for authentication with IntClaw server"
|
|
24
24
|
},
|
|
25
25
|
"shadowInstance": {
|
|
26
26
|
"type": "boolean",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@insta-dev01/intclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin for IntClaw services - WebSocket-based channel integration for Community Platform, Message Channel, and Agent Collaboration Engine",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"test": "node --test src/__tests__/**/*.test.js",
|
|
9
9
|
"test:watch": "node --test --watch src/__tests__/**/*.test.js",
|
|
10
10
|
"prepublishOnly": "pnpm test",
|
|
11
|
-
"publish:public": "npm publish --access public --registry=https://registry.npmjs.org/"
|
|
11
|
+
"publish:public": "npm publish --access public --registry=https://registry.npmjs.org/",
|
|
12
|
+
"tologin":"npm login --registry=https://registry.npmjs.org/"
|
|
12
13
|
},
|
|
13
14
|
"keywords": [
|
|
14
15
|
"openclaw",
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ---
|
|
3
|
+
* status: active
|
|
4
|
+
* birth_time: "2026-03-19T09:42:00Z"
|
|
5
|
+
* original_intent: "Provide IntClaw bidirectional websocket channel and proxy messages to OpenClaw gateway using chunked streaming"
|
|
6
|
+
* version_count: 1
|
|
7
|
+
* ---
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import WebSocket from 'ws';
|
|
11
|
+
|
|
12
|
+
export async function start_intclaw_channel(gateway, config) {
|
|
13
|
+
const ws_url = config.wsUrl || 'wss://claw-dev.int-os.com/user-ws/';
|
|
14
|
+
|
|
15
|
+
const ws_conn = new WebSocket(ws_url, {
|
|
16
|
+
headers: {
|
|
17
|
+
'X-App-Key': config.appKey,
|
|
18
|
+
'X-App-Secret': config.appSecret
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
ws_conn.on('open', () => {
|
|
23
|
+
const auth_payload = {
|
|
24
|
+
type: 'auth_request',
|
|
25
|
+
app_key: config.appKey,
|
|
26
|
+
timestamp: Date.now()
|
|
27
|
+
};
|
|
28
|
+
ws_conn.send(JSON.stringify(auth_payload));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
ws_conn.on('message', async (data) => {
|
|
32
|
+
try {
|
|
33
|
+
const msg = JSON.parse(data.toString());
|
|
34
|
+
|
|
35
|
+
if (msg.type === 'auth_response') {
|
|
36
|
+
if (msg.success && gateway.notifyChannelReady) {
|
|
37
|
+
await gateway.notifyChannelReady('intclaw');
|
|
38
|
+
}
|
|
39
|
+
} else if (msg.type === 'incoming_message') {
|
|
40
|
+
process_incoming_message(msg.payload, ws_conn, config);
|
|
41
|
+
} else if (msg.type === 'ping') {
|
|
42
|
+
ws_conn.send(JSON.stringify({ type: 'pong' }));
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error(JSON.stringify({ error: "failed_to_handle_msg", reason: err.message }));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
ws_conn.on('error', (err) => {
|
|
50
|
+
console.error(JSON.stringify({ error: "ws_error", reason: err.message }));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
ws_conn.on('close', (code, reason) => {
|
|
54
|
+
console.log(JSON.stringify({ event: "ws_closed", code, reason: reason?.toString() }));
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
start_intclaw_channel(gateway, config);
|
|
57
|
+
}, 5000);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return ws_conn;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function process_incoming_message(payload, ws_conn, config) {
|
|
64
|
+
const session_context = {
|
|
65
|
+
channel: 'intclaw',
|
|
66
|
+
account_id: payload.accountId || 'default',
|
|
67
|
+
chat_type: payload.peerKind || 'direct',
|
|
68
|
+
peer_id: payload.peerId
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const session_key = JSON.stringify(session_context);
|
|
72
|
+
const user_content = payload.text;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
for await (const chunk of stream_from_gateway(user_content, session_key, config)) {
|
|
76
|
+
send_stream_chunk(ws_conn, payload, chunk, false);
|
|
77
|
+
}
|
|
78
|
+
// send end of stream marker
|
|
79
|
+
send_stream_chunk(ws_conn, payload, "", true);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(JSON.stringify({ error: "gateway_stream_failed", reason: err.message }));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function* stream_from_gateway(user_content, session_key, config) {
|
|
86
|
+
const port = config.gatewayPort || 18789;
|
|
87
|
+
const url = `http://127.0.0.1:${port}/v1/chat/completions`;
|
|
88
|
+
|
|
89
|
+
const resp = await fetch(url, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
'X-OpenClaw-Agent-Id': 'main'
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
model: 'main',
|
|
97
|
+
messages: [{ role: 'user', content: user_content }],
|
|
98
|
+
stream: true,
|
|
99
|
+
user: session_key
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (!resp.ok) {
|
|
104
|
+
const err_text = await resp.text();
|
|
105
|
+
throw new Error(`status_${resp.status}_${err_text}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const reader = resp.body.getReader();
|
|
109
|
+
const decoder = new TextDecoder();
|
|
110
|
+
let buffer = '';
|
|
111
|
+
|
|
112
|
+
while (true) {
|
|
113
|
+
const { done, value } = await reader.read();
|
|
114
|
+
if (done) break;
|
|
115
|
+
|
|
116
|
+
buffer += decoder.decode(value, { stream: true });
|
|
117
|
+
const lines = buffer.split('\n');
|
|
118
|
+
buffer = lines.pop() || '';
|
|
119
|
+
|
|
120
|
+
for (const line of lines) {
|
|
121
|
+
if (!line.startsWith('data: ')) continue;
|
|
122
|
+
const data = line.slice(6).trim();
|
|
123
|
+
if (data === '[DONE]') return;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const chunk_obj = JSON.parse(data);
|
|
127
|
+
const content = chunk_obj.choices?.[0]?.delta?.content;
|
|
128
|
+
if (content) yield content;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
// partial chunk, ignore
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function send_stream_chunk(ws_conn, incoming_payload, chunk_text, is_done) {
|
|
137
|
+
if (ws_conn.readyState !== WebSocket.OPEN) return;
|
|
138
|
+
|
|
139
|
+
const out_msg = {
|
|
140
|
+
type: 'outgoing_message',
|
|
141
|
+
payload: {
|
|
142
|
+
id: `intclaw_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
143
|
+
account_id: incoming_payload.accountId || 'default',
|
|
144
|
+
peer_id: incoming_payload.peerId,
|
|
145
|
+
peer_kind: incoming_payload.peerKind,
|
|
146
|
+
text: chunk_text,
|
|
147
|
+
reply_to_id: incoming_payload.id,
|
|
148
|
+
timestamp: Date.now(),
|
|
149
|
+
is_chunk: true,
|
|
150
|
+
is_done: is_done
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
ws_conn.send(JSON.stringify(out_msg));
|
|
155
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,40 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* ---
|
|
3
|
+
* status: active
|
|
4
|
+
* birth_time: "2026-03-19T09:42:00Z"
|
|
5
|
+
* original_intent: "Entry point for IntClaw plugin"
|
|
6
|
+
* version_count: 2
|
|
7
|
+
* ---
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
|
-
import {
|
|
10
|
+
import { start_intclaw_channel } from './channel/intclaw_channel.js';
|
|
9
11
|
|
|
10
|
-
/**
|
|
11
|
-
* Register the IntClaw channel with OpenClaw
|
|
12
|
-
* @param {Object} gateway - OpenClaw gateway instance
|
|
13
|
-
* @param {Object} config - Channel configuration
|
|
14
|
-
* @returns {Promise<void>}
|
|
15
|
-
*/
|
|
16
|
-
export async function registerChannel(gateway, config) {
|
|
17
|
-
const channel = new IntClawChannel(gateway, config);
|
|
18
|
-
await channel.start();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Plugin initialization function (OpenClaw entry point)
|
|
23
|
-
* @param {Object} gateway - OpenClaw gateway instance
|
|
24
|
-
* @param {Object} config - Plugin configuration
|
|
25
|
-
* @returns {Promise<void>}
|
|
26
|
-
*/
|
|
27
12
|
export async function register(gateway, config) {
|
|
28
|
-
// Skip if explicitly disabled
|
|
29
13
|
if (config?.enabled === false) {
|
|
30
14
|
return;
|
|
31
15
|
}
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
console.log('[IntClaw] Configuration missing (wsUrl, apiKey)');
|
|
17
|
+
if (!config?.wsUrl || !config?.appKey || !config?.appSecret) {
|
|
18
|
+
console.log(JSON.stringify({ error: "missing_config_wsurl_or_keys" }));
|
|
36
19
|
return;
|
|
37
20
|
}
|
|
38
21
|
|
|
39
|
-
await
|
|
22
|
+
await start_intclaw_channel(gateway, config);
|
|
40
23
|
}
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IntClaw Channel Implementation
|
|
3
|
-
*
|
|
4
|
-
* Handles WebSocket connection and message routing for IntClaw services.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const CHANNEL_ID = 'intclaw';
|
|
8
|
-
const DEFAULT_WS_URL = 'wss://claw-message.int-os.com';
|
|
9
|
-
const DEFAULT_RECONNECT_INTERVAL = 5000;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* IntClaw message types
|
|
13
|
-
*/
|
|
14
|
-
export const MessageTypes = {
|
|
15
|
-
// Incoming messages from IntClaw server
|
|
16
|
-
INCOMING_MESSAGE: 'incoming_message',
|
|
17
|
-
// Outgoing messages to IntClaw server
|
|
18
|
-
OUTGOING_MESSAGE: 'outgoing_message',
|
|
19
|
-
// Connection status
|
|
20
|
-
CONNECTION_STATUS: 'connection_status',
|
|
21
|
-
// Authentication
|
|
22
|
-
AUTH_REQUEST: 'auth_request',
|
|
23
|
-
AUTH_RESPONSE: 'auth_response',
|
|
24
|
-
// Heartbeat
|
|
25
|
-
PING: 'ping',
|
|
26
|
-
PONG: 'pong',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* IntClaw peer kinds
|
|
31
|
-
*/
|
|
32
|
-
export const PeerKind = {
|
|
33
|
-
DIRECT: 'direct',
|
|
34
|
-
GROUP: 'group',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export class IntClawChannel {
|
|
38
|
-
#gateway;
|
|
39
|
-
#config;
|
|
40
|
-
#ws = null;
|
|
41
|
-
#reconnectTimer = null;
|
|
42
|
-
#isShuttingDown = false;
|
|
43
|
-
#isAuthenticated = false;
|
|
44
|
-
|
|
45
|
-
constructor(gateway, config) {
|
|
46
|
-
this.#gateway = gateway;
|
|
47
|
-
this.#config = config;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Start the channel
|
|
52
|
-
*/
|
|
53
|
-
async start() {
|
|
54
|
-
this.#log('info', 'Starting IntClaw channel...');
|
|
55
|
-
|
|
56
|
-
if (!this.#config.apiKey) {
|
|
57
|
-
throw new Error('IntClaw channel requires apiKey in configuration');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
await this.#connect();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Stop the channel
|
|
65
|
-
*/
|
|
66
|
-
async stop() {
|
|
67
|
-
this.#log('info', 'Stopping IntClaw channel...');
|
|
68
|
-
this.#isShuttingDown = true;
|
|
69
|
-
|
|
70
|
-
if (this.#reconnectTimer) {
|
|
71
|
-
clearTimeout(this.#reconnectTimer);
|
|
72
|
-
this.#reconnectTimer = null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (this.#ws) {
|
|
76
|
-
this.#ws.close();
|
|
77
|
-
this.#ws = null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.#log('info', 'IntClaw channel stopped');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Connect to IntClaw WebSocket server
|
|
85
|
-
*/
|
|
86
|
-
async #connect() {
|
|
87
|
-
if (this.#isShuttingDown) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const wsUrl = this.#config.wsUrl || DEFAULT_WS_URL;
|
|
93
|
-
this.#log('info', `Connecting to IntClaw server: ${wsUrl}`);
|
|
94
|
-
|
|
95
|
-
// Import ws module dynamically
|
|
96
|
-
const WebSocket = (await import('ws')).default;
|
|
97
|
-
|
|
98
|
-
this.#ws = new WebSocket(wsUrl, {
|
|
99
|
-
headers: {
|
|
100
|
-
'X-API-Key': this.#config.apiKey,
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
this.#ws.on('open', () => this.#onOpen());
|
|
105
|
-
this.#ws.on('message', (data) => this.#onMessage(data));
|
|
106
|
-
this.#ws.on('error', (error) => this.#onError(error));
|
|
107
|
-
this.#ws.on('close', (code, reason) => this.#onClose(code, reason));
|
|
108
|
-
|
|
109
|
-
} catch (error) {
|
|
110
|
-
this.#log('error', `Connection failed: ${error.message}`);
|
|
111
|
-
this.#scheduleReconnect();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Handle WebSocket open event
|
|
117
|
-
*/
|
|
118
|
-
#onOpen() {
|
|
119
|
-
this.#log('info', 'Connected to IntClaw server');
|
|
120
|
-
|
|
121
|
-
// Send authentication request
|
|
122
|
-
this.#send({
|
|
123
|
-
type: MessageTypes.AUTH_REQUEST,
|
|
124
|
-
apiKey: this.#config.apiKey,
|
|
125
|
-
timestamp: Date.now(),
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Handle WebSocket message event
|
|
131
|
-
*/
|
|
132
|
-
async #onMessage(data) {
|
|
133
|
-
try {
|
|
134
|
-
const message = JSON.parse(data.toString());
|
|
135
|
-
this.#log('debug', `Received message type: ${message.type}`);
|
|
136
|
-
|
|
137
|
-
switch (message.type) {
|
|
138
|
-
case MessageTypes.AUTH_RESPONSE:
|
|
139
|
-
await this.#handleAuthResponse(message);
|
|
140
|
-
break;
|
|
141
|
-
|
|
142
|
-
case MessageTypes.INCOMING_MESSAGE:
|
|
143
|
-
await this.#handleIncomingMessage(message);
|
|
144
|
-
break;
|
|
145
|
-
|
|
146
|
-
case MessageTypes.PING:
|
|
147
|
-
this.#send({ type: MessageTypes.PONG });
|
|
148
|
-
break;
|
|
149
|
-
|
|
150
|
-
case MessageTypes.PONG:
|
|
151
|
-
// Pong received, connection is alive
|
|
152
|
-
break;
|
|
153
|
-
|
|
154
|
-
default:
|
|
155
|
-
this.#log('warn', `Unknown message type: ${message.type}`);
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
this.#log('error', `Failed to handle message: ${error.message}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Handle authentication response
|
|
164
|
-
*/
|
|
165
|
-
async #handleAuthResponse(message) {
|
|
166
|
-
if (message.success) {
|
|
167
|
-
this.#isAuthenticated = true;
|
|
168
|
-
this.#log('info', 'Authenticated with IntClaw server');
|
|
169
|
-
|
|
170
|
-
// Notify gateway that channel is ready
|
|
171
|
-
if (this.#gateway?.notifyChannelReady) {
|
|
172
|
-
await this.#gateway.notifyChannelReady(CHANNEL_ID);
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
this.#log('error', `Authentication failed: ${message.error || 'Unknown error'}`);
|
|
176
|
-
this.#ws.close();
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Handle incoming message from IntClaw server
|
|
182
|
-
*/
|
|
183
|
-
async #handleIncomingMessage(message) {
|
|
184
|
-
if (!this.#isAuthenticated) {
|
|
185
|
-
this.#log('warn', 'Ignoring message: not authenticated');
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const { payload } = message;
|
|
190
|
-
|
|
191
|
-
// Normalize message for OpenClaw gateway
|
|
192
|
-
const normalizedMessage = {
|
|
193
|
-
channel: CHANNEL_ID,
|
|
194
|
-
accountId: payload.accountId || 'default',
|
|
195
|
-
peer: {
|
|
196
|
-
kind: payload.peerKind || PeerKind.DIRECT,
|
|
197
|
-
id: payload.peerId,
|
|
198
|
-
name: payload.peerName,
|
|
199
|
-
},
|
|
200
|
-
text: payload.text,
|
|
201
|
-
timestamp: payload.timestamp || Date.now(),
|
|
202
|
-
id: payload.id,
|
|
203
|
-
threadId: payload.threadId,
|
|
204
|
-
replyToId: payload.replyToId,
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Send to gateway for processing
|
|
208
|
-
if (this.#gateway?.handleChannelMessage) {
|
|
209
|
-
await this.#gateway.handleChannelMessage(normalizedMessage);
|
|
210
|
-
} else {
|
|
211
|
-
this.#log('warn', 'Gateway does not support handleChannelMessage');
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Handle WebSocket error event
|
|
217
|
-
*/
|
|
218
|
-
#onError(error) {
|
|
219
|
-
this.#log('error', `WebSocket error: ${error.message}`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Handle WebSocket close event
|
|
224
|
-
*/
|
|
225
|
-
#onClose(code, reason) {
|
|
226
|
-
this.#log('info', `Connection closed: ${code} - ${reason || 'No reason'}`);
|
|
227
|
-
this.#isAuthenticated = false;
|
|
228
|
-
|
|
229
|
-
if (!this.#isShuttingDown) {
|
|
230
|
-
this.#scheduleReconnect();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Schedule reconnection attempt
|
|
236
|
-
*/
|
|
237
|
-
#scheduleReconnect() {
|
|
238
|
-
const interval = this.#config.reconnectInterval || DEFAULT_RECONNECT_INTERVAL;
|
|
239
|
-
|
|
240
|
-
this.#log('info', `Reconnecting in ${interval}ms...`);
|
|
241
|
-
|
|
242
|
-
this.#reconnectTimer = setTimeout(() => {
|
|
243
|
-
this.#connect();
|
|
244
|
-
}, interval);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Send message to IntClaw server
|
|
249
|
-
*/
|
|
250
|
-
#send(message) {
|
|
251
|
-
if (this.#ws && this.#ws.readyState === 1 /* OPEN */) {
|
|
252
|
-
this.#ws.send(JSON.stringify(message));
|
|
253
|
-
} else {
|
|
254
|
-
this.#log('warn', 'Cannot send message: WebSocket not connected');
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Send message action
|
|
260
|
-
*/
|
|
261
|
-
async send(message) {
|
|
262
|
-
const payload = {
|
|
263
|
-
type: MessageTypes.OUTGOING_MESSAGE,
|
|
264
|
-
payload: {
|
|
265
|
-
id: message.id || this.#generateMessageId(),
|
|
266
|
-
accountId: message.accountId || 'default',
|
|
267
|
-
peerId: message.peer.id,
|
|
268
|
-
peerKind: message.peer.kind,
|
|
269
|
-
text: message.text,
|
|
270
|
-
threadId: message.threadId,
|
|
271
|
-
replyToId: message.replyToId,
|
|
272
|
-
timestamp: Date.now(),
|
|
273
|
-
},
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
this.#send(payload);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Generate unique message ID
|
|
281
|
-
*/
|
|
282
|
-
#generateMessageId() {
|
|
283
|
-
return `intclaw_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Log message
|
|
288
|
-
*/
|
|
289
|
-
#log(level, message) {
|
|
290
|
-
const logMessage = `[IntClaw Channel] ${message}`;
|
|
291
|
-
|
|
292
|
-
switch (level) {
|
|
293
|
-
case 'error':
|
|
294
|
-
console.error(logMessage);
|
|
295
|
-
break;
|
|
296
|
-
case 'warn':
|
|
297
|
-
console.warn(logMessage);
|
|
298
|
-
break;
|
|
299
|
-
case 'debug':
|
|
300
|
-
// Only log debug in development
|
|
301
|
-
if (process.env.NODE_ENV === 'development') {
|
|
302
|
-
console.log(logMessage);
|
|
303
|
-
}
|
|
304
|
-
break;
|
|
305
|
-
default:
|
|
306
|
-
console.log(logMessage);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|