adp-openclaw 0.0.13 → 0.0.14

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adp-openclaw",
3
- "version": "0.0.13",
4
- "description": "OpenClaw demo channel plugin (Go WebSocket backend)",
3
+ "version": "0.0.14",
4
+ "description": "ADP-OpenClaw demo channel plugin (Go WebSocket backend)",
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "ws": "^8.16.0",
@@ -16,10 +16,10 @@
16
16
  ],
17
17
  "channel": {
18
18
  "id": "adp-openclaw",
19
- "label": "ADP OpenClaw",
20
- "selectionLabel": "ADP OpenClaw",
19
+ "label": "ADP-OpenClaw",
20
+ "selectionLabel": "ADP-OpenClaw",
21
21
  "docsPath": "/channels/adp-openclaw",
22
- "blurb": "ADP channel backed by a Go WebSocket server.",
22
+ "blurb": "ADP-OpenClaw channel backed by a Go WebSocket server.",
23
23
  "order": 999
24
24
  }
25
25
  }
package/src/channel.ts CHANGED
@@ -7,14 +7,15 @@ import {
7
7
  DEFAULT_ACCOUNT_ID,
8
8
  } from "openclaw/plugin-sdk";
9
9
 
10
+ // Default WebSocket URL for ADP OpenClaw
11
+ const DEFAULT_WS_URL = "wss://wss.lke.cloud.tencent.com/bot/gateway/ws/";
12
+
10
13
  // Channel-level config type (from channels["adp-openclaw"])
11
14
  export type AdpOpenclawChannelConfig = {
12
15
  enabled?: boolean;
13
- serverUrl?: string;
14
- wsUrl?: string;
16
+ wsUrl?: string; // WebSocket URL (optional, has default)
15
17
  clientToken?: string;
16
- hmacKey?: string; // HMAC key for signature generation
17
- pollIntervalMs?: number;
18
+ signKey?: string; // HMAC key for signature generation
18
19
  };
19
20
 
