@openclaw/clickclack 2026.9.6 → 2026.9.7

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,4 +1,3 @@
1
- import { createRequire } from "node:module";
2
1
  import { createAccountListHelpers, hasConfiguredAccountValue } from "openclaw/plugin-sdk/account-helpers";
3
2
  import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
4
3
  import { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-resolution-runtime";
@@ -12,26 +11,10 @@ import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
12
11
  import { ChannelBotLoopProtectionSchema, buildChannelAllowBotsSchema, buildChannelConfigSchema, buildMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
13
12
  import { z } from "zod";
14
13
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
15
- import { bufferToBlobPart } from "openclaw/plugin-sdk/blob-runtime";
16
- import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
17
- import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
18
- import path from "node:path";
19
14
  import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
20
15
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, applyAccountNameToChannelSection, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, hasConfiguredSecretInput, moveSingleAccountChannelSectionToDefaultAccount, patchScopedAccountConfig, prepareScopedSetupConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
21
16
  import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
22
17
  import net from "node:net";
23
- //#region \0rolldown/runtime.js
24
- var __defProp = Object.defineProperty;
25
- var __exportAll = (all, no_symbols) => {
26
- let target = {};
27
- for (var name in all) __defProp(target, name, {
28
- get: all[name],
29
- enumerable: true
30
- });
31
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
32
- return target;
33
- };
34
- //#endregion
35
18
  //#region extensions/clickclack/src/accounts.ts
36
19
  /**
37
20
  * Resolves ClickClack account configuration from root channel config, named
@@ -59,6 +42,7 @@ function mergeClickClackGroups(...sources) {
59
42
  merged.set(key, {
60
43
  ...merged.get(key),
61
44
  ...value.requireMention !== void 0 ? { requireMention: value.requireMention } : {},
45
+ ...value.requireMentionInBotThreads !== void 0 ? { requireMentionInBotThreads: value.requireMentionInBotThreads } : {},
62
46
  ...value.mentionPatterns !== void 0 ? { mentionPatterns: value.mentionPatterns } : {},
63
47
  ...value.allowBots !== void 0 ? { allowBots: value.allowBots } : {},
64
48
  ...mergedBotLoopProtection ? { botLoopProtection: mergedBotLoopProtection } : {}
@@ -87,6 +71,21 @@ function resolveClickClackAccountConfig(cfg, accountId) {
87
71
  };
88
72
  return mergedWithGroups;
89
73
  }
74
+ function resolveClickClackAccountEndpoints(config) {
75
+ const baseUrl = config.baseUrl?.trim().replace(/\/$/, "") ?? "";
76
+ return {
77
+ baseUrl,
78
+ apiEndpoint: config.apiBaseUrl?.trim().replace(/\/$/, "") || baseUrl
79
+ };
80
+ }
81
+ /** Pins inbound authority to the configured identity that started the transport. */
82
+ function isClickClackAccountCurrent(params) {
83
+ const { cfg, account } = params;
84
+ if (!cfg.channels?.clickclack || cfg.channels.clickclack.enabled === false || !listClickClackAccountIds(cfg).includes(account.accountId)) return false;
85
+ const current = resolveClickClackAccountConfig(cfg, account.accountId);
86
+ const endpoints = resolveClickClackAccountEndpoints(current);
87
+ return current.enabled !== false && Boolean(endpoints.baseUrl && current.workspace?.trim()) && endpoints.baseUrl === account.baseUrl && endpoints.apiEndpoint === account.apiEndpoint && current.workspace?.trim() === account.config.workspace?.trim() && normalizeOptionalString(current.botUserId) === normalizeOptionalString(account.config.botUserId);
88
+ }
90
89
  function resolveClickClackToken(params) {
91
90
  const tokenFile = params.tokenFile?.trim();
92
91
  if (tokenFile) {
@@ -159,7 +158,7 @@ function resolveClickClackAccount(params) {
159
158
  const accountId = normalizeAccountId(params.accountId);
160
159
  const merged = resolveClickClackAccountConfig(params.cfg, accountId);
161
160
  const enabled = params.cfg.channels?.clickclack?.enabled !== false && merged.enabled !== false;
162
- const baseUrl = merged.baseUrl?.trim().replace(/\/$/, "") ?? "";
161
+ const { baseUrl, apiEndpoint } = resolveClickClackAccountEndpoints(merged);
163
162
  const token = resolveClickClackToken({
164
163
  cfg: params.cfg,
165
164
  value: merged.token,
@@ -170,7 +169,6 @@ function resolveClickClackAccount(params) {
170
169
  const workspace = merged.workspace?.trim() ?? "";
171
170
  const discussionsWorkspace = merged.discussions?.workspace?.trim() || workspace;
172
171
  const controlUrlBase = merged.discussions?.controlUrlBase?.trim();
173
- const apiEndpoint = merged.apiBaseUrl?.trim().replace(/\/$/, "") || baseUrl;
174
172
  return {
175
173
  accountId,
176
174
  enabled,
@@ -201,6 +199,7 @@ function resolveClickClackAccount(params) {
201
199
  section: merged.discussions?.section?.trim() || DEFAULT_DISCUSSIONS_SECTION
202
200
  },
203
201
  requireMention: merged.requireMention === true,
202
+ requireMentionInBotThreads: merged.requireMentionInBotThreads,
204
203
  mentionPatterns: merged.mentionPatterns ?? [],
205
204
  allowBots: merged.allowBots ?? false,
206
205
  botLoopProtection: merged.botLoopProtection,
@@ -274,9 +273,11 @@ const ClickClackAccountConfigSchema = z.object({
274
273
  nativeProgress: z.boolean().optional(),
275
274
  commandMenu: z.boolean().optional(),
276
275
  requireMention: z.boolean().optional(),
276
+ requireMentionInBotThreads: z.boolean().optional(),
277
277
  mentionPatterns: z.array(z.string()).optional(),
278
278
  groups: z.record(z.string(), z.object({
279
279
  requireMention: z.boolean().optional(),
280
+ requireMentionInBotThreads: z.boolean().optional(),
280
281
  mentionPatterns: z.array(z.string()).optional(),
281
282
  allowBots: buildChannelAllowBotsSchema({ allowMentions: true }),
282
283
  botLoopProtection: ChannelBotLoopProtectionSchema.optional()
@@ -293,361 +294,24 @@ const ClickClackConfigSchema = buildMultiAccountChannelSchema(ClickClackAccountC
293
294
  * Config schema exported to core so `openclaw doctor` and config validation
294
295
  * understand both default and named ClickClack accounts.
295
296
  */
296
- const clickClackConfigSchema = buildChannelConfigSchema(ClickClackConfigSchema);
297
- //#endregion
298
- //#region extensions/clickclack/src/ws-runtime.ts
299
- const require = createRequire(import.meta.url);
300
- const WebSocket = require(path.join(path.dirname(require.resolve("ws/package.json")), "index.js"));
301
- //#endregion
302
- //#region extensions/clickclack/src/http-client.ts
303
- /**
304
- * Thin ClickClack REST/websocket client used by gateway, resolver, and outbound
305
- * delivery code.
306
- */
307
- /**
308
- * Serializes optional provenance into the wire fields. Unknown JSON fields
309
- * are ignored by servers without the provenance columns, so these are safe
310
- * to send unconditionally when present.
311
- */
312
- function provenanceFields(provenance) {
313
- const fields = {};
314
- if (provenance?.model?.trim()) fields.author_model = provenance.model.trim();
315
- if (provenance?.thinking?.trim()) fields.author_thinking = provenance.thinking.trim();
316
- if (provenance?.runtime?.trim()) fields.author_runtime = provenance.runtime.trim();
317
- return fields;
318
- }
319
- const CLICKCLACK_ERROR_BODY_LIMIT_BYTES = 8192;
320
- const CLICKCLACK_CORRELATION_ID_MAX_LENGTH = 128;
321
- const CLICKCLACK_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/u;
322
- const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
323
- const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16777216;
324
- const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
325
- const CLICKCLACK_EPHEMERAL_REQUEST_TIMEOUT_MS = 15e3;
326
- const CLICKCLACK_MESSAGE_PAGE_LIMIT = 200;
327
- const CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT = 8;
328
- const CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT = 24;
329
- function compareMessages(left, right) {
330
- return left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id);
331
- }
332
- function keepLatestMessages(messages, limit) {
333
- return messages.toSorted(compareMessages).slice(-limit);
334
- }
335
- var ClickClackHttpError = class extends Error {
336
- constructor(status, detail, headers) {
337
- super(`ClickClack ${status}: ${detail}`);
338
- this.status = status;
339
- this.headers = headers;
340
- }
341
- };
342
- /** Matches the workspace/name uniqueness error returned by current ClickClack servers. */
343
- function isClickClackChannelNameConflict(error) {
344
- if (!(error instanceof ClickClackHttpError) || error.status !== 400 && error.status !== 409) return false;
345
- const message = error.message.toLowerCase();
346
- return (message.includes("unique") || message.includes("duplicate")) && message.includes("channel") && /workspace.*name|name.*workspace/u.test(message);
347
- }
348
- /** Accepts the same bounded request-correlation shape as the ClickClack API. */
349
- function normalizeClickClackCorrelationId(value) {
350
- if (typeof value !== "string") return;
351
- const normalized = value.trim();
352
- if (!normalized || normalized.length > CLICKCLACK_CORRELATION_ID_MAX_LENGTH || !CLICKCLACK_CORRELATION_ID_PATTERN.test(normalized)) return;
353
- return normalized;
354
- }
355
- /**
356
- * Creates a typed client for the ClickClack API using bearer-token auth.
357
- */
358
- function createClickClackClient(options) {
359
- const baseUrl = options.baseUrl.replace(/\/$/, "");
360
- const fetcher = options.fetch ?? fetch;
361
- const correlationId = normalizeClickClackCorrelationId(options.correlationId);
362
- const headers = {
363
- Authorization: `Bearer ${options.token}`,
364
- Accept: "application/json"
365
- };
366
- async function request(path, init = {}, requestOptions = {}) {
367
- const requestHeaders = new Headers(init.headers);
368
- for (const [key, value] of Object.entries(headers)) requestHeaders.set(key, value);
369
- if (correlationId) requestHeaders.set(CLICKCLACK_CORRELATION_ID_HEADER, correlationId);
370
- if (init.body && !(init.body instanceof FormData)) requestHeaders.set("Content-Type", "application/json");
371
- const controller = requestOptions.timeoutMs !== void 0 && !init.signal ? new AbortController() : void 0;
372
- const timeout = controller ? setTimeout(() => controller.abort(), requestOptions.timeoutMs) : void 0;
373
- try {
374
- const response = await fetcher(`${baseUrl}${path}`, {
375
- ...init,
376
- ...controller ? { signal: controller.signal } : {},
377
- headers: requestHeaders
378
- });
379
- if (!response.ok) {
380
- const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
381
- throw new ClickClackHttpError(response.status, redactToolPayloadText(detail), new Headers(response.headers));
382
- }
383
- if (requestOptions.responseMode === "none") {
384
- try {
385
- await response.body?.cancel();
386
- } catch {}
387
- return;
388
- }
389
- return await readProviderJsonResponse(response, "ClickClack response", { maxBytes: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES });
390
- } finally {
391
- if (timeout) clearTimeout(timeout);
392
- }
393
- }
394
- async function fetchEventPage(workspaceId, pageOptions = {}) {
395
- const query = new URLSearchParams({ workspace_id: workspaceId });
396
- if (pageOptions.afterCursor) query.set("after_cursor", pageOptions.afterCursor);
397
- if (pageOptions.limit !== void 0) query.set("limit", String(pageOptions.limit));
398
- if (pageOptions.includeTail) query.set("include_tail", "true");
399
- const data = await request(`/api/realtime/events?${query.toString()}`);
400
- return {
401
- events: data.events,
402
- ...typeof data.tail_cursor === "string" ? { tailCursor: data.tail_cursor } : {}
403
- };
297
+ const clickClackConfigSchema = buildChannelConfigSchema(ClickClackConfigSchema, { uiHints: {
298
+ requireMentionInBotThreads: {
299
+ label: "Require Mention in Bot Threads",
300
+ help: "Override mention requirements in threads rooted in this bot's messages. Unset keeps the normal mention policy."
301
+ },
302
+ "groups.*.requireMentionInBotThreads": {
303
+ label: "Require Mention in Bot Threads",
304
+ help: "Override the account policy for threads rooted in this bot's messages in this channel."
305
+ },
306
+ "accounts.*.requireMentionInBotThreads": {
307
+ label: "Require Mention in Bot Threads",
308
+ help: "Override mention requirements in threads rooted in this bot's messages. Unset inherits the channel configuration."
309
+ },
310
+ "accounts.*.groups.*.requireMentionInBotThreads": {
311
+ label: "Require Mention in Bot Threads",
312
+ help: "Override the account policy for threads rooted in this bot's messages in this channel."
404
313
  }
405
- return {
406
- me: async () => {
407
- return (await request("/api/me")).user;
408
- },
409
- setBotCommands: async (commands) => {
410
- return (await request("/api/bots/self/commands", {
411
- method: "PUT",
412
- body: JSON.stringify({ commands })
413
- })).bot_commands;
414
- },
415
- workspaces: async () => {
416
- return (await request("/api/workspaces")).workspaces;
417
- },
418
- channels: async (workspaceId) => {
419
- return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
420
- },
421
- createChannel: async (workspaceId, channel) => {
422
- return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
423
- method: "POST",
424
- body: JSON.stringify(channel)
425
- })).channel;
426
- },
427
- updateChannel: async (channelId, patch) => {
428
- return (await request(`/api/channels/${encodeURIComponent(channelId)}`, {
429
- method: "PATCH",
430
- body: JSON.stringify(patch)
431
- })).channel;
432
- },
433
- channelMessages: async (channelId, afterSeq, limit = 20) => {
434
- return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
435
- },
436
- latestChannelMessages: async (channelId, limit = 30) => {
437
- const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
438
- let beforeSeq;
439
- let latest = [];
440
- let rootPageCount = 0;
441
- let threadRequestCount = 0;
442
- let truncated = false;
443
- while (true) {
444
- rootPageCount += 1;
445
- const query = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
446
- if (beforeSeq !== void 0) query.set("before_seq", String(beforeSeq));
447
- const page = await request(`/api/channels/${encodeURIComponent(channelId)}/messages?${query.toString()}`);
448
- for (const root of page.messages) {
449
- latest = keepLatestMessages([...latest, root], boundedLimit);
450
- const lastReplyAt = root.thread_state?.last_reply_at;
451
- const cutoff = latest.length === boundedLimit ? latest[0]?.created_at : void 0;
452
- if (!root.thread_state?.reply_count || lastReplyAt !== void 0 && cutoff !== void 0 && lastReplyAt < cutoff) continue;
453
- if (threadRequestCount >= CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT) {
454
- truncated = true;
455
- continue;
456
- }
457
- threadRequestCount += 1;
458
- const threadQuery = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
459
- const thread = await request(`/api/messages/${encodeURIComponent(root.id)}/thread?${threadQuery.toString()}`);
460
- if (thread.replies.length < root.thread_state.reply_count) {
461
- truncated = true;
462
- continue;
463
- }
464
- latest = keepLatestMessages([...latest, ...thread.replies], boundedLimit);
465
- }
466
- if (!page.has_older) return {
467
- messages: latest,
468
- truncated
469
- };
470
- if (rootPageCount >= CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT) return {
471
- messages: latest,
472
- truncated: true
473
- };
474
- 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");
475
- beforeSeq = page.oldest_seq;
476
- }
477
- },
478
- directMessages: async (conversationId, afterSeq, limit = 20) => {
479
- return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
480
- },
481
- thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
482
- message: async (messageId) => {
483
- return (await request(`/api/messages/${encodeURIComponent(messageId)}`)).message;
484
- },
485
- findMessageByNonce: async (params) => {
486
- const query = new URLSearchParams({
487
- workspace_id: params.workspaceId,
488
- nonce: params.nonce
489
- });
490
- try {
491
- return (await request(`/api/messages/by-nonce?${query.toString()}`)).message;
492
- } catch (error) {
493
- if (error instanceof ClickClackHttpError && error.status === 404) {
494
- if (error.headers.get("X-ClickClack-Message-Nonce") === "supported") return;
495
- throw new Error("ClickClack server does not support durable message nonce lookup", { cause: error });
496
- }
497
- throw error;
498
- }
499
- },
500
- createChannelMessage: async (channelId, body, opts) => {
501
- return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages`, {
502
- method: "POST",
503
- body: JSON.stringify({
504
- body,
505
- ...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
506
- ...opts?.nonce ? { nonce: opts.nonce } : {},
507
- ...provenanceFields(opts?.provenance)
508
- })
509
- })).message;
510
- },
511
- createThreadReply: async (messageId, body, opts) => {
512
- return (await request(`/api/messages/${encodeURIComponent(messageId)}/thread/replies`, {
513
- method: "POST",
514
- body: JSON.stringify({
515
- body,
516
- ...opts?.nonce ? { nonce: opts.nonce } : {},
517
- ...provenanceFields(opts?.provenance)
518
- })
519
- })).message;
520
- },
521
- createDirectConversation: async (workspaceId, memberIds) => {
522
- return (await request("/api/dms", {
523
- method: "POST",
524
- body: JSON.stringify({
525
- workspace_id: workspaceId,
526
- member_ids: memberIds
527
- })
528
- })).conversation;
529
- },
530
- createUpload: async (params) => {
531
- const form = new FormData();
532
- const bytes = bufferToBlobPart(params.buffer);
533
- form.append("file", new Blob([bytes], { type: params.contentType }), params.filename);
534
- const query = new URLSearchParams({ workspace_id: params.workspaceId });
535
- if (params.nonce) query.set("nonce", params.nonce);
536
- return (await request(`/api/uploads?${query.toString()}`, {
537
- method: "POST",
538
- body: form
539
- })).upload;
540
- },
541
- findUploadByNonce: async (params) => {
542
- const query = new URLSearchParams({
543
- workspace_id: params.workspaceId,
544
- nonce: params.nonce
545
- });
546
- try {
547
- return (await request(`/api/uploads/by-nonce?${query.toString()}`)).upload;
548
- } catch (error) {
549
- if (error instanceof ClickClackHttpError && error.status === 404) {
550
- if (error.headers.get("X-ClickClack-Upload-Nonce") === "supported") return;
551
- throw new Error("ClickClack server does not support durable upload nonce lookup", { cause: error });
552
- }
553
- throw error;
554
- }
555
- },
556
- attachUpload: async (messageId, uploadId) => {
557
- await request(`/api/messages/${encodeURIComponent(messageId)}/attachments`, {
558
- method: "POST",
559
- body: JSON.stringify({ upload_id: uploadId })
560
- });
561
- },
562
- /**
563
- * POSTs a durable agent activity row (agent_commentary / agent_tool)
564
- * through the normal message create path. Requires a bot token carrying
565
- * the agent_activity:write scope on the ClickClack side.
566
- */
567
- createActivityMessage: async (params) => {
568
- if (!params.channelId && !params.conversationId) throw new Error("createActivityMessage requires a channelId or conversationId");
569
- return (await request(params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
570
- method: "POST",
571
- body: JSON.stringify({
572
- body: params.body,
573
- kind: params.kind,
574
- turn_id: params.turnId,
575
- ...provenanceFields(params.provenance)
576
- })
577
- })).message;
578
- },
579
- /** PATCHes the body of an existing message (activity row coalescing). */
580
- updateMessageBody: async (messageId, body) => {
581
- return (await request(`/api/messages/${encodeURIComponent(messageId)}`, {
582
- method: "PATCH",
583
- body: JSON.stringify({ body })
584
- })).message;
585
- },
586
- /**
587
- * Publishes an ephemeral realtime signal such as native agent progress.
588
- * These frames are intentionally not persisted as messages.
589
- */
590
- publishEphemeral: async (params) => {
591
- await request("/api/realtime/ephemeral", {
592
- method: "POST",
593
- body: JSON.stringify({
594
- workspace_id: params.workspaceId,
595
- ...params.channelId ? { channel_id: params.channelId } : {},
596
- ...params.conversationId ? { direct_conversation_id: params.conversationId } : {},
597
- type: params.type,
598
- payload: params.payload ?? {}
599
- })
600
- }, {
601
- timeoutMs: CLICKCLACK_EPHEMERAL_REQUEST_TIMEOUT_MS,
602
- responseMode: "none"
603
- });
604
- },
605
- createDirectMessage: async (conversationId, body, opts) => {
606
- return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages`, {
607
- method: "POST",
608
- body: JSON.stringify({
609
- body,
610
- ...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
611
- ...opts?.nonce ? { nonce: opts.nonce } : {}
612
- })
613
- })).message;
614
- },
615
- events: async (workspaceId, afterCursor) => (await fetchEventPage(workspaceId, { afterCursor })).events,
616
- eventPage: fetchEventPage,
617
- websocket: (workspaceId, afterCursor) => {
618
- const url = new URL(`${baseUrl}/api/realtime/ws`);
619
- url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
620
- url.searchParams.set("workspace_id", workspaceId);
621
- if (afterCursor) url.searchParams.set("after_cursor", afterCursor);
622
- return new WebSocket(url, {
623
- headers: { Authorization: `Bearer ${options.token}` },
624
- handshakeTimeout: CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS,
625
- maxPayload: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES
626
- });
627
- }
628
- };
629
- }
630
- //#endregion
631
- //#region extensions/clickclack/src/resolve.ts
632
- /**
633
- * Resolves a workspace slug/name/id from config to a ClickClack workspace id.
634
- */
635
- async function resolveWorkspaceId(client, workspace) {
636
- if (workspace.startsWith("wsp_")) return workspace;
637
- const found = (await client.workspaces()).find((candidate) => candidate.id === workspace || candidate.slug === workspace || candidate.name === workspace);
638
- if (!found) throw new Error(`ClickClack workspace not found: ${workspace}`);
639
- return found.id;
640
- }
641
- /**
642
- * Resolves a channel name/id from config or target input to a ClickClack
643
- * channel id.
644
- */
645
- async function resolveChannelId(client, workspaceId, channel) {
646
- if (channel.startsWith("chn_")) return channel;
647
- const found = (await client.channels(workspaceId)).find((candidate) => candidate.id === channel || candidate.name === channel);
648
- if (!found) throw new Error(`ClickClack channel not found: ${channel}`);
649
- return found.id;
650
- }
314
+ } });
651
315
  //#endregion
652
316
  //#region extensions/clickclack/src/setup-contract.ts
653
317
  const CLICKCLACK_SETUP_CODE_CLAIM_PATH = "/api/bot-setup-codes/claim";
@@ -735,6 +399,7 @@ function parseClickClackSetupCodeInput(params) {
735
399
  if (!rawCode) throw new Error("ClickClack --code must not be empty.");
736
400
  let code = rawCode;
737
401
  let baseUrl;
402
+ let exactClaimUrl;
738
403
  if (/^[a-z][a-z\d+.-]*:\/\//iu.test(rawCode)) {
739
404
  let setupUrl;
740
405
  try {
@@ -748,7 +413,6 @@ function parseClickClackSetupCodeInput(params) {
748
413
  code = setupUrl.hash.slice(1);
749
414
  if (!code) throw new Error("ClickClack setup URL is missing its #CODE fragment.");
750
415
  setupUrl.hash = "";
751
- let exactClaimUrl;
752
416
  if (setupUrl.pathname.endsWith("/api/bot-setup-codes/claim")) {
753
417
  const exactEndpoint = requireClickClackSetupClaimUrl(setupUrl.toString());
754
418
  baseUrl = exactEndpoint.apiBaseUrl;
@@ -757,22 +421,17 @@ function parseClickClackSetupCodeInput(params) {
757
421
  if (params.baseUrl) {
758
422
  if (requireClickClackSetupCodeBaseUrl(params.baseUrl) !== baseUrl) throw new Error("ClickClack --base-url does not match the server in the setup-code URL.");
759
423
  }
760
- const normalizedCode = normalizeClickClackSetupCode(code);
761
- if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
762
- return {
763
- code: normalizedCode,
764
- baseUrl,
765
- ...exactClaimUrl ? { exactClaimUrl } : {}
766
- };
424
+ } else {
425
+ code = code.startsWith("#") ? code.slice(1) : code;
426
+ if (!params.baseUrl) throw new Error("A bare ClickClack setup code requires --base-url.");
427
+ baseUrl = requireClickClackSetupCodeBaseUrl(params.baseUrl);
767
428
  }
768
- code = code.startsWith("#") ? code.slice(1) : code;
769
- if (!params.baseUrl) throw new Error("A bare ClickClack setup code requires --base-url.");
770
- baseUrl = requireClickClackSetupCodeBaseUrl(params.baseUrl);
771
429
  const normalizedCode = normalizeClickClackSetupCode(code);
772
430
  if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
773
431
  return {
774
432
  code: normalizedCode,
775
- baseUrl
433
+ baseUrl,
434
+ ...exactClaimUrl ? { exactClaimUrl } : {}
776
435
  };
777
436
  }
778
437
  function formatClickClackSetupCodeClaimError(error) {
@@ -819,7 +478,7 @@ const clickClackSetupAdapter = {
819
478
  const setupInput = input;
820
479
  if (!setupInput.code?.trim()) return setupInput;
821
480
  if (setupInput.token?.trim() || setupInput.tokenFile?.trim() || setupInput.useEnv) throw new Error(SETUP_CODE_CONFLICT_ERROR);
822
- let setup = parseClickClackSetupCodeInput({
481
+ const setup = parseClickClackSetupCodeInput({
823
482
  code: setupInput.code,
824
483
  baseUrl: setupInput.baseUrl
825
484
  });
@@ -827,7 +486,7 @@ const clickClackSetupAdapter = {
827
486
  const claimUrl = setup.exactClaimUrl && !privateApiBaseUrl ? setup.exactClaimUrl : buildClickClackSetupClaimUrl(privateApiBaseUrl ?? setup.baseUrl);
828
487
  let claim;
829
488
  try {
830
- const { claimClickClackSetupCode } = await import("./setup-claim-t5SbtvZy.mjs");
489
+ const { claimClickClackSetupCode } = await import("./setup-claim-Cx8knvXs.mjs");
831
490
  claim = await claimClickClackSetupCode({
832
491
  claimUrl,
833
492
  code: setup.code,
@@ -836,14 +495,10 @@ const clickClackSetupAdapter = {
836
495
  } catch (error) {
837
496
  throw formatClickClackSetupCodeClaimError(error);
838
497
  }
839
- setup = {
840
- ...setup,
841
- baseUrl: claim.api_base_url ?? setup.baseUrl
842
- };
843
498
  const { code: _code, tokenFile: _tokenFile, useEnv: _useEnv, ...remainingInput } = setupInput;
844
499
  return {
845
500
  ...remainingInput,
846
- baseUrl: setup.baseUrl,
501
+ baseUrl: claim.api_base_url ?? setup.baseUrl,
847
502
  token: claim.token,
848
503
  workspace: claim.workspace.id,
849
504
  ...claim.defaults.defaultTo !== void 0 ? { defaultTo: claim.defaults.defaultTo } : {},
@@ -913,7 +568,7 @@ const clickClackSetupAdapter = {
913
568
  });
914
569
  },
915
570
  afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
916
- const { verifyClickClackAccountAfterSetup } = await Promise.resolve().then(() => setup_verify_exports);
571
+ const { verifyClickClackAccountAfterSetup } = await import("./setup-verify-Msgd70_M.mjs");
917
572
  await verifyClickClackAccountAfterSetup({
918
573
  cfg,
919
574
  accountId,
@@ -994,115 +649,6 @@ const clickClackSetupContract = defineChannelSetupContract({
994
649
  legacyAdapter: clickClackSetupAdapter
995
650
  });
996
651
  //#endregion
997
- //#region extensions/clickclack/src/setup-verify.ts
998
- var setup_verify_exports = /* @__PURE__ */ __exportAll({
999
- checkClickClackSetupConnection: () => checkClickClackSetupConnection,
1000
- verifyClickClackAccountAfterSetup: () => verifyClickClackAccountAfterSetup
1001
- });
1002
- const GATEWAY_RUNNING_MESSAGE = "OpenClaw is running — ClickClack will connect automatically.";
1003
- const GATEWAY_NOT_RUNNING_MESSAGE = "Start OpenClaw to connect: openclaw gateway";
1004
- const GATEWAY_UNKNOWN_MESSAGE = "If OpenClaw is running it connects automatically; otherwise start it with: openclaw gateway";
1005
- function isHttpStatus(error, status) {
1006
- return typeof error === "object" && error !== null && "status" in error && error.status === status;
1007
- }
1008
- function isWorkspaceNotFound(error) {
1009
- return error instanceof Error && error.message.startsWith("ClickClack workspace not found:");
1010
- }
1011
- function usesUnavailableImplicitEnvToken(account, tokenOverride) {
1012
- return account.accountId === DEFAULT_ACCOUNT_ID && Boolean(account.baseUrl && account.workspace) && !tokenOverride && !account.token && !hasConfiguredSecretInput(account.config.token) && !account.config.tokenFile?.trim();
1013
- }
1014
- async function checkClickClackSetupConnection(params) {
1015
- let workspaceInput = "";
1016
- try {
1017
- const account = resolveClickClackAccount({
1018
- cfg: params.cfg,
1019
- accountId: params.accountId
1020
- });
1021
- workspaceInput = account.workspace;
1022
- const token = params.token?.trim() || account.token;
1023
- if (usesUnavailableImplicitEnvToken(account, token)) return { status: "skipped-env-token" };
1024
- if (!account.baseUrl || !account.workspace || !token) return { status: "skipped-unconfigured" };
1025
- const client = createClickClackClient({
1026
- baseUrl: account.apiEndpoint,
1027
- token
1028
- });
1029
- const me = await client.me();
1030
- const workspaceId = await resolveWorkspaceId(client, account.workspace);
1031
- const workspace = (await client.workspaces()).find((candidate) => candidate.id === workspaceId);
1032
- if (!workspace) throw new Error(`ClickClack workspace not found: ${account.workspace}`);
1033
- return {
1034
- status: "connected",
1035
- handle: me.handle,
1036
- workspaceName: workspace.name
1037
- };
1038
- } catch (error) {
1039
- if (isHttpStatus(error, 401)) return { status: "invalid-token" };
1040
- if (isWorkspaceNotFound(error)) return {
1041
- status: "workspace-not-found",
1042
- workspace: workspaceInput
1043
- };
1044
- return {
1045
- status: "failed",
1046
- error: formatErrorMessage(error)
1047
- };
1048
- }
1049
- }
1050
- function isGatewayNotRunningError(error) {
1051
- if (typeof error === "object" && error !== null && "name" in error && "kind" in error && "code" in error && error.name === "GatewayTransportError" && error.kind === "closed" && error.code === 1006) return true;
1052
- const message = formatErrorMessage(error).toLowerCase();
1053
- return message.includes("econnrefused") || message.includes("connection refused");
1054
- }
1055
- async function probeClickClackGatewayStatus() {
1056
- try {
1057
- const { callGatewayFromCli } = await import("openclaw/plugin-sdk/gateway-runtime");
1058
- await callGatewayFromCli("health", {
1059
- timeout: "1000",
1060
- json: true
1061
- }, void 0, {
1062
- expectFinal: false,
1063
- progress: false
1064
- });
1065
- return "running";
1066
- } catch (error) {
1067
- return isGatewayNotRunningError(error) ? "not-running" : "unavailable";
1068
- }
1069
- }
1070
- function formatClickClackConnectionLog(result) {
1071
- switch (result.status) {
1072
- case "connected": return `Connected as @${result.handle} — workspace ${result.workspaceName} resolved.`;
1073
- case "invalid-token": return "ClickClack rejected the bot token (401). Copy a current token and rerun setup.";
1074
- case "workspace-not-found": return `Workspace "${result.workspace}" was not found. Check the id, slug, or name, list available workspaces, and rerun setup.`;
1075
- case "failed": return `Connection check failed: ${result.error}. Setup was saved; fix the connection and rerun setup.`;
1076
- case "skipped-env-token": return "Token comes from CLICKCLACK_BOT_TOKEN; verification skipped.";
1077
- case "skipped-unconfigured": return;
1078
- }
1079
- }
1080
- function formatClickClackGatewayLog(status) {
1081
- switch (status) {
1082
- case "running": return GATEWAY_RUNNING_MESSAGE;
1083
- case "not-running": return GATEWAY_NOT_RUNNING_MESSAGE;
1084
- case "unavailable": return GATEWAY_UNKNOWN_MESSAGE;
1085
- }
1086
- return GATEWAY_UNKNOWN_MESSAGE;
1087
- }
1088
- async function verifyClickClackAccountAfterSetup(params) {
1089
- try {
1090
- const message = formatClickClackConnectionLog(await checkClickClackSetupConnection({
1091
- cfg: params.cfg,
1092
- accountId: params.accountId
1093
- }));
1094
- if (message) params.runtime.log(message);
1095
- } catch (error) {
1096
- params.runtime.log(`Connection check failed: ${formatErrorMessage(error)}. Setup was saved; fix the connection and rerun setup.`);
1097
- }
1098
- try {
1099
- const status = await probeClickClackGatewayStatus();
1100
- params.runtime.log(formatClickClackGatewayLog(status));
1101
- } catch {
1102
- params.runtime.log(GATEWAY_UNKNOWN_MESSAGE);
1103
- }
1104
- }
1105
- //#endregion
1106
652
  //#region extensions/clickclack/src/setup-surface.ts
1107
653
  const t = createSetupTranslator();
1108
654
  const channel = "clickclack";
@@ -1204,6 +750,7 @@ const clickClackSetupWizard = {
1204
750
  })
1205
751
  }],
1206
752
  finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
753
+ const { checkClickClackSetupConnection } = await import("./setup-verify-Msgd70_M.mjs");
1207
754
  const result = await checkClickClackSetupConnection({
1208
755
  cfg,
1209
756
  accountId,
@@ -1223,4 +770,4 @@ const clickClackSetupWizard = {
1223
770
  disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
1224
771
  };
1225
772
  //#endregion
1226
- export { DEFAULT_ACCOUNT_ID$2 as _, isClickClackSetupLoopbackHost as a, resolveClickClackAccount as b, resolveWorkspaceId as c, isClickClackChannelNameConflict as d, normalizeClickClackCorrelationId as f, clickClackMeta as g, clickClackConfigAdapter as h, buildClickClackSetupClaimUrl as i, ClickClackHttpError as l, CLICKCLACK_CHANNEL_ID as m, clickClackSetupContract as n, requireClickClackSetupApiBaseUrl as o, clickClackConfigSchema as p, CLICKCLACK_SETUP_CODE_CLAIM_PATH as r, resolveChannelId as s, clickClackSetupWizard as t, createClickClackClient as u, listClickClackAccountIds as v, resolveDefaultClickClackAccountId as x, listEnabledClickClackAccounts as y };
773
+ export { resolveDefaultClickClackAccountId as _, isClickClackSetupLoopbackHost as a, CLICKCLACK_CHANNEL_ID as c, DEFAULT_ACCOUNT_ID$2 as d, isClickClackAccountCurrent as f, resolveClickClackAccountConfig as g, resolveClickClackAccount as h, buildClickClackSetupClaimUrl as i, clickClackConfigAdapter as l, listEnabledClickClackAccounts as m, clickClackSetupContract as n, requireClickClackSetupApiBaseUrl as o, listClickClackAccountIds as p, CLICKCLACK_SETUP_CODE_CLAIM_PATH as r, clickClackConfigSchema as s, clickClackSetupWizard as t, clickClackMeta as u };