@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.
@@ -0,0 +1,336 @@
1
+ import { createRequire } from "node:module";
2
+ import { bufferToBlobPart } from "openclaw/plugin-sdk/blob-runtime";
3
+ import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
4
+ import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
5
+ import path from "node:path";
6
+ //#region extensions/clickclack/src/ws-runtime.ts
7
+ const require = createRequire(import.meta.url);
8
+ const WebSocket = require(path.join(path.dirname(require.resolve("ws/package.json")), "index.js"));
9
+ //#endregion
10
+ //#region extensions/clickclack/src/http-client.ts
11
+ /**
12
+ * Thin ClickClack REST/websocket client used by gateway, resolver, and outbound
13
+ * delivery code.
14
+ */
15
+ /**
16
+ * Serializes optional provenance into the wire fields. Unknown JSON fields
17
+ * are ignored by servers without the provenance columns, so these are safe
18
+ * to send unconditionally when present.
19
+ */
20
+ function provenanceFields(provenance) {
21
+ const fields = {};
22
+ if (provenance?.model?.trim()) fields.author_model = provenance.model.trim();
23
+ if (provenance?.thinking?.trim()) fields.author_thinking = provenance.thinking.trim();
24
+ if (provenance?.runtime?.trim()) fields.author_runtime = provenance.runtime.trim();
25
+ return fields;
26
+ }
27
+ const CLICKCLACK_ERROR_BODY_LIMIT_BYTES = 8192;
28
+ const CLICKCLACK_CORRELATION_ID_MAX_LENGTH = 128;
29
+ const CLICKCLACK_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/u;
30
+ const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
31
+ const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16777216;
32
+ const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
33
+ const CLICKCLACK_EPHEMERAL_REQUEST_TIMEOUT_MS = 15e3;
34
+ const CLICKCLACK_MESSAGE_PAGE_LIMIT = 200;
35
+ const CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT = 8;
36
+ const CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT = 24;
37
+ function compareMessages(left, right) {
38
+ return left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id);
39
+ }
40
+ function keepLatestMessages(messages, limit) {
41
+ return messages.toSorted(compareMessages).slice(-limit);
42
+ }
43
+ var ClickClackHttpError = class extends Error {
44
+ constructor(status, detail, headers) {
45
+ super(`ClickClack ${status}: ${detail}`);
46
+ this.status = status;
47
+ this.headers = headers;
48
+ }
49
+ };
50
+ /** Matches the workspace/name uniqueness error returned by current ClickClack servers. */
51
+ function isClickClackChannelNameConflict(error) {
52
+ if (!(error instanceof ClickClackHttpError) || error.status !== 400 && error.status !== 409) return false;
53
+ const message = error.message.toLowerCase();
54
+ return (message.includes("unique") || message.includes("duplicate")) && message.includes("channel") && /workspace.*name|name.*workspace/u.test(message);
55
+ }
56
+ /** Accepts the same bounded request-correlation shape as the ClickClack API. */
57
+ function normalizeClickClackCorrelationId(value) {
58
+ if (typeof value !== "string") return;
59
+ const normalized = value.trim();
60
+ if (!normalized || normalized.length > CLICKCLACK_CORRELATION_ID_MAX_LENGTH || !CLICKCLACK_CORRELATION_ID_PATTERN.test(normalized)) return;
61
+ return normalized;
62
+ }
63
+ /**
64
+ * Creates a typed client for the ClickClack API using bearer-token auth.
65
+ */
66
+ function createClickClackClient(options) {
67
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
68
+ const fetcher = options.fetch ?? fetch;
69
+ const correlationId = normalizeClickClackCorrelationId(options.correlationId);
70
+ const headers = {
71
+ Authorization: `Bearer ${options.token}`,
72
+ Accept: "application/json"
73
+ };
74
+ async function request(path, init = {}, requestOptions = {}) {
75
+ const requestHeaders = new Headers(init.headers);
76
+ for (const [key, value] of Object.entries(headers)) requestHeaders.set(key, value);
77
+ if (correlationId) requestHeaders.set(CLICKCLACK_CORRELATION_ID_HEADER, correlationId);
78
+ if (init.body && !(init.body instanceof FormData)) requestHeaders.set("Content-Type", "application/json");
79
+ const controller = requestOptions.timeoutMs !== void 0 && !init.signal ? new AbortController() : void 0;
80
+ const timeout = controller ? setTimeout(() => controller.abort(), requestOptions.timeoutMs) : void 0;
81
+ try {
82
+ const response = await fetcher(`${baseUrl}${path}`, {
83
+ ...init,
84
+ ...controller ? { signal: controller.signal } : {},
85
+ headers: requestHeaders
86
+ });
87
+ if (!response.ok) {
88
+ const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
89
+ throw new ClickClackHttpError(response.status, redactToolPayloadText(detail), new Headers(response.headers));
90
+ }
91
+ if (requestOptions.responseMode === "none") {
92
+ try {
93
+ await response.body?.cancel();
94
+ } catch {}
95
+ return;
96
+ }
97
+ return await readProviderJsonResponse(response, "ClickClack response", { maxBytes: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES });
98
+ } finally {
99
+ if (timeout) clearTimeout(timeout);
100
+ }
101
+ }
102
+ async function fetchEventPage(workspaceId, pageOptions = {}) {
103
+ const query = new URLSearchParams({ workspace_id: workspaceId });
104
+ if (pageOptions.afterCursor) query.set("after_cursor", pageOptions.afterCursor);
105
+ if (pageOptions.limit !== void 0) query.set("limit", String(pageOptions.limit));
106
+ if (pageOptions.includeTail) query.set("include_tail", "true");
107
+ const data = await request(`/api/realtime/events?${query.toString()}`);
108
+ return {
109
+ events: data.events,
110
+ ...typeof data.tail_cursor === "string" ? { tailCursor: data.tail_cursor } : {}
111
+ };
112
+ }
113
+ async function createMessage(path, body, opts) {
114
+ return (await request(path, {
115
+ method: "POST",
116
+ body: JSON.stringify({
117
+ body,
118
+ ...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
119
+ ...opts?.nonce ? { nonce: opts.nonce } : {},
120
+ ...provenanceFields(opts?.provenance)
121
+ })
122
+ })).message;
123
+ }
124
+ async function findByNonce(kind, params) {
125
+ const query = new URLSearchParams({
126
+ workspace_id: params.workspaceId,
127
+ nonce: params.nonce
128
+ });
129
+ try {
130
+ return (await request(`/api/${kind}s/by-nonce?${query.toString()}`))[kind];
131
+ } catch (error) {
132
+ if (error instanceof ClickClackHttpError && error.status === 404) {
133
+ if (error.headers.get(`X-ClickClack-${kind}-Nonce`) === "supported") return;
134
+ throw new Error(`ClickClack server does not support durable ${kind} nonce lookup`, { cause: error });
135
+ }
136
+ throw error;
137
+ }
138
+ }
139
+ return {
140
+ me: async () => {
141
+ return (await request("/api/me")).user;
142
+ },
143
+ setBotCommands: async (commands) => {
144
+ return (await request("/api/bots/self/commands", {
145
+ method: "PUT",
146
+ body: JSON.stringify({ commands })
147
+ })).bot_commands;
148
+ },
149
+ workspaces: async () => {
150
+ return (await request("/api/workspaces")).workspaces;
151
+ },
152
+ channels: async (workspaceId) => {
153
+ return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
154
+ },
155
+ createChannel: async (workspaceId, channel) => {
156
+ return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
157
+ method: "POST",
158
+ body: JSON.stringify(channel)
159
+ })).channel;
160
+ },
161
+ updateChannel: async (channelId, patch) => {
162
+ return (await request(`/api/channels/${encodeURIComponent(channelId)}`, {
163
+ method: "PATCH",
164
+ body: JSON.stringify(patch)
165
+ })).channel;
166
+ },
167
+ channelMessages: async (channelId, afterSeq, limit = 20) => {
168
+ return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
169
+ },
170
+ latestChannelMessages: async (channelId, limit = 30) => {
171
+ const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
172
+ let beforeSeq;
173
+ let latest = [];
174
+ let rootPageCount = 0;
175
+ let threadRequestCount = 0;
176
+ let truncated = false;
177
+ while (true) {
178
+ rootPageCount += 1;
179
+ const query = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
180
+ if (beforeSeq !== void 0) query.set("before_seq", String(beforeSeq));
181
+ const page = await request(`/api/channels/${encodeURIComponent(channelId)}/messages?${query.toString()}`);
182
+ for (const root of page.messages) {
183
+ latest = keepLatestMessages([...latest, root], boundedLimit);
184
+ const lastReplyAt = root.thread_state?.last_reply_at;
185
+ const cutoff = latest.length === boundedLimit ? latest[0]?.created_at : void 0;
186
+ if (!root.thread_state?.reply_count || lastReplyAt !== void 0 && cutoff !== void 0 && lastReplyAt < cutoff) continue;
187
+ if (threadRequestCount >= CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT) {
188
+ truncated = true;
189
+ continue;
190
+ }
191
+ threadRequestCount += 1;
192
+ const threadQuery = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
193
+ const thread = await request(`/api/messages/${encodeURIComponent(root.id)}/thread?${threadQuery.toString()}`);
194
+ if (thread.replies.length < root.thread_state.reply_count) {
195
+ truncated = true;
196
+ continue;
197
+ }
198
+ latest = keepLatestMessages([...latest, ...thread.replies], boundedLimit);
199
+ }
200
+ if (!page.has_older) return {
201
+ messages: latest,
202
+ truncated
203
+ };
204
+ if (rootPageCount >= CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT) return {
205
+ messages: latest,
206
+ truncated: true
207
+ };
208
+ 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");
209
+ beforeSeq = page.oldest_seq;
210
+ }
211
+ },
212
+ directMessages: async (conversationId, afterSeq, limit = 20) => {
213
+ return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
214
+ },
215
+ thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
216
+ message: async (messageId) => {
217
+ return (await request(`/api/messages/${encodeURIComponent(messageId)}`)).message;
218
+ },
219
+ findMessageByNonce: async (params) => findByNonce("message", params),
220
+ createChannelMessage: async (channelId, body, opts) => createMessage(`/api/channels/${encodeURIComponent(channelId)}/messages`, body, opts),
221
+ createThreadReply: async (messageId, body, opts) => createMessage(`/api/messages/${encodeURIComponent(messageId)}/thread/replies`, body, {
222
+ nonce: opts?.nonce,
223
+ provenance: opts?.provenance
224
+ }),
225
+ createDirectConversation: async (workspaceId, memberIds) => {
226
+ return (await request("/api/dms", {
227
+ method: "POST",
228
+ body: JSON.stringify({
229
+ workspace_id: workspaceId,
230
+ member_ids: memberIds
231
+ })
232
+ })).conversation;
233
+ },
234
+ createUpload: async (params) => {
235
+ const form = new FormData();
236
+ const bytes = bufferToBlobPart(params.buffer);
237
+ form.append("file", new Blob([bytes], { type: params.contentType }), params.filename);
238
+ const query = new URLSearchParams({ workspace_id: params.workspaceId });
239
+ if (params.nonce) query.set("nonce", params.nonce);
240
+ return (await request(`/api/uploads?${query.toString()}`, {
241
+ method: "POST",
242
+ body: form
243
+ })).upload;
244
+ },
245
+ findUploadByNonce: async (params) => findByNonce("upload", params),
246
+ attachUpload: async (messageId, uploadId) => {
247
+ await request(`/api/messages/${encodeURIComponent(messageId)}/attachments`, {
248
+ method: "POST",
249
+ body: JSON.stringify({ upload_id: uploadId })
250
+ });
251
+ },
252
+ /**
253
+ * POSTs a durable agent activity row (agent_commentary / agent_tool)
254
+ * through the normal message create path. Requires a bot token carrying
255
+ * the agent_activity:write scope on the ClickClack side.
256
+ */
257
+ createActivityMessage: async (params) => {
258
+ if (!params.channelId && !params.conversationId) throw new Error("createActivityMessage requires a channelId or conversationId");
259
+ return (await request(params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
260
+ method: "POST",
261
+ body: JSON.stringify({
262
+ body: params.body,
263
+ kind: params.kind,
264
+ turn_id: params.turnId,
265
+ ...provenanceFields(params.provenance)
266
+ })
267
+ })).message;
268
+ },
269
+ /** PATCHes the body of an existing message (activity row coalescing). */
270
+ updateMessageBody: async (messageId, body) => {
271
+ return (await request(`/api/messages/${encodeURIComponent(messageId)}`, {
272
+ method: "PATCH",
273
+ body: JSON.stringify({ body })
274
+ })).message;
275
+ },
276
+ /**
277
+ * Publishes an ephemeral realtime signal such as native agent progress.
278
+ * These frames are intentionally not persisted as messages.
279
+ */
280
+ publishEphemeral: async (params) => {
281
+ await request("/api/realtime/ephemeral", {
282
+ method: "POST",
283
+ body: JSON.stringify({
284
+ workspace_id: params.workspaceId,
285
+ ...params.channelId ? { channel_id: params.channelId } : {},
286
+ ...params.conversationId ? { direct_conversation_id: params.conversationId } : {},
287
+ type: params.type,
288
+ payload: params.payload ?? {}
289
+ })
290
+ }, {
291
+ timeoutMs: CLICKCLACK_EPHEMERAL_REQUEST_TIMEOUT_MS,
292
+ responseMode: "none"
293
+ });
294
+ },
295
+ createDirectMessage: async (conversationId, body, opts) => createMessage(`/api/dms/${encodeURIComponent(conversationId)}/messages`, body, {
296
+ quotedMessageId: opts?.quotedMessageId,
297
+ nonce: opts?.nonce
298
+ }),
299
+ events: async (workspaceId, afterCursor) => (await fetchEventPage(workspaceId, { afterCursor })).events,
300
+ eventPage: fetchEventPage,
301
+ websocket: (workspaceId, afterCursor) => {
302
+ const url = new URL(`${baseUrl}/api/realtime/ws`);
303
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
304
+ url.searchParams.set("workspace_id", workspaceId);
305
+ if (afterCursor) url.searchParams.set("after_cursor", afterCursor);
306
+ return new WebSocket(url, {
307
+ headers: { Authorization: `Bearer ${options.token}` },
308
+ handshakeTimeout: CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS,
309
+ maxPayload: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES
310
+ });
311
+ }
312
+ };
313
+ }
314
+ //#endregion
315
+ //#region extensions/clickclack/src/resolve.ts
316
+ /**
317
+ * Resolves a workspace slug/name/id from config to a ClickClack workspace id.
318
+ */
319
+ async function resolveWorkspaceId(client, workspace) {
320
+ if (workspace.startsWith("wsp_")) return workspace;
321
+ const found = (await client.workspaces()).find((candidate) => candidate.id === workspace || candidate.slug === workspace || candidate.name === workspace);
322
+ if (!found) throw new Error(`ClickClack workspace not found: ${workspace}`);
323
+ return found.id;
324
+ }
325
+ /**
326
+ * Resolves a channel name/id from config or target input to a ClickClack
327
+ * channel id.
328
+ */
329
+ async function resolveChannelId(client, workspaceId, channel) {
330
+ if (channel.startsWith("chn_")) return channel;
331
+ const found = (await client.channels(workspaceId)).find((candidate) => candidate.id === channel || candidate.name === channel);
332
+ if (!found) throw new Error(`ClickClack channel not found: ${channel}`);
333
+ return found.id;
334
+ }
335
+ //#endregion
336
+ export { isClickClackChannelNameConflict as a, createClickClackClient as i, resolveWorkspaceId as n, normalizeClickClackCorrelationId as o, ClickClackHttpError as r, resolveChannelId as t };
@@ -0,0 +1,28 @@
1
+ import "node:module";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __exportAll = (all, no_symbols) => {
8
+ let target = {};
9
+ for (var name in all) __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true
12
+ });
13
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
+ return target;
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
18
+ key = keys[i];
19
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
20
+ get: ((k) => from[k]).bind(null, key),
21
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
22
+ });
23
+ }
24
+ return to;
25
+ };
26
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
27
+ //#endregion
28
+ export { __reExport as n, __exportAll as t };
@@ -1,5 +1,6 @@
1
- import { b as resolveClickClackAccount, d as isClickClackChannelNameConflict, l as ClickClackHttpError, u as createClickClackClient, v as listClickClackAccountIds } from "./setup-surface-BBl3rvVx.mjs";
2
- import { C as slugifyDiscussionLabel, D as listPendingDiscussionOpens, E as clearPendingDiscussionOpen, O as recordPendingDiscussionOpen, S as resolveDiscussionLabel, T as clearDiscussionBindingGeneration, _ as getClickClackDiscussionBindingStore, b as fallbackDiscussionLabel, c as markClickClackDiscussionChannelIdentityRevoked, d as discussionInfoForBinding, f as normalizedServerBaseUrl, g as bindingMatchesActiveSessionIncarnation, h as attachBindingToCurrentActiveSession, k as reserveDiscussionBindingGeneration, l as markClickClackDiscussionChannelRevoked, o as clearClickClackDiscussionChannelRevoked, p as resolveDiscussionBindingAccount, s as isClickClackDiscussionChannelRevoked, u as discussionAccounts, v as discussionCredentialFingerprint, w as truncateDiscussionDisplayTitle, x as isDiscussionSessionKey, y as discussionExternalRef } from "./channel-CT4CVd4d.mjs";
1
+ import { h as resolveClickClackAccount, p as listClickClackAccountIds } from "./setup-surface-BW1Q06w5.mjs";
2
+ import { C as slugifyDiscussionLabel, D as listPendingDiscussionOpens, E as clearPendingDiscussionOpen, O as recordPendingDiscussionOpen, S as resolveDiscussionLabel, T as clearDiscussionBindingGeneration, _ as getClickClackDiscussionBindingStore, b as fallbackDiscussionLabel, c as markClickClackDiscussionChannelIdentityRevoked, d as discussionInfoForBinding, f as normalizedServerBaseUrl, g as bindingMatchesActiveSessionIncarnation, h as attachBindingToCurrentActiveSession, k as reserveDiscussionBindingGeneration, l as markClickClackDiscussionChannelRevoked, o as clearClickClackDiscussionChannelRevoked, p as resolveDiscussionBindingAccount, s as isClickClackDiscussionChannelRevoked, u as discussionAccounts, v as discussionCredentialFingerprint, w as truncateDiscussionDisplayTitle, x as isDiscussionSessionKey, y as discussionExternalRef } from "./channel-Bcwtohk7.mjs";
3
+ import { a as isClickClackChannelNameConflict, i as createClickClackClient, r as ClickClackHttpError } from "./resolve-BHGLohni.mjs";
3
4
  import "../api.js";
