@eclaw/openclaw-channel 1.0.13 → 1.0.15

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
@@ -12,13 +12,15 @@ export declare class EClawClient {
12
12
  constructor(config: EClawAccountConfig);
13
13
  /** Register callback URL with E-Claw backend */
14
14
  registerCallback(callbackUrl: string, callbackToken: string): Promise<RegisterResponse>;
15
- /** Bind an entity via channel API (bypasses 6-digit code) */
16
- bindEntity(entityId: number, name?: string): Promise<BindResponse>;
15
+ /** Bind an entity via channel API (bypasses 6-digit code).
16
+ * If entityId is omitted, the backend auto-selects the first free slot.
17
+ */
18
+ bindEntity(entityId?: number, name?: string): Promise<BindResponse>;
17
19
  /** Send bot message to user */
18
20
  sendMessage(message: string, state?: string, mediaType?: string, mediaUrl?: string): Promise<MessageResponse>;
19
21
  /** Unregister callback on shutdown */
20
22
  unregisterCallback(): Promise<void>;
21
23
  get currentDeviceId(): string | null;
22
24
  get currentBotSecret(): string | null;
23
- get currentEntityId(): number;
25
+ get currentEntityId(): number | undefined;
24
26
  }
package/dist/client.js CHANGED
@@ -11,7 +11,7 @@ export class EClawClient {
11
11
  constructor(config) {
12
12
  this.apiBase = config.apiBase;
13
13
  this.apiKey = config.apiKey;
14
- this.entityId = config.entityId;
14
+ this.entityId = config.entityId; // undefined until assigned by bindEntity
15
15
  }
16
16
  /** Register callback URL with E-Claw backend */
17
17
  async registerCallback(callbackUrl, callbackToken) {
@@ -31,24 +31,37 @@ export class EClawClient {
31
31
  this.deviceId = data.deviceId;
32
32
  return data;
33
33
  }
34
- /** Bind an entity via channel API (bypasses 6-digit code) */
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
+ */
35
37
  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;
36
44
  const res = await fetch(`${this.apiBase}/api/channel/bind`, {
37
45
  method: 'POST',
38
46
  headers: { 'Content-Type': 'application/json' },
39
- body: JSON.stringify({
40
- channel_api_key: this.apiKey,
41
- entityId,
42
- name: name || undefined,
43
- }),
47
+ body: JSON.stringify(body),
44
48
  });
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
50
  const data = await res.json();
46
51
  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
+ }
47
60
  throw new Error(data.message || `Bind failed (HTTP ${res.status})`);
48
61
  }
49
62
  this.botSecret = data.botSecret;
50
63
  this.deviceId = data.deviceId;
51
- this.entityId = entityId;
64
+ this.entityId = data.entityId; // Use server-assigned slot
52
65
  return data;
53
66
  }
54
67
  /** Send bot message to user */
package/dist/config.js CHANGED
@@ -28,7 +28,7 @@ export function resolveAccount(cfg, accountId) {
28
28
  apiKey: account?.apiKey ?? '',
29
29
  apiSecret: account?.apiSecret,
30
30
  apiBase: (account?.apiBase ?? 'https://eclawbot.com').replace(/\/$/, ''),
31
- entityId: account?.entityId ?? 0,
31
+ entityId: account?.entityId, // undefined = auto-assign
32
32
  botName: account?.botName,
33
33
  webhookUrl: account?.webhookUrl,
34
34
  };
package/dist/gateway.js CHANGED
@@ -80,16 +80,17 @@ export async function startAccount(ctx) {
80
80
  // Register callback with E-Claw backend
81
81
  const regData = await client.registerCallback(callbackUrl, callbackToken);
82
82
  console.log(`[E-Claw] Registered with E-Claw. Device: ${regData.deviceId}, Entities: ${regData.entities.length}`);
83
- // Auto-bind entity if not already bound
84
- const entity = regData.entities.find(e => e.entityId === account.entityId);
85
- if (!entity?.isBound) {
86
- console.log(`[E-Claw] Entity ${account.entityId} not bound, binding...`);
87
- const bindData = await client.bindEntity(account.entityId, account.botName);
88
- console.log(`[E-Claw] Bound entity ${account.entityId}, publicCode: ${bindData.publicCode}`);
89
- }
90
- else {
91
- console.log(`[E-Claw] Entity ${account.entityId} already bound, skipping bind`);
92
- }
83
+ // Bind entity via channel API.
84
+ // /api/channel/bind is idempotent for the same channel account:
85
+ // - Not bound → binds fresh, returns new botSecret
86
+ // - Already bound via this channel account returns existing botSecret
87
+ // - Bound via different method → throws error (user must unbind first)
88
+ const entityInfo = regData.entities.find(e => e.entityId === account.entityId);
89
+ const alreadyBound = entityInfo?.isBound ?? false;
90
+ const bindData = await client.bindEntity(account.entityId, account.botName);
91
+ console.log(alreadyBound
92
+ ? `[E-Claw] Entity ${account.entityId} reconnected (existing channel binding), publicCode: ${bindData.publicCode}`
93
+ : `[E-Claw] Entity ${account.entityId} bound, publicCode: ${bindData.publicCode}`);
93
94
  console.log(`[E-Claw] Account ${accountId} ready!`);
94
95
  }
95
96
  catch (err) {
package/dist/types.d.ts CHANGED
@@ -4,7 +4,7 @@ export interface EClawAccountConfig {
4
4
  apiKey: string;
5
5
  apiSecret?: string;
6
6
  apiBase: string;
7
- entityId: number;
7
+ entityId?: number;
8
8
  botName?: string;
9
9
  webhookUrl?: string;
10
10
  }
@@ -50,6 +50,13 @@ export interface BindResponse {
50
50
  publicCode: string;
51
51
  bindingType: string;
52
52
  }
53
+ /** Error response when all entity slots are full */
54
+ export interface SlotsFullError {
55
+ success: false;
56
+ message: string;
57
+ entities: EClawEntityInfo[];
58
+ hint: string;
59
+ }
53
60
  /** Response from POST /api/channel/message */
54
61
  export interface MessageResponse {
55
62
  success: boolean;
@@ -16,7 +16,7 @@
16
16
  "apiKey": { "type": "string", "description": "Channel API Key (eck_...)" },
17
17
  "apiSecret": { "type": "string", "description": "Channel API Secret (ecs_...)" },
18
18
  "apiBase": { "type": "string", "default": "https://eclawbot.com" },
19
- "entityId": { "type": "number", "default": 0, "minimum": 0, "maximum": 7 },
19
+ "entityId": { "type": "number", "minimum": 0, "maximum": 7, "description": "Optional: entity slot to use (0-7). If omitted, auto-assigned to first free slot." },
20
20
  "botName": { "type": "string", "maxLength": 20 }
21
21
  },
22
22
  "required": ["apiKey", "apiSecret"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclaw/openclaw-channel",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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",