@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.
@@ -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 { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, applyAccountNameToChannelSection, applySetupAccountConfigPatch, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, hasConfiguredSecretInput, migrateBaseNameToDefaultAccount, moveSingleAccountChannelSectionToDefaultAccount, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
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 { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resolveDefaultClickClackAccountId } = createAccountListHelpers("clickclack", {
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 = resolveMergedAccountConfig({
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
- setupUrl.search = "";
496
- baseUrl = requireClickClackSetupCodeBaseUrl(setupUrl.toString());
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
- } else {
501
- code = code.startsWith("#") ? code.slice(1) : code;
502
- if (!params.baseUrl) throw new Error("A bare ClickClack setup code requires --base-url.");
503
- baseUrl = requireClickClackSetupCodeBaseUrl(params.baseUrl);
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 applySetupAccountConfigPatch({
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 clearClickClackSetupConfigFields({
577
- cfg: applyClickClackSetupConfigPatch({
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
- fields: fieldsToClear
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
- if (!input.code?.trim()) return input;
590
- if (input.token?.trim() || input.tokenFile?.trim() || input.useEnv) throw new Error(SETUP_CODE_CONFLICT_ERROR);
591
- const setup = parseClickClackSetupCodeInput({
592
- code: input.code,
593
- baseUrl: input.baseUrl
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-BUWvZjtu.js");
598
- claim = await claimClickClackSetupCode(setup);
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
- const { code: _code, tokenFile: _tokenFile, useEnv: _useEnv, ...remainingInput } = input;
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 baseUrl = normalizeClickClackBaseUrl(input.baseUrl);
637
- if (input.baseUrl && !baseUrl) return INVALID_BASE_URL_ERROR;
638
- if (!input.useEnv) return null;
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 (!input.workspace?.trim() && !existing.workspace?.trim()) return REQUIRED_INPUT_ERROR;
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 existing = input.useEnv ? resolveClickClackAccountConfig(cfg, accountId) : void 0;
649
- const baseUrl = normalizeClickClackBaseUrl(input.baseUrl ?? existing?.baseUrl);
650
- const workspace = input.workspace?.trim() || existing?.workspace?.trim();
651
- const tokenFile = input.tokenFile?.trim();
652
- const token = input.token?.trim();
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: input.name,
783
+ name: setupInput.name,
658
784
  patch: {
659
785
  ...baseUrl ? { baseUrl } : {},
660
786
  ...workspace ? { workspace } : {},
661
- ...input.defaultTo?.trim() ? { defaultTo: input.defaultTo.trim() } : {},
662
- ...input.allowFrom ? { allowFrom: [...input.allowFrom] } : {},
663
- ...input.agentActivity !== void 0 ? { agentActivity: input.agentActivity } : {}
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: input.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.baseUrl,
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 { 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 };
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=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,