4
5
  import { randomUUID } from "node:crypto";
5
6
  import { buildControlUiSessionPath, registerSessionDiscussionProvider } from "openclaw/plugin-sdk/session-discussion";
@@ -1073,9 +1074,11 @@ function registerClickClackDiscussions(api) {
1073
1074
  api.lifecycle.registerRuntimeLifecycle({
1074
1075
  id: "clickclack-discussions",
1075
1076
  description: "Stops the lifecycle reconciler for managed ClickClack discussions.",
1076
- cleanup: () => {
1077
- unregisterSessionAccess();
1078
- return service.cleanup();
1077
+ cleanup: ({ reason, sessionKey, runId }) => {
1078
+ if (sessionKey === void 0 && runId === void 0 && (reason === "restart" || reason === "disable")) {
1079
+ unregisterSessionAccess();
1080
+ return service.cleanup();
1081
+ }
1079
1082
  }
1080
1083
  });
1081
1084
  }
@@ -1,4 +1,4 @@
1
- import { a as isClickClackSetupLoopbackHost, i as buildClickClackSetupClaimUrl, o as requireClickClackSetupApiBaseUrl } from "./setup-surface-BBl3rvVx.mjs";
1
+ import { a as isClickClackSetupLoopbackHost, i as buildClickClackSetupClaimUrl, o as requireClickClackSetupApiBaseUrl } from "./setup-surface-BW1Q06w5.mjs";
2
2
  import { withTimeout } from "openclaw/plugin-sdk/text-utility-runtime";
3
3
  import { createProviderOperationDeadline, readProviderJsonResponse, readResponseTextLimited, resolveProviderOperationTimeoutMs } from "openclaw/plugin-sdk/provider-http";
4
4
  import { fetchWithSsrFGuard, resolvePinnedHostnameWithPolicy, ssrfPolicyFromHttpBaseUrlAllowedHostname } from "openclaw/plugin-sdk/ssrf-runtime";
@@ -0,0 +1,2 @@
1
+ import { t as clickClackSetupPlugin } from "./setup-plugin-api-C9QcBJuW.mjs";
2
+ export { clickClackSetupPlugin };
@@ -1,4 +1,4 @@
1
- import { g as clickClackMeta, h as clickClackConfigAdapter, n as clickClackSetupContract, p as clickClackConfigSchema, t as clickClackSetupWizard } from "./setup-surface-BBl3rvVx.mjs";
1
+ import { l as clickClackConfigAdapter, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-BW1Q06w5.mjs";
2
2
  //#region extensions/clickclack/src/channel.setup.ts
3
3
  const clickClackSetupPlugin = {
4
4
  id: "clickclack",