@insta-dev01/intclaw 1.0.9 → 1.0.11

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.
@@ -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.9",
5
+ "version": "1.0.11",
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
- "apiKey": {
17
+ "appKey": {
18
18
  "type": "string",
19
- "description": "API key for authentication with IntClaw server"
19
+ "description": "APP key for authentication with IntClaw server"
20
20
  },
21
- "apiSecret": {
21
+ "appSecret": {
22
22
  "type": "string",
23
- "description": "API secret for authentication with IntClaw server"
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.9",
3
+ "version": "1.0.11",
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 = '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
- * IntClaw Channel Plugin for OpenClaw
3
- *
4
- * This plugin provides a WebSocket-based channel for connecting to IntClaw services.
5
- * It handles bidirectional message flow between OpenClaw and IntClaw servers.
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 { IntClawChannel } from './channel/IntClawChannel.js';
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
- // Skip if required configuration is missing
34
- if (!config?.wsUrl || !config?.apiKey) {
35
- console.log('[IntClaw] Configuration missing (wsUrl, apiKey)');
17
+ if (!config?.appKey || !config?.appSecret) {
18
+ console.log(JSON.stringify({ error: "missing_config_keys" }));
36
19
  return;
37
20
  }
38
21
 
39
- await registerChannel(gateway, config);
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
- }