@eclaw/openclaw-channel 1.2.7 → 1.3.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/dist/client.d.ts +24 -4
- package/dist/client.js +25 -35
- package/dist/outbound.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/webhook-handler.d.ts +4 -4
- package/dist/webhook-handler.js +24 -8
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -16,11 +16,31 @@ export declare class EClawClient {
|
|
|
16
16
|
* If entityId is omitted, the backend auto-selects the first free slot.
|
|
17
17
|
*/
|
|
18
18
|
bindEntity(entityId?: number, name?: string): Promise<BindResponse>;
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Send bot message — unified endpoint for status update + optional delivery.
|
|
21
|
+
*
|
|
22
|
+
* @param message - Text message (also used as delivery content)
|
|
23
|
+
* @param state - Entity state (IDLE, BUSY, etc.)
|
|
24
|
+
* @param opts.mediaType - Optional media type
|
|
25
|
+
* @param opts.mediaUrl - Optional media URL
|
|
26
|
+
* @param opts.speakTo - Array of target identifiers (entityId or publicCode) to deliver message to
|
|
27
|
+
* @param opts.broadcast - If true, deliver message to all other bound entities
|
|
28
|
+
*/
|
|
29
|
+
sendMessage(message: string, state?: string, opts?: {
|
|
30
|
+
mediaType?: string;
|
|
31
|
+
mediaUrl?: string;
|
|
32
|
+
speakTo?: (string | number)[];
|
|
33
|
+
broadcast?: boolean;
|
|
34
|
+
}): Promise<MessageResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated Use sendMessage(text, state, { speakTo: [targetId] }) instead.
|
|
37
|
+
* Kept for backward compatibility — calls sendMessage internally.
|
|
38
|
+
*/
|
|
22
39
|
speakTo(toEntityId: number, text: string, expectsReply?: boolean): Promise<void>;
|
|
23
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Use sendMessage(text, state, { broadcast: true }) instead.
|
|
42
|
+
* Kept for backward compatibility — calls sendMessage internally.
|
|
43
|
+
*/
|
|
24
44
|
broadcastToAll(text: string, expectsReply?: boolean): Promise<void>;
|
|
25
45
|
/** Unregister callback on shutdown */
|
|
26
46
|
unregisterCallback(): Promise<void>;
|
package/dist/client.js
CHANGED
|
@@ -63,8 +63,17 @@ export class EClawClient {
|
|
|
63
63
|
this.entityId = data.entityId; // Use server-assigned slot
|
|
64
64
|
return data;
|
|
65
65
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Send bot message — unified endpoint for status update + optional delivery.
|
|
68
|
+
*
|
|
69
|
+
* @param message - Text message (also used as delivery content)
|
|
70
|
+
* @param state - Entity state (IDLE, BUSY, etc.)
|
|
71
|
+
* @param opts.mediaType - Optional media type
|
|
72
|
+
* @param opts.mediaUrl - Optional media URL
|
|
73
|
+
* @param opts.speakTo - Array of target identifiers (entityId or publicCode) to deliver message to
|
|
74
|
+
* @param opts.broadcast - If true, deliver message to all other bound entities
|
|
75
|
+
*/
|
|
76
|
+
async sendMessage(message, state = 'IDLE', opts) {
|
|
68
77
|
if (!this.deviceId || !this.botSecret) {
|
|
69
78
|
throw new Error('Not bound — call bindEntity() first');
|
|
70
79
|
}
|
|
@@ -78,46 +87,27 @@ export class EClawClient {
|
|
|
78
87
|
botSecret: this.botSecret,
|
|
79
88
|
message,
|
|
80
89
|
state,
|
|
81
|
-
...(mediaType && { mediaType }),
|
|
82
|
-
...(mediaUrl && { mediaUrl }),
|
|
90
|
+
...(opts?.mediaType && { mediaType: opts.mediaType }),
|
|
91
|
+
...(opts?.mediaUrl && { mediaUrl: opts.mediaUrl }),
|
|
92
|
+
...(opts?.speakTo && { speakTo: opts.speakTo.map(String) }),
|
|
93
|
+
...(opts?.broadcast && { broadcast: true }),
|
|
83
94
|
}),
|
|
84
95
|
});
|
|
85
96
|
return await res.json();
|
|
86
97
|
}
|
|
87
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* @deprecated Use sendMessage(text, state, { speakTo: [targetId] }) instead.
|
|
100
|
+
* Kept for backward compatibility — calls sendMessage internally.
|
|
101
|
+
*/
|
|
88
102
|
async speakTo(toEntityId, text, expectsReply = false) {
|
|
89
|
-
|
|
90
|
-
throw new Error('Not bound — call bindEntity() first');
|
|
91
|
-
}
|
|
92
|
-
await fetch(`${this.apiBase}/api/entity/speak-to`, {
|
|
93
|
-
method: 'POST',
|
|
94
|
-
headers: { 'Content-Type': 'application/json' },
|
|
95
|
-
body: JSON.stringify({
|
|
96
|
-
deviceId: this.deviceId,
|
|
97
|
-
fromEntityId: this.entityId,
|
|
98
|
-
toEntityId,
|
|
99
|
-
botSecret: this.botSecret,
|
|
100
|
-
text,
|
|
101
|
-
expects_reply: expectsReply,
|
|
102
|
-
}),
|
|
103
|
-
});
|
|
103
|
+
await this.sendMessage(text, 'IDLE', { speakTo: [String(toEntityId)] });
|
|
104
104
|
}
|
|
105
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* @deprecated Use sendMessage(text, state, { broadcast: true }) instead.
|
|
107
|
+
* Kept for backward compatibility — calls sendMessage internally.
|
|
108
|
+
*/
|
|
106
109
|
async broadcastToAll(text, expectsReply = false) {
|
|
107
|
-
|
|
108
|
-
throw new Error('Not bound — call bindEntity() first');
|
|
109
|
-
}
|
|
110
|
-
await fetch(`${this.apiBase}/api/entity/broadcast`, {
|
|
111
|
-
method: 'POST',
|
|
112
|
-
headers: { 'Content-Type': 'application/json' },
|
|
113
|
-
body: JSON.stringify({
|
|
114
|
-
deviceId: this.deviceId,
|
|
115
|
-
fromEntityId: this.entityId,
|
|
116
|
-
botSecret: this.botSecret,
|
|
117
|
-
text,
|
|
118
|
-
expects_reply: expectsReply,
|
|
119
|
-
}),
|
|
120
|
-
});
|
|
110
|
+
await this.sendMessage(text, 'IDLE', { broadcast: true });
|
|
121
111
|
}
|
|
122
112
|
/** Unregister callback on shutdown */
|
|
123
113
|
async unregisterCallback() {
|
package/dist/outbound.js
CHANGED
|
@@ -59,7 +59,7 @@ export async function sendMedia(ctx) {
|
|
|
59
59
|
const mediaType = ctx.mediaType === 'image' ? 'photo'
|
|
60
60
|
: ctx.mediaType === 'audio' ? 'voice'
|
|
61
61
|
: ctx.mediaType ?? 'file';
|
|
62
|
-
const result = await client.sendMessage(ctx.text || `[${mediaType}]`, 'IDLE', mediaType, ctx.mediaUrl);
|
|
62
|
+
const result = await client.sendMessage(ctx.text || `[${mediaType}]`, 'IDLE', { mediaType, mediaUrl: ctx.mediaUrl });
|
|
63
63
|
return {
|
|
64
64
|
channel: 'eclaw',
|
|
65
65
|
messageId: `eclaw-${Date.now()}`,
|
package/dist/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface EClawContext {
|
|
|
17
17
|
}
|
|
18
18
|
/** Inbound message from E-Claw callback webhook */
|
|
19
19
|
export interface EClawInboundMessage {
|
|
20
|
-
event: 'message' | 'entity_message' | 'broadcast' | 'cross_device_message';
|
|
20
|
+
event: 'message' | 'entity_message' | 'broadcast' | 'cross_device_message' | 'kanban_notification';
|
|
21
21
|
deviceId: string;
|
|
22
22
|
entityId: number;
|
|
23
23
|
conversationId: string;
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles three event types:
|
|
5
5
|
* - 'message' → Normal human message; reply via sendMessage()
|
|
6
|
-
* - 'entity_message' → Bot-to-bot speak-to; reply via sendMessage(
|
|
7
|
-
* - 'broadcast' → Broadcast from another entity; reply via sendMessage(
|
|
6
|
+
* - 'entity_message' → Bot-to-bot speak-to; reply via sendMessage(text, state, { speakTo })
|
|
7
|
+
* - 'broadcast' → Broadcast from another entity; reply via sendMessage(text, state, { speakTo })
|
|
8
8
|
*
|
|
9
9
|
* The `deliver` callback routes AI response to the correct E-Claw endpoint
|
|
10
10
|
* based on the inbound event type.
|
|
11
11
|
*
|
|
12
|
-
* Channel Bot Context Parity v1.0.17
|
|
13
|
-
* - Bot-to-bot / broadcast
|
|
12
|
+
* Channel Bot Context Parity v1.0.17+:
|
|
13
|
+
* - Bot-to-bot / broadcast uses unified sendMessage() with speakTo option (single API call)
|
|
14
14
|
* - Quota awareness via eclaw_context.b2bRemaining / b2bMax
|
|
15
15
|
* - Mission context via eclaw_context.missionHints
|
|
16
16
|
* - Silent suppression via silentToken (default "[SILENT]")
|
package/dist/webhook-handler.js
CHANGED
|
@@ -5,14 +5,14 @@ import { getClient, setActiveEvent, clearActiveEvent } from './outbound.js';
|
|
|
5
5
|
*
|
|
6
6
|
* Handles three event types:
|
|
7
7
|
* - 'message' → Normal human message; reply via sendMessage()
|
|
8
|
-
* - 'entity_message' → Bot-to-bot speak-to; reply via sendMessage(
|
|
9
|
-
* - 'broadcast' → Broadcast from another entity; reply via sendMessage(
|
|
8
|
+
* - 'entity_message' → Bot-to-bot speak-to; reply via sendMessage(text, state, { speakTo })
|
|
9
|
+
* - 'broadcast' → Broadcast from another entity; reply via sendMessage(text, state, { speakTo })
|
|
10
10
|
*
|
|
11
11
|
* The `deliver` callback routes AI response to the correct E-Claw endpoint
|
|
12
12
|
* based on the inbound event type.
|
|
13
13
|
*
|
|
14
|
-
* Channel Bot Context Parity v1.0.17
|
|
15
|
-
* - Bot-to-bot / broadcast
|
|
14
|
+
* Channel Bot Context Parity v1.0.17+:
|
|
15
|
+
* - Bot-to-bot / broadcast uses unified sendMessage() with speakTo option (single API call)
|
|
16
16
|
* - Quota awareness via eclaw_context.b2bRemaining / b2bMax
|
|
17
17
|
* - Mission context via eclaw_context.missionHints
|
|
18
18
|
* - Silent suppression via silentToken (default "[SILENT]")
|
|
@@ -50,7 +50,18 @@ cfg // full openclaw config (ctx.cfg from startAccount)
|
|
|
50
50
|
: msg.mediaType ? 'file'
|
|
51
51
|
: undefined;
|
|
52
52
|
// Build body — enrich with event context for bot-to-bot and broadcast
|
|
53
|
+
// Append media URL to body so text-based agents can see/analyze images
|
|
53
54
|
let body = msg.text || '';
|
|
55
|
+
if (msg.mediaUrl && msg.mediaType) {
|
|
56
|
+
const mediaLabel = msg.mediaType === 'photo' ? 'Image'
|
|
57
|
+
: msg.mediaType === 'voice' ? 'Voice'
|
|
58
|
+
: msg.mediaType === 'video' ? 'Video'
|
|
59
|
+
: 'File';
|
|
60
|
+
const urlToAppend = msg.backupUrl || msg.mediaUrl;
|
|
61
|
+
body = body
|
|
62
|
+
? `${body}\n[${mediaLabel}: ${urlToAppend}]`
|
|
63
|
+
: `[${mediaLabel}: ${urlToAppend}]`;
|
|
64
|
+
}
|
|
54
65
|
if ((event === 'entity_message' || event === 'broadcast') && fromEntityId !== undefined) {
|
|
55
66
|
const senderLabel = fromCharacter
|
|
56
67
|
? `Entity ${fromEntityId} (${fromCharacter})`
|
|
@@ -66,6 +77,12 @@ cfg // full openclaw config (ctx.cfg from startAccount)
|
|
|
66
77
|
.filter(Boolean)
|
|
67
78
|
.join('\n');
|
|
68
79
|
}
|
|
80
|
+
else if (event === 'kanban_notification') {
|
|
81
|
+
// Kanban notifications: merge missionHints into body so channel bots
|
|
82
|
+
// can see available API tools (same as webhook path gets them inline)
|
|
83
|
+
const missionBlock = eclawCtx?.missionHints ?? '';
|
|
84
|
+
body = [msg.text || '', missionBlock].filter(Boolean).join('\n');
|
|
85
|
+
}
|
|
69
86
|
// Build context in OpenClaw's native PascalCase format
|
|
70
87
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
88
|
const inboundCtx = {
|
|
@@ -103,9 +120,8 @@ cfg // full openclaw config (ctx.cfg from startAccount)
|
|
|
103
120
|
if (!text || text === silentToken)
|
|
104
121
|
return;
|
|
105
122
|
if ((event === 'entity_message' || event === 'broadcast') && fromEntityId !== undefined) {
|
|
106
|
-
// Bot-to-bot / broadcast: update
|
|
107
|
-
await client.sendMessage(text, 'IDLE');
|
|
108
|
-
await client.speakTo(fromEntityId, text, false);
|
|
123
|
+
// Bot-to-bot / broadcast: update wallpaper + deliver reply in one API call
|
|
124
|
+
await client.sendMessage(text, 'IDLE', { speakTo: [String(fromEntityId)] });
|
|
109
125
|
}
|
|
110
126
|
else {
|
|
111
127
|
// Normal human message: reply via channel message
|
|
@@ -118,7 +134,7 @@ cfg // full openclaw config (ctx.cfg from startAccount)
|
|
|
118
134
|
: rawType === 'audio' ? 'voice'
|
|
119
135
|
: rawType === 'video' ? 'video'
|
|
120
136
|
: 'file';
|
|
121
|
-
await client.sendMessage('', 'IDLE', mediaType, payload.mediaUrl);
|
|
137
|
+
await client.sendMessage('', 'IDLE', { mediaType, mediaUrl: payload.mediaUrl });
|
|
122
138
|
}
|
|
123
139
|
}
|
|
124
140
|
},
|