adp-openclaw 0.0.13 → 0.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/package.json +5 -5
- package/src/channel.ts +39 -105
- package/src/config-schema.ts +2 -3
- package/src/monitor.ts +37 -30
- package/src/runtime.ts +1 -3
- package/e2e_test.cjs +0 -98
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adp-openclaw",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "OpenClaw demo channel plugin (Go WebSocket backend)",
|
|
3
|
+
"version": "0.0.15",
|
|
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
|
|
20
|
-
"selectionLabel": "ADP
|
|
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
|
-
|
|
14
|
-
wsUrl?: string;
|
|
16
|
+
wsUrl?: string; // WebSocket URL (optional, has default)
|
|
15
17
|
clientToken?: string;
|
|
16
|
-
|
|
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
|
-
|
|
26
|
+
wsUrl: string; // WebSocket URL
|
|
26
27
|
clientToken: string;
|
|
27
|
-
|
|
28
|
-
pollIntervalMs: number;
|
|
28
|
+
signKey: string; // HMAC key for signature generation
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
function resolveAdpOpenclawCredentials(channelCfg?: AdpOpenclawChannelConfig): {
|
|
32
|
-
|
|
32
|
+
wsUrl: string;
|
|
33
33
|
clientToken: string;
|
|
34
|
-
|
|
35
|
-
pollIntervalMs: number;
|
|
34
|
+
signKey: string;
|
|
36
35
|
} | null {
|
|
37
|
-
// Get
|
|
38
|
-
let
|
|
39
|
-
if (!
|
|
40
|
-
|
|
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
|
|
57
|
-
let
|
|
58
|
-
if (!
|
|
59
|
-
|
|
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
|
-
//
|
|
63
|
-
if (!
|
|
54
|
+
// clientToken is required for configured status (wsUrl has default)
|
|
55
|
+
if (!clientToken) {
|
|
64
56
|
return null;
|
|
65
57
|
}
|
|
66
58
|
|
|
67
|
-
|
|
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
|
-
|
|
72
|
+
wsUrl: creds?.wsUrl || DEFAULT_WS_URL,
|
|
83
73
|
clientToken: creds?.clientToken || "",
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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,
|
|
231
|
-
ctx.log?.info(`[adp-openclaw] starting
|
|
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
|
-
|
|
210
|
+
wsUrl: account.wsUrl,
|
|
236
211
|
clientToken: account.clientToken,
|
|
237
|
-
|
|
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
|
-
|
|
246
|
-
|
|
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
|
};
|
package/src/config-schema.ts
CHANGED
|
@@ -2,8 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const AdpOpenclawConfigSchema = z.object({
|
|
4
4
|
enabled: z.boolean().optional(),
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
+
wsUrl: string; // WebSocket URL (direct, no conversion needed)
|
|
10
13
|
clientToken: string;
|
|
11
|
-
|
|
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
|
|
@@ -52,6 +54,7 @@ type UserInfo = {
|
|
|
52
54
|
type InboundMessage = {
|
|
53
55
|
id: string;
|
|
54
56
|
conversationId: string;
|
|
57
|
+
recordId?: string; // Record ID from server for tracking message pairs
|
|
55
58
|
clientId: string;
|
|
56
59
|
from: string;
|
|
57
60
|
to: string;
|
|
@@ -67,10 +70,10 @@ type AuthResultPayload = {
|
|
|
67
70
|
};
|
|
68
71
|
|
|
69
72
|
// Generate HMAC-SHA256 signature for authentication (includes timestamp for anti-replay)
|
|
70
|
-
// Uses
|
|
71
|
-
function generateSignature(
|
|
72
|
-
// Use HMAC-SHA256 with
|
|
73
|
-
return crypto.createHmac("sha256",
|
|
73
|
+
// Uses signKey as the HMAC key, and "token:nonce:timestamp" as the message
|
|
74
|
+
function generateSignature(signKey: string, token: string, nonce: string, timestamp: number): string {
|
|
75
|
+
// Use HMAC-SHA256 with signKey as the key, and "token:nonce:timestamp" as the message
|
|
76
|
+
return crypto.createHmac("sha256", signKey).update(`${token}:${nonce}:${timestamp}`).digest("hex");
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
// Generate random nonce
|
|
@@ -84,12 +87,9 @@ function generateRequestId(): string {
|
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
87
|
-
const {
|
|
90
|
+
const { wsUrl, clientToken, signKey, abortSignal, log, cfg } = params;
|
|
88
91
|
const runtime = getAdpOpenclawRuntime();
|
|
89
92
|
|
|
90
|
-
// Convert HTTP URL to WebSocket URL
|
|
91
|
-
const wsUrl = serverUrl.replace(/^http/, "ws") + "/ws";
|
|
92
|
-
|
|
93
93
|
log?.info(`[adp-openclaw] WebSocket monitor started, connecting to ${wsUrl}`);
|
|
94
94
|
|
|
95
95
|
while (!abortSignal?.aborted) {
|
|
@@ -97,8 +97,7 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
|
97
97
|
await connectAndHandle({
|
|
98
98
|
wsUrl,
|
|
99
99
|
clientToken,
|
|
100
|
-
|
|
101
|
-
serverUrl,
|
|
100
|
+
signKey,
|
|
102
101
|
abortSignal,
|
|
103
102
|
log,
|
|
104
103
|
runtime,
|
|
@@ -111,8 +110,8 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
|
111
110
|
|
|
112
111
|
// Wait before reconnecting
|
|
113
112
|
if (!abortSignal?.aborted) {
|
|
114
|
-
log?.info(`[adp-openclaw] Reconnecting in ${
|
|
115
|
-
await sleep(
|
|
113
|
+
log?.info(`[adp-openclaw] Reconnecting in ${RECONNECT_DELAY_MS}ms...`);
|
|
114
|
+
await sleep(RECONNECT_DELAY_MS, abortSignal);
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
117
|
|
|
@@ -122,8 +121,7 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
|
122
121
|
type ConnectParams = {
|
|
123
122
|
wsUrl: string;
|
|
124
123
|
clientToken: string;
|
|
125
|
-
|
|
126
|
-
serverUrl: string;
|
|
124
|
+
signKey?: string;
|
|
127
125
|
abortSignal?: AbortSignal;
|
|
128
126
|
log?: PluginLogger;
|
|
129
127
|
runtime: ReturnType<typeof getAdpOpenclawRuntime>;
|
|
@@ -131,7 +129,7 @@ type ConnectParams = {
|
|
|
131
129
|
};
|
|
132
130
|
|
|
133
131
|
async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
134
|
-
const { wsUrl, clientToken,
|
|
132
|
+
const { wsUrl, clientToken, signKey, abortSignal, log, runtime, cfg } = params;
|
|
135
133
|
|
|
136
134
|
// Dynamic import for WebSocket (works in both Node.js and browser)
|
|
137
135
|
const WebSocket = (await import("ws")).default;
|
|
@@ -154,17 +152,17 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
154
152
|
// Send authentication message with signature (includes timestamp for anti-replay)
|
|
155
153
|
const nonce = generateNonce();
|
|
156
154
|
const timestamp = Date.now();
|
|
157
|
-
// Generate signature only if
|
|
158
|
-
const signature =
|
|
155
|
+
// Generate signature only if signKey is provided
|
|
156
|
+
const signature = signKey ? generateSignature(signKey, clientToken, nonce, timestamp) : "";
|
|
159
157
|
|
|
160
158
|
const authMsg: WSMessage = {
|
|
161
159
|
type: MsgType.Auth,
|
|
162
160
|
requestId: generateRequestId(),
|
|
163
161
|
payload: {
|
|
164
162
|
token: clientToken,
|
|
165
|
-
nonce:
|
|
166
|
-
signature:
|
|
167
|
-
timestamp:
|
|
163
|
+
nonce: signKey ? nonce : undefined,
|
|
164
|
+
signature: signKey ? signature : undefined,
|
|
165
|
+
timestamp: signKey ? timestamp : undefined, // Include timestamp in payload for server verification
|
|
168
166
|
},
|
|
169
167
|
timestamp: Date.now(),
|
|
170
168
|
};
|
|
@@ -208,7 +206,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
208
206
|
if (!authenticated) break;
|
|
209
207
|
|
|
210
208
|
const inMsg = msg.payload as InboundMessage;
|
|
211
|
-
log?.info(`[adp-openclaw] Received: ${inMsg.from}: ${inMsg.text} (conv=${inMsg.conversationId}, user=${JSON.stringify(inMsg.user || {})})`);
|
|
209
|
+
log?.info(`[adp-openclaw] Received: ${inMsg.from}: ${inMsg.text} (conv=${inMsg.conversationId}, rec=${inMsg.recordId || 'none'}, user=${JSON.stringify(inMsg.user || {})})`);
|
|
212
210
|
|
|
213
211
|
// Process the message with full user identity
|
|
214
212
|
try {
|
|
@@ -288,6 +286,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
288
286
|
to: inMsg.from,
|
|
289
287
|
text: text,
|
|
290
288
|
conversationId: inMsg.conversationId,
|
|
289
|
+
recordId: inMsg.recordId, // Pass recordId back to server
|
|
291
290
|
streamId: streamId,
|
|
292
291
|
totalChunks: chunkIndex,
|
|
293
292
|
user: inMsg.user,
|
|
@@ -305,6 +304,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
305
304
|
to: inMsg.from,
|
|
306
305
|
text: text,
|
|
307
306
|
conversationId: inMsg.conversationId,
|
|
307
|
+
recordId: inMsg.recordId, // Pass recordId back to server
|
|
308
308
|
user: inMsg.user,
|
|
309
309
|
},
|
|
310
310
|
timestamp: Date.now(),
|
|
@@ -313,6 +313,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
313
313
|
}
|
|
314
314
|
};
|
|
315
315
|
|
|
316
|
+
log?.info(`[adp-openclaw] Starting dispatchReplyWithBufferedBlockDispatcher for ${displayName}`);
|
|
316
317
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
317
318
|
ctx,
|
|
318
319
|
cfg: cfg ?? {},
|
|
@@ -349,6 +350,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
349
350
|
to: inMsg.from,
|
|
350
351
|
chunk: delta, // Send only the new delta, not cumulative
|
|
351
352
|
conversationId: inMsg.conversationId,
|
|
353
|
+
recordId: inMsg.recordId, // Pass recordId back to server
|
|
352
354
|
streamId: streamId,
|
|
353
355
|
index: chunkIndex,
|
|
354
356
|
isPartial: true, // Mark as incremental delta
|
|
@@ -366,10 +368,10 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
366
368
|
// SDK calls this with info.kind = "block" for streaming chunks, "final" for complete response
|
|
367
369
|
deliver: async (payload: { text?: string }, info?: { kind?: string }) => {
|
|
368
370
|
const text = payload.text || "";
|
|
369
|
-
const kind = info?.kind
|
|
371
|
+
const kind = info?.kind;
|
|
370
372
|
|
|
371
|
-
// Debug log for all deliver calls
|
|
372
|
-
log?.
|
|
373
|
+
// Debug log for all deliver calls - log the actual info object
|
|
374
|
+
log?.info(`[adp-openclaw] deliver called: kind=${kind}, text.length=${text.length}, info=${JSON.stringify(info)}`);
|
|
373
375
|
|
|
374
376
|
// Handle streaming block - send chunk via WebSocket
|
|
375
377
|
if (kind === "block" && text) {
|
|
@@ -382,6 +384,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
382
384
|
to: inMsg.from,
|
|
383
385
|
chunk: text,
|
|
384
386
|
conversationId: inMsg.conversationId,
|
|
387
|
+
recordId: inMsg.recordId, // Pass recordId back to server
|
|
385
388
|
streamId: streamId,
|
|
386
389
|
index: chunkIndex,
|
|
387
390
|
user: inMsg.user,
|
|
@@ -400,9 +403,11 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
400
403
|
return;
|
|
401
404
|
}
|
|
402
405
|
|
|
403
|
-
// Handle final reply - send outbound_end
|
|
404
|
-
|
|
405
|
-
|
|
406
|
+
// Handle final reply or undefined kind - send outbound_end
|
|
407
|
+
// SDK may call deliver without kind when streaming ends
|
|
408
|
+
if (kind === "final" || kind === undefined) {
|
|
409
|
+
log?.info(`[adp-openclaw] deliver triggering sendOutboundEnd (kind=${kind})`);
|
|
410
|
+
sendOutboundEnd(text || lastPartialText);
|
|
406
411
|
}
|
|
407
412
|
},
|
|
408
413
|
onError: (err: Error) => {
|
|
@@ -411,6 +416,8 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
411
416
|
},
|
|
412
417
|
});
|
|
413
418
|
|
|
419
|
+
log?.info(`[adp-openclaw] dispatchReplyWithBufferedBlockDispatcher returned (finalSent=${finalSent}, chunkIndex=${chunkIndex})`);
|
|
420
|
+
|
|
414
421
|
// IMPORTANT: After dispatchReplyWithBufferedBlockDispatcher completes,
|
|
415
422
|
// ensure outbound_end is sent even if "final" deliver was not called.
|
|
416
423
|
// 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
|
-
|
|
11
|
-
serverUrl?: string;
|
|
12
|
-
pollIntervalMs?: number;
|
|
10
|
+
signKey?: string;
|
|
13
11
|
};
|
|
14
12
|
|
|
15
13
|
let pluginConfig: PluginConfig = {};
|
package/e2e_test.cjs
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
const WebSocket = require('ws');
|
|
2
|
-
|
|
3
|
-
// 测试配置
|
|
4
|
-
const token = '334b209ed0833464202cc4438e7ce801583fc74c13384cbac60d02949f33e09c';
|
|
5
|
-
const host = '101.32.33.231';
|
|
6
|
-
const port = 9876;
|
|
7
|
-
|
|
8
|
-
console.log('🧪 OpenClaw 发送消息测试');
|
|
9
|
-
console.log(`📍 目标服务器: ${host}:${port}`);
|
|
10
|
-
console.log(`🔑 Token: ${token.slice(0, 16)}...\n`);
|
|
11
|
-
|
|
12
|
-
// Step 1: 测试 WebSocket 连接
|
|
13
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
14
|
-
console.log('1️⃣ 连接 WebSocket...');
|
|
15
|
-
const ws = new WebSocket(`ws://${host}:${port}/ws`);
|
|
16
|
-
|
|
17
|
-
ws.on('open', () => {
|
|
18
|
-
console.log(' ✅ WebSocket 连接成功');
|
|
19
|
-
console.log(' 📤 发送认证请求...');
|
|
20
|
-
ws.send(JSON.stringify({ type: 'auth', payload: { token, name: 'e2e-test-client' } }));
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
ws.on('message', (data) => {
|
|
24
|
-
const msg = JSON.parse(data.toString());
|
|
25
|
-
console.log(' 📥 收到消息:', msg.type);
|
|
26
|
-
|
|
27
|
-
if (msg.type === 'auth_result') {
|
|
28
|
-
console.log(' 完整响应:', JSON.stringify(msg.payload, null, 2));
|
|
29
|
-
if (msg.payload.success) {
|
|
30
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
31
|
-
console.log('2️⃣ 认证成功');
|
|
32
|
-
console.log(' ClientID:', msg.payload.clientId);
|
|
33
|
-
|
|
34
|
-
// Step 3: 发送 outbound 消息 (Bot 回复)
|
|
35
|
-
setTimeout(() => {
|
|
36
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
37
|
-
console.log('3️⃣ 发送 outbound 消息...');
|
|
38
|
-
|
|
39
|
-
const requestId = 'req-' + Date.now() + '-test';
|
|
40
|
-
const testMessage = {
|
|
41
|
-
type: 'outbound',
|
|
42
|
-
requestId: requestId,
|
|
43
|
-
payload: {
|
|
44
|
-
to: 'test-user',
|
|
45
|
-
text: '🎉 这是来自 E2E 测试的消息!时间: ' + new Date().toLocaleString('zh-CN'),
|
|
46
|
-
conversationId: 'conv-test-' + Date.now()
|
|
47
|
-
},
|
|
48
|
-
timestamp: Date.now()
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
console.log(' 发送内容:', JSON.stringify(testMessage, null, 2));
|
|
52
|
-
ws.send(JSON.stringify(testMessage));
|
|
53
|
-
|
|
54
|
-
console.log(' 📤 消息已发送,等待服务器确认 (ack)...');
|
|
55
|
-
}, 500);
|
|
56
|
-
} else {
|
|
57
|
-
console.log(' ❌ 认证失败:', msg.payload.message || msg.payload.error);
|
|
58
|
-
ws.close();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 处理服务器的响应
|
|
63
|
-
if (msg.type === 'ack') {
|
|
64
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
65
|
-
console.log('4️⃣ 收到服务器确认 (ack)');
|
|
66
|
-
console.log(' 确认内容:', JSON.stringify(msg.payload, null, 2));
|
|
67
|
-
if (msg.payload?.ok) {
|
|
68
|
-
console.log(' ✅ 消息发送成功!');
|
|
69
|
-
console.log(' 消息ID:', msg.payload.message?.id);
|
|
70
|
-
} else {
|
|
71
|
-
console.log(' ❌ 消息发送失败');
|
|
72
|
-
}
|
|
73
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
74
|
-
console.log('✅ 测试完成!');
|
|
75
|
-
ws.close();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (msg.type === 'error') {
|
|
79
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
80
|
-
console.log('❌ 服务器错误:', JSON.stringify(msg.payload, null, 2));
|
|
81
|
-
ws.close();
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
ws.on('error', (err) => {
|
|
86
|
-
console.log(' ❌ WebSocket 错误:', err.message);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
ws.on('close', () => {
|
|
90
|
-
console.log('\n📴 连接已关闭');
|
|
91
|
-
process.exit(0);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// 超时处理
|
|
95
|
-
setTimeout(() => {
|
|
96
|
-
console.log('\n⏱️ 测试超时 (10秒)');
|
|
97
|
-
ws.close();
|
|
98
|
-
}, 10000);
|