chrome-openclaw-sider 1.0.1 → 1.0.2
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/account.ts +4 -0
- package/src/auth.ts +8 -17
- package/src/channel.ts +4 -0
- package/src/config.ts +15 -0
- package/src/remote-browser-support.ts +7 -3
- package/src/setup-core.ts +110 -13
package/package.json
CHANGED
package/src/account.ts
CHANGED
|
@@ -191,6 +191,7 @@ export const siderSetupWizard: ChannelSetupWizard = {
|
|
|
191
191
|
|
|
192
192
|
const progress = prompter.progress("Waiting for connection...");
|
|
193
193
|
const reportPendingUpdate = createSiderPairingPendingUpdateReporter({
|
|
194
|
+
pairingCode: pairing.pairingCode,
|
|
194
195
|
report: (message) => {
|
|
195
196
|
progress.update(message);
|
|
196
197
|
},
|
|
@@ -199,6 +200,9 @@ export const siderSetupWizard: ChannelSetupWizard = {
|
|
|
199
200
|
const paired = await waitForSiderPairing({
|
|
200
201
|
pairing,
|
|
201
202
|
onPending: reportPendingUpdate,
|
|
203
|
+
onRetryableError: (message) => {
|
|
204
|
+
progress.update(message);
|
|
205
|
+
},
|
|
202
206
|
});
|
|
203
207
|
progress.stop("Connected.");
|
|
204
208
|
|
package/src/auth.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { type OpenClawConfig, type PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
3
3
|
import { type OpenClawPluginService } from "openclaw/plugin-sdk/plugin-entry";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
SIDER_CHANNEL_ID,
|
|
6
|
+
SIDER_DEFAULT_BASE_URL,
|
|
7
|
+
readDefaultSiderSetupTokenEnv,
|
|
8
|
+
readSiderBaseUrlEnv,
|
|
9
|
+
} from "./config.js";
|
|
5
10
|
import { SIDER_USER_AGENT } from "./user-agent.js";
|
|
6
11
|
|
|
7
12
|
export const SIDER_AUTH_EXCHANGE_API_PATH = "/v1/claws/register";
|
|
8
|
-
export const SIDER_SETUP_TOKEN_ENV = "SIDER_SETUP_TOKEN";
|
|
9
|
-
export const SIDER_BASE_URL_ENV = "SIDER_BASE_URL";
|
|
10
|
-
|
|
11
13
|
export type SiderSetupConfigSnapshot = {
|
|
12
14
|
enabled?: boolean;
|
|
13
15
|
setupToken?: string;
|
|
@@ -51,12 +53,8 @@ function normalizeGatewayUrl(raw?: string): string | undefined {
|
|
|
51
53
|
return trimmed ? trimmed.replace(/\/+$/, "") : undefined;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
function normalizeAccountEnvSuffix(accountId: string): string {
|
|
55
|
-
return accountId.trim().replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
56
|
export function resolveSiderBaseUrl(): string {
|
|
59
|
-
return normalizeGatewayUrl(
|
|
57
|
+
return normalizeGatewayUrl(readSiderBaseUrlEnv()) ?? SIDER_DEFAULT_BASE_URL;
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
function getPendingOperation(accountId: string): Promise<OpenClawConfig> | undefined {
|
|
@@ -232,15 +230,8 @@ export function resolveSiderSetupToken(
|
|
|
232
230
|
if (configuredToken) {
|
|
233
231
|
return configuredToken;
|
|
234
232
|
}
|
|
235
|
-
const accountSuffix = normalizeAccountEnvSuffix(accountId);
|
|
236
|
-
if (accountSuffix) {
|
|
237
|
-
const namedToken = trimMaybe(process.env[`${SIDER_SETUP_TOKEN_ENV}_${accountSuffix}`]);
|
|
238
|
-
if (namedToken) {
|
|
239
|
-
return namedToken;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
233
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
243
|
-
return trimMaybe(
|
|
234
|
+
return trimMaybe(readDefaultSiderSetupTokenEnv());
|
|
244
235
|
}
|
|
245
236
|
return undefined;
|
|
246
237
|
}
|
package/src/channel.ts
CHANGED
|
@@ -3817,6 +3817,7 @@ export const siderPlugin: ChannelPlugin<ResolvedSiderAccount> = {
|
|
|
3817
3817
|
}),
|
|
3818
3818
|
);
|
|
3819
3819
|
const reportPendingUpdate = createSiderPairingPendingUpdateReporter({
|
|
3820
|
+
pairingCode: pairing.pairingCode,
|
|
3820
3821
|
report: (message) => {
|
|
3821
3822
|
runtime.log(message);
|
|
3822
3823
|
},
|
|
@@ -3825,6 +3826,9 @@ export const siderPlugin: ChannelPlugin<ResolvedSiderAccount> = {
|
|
|
3825
3826
|
const paired = await waitForSiderPairing({
|
|
3826
3827
|
pairing,
|
|
3827
3828
|
onPending: reportPendingUpdate,
|
|
3829
|
+
onRetryableError: (message) => {
|
|
3830
|
+
runtime.log(message);
|
|
3831
|
+
},
|
|
3828
3832
|
});
|
|
3829
3833
|
const latestCfg = loadConfig();
|
|
3830
3834
|
const nextCfg = applySiderSetupAccountConfig({
|
package/src/config.ts
CHANGED
|
@@ -12,3 +12,18 @@ export const SIDER_CHANNEL_BLURB = "Command your OpenClaw from Chrome Sidebar di
|
|
|
12
12
|
export const SIDER_CHANNEL_ALIASES = ["sider"] as const;
|
|
13
13
|
|
|
14
14
|
export const SIDER_DEFAULT_BASE_URL = "https://sider.ai/api/claw/self";
|
|
15
|
+
export const SIDER_SETUP_TOKEN_ENV = "SIDER_SETUP_TOKEN";
|
|
16
|
+
export const SIDER_BASE_URL_ENV = "SIDER_BASE_URL";
|
|
17
|
+
export const SIDER_REMOTE_BROWSER_ENABLE_ENV = "SIDER_ENABLE_REMOTE_BROWSER_MCP";
|
|
18
|
+
|
|
19
|
+
export function readSiderBaseUrlEnv(): string | undefined {
|
|
20
|
+
return process.env.SIDER_BASE_URL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function readDefaultSiderSetupTokenEnv(): string | undefined {
|
|
24
|
+
return process.env.SIDER_SETUP_TOKEN;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function readSiderRemoteBrowserEnableEnv(): string | undefined {
|
|
28
|
+
return process.env.SIDER_ENABLE_REMOTE_BROWSER_MCP;
|
|
29
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
SIDER_CHANNEL_ID,
|
|
4
|
+
SIDER_REMOTE_BROWSER_ENABLE_ENV,
|
|
5
|
+
readSiderRemoteBrowserEnableEnv,
|
|
6
|
+
} from "./config.js";
|
|
3
7
|
|
|
4
|
-
export
|
|
8
|
+
export { SIDER_REMOTE_BROWSER_ENABLE_ENV } from "./config.js";
|
|
5
9
|
|
|
6
10
|
const SIDER_REMOTE_BROWSER_PREPEND_CONTEXT = [
|
|
7
11
|
"Sider remote-browser mode is enabled.",
|
|
@@ -31,7 +35,7 @@ function isTruthyEnv(value: string | undefined): boolean {
|
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
function isRemoteBrowserSupportEnabled(): boolean {
|
|
34
|
-
return isTruthyEnv(
|
|
38
|
+
return isTruthyEnv(readSiderRemoteBrowserEnableEnv());
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
function buildRemoteBrowserPromptInjection(): RemoteBrowserPromptInjection {
|
package/src/setup-core.ts
CHANGED
|
@@ -60,6 +60,13 @@ export class SiderPairingExpiredError extends Error {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export class SiderPairingTransientError extends Error {
|
|
64
|
+
constructor(message: string) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = "SiderPairingTransientError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
function trimMaybe(value: unknown): string | undefined {
|
|
64
71
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
65
72
|
}
|
|
@@ -134,6 +141,43 @@ function sleep(ms: number): Promise<void> {
|
|
|
134
141
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
function describeUnknownError(error: unknown): string {
|
|
145
|
+
if (error instanceof Error && error.message) {
|
|
146
|
+
return error.message;
|
|
147
|
+
}
|
|
148
|
+
return String(error);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isRetryableFetchError(error: unknown): boolean {
|
|
152
|
+
if (!(error instanceof Error)) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
if (error.name === "TypeError" && /fetch failed/i.test(error.message)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const cause = "cause" in error ? error.cause : undefined;
|
|
160
|
+
const causeCode =
|
|
161
|
+
cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string"
|
|
162
|
+
? cause.code
|
|
163
|
+
: undefined;
|
|
164
|
+
return Boolean(
|
|
165
|
+
causeCode &&
|
|
166
|
+
[
|
|
167
|
+
"ECONNRESET",
|
|
168
|
+
"ECONNREFUSED",
|
|
169
|
+
"EHOSTUNREACH",
|
|
170
|
+
"ENETUNREACH",
|
|
171
|
+
"ENOTFOUND",
|
|
172
|
+
"ETIMEDOUT",
|
|
173
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
174
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
175
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
176
|
+
"UND_ERR_SOCKET",
|
|
177
|
+
].includes(causeCode),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
137
181
|
function supportsTerminalFormatting(): boolean {
|
|
138
182
|
return Boolean(process.stdout.isTTY);
|
|
139
183
|
}
|
|
@@ -170,11 +214,24 @@ export function formatSiderPairingTtl(remainingMs: number): string {
|
|
|
170
214
|
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
171
215
|
}
|
|
172
216
|
|
|
173
|
-
export function formatSiderPairingPendingMessage(
|
|
174
|
-
|
|
217
|
+
export function formatSiderPairingPendingMessage(params: {
|
|
218
|
+
pairingCode: string;
|
|
219
|
+
remainingMs: number;
|
|
220
|
+
}): string {
|
|
221
|
+
return `Waiting for connection... Pairing code: ${formatHighlightedPairingCode(params.pairingCode)}. Pairing code expires in ${formatSiderPairingTtl(params.remainingMs)}.`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function formatSiderPairingRetryMessage(params: {
|
|
225
|
+
pairingCode: string;
|
|
226
|
+
remainingMs: number;
|
|
227
|
+
retryAfterMs: number;
|
|
228
|
+
detail: string;
|
|
229
|
+
}): string {
|
|
230
|
+
return `Temporary connection issue while checking pairing status (${params.detail}). Retrying in ${formatSiderPairingTtl(params.retryAfterMs)}. Pairing code: ${formatHighlightedPairingCode(params.pairingCode)}. Pairing code expires in ${formatSiderPairingTtl(params.remainingMs)}.`;
|
|
175
231
|
}
|
|
176
232
|
|
|
177
233
|
export function createSiderPairingPendingUpdateReporter(params: {
|
|
234
|
+
pairingCode: string;
|
|
178
235
|
report: (message: string) => void | Promise<void>;
|
|
179
236
|
intervalMs?: number;
|
|
180
237
|
}): (state: { remainingMs: number; pollIntervalMs: number }) => void | Promise<void> {
|
|
@@ -192,7 +249,12 @@ export function createSiderPairingPendingUpdateReporter(params: {
|
|
|
192
249
|
}
|
|
193
250
|
hasReported = true;
|
|
194
251
|
lastReportedAt = now;
|
|
195
|
-
await params.report(
|
|
252
|
+
await params.report(
|
|
253
|
+
formatSiderPairingPendingMessage({
|
|
254
|
+
pairingCode: params.pairingCode,
|
|
255
|
+
remainingMs,
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
196
258
|
};
|
|
197
259
|
}
|
|
198
260
|
|
|
@@ -262,19 +324,33 @@ export async function requestSiderPairing(): Promise<SiderPairingSession> {
|
|
|
262
324
|
export async function readSiderPairingStatus(
|
|
263
325
|
pairingToken: string,
|
|
264
326
|
): Promise<SiderPairStatusResponse | { status: "expired" }> {
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
"
|
|
270
|
-
|
|
271
|
-
|
|
327
|
+
const url = resolveSiderApiUrl(SIDER_PAIR_STATUS_API_PATH);
|
|
328
|
+
let response: Response;
|
|
329
|
+
try {
|
|
330
|
+
response = await fetch(url, {
|
|
331
|
+
method: "GET",
|
|
332
|
+
headers: {
|
|
333
|
+
Authorization: formatAuthorizationHeader(pairingToken),
|
|
334
|
+
"User-Agent": SIDER_USER_AGENT,
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (isRetryableFetchError(error)) {
|
|
339
|
+
throw new SiderPairingTransientError(describeUnknownError(error));
|
|
340
|
+
}
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
272
343
|
if (response.status === 410) {
|
|
273
344
|
return { status: "expired" };
|
|
274
345
|
}
|
|
346
|
+
if ([502, 503, 504].includes(response.status)) {
|
|
347
|
+
throw new SiderPairingTransientError(
|
|
348
|
+
`server returned ${response.status} ${response.statusText}${await parseErrorDetail(response)}`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
275
351
|
if (!response.ok) {
|
|
276
352
|
throw new Error(
|
|
277
|
-
`selfclaw pairing status failed (${response.status} ${response.statusText})${await parseErrorDetail(response)}`,
|
|
353
|
+
`selfclaw pairing status failed (${url} ${response.status} ${response.statusText})${await parseErrorDetail(response)}`,
|
|
278
354
|
);
|
|
279
355
|
}
|
|
280
356
|
return parsePairStatusResponse(await readJsonResponse(response));
|
|
@@ -283,11 +359,31 @@ export async function readSiderPairingStatus(
|
|
|
283
359
|
export async function waitForSiderPairing(params: {
|
|
284
360
|
pairing: SiderPairingSession;
|
|
285
361
|
onPending?: (state: { remainingMs: number; pollIntervalMs: number }) => void | Promise<void>;
|
|
362
|
+
onRetryableError?: (message: string) => void | Promise<void>;
|
|
286
363
|
}): Promise<SiderPairingResult> {
|
|
287
364
|
const expiresAtMs = params.pairing.expiresAt * 1000;
|
|
288
365
|
|
|
289
366
|
while (true) {
|
|
290
|
-
|
|
367
|
+
let status: SiderPairStatusResponse | { status: "expired" };
|
|
368
|
+
try {
|
|
369
|
+
status = await readSiderPairingStatus(params.pairing.pairingToken);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const remainingMs = Math.max(0, expiresAtMs - Date.now());
|
|
372
|
+
if (error instanceof SiderPairingTransientError && remainingMs > 0) {
|
|
373
|
+
const retryAfterMs = Math.min(params.pairing.pollIntervalMs, remainingMs);
|
|
374
|
+
await params.onRetryableError?.(
|
|
375
|
+
formatSiderPairingRetryMessage({
|
|
376
|
+
pairingCode: params.pairing.pairingCode,
|
|
377
|
+
remainingMs,
|
|
378
|
+
retryAfterMs,
|
|
379
|
+
detail: error.message,
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
382
|
+
await sleep(retryAfterMs);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
291
387
|
if (status.status === "paired") {
|
|
292
388
|
return {
|
|
293
389
|
clawId: status.claw_id,
|
|
@@ -323,7 +419,8 @@ export function formatSiderPairingInstructions(params: {
|
|
|
323
419
|
"",
|
|
324
420
|
"1. Install the Sider Chrome extension from the link above",
|
|
325
421
|
"2. Click the Sider icon in your browser toolbar to open the side panel",
|
|
326
|
-
|
|
422
|
+
"3. In the right sidebar, find and click the Claw icon (the paw-shaped icon, 2nd from top)",
|
|
423
|
+
`4. Enter the pairing code "${params.pairingCode}" and click Connect`,
|
|
327
424
|
"",
|
|
328
425
|
"I'm waiting for the connection...",
|
|
329
426
|
].join("\n");
|