20
21
  export type ResolvedAdpOpenclawAccount = {
@@ -22,29 +23,20 @@ export type ResolvedAdpOpenclawAccount = {
22
23
  name: string;
23
24
  enabled: boolean;
24
25
  configured: boolean;
25
- serverUrl: string;
26
+ wsUrl: string; // WebSocket URL
26
27
  clientToken: string;
27
- hmacKey: string; // HMAC key for signature generation
28
- pollIntervalMs: number;
28
+ signKey: string; // HMAC key for signature generation
29
29
  };
30
30
 
31
31
  function resolveAdpOpenclawCredentials(channelCfg?: AdpOpenclawChannelConfig): {
32
- serverUrl: string;
32
+ wsUrl: string;
33
33
  clientToken: string;
34
- hmacKey: string;
35
- pollIntervalMs: number;
34
+ signKey: string;
36
35
  } | null {
37
- // Get serverUrl: support both serverUrl and wsUrl
38
- let serverUrl = channelCfg?.serverUrl?.trim();
39
- if (!serverUrl && channelCfg?.wsUrl) {
40
- // Convert ws://host:port/ws to http://host:port
41
- serverUrl = channelCfg.wsUrl
42
- .replace(/^wss:\/\//, "https://")
43
- .replace(/^ws:\/\//, "http://")
44
- .replace(/\/ws\/?$/, "");
45
- }
46
- if (!serverUrl) {
47
- serverUrl = process.env.ADP_OPENCLAW_SERVER_URL || "";
36
+ // Get wsUrl from config or env (has default value)
37
+ let wsUrl = channelCfg?.wsUrl?.trim();
38
+ if (!wsUrl) {
39
+ wsUrl = process.env.ADP_OPENCLAW_WS_URL || DEFAULT_WS_URL;
48
40
  }
49
41
 
50
42
  // Get clientToken from config or env
@@ -53,20 +45,18 @@ function resolveAdpOpenclawCredentials(channelCfg?: AdpOpenclawChannelConfig): {
53
45
  clientToken = process.env.ADP_OPENCLAW_CLIENT_TOKEN || "";
54
46
  }
55
47
 
56
- // Get hmacKey from config or env (default: ADPOpenClaw)
57
- let hmacKey = channelCfg?.hmacKey?.trim();
58
- if (!hmacKey) {
59
- hmacKey = process.env.ADP_OPENCLAW_HMAC_KEY || "ADPOpenClaw";
48
+ // Get signKey from config or env (default: ADPOpenClaw)
49
+ let signKey = channelCfg?.signKey?.trim();
50
+ if (!signKey) {
51
+ signKey = process.env.ADP_OPENCLAW_SIGN_KEY || "ADPOpenClaw";
60
52
  }
61
53
 
62
- // Both are required for configured status
63
- if (!serverUrl || !clientToken) {
54
+ // clientToken is required for configured status (wsUrl has default)
55
+ if (!clientToken) {
64
56
  return null;
65
57
  }
66
58
 
67
- const pollIntervalMs = channelCfg?.pollIntervalMs ?? 1000;
68
-
69
- return { serverUrl, clientToken, hmacKey, pollIntervalMs };
59
+ return { wsUrl, clientToken, signKey };
70
60
  }
71
61
 
72
62
  function resolveAccount(cfg: ClawdbotConfig, accountId?: string): ResolvedAdpOpenclawAccount {
@@ -79,10 +69,9 @@ function resolveAccount(cfg: ClawdbotConfig, accountId?: string): ResolvedAdpOpe
79
69
  name: "ADP OpenClaw",
80
70
  enabled,
81
71
  configured: Boolean(creds),
82
- serverUrl: creds?.serverUrl || "http://localhost:9876",
72
+ wsUrl: creds?.wsUrl || DEFAULT_WS_URL,
83
73
  clientToken: creds?.clientToken || "",
84
- hmacKey: creds?.hmacKey || "ADPOpenClaw",
85
- pollIntervalMs: creds?.pollIntervalMs || 1000,
74
+ signKey: creds?.signKey || "ADPOpenClaw",
86
75
  };
87
76
  }
88
77
 
@@ -115,11 +104,9 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
115
104
  additionalProperties: false,
116
105
  properties: {
117
106
  enabled: { type: "boolean" },
118
- serverUrl: { type: "string" },
119
- wsUrl: { type: "string" },
107
+ wsUrl: { type: "string" }, // WebSocket URL (optional, default: wss://wss.lke.cloud.tencent.com/bot/gateway/ws/)
120
108
  clientToken: { type: "string" },
121
- hmacKey: { type: "string" },
122
- pollIntervalMs: { type: "integer", minimum: 100 },
109
+ signKey: { type: "string" },
123
110
  },
124
111
  },
125
112
  },
@@ -155,7 +142,7 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
155
142
  name: account.name,
156
143
  enabled: account.enabled,
157
144
  configured: account.configured,
158
- baseUrl: account.serverUrl,
145
+ wsUrl: account.wsUrl,
159
146
  }),
160
147
  resolveAllowFrom: () => [],
161
148
  formatAllowFrom: ({ allowFrom }) => allowFrom,
@@ -184,39 +171,27 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
184
171
  collectStatusIssues: () => [],
185
172
  buildChannelSummary: ({ snapshot }) => ({
186
173
  configured: snapshot.configured ?? false,
187
- baseUrl: snapshot.baseUrl ?? null,
174
+ wsUrl: snapshot.wsUrl ?? null,
188
175
  running: snapshot.running ?? false,
189
176
  lastStartAt: snapshot.lastStartAt ?? null,
190
177
  lastStopAt: snapshot.lastStopAt ?? null,
191
178
  lastError: snapshot.lastError ?? null,
192
179
  }),
193
- probeAccount: async ({ cfg, timeoutMs }) => {
180
+ probeAccount: async ({ cfg }) => {
181
+ // For WebSocket-only architecture, we just check if config is valid
194
182
  const account = resolveAccount(cfg);
195
183
  const start = Date.now();
196
- try {
197
- const controller = new AbortController();
198
- const timeout = setTimeout(() => controller.abort(), timeoutMs ?? 5000);
199
- const res = await fetch(`${account.serverUrl}/health`, { signal: controller.signal });
200
- clearTimeout(timeout);
201
- const data = (await res.json()) as { ok?: boolean };
202
- return {
203
- ok: data.ok === true,
204
- elapsedMs: Date.now() - start,
205
- };
206
- } catch (err) {
207
- return {
208
- ok: false,
209
- error: err instanceof Error ? err.message : String(err),
210
- elapsedMs: Date.now() - start,
211
- };
212
- }
184
+ return {
185
+ ok: account.configured && Boolean(account.clientToken),
186
+ elapsedMs: Date.now() - start,
187
+ };
213
188
  },
214
189
  buildAccountSnapshot: ({ account, runtime, probe }) => ({
215
190
  accountId: account.accountId,
216
191
  name: account.name,
217
192
  enabled: account.enabled,
218
193
  configured: account.configured,
219
- baseUrl: account.serverUrl,
194
+ wsUrl: account.wsUrl,
220
195
  running: runtime?.running ?? false,
221
196
  lastStartAt: runtime?.lastStartAt ?? null,
222
197
  lastStopAt: runtime?.lastStopAt ?? null,
@@ -227,61 +202,20 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
227
202
  gateway: {
228
203
  startAccount: async (ctx) => {
229
204
  const account = ctx.account;
230
- ctx.setStatus({ accountId: account.accountId, baseUrl: account.serverUrl });
231
- ctx.log?.info(`[adp-openclaw] starting poll loop → ${account.serverUrl}`);
205
+ ctx.setStatus({ accountId: account.accountId, wsUrl: account.wsUrl });
206
+ ctx.log?.info(`[adp-openclaw] starting WebSocket connection → ${account.wsUrl}`);
232
207
 
233
208
  const { monitorAdpOpenclaw } = await import("./monitor.js");
234
209
  return monitorAdpOpenclaw({
235
- serverUrl: account.serverUrl,
210
+ wsUrl: account.wsUrl,
236
211
  clientToken: account.clientToken,
237
- hmacKey: account.hmacKey,
238
- pollIntervalMs: account.pollIntervalMs,
212
+ signKey: account.signKey,
239
213
  abortSignal: ctx.abortSignal,
240
214
  log: ctx.log,
241
215
  cfg: ctx.cfg,
242
216
  });
243
217
  },
244
218
  },
245
- outbound: {
246
- send: async ({ target, message, cfg, context }: {
247
- target: string;
248
- message: string;
249
- cfg: unknown;
250
- context?: {
251
- conversationId?: string;
252
- userId?: string;
253
- username?: string;
254
- tenantId?: string;
255
- };
256
- }) => {
257
- const account = resolveAccount(cfg as ClawdbotConfig);
258
- const conversationId = context?.conversationId || "";
259
-
260
- // Extract user info from context for response tracing
261
- const userInfo = context?.userId ? {
262
- userId: context.userId,
263
- username: context.username,
264
- tenantId: context.tenantId,
265
- } : undefined;
266
-
267
- const res = await fetch(`${account.serverUrl}/send`, {
268
- method: "POST",
269
- headers: {
270
- "Content-Type": "application/json",
271
- Authorization: `Bearer ${account.clientToken}`,
272
- },
273
- body: JSON.stringify({
274
- to: target,
275
- text: message,
276
- conversationId,
277
- user: userInfo,
278
- }),
279
- });
280
- const data = (await res.json()) as { ok?: boolean; message?: { id?: string } };
281
- return {
282
- ok: data.ok === true,
283
- messageId: data.message?.id,
284
- };
285
- },
286
- },
219
+ // Note: outbound.send is not available in WebSocket-only architecture
220
+ // All message sending is done through the WebSocket connection in monitor.ts
287
221
  };
@@ -2,8 +2,7 @@ import { z } from "zod";
2
2
 
3
3
  export const AdpOpenclawConfigSchema = z.object({
4
4
  enabled: z.boolean().optional(),
5
- serverUrl: z.string().optional(),
5
+ wsUrl: z.string().optional(), // WebSocket URL (optional, default: wss://wss.lke.cloud.tencent.com/bot/gateway/ws/)
6
6
  clientToken: z.string().optional(),
7
- hmacKey: z.string().optional(),
8
- pollIntervalMs: z.number().optional(),
7
+ signKey: z.string().optional(),
9
8
  });
package/src/monitor.ts CHANGED
@@ -5,11 +5,13 @@ import type { PluginLogger, ClawdbotConfig } from "openclaw/plugin-sdk";
5
5
  import { getAdpOpenclawRuntime } from "./runtime.js";
6
6
  import crypto from "crypto";
7
7
 
8
+ // WebSocket reconnect delay (fixed at 1 second)
9
+ const RECONNECT_DELAY_MS = 1000;
10
+
8
11
  export type MonitorParams = {
9
- serverUrl: string;
12
+ wsUrl: string; // WebSocket URL (direct, no conversion needed)
10
13
  clientToken: string;
11
- hmacKey?: string; // HMAC key for signature generation
12
- pollIntervalMs: number; // Used as reconnect delay
14
+ signKey?: string; // HMAC key for signature generation
13
15
  abortSignal?: AbortSignal;
14
16
  log?: PluginLogger;
15
17
  cfg?: ClawdbotConfig; // OpenClaw config for model settings
@@ -67,10 +69,10 @@ type AuthResultPayload = {
67
69
  };
68
70
 
69
71
  // Generate HMAC-SHA256 signature for authentication (includes timestamp for anti-replay)
70
- // Uses hmacKey as the HMAC key, and "token:nonce:timestamp" as the message
71
- function generateSignature(hmacKey: string, token: string, nonce: string, timestamp: number): string {
72
- // Use HMAC-SHA256 with hmacKey as the key, and "token:nonce:timestamp" as the message
73
- return crypto.createHmac("sha256", hmacKey).update(`${token}:${nonce}:${timestamp}`).digest("hex");
72
+ // Uses signKey as the HMAC key, and "token:nonce:timestamp" as the message
73
+ function generateSignature(signKey: string, token: string, nonce: string, timestamp: number): string {
74
+ // Use HMAC-SHA256 with signKey as the key, and "token:nonce:timestamp" as the message
75
+ return crypto.createHmac("sha256", signKey).update(`${token}:${nonce}:${timestamp}`).digest("hex");
74
76
  }
75
77
 
76
78
  // Generate random nonce
@@ -84,12 +86,9 @@ function generateRequestId(): string {
84
86
  }
85
87
 
86
88
  export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
87
- const { serverUrl, clientToken, hmacKey, pollIntervalMs, abortSignal, log, cfg } = params;
89
+ const { wsUrl, clientToken, signKey, abortSignal, log, cfg } = params;
88
90
  const runtime = getAdpOpenclawRuntime();
89
91
 
90
- // Convert HTTP URL to WebSocket URL
91
- const wsUrl = serverUrl.replace(/^http/, "ws") + "/ws";
92
-
93
92
  log?.info(`[adp-openclaw] WebSocket monitor started, connecting to ${wsUrl}`);
94
93
 
95
94
  while (!abortSignal?.aborted) {
@@ -97,8 +96,7 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
97
96
  await connectAndHandle({
98
97
  wsUrl,
99
98
  clientToken,
100
- hmacKey,
101
- serverUrl,
99
+ signKey,
102
100
  abortSignal,
103
101
  log,
104
102
  runtime,
@@ -111,8 +109,8 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
111
109
 
112
110
  // Wait before reconnecting
113
111
  if (!abortSignal?.aborted) {
114
- log?.info(`[adp-openclaw] Reconnecting in ${pollIntervalMs}ms...`);
115
- await sleep(pollIntervalMs, abortSignal);
112
+ log?.info(`[adp-openclaw] Reconnecting in ${RECONNECT_DELAY_MS}ms...`);
113
+ await sleep(RECONNECT_DELAY_MS, abortSignal);
116
114
  }
117
115
  }
118
116
 
@@ -122,8 +120,7 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
122
120
  type ConnectParams = {
123
121
  wsUrl: string;
124
122
  clientToken: string;
125
- hmacKey?: string;
126
- serverUrl: string;
123
+ signKey?: string;
127
124
  abortSignal?: AbortSignal;
128
125
  log?: PluginLogger;
129
126
  runtime: ReturnType<typeof getAdpOpenclawRuntime>;
@@ -131,7 +128,7 @@ type ConnectParams = {
131
128
  };
132
129
 
133
130
  async function connectAndHandle(params: ConnectParams): Promise<void> {
134
- const { wsUrl, clientToken, hmacKey, serverUrl, abortSignal, log, runtime, cfg } = params;
131
+ const { wsUrl, clientToken, signKey, abortSignal, log, runtime, cfg } = params;
135
132
 
136
133
  // Dynamic import for WebSocket (works in both Node.js and browser)
137
134
  const WebSocket = (await import("ws")).default;
@@ -154,17 +151,17 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
154
151
  // Send authentication message with signature (includes timestamp for anti-replay)
155
152
  const nonce = generateNonce();
156
153
  const timestamp = Date.now();
157
- // Generate signature only if hmacKey is provided
158
- const signature = hmacKey ? generateSignature(hmacKey, clientToken, nonce, timestamp) : "";
154
+ // Generate signature only if signKey is provided
155
+ const signature = signKey ? generateSignature(signKey, clientToken, nonce, timestamp) : "";
159
156
 
160
157
  const authMsg: WSMessage = {
161
158
  type: MsgType.Auth,
162
159
  requestId: generateRequestId(),
163
160
  payload: {
164
161
  token: clientToken,
165
- nonce: hmacKey ? nonce : undefined,
166
- signature: hmacKey ? signature : undefined,
167
- timestamp: hmacKey ? timestamp : undefined, // Include timestamp in payload for server verification
162
+ nonce: signKey ? nonce : undefined,
163
+ signature: signKey ? signature : undefined,
164
+ timestamp: signKey ? timestamp : undefined, // Include timestamp in payload for server verification
168
165
  },
169
166
  timestamp: Date.now(),
170
167
  };
@@ -313,6 +310,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
313
310
  }
314
311
  };
315
312
 
313
+ log?.info(`[adp-openclaw] Starting dispatchReplyWithBufferedBlockDispatcher for ${displayName}`);
316
314
  await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
317
315
  ctx,
318
316
  cfg: cfg ?? {},
@@ -366,10 +364,10 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
366
364
  // SDK calls this with info.kind = "block" for streaming chunks, "final" for complete response
367
365
  deliver: async (payload: { text?: string }, info?: { kind?: string }) => {
368
366
  const text = payload.text || "";
369
- const kind = info?.kind || "final";
367
+ const kind = info?.kind;
370
368
 
371
- // Debug log for all deliver calls
372
- log?.debug?.(`[adp-openclaw] deliver called with kind=${kind}, text length=${text.length}`);
369
+ // Debug log for all deliver calls - log the actual info object
370
+ log?.info(`[adp-openclaw] deliver called: kind=${kind}, text.length=${text.length}, info=${JSON.stringify(info)}`);
373
371
 
374
372
  // Handle streaming block - send chunk via WebSocket
375
373
  if (kind === "block" && text) {
@@ -400,9 +398,11 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
400
398
  return;
401
399
  }
402
400
 
403
- // Handle final reply - send outbound_end
404
- if (kind === "final") {
405
- sendOutboundEnd(text);
401
+ // Handle final reply or undefined kind - send outbound_end
402
+ // SDK may call deliver without kind when streaming ends
403
+ if (kind === "final" || kind === undefined) {
404
+ log?.info(`[adp-openclaw] deliver triggering sendOutboundEnd (kind=${kind})`);
405
+ sendOutboundEnd(text || lastPartialText);
406
406
  }
407
407
  },
408
408
  onError: (err: Error) => {
@@ -411,6 +411,8 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
411
411
  },
412
412
  });
413
413
 
414
+ log?.info(`[adp-openclaw] dispatchReplyWithBufferedBlockDispatcher returned (finalSent=${finalSent}, chunkIndex=${chunkIndex})`);
415
+
414
416
  // IMPORTANT: After dispatchReplyWithBufferedBlockDispatcher completes,
415
417
  // ensure outbound_end is sent even if "final" deliver was not called.
416
418
  // This handles cases where the SDK only sends blocks without a final callback.
package/src/runtime.ts CHANGED
@@ -7,9 +7,7 @@ let adpOpenclawRuntime: PluginRuntime | null = null;
7
7
  export type PluginConfig = {
8
8
  wsUrl?: string;
9
9
  clientToken?: string;
10
- hmacKey?: string;
11
- serverUrl?: string;
12
- pollIntervalMs?: number;
10
+ signKey?: string;
13
11
  };
14
12
 
15
13
  let pluginConfig: PluginConfig = {};