adp-openclaw 0.0.2 → 0.0.4
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/e2e_test.cjs +98 -0
- package/index.ts +8 -7
- package/openclaw.plugin.json +4 -17
- package/package.json +6 -6
- package/src/channel.ts +120 -90
- package/src/config-schema.ts +1 -1
- package/src/monitor.ts +50 -34
- package/src/runtime.ts +27 -8
- package/DESIGN.md +0 -733
- package/README.md +0 -70
- package/USAGE.md +0 -910
- package/WEBSOCKET_PROTOCOL.md +0 -506
- package/server/.claude/settings.local.json +0 -16
- package/server/go.mod +0 -5
- package/server/main.go +0 -786
package/src/monitor.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Monitor: WebSocket connection to Go server for real-time message handling
|
|
2
2
|
// Supports: API Token auth, conversation tracking for multi-turn dialogues
|
|
3
3
|
|
|
4
|
-
import type { PluginLogger } from "openclaw/plugin-sdk";
|
|
5
|
-
import {
|
|
4
|
+
import type { PluginLogger, ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
5
|
+
import { getAdpOpenclawRuntime } from "./runtime.js";
|
|
6
6
|
import crypto from "crypto";
|
|
7
7
|
|
|
8
8
|
export type MonitorParams = {
|
|
@@ -11,6 +11,7 @@ export type MonitorParams = {
|
|
|
11
11
|
pollIntervalMs: number; // Used as reconnect delay
|
|
12
12
|
abortSignal?: AbortSignal;
|
|
13
13
|
log?: PluginLogger;
|
|
14
|
+
cfg?: ClawdbotConfig; // OpenClaw config for model settings
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
// WebSocket message types
|
|
@@ -77,14 +78,14 @@ function generateRequestId(): string {
|
|
|
77
78
|
return `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
export async function
|
|
81
|
-
const { serverUrl, apiToken, pollIntervalMs, abortSignal, log } = params;
|
|
82
|
-
const runtime =
|
|
81
|
+
export async function monitorAdpOpenclaw(params: MonitorParams): Promise<void> {
|
|
82
|
+
const { serverUrl, apiToken, pollIntervalMs, abortSignal, log, cfg } = params;
|
|
83
|
+
const runtime = getAdpOpenclawRuntime();
|
|
83
84
|
|
|
84
85
|
// Convert HTTP URL to WebSocket URL
|
|
85
86
|
const wsUrl = serverUrl.replace(/^http/, "ws") + "/ws";
|
|
86
87
|
|
|
87
|
-
log?.info(`[
|
|
88
|
+
log?.info(`[adp-openclaw] WebSocket monitor started, connecting to ${wsUrl}`);
|
|
88
89
|
|
|
89
90
|
while (!abortSignal?.aborted) {
|
|
90
91
|
try {
|
|
@@ -95,20 +96,21 @@ export async function monitorSimpleGo(params: MonitorParams): Promise<void> {
|
|
|
95
96
|
abortSignal,
|
|
96
97
|
log,
|
|
97
98
|
runtime,
|
|
99
|
+
cfg,
|
|
98
100
|
});
|
|
99
101
|
} catch (err) {
|
|
100
102
|
if (abortSignal?.aborted) break;
|
|
101
|
-
log?.error(`[
|
|
103
|
+
log?.error(`[adp-openclaw] WebSocket error: ${err}`);
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
// Wait before reconnecting
|
|
105
107
|
if (!abortSignal?.aborted) {
|
|
106
|
-
log?.info(`[
|
|
108
|
+
log?.info(`[adp-openclaw] Reconnecting in ${pollIntervalMs}ms...`);
|
|
107
109
|
await sleep(pollIntervalMs, abortSignal);
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
log?.info(`[
|
|
113
|
+
log?.info(`[adp-openclaw] WebSocket monitor stopped`);
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
type ConnectParams = {
|
|
@@ -117,11 +119,12 @@ type ConnectParams = {
|
|
|
117
119
|
serverUrl: string;
|
|
118
120
|
abortSignal?: AbortSignal;
|
|
119
121
|
log?: PluginLogger;
|
|
120
|
-
runtime: ReturnType<typeof
|
|
122
|
+
runtime: ReturnType<typeof getAdpOpenclawRuntime>;
|
|
123
|
+
cfg?: ClawdbotConfig;
|
|
121
124
|
};
|
|
122
125
|
|
|
123
126
|
async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
124
|
-
const { wsUrl, apiToken, serverUrl, abortSignal, log, runtime } = params;
|
|
127
|
+
const { wsUrl, apiToken, serverUrl, abortSignal, log, runtime, cfg } = params;
|
|
125
128
|
|
|
126
129
|
// Dynamic import for WebSocket (works in both Node.js and browser)
|
|
127
130
|
const WebSocket = (await import("ws")).default;
|
|
@@ -139,7 +142,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
139
142
|
abortSignal?.addEventListener("abort", abortHandler);
|
|
140
143
|
|
|
141
144
|
ws.on("open", () => {
|
|
142
|
-
log?.info(`[
|
|
145
|
+
log?.info(`[adp-openclaw] WebSocket connected, authenticating...`);
|
|
143
146
|
|
|
144
147
|
// Send authentication message with signature
|
|
145
148
|
const nonce = generateNonce();
|
|
@@ -168,7 +171,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
168
171
|
const result = msg.payload as AuthResultPayload;
|
|
169
172
|
if (result.success) {
|
|
170
173
|
authenticated = true;
|
|
171
|
-
log?.info(`[
|
|
174
|
+
log?.info(`[adp-openclaw] Authenticated as client ${result.clientId}`);
|
|
172
175
|
|
|
173
176
|
// Start ping interval
|
|
174
177
|
pingInterval = setInterval(() => {
|
|
@@ -181,7 +184,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
181
184
|
}
|
|
182
185
|
}, 25000);
|
|
183
186
|
} else {
|
|
184
|
-
log?.error(`[
|
|
187
|
+
log?.error(`[adp-openclaw] Authentication failed: ${result.message}`);
|
|
185
188
|
ws.close();
|
|
186
189
|
}
|
|
187
190
|
break;
|
|
@@ -195,7 +198,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
195
198
|
if (!authenticated) break;
|
|
196
199
|
|
|
197
200
|
const inMsg = msg.payload as InboundMessage;
|
|
198
|
-
log?.info(`[
|
|
201
|
+
log?.info(`[adp-openclaw] Received: ${inMsg.from}: ${inMsg.text} (conv=${inMsg.conversationId}, user=${JSON.stringify(inMsg.user || {})})`);
|
|
199
202
|
|
|
200
203
|
// Process the message with full user identity
|
|
201
204
|
try {
|
|
@@ -218,36 +221,49 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
218
221
|
}
|
|
219
222
|
}
|
|
220
223
|
|
|
224
|
+
// Use resolveAgentRoute to get proper sessionKey (like QQBot does)
|
|
225
|
+
// This ensures session history is correctly associated
|
|
226
|
+
const peerId = inMsg.conversationId;
|
|
227
|
+
const route = runtime.channel.routing.resolveAgentRoute({
|
|
228
|
+
cfg: cfg ?? {},
|
|
229
|
+
channel: "adp-openclaw",
|
|
230
|
+
accountId: "default",
|
|
231
|
+
peer: {
|
|
232
|
+
kind: "dm", // direct message
|
|
233
|
+
id: peerId,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
221
237
|
const ctx = runtime.channel.reply.finalizeInboundContext({
|
|
222
238
|
Body: inMsg.text,
|
|
223
239
|
RawBody: inMsg.text,
|
|
224
240
|
CommandBody: inMsg.text,
|
|
225
|
-
// User identity: format as "
|
|
226
|
-
From: `
|
|
227
|
-
To: `
|
|
228
|
-
// SessionKey
|
|
229
|
-
SessionKey:
|
|
230
|
-
AccountId:
|
|
241
|
+
// User identity: format as "adp-openclaw:{tenantId}:{userId}" for multi-tenant support
|
|
242
|
+
From: `adp-openclaw:${tenantPrefix}${userIdentifier}`,
|
|
243
|
+
To: `adp-openclaw:bot`,
|
|
244
|
+
// SessionKey from resolveAgentRoute for proper session history tracking
|
|
245
|
+
SessionKey: route.sessionKey,
|
|
246
|
+
AccountId: route.accountId,
|
|
231
247
|
ChatType: "direct",
|
|
232
248
|
// SenderId carries the raw user ID for identification
|
|
233
249
|
SenderId: userIdentifier,
|
|
234
|
-
Provider: "
|
|
235
|
-
Surface: inMsg.user?.source || "
|
|
250
|
+
Provider: "adp-openclaw",
|
|
251
|
+
Surface: inMsg.user?.source || "adp-openclaw",
|
|
236
252
|
MessageSid: inMsg.id,
|
|
237
253
|
MessageSidFull: inMsg.id,
|
|
238
|
-
OriginatingChannel: "
|
|
239
|
-
OriginatingTo: "
|
|
254
|
+
OriginatingChannel: "adp-openclaw",
|
|
255
|
+
OriginatingTo: "adp-openclaw:bot",
|
|
240
256
|
// Pass user metadata through context (like Feishu does)
|
|
241
257
|
...userMetadata,
|
|
242
258
|
});
|
|
243
259
|
|
|
244
260
|
await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
245
261
|
ctx,
|
|
246
|
-
cfg: {},
|
|
262
|
+
cfg: cfg ?? {},
|
|
247
263
|
dispatcherOptions: {
|
|
248
264
|
deliver: async (payload: { text: string }) => {
|
|
249
265
|
const displayName = inMsg.user?.username || inMsg.from;
|
|
250
|
-
log?.info(`[
|
|
266
|
+
log?.info(`[adp-openclaw] Sending reply to ${displayName}: ${payload.text.slice(0, 50)}...`);
|
|
251
267
|
|
|
252
268
|
// Send via WebSocket
|
|
253
269
|
const outMsg: WSMessage = {
|
|
@@ -266,39 +282,39 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
|
|
|
266
282
|
ws.send(JSON.stringify(outMsg));
|
|
267
283
|
},
|
|
268
284
|
onError: (err: Error) => {
|
|
269
|
-
log?.error(`[
|
|
285
|
+
log?.error(`[adp-openclaw] Reply error: ${err.message}`);
|
|
270
286
|
},
|
|
271
287
|
},
|
|
272
288
|
});
|
|
273
289
|
} catch (err) {
|
|
274
|
-
log?.error(`[
|
|
290
|
+
log?.error(`[adp-openclaw] Failed to process message: ${err}`);
|
|
275
291
|
}
|
|
276
292
|
break;
|
|
277
293
|
}
|
|
278
294
|
|
|
279
295
|
case MsgType.Ack:
|
|
280
296
|
// Message acknowledgment
|
|
281
|
-
log?.debug?.(`[
|
|
297
|
+
log?.debug?.(`[adp-openclaw] Message acknowledged: ${msg.requestId}`);
|
|
282
298
|
break;
|
|
283
299
|
|
|
284
300
|
case MsgType.Error: {
|
|
285
301
|
const error = msg.payload as { error: string; message: string };
|
|
286
|
-
log?.error(`[
|
|
302
|
+
log?.error(`[adp-openclaw] Server error: ${error.error} - ${error.message}`);
|
|
287
303
|
break;
|
|
288
304
|
}
|
|
289
305
|
|
|
290
306
|
default:
|
|
291
|
-
log?.warn(`[
|
|
307
|
+
log?.warn(`[adp-openclaw] Unknown message type: ${msg.type}`);
|
|
292
308
|
}
|
|
293
309
|
} catch (err) {
|
|
294
|
-
log?.error(`[
|
|
310
|
+
log?.error(`[adp-openclaw] Failed to parse message: ${err}`);
|
|
295
311
|
}
|
|
296
312
|
});
|
|
297
313
|
|
|
298
314
|
ws.on("close", (code, reason) => {
|
|
299
315
|
if (pingInterval) clearInterval(pingInterval);
|
|
300
316
|
abortSignal?.removeEventListener("abort", abortHandler);
|
|
301
|
-
log?.info(`[
|
|
317
|
+
log?.info(`[adp-openclaw] WebSocket closed: ${code} ${reason.toString()}`);
|
|
302
318
|
resolve();
|
|
303
319
|
});
|
|
304
320
|
|
package/src/runtime.ts
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
|
-
// Runtime singleton for
|
|
1
|
+
// Runtime singleton for adp-openclaw plugin
|
|
2
2
|
import type { PluginRuntime } from "openclaw/plugin-sdk";
|
|
3
3
|
|
|
4
|
-
let
|
|
4
|
+
let adpOpenclawRuntime: PluginRuntime | null = null;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Plugin-level config storage (from plugins.entries.adp-openclaw.config)
|
|
7
|
+
export type PluginConfig = {
|
|
8
|
+
wsUrl?: string;
|
|
9
|
+
clientToken?: string;
|
|
10
|
+
serverUrl?: string;
|
|
11
|
+
apiToken?: string;
|
|
12
|
+
pollIntervalMs?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let pluginConfig: PluginConfig = {};
|
|
16
|
+
|
|
17
|
+
export function setAdpOpenclawRuntime(runtime: PluginRuntime): void {
|
|
18
|
+
adpOpenclawRuntime = runtime;
|
|
8
19
|
}
|
|
9
20
|
|
|
10
|
-
export function
|
|
11
|
-
if (!
|
|
12
|
-
throw new Error("
|
|
21
|
+
export function getAdpOpenclawRuntime(): PluginRuntime {
|
|
22
|
+
if (!adpOpenclawRuntime) {
|
|
23
|
+
throw new Error("ADP OpenClaw runtime not initialized");
|
|
13
24
|
}
|
|
14
|
-
return
|
|
25
|
+
return adpOpenclawRuntime;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function setPluginConfig(config: PluginConfig): void {
|
|
29
|
+
pluginConfig = config;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getPluginConfig(): PluginConfig {
|
|
33
|
+
return pluginConfig;
|
|
15
34
|
}
|