@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 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
- /** Send bot message to user (updates own entity state on wallpaper) */
20
- sendMessage(message: string, state?: string, mediaType?: string, mediaUrl?: string): Promise<MessageResponse>;
21
- /** Send bot-to-bot message to another entity (speak-to) */
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
- /** Broadcast message to all other bound entities */
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
- /** Send bot message to user (updates own entity state on wallpaper) */
67
- async sendMessage(message, state = 'IDLE', mediaType, mediaUrl) {
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
- /** Send bot-to-bot message to another entity (speak-to) */
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
- if (!this.deviceId || !this.botSecret) {
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
- /** Broadcast message to all other bound entities */
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
- if (!this.deviceId || !this.botSecret) {
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() + speakTo(fromEntityId)
7
- * - 'broadcast' → Broadcast from another entity; reply via sendMessage() + speakTo(fromEntityId)
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 now calls sendMessage() to update own wallpaper AND speakTo() to reply
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]")
@@ -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() + speakTo(fromEntityId)
9
- * - 'broadcast' → Broadcast from another entity; reply via sendMessage() + speakTo(fromEntityId)
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 now calls sendMessage() to update own wallpaper AND speakTo() to reply
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 own wallpaper AND reply to sender
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
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclaw/openclaw-channel",
3
- "version": "1.2.7",
3
+ "version": "1.3.0",
4
4
  "description": "E-Claw channel plugin for OpenClaw — AI chat platform for live wallpaper entities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",