Update README with capabilities negotiation flow
- Add capabilities negotiation to Features list - Document connection flow (5 steps) - Add capabilities_response message format - Add request_capabilities message format - Update JavaScript example with capabilities handling - Update Python example with capabilities handling
This commit is contained in:
111
README.md
111
README.md
@@ -14,6 +14,7 @@ Custom WebSocket-based messaging channel plugin for OpenClaw. Enables custom mes
|
|||||||
- Reactions
|
- Reactions
|
||||||
- Threads
|
- Threads
|
||||||
- Typing indicators
|
- Typing indicators
|
||||||
|
- 🔄 **Capabilities Negotiation** - Clients report their capabilities on connect
|
||||||
- 🛡️ **DM Security** - Three policies: `allowlist`, `open`, `closed`
|
- 🛡️ **DM Security** - Three policies: `allowlist`, `open`, `closed`
|
||||||
- 🔗 **Threading** - Full support for threaded conversations
|
- 🔗 **Threading** - Full support for threaded conversations
|
||||||
- 🤝 **Pairing Flow** - Text-based pairing with code exchange
|
- 🤝 **Pairing Flow** - Text-based pairing with code exchange
|
||||||
@@ -87,9 +88,41 @@ const ws = new WebSocket('ws://localhost:8765/ws', {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Connection Flow
|
||||||
|
|
||||||
|
1. **Client connects** → Server authenticates via API key
|
||||||
|
2. **Server sends handshake** → Server capabilities (maximum allowed)
|
||||||
|
3. **Server requests capabilities** → Asks client what it supports
|
||||||
|
4. **Client responds** → Client capabilities + metadata
|
||||||
|
5. **Effective capabilities** → Server uses intersection of both
|
||||||
|
6. **Messaging begins** → Only supported message types allowed
|
||||||
|
|
||||||
### Message Format
|
### Message Format
|
||||||
|
|
||||||
**Client → Server:**
|
#### Client → Server
|
||||||
|
|
||||||
|
**Capabilities Response (required after handshake):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "capabilities_response",
|
||||||
|
"capabilities": {
|
||||||
|
"text": true,
|
||||||
|
"images": true,
|
||||||
|
"files": false,
|
||||||
|
"voice": false,
|
||||||
|
"reactions": false,
|
||||||
|
"threads": true,
|
||||||
|
"typing": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"clientName": "My App",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"platform": "custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Text Message:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "message",
|
"type": "message",
|
||||||
@@ -101,7 +134,9 @@ const ws = new WebSocket('ws://localhost:8765/ws', {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Server → Client (Handshake):**
|
#### Server → Client
|
||||||
|
|
||||||
|
**Handshake:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "handshake",
|
"type": "handshake",
|
||||||
@@ -109,19 +144,27 @@ const ws = new WebSocket('ws://localhost:8765/ws', {
|
|||||||
"capabilities": {
|
"capabilities": {
|
||||||
"text": true,
|
"text": true,
|
||||||
"images": true,
|
"images": true,
|
||||||
"files": false,
|
"files": true,
|
||||||
"voice": false,
|
"voice": true,
|
||||||
"reactions": false,
|
"reactions": false,
|
||||||
"threads": true,
|
"threads": true,
|
||||||
"typing": true
|
"typing": true
|
||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"serverName": "My WebSocket Server"
|
"serverName": "My WebSocket Server"
|
||||||
}
|
},
|
||||||
|
"timestamp": 1703275200000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Server → Client (Message):**
|
**Capabilities Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "request_capabilities"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Message:**
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "message",
|
"type": "message",
|
||||||
@@ -146,12 +189,39 @@ const ws = new WebSocket('ws://localhost:8765/ws?key=YOUR_API_KEY');
|
|||||||
|
|
||||||
ws.on('message', (data) => {
|
ws.on('message', (data) => {
|
||||||
const msg = JSON.parse(data.toString());
|
const msg = JSON.parse(data.toString());
|
||||||
if (msg.type === 'message') {
|
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'handshake':
|
||||||
|
console.log('Server capabilities:', msg.capabilities);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'request_capabilities':
|
||||||
|
// Send client capabilities
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: 'capabilities_response',
|
||||||
|
capabilities: {
|
||||||
|
text: true,
|
||||||
|
images: true,
|
||||||
|
files: false,
|
||||||
|
voice: false,
|
||||||
|
reactions: false,
|
||||||
|
threads: true,
|
||||||
|
typing: true
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
clientName: 'My App',
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'message':
|
||||||
console.log('Bot:', msg.content.text);
|
console.log('Bot:', msg.content.text);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send message
|
// Send message (after capabilities_response)
|
||||||
ws.send(JSON.stringify({
|
ws.send(JSON.stringify({
|
||||||
type: 'message',
|
type: 'message',
|
||||||
from: 'user-123',
|
from: 'user-123',
|
||||||
@@ -167,7 +237,30 @@ import json
|
|||||||
|
|
||||||
def on_message(ws, message):
|
def on_message(ws, message):
|
||||||
data = json.loads(message)
|
data = json.loads(message)
|
||||||
if data['type'] == 'message':
|
|
||||||
|
if data['type'] == 'handshake':
|
||||||
|
print(f"Server capabilities: {data['capabilities']}")
|
||||||
|
|
||||||
|
elif data['type'] == 'request_capabilities':
|
||||||
|
# Send client capabilities
|
||||||
|
ws.send(json.dumps({
|
||||||
|
"type": "capabilities_response",
|
||||||
|
"capabilities": {
|
||||||
|
"text": True,
|
||||||
|
"images": True,
|
||||||
|
"files": False,
|
||||||
|
"voice": False,
|
||||||
|
"reactions": False,
|
||||||
|
"threads": True,
|
||||||
|
"typing": True
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"clientName": "My Python App",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
elif data['type'] == 'message':
|
||||||
print(f"Bot: {data['content']['text']}")
|
print(f"Bot: {data['content']['text']}")
|
||||||
|
|
||||||
ws = websocket.WebSocketApp(
|
ws = websocket.WebSocketApp(
|
||||||
|
|||||||
Reference in New Issue
Block a user