adp-openclaw 0.0.69 → 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.69",
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
@@ -3,14 +3,28 @@
3
3
 
4
4
  import {
5
5
  type ChannelPlugin,
6
- type ClawdbotConfig,
7
- DEFAULT_ACCOUNT_ID,
6
+ type OpenClawConfig,
8
7
  } from "openclaw/plugin-sdk";
9
- import { adpOpenclawOnboardingAdapter } from "./onboarding.js";
8
+ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/core";
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,33 +49,33 @@ 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
 
64
- function resolveAccount(cfg: ClawdbotConfig, accountId?: string): ResolvedAdpOpenclawAccount {
78
+ function resolveAccount(cfg: OpenClawConfig, accountId?: string): ResolvedAdpOpenclawAccount {
65
79
  const channelCfg = cfg.channels?.["adp-openclaw"] as AdpOpenclawChannelConfig | undefined;
66
80
  const enabled = channelCfg?.enabled !== false;
67
81
  const creds = resolveAdpOpenclawCredentials(channelCfg);
@@ -71,7 +85,7 @@ function resolveAccount(cfg: ClawdbotConfig, 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
  };
@@ -87,7 +101,7 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
87
101
  blurb: "ADP channel backed by a Go WebSocket server.",
88
102
  order: 999,
89
103
  },
90
- onboarding: adpOpenclawOnboardingAdapter,
104
+ setupWizard: adpOpenclawSetupWizard,
91
105
  capabilities: {
92
106
  chatTypes: ["direct"],
93
107
  polls: false,
@@ -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
  },
@@ -128,7 +142,7 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
128
142
  },
129
143
  }),
130
144
  deleteAccount: ({ cfg }) => {
131
- const next = { ...cfg } as ClawdbotConfig;
145
+ const next = { ...cfg } as OpenClawConfig;
132
146
  const nextChannels = { ...cfg.channels };
133
147
  delete (nextChannels as Record<string, unknown>)["adp-openclaw"];
134
148
  if (Object.keys(nextChannels).length > 0) {
@@ -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
  });
package/src/monitor.ts CHANGED
@@ -1,7 +1,7 @@
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, ClawdbotConfig } from "openclaw/plugin-sdk";
4
+ import type { PluginLogger, OpenClawConfig } from "openclaw/plugin-sdk";
5
5
  import { getAdpOpenclawRuntime, setActiveWebSocket } from "./runtime.js";
