@openclaw/clickclack 2026.7.2-beta.2 → 2026.7.2-beta.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/README.md +120 -0
- package/dist/api.js +2 -2
- package/dist/{channel-BnREQIP8.js → channel-BDq0KWDB.js} +456 -53
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +48 -0
- package/dist/index.js +3 -1
- package/dist/runtime-api-CV1cOqFO.js +782 -0
- package/dist/runtime-api.js +4 -4
- package/dist/setup-claim-CJIUfZS7.js +136 -0
- package/dist/setup-plugin-api.js +2 -1
- package/dist/{setup-surface-B346XaAu.js → setup-surface-Baf26N-D.js} +341 -20
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/npm-shrinkwrap.json +7 -7
- package/openclaw.plugin.json +49 -13
- package/package.json +91 -11
|
@@ -11,10 +11,13 @@ import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
|
11
11
|
import { buildChannelConfigSchema, buildMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
14
|
+
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
14
15
|
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
15
16
|
import { WebSocket } from "ws";
|
|
17
|
+
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
16
18
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, applyAccountNameToChannelSection, applySetupAccountConfigPatch, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, hasConfiguredSecretInput, migrateBaseNameToDefaultAccount, moveSingleAccountChannelSectionToDefaultAccount, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
17
19
|
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
|
|
20
|
+
import net from "node:net";
|
|
18
21
|
//#region \0rolldown/runtime.js
|
|
19
22
|
var __defProp = Object.defineProperty;
|
|
20
23
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -35,6 +38,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
35
38
|
const DEFAULT_RECONNECT_MS = 1500;
|
|
36
39
|
const MIN_RECONNECT_MS = 100;
|
|
37
40
|
const MAX_RECONNECT_MS = 6e4;
|
|
41
|
+
const DEFAULT_DISCUSSIONS_SECTION = "Sessions";
|
|
38
42
|
const { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resolveDefaultClickClackAccountId } = createAccountListHelpers("clickclack", {
|
|
39
43
|
normalizeAccountId,
|
|
40
44
|
hasImplicitDefaultAccount: (cfg) => {
|
|
@@ -49,6 +53,7 @@ function resolveClickClackAccountConfig(cfg, accountId) {
|
|
|
49
53
|
accounts: channel?.accounts,
|
|
50
54
|
accountId,
|
|
51
55
|
omitKeys: ["defaultAccount"],
|
|
56
|
+
nestedObjectKeys: ["discussions"],
|
|
52
57
|
normalizeAccountId
|
|
53
58
|
});
|
|
54
59
|
const account = resolveNormalizedAccountEntry(channel?.accounts, accountId, normalizeAccountId);
|
|
@@ -108,6 +113,9 @@ function resolveClickClackAccount(params) {
|
|
|
108
113
|
env: params.env
|
|
109
114
|
});
|
|
110
115
|
const workspace = merged.workspace?.trim() ?? "";
|
|
116
|
+
const discussionsWorkspace = merged.discussions?.workspace?.trim() || workspace;
|
|
117
|
+
const controlUrlBase = merged.discussions?.controlUrlBase?.trim();
|
|
118
|
+
const apiEndpoint = merged.apiBaseUrl?.trim().replace(/\/$/, "") || baseUrl;
|
|
111
119
|
return {
|
|
112
120
|
accountId,
|
|
113
121
|
enabled,
|
|
@@ -121,7 +129,6 @@ function resolveClickClackAccount(params) {
|
|
|
121
129
|
replyMode: merged.replyMode === "model" ? "model" : "agent",
|
|
122
130
|
model: normalizeOptionalString(merged.model),
|
|
123
131
|
systemPrompt: normalizeOptionalString(merged.systemPrompt),
|
|
124
|
-
timeoutSeconds: merged.timeoutSeconds,
|
|
125
132
|
toolsAllow: merged.toolsAllow,
|
|
126
133
|
defaultTo: merged.defaultTo?.trim() || "channel:general",
|
|
127
134
|
allowFrom: merged.allowFrom ?? ["*"],
|
|
@@ -131,10 +138,17 @@ function resolveClickClackAccount(params) {
|
|
|
131
138
|
}),
|
|
132
139
|
agentActivity: merged.agentActivity === true,
|
|
133
140
|
commandMenu: merged.commandMenu !== false,
|
|
141
|
+
discussions: {
|
|
142
|
+
enabled: merged.discussions?.enabled === true,
|
|
143
|
+
workspace: discussionsWorkspace,
|
|
144
|
+
...controlUrlBase ? { controlUrlBase } : {},
|
|
145
|
+
section: merged.discussions?.section?.trim() || DEFAULT_DISCUSSIONS_SECTION
|
|
146
|
+
},
|
|
134
147
|
config: {
|
|
135
148
|
...merged,
|
|
136
149
|
allowFrom: merged.allowFrom ?? ["*"]
|
|
137
|
-
}
|
|
150
|
+
},
|
|
151
|
+
apiEndpoint
|
|
138
152
|
};
|
|
139
153
|
}
|
|
140
154
|
/**
|
|
@@ -177,6 +191,7 @@ const ClickClackAccountConfigSchema = z.object({
|
|
|
177
191
|
name: z.string().optional(),
|
|
178
192
|
enabled: z.boolean().optional(),
|
|
179
193
|
baseUrl: z.string().url().optional(),
|
|
194
|
+
apiBaseUrl: z.string().url().optional(),
|
|
180
195
|
token: buildSecretInputSchema().optional(),
|
|
181
196
|
tokenFile: z.string().optional(),
|
|
182
197
|
workspace: z.string().optional(),
|
|
@@ -185,13 +200,18 @@ const ClickClackAccountConfigSchema = z.object({
|
|
|
185
200
|
replyMode: z.enum(["agent", "model"]).optional(),
|
|
186
201
|
model: z.string().optional(),
|
|
187
202
|
systemPrompt: z.string().optional(),
|
|
188
|
-
timeoutSeconds: z.number().int().min(1).max(3600).optional(),
|
|
189
203
|
toolsAllow: z.array(z.string()).optional(),
|
|
190
204
|
defaultTo: z.string().optional(),
|
|
191
205
|
allowFrom: z.array(z.string()).optional(),
|
|
192
206
|
reconnectMs: z.number().int().min(100).max(6e4).optional(),
|
|
193
207
|
agentActivity: z.boolean().optional(),
|
|
194
|
-
commandMenu: z.boolean().optional()
|
|
208
|
+
commandMenu: z.boolean().optional(),
|
|
209
|
+
discussions: z.object({
|
|
210
|
+
enabled: z.boolean().optional(),
|
|
211
|
+
workspace: z.string().optional(),
|
|
212
|
+
controlUrlBase: z.string().url().optional(),
|
|
213
|
+
section: z.string().optional()
|
|
214
|
+
}).strict().optional()
|
|
195
215
|
}).strict();
|
|
196
216
|
/**
|
|
197
217
|
* Config schema exported to core so `openclaw doctor` and config validation
|
|
@@ -222,6 +242,15 @@ const CLICKCLACK_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/u;
|
|
|
222
242
|
const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
223
243
|
const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
224
244
|
const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
|
|
245
|
+
const CLICKCLACK_MESSAGE_PAGE_LIMIT = 200;
|
|
246
|
+
const CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT = 8;
|
|
247
|
+
const CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT = 24;
|
|
248
|
+
function compareMessages(left, right) {
|
|
249
|
+
return left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id);
|
|
250
|
+
}
|
|
251
|
+
function keepLatestMessages(messages, limit) {
|
|
252
|
+
return messages.toSorted(compareMessages).slice(-limit);
|
|
253
|
+
}
|
|
225
254
|
var ClickClackHttpError = class extends Error {
|
|
226
255
|
constructor(status, detail, headers) {
|
|
227
256
|
super(`ClickClack ${status}: ${detail}`);
|
|
@@ -229,6 +258,12 @@ var ClickClackHttpError = class extends Error {
|
|
|
229
258
|
this.headers = headers;
|
|
230
259
|
}
|
|
231
260
|
};
|
|
261
|
+
/** Matches the workspace/name uniqueness error returned by current ClickClack servers. */
|
|
262
|
+
function isClickClackChannelNameConflict(error) {
|
|
263
|
+
if (!(error instanceof ClickClackHttpError) || error.status !== 400 && error.status !== 409) return false;
|
|
264
|
+
const message = error.message.toLowerCase();
|
|
265
|
+
return (message.includes("unique") || message.includes("duplicate")) && message.includes("channel") && /workspace.*name|name.*workspace/u.test(message);
|
|
266
|
+
}
|
|
232
267
|
/** Accepts the same bounded request-correlation shape as the ClickClack API. */
|
|
233
268
|
function normalizeClickClackCorrelationId(value) {
|
|
234
269
|
if (typeof value !== "string") return;
|
|
@@ -258,7 +293,7 @@ function createClickClackClient(options) {
|
|
|
258
293
|
});
|
|
259
294
|
if (!response.ok) {
|
|
260
295
|
const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
|
|
261
|
-
throw new ClickClackHttpError(response.status, detail, new Headers(response.headers));
|
|
296
|
+
throw new ClickClackHttpError(response.status, redactToolPayloadText(detail), new Headers(response.headers));
|
|
262
297
|
}
|
|
263
298
|
return await readProviderJsonResponse(response, "ClickClack response", { maxBytes: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES });
|
|
264
299
|
}
|
|
@@ -289,9 +324,63 @@ function createClickClackClient(options) {
|
|
|
289
324
|
channels: async (workspaceId) => {
|
|
290
325
|
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
|
|
291
326
|
},
|
|
327
|
+
createChannel: async (workspaceId, channel) => {
|
|
328
|
+
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
|
|
329
|
+
method: "POST",
|
|
330
|
+
body: JSON.stringify(channel)
|
|
331
|
+
})).channel;
|
|
332
|
+
},
|
|
333
|
+
updateChannel: async (channelId, patch) => {
|
|
334
|
+
return (await request(`/api/channels/${encodeURIComponent(channelId)}`, {
|
|
335
|
+
method: "PATCH",
|
|
336
|
+
body: JSON.stringify(patch)
|
|
337
|
+
})).channel;
|
|
338
|
+
},
|
|
292
339
|
channelMessages: async (channelId, afterSeq, limit = 20) => {
|
|
293
340
|
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
294
341
|
},
|
|
342
|
+
latestChannelMessages: async (channelId, limit = 30) => {
|
|
343
|
+
const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
|
|
344
|
+
let beforeSeq;
|
|
345
|
+
let latest = [];
|
|
346
|
+
let rootPageCount = 0;
|
|
347
|
+
let threadRequestCount = 0;
|
|
348
|
+
let truncated = false;
|
|
349
|
+
while (true) {
|
|
350
|
+
rootPageCount += 1;
|
|
351
|
+
const query = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
352
|
+
if (beforeSeq !== void 0) query.set("before_seq", String(beforeSeq));
|
|
353
|
+
const page = await request(`/api/channels/${encodeURIComponent(channelId)}/messages?${query.toString()}`);
|
|
354
|
+
for (const root of page.messages) {
|
|
355
|
+
latest = keepLatestMessages([...latest, root], boundedLimit);
|
|
356
|
+
const lastReplyAt = root.thread_state?.last_reply_at;
|
|
357
|
+
const cutoff = latest.length === boundedLimit ? latest[0]?.created_at : void 0;
|
|
358
|
+
if (!root.thread_state?.reply_count || lastReplyAt !== void 0 && cutoff !== void 0 && lastReplyAt < cutoff) continue;
|
|
359
|
+
if (threadRequestCount >= CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT) {
|
|
360
|
+
truncated = true;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
threadRequestCount += 1;
|
|
364
|
+
const threadQuery = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
365
|
+
const thread = await request(`/api/messages/${encodeURIComponent(root.id)}/thread?${threadQuery.toString()}`);
|
|
366
|
+
if (thread.replies.length < root.thread_state.reply_count) {
|
|
367
|
+
truncated = true;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
latest = keepLatestMessages([...latest, ...thread.replies], boundedLimit);
|
|
371
|
+
}
|
|
372
|
+
if (!page.has_older) return {
|
|
373
|
+
messages: latest,
|
|
374
|
+
truncated
|
|
375
|
+
};
|
|
376
|
+
if (rootPageCount >= CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT) return {
|
|
377
|
+
messages: latest,
|
|
378
|
+
truncated: true
|
|
379
|
+
};
|
|
380
|
+
if (page.messages.length === 0 || !Number.isSafeInteger(page.oldest_seq) || page.oldest_seq < 0 || page.oldest_seq === beforeSeq) throw new Error("ClickClack message pagination did not advance");
|
|
381
|
+
beforeSeq = page.oldest_seq;
|
|
382
|
+
}
|
|
383
|
+
},
|
|
295
384
|
directMessages: async (conversationId, afterSeq, limit = 20) => {
|
|
296
385
|
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
297
386
|
},
|
|
@@ -447,10 +536,66 @@ async function resolveChannelId(client, workspaceId, channel) {
|
|
|
447
536
|
return found.id;
|
|
448
537
|
}
|
|
449
538
|
//#endregion
|
|
539
|
+
//#region extensions/clickclack/src/setup-contract.ts
|
|
540
|
+
const CLICKCLACK_SETUP_CODE_CLAIM_PATH = "/api/bot-setup-codes/claim";
|
|
541
|
+
const LOOPBACK_ADDRESSES = new net.BlockList();
|
|
542
|
+
LOOPBACK_ADDRESSES.addSubnet("127.0.0.0", 8, "ipv4");
|
|
543
|
+
LOOPBACK_ADDRESSES.addAddress("::1", "ipv6");
|
|
544
|
+
const BASE_PATH_SEGMENT = /^[A-Za-z0-9._~-]+$/u;
|
|
545
|
+
function isClickClackSetupLoopbackHost(hostname) {
|
|
546
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
547
|
+
if (normalized === "localhost") return true;
|
|
548
|
+
const family = net.isIP(normalized);
|
|
549
|
+
return family !== 0 && LOOPBACK_ADDRESSES.check(normalized, family === 4 ? "ipv4" : "ipv6");
|
|
550
|
+
}
|
|
551
|
+
function requireClickClackSetupApiBaseUrl(value, label) {
|
|
552
|
+
if (!value || value !== value.trim()) throw new Error(`ClickClack ${label} is invalid`);
|
|
553
|
+
let parsed;
|
|
554
|
+
try {
|
|
555
|
+
parsed = new URL(value);
|
|
556
|
+
} catch {
|
|
557
|
+
throw new Error(`ClickClack ${label} is invalid`);
|
|
558
|
+
}
|
|
559
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname || parsed.hostname.endsWith(".") || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error(`ClickClack ${label} is invalid`);
|
|
560
|
+
if (parsed.protocol === "http:" && !isClickClackSetupLoopbackHost(parsed.hostname)) throw new Error(`ClickClack ${label} must use HTTPS unless it is on loopback`);
|
|
561
|
+
const pathname = parsed.pathname;
|
|
562
|
+
let basePath = "";
|
|
563
|
+
if (pathname !== "/") {
|
|
564
|
+
if (pathname.endsWith("/") || pathname.includes("//") || pathname.includes("\\") || pathname.slice(1).split("/").some((segment) => !BASE_PATH_SEGMENT.test(segment))) throw new Error(`ClickClack ${label} is invalid`);
|
|
565
|
+
basePath = pathname;
|
|
566
|
+
}
|
|
567
|
+
const canonical = parsed.origin + basePath;
|
|
568
|
+
if (value !== canonical) throw new Error(`ClickClack ${label} is not canonical`);
|
|
569
|
+
return canonical;
|
|
570
|
+
}
|
|
571
|
+
function requireClickClackSetupClaimUrl(value) {
|
|
572
|
+
let parsed;
|
|
573
|
+
try {
|
|
574
|
+
parsed = new URL(value);
|
|
575
|
+
} catch {
|
|
576
|
+
throw new Error("ClickClack setup URL has an invalid claim endpoint.");
|
|
577
|
+
}
|
|
578
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash || !parsed.pathname.endsWith("/api/bot-setup-codes/claim")) throw new Error("ClickClack setup URL has an invalid claim endpoint.");
|
|
579
|
+
const basePath = parsed.pathname.slice(0, -26);
|
|
580
|
+
const apiBaseUrl = requireClickClackSetupApiBaseUrl(parsed.origin + basePath, "setup URL API base");
|
|
581
|
+
const claimUrl = apiBaseUrl + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
582
|
+
if (value !== claimUrl) throw new Error("ClickClack setup URL has a non-canonical claim endpoint.");
|
|
583
|
+
return {
|
|
584
|
+
claimUrl,
|
|
585
|
+
apiBaseUrl
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function buildClickClackSetupClaimUrl(baseUrl) {
|
|
589
|
+
return baseUrl.replace(/\/+$/u, "") + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
590
|
+
}
|
|
591
|
+
//#endregion
|
|
450
592
|
//#region extensions/clickclack/src/setup-core.ts
|
|
451
593
|
const channel$1 = "clickclack";
|
|
594
|
+
const SETUP_CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
595
|
+
const SETUP_CODE_LENGTH = 12;
|
|
452
596
|
const REQUIRED_INPUT_ERROR = "ClickClack requires --token, --base-url, and --workspace (or --use-env).";
|
|
453
597
|
const INVALID_BASE_URL_ERROR = "ClickClack base URL must be a valid http(s) URL.";
|
|
598
|
+
const SETUP_CODE_CONFLICT_ERROR = "ClickClack --code cannot be combined with --token, --token-file, or --use-env.";
|
|
454
599
|
function normalizeClickClackBaseUrl(value) {
|
|
455
600
|
const trimmed = value?.trim();
|
|
456
601
|
if (!trimmed) return;
|
|
@@ -462,12 +607,76 @@ function normalizeClickClackBaseUrl(value) {
|
|
|
462
607
|
return;
|
|
463
608
|
}
|
|
464
609
|
}
|
|
610
|
+
function normalizeClickClackSetupCode(value) {
|
|
611
|
+
const normalized = value.trim().toUpperCase().replaceAll("-", "").replaceAll(" ", "");
|
|
612
|
+
if (normalized.length !== SETUP_CODE_LENGTH || Array.from(normalized).some((character) => !SETUP_CODE_ALPHABET.includes(character))) return;
|
|
613
|
+
return normalized;
|
|
614
|
+
}
|
|
615
|
+
function requireClickClackSetupCodeBaseUrl(value) {
|
|
616
|
+
const baseUrl = normalizeClickClackBaseUrl(value);
|
|
617
|
+
if (!baseUrl) throw new Error("ClickClack setup codes require a valid HTTP(S) base URL.");
|
|
618
|
+
return baseUrl;
|
|
619
|
+
}
|
|
620
|
+
function parseClickClackSetupCodeInput(params) {
|
|
621
|
+
const rawCode = params.code.trim();
|
|
622
|
+
if (!rawCode) throw new Error("ClickClack --code must not be empty.");
|
|
623
|
+
let code = rawCode;
|
|
624
|
+
let baseUrl;
|
|
625
|
+
if (/^[a-z][a-z\d+.-]*:\/\//iu.test(rawCode)) {
|
|
626
|
+
let setupUrl;
|
|
627
|
+
try {
|
|
628
|
+
setupUrl = new URL(rawCode);
|
|
629
|
+
} catch {
|
|
630
|
+
throw new Error("ClickClack --code must be a valid HTTP(S) setup URL or a bare setup code.");
|
|
631
|
+
}
|
|
632
|
+
if (setupUrl.protocol !== "http:" && setupUrl.protocol !== "https:") throw new Error("ClickClack setup codes require an HTTP(S) URL.");
|
|
633
|
+
if (setupUrl.username || setupUrl.password) throw new Error("ClickClack setup URLs must not include credentials.");
|
|
634
|
+
if (setupUrl.search) throw new Error("ClickClack setup URLs must not include a query.");
|
|
635
|
+
code = setupUrl.hash.slice(1);
|
|
636
|
+
if (!code) throw new Error("ClickClack setup URL is missing its #CODE fragment.");
|
|
637
|
+
setupUrl.hash = "";
|
|
638
|
+
let exactClaimUrl;
|
|
639
|
+
if (setupUrl.pathname.endsWith("/api/bot-setup-codes/claim")) {
|
|
640
|
+
const exactEndpoint = requireClickClackSetupClaimUrl(setupUrl.toString());
|
|
641
|
+
baseUrl = exactEndpoint.apiBaseUrl;
|
|
642
|
+
exactClaimUrl = exactEndpoint.claimUrl;
|
|
643
|
+
} else baseUrl = requireClickClackSetupCodeBaseUrl(setupUrl.toString());
|
|
644
|
+
if (params.baseUrl) {
|
|
645
|
+
if (requireClickClackSetupCodeBaseUrl(params.baseUrl) !== baseUrl) throw new Error("ClickClack --base-url does not match the server in the setup-code URL.");
|
|
646
|
+
}
|
|
647
|
+
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
648
|
+
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
649
|
+
return {
|
|
650
|
+
code: normalizedCode,
|
|
651
|
+
baseUrl,
|
|
652
|
+
...exactClaimUrl ? { exactClaimUrl } : {}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
code = code.startsWith("#") ? code.slice(1) : code;
|
|
656
|
+
if (!params.baseUrl) throw new Error("A bare ClickClack setup code requires --base-url.");
|
|
657
|
+
baseUrl = requireClickClackSetupCodeBaseUrl(params.baseUrl);
|
|
658
|
+
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
659
|
+
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
660
|
+
return {
|
|
661
|
+
code: normalizedCode,
|
|
662
|
+
baseUrl
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function formatClickClackSetupCodeClaimError(error) {
|
|
666
|
+
if (typeof error === "object" && error !== null && "status" in error) {
|
|
667
|
+
const status = error.status;
|
|
668
|
+
if (status === 404) return /* @__PURE__ */ new Error("ClickClack setup code is invalid, expired, or already used. Generate a new code and try again.");
|
|
669
|
+
if (status === 429) return /* @__PURE__ */ new Error("Too many ClickClack setup code attempts. Wait and try again.");
|
|
670
|
+
}
|
|
671
|
+
return /* @__PURE__ */ new Error(`Could not claim ClickClack setup code: ${formatErrorMessage(error)}`);
|
|
672
|
+
}
|
|
465
673
|
function applyClickClackSetupConfigPatch(params) {
|
|
466
674
|
const accountId = normalizeAccountId(params.accountId);
|
|
467
675
|
const namedConfig = applyAccountNameToChannelSection({
|
|
468
676
|
cfg: accountId === DEFAULT_ACCOUNT_ID ? params.cfg : moveSingleAccountChannelSectionToDefaultAccount({
|
|
469
677
|
cfg: params.cfg,
|
|
470
|
-
channelKey: channel$1
|
|
678
|
+
channelKey: channel$1,
|
|
679
|
+
setupSurface: clickClackSetupAdapter
|
|
471
680
|
}),
|
|
472
681
|
channelKey: channel$1,
|
|
473
682
|
accountId,
|
|
@@ -530,6 +739,42 @@ function applyClickClackCredentialConfig(params) {
|
|
|
530
739
|
}
|
|
531
740
|
const clickClackSetupAdapter = {
|
|
532
741
|
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
742
|
+
prepareAccountConfigInput: async ({ cfg, accountId, input }) => {
|
|
743
|
+
const setupInput = input;
|
|
744
|
+
if (!setupInput.code?.trim()) return setupInput;
|
|
745
|
+
if (setupInput.token?.trim() || setupInput.tokenFile?.trim() || setupInput.useEnv) throw new Error(SETUP_CODE_CONFLICT_ERROR);
|
|
746
|
+
let setup = parseClickClackSetupCodeInput({
|
|
747
|
+
code: setupInput.code,
|
|
748
|
+
baseUrl: setupInput.baseUrl
|
|
749
|
+
});
|
|
750
|
+
const privateApiBaseUrl = normalizeClickClackBaseUrl(resolveClickClackAccountConfig(cfg, accountId).apiBaseUrl);
|
|
751
|
+
const claimUrl = setup.exactClaimUrl && !privateApiBaseUrl ? setup.exactClaimUrl : buildClickClackSetupClaimUrl(privateApiBaseUrl ?? setup.baseUrl);
|
|
752
|
+
let claim;
|
|
753
|
+
try {
|
|
754
|
+
const { claimClickClackSetupCode } = await import("./setup-claim-CJIUfZS7.js");
|
|
755
|
+
claim = await claimClickClackSetupCode({
|
|
756
|
+
claimUrl,
|
|
757
|
+
code: setup.code,
|
|
758
|
+
...setup.exactClaimUrl ? { expectedClaimUrl: setup.exactClaimUrl } : {}
|
|
759
|
+
});
|
|
760
|
+
} catch (error) {
|
|
761
|
+
throw formatClickClackSetupCodeClaimError(error);
|
|
762
|
+
}
|
|
763
|
+
setup = {
|
|
764
|
+
...setup,
|
|
765
|
+
baseUrl: claim.api_base_url ?? setup.baseUrl
|
|
766
|
+
};
|
|
767
|
+
const { code: _code, tokenFile: _tokenFile, useEnv: _useEnv, ...remainingInput } = setupInput;
|
|
768
|
+
return {
|
|
769
|
+
...remainingInput,
|
|
770
|
+
baseUrl: setup.baseUrl,
|
|
771
|
+
token: claim.token,
|
|
772
|
+
workspace: claim.workspace.id,
|
|
773
|
+
...claim.defaults.defaultTo !== void 0 ? { defaultTo: claim.defaults.defaultTo } : {},
|
|
774
|
+
...claim.defaults.allowFrom !== void 0 ? { allowFrom: [...claim.defaults.allowFrom] } : {},
|
|
775
|
+
...claim.defaults.agentActivity !== void 0 ? { agentActivity: claim.defaults.agentActivity } : {}
|
|
776
|
+
};
|
|
777
|
+
},
|
|
533
778
|
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
534
779
|
cfg,
|
|
535
780
|
channelKey: channel$1,
|
|
@@ -553,37 +798,42 @@ const clickClackSetupAdapter = {
|
|
|
553
798
|
}
|
|
554
799
|
],
|
|
555
800
|
validate: ({ cfg, accountId, input }) => {
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
if (!
|
|
801
|
+
const setupInput = input;
|
|
802
|
+
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl);
|
|
803
|
+
if (setupInput.baseUrl && !baseUrl) return INVALID_BASE_URL_ERROR;
|
|
804
|
+
if (!setupInput.useEnv) return null;
|
|
559
805
|
const existing = resolveClickClackAccountConfig(cfg, accountId);
|
|
560
806
|
const existingBaseUrl = normalizeClickClackBaseUrl(existing.baseUrl);
|
|
561
807
|
if (!baseUrl && existing.baseUrl?.trim() && !existingBaseUrl) return INVALID_BASE_URL_ERROR;
|
|
562
808
|
if (!baseUrl && !existingBaseUrl) return REQUIRED_INPUT_ERROR;
|
|
563
|
-
if (!
|
|
809
|
+
if (!setupInput.workspace?.trim() && !existing.workspace?.trim()) return REQUIRED_INPUT_ERROR;
|
|
564
810
|
return null;
|
|
565
811
|
}
|
|
566
812
|
}),
|
|
567
813
|
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
const
|
|
814
|
+
const setupInput = input;
|
|
815
|
+
const existing = setupInput.useEnv ? resolveClickClackAccountConfig(cfg, accountId) : void 0;
|
|
816
|
+
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl ?? existing?.baseUrl);
|
|
817
|
+
const workspace = setupInput.workspace?.trim() || existing?.workspace?.trim();
|
|
818
|
+
const tokenFile = setupInput.tokenFile?.trim();
|
|
819
|
+
const token = setupInput.token?.trim();
|
|
573
820
|
return applyClickClackCredentialConfig({
|
|
574
821
|
cfg: applyClickClackSetupConfigPatch({
|
|
575
822
|
cfg,
|
|
576
823
|
accountId,
|
|
577
|
-
name:
|
|
824
|
+
name: setupInput.name,
|
|
578
825
|
patch: {
|
|
579
826
|
...baseUrl ? { baseUrl } : {},
|
|
580
|
-
...workspace ? { workspace } : {}
|
|
827
|
+
...workspace ? { workspace } : {},
|
|
828
|
+
...setupInput.defaultTo?.trim() ? { defaultTo: setupInput.defaultTo.trim() } : {},
|
|
829
|
+
...setupInput.allowFrom ? { allowFrom: [...setupInput.allowFrom] } : {},
|
|
830
|
+
...setupInput.agentActivity !== void 0 ? { agentActivity: setupInput.agentActivity } : {}
|
|
581
831
|
}
|
|
582
832
|
}),
|
|
583
833
|
accountId,
|
|
584
834
|
token,
|
|
585
835
|
tokenFile,
|
|
586
|
-
useEnv:
|
|
836
|
+
useEnv: setupInput.useEnv
|
|
587
837
|
});
|
|
588
838
|
},
|
|
589
839
|
afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
|
|
@@ -595,6 +845,77 @@ const clickClackSetupAdapter = {
|
|
|
595
845
|
});
|
|
596
846
|
}
|
|
597
847
|
};
|
|
848
|
+
const clickClackSetupContract = defineChannelSetupContract({
|
|
849
|
+
fields: {
|
|
850
|
+
code: {
|
|
851
|
+
kind: "string",
|
|
852
|
+
sensitive: true,
|
|
853
|
+
cli: {
|
|
854
|
+
flags: "--code <code>",
|
|
855
|
+
description: "ClickClack one-time setup code or setup URL"
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
token: {
|
|
859
|
+
kind: "string",
|
|
860
|
+
sensitive: true,
|
|
861
|
+
cli: {
|
|
862
|
+
flags: "--token <token>",
|
|
863
|
+
description: "ClickClack bot token"
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
tokenFile: {
|
|
867
|
+
kind: "string",
|
|
868
|
+
sensitive: true,
|
|
869
|
+
cli: {
|
|
870
|
+
flags: "--token-file <path>",
|
|
871
|
+
description: "ClickClack bot token file"
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
baseUrl: {
|
|
875
|
+
kind: "string",
|
|
876
|
+
cli: {
|
|
877
|
+
flags: "--base-url <url>",
|
|
878
|
+
description: "ClickClack API base URL"
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
workspace: {
|
|
882
|
+
kind: "string",
|
|
883
|
+
cli: {
|
|
884
|
+
flags: "--workspace <workspace>",
|
|
885
|
+
description: "ClickClack workspace id, slug, or name"
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
defaultTo: {
|
|
889
|
+
kind: "string",
|
|
890
|
+
cli: {
|
|
891
|
+
flags: "--default-to <target>",
|
|
892
|
+
description: "Default ClickClack target"
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
allowFrom: {
|
|
896
|
+
kind: "string-list",
|
|
897
|
+
cli: {
|
|
898
|
+
flags: "--allow-from <ids>",
|
|
899
|
+
description: "Allowed ClickClack senders"
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
agentActivity: {
|
|
903
|
+
kind: "boolean",
|
|
904
|
+
cli: {
|
|
905
|
+
flags: "--agent-activity",
|
|
906
|
+
description: "Enable ClickClack agent activity"
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
useEnv: {
|
|
910
|
+
kind: "boolean",
|
|
911
|
+
cli: {
|
|
912
|
+
flags: "--use-env",
|
|
913
|
+
description: "Use CLICKCLACK_BOT_TOKEN"
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
legacyAdapter: clickClackSetupAdapter
|
|
918
|
+
});
|
|
598
919
|
//#endregion
|
|
599
920
|
//#region extensions/clickclack/src/setup-verify.ts
|
|
600
921
|
var setup_verify_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -625,7 +946,7 @@ async function checkClickClackSetupConnection(params) {
|
|
|
625
946
|
if (usesUnavailableImplicitEnvToken(account, token)) return { status: "skipped-env-token" };
|
|
626
947
|
if (!account.baseUrl || !account.workspace || !token) return { status: "skipped-unconfigured" };
|
|
627
948
|
const client = createClickClackClient({
|
|
628
|
-
baseUrl: account.
|
|
949
|
+
baseUrl: account.apiEndpoint,
|
|
629
950
|
token
|
|
630
951
|
});
|
|
631
952
|
const me = await client.me();
|
|
@@ -825,4 +1146,4 @@ const clickClackSetupWizard = {
|
|
|
825
1146
|
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
|
|
826
1147
|
};
|
|
827
1148
|
//#endregion
|
|
828
|
-
export {
|
|
1149
|
+
export { resolveDefaultClickClackAccountId as S, clickClackMeta as _, buildClickClackSetupClaimUrl as a, listEnabledClickClackAccounts as b, resolveChannelId as c, createClickClackClient as d, isClickClackChannelNameConflict as f, clickClackConfigAdapter as g, CLICKCLACK_CHANNEL_ID as h, CLICKCLACK_SETUP_CODE_CLAIM_PATH as i, resolveWorkspaceId as l, clickClackConfigSchema as m, clickClackSetupAdapter as n, isClickClackSetupLoopbackHost as o, normalizeClickClackCorrelationId as p, clickClackSetupContract as r, requireClickClackSetupApiBaseUrl as s, clickClackSetupWizard as t, ClickClackHttpError as u, DEFAULT_ACCOUNT_ID$2 as v, resolveClickClackAccount as x, listClickClackAccountIds as y };
|
|
@@ -77,6 +77,7 @@ class Receiver extends Writable {
|
|
|
77
77
|
|
|
78
78
|
this._totalPayloadLength = 0;
|
|
79
79
|
this._messageLength = 0;
|
|
80
|
+
this._numFragments = 0;
|
|
80
81
|
this._fragments = [];
|
|
81
82
|
|
|
82
83
|
this._errored = false;
|
|
@@ -500,6 +501,19 @@ class Receiver extends Writable {
|
|
|
500
501
|
return;
|
|
501
502
|
}
|
|
502
503
|
|
|
504
|
+
if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
|
|
505
|
+
const error = this.createError(
|
|
506
|
+
RangeError,
|
|
507
|
+
'Too many message fragments',
|
|
508
|
+
false,
|
|
509
|
+
1008,
|
|
510
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
cb(error);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
|
|
503
517
|
if (this._compressed) {
|
|
504
518
|
this._state = INFLATING;
|
|
505
519
|
this.decompress(data, cb);
|
|
@@ -507,22 +521,6 @@ class Receiver extends Writable {
|
|
|
507
521
|
}
|
|
508
522
|
|
|
509
523
|
if (data.length) {
|
|
510
|
-
if (
|
|
511
|
-
this._maxFragments > 0 &&
|
|
512
|
-
this._fragments.length >= this._maxFragments
|
|
513
|
-
) {
|
|
514
|
-
const error = this.createError(
|
|
515
|
-
RangeError,
|
|
516
|
-
'Too many message fragments',
|
|
517
|
-
false,
|
|
518
|
-
1008,
|
|
519
|
-
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
cb(error);
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
524
|
//
|
|
527
525
|
// This message is not compressed so its length is the sum of the payload
|
|
528
526
|
// length of all fragments.
|
|
@@ -562,22 +560,6 @@ class Receiver extends Writable {
|
|
|
562
560
|
return;
|
|
563
561
|
}
|
|
564
562
|
|
|
565
|
-
if (
|
|
566
|
-
this._maxFragments > 0 &&
|
|
567
|
-
this._fragments.length >= this._maxFragments
|
|
568
|
-
) {
|
|
569
|
-
const error = this.createError(
|
|
570
|
-
RangeError,
|
|
571
|
-
'Too many message fragments',
|
|
572
|
-
false,
|
|
573
|
-
1008,
|
|
574
|
-
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
cb(error);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
563
|
this._fragments.push(buf);
|
|
582
564
|
}
|
|
583
565
|
|
|
@@ -604,6 +586,7 @@ class Receiver extends Writable {
|
|
|
604
586
|
this._totalPayloadLength = 0;
|
|
605
587
|
this._messageLength = 0;
|
|
606
588
|
this._fragmented = 0;
|
|
589
|
+
this._numFragments = 0;
|
|
607
590
|
this._fragments = [];
|
|
608
591
|
|
|
609
592
|
if (this._opcode === 2) {
|
|
@@ -43,9 +43,9 @@ class WebSocketServer extends EventEmitter {
|
|
|
43
43
|
* called
|
|
44
44
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
45
45
|
* @param {String} [options.host] The hostname where to bind the server
|
|
46
|
-
* @param {Number} [options.maxBufferedChunks=
|
|
46
|
+
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
|
|
47
47
|
* buffered data chunks
|
|
48
|
-
* @param {Number} [options.maxFragments=
|
|
48
|
+
* @param {Number} [options.maxFragments=16384] The maximum number of message
|
|
49
49
|
* fragments
|
|
50
50
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
51
51
|
* size
|
|
@@ -69,8 +69,8 @@ class WebSocketServer extends EventEmitter {
|
|
|
69
69
|
options = {
|
|
70
70
|
allowSynchronousEvents: true,
|
|
71
71
|
autoPong: true,
|
|
72
|
-
maxBufferedChunks:
|
|
73
|
-
maxFragments:
|
|
72
|
+
maxBufferedChunks: 256 * 1024,
|
|
73
|
+
maxFragments: 16 * 1024,
|
|
74
74
|
maxPayload: 100 * 1024 * 1024,
|
|
75
75
|
skipUTF8Validation: false,
|
|
76
76
|
perMessageDeflate: false,
|
|
@@ -646,9 +646,9 @@ module.exports = WebSocket;
|
|
|
646
646
|
* masking key
|
|
647
647
|
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
|
|
648
648
|
* handshake request
|
|
649
|
-
* @param {Number} [options.maxBufferedChunks=
|
|
649
|
+
* @param {Number} [options.maxBufferedChunks=262144] The maximum number of
|
|
650
650
|
* buffered data chunks
|
|
651
|
-
* @param {Number} [options.maxFragments=
|
|
651
|
+
* @param {Number} [options.maxFragments=16384] The maximum number of message
|
|
652
652
|
* fragments
|
|
653
653
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
654
654
|
* size
|
|
@@ -670,8 +670,8 @@ function initAsClient(websocket, address, protocols, options) {
|
|
|
670
670
|
autoPong: true,
|
|
671
671
|
closeTimeout: CLOSE_TIMEOUT,
|
|
672
672
|
protocolVersion: protocolVersions[1],
|
|
673
|
-
maxBufferedChunks:
|
|
674
|
-
maxFragments:
|
|
673
|
+
maxBufferedChunks: 256 * 1024,
|
|
674
|
+
maxFragments: 16 * 1024,
|
|
675
675
|
maxPayload: 100 * 1024 * 1024,
|
|
676
676
|
skipUTF8Validation: false,
|
|
677
677
|
perMessageDeflate: true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ws",
|
|
3
|
-
"version": "8.21.
|
|
3
|
+
"version": "8.21.1",
|
|
4
4
|
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"HyBi",
|
|
@@ -66,5 +66,9 @@
|
|
|
66
66
|
"nyc": "^15.0.0",
|
|
67
67
|
"prettier": "^3.0.0",
|
|
68
68
|
"utf-8-validate": "^6.0.0"
|
|
69
|
+
},
|
|
70
|
+
"allowScripts": {
|
|
71
|
+
"bufferutil": true,
|
|
72
|
+
"utf-8-validate": true
|
|
69
73
|
}
|
|
70
74
|
}
|