adp-openclaw 0.0.4 → 0.0.6
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 +1 -1
- package/server.tar +0 -0
- package/src/channel.ts +16 -16
- package/src/config-schema.ts +1 -1
- package/src/monitor.ts +96 -31
- package/src/runtime.ts +0 -1
package/package.json
CHANGED
package/server.tar
ADDED
|
Binary file
|
package/src/channel.ts
CHANGED
|
@@ -12,7 +12,6 @@ export type AdpOpenclawChannelConfig = {
|
|
|
12
12
|
enabled?: boolean;
|
|
13
13
|
serverUrl?: string;
|
|
14
14
|
wsUrl?: string;
|
|
15
|
-
apiToken?: string;
|
|
16
15
|
clientToken?: string;
|
|
17
16
|
pollIntervalMs?: number;
|
|
18
17
|
};
|
|
@@ -23,13 +22,13 @@ export type ResolvedAdpOpenclawAccount = {
|
|
|
23
22
|
enabled: boolean;
|
|
24
23
|
configured: boolean;
|
|
25
24
|
serverUrl: string;
|
|
26
|
-
|
|
25
|
+
clientToken: string;
|
|
27
26
|
pollIntervalMs: number;
|
|
28
27
|
};
|
|
29
28
|
|
|
30
29
|
function resolveAdpOpenclawCredentials(channelCfg?: AdpOpenclawChannelConfig): {
|
|
31
30
|
serverUrl: string;
|
|
32
|
-
|
|
31
|
+
clientToken: string;
|
|
33
32
|
pollIntervalMs: number;
|
|
34
33
|
} | null {
|
|
35
34
|
// Get serverUrl: support both serverUrl and wsUrl
|
|
@@ -45,23 +44,20 @@ function resolveAdpOpenclawCredentials(channelCfg?: AdpOpenclawChannelConfig): {
|
|
|
45
44
|
serverUrl = process.env.ADP_OPENCLAW_SERVER_URL || "";
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
// Get
|
|
49
|
-
let
|
|
50
|
-
if (!
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
if (!apiToken) {
|
|
54
|
-
apiToken = process.env.ADP_OPENCLAW_API_TOKEN || "";
|
|
47
|
+
// Get clientToken from config or env
|
|
48
|
+
let clientToken = channelCfg?.clientToken?.trim();
|
|
49
|
+
if (!clientToken) {
|
|
50
|
+
clientToken = process.env.ADP_OPENCLAW_CLIENT_TOKEN || "";
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
// Both are required for configured status
|
|
58
|
-
if (!serverUrl || !
|
|
54
|
+
if (!serverUrl || !clientToken) {
|
|
59
55
|
return null;
|
|
60
56
|
}
|
|
61
57
|
|
|
62
58
|
const pollIntervalMs = channelCfg?.pollIntervalMs ?? 1000;
|
|
63
59
|
|
|
64
|
-
return { serverUrl,
|
|
60
|
+
return { serverUrl, clientToken, pollIntervalMs };
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
function resolveAccount(cfg: ClawdbotConfig, accountId?: string): ResolvedAdpOpenclawAccount {
|
|
@@ -75,7 +71,7 @@ function resolveAccount(cfg: ClawdbotConfig, accountId?: string): ResolvedAdpOpe
|
|
|
75
71
|
enabled,
|
|
76
72
|
configured: Boolean(creds),
|
|
77
73
|
serverUrl: creds?.serverUrl || "http://localhost:9876",
|
|
78
|
-
|
|
74
|
+
clientToken: creds?.clientToken || "",
|
|
79
75
|
pollIntervalMs: creds?.pollIntervalMs || 1000,
|
|
80
76
|
};
|
|
81
77
|
}
|
|
@@ -96,6 +92,11 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
|
|
|
96
92
|
reactions: false,
|
|
97
93
|
threads: false,
|
|
98
94
|
media: false,
|
|
95
|
+
/**
|
|
96
|
+
* blockStreaming: true 启用 SDK 的块流式功能
|
|
97
|
+
* SDK 会通过 deliver 回调的 info.kind="block" 传递流式块
|
|
98
|
+
*/
|
|
99
|
+
blockStreaming: true,
|
|
99
100
|
},
|
|
100
101
|
reload: { configPrefixes: ["channels.adp-openclaw"] },
|
|
101
102
|
configSchema: {
|
|
@@ -106,7 +107,6 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
|
|
|
106
107
|
enabled: { type: "boolean" },
|
|
107
108
|
serverUrl: { type: "string" },
|
|
108
109
|
wsUrl: { type: "string" },
|
|
109
|
-
apiToken: { type: "string" },
|
|
110
110
|
clientToken: { type: "string" },
|
|
111
111
|
pollIntervalMs: { type: "integer", minimum: 100 },
|
|
112
112
|
},
|
|
@@ -222,7 +222,7 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
|
|
|
222
222
|
const { monitorAdpOpenclaw } = await import("./monitor.js");
|
|
223
223
|
return monitorAdpOpenclaw({
|
|
224
224
|
serverUrl: account.serverUrl,
|
|
225
|
-
|
|
225
|
+
clientToken: account.clientToken,
|
|
226
226
|
pollIntervalMs: account.pollIntervalMs,
|
|
227
227
|
abortSignal: ctx.abortSignal,
|
|
228
228
|
log: ctx.log,
|
|
@@ -256,7 +256,7 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
|
|
|
256
256
|
method: "POST",
|
|
257
257
|
headers: {
|
|
258
258
|
"Content-Type": "application/json",
|
|
259
|
-
Authorization: `Bearer ${account.
|
|
259
|
+
Authorization: `Bearer ${account.clientToken}`,
|
|
260
260
|
},
|
|
261
261
|
body: JSON.stringify({
|
|
262
262
|
to: target,
|
package/src/config-schema.ts
CHANGED
package/src/monitor.ts
CHANGED
|
@@ -7,7 +7,7 @@ import crypto from "crypto";
|
|
|
7
7
|
|
|
8
8
|
export type MonitorParams = {
|
|
9
9
|
serverUrl: string;
|
|
10
|
-
|
|
10
|
+
clientToken: string;
|
|
11
11
|
pollIntervalMs: number; // Used as reconnect delay
|
|
12
12
|
abortSignal?: AbortSignal;
|
|
13
13
|
log?: PluginLogger;
|
|
@@ -22,6 +22,8 @@ const MsgType = {
|
|
|
22
22
|
Pong: "pong",
|
|
23
23
|
Inbound: "inbound",
|
|
24
24
|
Outbound: "outbound",
|
|
25
|
+
OutboundChunk: "outbound_chunk",
|
|
26
|
+
OutboundEnd: "outbound_end",
|
|
25
27
|
Ack: "ack",
|
|
26
28
|
Error: "error",
|
|
27
29
|
ConvHistory: "conv_history",
|
|
@@ -63,9 +65,10 @@ type AuthResultPayload = {
|
|
|
63
65
|
message?: string;
|
|
64
66
|
};
|
|
65
67
|
|
|
66
|
-
// Generate HMAC-SHA256 signature for
|
|
67
|
-
function generateSignature(token: string, nonce: string): string {
|
|
68
|
-
|
|
68
|
+
// Generate HMAC-SHA256 signature for authentication (includes timestamp for anti-replay)
|
|
69
|
+
function generateSignature(token: string, nonce: string, timestamp: number): string {
|
|
70
|
+
// Use HMAC-SHA256 with token as the key, and "nonce:timestamp" as the message
|
|
71
|
+
return crypto.createHmac("sha256", token).update(`${nonce}:${timestamp}`).digest("hex");
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
// Generate random nonce
|
|
@@ -79,7 +82,7 @@ function generateRequestId(): string {
|
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
82
|
-
const { serverUrl,
|
|
85
|
+
const { serverUrl, clientToken, pollIntervalMs, abortSignal, log, cfg } = params;
|
|
83
86
|
const runtime = getAdpOpenclawRuntime();
|
|
84
87
|
|
|
85
88
|
// Convert HTTP URL to WebSocket URL
|
|
@@ -91,7 +94,7 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
|
91
94
|
try {
|
|
92
95
|
await connectAndHandle({
|
|
93
96
|
wsUrl,
|
|
94
|
-
|
|
97
|
+
clientToken,
|
|
95
98
|
serverUrl,
|
|
96
99
|
abortSignal,
|
|
97
100
|
log,
|
|
@@ -115,7 +118,7 @@ export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
|
115
118
|
|
|
116
119
|
type ConnectParams = {
|
|
117
120
|
wsUrl: string;
|
|
118
|
-
|
|
121
|
+
clientToken: string;
|
|
119
122
|
serverUrl: string;
|
|
120
123
|
abortSignal?: AbortSignal;
|
|
121
124
|
log?: PluginLogger;
|
|
@@ -124,7 +127,7 @@ type ConnectParams = {
|
|
|
124
127
|
};
|
|
125
128
|
|
|
126
129
|
async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
127
|
-
const { wsUrl,
|
|
130
|
+
const { wsUrl, clientToken, serverUrl, abortSignal, log, runtime, cfg } = params;
|
|
128
131
|
|
|
129
132
|
// Dynamic import for WebSocket (works in both Node.js and browser)
|
|
130
133
|
const WebSocket = (await import("ws")).default;
|
|
@@ -144,19 +147,21 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
144
147
|
ws.on("open", () => {
|
|
145
148
|
log?.info(`[adp-openclaw] WebSocket connected, authenticating...`);
|
|
146
149
|
|
|
147
|
-
// Send authentication message with signature
|
|
150
|
+
// Send authentication message with signature (includes timestamp for anti-replay)
|
|
148
151
|
const nonce = generateNonce();
|
|
149
|
-
const
|
|
152
|
+
const timestamp = Date.now();
|
|
153
|
+
const signature = generateSignature(clientToken, nonce, timestamp);
|
|
150
154
|
|
|
151
155
|
const authMsg: WSMessage = {
|
|
152
156
|
type: MsgType.Auth,
|
|
153
157
|
requestId: generateRequestId(),
|
|
154
158
|
payload: {
|
|
155
|
-
token:
|
|
159
|
+
token: clientToken,
|
|
156
160
|
nonce,
|
|
157
161
|
signature,
|
|
162
|
+
timestamp, // Include timestamp in payload for server verification
|
|
158
163
|
},
|
|
159
|
-
timestamp
|
|
164
|
+
timestamp,
|
|
160
165
|
};
|
|
161
166
|
|
|
162
167
|
ws.send(JSON.stringify(authMsg));
|
|
@@ -257,29 +262,89 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
257
262
|
...userMetadata,
|
|
258
263
|
});
|
|
259
264
|
|
|
265
|
+
// Generate unique stream ID for this response
|
|
266
|
+
const streamId = `stream-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
267
|
+
let chunkIndex = 0;
|
|
268
|
+
const displayName = inMsg.user?.username || inMsg.from;
|
|
269
|
+
|
|
260
270
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
261
271
|
ctx,
|
|
262
272
|
cfg: cfg ?? {},
|
|
263
273
|
dispatcherOptions: {
|
|
264
|
-
deliver
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
274
|
+
// Unified deliver callback - handles both streaming blocks and final reply
|
|
275
|
+
// SDK calls this with info.kind = "block" for streaming chunks, "final" for complete response
|
|
276
|
+
deliver: async (payload: { text?: string }, info?: { kind?: string }) => {
|
|
277
|
+
const text = payload.text || "";
|
|
278
|
+
const kind = info?.kind || "final";
|
|
279
|
+
|
|
280
|
+
// Handle streaming block - send chunk via WebSocket
|
|
281
|
+
if (kind === "block" && text) {
|
|
282
|
+
log?.debug?.(`[adp-openclaw] Streaming block[${chunkIndex}] to ${displayName}: ${text.slice(0, 30)}...`);
|
|
283
|
+
|
|
284
|
+
const chunkMsg: WSMessage = {
|
|
285
|
+
type: MsgType.OutboundChunk,
|
|
286
|
+
requestId: generateRequestId(),
|
|
287
|
+
payload: {
|
|
288
|
+
to: inMsg.from,
|
|
289
|
+
chunk: text,
|
|
290
|
+
conversationId: inMsg.conversationId,
|
|
291
|
+
streamId: streamId,
|
|
292
|
+
index: chunkIndex,
|
|
293
|
+
user: inMsg.user,
|
|
294
|
+
},
|
|
295
|
+
timestamp: Date.now(),
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
ws.send(JSON.stringify(chunkMsg));
|
|
299
|
+
chunkIndex++;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Handle tool result - log but don't send separately
|
|
304
|
+
if (kind === "tool") {
|
|
305
|
+
log?.debug?.(`[adp-openclaw] Tool result received for ${displayName}`);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Handle final reply
|
|
310
|
+
if (kind === "final") {
|
|
311
|
+
log?.info(`[adp-openclaw] Sending final reply to ${displayName}: ${text.slice(0, 50)}...`);
|
|
312
|
+
|
|
313
|
+
// If we have streaming chunks, send stream end message
|
|
314
|
+
if (chunkIndex > 0) {
|
|
315
|
+
const endMsg: WSMessage = {
|
|
316
|
+
type: MsgType.OutboundEnd,
|
|
317
|
+
requestId: generateRequestId(),
|
|
318
|
+
payload: {
|
|
319
|
+
to: inMsg.from,
|
|
320
|
+
text: text,
|
|
321
|
+
conversationId: inMsg.conversationId,
|
|
322
|
+
streamId: streamId,
|
|
323
|
+
totalChunks: chunkIndex,
|
|
324
|
+
user: inMsg.user,
|
|
325
|
+
},
|
|
326
|
+
timestamp: Date.now(),
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
ws.send(JSON.stringify(endMsg));
|
|
330
|
+
} else {
|
|
331
|
+
// No streaming chunks were sent, send as regular outbound message
|
|
332
|
+
const outMsg: WSMessage = {
|
|
333
|
+
type: MsgType.Outbound,
|
|
334
|
+
requestId: generateRequestId(),
|
|
335
|
+
payload: {
|
|
336
|
+
to: inMsg.from,
|
|
337
|
+
text: text,
|
|
338
|
+
conversationId: inMsg.conversationId,
|
|
339
|
+
// Include user info in response for tracing
|
|
340
|
+
user: inMsg.user,
|
|
341
|
+
},
|
|
342
|
+
timestamp: Date.now(),
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
ws.send(JSON.stringify(outMsg));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
283
348
|
},
|
|
284
349
|
onError: (err: Error) => {
|
|
285
350
|
log?.error(`[adp-openclaw] Reply error: ${err.message}`);
|