6
6
  import {
7
7
  getChatHistory,
@@ -40,7 +40,7 @@ export type MonitorParams = {
40
40
  signKey?: string; // HMAC key for signature generation
41
41
  abortSignal?: AbortSignal;
42
42
  log?: PluginLogger;
43
- cfg?: ClawdbotConfig; // OpenClaw config for model settings
43
+ cfg?: OpenClawConfig; // OpenClaw config for model settings
44
44
  setStatus?: (next: Record<string, unknown>) => void; // SDK health-monitor status reporter
45
45
  };
46
46
 
@@ -127,7 +127,7 @@ function generateRequestId(): string {
127
127
  async function markSessionAborted(params: {
128
128
  sessionKey: string;
129
129
  runtime: ReturnType<typeof getAdpOpenclawRuntime>;
130
- cfg?: ClawdbotConfig;
130
+ cfg?: OpenClawConfig;
131
131
  log?: PluginLogger;
132
132
  }): Promise<void> {
133
133
  const { sessionKey, runtime, cfg, log } = params;
@@ -206,7 +206,7 @@ type ConnectParams = {
206
206
  abortSignal?: AbortSignal;
207
207
  log?: PluginLogger;
208
208
  runtime: ReturnType<typeof getAdpOpenclawRuntime>;
209
- cfg?: ClawdbotConfig;
209
+ cfg?: OpenClawConfig;
210
210
  setStatus?: (next: Record<string, unknown>) => void;
211
211
  };
212
212
 
package/src/onboarding.ts CHANGED
@@ -1,10 +1,9 @@
1
- // Onboarding adapter for ADP OpenClaw channel plugin
1
+ // Setup wizard adapter for ADP OpenClaw channel plugin
2
+ // Migrated from ChannelOnboardingAdapter to ChannelSetupWizard for openclaw >= 2026.3.22
2
3
  import type {
3
- ChannelOnboardingAdapter,
4
+ ChannelSetupWizard,
4
5
  OpenClawConfig,
5
- WizardPrompter,
6
6
  } from "openclaw/plugin-sdk";
7
- import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
8
7
 
9
8
  const channel = "adp-openclaw" as const;
10
9
 
@@ -42,81 +41,89 @@ function updateAdpOpenclawConfig(
42
41
  };
43
42
  }
44
43
 
45
- async function noteAdpOpenclawSetup(prompter: WizardPrompter): Promise<void> {
46
- await prompter.note(
47
- [
48
- "ADP OpenClaw connects to a WebSocket server for real-time messaging.",
49
- "You need a clientToken to authenticate with the server.",
50
- "The signKey is used for HMAC signature generation (default: ADPOpenClaw).",
51
- ].join("\n"),
52
- "ADP OpenClaw setup",
53
- );
54
- }
55
-
56
- export const adpOpenclawOnboardingAdapter: ChannelOnboardingAdapter = {
44
+ export const adpOpenclawSetupWizard: ChannelSetupWizard = {
57
45
  channel,
58
- getStatus: async ({ cfg }) => {
59
- const channelCfg = getChannelConfig(cfg);
60
- const configured = isConfigured(channelCfg);
61
46
 
62
- return {
63
- channel,
64
- configured,
65
- statusLines: [`ADP OpenClaw: ${configured ? "configured" : "needs clientToken"}`],
66
- selectionHint: configured ? "configured" : "requires clientToken",
67
- quickstartScore: configured ? 1 : 10,
68
- };
47
+ status: {
48
+ configuredLabel: "configured",
49
+ unconfiguredLabel: "needs clientToken",
50
+ configuredHint: "configured",
51
+ unconfiguredHint: "requires clientToken",
52
+ configuredScore: 1,
53
+ unconfiguredScore: 10,
54
+ resolveConfigured: ({ cfg }) => {
55
+ const channelCfg = getChannelConfig(cfg);
56
+ return isConfigured(channelCfg);
57
+ },
58
+ resolveStatusLines: ({ cfg, configured }) => {
59
+ return [`ADP OpenClaw: ${configured ? "configured" : "needs clientToken"}`];
60
+ },
69
61
  },
70
- configure: async ({ cfg, prompter }) => {
71
- let next = cfg;
72
- const accountId = DEFAULT_ACCOUNT_ID;
73
-
74
- await noteAdpOpenclawSetup(prompter);
75
62
 
76
- const channelCfg = getChannelConfig(next);
77
- const existingClientToken = channelCfg?.clientToken?.trim();
78
- const existingSignKey = channelCfg?.signKey?.trim();
79
-
80
- // Check for env vars
81
- const envClientToken = process.env.ADP_OPENCLAW_CLIENT_TOKEN?.trim();
82
- const envSignKey = process.env.ADP_OPENCLAW_SIGN_KEY?.trim();
63
+ introNote: {
64
+ title: "ADP OpenClaw setup",
65
+ lines: [
66
+ "ADP OpenClaw connects to a WebSocket server for real-time messaging.",
67
+ "You need a clientToken to authenticate with the server.",
68
+ "The signKey is used for HMAC signature generation (default: ADPOpenClaw).",
69
+ ],
70
+ },
83
71
 
84
- if (envClientToken) {
85
- const useEnv = await prompter.confirm({
86
- message: "ADP_OPENCLAW_CLIENT_TOKEN detected in env. Use environment variables?",
87
- initialValue: true,
88
- });
89
- if (useEnv) {
90
- next = updateAdpOpenclawConfig(next, { enabled: true });
91
- return { cfg: next, accountId };
92
- }
93
- }
72
+ envShortcut: {
73
+ prompt: "ADP_OPENCLAW_CLIENT_TOKEN detected in env. Use environment variables?",
74
+ preferredEnvVar: "ADP_OPENCLAW_CLIENT_TOKEN",
75
+ isAvailable: () => Boolean(process.env.ADP_OPENCLAW_CLIENT_TOKEN?.trim()),
76
+ apply: ({ cfg }) => updateAdpOpenclawConfig(cfg, { enabled: true }),
77
+ },
94
78
 
95
- // Prompt for clientToken (required)
96
- const clientTokenInput = await prompter.text({
97
- message: "Client Token",
98
- placeholder: "your-client-token",
99
- initialValue: existingClientToken || undefined,
100
- validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
101
- });
102
- const clientToken = String(clientTokenInput).trim();
79
+ credentials: [
80
+ {
81
+ inputKey: "token",
82
+ providerHint: "ADP OpenClaw",
83
+ credentialLabel: "Client Token",
84
+ preferredEnvVar: "ADP_OPENCLAW_CLIENT_TOKEN",
85
+ envPrompt: "ADP_OPENCLAW_CLIENT_TOKEN detected. Use it?",
86
+ keepPrompt: "Client Token already set. Keep it?",
87
+ inputPrompt: "Client Token",
88
+ inspect: ({ cfg }) => {
89
+ const channelCfg = getChannelConfig(cfg);
90
+ const configuredValue = channelCfg?.clientToken?.trim();
91
+ const envValue = process.env.ADP_OPENCLAW_CLIENT_TOKEN?.trim();
92
+ return {
93
+ accountConfigured: Boolean(configuredValue || envValue),
94
+ hasConfiguredValue: Boolean(configuredValue),
95
+ resolvedValue: configuredValue || envValue,
96
+ envValue,
97
+ };
98
+ },
99
+ applySet: ({ cfg, resolvedValue }) => {
100
+ return updateAdpOpenclawConfig(cfg, { clientToken: resolvedValue, enabled: true });
101
+ },
102
+ applyUseEnv: ({ cfg }) => {
103
+ return updateAdpOpenclawConfig(cfg, { enabled: true });
104
+ },
105
+ },
106
+ ],
103
107
 
104
- // Prompt for signKey (optional, has default)
105
- const signKeyInput = await prompter.text({
108
+ textInputs: [
109
+ {
110
+ inputKey: "name",
106
111
  message: "Sign Key (press Enter for default: ADPOpenClaw)",
107
112
  placeholder: "ADPOpenClaw",
108
- initialValue: existingSignKey || envSignKey || undefined,
109
- });
110
- const signKey = String(signKeyInput ?? "").trim() || undefined;
111
-
112
- next = updateAdpOpenclawConfig(next, {
113
- clientToken,
114
- ...(signKey ? { signKey } : {}),
115
- enabled: true,
116
- });
113
+ required: false,
114
+ currentValue: ({ cfg }) => {
115
+ const channelCfg = getChannelConfig(cfg);
116
+ return channelCfg?.signKey?.trim() || process.env.ADP_OPENCLAW_SIGN_KEY?.trim();
117
+ },
118
+ applySet: ({ cfg, value }) => {
119
+ return updateAdpOpenclawConfig(cfg, {
120
+ ...(value ? { signKey: value } : {}),
121
+ enabled: true,
122
+ });
123
+ },
124
+ },
125
+ ],
117
126
 
118
- return { cfg: next, accountId };
119
- },
120
127
  disable: (cfg) => {
121
128
  return {
122
129
  ...cfg,
@@ -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