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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adp-openclaw",
3
- "version": "0.0.70",
3
+ "version": "0.0.71",
4
4
  "description": "ADP-OpenClaw demo channel plugin (Go WebSocket backend)",
5
5
  "type": "module",
6
6
  "dependencies": {
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
- // Default WebSocket URL for ADP OpenClaw
13
- const DEFAULT_WS_URL = "wss://wss.lke.cloud.tencent.com/bot/gateway/conn";
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 || DEFAULT_WS_URL,
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, default: wss://wss.lke.cloud.tencent.com/bot/gateway/conn)
124
+ wsUrl: { type: "string" }, // WebSocket URL (optional, auto-detected from clientToken prefix)
111
125
  clientToken: { type: "string" },
112
126
  signKey: { type: "string" },
113
127
  },
@@ -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, default: wss://wss.lke.cloud.tencent.com/bot/gateway/conn)
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
  });
@@ -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
- return content;
200
- }
201
- if (Array.isArray(content)) {
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 String(content);
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
- role: msg.role as "user" | "assistant" | "system",
872
- content: typeof msg.content === "string"
873
- ? msg.content
874
- : msg.content
875
- .filter((p) => p.type === "text" && p.text)
876
- .map((p) => p.text)
877
- .join("\n"),
878
- id: msg.id,
879
- timestamp: msg.timestamp ? new Date(msg.timestamp).getTime() : undefined,
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