@insta-dev01/intclaw 1.0.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/.env.example +11 -0
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/openclaw.plugin.json +141 -0
- package/package.json +57 -0
- package/skills/intclaw_matrix/SKILL.md +20 -0
- package/src/channel/IntClawChannel.js +311 -0
- package/src/index.js +31 -0
package/.env.example
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# IntClaw Plugin Configuration Example
|
|
2
|
+
# Copy this to .env or add to your OpenClaw config
|
|
3
|
+
|
|
4
|
+
# WebSocket Server URL (required)
|
|
5
|
+
INTCLAW_WS_URL=wss://api.intclaw.example.com/ws
|
|
6
|
+
|
|
7
|
+
# API Key for authentication (required)
|
|
8
|
+
INTCLAW_API_KEY=your-api-key-here
|
|
9
|
+
|
|
10
|
+
# Reconnection interval in milliseconds (optional, default: 5000)
|
|
11
|
+
# INTCLAW_RECONNECT_INTERVAL=5000
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 引态科技
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# @insta-sdk/intclaw
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin for IntClaw services - provides WebSocket-based channel integration connecting OpenClaw to:
|
|
4
|
+
|
|
5
|
+
- IntClaw Community Platform (引态社区平台)
|
|
6
|
+
- IntClaw Message Channel (引态消息通道)
|
|
7
|
+
- IntClaw Agent Collaboration Engine (引态智能体协作引擎)
|
|
8
|
+
- IntClaw Claw Hub Services (引态 Claw Hub 服务)
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
This plugin implements a bidirectional WebSocket channel that:
|
|
13
|
+
- Connects to IntClaw servers using WebSocket protocol
|
|
14
|
+
- Authenticates via API Key/Token
|
|
15
|
+
- Exchanges JSON-formatted messages
|
|
16
|
+
- Supports both direct messages and group messages
|
|
17
|
+
- Handles automatic reconnection on connection loss
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Install from npm
|
|
23
|
+
openclaw plugins install @insta-sdk/intclaw
|
|
24
|
+
|
|
25
|
+
# Install from local directory (for development)
|
|
26
|
+
openclaw plugins install /path/to/intclaw
|
|
27
|
+
|
|
28
|
+
# Install dependencies
|
|
29
|
+
cd /path/to/intclaw
|
|
30
|
+
pnpm install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
### Method 1: Interactive Setup Wizard (Recommended)
|
|
36
|
+
|
|
37
|
+
Run the setup wizard and follow the prompts:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
openclaw channels add
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Select **IntClaw** from the channel list, then enter:
|
|
44
|
+
- **WebSocket Server URL**: Your IntClaw server WebSocket URL (e.g., `wss://api.intclaw.example.com/ws`)
|
|
45
|
+
- **API Key**: Your API key for authentication
|
|
46
|
+
|
|
47
|
+
The wizard will guide you through additional optional settings like DM policy and group policy.
|
|
48
|
+
|
|
49
|
+
### Method 2: Configuration File
|
|
50
|
+
|
|
51
|
+
Edit your OpenClaw config file at `~/.openclaw/openclaw.json`:
|
|
52
|
+
|
|
53
|
+
```json5
|
|
54
|
+
{
|
|
55
|
+
channels: {
|
|
56
|
+
intclaw: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
wsUrl: "wss://api.intclaw.example.com/ws",
|
|
59
|
+
apiKey: "your-api-key-here",
|
|
60
|
+
dmPolicy: "pairing",
|
|
61
|
+
groupPolicy: "allowlist",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
After editing, restart the gateway:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
openclaw gateway restart
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Method 3: Environment Variables
|
|
74
|
+
|
|
75
|
+
Set environment variables before starting OpenClaw:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
export INTCLAW_WS_URL="wss://api.intclaw.example.com/ws"
|
|
79
|
+
export INTCLAW_API_KEY="your-api-key-here"
|
|
80
|
+
openclaw gateway
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Note**: Configuration file values take precedence over environment variables.
|
|
84
|
+
|
|
85
|
+
### Full Configuration Options
|
|
86
|
+
|
|
87
|
+
| Option | Type | Description | Default |
|
|
88
|
+
|--------|------|-------------|---------|
|
|
89
|
+
| `enabled` | boolean | Enable/disable the channel | `true` |
|
|
90
|
+
| `wsUrl` | string | WebSocket server URL | **Required** |
|
|
91
|
+
| `apiKey` | string | API key for authentication | **Required** |
|
|
92
|
+
| `reconnectInterval` | number | Reconnection interval (ms) | `5000` |
|
|
93
|
+
| `dmPolicy` | string | DM policy (`pairing`, `allowlist`, `open`, `disabled`) | `pairing` |
|
|
94
|
+
| `allowFrom` | array | DM allowlist (peer IDs) | - |
|
|
95
|
+
| `groupPolicy` | string | Group policy (`allowlist`, `open`, `disabled`) | `allowlist` |
|
|
96
|
+
| `groupAllowFrom` | array | Group allowlist (peer IDs) | - |
|
|
97
|
+
| `groups` | object | Per-group configuration | - |
|
|
98
|
+
|
|
99
|
+
### Multi-Account Configuration
|
|
100
|
+
|
|
101
|
+
```json5
|
|
102
|
+
{
|
|
103
|
+
channels: {
|
|
104
|
+
intclaw: {
|
|
105
|
+
enabled: true,
|
|
106
|
+
defaultAccount: "main",
|
|
107
|
+
accounts: {
|
|
108
|
+
main: {
|
|
109
|
+
wsUrl: "wss://main.intclaw.example.com/ws",
|
|
110
|
+
apiKey: "main-api-key",
|
|
111
|
+
enabled: true,
|
|
112
|
+
},
|
|
113
|
+
secondary: {
|
|
114
|
+
wsUrl: "wss://secondary.intclaw.example.com/ws",
|
|
115
|
+
apiKey: "secondary-api-key",
|
|
116
|
+
enabled: true,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Message Protocol
|
|
125
|
+
|
|
126
|
+
### Incoming Messages (IntClaw → OpenClaw)
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"type": "incoming_message",
|
|
131
|
+
"payload": {
|
|
132
|
+
"id": "msg_1234567890",
|
|
133
|
+
"accountId": "default",
|
|
134
|
+
"peerId": "user_abc",
|
|
135
|
+
"peerKind": "direct",
|
|
136
|
+
"peerName": "Alice",
|
|
137
|
+
"text": "Hello from IntClaw!",
|
|
138
|
+
"timestamp": 1704067200000,
|
|
139
|
+
"threadId": null,
|
|
140
|
+
"replyToId": null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Outgoing Messages (OpenClaw → IntClaw)
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"type": "outgoing_message",
|
|
150
|
+
"payload": {
|
|
151
|
+
"id": "intclaw_1704067200000_abc123",
|
|
152
|
+
"accountId": "default",
|
|
153
|
+
"peerId": "user_abc",
|
|
154
|
+
"peerKind": "direct",
|
|
155
|
+
"text": "Hello from OpenClaw!",
|
|
156
|
+
"timestamp": 1704067200000,
|
|
157
|
+
"threadId": null,
|
|
158
|
+
"replyToId": null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Authentication Flow
|
|
164
|
+
|
|
165
|
+
1. Client connects with API key in header
|
|
166
|
+
2. Client sends `auth_request`:
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"type": "auth_request",
|
|
170
|
+
"apiKey": "your-api-key",
|
|
171
|
+
"timestamp": 1704067200000
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
3. Server responds with `auth_response`:
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"type": "auth_response",
|
|
178
|
+
"success": true,
|
|
179
|
+
"timestamp": 1704067200000
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Development
|
|
184
|
+
|
|
185
|
+
### Requirements
|
|
186
|
+
|
|
187
|
+
- Node.js 22.x+
|
|
188
|
+
- pnpm 10.25.0+
|
|
189
|
+
|
|
190
|
+
### Project Structure
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
intclaw-plugin/
|
|
194
|
+
├── openclaw.plugin.json # Plugin manifest
|
|
195
|
+
├── package.json # Node.js package config
|
|
196
|
+
├── src/
|
|
197
|
+
│ ├── index.js # Plugin entry point
|
|
198
|
+
│ └── channel/
|
|
199
|
+
│ └── IntClawChannel.js # Channel implementation
|
|
200
|
+
└── skills/ # Optional skills
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Running Locally
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Install dependencies
|
|
207
|
+
pnpm install
|
|
208
|
+
|
|
209
|
+
# The plugin will be loaded by OpenClaw gateway
|
|
210
|
+
# No standalone run mode
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Troubleshooting
|
|
214
|
+
|
|
215
|
+
### Connection Issues
|
|
216
|
+
|
|
217
|
+
1. Check WebSocket URL is correct and accessible
|
|
218
|
+
2. Verify API key is valid
|
|
219
|
+
3. Check firewall/network settings
|
|
220
|
+
4. Review logs: `openclaw logs --follow`
|
|
221
|
+
|
|
222
|
+
### Authentication Failures
|
|
223
|
+
|
|
224
|
+
1. Verify API key matches server expectations
|
|
225
|
+
2. Check API key hasn't expired
|
|
226
|
+
3. Ensure API key has required permissions
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
ISC
|
|
231
|
+
|
|
232
|
+
## Development Guide
|
|
233
|
+
|
|
234
|
+
- [OpenClaw Plugin Development Guide](Development%20Guide%20docs/)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "intclaw-plugin",
|
|
3
|
+
"name": "IntClaw Plugin",
|
|
4
|
+
"description": "OpenClaw plugin for IntClaw services - WebSocket-based channel integration for Community Platform, Message Channel, and Agent Collaboration Engine",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"kind": "channel",
|
|
7
|
+
"channels": ["intclaw"],
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {
|
|
12
|
+
"enabled": {
|
|
13
|
+
"type": "boolean",
|
|
14
|
+
"description": "Enable/disable the IntClaw channel",
|
|
15
|
+
"default": true
|
|
16
|
+
},
|
|
17
|
+
"wsUrl": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "WebSocket server URL for IntClaw connection (e.g., wss://api.intclaw.example.com/ws)",
|
|
20
|
+
"format": "uri"
|
|
21
|
+
},
|
|
22
|
+
"apiKey": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "API key for authentication with IntClaw server"
|
|
25
|
+
},
|
|
26
|
+
"reconnectInterval": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "Reconnection interval in milliseconds",
|
|
29
|
+
"default": 5000,
|
|
30
|
+
"minimum": 1000
|
|
31
|
+
},
|
|
32
|
+
"dmPolicy": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"enum": ["pairing", "allowlist", "open", "disabled"],
|
|
35
|
+
"description": "Direct message policy",
|
|
36
|
+
"default": "pairing"
|
|
37
|
+
},
|
|
38
|
+
"allowFrom": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"description": "DM allowlist (peer IDs)",
|
|
41
|
+
"items": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"groupPolicy": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"enum": ["allowlist", "open", "disabled"],
|
|
48
|
+
"description": "Group message policy",
|
|
49
|
+
"default": "allowlist"
|
|
50
|
+
},
|
|
51
|
+
"groupAllowFrom": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"description": "Group allowlist (peer IDs)",
|
|
54
|
+
"items": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"groups": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"description": "Per-group configuration",
|
|
61
|
+
"additionalProperties": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"allow": {
|
|
65
|
+
"type": "boolean",
|
|
66
|
+
"default": true
|
|
67
|
+
},
|
|
68
|
+
"requireMention": {
|
|
69
|
+
"type": "boolean",
|
|
70
|
+
"default": true
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"accounts": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"description": "Multi-account configuration",
|
|
78
|
+
"additionalProperties": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"enabled": {
|
|
82
|
+
"type": "boolean",
|
|
83
|
+
"default": true
|
|
84
|
+
},
|
|
85
|
+
"wsUrl": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "Account-specific WebSocket URL"
|
|
88
|
+
},
|
|
89
|
+
"apiKey": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"description": "Account-specific API key"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"required": ["wsUrl", "apiKey"]
|
|
98
|
+
},
|
|
99
|
+
"uiHints": {
|
|
100
|
+
"wsUrl": {
|
|
101
|
+
"label": "WebSocket Server URL",
|
|
102
|
+
"placeholder": "wss://api.intclaw.example.com/ws",
|
|
103
|
+
"helpText": "The WebSocket URL of your IntClaw server",
|
|
104
|
+
"order": 1
|
|
105
|
+
},
|
|
106
|
+
"apiKey": {
|
|
107
|
+
"label": "API Key",
|
|
108
|
+
"placeholder": "Enter your API key",
|
|
109
|
+
"helpText": "API key for authenticating with the IntClaw server",
|
|
110
|
+
"sensitive": true,
|
|
111
|
+
"order": 2
|
|
112
|
+
},
|
|
113
|
+
"reconnectInterval": {
|
|
114
|
+
"label": "Reconnection Interval (ms)",
|
|
115
|
+
"placeholder": "5000",
|
|
116
|
+
"helpText": "How long to wait before reconnecting (in milliseconds)",
|
|
117
|
+
"order": 3
|
|
118
|
+
},
|
|
119
|
+
"dmPolicy": {
|
|
120
|
+
"label": "Direct Message Policy",
|
|
121
|
+
"helpText": "How to handle direct messages from unknown users",
|
|
122
|
+
"options": {
|
|
123
|
+
"pairing": "Require pairing approval",
|
|
124
|
+
"allowlist": "Only allow listed users",
|
|
125
|
+
"open": "Allow all users",
|
|
126
|
+
"disabled": "Disable direct messages"
|
|
127
|
+
},
|
|
128
|
+
"order": 4
|
|
129
|
+
},
|
|
130
|
+
"groupPolicy": {
|
|
131
|
+
"label": "Group Message Policy",
|
|
132
|
+
"helpText": "How to handle group messages",
|
|
133
|
+
"options": {
|
|
134
|
+
"allowlist": "Only allow listed groups",
|
|
135
|
+
"open": "Allow all groups",
|
|
136
|
+
"disabled": "Disable group messages"
|
|
137
|
+
},
|
|
138
|
+
"order": 5
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@insta-dev01/intclaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "OpenClaw plugin for IntClaw services - WebSocket-based channel integration for Community Platform, Message Channel, and Agent Collaboration Engine",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --test src/__tests__/**/*.test.js",
|
|
9
|
+
"test:watch": "node --test --watch src/__tests__/**/*.test.js",
|
|
10
|
+
"prepublishOnly": "pnpm test",
|
|
11
|
+
"publish:public": "npm publish --access public"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"openclaw",
|
|
15
|
+
"openclaw-plugin",
|
|
16
|
+
"plugin",
|
|
17
|
+
"channel",
|
|
18
|
+
"intclaw",
|
|
19
|
+
"websocket",
|
|
20
|
+
"messaging",
|
|
21
|
+
"gateway",
|
|
22
|
+
"ai",
|
|
23
|
+
"bot",
|
|
24
|
+
"insta-sdk"
|
|
25
|
+
],
|
|
26
|
+
"author": "Your Name <your.email@example.com>",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"packageManager": "pnpm@10.25.0",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/insta-sdk/intclaw.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/insta-sdk/intclaw/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/insta-sdk/intclaw#readme",
|
|
37
|
+
"files": [
|
|
38
|
+
"src/index.js",
|
|
39
|
+
"src/channel",
|
|
40
|
+
"openclaw.plugin.json",
|
|
41
|
+
"skills",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
".env.example"
|
|
45
|
+
],
|
|
46
|
+
"openclaw": {
|
|
47
|
+
"extensions": [
|
|
48
|
+
"src/index.js"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"ws": "^8.18.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: intclaw_matrix
|
|
3
|
+
description: Instructs the AI on formatting and targeting rules for the intclaw_matrix_send tool
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# IntClaw Matrix Plugin SKILL
|
|
7
|
+
|
|
8
|
+
This skill activates when the user requests integration or message delivery through the IntClaw Matrix framework.
|
|
9
|
+
|
|
10
|
+
## Behavior
|
|
11
|
+
|
|
12
|
+
1. **Active Invocation**: Whenever the user explicitly asks you to "send a matrix message", "dispatch via matrix", or similar, YOU MUST call the `intclaw_matrix_send` tool.
|
|
13
|
+
2. **Target Resolution**: Always require the user to specify a full Matrix destination such as `!roomid:matrix.org` or `@username:homeserver.com`. If the destination is ambiguous or not provided, ask the user to clarify before dispatching.
|
|
14
|
+
3. **Account Targeting**: By default, use the `default` account ID. If the user expresses that they want to send it from a specific bot instance or account, pass that through the `account_id` parameter of the tool.
|
|
15
|
+
|
|
16
|
+
## Formatting Rules
|
|
17
|
+
|
|
18
|
+
- Output pure structured text without heavy Markdown when dispatching text payloads to `intclaw_matrix_send`. Matrix handles plaintext or basic HTML best. Do not wrap payloads in codeblocks unless explicitly communicating code.
|
|
19
|
+
- Report back the Matrix Event ID obtained from the tool output directly to the user upon a successful dispatch.
|
|
20
|
+
|
|
@@ -0,0 +1,311 @@
|
|
|
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_RECONNECT_INTERVAL = 5000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* IntClaw message types
|
|
12
|
+
*/
|
|
13
|
+
export const MessageTypes = {
|
|
14
|
+
// Incoming messages from IntClaw server
|
|
15
|
+
INCOMING_MESSAGE: 'incoming_message',
|
|
16
|
+
// Outgoing messages to IntClaw server
|
|
17
|
+
OUTGOING_MESSAGE: 'outgoing_message',
|
|
18
|
+
// Connection status
|
|
19
|
+
CONNECTION_STATUS: 'connection_status',
|
|
20
|
+
// Authentication
|
|
21
|
+
AUTH_REQUEST: 'auth_request',
|
|
22
|
+
AUTH_RESPONSE: 'auth_response',
|
|
23
|
+
// Heartbeat
|
|
24
|
+
PING: 'ping',
|
|
25
|
+
PONG: 'pong',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* IntClaw peer kinds
|
|
30
|
+
*/
|
|
31
|
+
export const PeerKind = {
|
|
32
|
+
DIRECT: 'direct',
|
|
33
|
+
GROUP: 'group',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class IntClawChannel {
|
|
37
|
+
#gateway;
|
|
38
|
+
#config;
|
|
39
|
+
#ws = null;
|
|
40
|
+
#reconnectTimer = null;
|
|
41
|
+
#isShuttingDown = false;
|
|
42
|
+
#isAuthenticated = false;
|
|
43
|
+
|
|
44
|
+
constructor(gateway, config) {
|
|
45
|
+
this.#gateway = gateway;
|
|
46
|
+
this.#config = config;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Start the channel
|
|
51
|
+
*/
|
|
52
|
+
async start() {
|
|
53
|
+
this.#log('info', 'Starting IntClaw channel...');
|
|
54
|
+
|
|
55
|
+
if (!this.#config.wsUrl) {
|
|
56
|
+
throw new Error('IntClaw channel requires wsUrl in configuration');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!this.#config.apiKey) {
|
|
60
|
+
throw new Error('IntClaw channel requires apiKey in configuration');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await this.#connect();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Stop the channel
|
|
68
|
+
*/
|
|
69
|
+
async stop() {
|
|
70
|
+
this.#log('info', 'Stopping IntClaw channel...');
|
|
71
|
+
this.#isShuttingDown = true;
|
|
72
|
+
|
|
73
|
+
if (this.#reconnectTimer) {
|
|
74
|
+
clearTimeout(this.#reconnectTimer);
|
|
75
|
+
this.#reconnectTimer = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.#ws) {
|
|
79
|
+
this.#ws.close();
|
|
80
|
+
this.#ws = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.#log('info', 'IntClaw channel stopped');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Connect to IntClaw WebSocket server
|
|
88
|
+
*/
|
|
89
|
+
async #connect() {
|
|
90
|
+
if (this.#isShuttingDown) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
this.#log('info', `Connecting to IntClaw server: ${this.#config.wsUrl}`);
|
|
96
|
+
|
|
97
|
+
// Import ws module dynamically
|
|
98
|
+
const WebSocket = (await import('ws')).default;
|
|
99
|
+
|
|
100
|
+
this.#ws = new WebSocket(this.#config.wsUrl, {
|
|
101
|
+
headers: {
|
|
102
|
+
'X-API-Key': this.#config.apiKey,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.#ws.on('open', () => this.#onOpen());
|
|
107
|
+
this.#ws.on('message', (data) => this.#onMessage(data));
|
|
108
|
+
this.#ws.on('error', (error) => this.#onError(error));
|
|
109
|
+
this.#ws.on('close', (code, reason) => this.#onClose(code, reason));
|
|
110
|
+
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.#log('error', `Connection failed: ${error.message}`);
|
|
113
|
+
this.#scheduleReconnect();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handle WebSocket open event
|
|
119
|
+
*/
|
|
120
|
+
#onOpen() {
|
|
121
|
+
this.#log('info', 'Connected to IntClaw server');
|
|
122
|
+
|
|
123
|
+
// Send authentication request
|
|
124
|
+
this.#send({
|
|
125
|
+
type: MessageTypes.AUTH_REQUEST,
|
|
126
|
+
apiKey: this.#config.apiKey,
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Handle WebSocket message event
|
|
133
|
+
*/
|
|
134
|
+
async #onMessage(data) {
|
|
135
|
+
try {
|
|
136
|
+
const message = JSON.parse(data.toString());
|
|
137
|
+
this.#log('debug', `Received message type: ${message.type}`);
|
|
138
|
+
|
|
139
|
+
switch (message.type) {
|
|
140
|
+
case MessageTypes.AUTH_RESPONSE:
|
|
141
|
+
await this.#handleAuthResponse(message);
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case MessageTypes.INCOMING_MESSAGE:
|
|
145
|
+
await this.#handleIncomingMessage(message);
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case MessageTypes.PING:
|
|
149
|
+
this.#send({ type: MessageTypes.PONG });
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case MessageTypes.PONG:
|
|
153
|
+
// Pong received, connection is alive
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
default:
|
|
157
|
+
this.#log('warn', `Unknown message type: ${message.type}`);
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.#log('error', `Failed to handle message: ${error.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Handle authentication response
|
|
166
|
+
*/
|
|
167
|
+
async #handleAuthResponse(message) {
|
|
168
|
+
if (message.success) {
|
|
169
|
+
this.#isAuthenticated = true;
|
|
170
|
+
this.#log('info', 'Authenticated with IntClaw server');
|
|
171
|
+
|
|
172
|
+
// Notify gateway that channel is ready
|
|
173
|
+
if (this.#gateway?.notifyChannelReady) {
|
|
174
|
+
await this.#gateway.notifyChannelReady(CHANNEL_ID);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
this.#log('error', `Authentication failed: ${message.error || 'Unknown error'}`);
|
|
178
|
+
this.#ws.close();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Handle incoming message from IntClaw server
|
|
184
|
+
*/
|
|
185
|
+
async #handleIncomingMessage(message) {
|
|
186
|
+
if (!this.#isAuthenticated) {
|
|
187
|
+
this.#log('warn', 'Ignoring message: not authenticated');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const { payload } = message;
|
|
192
|
+
|
|
193
|
+
// Normalize message for OpenClaw gateway
|
|
194
|
+
const normalizedMessage = {
|
|
195
|
+
channel: CHANNEL_ID,
|
|
196
|
+
accountId: payload.accountId || 'default',
|
|
197
|
+
peer: {
|
|
198
|
+
kind: payload.peerKind || PeerKind.DIRECT,
|
|
199
|
+
id: payload.peerId,
|
|
200
|
+
name: payload.peerName,
|
|
201
|
+
},
|
|
202
|
+
text: payload.text,
|
|
203
|
+
timestamp: payload.timestamp || Date.now(),
|
|
204
|
+
id: payload.id,
|
|
205
|
+
threadId: payload.threadId,
|
|
206
|
+
replyToId: payload.replyToId,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Send to gateway for processing
|
|
210
|
+
if (this.#gateway?.handleChannelMessage) {
|
|
211
|
+
await this.#gateway.handleChannelMessage(normalizedMessage);
|
|
212
|
+
} else {
|
|
213
|
+
this.#log('warn', 'Gateway does not support handleChannelMessage');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Handle WebSocket error event
|
|
219
|
+
*/
|
|
220
|
+
#onError(error) {
|
|
221
|
+
this.#log('error', `WebSocket error: ${error.message}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Handle WebSocket close event
|
|
226
|
+
*/
|
|
227
|
+
#onClose(code, reason) {
|
|
228
|
+
this.#log('info', `Connection closed: ${code} - ${reason || 'No reason'}`);
|
|
229
|
+
this.#isAuthenticated = false;
|
|
230
|
+
|
|
231
|
+
if (!this.#isShuttingDown) {
|
|
232
|
+
this.#scheduleReconnect();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Schedule reconnection attempt
|
|
238
|
+
*/
|
|
239
|
+
#scheduleReconnect() {
|
|
240
|
+
const interval = this.#config.reconnectInterval || DEFAULT_RECONNECT_INTERVAL;
|
|
241
|
+
|
|
242
|
+
this.#log('info', `Reconnecting in ${interval}ms...`);
|
|
243
|
+
|
|
244
|
+
this.#reconnectTimer = setTimeout(() => {
|
|
245
|
+
this.#connect();
|
|
246
|
+
}, interval);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Send message to IntClaw server
|
|
251
|
+
*/
|
|
252
|
+
#send(message) {
|
|
253
|
+
if (this.#ws && this.#ws.readyState === 1 /* OPEN */) {
|
|
254
|
+
this.#ws.send(JSON.stringify(message));
|
|
255
|
+
} else {
|
|
256
|
+
this.#log('warn', 'Cannot send message: WebSocket not connected');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Send message action
|
|
262
|
+
*/
|
|
263
|
+
async send(message) {
|
|
264
|
+
const payload = {
|
|
265
|
+
type: MessageTypes.OUTGOING_MESSAGE,
|
|
266
|
+
payload: {
|
|
267
|
+
id: message.id || this.#generateMessageId(),
|
|
268
|
+
accountId: message.accountId || 'default',
|
|
269
|
+
peerId: message.peer.id,
|
|
270
|
+
peerKind: message.peer.kind,
|
|
271
|
+
text: message.text,
|
|
272
|
+
threadId: message.threadId,
|
|
273
|
+
replyToId: message.replyToId,
|
|
274
|
+
timestamp: Date.now(),
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
this.#send(payload);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Generate unique message ID
|
|
283
|
+
*/
|
|
284
|
+
#generateMessageId() {
|
|
285
|
+
return `intclaw_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Log message
|
|
290
|
+
*/
|
|
291
|
+
#log(level, message) {
|
|
292
|
+
const logMessage = `[IntClaw Channel] ${message}`;
|
|
293
|
+
|
|
294
|
+
switch (level) {
|
|
295
|
+
case 'error':
|
|
296
|
+
console.error(logMessage);
|
|
297
|
+
break;
|
|
298
|
+
case 'warn':
|
|
299
|
+
console.warn(logMessage);
|
|
300
|
+
break;
|
|
301
|
+
case 'debug':
|
|
302
|
+
// Only log debug in development
|
|
303
|
+
if (process.env.NODE_ENV === 'development') {
|
|
304
|
+
console.log(logMessage);
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
default:
|
|
308
|
+
console.log(logMessage);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { IntClawChannel } from './channel/IntClawChannel.js';
|
|
9
|
+
|
|
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
|
|
23
|
+
* @param {Object} gateway - OpenClaw gateway instance
|
|
24
|
+
* @param {Object} config - Plugin configuration
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
export async function init(gateway, config) {
|
|
28
|
+
if (config.enabled !== false) {
|
|
29
|
+
await registerChannel(gateway, config);
|
|
30
|
+
}
|
|
31
|
+
}
|