@openclaw/clickclack 2026.7.2-beta.1 → 2026.7.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/api.js +2 -1
- package/dist/{channel-Ds6d_3nx.js → channel-BnREQIP8.js} +111 -422
- package/dist/channel-plugin-api.js +1 -1
- package/dist/runtime-api.js +2 -1
- package/dist/secret-contract-BKkzzjBA.js +35 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-plugin-api.js +18 -0
- package/dist/setup-surface-B346XaAu.js +828 -0
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +12 -0
- package/package.json +14 -6
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
import { createAccountListHelpers, hasConfiguredAccountValue } from "openclaw/plugin-sdk/account-helpers";
|
|
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
|
+
import { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-resolution-runtime";
|
|
5
|
+
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
|
6
|
+
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
|
7
|
+
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
8
|
+
import { buildSecretInputSchema, normalizeResolvedSecretInputString, normalizeSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
9
|
+
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
10
|
+
import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
11
|
+
import { buildChannelConfigSchema, buildMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
14
|
+
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
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";
|
|
17
|
+
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
|
|
18
|
+
//#region \0rolldown/runtime.js
|
|
19
|
+
var __defProp = Object.defineProperty;
|
|
20
|
+
var __exportAll = (all, no_symbols) => {
|
|
21
|
+
let target = {};
|
|
22
|
+
for (var name in all) __defProp(target, name, {
|
|
23
|
+
get: all[name],
|
|
24
|
+
enumerable: true
|
|
25
|
+
});
|
|
26
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region extensions/clickclack/src/accounts.ts
|
|
31
|
+
/**
|
|
32
|
+
* Resolves ClickClack account configuration from root channel config, named
|
|
33
|
+
* account overrides, and secret-provider references.
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_RECONNECT_MS = 1500;
|
|
36
|
+
const MIN_RECONNECT_MS = 100;
|
|
37
|
+
const MAX_RECONNECT_MS = 6e4;
|
|
38
|
+
const { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resolveDefaultClickClackAccountId } = createAccountListHelpers("clickclack", {
|
|
39
|
+
normalizeAccountId,
|
|
40
|
+
hasImplicitDefaultAccount: (cfg) => {
|
|
41
|
+
const channel = cfg.channels?.clickclack;
|
|
42
|
+
return Boolean(channel?.baseUrl?.trim() && (hasConfiguredAccountValue(channel.token) || Boolean(channel.tokenFile?.trim()) || Boolean(process.env.CLICKCLACK_BOT_TOKEN?.trim())) && channel.workspace?.trim());
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
function resolveClickClackAccountConfig(cfg, accountId) {
|
|
46
|
+
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
|
+
});
|
|
54
|
+
const account = resolveNormalizedAccountEntry(channel?.accounts, accountId, normalizeAccountId);
|
|
55
|
+
const accountTokenFile = account?.tokenFile?.trim();
|
|
56
|
+
if (accountTokenFile) return {
|
|
57
|
+
...merged,
|
|
58
|
+
token: account?.token,
|
|
59
|
+
tokenFile: accountTokenFile
|
|
60
|
+
};
|
|
61
|
+
if (hasConfiguredAccountValue(account?.token)) return {
|
|
62
|
+
...merged,
|
|
63
|
+
token: account?.token,
|
|
64
|
+
tokenFile: void 0
|
|
65
|
+
};
|
|
66
|
+
return merged;
|
|
67
|
+
}
|
|
68
|
+
function resolveClickClackToken(params) {
|
|
69
|
+
const tokenFile = params.tokenFile?.trim();
|
|
70
|
+
if (tokenFile) return tryReadSecretFileSync(tokenFile, params.accountId === DEFAULT_ACCOUNT_ID$2 ? "channels.clickclack.tokenFile" : `channels.clickclack.accounts.${params.accountId}.tokenFile`, { rejectSymlink: true }) ?? "";
|
|
71
|
+
const resolved = resolveSecretInputString({
|
|
72
|
+
value: params.value,
|
|
73
|
+
path: params.accountId === DEFAULT_ACCOUNT_ID$2 ? "channels.clickclack.token" : `channels.clickclack.accounts.${params.accountId}.token`,
|
|
74
|
+
defaults: params.cfg.secrets?.defaults,
|
|
75
|
+
mode: "inspect"
|
|
76
|
+
});
|
|
77
|
+
if (resolved.status !== "available") {
|
|
78
|
+
if (resolved.status === "missing" && params.accountId === DEFAULT_ACCOUNT_ID$2) return normalizeSecretInputString((params.env ?? process.env).CLICKCLACK_BOT_TOKEN) ?? "";
|
|
79
|
+
if (resolved.status === "configured_unavailable" && resolved.ref.source === "env") {
|
|
80
|
+
const providerConfig = params.cfg.secrets?.providers?.[resolved.ref.provider];
|
|
81
|
+
if (providerConfig) {
|
|
82
|
+
if (providerConfig.source !== "env") throw new Error(`Secret provider "${resolved.ref.provider}" has source "${providerConfig.source}" but ref requests "env".`);
|
|
83
|
+
if (providerConfig.allowlist && !providerConfig.allowlist.includes(resolved.ref.id)) throw new Error(`Environment variable "${resolved.ref.id}" is not allowlisted in secrets.providers.${resolved.ref.provider}.allowlist.`);
|
|
84
|
+
} else if (resolved.ref.provider !== resolveDefaultSecretProviderAlias({ secrets: params.cfg.secrets }, "env")) throw new Error(`Secret provider "${resolved.ref.provider}" is not configured (ref: env:${resolved.ref.provider}:${resolved.ref.id}).`);
|
|
85
|
+
return normalizeSecretInputString((params.env ?? process.env)[resolved.ref.id]) ?? "";
|
|
86
|
+
}
|
|
87
|
+
return "";
|
|
88
|
+
}
|
|
89
|
+
return normalizeResolvedSecretInputString({
|
|
90
|
+
value: resolved.value,
|
|
91
|
+
path: "channels.clickclack.token"
|
|
92
|
+
}) ?? "";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Builds the normalized account snapshot used by gateway, outbound delivery,
|
|
96
|
+
* status reporting, and channel routing.
|
|
97
|
+
*/
|
|
98
|
+
function resolveClickClackAccount(params) {
|
|
99
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
100
|
+
const merged = resolveClickClackAccountConfig(params.cfg, accountId);
|
|
101
|
+
const enabled = params.cfg.channels?.clickclack?.enabled !== false && merged.enabled !== false;
|
|
102
|
+
const baseUrl = merged.baseUrl?.trim().replace(/\/$/, "") ?? "";
|
|
103
|
+
const token = resolveClickClackToken({
|
|
104
|
+
cfg: params.cfg,
|
|
105
|
+
value: merged.token,
|
|
106
|
+
tokenFile: merged.tokenFile,
|
|
107
|
+
accountId,
|
|
108
|
+
env: params.env
|
|
109
|
+
});
|
|
110
|
+
const workspace = merged.workspace?.trim() ?? "";
|
|
111
|
+
return {
|
|
112
|
+
accountId,
|
|
113
|
+
enabled,
|
|
114
|
+
configured: Boolean(baseUrl && token && workspace),
|
|
115
|
+
name: normalizeOptionalString(merged.name),
|
|
116
|
+
baseUrl,
|
|
117
|
+
token,
|
|
118
|
+
workspace,
|
|
119
|
+
botUserId: normalizeOptionalString(merged.botUserId),
|
|
120
|
+
agentId: normalizeOptionalString(merged.agentId),
|
|
121
|
+
replyMode: merged.replyMode === "model" ? "model" : "agent",
|
|
122
|
+
model: normalizeOptionalString(merged.model),
|
|
123
|
+
systemPrompt: normalizeOptionalString(merged.systemPrompt),
|
|
124
|
+
timeoutSeconds: merged.timeoutSeconds,
|
|
125
|
+
toolsAllow: merged.toolsAllow,
|
|
126
|
+
defaultTo: merged.defaultTo?.trim() || "channel:general",
|
|
127
|
+
allowFrom: merged.allowFrom ?? ["*"],
|
|
128
|
+
reconnectMs: resolveIntegerOption(merged.reconnectMs, DEFAULT_RECONNECT_MS, {
|
|
129
|
+
min: MIN_RECONNECT_MS,
|
|
130
|
+
max: MAX_RECONNECT_MS
|
|
131
|
+
}),
|
|
132
|
+
agentActivity: merged.agentActivity === true,
|
|
133
|
+
commandMenu: merged.commandMenu !== false,
|
|
134
|
+
config: {
|
|
135
|
+
...merged,
|
|
136
|
+
allowFrom: merged.allowFrom ?? ["*"]
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns all enabled accounts, including the implicit default account when
|
|
142
|
+
* legacy top-level ClickClack config is present.
|
|
143
|
+
*/
|
|
144
|
+
function listEnabledClickClackAccounts(cfg) {
|
|
145
|
+
return listClickClackAccountIds(cfg).map((accountId) => resolveClickClackAccount({
|
|
146
|
+
cfg,
|
|
147
|
+
accountId
|
|
148
|
+
})).filter((account) => account.enabled);
|
|
149
|
+
}
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region extensions/clickclack/src/channel-config.ts
|
|
152
|
+
const CLICKCLACK_CHANNEL_ID = "clickclack";
|
|
153
|
+
const clickClackMeta = { ...getChatChannelMeta(CLICKCLACK_CHANNEL_ID) };
|
|
154
|
+
const clickClackConfigAdapter = {
|
|
155
|
+
listAccountIds: (cfg) => listClickClackAccountIds(cfg),
|
|
156
|
+
resolveAccount: (cfg, accountId) => resolveClickClackAccount({
|
|
157
|
+
cfg,
|
|
158
|
+
accountId
|
|
159
|
+
}),
|
|
160
|
+
defaultAccountId: (cfg) => resolveDefaultClickClackAccountId(cfg),
|
|
161
|
+
isConfigured: (account) => account.configured,
|
|
162
|
+
resolveAllowFrom: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
163
|
+
cfg,
|
|
164
|
+
accountId
|
|
165
|
+
}).allowFrom,
|
|
166
|
+
resolveDefaultTo: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
167
|
+
cfg,
|
|
168
|
+
accountId
|
|
169
|
+
}).defaultTo
|
|
170
|
+
};
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region extensions/clickclack/src/config-schema.ts
|
|
173
|
+
/**
|
|
174
|
+
* Zod-backed config schema for ClickClack channel accounts.
|
|
175
|
+
*/
|
|
176
|
+
const ClickClackAccountConfigSchema = z.object({
|
|
177
|
+
name: z.string().optional(),
|
|
178
|
+
enabled: z.boolean().optional(),
|
|
179
|
+
baseUrl: z.string().url().optional(),
|
|
180
|
+
token: buildSecretInputSchema().optional(),
|
|
181
|
+
tokenFile: z.string().optional(),
|
|
182
|
+
workspace: z.string().optional(),
|
|
183
|
+
botUserId: z.string().optional(),
|
|
184
|
+
agentId: z.string().optional(),
|
|
185
|
+
replyMode: z.enum(["agent", "model"]).optional(),
|
|
186
|
+
model: z.string().optional(),
|
|
187
|
+
systemPrompt: z.string().optional(),
|
|
188
|
+
timeoutSeconds: z.number().int().min(1).max(3600).optional(),
|
|
189
|
+
toolsAllow: z.array(z.string()).optional(),
|
|
190
|
+
defaultTo: z.string().optional(),
|
|
191
|
+
allowFrom: z.array(z.string()).optional(),
|
|
192
|
+
reconnectMs: z.number().int().min(100).max(6e4).optional(),
|
|
193
|
+
agentActivity: z.boolean().optional(),
|
|
194
|
+
commandMenu: z.boolean().optional()
|
|
195
|
+
}).strict();
|
|
196
|
+
/**
|
|
197
|
+
* Config schema exported to core so `openclaw doctor` and config validation
|
|
198
|
+
* understand both default and named ClickClack accounts.
|
|
199
|
+
*/
|
|
200
|
+
const clickClackConfigSchema = buildChannelConfigSchema(buildMultiAccountChannelSchema(ClickClackAccountConfigSchema, { accountSchema: ClickClackAccountConfigSchema.partial() }));
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region extensions/clickclack/src/http-client.ts
|
|
203
|
+
/**
|
|
204
|
+
* Thin ClickClack REST/websocket client used by gateway, resolver, and outbound
|
|
205
|
+
* delivery code.
|
|
206
|
+
*/
|
|
207
|
+
/**
|
|
208
|
+
* Serializes optional provenance into the wire fields. Unknown JSON fields
|
|
209
|
+
* are ignored by servers without the provenance columns, so these are safe
|
|
210
|
+
* to send unconditionally when present.
|
|
211
|
+
*/
|
|
212
|
+
function provenanceFields(provenance) {
|
|
213
|
+
const fields = {};
|
|
214
|
+
if (provenance?.model?.trim()) fields.author_model = provenance.model.trim();
|
|
215
|
+
if (provenance?.thinking?.trim()) fields.author_thinking = provenance.thinking.trim();
|
|
216
|
+
if (provenance?.runtime?.trim()) fields.author_runtime = provenance.runtime.trim();
|
|
217
|
+
return fields;
|
|
218
|
+
}
|
|
219
|
+
const CLICKCLACK_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
|
220
|
+
const CLICKCLACK_CORRELATION_ID_MAX_LENGTH = 128;
|
|
221
|
+
const CLICKCLACK_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/u;
|
|
222
|
+
const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
223
|
+
const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
224
|
+
const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
|
|
225
|
+
var ClickClackHttpError = class extends Error {
|
|
226
|
+
constructor(status, detail, headers) {
|
|
227
|
+
super(`ClickClack ${status}: ${detail}`);
|
|
228
|
+
this.status = status;
|
|
229
|
+
this.headers = headers;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/** Accepts the same bounded request-correlation shape as the ClickClack API. */
|
|
233
|
+
function normalizeClickClackCorrelationId(value) {
|
|
234
|
+
if (typeof value !== "string") return;
|
|
235
|
+
const normalized = value.trim();
|
|
236
|
+
if (!normalized || normalized.length > CLICKCLACK_CORRELATION_ID_MAX_LENGTH || !CLICKCLACK_CORRELATION_ID_PATTERN.test(normalized)) return;
|
|
237
|
+
return normalized;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Creates a typed client for the ClickClack API using bearer-token auth.
|
|
241
|
+
*/
|
|
242
|
+
function createClickClackClient(options) {
|
|
243
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
244
|
+
const fetcher = options.fetch ?? fetch;
|
|
245
|
+
const correlationId = normalizeClickClackCorrelationId(options.correlationId);
|
|
246
|
+
const headers = {
|
|
247
|
+
Authorization: `Bearer ${options.token}`,
|
|
248
|
+
Accept: "application/json"
|
|
249
|
+
};
|
|
250
|
+
async function request(path, init = {}) {
|
|
251
|
+
const requestHeaders = new Headers(init.headers);
|
|
252
|
+
for (const [key, value] of Object.entries(headers)) requestHeaders.set(key, value);
|
|
253
|
+
if (correlationId) requestHeaders.set(CLICKCLACK_CORRELATION_ID_HEADER, correlationId);
|
|
254
|
+
if (init.body && !(init.body instanceof FormData)) requestHeaders.set("Content-Type", "application/json");
|
|
255
|
+
const response = await fetcher(`${baseUrl}${path}`, {
|
|
256
|
+
...init,
|
|
257
|
+
headers: requestHeaders
|
|
258
|
+
});
|
|
259
|
+
if (!response.ok) {
|
|
260
|
+
const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
|
|
261
|
+
throw new ClickClackHttpError(response.status, detail, new Headers(response.headers));
|
|
262
|
+
}
|
|
263
|
+
return await readProviderJsonResponse(response, "ClickClack response", { maxBytes: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES });
|
|
264
|
+
}
|
|
265
|
+
async function fetchEventPage(workspaceId, pageOptions = {}) {
|
|
266
|
+
const query = new URLSearchParams({ workspace_id: workspaceId });
|
|
267
|
+
if (pageOptions.afterCursor) query.set("after_cursor", pageOptions.afterCursor);
|
|
268
|
+
if (pageOptions.limit !== void 0) query.set("limit", String(pageOptions.limit));
|
|
269
|
+
if (pageOptions.includeTail) query.set("include_tail", "true");
|
|
270
|
+
const data = await request(`/api/realtime/events?${query.toString()}`);
|
|
271
|
+
return {
|
|
272
|
+
events: data.events,
|
|
273
|
+
...typeof data.tail_cursor === "string" ? { tailCursor: data.tail_cursor } : {}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
me: async () => {
|
|
278
|
+
return (await request("/api/me")).user;
|
|
279
|
+
},
|
|
280
|
+
setBotCommands: async (commands) => {
|
|
281
|
+
return (await request("/api/bots/self/commands", {
|
|
282
|
+
method: "PUT",
|
|
283
|
+
body: JSON.stringify({ commands })
|
|
284
|
+
})).bot_commands;
|
|
285
|
+
},
|
|
286
|
+
workspaces: async () => {
|
|
287
|
+
return (await request("/api/workspaces")).workspaces;
|
|
288
|
+
},
|
|
289
|
+
channels: async (workspaceId) => {
|
|
290
|
+
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
|
|
291
|
+
},
|
|
292
|
+
channelMessages: async (channelId, afterSeq, limit = 20) => {
|
|
293
|
+
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
294
|
+
},
|
|
295
|
+
directMessages: async (conversationId, afterSeq, limit = 20) => {
|
|
296
|
+
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
297
|
+
},
|
|
298
|
+
thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
|
|
299
|
+
message: async (messageId) => {
|
|
300
|
+
return (await request(`/api/messages/${encodeURIComponent(messageId)}`)).message;
|
|
301
|
+
},
|
|
302
|
+
findMessageByNonce: async (params) => {
|
|
303
|
+
const query = new URLSearchParams({
|
|
304
|
+
workspace_id: params.workspaceId,
|
|
305
|
+
nonce: params.nonce
|
|
306
|
+
});
|
|
307
|
+
try {
|
|
308
|
+
return (await request(`/api/messages/by-nonce?${query.toString()}`)).message;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (error instanceof ClickClackHttpError && error.status === 404) {
|
|
311
|
+
if (error.headers.get("X-ClickClack-Message-Nonce") === "supported") return;
|
|
312
|
+
throw new Error("ClickClack server does not support durable message nonce lookup", { cause: error });
|
|
313
|
+
}
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
createChannelMessage: async (channelId, body, opts) => {
|
|
318
|
+
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages`, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
body: JSON.stringify({
|
|
321
|
+
body,
|
|
322
|
+
...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
|
|
323
|
+
...opts?.nonce ? { nonce: opts.nonce } : {},
|
|
324
|
+
...provenanceFields(opts?.provenance)
|
|
325
|
+
})
|
|
326
|
+
})).message;
|
|
327
|
+
},
|
|
328
|
+
createThreadReply: async (messageId, body, opts) => {
|
|
329
|
+
return (await request(`/api/messages/${encodeURIComponent(messageId)}/thread/replies`, {
|
|
330
|
+
method: "POST",
|
|
331
|
+
body: JSON.stringify({
|
|
332
|
+
body,
|
|
333
|
+
...opts?.nonce ? { nonce: opts.nonce } : {},
|
|
334
|
+
...provenanceFields(opts?.provenance)
|
|
335
|
+
})
|
|
336
|
+
})).message;
|
|
337
|
+
},
|
|
338
|
+
createDirectConversation: async (workspaceId, memberIds) => {
|
|
339
|
+
return (await request("/api/dms", {
|
|
340
|
+
method: "POST",
|
|
341
|
+
body: JSON.stringify({
|
|
342
|
+
workspace_id: workspaceId,
|
|
343
|
+
member_ids: memberIds
|
|
344
|
+
})
|
|
345
|
+
})).conversation;
|
|
346
|
+
},
|
|
347
|
+
createUpload: async (params) => {
|
|
348
|
+
const form = new FormData();
|
|
349
|
+
const bytes = new Uint8Array(params.buffer);
|
|
350
|
+
form.append("file", new Blob([bytes], { type: params.contentType }), params.filename);
|
|
351
|
+
const query = new URLSearchParams({ workspace_id: params.workspaceId });
|
|
352
|
+
if (params.nonce) query.set("nonce", params.nonce);
|
|
353
|
+
return (await request(`/api/uploads?${query.toString()}`, {
|
|
354
|
+
method: "POST",
|
|
355
|
+
body: form
|
|
356
|
+
})).upload;
|
|
357
|
+
},
|
|
358
|
+
findUploadByNonce: async (params) => {
|
|
359
|
+
const query = new URLSearchParams({
|
|
360
|
+
workspace_id: params.workspaceId,
|
|
361
|
+
nonce: params.nonce
|
|
362
|
+
});
|
|
363
|
+
try {
|
|
364
|
+
return (await request(`/api/uploads/by-nonce?${query.toString()}`)).upload;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error instanceof ClickClackHttpError && error.status === 404) {
|
|
367
|
+
if (error.headers.get("X-ClickClack-Upload-Nonce") === "supported") return;
|
|
368
|
+
throw new Error("ClickClack server does not support durable upload nonce lookup", { cause: error });
|
|
369
|
+
}
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
attachUpload: async (messageId, uploadId) => {
|
|
374
|
+
await request(`/api/messages/${encodeURIComponent(messageId)}/attachments`, {
|
|
375
|
+
method: "POST",
|
|
376
|
+
body: JSON.stringify({ upload_id: uploadId })
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
/**
|
|
380
|
+
* POSTs a durable agent activity row (agent_commentary / agent_tool)
|
|
381
|
+
* through the normal message create path. Requires a bot token carrying
|
|
382
|
+
* the agent_activity:write scope on the ClickClack side.
|
|
383
|
+
*/
|
|
384
|
+
createActivityMessage: async (params) => {
|
|
385
|
+
if (!params.channelId && !params.conversationId) throw new Error("createActivityMessage requires a channelId or conversationId");
|
|
386
|
+
return (await request(params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
|
|
387
|
+
method: "POST",
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
body: params.body,
|
|
390
|
+
kind: params.kind,
|
|
391
|
+
turn_id: params.turnId,
|
|
392
|
+
...provenanceFields(params.provenance)
|
|
393
|
+
})
|
|
394
|
+
})).message;
|
|
395
|
+
},
|
|
396
|
+
/** PATCHes the body of an existing message (activity row coalescing). */
|
|
397
|
+
updateMessageBody: async (messageId, body) => {
|
|
398
|
+
return (await request(`/api/messages/${encodeURIComponent(messageId)}`, {
|
|
399
|
+
method: "PATCH",
|
|
400
|
+
body: JSON.stringify({ body })
|
|
401
|
+
})).message;
|
|
402
|
+
},
|
|
403
|
+
createDirectMessage: async (conversationId, body, opts) => {
|
|
404
|
+
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages`, {
|
|
405
|
+
method: "POST",
|
|
406
|
+
body: JSON.stringify({
|
|
407
|
+
body,
|
|
408
|
+
...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
|
|
409
|
+
...opts?.nonce ? { nonce: opts.nonce } : {}
|
|
410
|
+
})
|
|
411
|
+
})).message;
|
|
412
|
+
},
|
|
413
|
+
events: async (workspaceId, afterCursor) => (await fetchEventPage(workspaceId, { afterCursor })).events,
|
|
414
|
+
eventPage: fetchEventPage,
|
|
415
|
+
websocket: (workspaceId, afterCursor) => {
|
|
416
|
+
const url = new URL(`${baseUrl}/api/realtime/ws`);
|
|
417
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
418
|
+
url.searchParams.set("workspace_id", workspaceId);
|
|
419
|
+
if (afterCursor) url.searchParams.set("after_cursor", afterCursor);
|
|
420
|
+
return new WebSocket(url, {
|
|
421
|
+
headers: { Authorization: `Bearer ${options.token}` },
|
|
422
|
+
handshakeTimeout: CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS,
|
|
423
|
+
maxPayload: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
//#endregion
|
|
429
|
+
//#region extensions/clickclack/src/resolve.ts
|
|
430
|
+
/**
|
|
431
|
+
* Resolves a workspace slug/name/id from config to a ClickClack workspace id.
|
|
432
|
+
*/
|
|
433
|
+
async function resolveWorkspaceId(client, workspace) {
|
|
434
|
+
if (workspace.startsWith("wsp_")) return workspace;
|
|
435
|
+
const found = (await client.workspaces()).find((candidate) => candidate.id === workspace || candidate.slug === workspace || candidate.name === workspace);
|
|
436
|
+
if (!found) throw new Error(`ClickClack workspace not found: ${workspace}`);
|
|
437
|
+
return found.id;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Resolves a channel name/id from config or target input to a ClickClack
|
|
441
|
+
* channel id.
|
|
442
|
+
*/
|
|
443
|
+
async function resolveChannelId(client, workspaceId, channel) {
|
|
444
|
+
if (channel.startsWith("chn_")) return channel;
|
|
445
|
+
const found = (await client.channels(workspaceId)).find((candidate) => candidate.id === channel || candidate.name === channel);
|
|
446
|
+
if (!found) throw new Error(`ClickClack channel not found: ${channel}`);
|
|
447
|
+
return found.id;
|
|
448
|
+
}
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region extensions/clickclack/src/setup-core.ts
|
|
451
|
+
const channel$1 = "clickclack";
|
|
452
|
+
const REQUIRED_INPUT_ERROR = "ClickClack requires --token, --base-url, and --workspace (or --use-env).";
|
|
453
|
+
const INVALID_BASE_URL_ERROR = "ClickClack base URL must be a valid http(s) URL.";
|
|
454
|
+
function normalizeClickClackBaseUrl(value) {
|
|
455
|
+
const trimmed = value?.trim();
|
|
456
|
+
if (!trimmed) return;
|
|
457
|
+
try {
|
|
458
|
+
const parsed = new URL(trimmed);
|
|
459
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
|
|
460
|
+
return parsed.toString().replace(/\/+$/, "");
|
|
461
|
+
} catch {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function applyClickClackSetupConfigPatch(params) {
|
|
466
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
467
|
+
const namedConfig = applyAccountNameToChannelSection({
|
|
468
|
+
cfg: accountId === DEFAULT_ACCOUNT_ID ? params.cfg : moveSingleAccountChannelSectionToDefaultAccount({
|
|
469
|
+
cfg: params.cfg,
|
|
470
|
+
channelKey: channel$1
|
|
471
|
+
}),
|
|
472
|
+
channelKey: channel$1,
|
|
473
|
+
accountId,
|
|
474
|
+
name: params.name
|
|
475
|
+
});
|
|
476
|
+
return applySetupAccountConfigPatch({
|
|
477
|
+
cfg: accountId !== DEFAULT_ACCOUNT_ID ? migrateBaseNameToDefaultAccount({
|
|
478
|
+
cfg: namedConfig,
|
|
479
|
+
channelKey: channel$1
|
|
480
|
+
}) : namedConfig,
|
|
481
|
+
channelKey: channel$1,
|
|
482
|
+
accountId,
|
|
483
|
+
patch: params.patch
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
function clearClickClackSetupConfigFields(params) {
|
|
487
|
+
const clickclack = params.cfg.channels?.clickclack;
|
|
488
|
+
if (!clickclack) return params.cfg;
|
|
489
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
490
|
+
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
491
|
+
const nextClickClack = { ...clickclack };
|
|
492
|
+
for (const field of params.fields) delete nextClickClack[field];
|
|
493
|
+
return {
|
|
494
|
+
...params.cfg,
|
|
495
|
+
channels: {
|
|
496
|
+
...params.cfg.channels,
|
|
497
|
+
clickclack: nextClickClack
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
const currentAccount = clickclack.accounts?.[accountId];
|
|
502
|
+
if (!currentAccount) return params.cfg;
|
|
503
|
+
const nextAccount = { ...currentAccount };
|
|
504
|
+
for (const field of params.fields) delete nextAccount[field];
|
|
505
|
+
return {
|
|
506
|
+
...params.cfg,
|
|
507
|
+
channels: {
|
|
508
|
+
...params.cfg.channels,
|
|
509
|
+
clickclack: {
|
|
510
|
+
...clickclack,
|
|
511
|
+
accounts: {
|
|
512
|
+
...clickclack.accounts,
|
|
513
|
+
[accountId]: nextAccount
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function applyClickClackCredentialConfig(params) {
|
|
520
|
+
const fieldsToClear = params.useEnv ? ["token", "tokenFile"] : params.tokenFile ? ["token"] : params.token !== void 0 ? ["tokenFile"] : [];
|
|
521
|
+
return clearClickClackSetupConfigFields({
|
|
522
|
+
cfg: applyClickClackSetupConfigPatch({
|
|
523
|
+
cfg: params.cfg,
|
|
524
|
+
accountId: params.accountId,
|
|
525
|
+
patch: params.useEnv ? {} : params.tokenFile ? { tokenFile: params.tokenFile } : params.token !== void 0 ? { token: params.token } : {}
|
|
526
|
+
}),
|
|
527
|
+
accountId: params.accountId,
|
|
528
|
+
fields: fieldsToClear
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
const clickClackSetupAdapter = {
|
|
532
|
+
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
533
|
+
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
534
|
+
cfg,
|
|
535
|
+
channelKey: channel$1,
|
|
536
|
+
accountId,
|
|
537
|
+
name
|
|
538
|
+
}),
|
|
539
|
+
validateInput: createSetupInputPresenceValidator({
|
|
540
|
+
defaultAccountOnlyEnvError: "CLICKCLACK_BOT_TOKEN can only be used for the default account.",
|
|
541
|
+
whenNotUseEnv: [
|
|
542
|
+
{
|
|
543
|
+
someOf: ["token", "tokenFile"],
|
|
544
|
+
message: REQUIRED_INPUT_ERROR
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
someOf: ["baseUrl"],
|
|
548
|
+
message: REQUIRED_INPUT_ERROR
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
someOf: ["workspace"],
|
|
552
|
+
message: REQUIRED_INPUT_ERROR
|
|
553
|
+
}
|
|
554
|
+
],
|
|
555
|
+
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;
|
|
559
|
+
const existing = resolveClickClackAccountConfig(cfg, accountId);
|
|
560
|
+
const existingBaseUrl = normalizeClickClackBaseUrl(existing.baseUrl);
|
|
561
|
+
if (!baseUrl && existing.baseUrl?.trim() && !existingBaseUrl) return INVALID_BASE_URL_ERROR;
|
|
562
|
+
if (!baseUrl && !existingBaseUrl) return REQUIRED_INPUT_ERROR;
|
|
563
|
+
if (!input.workspace?.trim() && !existing.workspace?.trim()) return REQUIRED_INPUT_ERROR;
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
}),
|
|
567
|
+
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();
|
|
573
|
+
return applyClickClackCredentialConfig({
|
|
574
|
+
cfg: applyClickClackSetupConfigPatch({
|
|
575
|
+
cfg,
|
|
576
|
+
accountId,
|
|
577
|
+
name: input.name,
|
|
578
|
+
patch: {
|
|
579
|
+
...baseUrl ? { baseUrl } : {},
|
|
580
|
+
...workspace ? { workspace } : {}
|
|
581
|
+
}
|
|
582
|
+
}),
|
|
583
|
+
accountId,
|
|
584
|
+
token,
|
|
585
|
+
tokenFile,
|
|
586
|
+
useEnv: input.useEnv
|
|
587
|
+
});
|
|
588
|
+
},
|
|
589
|
+
afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
|
|
590
|
+
const { verifyClickClackAccountAfterSetup } = await Promise.resolve().then(() => setup_verify_exports);
|
|
591
|
+
await verifyClickClackAccountAfterSetup({
|
|
592
|
+
cfg,
|
|
593
|
+
accountId,
|
|
594
|
+
runtime
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region extensions/clickclack/src/setup-verify.ts
|
|
600
|
+
var setup_verify_exports = /* @__PURE__ */ __exportAll({
|
|
601
|
+
checkClickClackSetupConnection: () => checkClickClackSetupConnection,
|
|
602
|
+
verifyClickClackAccountAfterSetup: () => verifyClickClackAccountAfterSetup
|
|
603
|
+
});
|
|
604
|
+
const GATEWAY_RUNNING_MESSAGE = "OpenClaw is running — ClickClack will connect automatically.";
|
|
605
|
+
const GATEWAY_NOT_RUNNING_MESSAGE = "Start OpenClaw to connect: openclaw gateway";
|
|
606
|
+
const GATEWAY_UNKNOWN_MESSAGE = "If OpenClaw is running it connects automatically; otherwise start it with: openclaw gateway";
|
|
607
|
+
function isHttpStatus(error, status) {
|
|
608
|
+
return typeof error === "object" && error !== null && "status" in error && error.status === status;
|
|
609
|
+
}
|
|
610
|
+
function isWorkspaceNotFound(error) {
|
|
611
|
+
return error instanceof Error && error.message.startsWith("ClickClack workspace not found:");
|
|
612
|
+
}
|
|
613
|
+
function usesUnavailableImplicitEnvToken(account, tokenOverride) {
|
|
614
|
+
return account.accountId === DEFAULT_ACCOUNT_ID && Boolean(account.baseUrl && account.workspace) && !tokenOverride && !account.token && !hasConfiguredSecretInput(account.config.token) && !account.config.tokenFile?.trim();
|
|
615
|
+
}
|
|
616
|
+
async function checkClickClackSetupConnection(params) {
|
|
617
|
+
let workspaceInput = "";
|
|
618
|
+
try {
|
|
619
|
+
const account = resolveClickClackAccount({
|
|
620
|
+
cfg: params.cfg,
|
|
621
|
+
accountId: params.accountId
|
|
622
|
+
});
|
|
623
|
+
workspaceInput = account.workspace;
|
|
624
|
+
const token = params.token?.trim() || account.token;
|
|
625
|
+
if (usesUnavailableImplicitEnvToken(account, token)) return { status: "skipped-env-token" };
|
|
626
|
+
if (!account.baseUrl || !account.workspace || !token) return { status: "skipped-unconfigured" };
|
|
627
|
+
const client = createClickClackClient({
|
|
628
|
+
baseUrl: account.baseUrl,
|
|
629
|
+
token
|
|
630
|
+
});
|
|
631
|
+
const me = await client.me();
|
|
632
|
+
const workspaceId = await resolveWorkspaceId(client, account.workspace);
|
|
633
|
+
const workspace = (await client.workspaces()).find((candidate) => candidate.id === workspaceId);
|
|
634
|
+
if (!workspace) throw new Error(`ClickClack workspace not found: ${account.workspace}`);
|
|
635
|
+
return {
|
|
636
|
+
status: "connected",
|
|
637
|
+
handle: me.handle,
|
|
638
|
+
workspaceName: workspace.name
|
|
639
|
+
};
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (isHttpStatus(error, 401)) return { status: "invalid-token" };
|
|
642
|
+
if (isWorkspaceNotFound(error)) return {
|
|
643
|
+
status: "workspace-not-found",
|
|
644
|
+
workspace: workspaceInput
|
|
645
|
+
};
|
|
646
|
+
return {
|
|
647
|
+
status: "failed",
|
|
648
|
+
error: formatErrorMessage(error)
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function isGatewayNotRunningError(error) {
|
|
653
|
+
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;
|
|
654
|
+
const message = formatErrorMessage(error).toLowerCase();
|
|
655
|
+
return message.includes("econnrefused") || message.includes("connection refused");
|
|
656
|
+
}
|
|
657
|
+
async function probeClickClackGatewayStatus() {
|
|
658
|
+
try {
|
|
659
|
+
const { callGatewayFromCli } = await import("openclaw/plugin-sdk/gateway-runtime");
|
|
660
|
+
await callGatewayFromCli("health", {
|
|
661
|
+
timeout: "1000",
|
|
662
|
+
json: true
|
|
663
|
+
}, void 0, {
|
|
664
|
+
expectFinal: false,
|
|
665
|
+
progress: false
|
|
666
|
+
});
|
|
667
|
+
return "running";
|
|
668
|
+
} catch (error) {
|
|
669
|
+
return isGatewayNotRunningError(error) ? "not-running" : "unavailable";
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function formatClickClackConnectionLog(result) {
|
|
673
|
+
switch (result.status) {
|
|
674
|
+
case "connected": return `Connected as @${result.handle} — workspace ${result.workspaceName} resolved.`;
|
|
675
|
+
case "invalid-token": return "ClickClack rejected the bot token (401). Copy a current token and rerun setup.";
|
|
676
|
+
case "workspace-not-found": return `Workspace "${result.workspace}" was not found. Check the id, slug, or name, list available workspaces, and rerun setup.`;
|
|
677
|
+
case "failed": return `Connection check failed: ${result.error}. Setup was saved; fix the connection and rerun setup.`;
|
|
678
|
+
case "skipped-env-token": return "Token comes from CLICKCLACK_BOT_TOKEN; verification skipped.";
|
|
679
|
+
case "skipped-unconfigured": return;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function formatClickClackGatewayLog(status) {
|
|
683
|
+
switch (status) {
|
|
684
|
+
case "running": return GATEWAY_RUNNING_MESSAGE;
|
|
685
|
+
case "not-running": return GATEWAY_NOT_RUNNING_MESSAGE;
|
|
686
|
+
case "unavailable": return GATEWAY_UNKNOWN_MESSAGE;
|
|
687
|
+
}
|
|
688
|
+
return GATEWAY_UNKNOWN_MESSAGE;
|
|
689
|
+
}
|
|
690
|
+
async function verifyClickClackAccountAfterSetup(params) {
|
|
691
|
+
try {
|
|
692
|
+
const message = formatClickClackConnectionLog(await checkClickClackSetupConnection({
|
|
693
|
+
cfg: params.cfg,
|
|
694
|
+
accountId: params.accountId
|
|
695
|
+
}));
|
|
696
|
+
if (message) params.runtime.log(message);
|
|
697
|
+
} catch (error) {
|
|
698
|
+
params.runtime.log(`Connection check failed: ${formatErrorMessage(error)}. Setup was saved; fix the connection and rerun setup.`);
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
const status = await probeClickClackGatewayStatus();
|
|
702
|
+
params.runtime.log(formatClickClackGatewayLog(status));
|
|
703
|
+
} catch {
|
|
704
|
+
params.runtime.log(GATEWAY_UNKNOWN_MESSAGE);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
//#endregion
|
|
708
|
+
//#region extensions/clickclack/src/setup-surface.ts
|
|
709
|
+
const t = createSetupTranslator();
|
|
710
|
+
const channel = "clickclack";
|
|
711
|
+
function hasConfiguredClickClackCredential(account) {
|
|
712
|
+
return hasConfiguredSecretInput(account.config.token) || Boolean(account.config.tokenFile?.trim());
|
|
713
|
+
}
|
|
714
|
+
function isClickClackSetupConfigured(account) {
|
|
715
|
+
return Boolean(account.baseUrl && account.workspace && (account.token || hasConfiguredClickClackCredential(account)));
|
|
716
|
+
}
|
|
717
|
+
const clickClackSetupWizard = {
|
|
718
|
+
channel,
|
|
719
|
+
status: createStandardChannelSetupStatus({
|
|
720
|
+
channelLabel: "ClickClack",
|
|
721
|
+
configuredLabel: t("wizard.channels.statusConfigured"),
|
|
722
|
+
unconfiguredLabel: t("wizard.channels.statusNeedsSetup"),
|
|
723
|
+
configuredHint: t("wizard.channels.statusSelfHostedChat"),
|
|
724
|
+
unconfiguredHint: t("wizard.channels.statusNeedsSetup"),
|
|
725
|
+
configuredScore: 2,
|
|
726
|
+
unconfiguredScore: 1,
|
|
727
|
+
resolveConfigured: ({ cfg, accountId }) => (accountId ? [accountId] : listClickClackAccountIds(cfg)).some((resolvedAccountId) => isClickClackSetupConfigured(resolveClickClackAccount({
|
|
728
|
+
cfg,
|
|
729
|
+
accountId: resolvedAccountId
|
|
730
|
+
})))
|
|
731
|
+
}),
|
|
732
|
+
introNote: {
|
|
733
|
+
title: t("wizard.clickclack.botTokenTitle"),
|
|
734
|
+
lines: [t("wizard.clickclack.helpCreateToken"), t("wizard.channels.docs", { link: formatDocsLink("/channels/clickclack", "clickclack") })],
|
|
735
|
+
shouldShow: ({ cfg, accountId }) => !isClickClackSetupConfigured(resolveClickClackAccount({
|
|
736
|
+
cfg,
|
|
737
|
+
accountId
|
|
738
|
+
}))
|
|
739
|
+
},
|
|
740
|
+
credentials: [defineTokenCredential({
|
|
741
|
+
inputKey: "token",
|
|
742
|
+
configKey: "token",
|
|
743
|
+
configuredFields: ["token", "tokenFile"],
|
|
744
|
+
providerHint: channel,
|
|
745
|
+
credentialLabel: t("wizard.clickclack.botToken"),
|
|
746
|
+
preferredEnvVar: "CLICKCLACK_BOT_TOKEN",
|
|
747
|
+
envPrompt: t("wizard.clickclack.envPrompt"),
|
|
748
|
+
keepPrompt: t("wizard.clickclack.botTokenKeep"),
|
|
749
|
+
inputPrompt: t("wizard.clickclack.botTokenInput"),
|
|
750
|
+
allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1,
|
|
751
|
+
resolveAccount: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
752
|
+
cfg,
|
|
753
|
+
accountId
|
|
754
|
+
}),
|
|
755
|
+
hasConfiguredValue: hasConfiguredClickClackCredential,
|
|
756
|
+
resolvedValue: (account) => account.token || void 0,
|
|
757
|
+
envValue: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1 ? process.env.CLICKCLACK_BOT_TOKEN?.trim() || void 0 : void 0,
|
|
758
|
+
patchAccount: ({ cfg, accountId, mode, patch }) => mode === "env" ? applyClickClackCredentialConfig({
|
|
759
|
+
cfg,
|
|
760
|
+
accountId,
|
|
761
|
+
useEnv: true
|
|
762
|
+
}) : applyClickClackCredentialConfig({
|
|
763
|
+
cfg,
|
|
764
|
+
accountId,
|
|
765
|
+
...patch
|
|
766
|
+
}),
|
|
767
|
+
useEnv: { clearFields: ["token", "tokenFile"] },
|
|
768
|
+
set: { clearFields: ["tokenFile"] }
|
|
769
|
+
})],
|
|
770
|
+
textInputs: [baseUrlTextInput({
|
|
771
|
+
inputKey: "baseUrl",
|
|
772
|
+
configKey: "baseUrl",
|
|
773
|
+
message: t("wizard.clickclack.baseUrlPrompt"),
|
|
774
|
+
resolveAccount: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
775
|
+
cfg,
|
|
776
|
+
accountId
|
|
777
|
+
}),
|
|
778
|
+
currentValue: (account) => account.baseUrl || void 0,
|
|
779
|
+
includeInitialValue: true,
|
|
780
|
+
validate: (value) => normalizeClickClackBaseUrl(value) ? void 0 : "ClickClack server URL must be a valid http(s) URL.",
|
|
781
|
+
normalize: (value) => normalizeClickClackBaseUrl(value) ?? value.trim(),
|
|
782
|
+
patchAccount: ({ cfg, accountId, patch }) => applyClickClackSetupConfigPatch({
|
|
783
|
+
cfg,
|
|
784
|
+
accountId,
|
|
785
|
+
patch
|
|
786
|
+
})
|
|
787
|
+
}), {
|
|
788
|
+
inputKey: "workspace",
|
|
789
|
+
message: t("wizard.clickclack.workspacePrompt"),
|
|
790
|
+
helpTitle: t("wizard.clickclack.workspacePrompt"),
|
|
791
|
+
helpLines: [t("wizard.clickclack.workspaceHelp")],
|
|
792
|
+
currentValue: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
793
|
+
cfg,
|
|
794
|
+
accountId
|
|
795
|
+
}).workspace || void 0,
|
|
796
|
+
initialValue: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
797
|
+
cfg,
|
|
798
|
+
accountId
|
|
799
|
+
}).workspace || void 0,
|
|
800
|
+
validate: ({ value }) => value.trim() ? void 0 : "Required",
|
|
801
|
+
normalizeValue: ({ value }) => value.trim(),
|
|
802
|
+
applySet: async ({ cfg, accountId, value }) => applyClickClackSetupConfigPatch({
|
|
803
|
+
cfg,
|
|
804
|
+
accountId,
|
|
805
|
+
patch: { workspace: value }
|
|
806
|
+
})
|
|
807
|
+
}],
|
|
808
|
+
finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
|
|
809
|
+
const result = await checkClickClackSetupConnection({
|
|
810
|
+
cfg,
|
|
811
|
+
accountId,
|
|
812
|
+
token: credentialValues.token
|
|
813
|
+
});
|
|
814
|
+
if (result.status === "connected") {
|
|
815
|
+
await prompter.note(t("wizard.clickclack.connected", {
|
|
816
|
+
handle: result.handle,
|
|
817
|
+
workspace: result.workspaceName
|
|
818
|
+
}), t("wizard.clickclack.connectionTitle"));
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (result.status === "skipped-env-token" || result.status === "skipped-unconfigured") return;
|
|
822
|
+
const message = result.status === "invalid-token" ? t("wizard.clickclack.invalidToken") : result.status === "workspace-not-found" ? t("wizard.clickclack.workspaceNotFound", { workspace: result.workspace }) : t("wizard.clickclack.connectionFailed", { error: result.error });
|
|
823
|
+
await prompter.note(message, t("wizard.clickclack.validationWarningTitle"));
|
|
824
|
+
},
|
|
825
|
+
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
|
|
826
|
+
};
|
|
827
|
+
//#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 };
|