@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.
@@ -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 baseUrl = normalizeClickClackBaseUrl(input.baseUrl);
557
- if (input.baseUrl && !baseUrl) return INVALID_BASE_URL_ERROR;
558
- if (!input.useEnv) return null;
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 (!input.workspace?.trim() && !existing.workspace?.trim()) return REQUIRED_INPUT_ERROR;
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 existing = input.useEnv ? resolveClickClackAccountConfig(cfg, accountId) : void 0;
569
- const baseUrl = normalizeClickClackBaseUrl(input.baseUrl ?? existing?.baseUrl);
570
- const workspace = input.workspace?.trim() || existing?.workspace?.trim();
571
- const tokenFile = input.tokenFile?.trim();
572
- const token = input.token?.trim();
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: input.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: input.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.baseUrl,
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 { createClickClackClient as a, CLICKCLACK_CHANNEL_ID as c, DEFAULT_ACCOUNT_ID$2 as d, listClickClackAccountIds as f, resolveDefaultClickClackAccountId as h, resolveWorkspaceId as i, clickClackConfigAdapter as l, resolveClickClackAccount as m, clickClackSetupAdapter as n, normalizeClickClackCorrelationId as o, listEnabledClickClackAccounts as p, resolveChannelId as r, clickClackConfigSchema as s, clickClackSetupWizard as t, clickClackMeta as u };
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=1048576] The maximum number of
46
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
47
47
  * buffered data chunks
48
- * @param {Number} [options.maxFragments=131072] The maximum number of message
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: 1024 * 1024,
73
- maxFragments: 128 * 1024,
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=1048576] The maximum number of
649
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
650
650
  * buffered data chunks
651
- * @param {Number} [options.maxFragments=131072] The maximum number of message
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: 1024 * 1024,
674
- maxFragments: 128 * 1024,
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.0",
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
  }