@openclaw/clickclack 2026.7.2-beta.3 → 2026.7.2-beta.5
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 +125 -0
- package/dist/api.js +2 -2
- package/dist/{channel-CEYthZ9N.js → channel-DfcFlLjC.js} +397 -13
- 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-D1H9vheM.js +1029 -0
- package/dist/runtime-api.js +4 -4
- package/dist/{setup-claim-BUWvZjtu.js → setup-claim-BFU16zoo.js} +33 -11
- package/dist/setup-plugin-api.js +2 -1
- package/dist/{setup-surface-CNjp76Xx.js → setup-surface-CD6MUC1g.js} +286 -89
- 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/openclaw.plugin.json +49 -13
- package/package.json +91 -16
- package/npm-shrinkwrap.json +0 -54
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { createAccountListHelpers, hasConfiguredAccountValue } from "openclaw/plugin-sdk/account-helpers";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
3
|
-
import { resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
|
4
3
|
import { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-resolution-runtime";
|
|
5
4
|
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
|
6
5
|
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
|
@@ -11,10 +10,13 @@ import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
|
11
10
|
import { buildChannelConfigSchema, buildMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
12
11
|
import { z } from "zod";
|
|
13
12
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
13
|
+
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
14
14
|
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
15
15
|
import { WebSocket } from "ws";
|
|
16
|
-
import {
|
|
16
|
+
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
17
|
+
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, applyAccountNameToChannelSection, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, hasConfiguredSecretInput, migrateBaseNameToDefaultAccount, moveSingleAccountChannelSectionToDefaultAccount, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
17
18
|
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
|
|
19
|
+
import net from "node:net";
|
|
18
20
|
//#region \0rolldown/runtime.js
|
|
19
21
|
var __defProp = Object.defineProperty;
|
|
20
22
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -35,8 +37,11 @@ var __exportAll = (all, no_symbols) => {
|
|
|
35
37
|
const DEFAULT_RECONNECT_MS = 1500;
|
|
36
38
|
const MIN_RECONNECT_MS = 100;
|
|
37
39
|
const MAX_RECONNECT_MS = 6e4;
|
|
38
|
-
const
|
|
40
|
+
const DEFAULT_DISCUSSIONS_SECTION = "Sessions";
|
|
41
|
+
const { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resolveDefaultClickClackAccountId, resolveAccountConfig: resolveMergedClickClackAccountConfig } = createAccountListHelpers("clickclack", {
|
|
39
42
|
normalizeAccountId,
|
|
43
|
+
omitKeys: ["defaultAccount"],
|
|
44
|
+
nestedObjectKeys: ["discussions"],
|
|
40
45
|
hasImplicitDefaultAccount: (cfg) => {
|
|
41
46
|
const channel = cfg.channels?.clickclack;
|
|
42
47
|
return Boolean(channel?.baseUrl?.trim() && (hasConfiguredAccountValue(channel.token) || Boolean(channel.tokenFile?.trim()) || Boolean(process.env.CLICKCLACK_BOT_TOKEN?.trim())) && channel.workspace?.trim());
|
|
@@ -44,13 +49,7 @@ const { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resol
|
|
|
44
49
|
});
|
|
45
50
|
function resolveClickClackAccountConfig(cfg, accountId) {
|
|
46
51
|
const channel = cfg.channels?.clickclack;
|
|
47
|
-
const merged =
|
|
48
|
-
channelConfig: cfg.channels?.clickclack,
|
|
49
|
-
accounts: channel?.accounts,
|
|
50
|
-
accountId,
|
|
51
|
-
omitKeys: ["defaultAccount"],
|
|
52
|
-
normalizeAccountId
|
|
53
|
-
});
|
|
52
|
+
const merged = resolveMergedClickClackAccountConfig(cfg, accountId);
|
|
54
53
|
const account = resolveNormalizedAccountEntry(channel?.accounts, accountId, normalizeAccountId);
|
|
55
54
|
const accountTokenFile = account?.tokenFile?.trim();
|
|
56
55
|
if (accountTokenFile) return {
|
|
@@ -108,6 +107,9 @@ function resolveClickClackAccount(params) {
|
|
|
108
107
|
env: params.env
|
|
109
108
|
});
|
|
110
109
|
const workspace = merged.workspace?.trim() ?? "";
|
|
110
|
+
const discussionsWorkspace = merged.discussions?.workspace?.trim() || workspace;
|
|
111
|
+
const controlUrlBase = merged.discussions?.controlUrlBase?.trim();
|
|
112
|
+
const apiEndpoint = merged.apiBaseUrl?.trim().replace(/\/$/, "") || baseUrl;
|
|
111
113
|
return {
|
|
112
114
|
accountId,
|
|
113
115
|
enabled,
|
|
@@ -121,7 +123,6 @@ function resolveClickClackAccount(params) {
|
|
|
121
123
|
replyMode: merged.replyMode === "model" ? "model" : "agent",
|
|
122
124
|
model: normalizeOptionalString(merged.model),
|
|
123
125
|
systemPrompt: normalizeOptionalString(merged.systemPrompt),
|
|
124
|
-
timeoutSeconds: merged.timeoutSeconds,
|
|
125
126
|
toolsAllow: merged.toolsAllow,
|
|
126
127
|
defaultTo: merged.defaultTo?.trim() || "channel:general",
|
|
127
128
|
allowFrom: merged.allowFrom ?? ["*"],
|
|
@@ -131,10 +132,17 @@ function resolveClickClackAccount(params) {
|
|
|
131
132
|
}),
|
|
132
133
|
agentActivity: merged.agentActivity === true,
|
|
133
134
|
commandMenu: merged.commandMenu !== false,
|
|
135
|
+
discussions: {
|
|
136
|
+
enabled: merged.discussions?.enabled === true,
|
|
137
|
+
workspace: discussionsWorkspace,
|
|
138
|
+
...controlUrlBase ? { controlUrlBase } : {},
|
|
139
|
+
section: merged.discussions?.section?.trim() || DEFAULT_DISCUSSIONS_SECTION
|
|
140
|
+
},
|
|
134
141
|
config: {
|
|
135
142
|
...merged,
|
|
136
143
|
allowFrom: merged.allowFrom ?? ["*"]
|
|
137
|
-
}
|
|
144
|
+
},
|
|
145
|
+
apiEndpoint
|
|
138
146
|
};
|
|
139
147
|
}
|
|
140
148
|
/**
|
|
@@ -177,6 +185,7 @@ const ClickClackAccountConfigSchema = z.object({
|
|
|
177
185
|
name: z.string().optional(),
|
|
178
186
|
enabled: z.boolean().optional(),
|
|
179
187
|
baseUrl: z.string().url().optional(),
|
|
188
|
+
apiBaseUrl: z.string().url().optional(),
|
|
180
189
|
token: buildSecretInputSchema().optional(),
|
|
181
190
|
tokenFile: z.string().optional(),
|
|
182
191
|
workspace: z.string().optional(),
|
|
@@ -185,13 +194,18 @@ const ClickClackAccountConfigSchema = z.object({
|
|
|
185
194
|
replyMode: z.enum(["agent", "model"]).optional(),
|
|
186
195
|
model: z.string().optional(),
|
|
187
196
|
systemPrompt: z.string().optional(),
|
|
188
|
-
timeoutSeconds: z.number().int().min(1).max(3600).optional(),
|
|
189
197
|
toolsAllow: z.array(z.string()).optional(),
|
|
190
198
|
defaultTo: z.string().optional(),
|
|
191
199
|
allowFrom: z.array(z.string()).optional(),
|
|
192
200
|
reconnectMs: z.number().int().min(100).max(6e4).optional(),
|
|
193
201
|
agentActivity: z.boolean().optional(),
|
|
194
|
-
commandMenu: z.boolean().optional()
|
|
202
|
+
commandMenu: z.boolean().optional(),
|
|
203
|
+
discussions: z.object({
|
|
204
|
+
enabled: z.boolean().optional(),
|
|
205
|
+
workspace: z.string().optional(),
|
|
206
|
+
controlUrlBase: z.string().url().optional(),
|
|
207
|
+
section: z.string().optional()
|
|
208
|
+
}).strict().optional()
|
|
195
209
|
}).strict();
|
|
196
210
|
/**
|
|
197
211
|
* Config schema exported to core so `openclaw doctor` and config validation
|
|
@@ -222,6 +236,15 @@ const CLICKCLACK_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/u;
|
|
|
222
236
|
const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
223
237
|
const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
224
238
|
const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
|
|
239
|
+
const CLICKCLACK_MESSAGE_PAGE_LIMIT = 200;
|
|
240
|
+
const CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT = 8;
|
|
241
|
+
const CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT = 24;
|
|
242
|
+
function compareMessages(left, right) {
|
|
243
|
+
return left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id);
|
|
244
|
+
}
|
|
245
|
+
function keepLatestMessages(messages, limit) {
|
|
246
|
+
return messages.toSorted(compareMessages).slice(-limit);
|
|
247
|
+
}
|
|
225
248
|
var ClickClackHttpError = class extends Error {
|
|
226
249
|
constructor(status, detail, headers) {
|
|
227
250
|
super(`ClickClack ${status}: ${detail}`);
|
|
@@ -229,6 +252,12 @@ var ClickClackHttpError = class extends Error {
|
|
|
229
252
|
this.headers = headers;
|
|
230
253
|
}
|
|
231
254
|
};
|
|
255
|
+
/** Matches the workspace/name uniqueness error returned by current ClickClack servers. */
|
|
256
|
+
function isClickClackChannelNameConflict(error) {
|
|
257
|
+
if (!(error instanceof ClickClackHttpError) || error.status !== 400 && error.status !== 409) return false;
|
|
258
|
+
const message = error.message.toLowerCase();
|
|
259
|
+
return (message.includes("unique") || message.includes("duplicate")) && message.includes("channel") && /workspace.*name|name.*workspace/u.test(message);
|
|
260
|
+
}
|
|
232
261
|
/** Accepts the same bounded request-correlation shape as the ClickClack API. */
|
|
233
262
|
function normalizeClickClackCorrelationId(value) {
|
|
234
263
|
if (typeof value !== "string") return;
|
|
@@ -258,7 +287,7 @@ function createClickClackClient(options) {
|
|
|
258
287
|
});
|
|
259
288
|
if (!response.ok) {
|
|
260
289
|
const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
|
|
261
|
-
throw new ClickClackHttpError(response.status, detail, new Headers(response.headers));
|
|
290
|
+
throw new ClickClackHttpError(response.status, redactToolPayloadText(detail), new Headers(response.headers));
|
|
262
291
|
}
|
|
263
292
|
return await readProviderJsonResponse(response, "ClickClack response", { maxBytes: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES });
|
|
264
293
|
}
|
|
@@ -289,9 +318,63 @@ function createClickClackClient(options) {
|
|
|
289
318
|
channels: async (workspaceId) => {
|
|
290
319
|
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
|
|
291
320
|
},
|
|
321
|
+
createChannel: async (workspaceId, channel) => {
|
|
322
|
+
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
body: JSON.stringify(channel)
|
|
325
|
+
})).channel;
|
|
326
|
+
},
|
|
327
|
+
updateChannel: async (channelId, patch) => {
|
|
328
|
+
return (await request(`/api/channels/${encodeURIComponent(channelId)}`, {
|
|
329
|
+
method: "PATCH",
|
|
330
|
+
body: JSON.stringify(patch)
|
|
331
|
+
})).channel;
|
|
332
|
+
},
|
|
292
333
|
channelMessages: async (channelId, afterSeq, limit = 20) => {
|
|
293
334
|
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
294
335
|
},
|
|
336
|
+
latestChannelMessages: async (channelId, limit = 30) => {
|
|
337
|
+
const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
|
|
338
|
+
let beforeSeq;
|
|
339
|
+
let latest = [];
|
|
340
|
+
let rootPageCount = 0;
|
|
341
|
+
let threadRequestCount = 0;
|
|
342
|
+
let truncated = false;
|
|
343
|
+
while (true) {
|
|
344
|
+
rootPageCount += 1;
|
|
345
|
+
const query = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
346
|
+
if (beforeSeq !== void 0) query.set("before_seq", String(beforeSeq));
|
|
347
|
+
const page = await request(`/api/channels/${encodeURIComponent(channelId)}/messages?${query.toString()}`);
|
|
348
|
+
for (const root of page.messages) {
|
|
349
|
+
latest = keepLatestMessages([...latest, root], boundedLimit);
|
|
350
|
+
const lastReplyAt = root.thread_state?.last_reply_at;
|
|
351
|
+
const cutoff = latest.length === boundedLimit ? latest[0]?.created_at : void 0;
|
|
352
|
+
if (!root.thread_state?.reply_count || lastReplyAt !== void 0 && cutoff !== void 0 && lastReplyAt < cutoff) continue;
|
|
353
|
+
if (threadRequestCount >= CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT) {
|
|
354
|
+
truncated = true;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
threadRequestCount += 1;
|
|
358
|
+
const threadQuery = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
359
|
+
const thread = await request(`/api/messages/${encodeURIComponent(root.id)}/thread?${threadQuery.toString()}`);
|
|
360
|
+
if (thread.replies.length < root.thread_state.reply_count) {
|
|
361
|
+
truncated = true;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
latest = keepLatestMessages([...latest, ...thread.replies], boundedLimit);
|
|
365
|
+
}
|
|
366
|
+
if (!page.has_older) return {
|
|
367
|
+
messages: latest,
|
|
368
|
+
truncated
|
|
369
|
+
};
|
|
370
|
+
if (rootPageCount >= CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT) return {
|
|
371
|
+
messages: latest,
|
|
372
|
+
truncated: true
|
|
373
|
+
};
|
|
374
|
+
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");
|
|
375
|
+
beforeSeq = page.oldest_seq;
|
|
376
|
+
}
|
|
377
|
+
},
|
|
295
378
|
directMessages: async (conversationId, afterSeq, limit = 20) => {
|
|
296
379
|
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
297
380
|
},
|
|
@@ -447,6 +530,59 @@ async function resolveChannelId(client, workspaceId, channel) {
|
|
|
447
530
|
return found.id;
|
|
448
531
|
}
|
|
449
532
|
//#endregion
|
|
533
|
+
//#region extensions/clickclack/src/setup-contract.ts
|
|
534
|
+
const CLICKCLACK_SETUP_CODE_CLAIM_PATH = "/api/bot-setup-codes/claim";
|
|
535
|
+
const LOOPBACK_ADDRESSES = new net.BlockList();
|
|
536
|
+
LOOPBACK_ADDRESSES.addSubnet("127.0.0.0", 8, "ipv4");
|
|
537
|
+
LOOPBACK_ADDRESSES.addAddress("::1", "ipv6");
|
|
538
|
+
const BASE_PATH_SEGMENT = /^[A-Za-z0-9._~-]+$/u;
|
|
539
|
+
function isClickClackSetupLoopbackHost(hostname) {
|
|
540
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
541
|
+
if (normalized === "localhost") return true;
|
|
542
|
+
const family = net.isIP(normalized);
|
|
543
|
+
return family !== 0 && LOOPBACK_ADDRESSES.check(normalized, family === 4 ? "ipv4" : "ipv6");
|
|
544
|
+
}
|
|
545
|
+
function requireClickClackSetupApiBaseUrl(value, label) {
|
|
546
|
+
if (!value || value !== value.trim()) throw new Error(`ClickClack ${label} is invalid`);
|
|
547
|
+
let parsed;
|
|
548
|
+
try {
|
|
549
|
+
parsed = new URL(value);
|
|
550
|
+
} catch {
|
|
551
|
+
throw new Error(`ClickClack ${label} is invalid`);
|
|
552
|
+
}
|
|
553
|
+
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`);
|
|
554
|
+
if (parsed.protocol === "http:" && !isClickClackSetupLoopbackHost(parsed.hostname)) throw new Error(`ClickClack ${label} must use HTTPS unless it is on loopback`);
|
|
555
|
+
const pathname = parsed.pathname;
|
|
556
|
+
let basePath = "";
|
|
557
|
+
if (pathname !== "/") {
|
|
558
|
+
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`);
|
|
559
|
+
basePath = pathname;
|
|
560
|
+
}
|
|
561
|
+
const canonical = parsed.origin + basePath;
|
|
562
|
+
if (value !== canonical) throw new Error(`ClickClack ${label} is not canonical`);
|
|
563
|
+
return canonical;
|
|
564
|
+
}
|
|
565
|
+
function requireClickClackSetupClaimUrl(value) {
|
|
566
|
+
let parsed;
|
|
567
|
+
try {
|
|
568
|
+
parsed = new URL(value);
|
|
569
|
+
} catch {
|
|
570
|
+
throw new Error("ClickClack setup URL has an invalid claim endpoint.");
|
|
571
|
+
}
|
|
572
|
+
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.");
|
|
573
|
+
const basePath = parsed.pathname.slice(0, -26);
|
|
574
|
+
const apiBaseUrl = requireClickClackSetupApiBaseUrl(parsed.origin + basePath, "setup URL API base");
|
|
575
|
+
const claimUrl = apiBaseUrl + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
576
|
+
if (value !== claimUrl) throw new Error("ClickClack setup URL has a non-canonical claim endpoint.");
|
|
577
|
+
return {
|
|
578
|
+
claimUrl,
|
|
579
|
+
apiBaseUrl
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function buildClickClackSetupClaimUrl(baseUrl) {
|
|
583
|
+
return baseUrl.replace(/\/+$/u, "") + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
584
|
+
}
|
|
585
|
+
//#endregion
|
|
450
586
|
//#region extensions/clickclack/src/setup-core.ts
|
|
451
587
|
const channel$1 = "clickclack";
|
|
452
588
|
const SETUP_CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
@@ -489,19 +625,30 @@ function parseClickClackSetupCodeInput(params) {
|
|
|
489
625
|
}
|
|
490
626
|
if (setupUrl.protocol !== "http:" && setupUrl.protocol !== "https:") throw new Error("ClickClack setup codes require an HTTP(S) URL.");
|
|
491
627
|
if (setupUrl.username || setupUrl.password) throw new Error("ClickClack setup URLs must not include credentials.");
|
|
628
|
+
if (setupUrl.search) throw new Error("ClickClack setup URLs must not include a query.");
|
|
492
629
|
code = setupUrl.hash.slice(1);
|
|
493
630
|
if (!code) throw new Error("ClickClack setup URL is missing its #CODE fragment.");
|
|
494
631
|
setupUrl.hash = "";
|
|
495
|
-
|
|
496
|
-
|
|
632
|
+
let exactClaimUrl;
|
|
633
|
+
if (setupUrl.pathname.endsWith("/api/bot-setup-codes/claim")) {
|
|
634
|
+
const exactEndpoint = requireClickClackSetupClaimUrl(setupUrl.toString());
|
|
635
|
+
baseUrl = exactEndpoint.apiBaseUrl;
|
|
636
|
+
exactClaimUrl = exactEndpoint.claimUrl;
|
|
637
|
+
} else baseUrl = requireClickClackSetupCodeBaseUrl(setupUrl.toString());
|
|
497
638
|
if (params.baseUrl) {
|
|
498
639
|
if (requireClickClackSetupCodeBaseUrl(params.baseUrl) !== baseUrl) throw new Error("ClickClack --base-url does not match the server in the setup-code URL.");
|
|
499
640
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
641
|
+
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
642
|
+
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
643
|
+
return {
|
|
644
|
+
code: normalizedCode,
|
|
645
|
+
baseUrl,
|
|
646
|
+
...exactClaimUrl ? { exactClaimUrl } : {}
|
|
647
|
+
};
|
|
504
648
|
}
|
|
649
|
+
code = code.startsWith("#") ? code.slice(1) : code;
|
|
650
|
+
if (!params.baseUrl) throw new Error("A bare ClickClack setup code requires --base-url.");
|
|
651
|
+
baseUrl = requireClickClackSetupCodeBaseUrl(params.baseUrl);
|
|
505
652
|
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
506
653
|
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
507
654
|
return {
|
|
@@ -522,84 +669,61 @@ function applyClickClackSetupConfigPatch(params) {
|
|
|
522
669
|
const namedConfig = applyAccountNameToChannelSection({
|
|
523
670
|
cfg: accountId === DEFAULT_ACCOUNT_ID ? params.cfg : moveSingleAccountChannelSectionToDefaultAccount({
|
|
524
671
|
cfg: params.cfg,
|
|
525
|
-
channelKey: channel$1
|
|
672
|
+
channelKey: channel$1,
|
|
673
|
+
setupSurface: clickClackSetupAdapter
|
|
526
674
|
}),
|
|
527
675
|
channelKey: channel$1,
|
|
528
676
|
accountId,
|
|
529
677
|
name: params.name
|
|
530
678
|
});
|
|
531
|
-
return
|
|
679
|
+
return patchScopedAccountConfig({
|
|
532
680
|
cfg: accountId !== DEFAULT_ACCOUNT_ID ? migrateBaseNameToDefaultAccount({
|
|
533
681
|
cfg: namedConfig,
|
|
534
682
|
channelKey: channel$1
|
|
535
683
|
}) : namedConfig,
|
|
536
684
|
channelKey: channel$1,
|
|
537
685
|
accountId,
|
|
538
|
-
patch: params.patch
|
|
686
|
+
patch: params.patch,
|
|
687
|
+
...params.clearFields ? { clearFields: params.clearFields } : {}
|
|
539
688
|
});
|
|
540
689
|
}
|
|
541
|
-
function clearClickClackSetupConfigFields(params) {
|
|
542
|
-
const clickclack = params.cfg.channels?.clickclack;
|
|
543
|
-
if (!clickclack) return params.cfg;
|
|
544
|
-
const accountId = normalizeAccountId(params.accountId);
|
|
545
|
-
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
546
|
-
const nextClickClack = { ...clickclack };
|
|
547
|
-
for (const field of params.fields) delete nextClickClack[field];
|
|
548
|
-
return {
|
|
549
|
-
...params.cfg,
|
|
550
|
-
channels: {
|
|
551
|
-
...params.cfg.channels,
|
|
552
|
-
clickclack: nextClickClack
|
|
553
|
-
}
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
const currentAccount = clickclack.accounts?.[accountId];
|
|
557
|
-
if (!currentAccount) return params.cfg;
|
|
558
|
-
const nextAccount = { ...currentAccount };
|
|
559
|
-
for (const field of params.fields) delete nextAccount[field];
|
|
560
|
-
return {
|
|
561
|
-
...params.cfg,
|
|
562
|
-
channels: {
|
|
563
|
-
...params.cfg.channels,
|
|
564
|
-
clickclack: {
|
|
565
|
-
...clickclack,
|
|
566
|
-
accounts: {
|
|
567
|
-
...clickclack.accounts,
|
|
568
|
-
[accountId]: nextAccount
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
690
|
function applyClickClackCredentialConfig(params) {
|
|
575
691
|
const fieldsToClear = params.useEnv ? ["token", "tokenFile"] : params.tokenFile ? ["token"] : params.token !== void 0 ? ["tokenFile"] : [];
|
|
576
|
-
return
|
|
577
|
-
cfg:
|
|
578
|
-
cfg: params.cfg,
|
|
579
|
-
accountId: params.accountId,
|
|
580
|
-
patch: params.useEnv ? {} : params.tokenFile ? { tokenFile: params.tokenFile } : params.token !== void 0 ? { token: params.token } : {}
|
|
581
|
-
}),
|
|
692
|
+
return applyClickClackSetupConfigPatch({
|
|
693
|
+
cfg: params.cfg,
|
|
582
694
|
accountId: params.accountId,
|
|
583
|
-
|
|
695
|
+
clearFields: fieldsToClear,
|
|
696
|
+
patch: params.useEnv ? {} : params.tokenFile ? { tokenFile: params.tokenFile } : params.token !== void 0 ? { token: params.token } : {}
|
|
584
697
|
});
|
|
585
698
|
}
|
|
586
699
|
const clickClackSetupAdapter = {
|
|
587
700
|
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
588
|
-
prepareAccountConfigInput: async ({ input }) => {
|
|
589
|
-
|
|
590
|
-
if (
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
701
|
+
prepareAccountConfigInput: async ({ cfg, accountId, input }) => {
|
|
702
|
+
const setupInput = input;
|
|
703
|
+
if (!setupInput.code?.trim()) return setupInput;
|
|
704
|
+
if (setupInput.token?.trim() || setupInput.tokenFile?.trim() || setupInput.useEnv) throw new Error(SETUP_CODE_CONFLICT_ERROR);
|
|
705
|
+
let setup = parseClickClackSetupCodeInput({
|
|
706
|
+
code: setupInput.code,
|
|
707
|
+
baseUrl: setupInput.baseUrl
|
|
594
708
|
});
|
|
709
|
+
const privateApiBaseUrl = normalizeClickClackBaseUrl(resolveClickClackAccountConfig(cfg, accountId).apiBaseUrl);
|
|
710
|
+
const claimUrl = setup.exactClaimUrl && !privateApiBaseUrl ? setup.exactClaimUrl : buildClickClackSetupClaimUrl(privateApiBaseUrl ?? setup.baseUrl);
|
|
595
711
|
let claim;
|
|
596
712
|
try {
|
|
597
|
-
const { claimClickClackSetupCode } = await import("./setup-claim-
|
|
598
|
-
claim = await claimClickClackSetupCode(
|
|
713
|
+
const { claimClickClackSetupCode } = await import("./setup-claim-BFU16zoo.js");
|
|
714
|
+
claim = await claimClickClackSetupCode({
|
|
715
|
+
claimUrl,
|
|
716
|
+
code: setup.code,
|
|
717
|
+
...setup.exactClaimUrl ? { expectedClaimUrl: setup.exactClaimUrl } : {}
|
|
718
|
+
});
|
|
599
719
|
} catch (error) {
|
|
600
720
|
throw formatClickClackSetupCodeClaimError(error);
|
|
601
721
|
}
|
|
602
|
-
|
|
722
|
+
setup = {
|
|
723
|
+
...setup,
|
|
724
|
+
baseUrl: claim.api_base_url ?? setup.baseUrl
|
|
725
|
+
};
|
|
726
|
+
const { code: _code, tokenFile: _tokenFile, useEnv: _useEnv, ...remainingInput } = setupInput;
|
|
603
727
|
return {
|
|
604
728
|
...remainingInput,
|
|
605
729
|
baseUrl: setup.baseUrl,
|
|
@@ -633,40 +757,42 @@ const clickClackSetupAdapter = {
|
|
|
633
757
|
}
|
|
634
758
|
],
|
|
635
759
|
validate: ({ cfg, accountId, input }) => {
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
if (!
|
|
760
|
+
const setupInput = input;
|
|
761
|
+
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl);
|
|
762
|
+
if (setupInput.baseUrl && !baseUrl) return INVALID_BASE_URL_ERROR;
|
|
763
|
+
if (!setupInput.useEnv) return null;
|
|
639
764
|
const existing = resolveClickClackAccountConfig(cfg, accountId);
|
|
640
765
|
const existingBaseUrl = normalizeClickClackBaseUrl(existing.baseUrl);
|
|
641
766
|
if (!baseUrl && existing.baseUrl?.trim() && !existingBaseUrl) return INVALID_BASE_URL_ERROR;
|
|
642
767
|
if (!baseUrl && !existingBaseUrl) return REQUIRED_INPUT_ERROR;
|
|
643
|
-
if (!
|
|
768
|
+
if (!setupInput.workspace?.trim() && !existing.workspace?.trim()) return REQUIRED_INPUT_ERROR;
|
|
644
769
|
return null;
|
|
645
770
|
}
|
|
646
771
|
}),
|
|
647
772
|
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
648
|
-
const
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
const
|
|
773
|
+
const setupInput = input;
|
|
774
|
+
const existing = setupInput.useEnv ? resolveClickClackAccountConfig(cfg, accountId) : void 0;
|
|
775
|
+
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl ?? existing?.baseUrl);
|
|
776
|
+
const workspace = setupInput.workspace?.trim() || existing?.workspace?.trim();
|
|
777
|
+
const tokenFile = setupInput.tokenFile?.trim();
|
|
778
|
+
const token = setupInput.token?.trim();
|
|
653
779
|
return applyClickClackCredentialConfig({
|
|
654
780
|
cfg: applyClickClackSetupConfigPatch({
|
|
655
781
|
cfg,
|
|
656
782
|
accountId,
|
|
657
|
-
name:
|
|
783
|
+
name: setupInput.name,
|
|
658
784
|
patch: {
|
|
659
785
|
...baseUrl ? { baseUrl } : {},
|
|
660
786
|
...workspace ? { workspace } : {},
|
|
661
|
-
...
|
|
662
|
-
...
|
|
663
|
-
...
|
|
787
|
+
...setupInput.defaultTo?.trim() ? { defaultTo: setupInput.defaultTo.trim() } : {},
|
|
788
|
+
...setupInput.allowFrom ? { allowFrom: [...setupInput.allowFrom] } : {},
|
|
789
|
+
...setupInput.agentActivity !== void 0 ? { agentActivity: setupInput.agentActivity } : {}
|
|
664
790
|
}
|
|
665
791
|
}),
|
|
666
792
|
accountId,
|
|
667
793
|
token,
|
|
668
794
|
tokenFile,
|
|
669
|
-
useEnv:
|
|
795
|
+
useEnv: setupInput.useEnv
|
|
670
796
|
});
|
|
671
797
|
},
|
|
672
798
|
afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
|
|
@@ -678,6 +804,77 @@ const clickClackSetupAdapter = {
|
|
|
678
804
|
});
|
|
679
805
|
}
|
|
680
806
|
};
|
|
807
|
+
const clickClackSetupContract = defineChannelSetupContract({
|
|
808
|
+
fields: {
|
|
809
|
+
code: {
|
|
810
|
+
kind: "string",
|
|
811
|
+
sensitive: true,
|
|
812
|
+
cli: {
|
|
813
|
+
flags: "--code <code>",
|
|
814
|
+
description: "ClickClack one-time setup code or setup URL"
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
token: {
|
|
818
|
+
kind: "string",
|
|
819
|
+
sensitive: true,
|
|
820
|
+
cli: {
|
|
821
|
+
flags: "--token <token>",
|
|
822
|
+
description: "ClickClack bot token"
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
tokenFile: {
|
|
826
|
+
kind: "string",
|
|
827
|
+
sensitive: true,
|
|
828
|
+
cli: {
|
|
829
|
+
flags: "--token-file <path>",
|
|
830
|
+
description: "ClickClack bot token file"
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
baseUrl: {
|
|
834
|
+
kind: "string",
|
|
835
|
+
cli: {
|
|
836
|
+
flags: "--base-url <url>",
|
|
837
|
+
description: "ClickClack API base URL"
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
workspace: {
|
|
841
|
+
kind: "string",
|
|
842
|
+
cli: {
|
|
843
|
+
flags: "--workspace <workspace>",
|
|
844
|
+
description: "ClickClack workspace id, slug, or name"
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
defaultTo: {
|
|
848
|
+
kind: "string",
|
|
849
|
+
cli: {
|
|
850
|
+
flags: "--default-to <target>",
|
|
851
|
+
description: "Default ClickClack target"
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
allowFrom: {
|
|
855
|
+
kind: "string-list",
|
|
856
|
+
cli: {
|
|
857
|
+
flags: "--allow-from <ids>",
|
|
858
|
+
description: "Allowed ClickClack senders"
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
agentActivity: {
|
|
862
|
+
kind: "boolean",
|
|
863
|
+
cli: {
|
|
864
|
+
flags: "--agent-activity",
|
|
865
|
+
description: "Enable ClickClack agent activity"
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
useEnv: {
|
|
869
|
+
kind: "boolean",
|
|
870
|
+
cli: {
|
|
871
|
+
flags: "--use-env",
|
|
872
|
+
description: "Use CLICKCLACK_BOT_TOKEN"
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
legacyAdapter: clickClackSetupAdapter
|
|
877
|
+
});
|
|
681
878
|
//#endregion
|
|
682
879
|
//#region extensions/clickclack/src/setup-verify.ts
|
|
683
880
|
var setup_verify_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -708,7 +905,7 @@ async function checkClickClackSetupConnection(params) {
|
|
|
708
905
|
if (usesUnavailableImplicitEnvToken(account, token)) return { status: "skipped-env-token" };
|
|
709
906
|
if (!account.baseUrl || !account.workspace || !token) return { status: "skipped-unconfigured" };
|
|
710
907
|
const client = createClickClackClient({
|
|
711
|
-
baseUrl: account.
|
|
908
|
+
baseUrl: account.apiEndpoint,
|
|
712
909
|
token
|
|
713
910
|
});
|
|
714
911
|
const me = await client.me();
|
|
@@ -908,4 +1105,4 @@ const clickClackSetupWizard = {
|
|
|
908
1105
|
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
|
|
909
1106
|
};
|
|
910
1107
|
//#endregion
|
|
911
|
-
export {
|
|
1108
|
+
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,
|