@eclaw/openclaw-channel 1.0.17 → 1.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/README.md +58 -58
- package/dist/channel.d.ts +0 -19
- package/dist/channel.js +2 -4
- package/dist/client.d.ts +5 -10
- package/dist/client.js +14 -57
- package/dist/config.js +4 -16
- package/dist/gateway.d.ts +6 -5
- package/dist/gateway.js +165 -89
- package/dist/index.d.ts +21 -0
- package/dist/index.js +6 -35
- package/dist/outbound.d.ts +0 -2
- package/dist/outbound.js +0 -18
- package/dist/types.d.ts +2 -19
- package/dist/webhook-handler.d.ts +3 -14
- package/dist/webhook-handler.js +29 -111
- package/openclaw.plugin.json +27 -27
- package/package.json +60 -60
- package/dist/onboarding.d.ts +0 -19
- package/dist/onboarding.js +0 -77
- package/dist/webhook-registry.d.ts +0 -19
- package/dist/webhook-registry.js +0 -39
package/README.md
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
# @eclaw/openclaw-channel
|
|
2
|
-
|
|
3
|
-
OpenClaw channel plugin for [E-Claw](https://eclawbot.com) —
|
|
4
|
-
|
|
5
|
-
This plugin enables OpenClaw bots to communicate with E-Claw users as a native channel, alongside Telegram, Discord, and Slack.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @eclaw/openclaw-channel
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Configuration
|
|
14
|
-
|
|
15
|
-
Add to your OpenClaw `config.yaml`:
|
|
16
|
-
|
|
17
|
-
```yaml
|
|
18
|
-
plugins:
|
|
19
|
-
- "@eclaw/openclaw-channel"
|
|
20
|
-
|
|
21
|
-
channels:
|
|
22
|
-
eclaw:
|
|
23
|
-
accounts:
|
|
24
|
-
default:
|
|
25
|
-
apiKey: "eck_..." # From E-Claw Portal → Settings → Channel API
|
|
26
|
-
apiSecret: "ecs_..." # From E-Claw Portal → Settings → Channel API
|
|
27
|
-
apiBase: "https://eclawbot.com"
|
|
28
|
-
entityId: 0 # Which entity slot to use (0-3)
|
|
29
|
-
botName: "My Bot"
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Getting API Credentials
|
|
33
|
-
|
|
34
|
-
1. Log in to [E-Claw Portal](https://eclawbot.com/portal)
|
|
35
|
-
2. Go to **Settings → Channel API**
|
|
36
|
-
3. Copy your `API Key` and `API Secret`
|
|
37
|
-
|
|
38
|
-
## How It Works
|
|
39
|
-
|
|
40
|
-
```
|
|
41
|
-
User (Android) ──speaks──▶ E-Claw Backend ──webhook──▶ OpenClaw Agent
|
|
42
|
-
OpenClaw Agent ──replies──▶ POST /api/channel/message ──▶ User (Android)
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
- **Inbound**: E-Claw POSTs structured JSON to a webhook URL registered by this plugin
|
|
46
|
-
- **Outbound**: Plugin calls `POST /api/channel/message` with the bot reply
|
|
47
|
-
- **Auth**: `eck_`/`ecs_` channel credentials for API auth, per-entity `botSecret` for message auth
|
|
48
|
-
|
|
49
|
-
## Environment Variables
|
|
50
|
-
|
|
51
|
-
| Variable | Required | Description |
|
|
52
|
-
|----------|----------|-------------|
|
|
53
|
-
| `ECLAW_WEBHOOK_URL` | Production | Public URL for receiving inbound messages |
|
|
54
|
-
| `ECLAW_WEBHOOK_PORT` | Optional | Webhook server port (default: random) |
|
|
55
|
-
|
|
56
|
-
## License
|
|
57
|
-
|
|
58
|
-
MIT
|
|
1
|
+
# @eclaw/openclaw-channel
|
|
2
|
+
|
|
3
|
+
OpenClaw channel plugin for [E-Claw](https://eclawbot.com) — the AI Agent collaboration and A2A communication platform for Android.
|
|
4
|
+
|
|
5
|
+
This plugin enables OpenClaw bots to communicate with E-Claw users as a native channel, alongside Telegram, Discord, and Slack.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @eclaw/openclaw-channel
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Add to your OpenClaw `config.yaml`:
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
plugins:
|
|
19
|
+
- "@eclaw/openclaw-channel"
|
|
20
|
+
|
|
21
|
+
channels:
|
|
22
|
+
eclaw:
|
|
23
|
+
accounts:
|
|
24
|
+
default:
|
|
25
|
+
apiKey: "eck_..." # From E-Claw Portal → Settings → Channel API
|
|
26
|
+
apiSecret: "ecs_..." # From E-Claw Portal → Settings → Channel API
|
|
27
|
+
apiBase: "https://eclawbot.com"
|
|
28
|
+
entityId: 0 # Which entity slot to use (0-3)
|
|
29
|
+
botName: "My Bot"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Getting API Credentials
|
|
33
|
+
|
|
34
|
+
1. Log in to [E-Claw Portal](https://eclawbot.com/portal)
|
|
35
|
+
2. Go to **Settings → Channel API**
|
|
36
|
+
3. Copy your `API Key` and `API Secret`
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
User (Android) ──speaks──▶ E-Claw Backend ──webhook──▶ OpenClaw Agent
|
|
42
|
+
OpenClaw Agent ──replies──▶ POST /api/channel/message ──▶ User (Android)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- **Inbound**: E-Claw POSTs structured JSON to a webhook URL registered by this plugin
|
|
46
|
+
- **Outbound**: Plugin calls `POST /api/channel/message` with the bot reply
|
|
47
|
+
- **Auth**: `eck_`/`ecs_` channel credentials for API auth, per-entity `botSecret` for message auth
|
|
48
|
+
|
|
49
|
+
## Environment Variables
|
|
50
|
+
|
|
51
|
+
| Variable | Required | Description |
|
|
52
|
+
|----------|----------|-------------|
|
|
53
|
+
| `ECLAW_WEBHOOK_URL` | Production | Public URL for receiving inbound messages |
|
|
54
|
+
| `ECLAW_WEBHOOK_PORT` | Optional | Webhook server port (default: random) |
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
package/dist/channel.d.ts
CHANGED
|
@@ -40,23 +40,4 @@ export declare const eclawChannel: {
|
|
|
40
40
|
gateway: {
|
|
41
41
|
startAccount: typeof startAccount;
|
|
42
42
|
};
|
|
43
|
-
onboarding: {
|
|
44
|
-
channel: string;
|
|
45
|
-
getStatus: ({ cfg }: {
|
|
46
|
-
cfg: any;
|
|
47
|
-
}) => Promise<{
|
|
48
|
-
channel: string;
|
|
49
|
-
configured: boolean;
|
|
50
|
-
statusLines: string[];
|
|
51
|
-
selectionHint: string;
|
|
52
|
-
quickstartScore: number;
|
|
53
|
-
}>;
|
|
54
|
-
configure: ({ cfg, prompter }: {
|
|
55
|
-
cfg: any;
|
|
56
|
-
prompter: any;
|
|
57
|
-
}) => Promise<{
|
|
58
|
-
cfg: any;
|
|
59
|
-
accountId: string;
|
|
60
|
-
}>;
|
|
61
|
-
};
|
|
62
43
|
};
|
package/dist/channel.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { listAccountIds, resolveAccount } from './config.js';
|
|
2
2
|
import { sendText, sendMedia } from './outbound.js';
|
|
3
3
|
import { startAccount } from './gateway.js';
|
|
4
|
-
import { eclawOnboardingAdapter } from './onboarding.js';
|
|
5
4
|
/**
|
|
6
5
|
* E-Claw ChannelPlugin definition.
|
|
7
6
|
*
|
|
@@ -14,9 +13,9 @@ export const eclawChannel = {
|
|
|
14
13
|
meta: {
|
|
15
14
|
id: 'eclaw',
|
|
16
15
|
label: 'E-Claw',
|
|
17
|
-
selectionLabel: 'E-Claw (AI
|
|
16
|
+
selectionLabel: 'E-Claw (AI Agent Collaboration)',
|
|
18
17
|
docsPath: '/channels/eclaw',
|
|
19
|
-
blurb: 'Connect OpenClaw to E-Claw —
|
|
18
|
+
blurb: 'Connect OpenClaw to E-Claw — the AI Agent collaboration and A2A communication platform for Android.',
|
|
20
19
|
aliases: ['eclaw', 'claw', 'e-claw'],
|
|
21
20
|
},
|
|
22
21
|
capabilities: {
|
|
@@ -41,5 +40,4 @@ export const eclawChannel = {
|
|
|
41
40
|
gateway: {
|
|
42
41
|
startAccount,
|
|
43
42
|
},
|
|
44
|
-
onboarding: eclawOnboardingAdapter,
|
|
45
43
|
};
|
package/dist/client.d.ts
CHANGED
|
@@ -6,25 +6,20 @@ import type { EClawAccountConfig, RegisterResponse, BindResponse, MessageRespons
|
|
|
6
6
|
export declare class EClawClient {
|
|
7
7
|
private readonly apiBase;
|
|
8
8
|
private readonly apiKey;
|
|
9
|
+
private readonly apiSecret;
|
|
9
10
|
private deviceId;
|
|
10
11
|
private botSecret;
|
|
11
12
|
private entityId;
|
|
12
13
|
constructor(config: EClawAccountConfig);
|
|
13
14
|
/** Register callback URL with E-Claw backend */
|
|
14
15
|
registerCallback(callbackUrl: string, callbackToken: string): Promise<RegisterResponse>;
|
|
15
|
-
/** Bind an entity via channel API (bypasses 6-digit code)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
bindEntity(entityId?: number, name?: string): Promise<BindResponse>;
|
|
19
|
-
/** Send bot message to user (updates own entity state on wallpaper) */
|
|
16
|
+
/** Bind an entity via channel API (bypasses 6-digit code) */
|
|
17
|
+
bindEntity(entityId: number, name?: string): Promise<BindResponse>;
|
|
18
|
+
/** Send bot message to user */
|
|
20
19
|
sendMessage(message: string, state?: string, mediaType?: string, mediaUrl?: string): Promise<MessageResponse>;
|
|
21
|
-
/** Send bot-to-bot message to another entity (speak-to) */
|
|
22
|
-
speakTo(toEntityId: number, text: string, expectsReply?: boolean): Promise<void>;
|
|
23
|
-
/** Broadcast message to all other bound entities */
|
|
24
|
-
broadcastToAll(text: string, expectsReply?: boolean): Promise<void>;
|
|
25
20
|
/** Unregister callback on shutdown */
|
|
26
21
|
unregisterCallback(): Promise<void>;
|
|
27
22
|
get currentDeviceId(): string | null;
|
|
28
23
|
get currentBotSecret(): string | null;
|
|
29
|
-
get currentEntityId(): number
|
|
24
|
+
get currentEntityId(): number;
|
|
30
25
|
}
|
package/dist/client.js
CHANGED
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
export class EClawClient {
|
|
6
6
|
apiBase;
|
|
7
7
|
apiKey;
|
|
8
|
+
apiSecret;
|
|
8
9
|
deviceId = null;
|
|
9
10
|
botSecret = null;
|
|
10
11
|
entityId;
|
|
11
12
|
constructor(config) {
|
|
12
13
|
this.apiBase = config.apiBase;
|
|
13
14
|
this.apiKey = config.apiKey;
|
|
14
|
-
this.
|
|
15
|
+
this.apiSecret = config.apiSecret;
|
|
16
|
+
this.entityId = config.entityId;
|
|
15
17
|
}
|
|
16
18
|
/** Register callback URL with E-Claw backend */
|
|
17
19
|
async registerCallback(callbackUrl, callbackToken) {
|
|
@@ -20,6 +22,7 @@ export class EClawClient {
|
|
|
20
22
|
headers: { 'Content-Type': 'application/json' },
|
|
21
23
|
body: JSON.stringify({
|
|
22
24
|
channel_api_key: this.apiKey,
|
|
25
|
+
channel_api_secret: this.apiSecret,
|
|
23
26
|
callback_url: callbackUrl,
|
|
24
27
|
callback_token: callbackToken,
|
|
25
28
|
}),
|
|
@@ -31,40 +34,28 @@ export class EClawClient {
|
|
|
31
34
|
this.deviceId = data.deviceId;
|
|
32
35
|
return data;
|
|
33
36
|
}
|
|
34
|
-
/** Bind an entity via channel API (bypasses 6-digit code)
|
|
35
|
-
* If entityId is omitted, the backend auto-selects the first free slot.
|
|
36
|
-
*/
|
|
37
|
+
/** Bind an entity via channel API (bypasses 6-digit code) */
|
|
37
38
|
async bindEntity(entityId, name) {
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
-
const body = { channel_api_key: this.apiKey };
|
|
40
|
-
if (entityId !== undefined)
|
|
41
|
-
body.entityId = entityId;
|
|
42
|
-
if (name)
|
|
43
|
-
body.name = name;
|
|
44
39
|
const res = await fetch(`${this.apiBase}/api/channel/bind`, {
|
|
45
40
|
method: 'POST',
|
|
46
41
|
headers: { 'Content-Type': 'application/json' },
|
|
47
|
-
body: JSON.stringify(
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
channel_api_key: this.apiKey,
|
|
44
|
+
channel_api_secret: this.apiSecret,
|
|
45
|
+
entityId,
|
|
46
|
+
name: name || undefined,
|
|
47
|
+
}),
|
|
48
48
|
});
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
49
|
const data = await res.json();
|
|
51
50
|
if (!data.success) {
|
|
52
|
-
// Build a detailed error message when all slots are full
|
|
53
|
-
if (res.status === 409 && data.entities) {
|
|
54
|
-
const list = data.entities
|
|
55
|
-
.map((e) => ` slot ${e.entityId} (${e.character})${e.name ? ` "${e.name}"` : ''}`)
|
|
56
|
-
.join('\n');
|
|
57
|
-
throw new Error(`${data.message}\nCurrent entities:\n${list}\n` +
|
|
58
|
-
'Add entityId to your channel config to target a specific slot after unbinding it.');
|
|
59
|
-
}
|
|
60
51
|
throw new Error(data.message || `Bind failed (HTTP ${res.status})`);
|
|
61
52
|
}
|
|
62
53
|
this.botSecret = data.botSecret;
|
|
63
54
|
this.deviceId = data.deviceId;
|
|
64
|
-
this.entityId =
|
|
55
|
+
this.entityId = entityId;
|
|
65
56
|
return data;
|
|
66
57
|
}
|
|
67
|
-
/** Send bot message to user
|
|
58
|
+
/** Send bot message to user */
|
|
68
59
|
async sendMessage(message, state = 'IDLE', mediaType, mediaUrl) {
|
|
69
60
|
if (!this.deviceId || !this.botSecret) {
|
|
70
61
|
throw new Error('Not bound — call bindEntity() first');
|
|
@@ -85,41 +76,6 @@ export class EClawClient {
|
|
|
85
76
|
});
|
|
86
77
|
return await res.json();
|
|
87
78
|
}
|
|
88
|
-
/** Send bot-to-bot message to another entity (speak-to) */
|
|
89
|
-
async speakTo(toEntityId, text, expectsReply = false) {
|
|
90
|
-
if (!this.deviceId || !this.botSecret) {
|
|
91
|
-
throw new Error('Not bound — call bindEntity() first');
|
|
92
|
-
}
|
|
93
|
-
await fetch(`${this.apiBase}/api/entity/speak-to`, {
|
|
94
|
-
method: 'POST',
|
|
95
|
-
headers: { 'Content-Type': 'application/json' },
|
|
96
|
-
body: JSON.stringify({
|
|
97
|
-
deviceId: this.deviceId,
|
|
98
|
-
fromEntityId: this.entityId,
|
|
99
|
-
toEntityId,
|
|
100
|
-
botSecret: this.botSecret,
|
|
101
|
-
text,
|
|
102
|
-
expects_reply: expectsReply,
|
|
103
|
-
}),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
/** Broadcast message to all other bound entities */
|
|
107
|
-
async broadcastToAll(text, expectsReply = false) {
|
|
108
|
-
if (!this.deviceId || !this.botSecret) {
|
|
109
|
-
throw new Error('Not bound — call bindEntity() first');
|
|
110
|
-
}
|
|
111
|
-
await fetch(`${this.apiBase}/api/entity/broadcast`, {
|
|
112
|
-
method: 'POST',
|
|
113
|
-
headers: { 'Content-Type': 'application/json' },
|
|
114
|
-
body: JSON.stringify({
|
|
115
|
-
deviceId: this.deviceId,
|
|
116
|
-
fromEntityId: this.entityId,
|
|
117
|
-
botSecret: this.botSecret,
|
|
118
|
-
text,
|
|
119
|
-
expects_reply: expectsReply,
|
|
120
|
-
}),
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
79
|
/** Unregister callback on shutdown */
|
|
124
80
|
async unregisterCallback() {
|
|
125
81
|
await fetch(`${this.apiBase}/api/channel/register`, {
|
|
@@ -127,6 +83,7 @@ export class EClawClient {
|
|
|
127
83
|
headers: { 'Content-Type': 'application/json' },
|
|
128
84
|
body: JSON.stringify({
|
|
129
85
|
channel_api_key: this.apiKey,
|
|
86
|
+
channel_api_secret: this.apiSecret,
|
|
130
87
|
}),
|
|
131
88
|
});
|
|
132
89
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
/** Extract accounts map from full openclaw config or eclaw-specific config */
|
|
2
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3
|
-
function getAccounts(cfg) {
|
|
4
|
-
// Full openclaw config: { channels: { eclaw: { accounts: {...} } } }
|
|
5
|
-
if (cfg?.channels?.eclaw?.accounts)
|
|
6
|
-
return cfg.channels.eclaw.accounts;
|
|
7
|
-
// Eclaw channel config: { accounts: {...} }
|
|
8
|
-
if (cfg?.accounts && typeof cfg.accounts === 'object')
|
|
9
|
-
return cfg.accounts;
|
|
10
|
-
return {};
|
|
11
|
-
}
|
|
12
1
|
/** List all configured account IDs from OpenClaw config */
|
|
13
2
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
3
|
export function listAccountIds(cfg) {
|
|
15
|
-
const accounts =
|
|
4
|
+
const accounts = cfg?.channels?.eclaw?.accounts;
|
|
16
5
|
if (!accounts || typeof accounts !== 'object')
|
|
17
6
|
return [];
|
|
18
7
|
return Object.keys(accounts);
|
|
@@ -20,16 +9,15 @@ export function listAccountIds(cfg) {
|
|
|
20
9
|
/** Resolve a specific account's config, with defaults */
|
|
21
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
11
|
export function resolveAccount(cfg, accountId) {
|
|
23
|
-
const accounts =
|
|
12
|
+
const accounts = cfg?.channels?.eclaw?.accounts ?? {};
|
|
24
13
|
const id = accountId ?? Object.keys(accounts)[0] ?? 'default';
|
|
25
14
|
const account = accounts[id];
|
|
26
15
|
return {
|
|
27
16
|
enabled: account?.enabled ?? true,
|
|
28
17
|
apiKey: account?.apiKey ?? '',
|
|
29
|
-
apiSecret: account?.apiSecret,
|
|
18
|
+
apiSecret: account?.apiSecret ?? '',
|
|
30
19
|
apiBase: (account?.apiBase ?? 'https://eclawbot.com').replace(/\/$/, ''),
|
|
31
|
-
entityId: account?.entityId
|
|
20
|
+
entityId: account?.entityId ?? 0,
|
|
32
21
|
botName: account?.botName,
|
|
33
|
-
webhookUrl: account?.webhookUrl,
|
|
34
22
|
};
|
|
35
23
|
}
|
package/dist/gateway.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gateway lifecycle: start an E-Claw account.
|
|
3
3
|
*
|
|
4
|
-
* 1.
|
|
5
|
-
* 2.
|
|
6
|
-
*
|
|
7
|
-
* 3. Register callback URL with E-Claw backend
|
|
4
|
+
* 1. Initialize HTTP client with channel API credentials
|
|
5
|
+
* 2. Start a local HTTP server to receive webhook callbacks
|
|
6
|
+
* 3. Register callback URL with E-Claw backend (with exponential-backoff retry)
|
|
8
7
|
* 4. Auto-bind entity if not already bound
|
|
9
|
-
* 5.
|
|
8
|
+
* 5. Periodically re-register to keep callback URL live (health check)
|
|
9
|
+
* 6. On health-check failure, reconnect with exponential backoff
|
|
10
|
+
* 7. Keep the promise alive until abort signal fires
|
|
10
11
|
*/
|
|
11
12
|
export declare function startAccount(ctx: any): Promise<void>;
|