adp-openclaw 0.0.70 → 0.0.71
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/src/channel.ts +29 -15
- package/src/config-schema.ts +1 -1
- package/src/session-history.ts +72 -18
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -9,8 +9,22 @@ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/core";
|
|
|
9
9
|
import { adpOpenclawSetupWizard } from "./onboarding.js";
|
|
10
10
|
import { getActiveWebSocket } from "./runtime.js";
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
const
|
|
12
|
+
// WebSocket URLs for ADP OpenClaw (domestic vs international)
|
|
13
|
+
const WS_URL_DOMESTIC = "wss://wss.lke.cloud.tencent.com/bot/gateway/conn";
|
|
14
|
+
const WS_URL_INTL = "wss://wss.lke.tencentcloud.com/bot/gateway/conn";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve WebSocket URL based on clientToken prefix.
|
|
18
|
+
* - Domestic token: sk-adp-{part1}-{part2} → WS_URL_DOMESTIC
|
|
19
|
+
* - International token: sk-adp_intl-{part1}-{part2} → WS_URL_INTL
|
|
20
|
+
* - Fallback: domestic URL
|
|
21
|
+
*/
|
|
22
|
+
function resolveWsUrlFromToken(clientToken?: string): string {
|
|
23
|
+
if (clientToken?.startsWith("sk-adp_intl-")) {
|
|
24
|
+
return WS_URL_INTL;
|
|
25
|
+
}
|
|
26
|
+
return WS_URL_DOMESTIC;
|
|
27
|
+
}
|
|
14
28
|
|
|
15
29
|
// Channel-level config type (from channels["adp-openclaw"])
|
|
16
30
|
export type AdpOpenclawChannelConfig = {
|
|
@@ -35,29 +49,29 @@ function resolveAdpOpenclawCredentials(channelCfg?: AdpOpenclawChannelConfig): {
|
|
|
35
49
|
clientToken: string;
|
|
36
50
|
signKey: string;
|
|
37
51
|
} | null {
|
|
38
|
-
// Get wsUrl from config or env (has default value)
|
|
39
|
-
let wsUrl = channelCfg?.wsUrl?.trim();
|
|
40
|
-
if (!wsUrl) {
|
|
41
|
-
wsUrl = process.env.ADP_OPENCLAW_WS_URL || DEFAULT_WS_URL;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
52
|
// Get clientToken from config or env
|
|
45
53
|
let clientToken = channelCfg?.clientToken?.trim();
|
|
46
54
|
if (!clientToken) {
|
|
47
55
|
clientToken = process.env.ADP_OPENCLAW_CLIENT_TOKEN || "";
|
|
48
56
|
}
|
|
49
57
|
|
|
58
|
+
// clientToken is required for configured status
|
|
59
|
+
if (!clientToken) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Get wsUrl: explicit config > env var > auto-detect from clientToken prefix
|
|
64
|
+
let wsUrl = channelCfg?.wsUrl?.trim();
|
|
65
|
+
if (!wsUrl) {
|
|
66
|
+
wsUrl = process.env.ADP_OPENCLAW_WS_URL || resolveWsUrlFromToken(clientToken);
|
|
67
|
+
}
|
|
68
|
+
|
|
50
69
|
// Get signKey from config or env (default: ADPOpenClaw)
|
|
51
70
|
let signKey = channelCfg?.signKey?.trim();
|
|
52
71
|
if (!signKey) {
|
|
53
72
|
signKey = process.env.ADP_OPENCLAW_SIGN_KEY || "ADPOpenClaw";
|
|
54
73
|
}
|
|
55
74
|
|
|
56
|
-
// clientToken is required for configured status (wsUrl has default)
|
|
57
|
-
if (!clientToken) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
75
|
return { wsUrl, clientToken, signKey };
|
|
62
76
|
}
|
|
63
77
|
|
|
@@ -71,7 +85,7 @@ function resolveAccount(cfg: OpenClawConfig, accountId?: string): ResolvedAdpOpe
|
|
|
71
85
|
name: "ADP OpenClaw",
|
|
72
86
|
enabled,
|
|
73
87
|
configured: Boolean(creds),
|
|
74
|
-
wsUrl: creds?.wsUrl ||
|
|
88
|
+
wsUrl: creds?.wsUrl || resolveWsUrlFromToken(creds?.clientToken),
|
|
75
89
|
clientToken: creds?.clientToken || "",
|
|
76
90
|
signKey: creds?.signKey || "ADPOpenClaw",
|
|
77
91
|
};
|
|
@@ -107,7 +121,7 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
|
|
|
107
121
|
additionalProperties: false,
|
|
108
122
|
properties: {
|
|
109
123
|
enabled: { type: "boolean" },
|
|
110
|
-
wsUrl: { type: "string" }, // WebSocket URL (optional,
|
|
124
|
+
wsUrl: { type: "string" }, // WebSocket URL (optional, auto-detected from clientToken prefix)
|
|
111
125
|
clientToken: { type: "string" },
|
|
112
126
|
signKey: { type: "string" },
|
|
113
127
|
},
|
package/src/config-schema.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const AdpOpenclawConfigSchema = z.object({
|
|
4
4
|
enabled: z.boolean().optional(),
|
|
5
|
-
wsUrl: z.string().optional(), // WebSocket URL (optional,
|
|
5
|
+
wsUrl: z.string().optional(), // WebSocket URL (optional, auto-detected from clientToken prefix: sk-adp- → domestic, sk-adp_intl- → international)
|
|
6
6
|
clientToken: z.string().optional(),
|
|
7
7
|
signKey: z.string().optional(),
|
|
8
8
|
});
|
package/src/session-history.ts
CHANGED
|
@@ -191,20 +191,46 @@ function resolveSessionTranscriptCandidates(
|
|
|
191
191
|
return candidates;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Sanitize string to ensure it's valid for JSON serialization.
|
|
196
|
+
* Removes or replaces characters that could cause JSON parsing issues.
|
|
197
|
+
*/
|
|
198
|
+
function sanitizeForJson(str: string): string {
|
|
199
|
+
if (!str) return str;
|
|
200
|
+
|
|
201
|
+
let result = str;
|
|
202
|
+
|
|
203
|
+
// Remove EXTERNAL_UNTRUSTED_CONTENT blocks which often contain binary garbage (e.g., images as text)
|
|
204
|
+
// These blocks are from web_fetch and contain external webpage content that may have encoding issues
|
|
205
|
+
result = result.replace(
|
|
206
|
+
/<<<EXTERNAL_UNTRUSTED_CONTENT>>>[\s\S]*?<<<\/EXTERNAL_UNTRUSTED_CONTENT>>>/g,
|
|
207
|
+
"[External content removed]"
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Remove null characters and other control characters (except common whitespace)
|
|
211
|
+
// Control characters: 0x00-0x1F (except 0x09 tab, 0x0A newline, 0x0D carriage return)
|
|
212
|
+
// Also remove 0x7F (DEL) and other problematic chars
|
|
213
|
+
result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
194
218
|
/**
|
|
195
219
|
* Extract text content from message content (handles both string and array formats)
|
|
196
220
|
*/
|
|
197
221
|
function extractMessageContent(content: string | Array<{ type: string; text?: string }>): string {
|
|
222
|
+
let text: string;
|
|
198
223
|
if (typeof content === "string") {
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return content
|
|
224
|
+
text = content;
|
|
225
|
+
} else if (Array.isArray(content)) {
|
|
226
|
+
text = content
|
|
203
227
|
.filter((part) => part.type === "text" && part.text)
|
|
204
228
|
.map((part) => part.text)
|
|
205
229
|
.join("\n");
|
|
230
|
+
} else {
|
|
231
|
+
text = String(content);
|
|
206
232
|
}
|
|
207
|
-
return
|
|
233
|
+
return sanitizeForJson(text);
|
|
208
234
|
}
|
|
209
235
|
|
|
210
236
|
/**
|
|
@@ -857,27 +883,55 @@ export async function getOpenClawChatHistoryViaCli(
|
|
|
857
883
|
);
|
|
858
884
|
|
|
859
885
|
// Parse JSON result
|
|
886
|
+
// OpenClaw CLI returns messages with content that can be:
|
|
887
|
+
// - string: plain text content
|
|
888
|
+
// - array: mixed content parts including text, toolCall, etc.
|
|
889
|
+
// Timestamp can be either number (ms) or ISO string
|
|
860
890
|
const parsed = JSON.parse(result) as {
|
|
861
891
|
messages?: Array<{
|
|
862
892
|
role: string;
|
|
863
|
-
content: string | Array<{ type: string; text?: string }>;
|
|
893
|
+
content: string | Array<{ type: string; text?: string; [key: string]: unknown }>;
|
|
864
894
|
id?: string;
|
|
865
|
-
timestamp?: string;
|
|
895
|
+
timestamp?: string | number;
|
|
866
896
|
}>;
|
|
867
897
|
sessionId?: string; // OpenClaw may return the current sessionId
|
|
868
898
|
};
|
|
869
899
|
|
|
870
|
-
let messages: OpenClawMessage[] = (parsed.messages ?? []).map((msg) =>
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
900
|
+
let messages: OpenClawMessage[] = (parsed.messages ?? []).map((msg) => {
|
|
901
|
+
// Extract text content from message
|
|
902
|
+
let textContent: string;
|
|
903
|
+
if (typeof msg.content === "string") {
|
|
904
|
+
textContent = msg.content;
|
|
905
|
+
} else if (Array.isArray(msg.content)) {
|
|
906
|
+
// Filter for text parts only (skip toolCall, toolResult, etc.)
|
|
907
|
+
textContent = msg.content
|
|
908
|
+
.filter((p): p is { type: string; text: string } =>
|
|
909
|
+
p.type === "text" && typeof p.text === "string"
|
|
910
|
+
)
|
|
911
|
+
.map((p) => p.text)
|
|
912
|
+
.join("\n");
|
|
913
|
+
} else {
|
|
914
|
+
textContent = String(msg.content ?? "");
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Sanitize content to remove invalid characters that break JSON serialization
|
|
918
|
+
textContent = sanitizeForJson(textContent);
|
|
919
|
+
|
|
920
|
+
// Handle timestamp: can be number (ms) or ISO string
|
|
921
|
+
let timestamp: number | undefined;
|
|
922
|
+
if (typeof msg.timestamp === "number") {
|
|
923
|
+
timestamp = msg.timestamp;
|
|
924
|
+
} else if (typeof msg.timestamp === "string") {
|
|
925
|
+
timestamp = new Date(msg.timestamp).getTime();
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return {
|
|
929
|
+
role: msg.role as "user" | "assistant" | "system",
|
|
930
|
+
content: textContent,
|
|
931
|
+
id: msg.id,
|
|
932
|
+
timestamp,
|
|
933
|
+
};
|
|
934
|
+
});
|
|
881
935
|
|
|
882
936
|
log?.info?.(`[session-history] Got ${messages.length} messages via CLI`);
|
|
883
937
|
|