@openclaw/clickclack 2026.7.2-beta.7 → 2026.7.34
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 +0 -152
- package/dist/.setup/channel-DGqOQ-o2.mjs +1172 -0
- package/dist/api.js +1 -2
- package/dist/channel-plugin-api.js +1 -1
- package/dist/index.js +1 -3
- package/dist/runtime-api.js +3 -4
- package/node_modules/ws/lib/permessage-deflate.js +3 -1
- package/node_modules/ws/package.json +1 -1
- package/openclaw.plugin.json +21 -129
- package/package.json +6 -94
- package/dist/channel-CECIpaS0.js +0 -1852
- package/dist/doctor-contract-api.js +0 -48
- package/dist/runtime-api-CZ1rmHjv.js +0 -1029
- package/dist/secret-contract-BKkzzjBA.js +0 -35
- package/dist/secret-contract-api.js +0 -2
- package/dist/setup-claim-CP-QZ5bt.js +0 -136
- package/dist/setup-entry.js +0 -15
- package/dist/setup-plugin-api.js +0 -19
- package/dist/setup-surface-DAy8vDJK.js +0 -1131
|
@@ -1,1131 +0,0 @@
|
|
|
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 { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-resolution-runtime";
|
|
4
|
-
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
|
5
|
-
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
|
6
|
-
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
7
|
-
import { buildSecretInputSchema, normalizeResolvedSecretInputString, normalizeSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
8
|
-
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
9
|
-
import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
10
|
-
import { buildChannelConfigSchema, buildMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
13
|
-
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
14
|
-
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
15
|
-
import { WebSocket } from "ws";
|
|
16
|
-
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
17
|
-
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, applyAccountNameToChannelSection, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, hasConfiguredSecretInput, moveSingleAccountChannelSectionToDefaultAccount, patchScopedAccountConfig, prepareScopedSetupConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
18
|
-
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
|
|
19
|
-
import net from "node:net";
|
|
20
|
-
//#region \0rolldown/runtime.js
|
|
21
|
-
var __defProp = Object.defineProperty;
|
|
22
|
-
var __exportAll = (all, no_symbols) => {
|
|
23
|
-
let target = {};
|
|
24
|
-
for (var name in all) __defProp(target, name, {
|
|
25
|
-
get: all[name],
|
|
26
|
-
enumerable: true
|
|
27
|
-
});
|
|
28
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
29
|
-
return target;
|
|
30
|
-
};
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region extensions/clickclack/src/accounts.ts
|
|
33
|
-
/**
|
|
34
|
-
* Resolves ClickClack account configuration from root channel config, named
|
|
35
|
-
* account overrides, and secret-provider references.
|
|
36
|
-
*/
|
|
37
|
-
const DEFAULT_RECONNECT_MS = 1500;
|
|
38
|
-
const MIN_RECONNECT_MS = 100;
|
|
39
|
-
const MAX_RECONNECT_MS = 6e4;
|
|
40
|
-
const DEFAULT_DISCUSSIONS_SECTION = "Sessions";
|
|
41
|
-
const { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resolveDefaultClickClackAccountId, resolveAccountConfig: resolveMergedClickClackAccountConfig } = createAccountListHelpers("clickclack", {
|
|
42
|
-
normalizeAccountId,
|
|
43
|
-
omitKeys: ["defaultAccount"],
|
|
44
|
-
nestedObjectKeys: ["discussions"],
|
|
45
|
-
hasImplicitDefaultAccount: (cfg) => {
|
|
46
|
-
const channel = cfg.channels?.clickclack;
|
|
47
|
-
return Boolean(channel?.baseUrl?.trim() && (hasConfiguredAccountValue(channel.token) || Boolean(channel.tokenFile?.trim()) || Boolean(process.env.CLICKCLACK_BOT_TOKEN?.trim())) && channel.workspace?.trim());
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
function mergeClickClackGroups(...sources) {
|
|
51
|
-
const merged = /* @__PURE__ */ new Map();
|
|
52
|
-
for (const source of sources) for (const [rawKey, value] of Object.entries(source ?? {})) {
|
|
53
|
-
const key = rawKey.trim();
|
|
54
|
-
if (!key) continue;
|
|
55
|
-
merged.set(key, {
|
|
56
|
-
...merged.get(key),
|
|
57
|
-
...value.requireMention !== void 0 ? { requireMention: value.requireMention } : {},
|
|
58
|
-
...value.mentionPatterns !== void 0 ? { mentionPatterns: value.mentionPatterns } : {}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return Object.fromEntries(merged);
|
|
62
|
-
}
|
|
63
|
-
function resolveClickClackAccountConfig(cfg, accountId) {
|
|
64
|
-
const channel = cfg.channels?.clickclack;
|
|
65
|
-
const merged = resolveMergedClickClackAccountConfig(cfg, accountId);
|
|
66
|
-
const account = resolveNormalizedAccountEntry(channel?.accounts, accountId, normalizeAccountId);
|
|
67
|
-
const mergedWithGroups = channel?.groups || account?.groups ? {
|
|
68
|
-
...merged,
|
|
69
|
-
groups: mergeClickClackGroups(channel?.groups, account?.groups)
|
|
70
|
-
} : merged;
|
|
71
|
-
const accountTokenFile = account?.tokenFile?.trim();
|
|
72
|
-
if (accountTokenFile) return {
|
|
73
|
-
...mergedWithGroups,
|
|
74
|
-
token: account?.token,
|
|
75
|
-
tokenFile: accountTokenFile
|
|
76
|
-
};
|
|
77
|
-
if (hasConfiguredAccountValue(account?.token)) return {
|
|
78
|
-
...mergedWithGroups,
|
|
79
|
-
token: account?.token,
|
|
80
|
-
tokenFile: void 0
|
|
81
|
-
};
|
|
82
|
-
return mergedWithGroups;
|
|
83
|
-
}
|
|
84
|
-
function resolveClickClackToken(params) {
|
|
85
|
-
const tokenFile = params.tokenFile?.trim();
|
|
86
|
-
if (tokenFile) return tryReadSecretFileSync(tokenFile, params.accountId === DEFAULT_ACCOUNT_ID$2 ? "channels.clickclack.tokenFile" : `channels.clickclack.accounts.${params.accountId}.tokenFile`, { rejectSymlink: true }) ?? "";
|
|
87
|
-
const resolved = resolveSecretInputString({
|
|
88
|
-
value: params.value,
|
|
89
|
-
path: params.accountId === DEFAULT_ACCOUNT_ID$2 ? "channels.clickclack.token" : `channels.clickclack.accounts.${params.accountId}.token`,
|
|
90
|
-
defaults: params.cfg.secrets?.defaults,
|
|
91
|
-
mode: "inspect"
|
|
92
|
-
});
|
|
93
|
-
if (resolved.status !== "available") {
|
|
94
|
-
if (resolved.status === "missing" && params.accountId === DEFAULT_ACCOUNT_ID$2) return normalizeSecretInputString((params.env ?? process.env).CLICKCLACK_BOT_TOKEN) ?? "";
|
|
95
|
-
if (resolved.status === "configured_unavailable" && resolved.ref.source === "env") {
|
|
96
|
-
const providerConfig = params.cfg.secrets?.providers?.[resolved.ref.provider];
|
|
97
|
-
if (providerConfig) {
|
|
98
|
-
if (providerConfig.source !== "env") throw new Error(`Secret provider "${resolved.ref.provider}" has source "${providerConfig.source}" but ref requests "env".`);
|
|
99
|
-
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.`);
|
|
100
|
-
} 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}).`);
|
|
101
|
-
return normalizeSecretInputString((params.env ?? process.env)[resolved.ref.id]) ?? "";
|
|
102
|
-
}
|
|
103
|
-
return "";
|
|
104
|
-
}
|
|
105
|
-
return normalizeResolvedSecretInputString({
|
|
106
|
-
value: resolved.value,
|
|
107
|
-
path: "channels.clickclack.token"
|
|
108
|
-
}) ?? "";
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Builds the normalized account snapshot used by gateway, outbound delivery,
|
|
112
|
-
* status reporting, and channel routing.
|
|
113
|
-
*/
|
|
114
|
-
function resolveClickClackAccount(params) {
|
|
115
|
-
const accountId = normalizeAccountId(params.accountId);
|
|
116
|
-
const merged = resolveClickClackAccountConfig(params.cfg, accountId);
|
|
117
|
-
const enabled = params.cfg.channels?.clickclack?.enabled !== false && merged.enabled !== false;
|
|
118
|
-
const baseUrl = merged.baseUrl?.trim().replace(/\/$/, "") ?? "";
|
|
119
|
-
const token = resolveClickClackToken({
|
|
120
|
-
cfg: params.cfg,
|
|
121
|
-
value: merged.token,
|
|
122
|
-
tokenFile: merged.tokenFile,
|
|
123
|
-
accountId,
|
|
124
|
-
env: params.env
|
|
125
|
-
});
|
|
126
|
-
const workspace = merged.workspace?.trim() ?? "";
|
|
127
|
-
const discussionsWorkspace = merged.discussions?.workspace?.trim() || workspace;
|
|
128
|
-
const controlUrlBase = merged.discussions?.controlUrlBase?.trim();
|
|
129
|
-
const apiEndpoint = merged.apiBaseUrl?.trim().replace(/\/$/, "") || baseUrl;
|
|
130
|
-
return {
|
|
131
|
-
accountId,
|
|
132
|
-
enabled,
|
|
133
|
-
configured: Boolean(baseUrl && token && workspace),
|
|
134
|
-
name: normalizeOptionalString(merged.name),
|
|
135
|
-
baseUrl,
|
|
136
|
-
token,
|
|
137
|
-
workspace,
|
|
138
|
-
botUserId: normalizeOptionalString(merged.botUserId),
|
|
139
|
-
agentId: normalizeOptionalString(merged.agentId),
|
|
140
|
-
replyMode: merged.replyMode === "model" ? "model" : "agent",
|
|
141
|
-
model: normalizeOptionalString(merged.model),
|
|
142
|
-
systemPrompt: normalizeOptionalString(merged.systemPrompt),
|
|
143
|
-
toolsAllow: merged.toolsAllow,
|
|
144
|
-
defaultTo: merged.defaultTo?.trim() || "channel:general",
|
|
145
|
-
allowFrom: merged.allowFrom ?? ["*"],
|
|
146
|
-
reconnectMs: resolveIntegerOption(merged.reconnectMs, DEFAULT_RECONNECT_MS, {
|
|
147
|
-
min: MIN_RECONNECT_MS,
|
|
148
|
-
max: MAX_RECONNECT_MS
|
|
149
|
-
}),
|
|
150
|
-
agentActivity: merged.agentActivity === true,
|
|
151
|
-
commandMenu: merged.commandMenu !== false,
|
|
152
|
-
discussions: {
|
|
153
|
-
enabled: merged.discussions?.enabled === true,
|
|
154
|
-
workspace: discussionsWorkspace,
|
|
155
|
-
...controlUrlBase ? { controlUrlBase } : {},
|
|
156
|
-
section: merged.discussions?.section?.trim() || DEFAULT_DISCUSSIONS_SECTION
|
|
157
|
-
},
|
|
158
|
-
requireMention: merged.requireMention === true,
|
|
159
|
-
mentionPatterns: merged.mentionPatterns ?? [],
|
|
160
|
-
groups: mergeClickClackGroups(merged.groups),
|
|
161
|
-
config: {
|
|
162
|
-
...merged,
|
|
163
|
-
allowFrom: merged.allowFrom ?? ["*"]
|
|
164
|
-
},
|
|
165
|
-
apiEndpoint
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Returns all enabled accounts, including the implicit default account when
|
|
170
|
-
* legacy top-level ClickClack config is present.
|
|
171
|
-
*/
|
|
172
|
-
function listEnabledClickClackAccounts(cfg) {
|
|
173
|
-
return listClickClackAccountIds(cfg).map((accountId) => resolveClickClackAccount({
|
|
174
|
-
cfg,
|
|
175
|
-
accountId
|
|
176
|
-
})).filter((account) => account.enabled);
|
|
177
|
-
}
|
|
178
|
-
//#endregion
|
|
179
|
-
//#region extensions/clickclack/src/channel-config.ts
|
|
180
|
-
const CLICKCLACK_CHANNEL_ID = "clickclack";
|
|
181
|
-
const clickClackMeta = { ...getChatChannelMeta(CLICKCLACK_CHANNEL_ID) };
|
|
182
|
-
const clickClackConfigAdapter = {
|
|
183
|
-
listAccountIds: (cfg) => listClickClackAccountIds(cfg),
|
|
184
|
-
resolveAccount: (cfg, accountId) => resolveClickClackAccount({
|
|
185
|
-
cfg,
|
|
186
|
-
accountId
|
|
187
|
-
}),
|
|
188
|
-
defaultAccountId: (cfg) => resolveDefaultClickClackAccountId(cfg),
|
|
189
|
-
isConfigured: (account) => account.configured,
|
|
190
|
-
resolveAllowFrom: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
191
|
-
cfg,
|
|
192
|
-
accountId
|
|
193
|
-
}).allowFrom,
|
|
194
|
-
resolveDefaultTo: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
195
|
-
cfg,
|
|
196
|
-
accountId
|
|
197
|
-
}).defaultTo
|
|
198
|
-
};
|
|
199
|
-
//#endregion
|
|
200
|
-
//#region extensions/clickclack/src/config-schema.ts
|
|
201
|
-
/**
|
|
202
|
-
* Zod-backed config schema for ClickClack channel accounts.
|
|
203
|
-
*/
|
|
204
|
-
const ClickClackAccountConfigSchema = z.object({
|
|
205
|
-
name: z.string().optional(),
|
|
206
|
-
enabled: z.boolean().optional(),
|
|
207
|
-
baseUrl: z.string().url().optional(),
|
|
208
|
-
apiBaseUrl: z.string().url().optional(),
|
|
209
|
-
token: buildSecretInputSchema().optional(),
|
|
210
|
-
tokenFile: z.string().optional(),
|
|
211
|
-
workspace: z.string().optional(),
|
|
212
|
-
botUserId: z.string().optional(),
|
|
213
|
-
agentId: z.string().optional(),
|
|
214
|
-
replyMode: z.enum(["agent", "model"]).optional(),
|
|
215
|
-
model: z.string().optional(),
|
|
216
|
-
systemPrompt: z.string().optional(),
|
|
217
|
-
toolsAllow: z.array(z.string()).optional(),
|
|
218
|
-
defaultTo: z.string().optional(),
|
|
219
|
-
allowFrom: z.array(z.string()).optional(),
|
|
220
|
-
reconnectMs: z.number().int().min(100).max(6e4).optional(),
|
|
221
|
-
agentActivity: z.boolean().optional(),
|
|
222
|
-
commandMenu: z.boolean().optional(),
|
|
223
|
-
requireMention: z.boolean().optional(),
|
|
224
|
-
mentionPatterns: z.array(z.string()).optional(),
|
|
225
|
-
groups: z.record(z.string(), z.object({
|
|
226
|
-
requireMention: z.boolean().optional(),
|
|
227
|
-
mentionPatterns: z.array(z.string()).optional()
|
|
228
|
-
}).strict()).optional(),
|
|
229
|
-
discussions: z.object({
|
|
230
|
-
enabled: z.boolean().optional(),
|
|
231
|
-
workspace: z.string().optional(),
|
|
232
|
-
controlUrlBase: z.string().url().optional(),
|
|
233
|
-
section: z.string().optional()
|
|
234
|
-
}).strict().optional()
|
|
235
|
-
}).strict();
|
|
236
|
-
/**
|
|
237
|
-
* Config schema exported to core so `openclaw doctor` and config validation
|
|
238
|
-
* understand both default and named ClickClack accounts.
|
|
239
|
-
*/
|
|
240
|
-
const clickClackConfigSchema = buildChannelConfigSchema(buildMultiAccountChannelSchema(ClickClackAccountConfigSchema, { accountSchema: ClickClackAccountConfigSchema.partial() }));
|
|
241
|
-
//#endregion
|
|
242
|
-
//#region extensions/clickclack/src/http-client.ts
|
|
243
|
-
/**
|
|
244
|
-
* Thin ClickClack REST/websocket client used by gateway, resolver, and outbound
|
|
245
|
-
* delivery code.
|
|
246
|
-
*/
|
|
247
|
-
/**
|
|
248
|
-
* Serializes optional provenance into the wire fields. Unknown JSON fields
|
|
249
|
-
* are ignored by servers without the provenance columns, so these are safe
|
|
250
|
-
* to send unconditionally when present.
|
|
251
|
-
*/
|
|
252
|
-
function provenanceFields(provenance) {
|
|
253
|
-
const fields = {};
|
|
254
|
-
if (provenance?.model?.trim()) fields.author_model = provenance.model.trim();
|
|
255
|
-
if (provenance?.thinking?.trim()) fields.author_thinking = provenance.thinking.trim();
|
|
256
|
-
if (provenance?.runtime?.trim()) fields.author_runtime = provenance.runtime.trim();
|
|
257
|
-
return fields;
|
|
258
|
-
}
|
|
259
|
-
const CLICKCLACK_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
|
260
|
-
const CLICKCLACK_CORRELATION_ID_MAX_LENGTH = 128;
|
|
261
|
-
const CLICKCLACK_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/u;
|
|
262
|
-
const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
263
|
-
const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
264
|
-
const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
|
|
265
|
-
const CLICKCLACK_MESSAGE_PAGE_LIMIT = 200;
|
|
266
|
-
const CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT = 8;
|
|
267
|
-
const CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT = 24;
|
|
268
|
-
function compareMessages(left, right) {
|
|
269
|
-
return left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id);
|
|
270
|
-
}
|
|
271
|
-
function keepLatestMessages(messages, limit) {
|
|
272
|
-
return messages.toSorted(compareMessages).slice(-limit);
|
|
273
|
-
}
|
|
274
|
-
var ClickClackHttpError = class extends Error {
|
|
275
|
-
constructor(status, detail, headers) {
|
|
276
|
-
super(`ClickClack ${status}: ${detail}`);
|
|
277
|
-
this.status = status;
|
|
278
|
-
this.headers = headers;
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
/** Matches the workspace/name uniqueness error returned by current ClickClack servers. */
|
|
282
|
-
function isClickClackChannelNameConflict(error) {
|
|
283
|
-
if (!(error instanceof ClickClackHttpError) || error.status !== 400 && error.status !== 409) return false;
|
|
284
|
-
const message = error.message.toLowerCase();
|
|
285
|
-
return (message.includes("unique") || message.includes("duplicate")) && message.includes("channel") && /workspace.*name|name.*workspace/u.test(message);
|
|
286
|
-
}
|
|
287
|
-
/** Accepts the same bounded request-correlation shape as the ClickClack API. */
|
|
288
|
-
function normalizeClickClackCorrelationId(value) {
|
|
289
|
-
if (typeof value !== "string") return;
|
|
290
|
-
const normalized = value.trim();
|
|
291
|
-
if (!normalized || normalized.length > CLICKCLACK_CORRELATION_ID_MAX_LENGTH || !CLICKCLACK_CORRELATION_ID_PATTERN.test(normalized)) return;
|
|
292
|
-
return normalized;
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Creates a typed client for the ClickClack API using bearer-token auth.
|
|
296
|
-
*/
|
|
297
|
-
function createClickClackClient(options) {
|
|
298
|
-
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
299
|
-
const fetcher = options.fetch ?? fetch;
|
|
300
|
-
const correlationId = normalizeClickClackCorrelationId(options.correlationId);
|
|
301
|
-
const headers = {
|
|
302
|
-
Authorization: `Bearer ${options.token}`,
|
|
303
|
-
Accept: "application/json"
|
|
304
|
-
};
|
|
305
|
-
async function request(path, init = {}) {
|
|
306
|
-
const requestHeaders = new Headers(init.headers);
|
|
307
|
-
for (const [key, value] of Object.entries(headers)) requestHeaders.set(key, value);
|
|
308
|
-
if (correlationId) requestHeaders.set(CLICKCLACK_CORRELATION_ID_HEADER, correlationId);
|
|
309
|
-
if (init.body && !(init.body instanceof FormData)) requestHeaders.set("Content-Type", "application/json");
|
|
310
|
-
const response = await fetcher(`${baseUrl}${path}`, {
|
|
311
|
-
...init,
|
|
312
|
-
headers: requestHeaders
|
|
313
|
-
});
|
|
314
|
-
if (!response.ok) {
|
|
315
|
-
const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
|
|
316
|
-
throw new ClickClackHttpError(response.status, redactToolPayloadText(detail), new Headers(response.headers));
|
|
317
|
-
}
|
|
318
|
-
return await readProviderJsonResponse(response, "ClickClack response", { maxBytes: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES });
|
|
319
|
-
}
|
|
320
|
-
async function fetchEventPage(workspaceId, pageOptions = {}) {
|
|
321
|
-
const query = new URLSearchParams({ workspace_id: workspaceId });
|
|
322
|
-
if (pageOptions.afterCursor) query.set("after_cursor", pageOptions.afterCursor);
|
|
323
|
-
if (pageOptions.limit !== void 0) query.set("limit", String(pageOptions.limit));
|
|
324
|
-
if (pageOptions.includeTail) query.set("include_tail", "true");
|
|
325
|
-
const data = await request(`/api/realtime/events?${query.toString()}`);
|
|
326
|
-
return {
|
|
327
|
-
events: data.events,
|
|
328
|
-
...typeof data.tail_cursor === "string" ? { tailCursor: data.tail_cursor } : {}
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
me: async () => {
|
|
333
|
-
return (await request("/api/me")).user;
|
|
334
|
-
},
|
|
335
|
-
setBotCommands: async (commands) => {
|
|
336
|
-
return (await request("/api/bots/self/commands", {
|
|
337
|
-
method: "PUT",
|
|
338
|
-
body: JSON.stringify({ commands })
|
|
339
|
-
})).bot_commands;
|
|
340
|
-
},
|
|
341
|
-
workspaces: async () => {
|
|
342
|
-
return (await request("/api/workspaces")).workspaces;
|
|
343
|
-
},
|
|
344
|
-
channels: async (workspaceId) => {
|
|
345
|
-
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
|
|
346
|
-
},
|
|
347
|
-
createChannel: async (workspaceId, channel) => {
|
|
348
|
-
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
|
|
349
|
-
method: "POST",
|
|
350
|
-
body: JSON.stringify(channel)
|
|
351
|
-
})).channel;
|
|
352
|
-
},
|
|
353
|
-
updateChannel: async (channelId, patch) => {
|
|
354
|
-
return (await request(`/api/channels/${encodeURIComponent(channelId)}`, {
|
|
355
|
-
method: "PATCH",
|
|
356
|
-
body: JSON.stringify(patch)
|
|
357
|
-
})).channel;
|
|
358
|
-
},
|
|
359
|
-
channelMessages: async (channelId, afterSeq, limit = 20) => {
|
|
360
|
-
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
361
|
-
},
|
|
362
|
-
latestChannelMessages: async (channelId, limit = 30) => {
|
|
363
|
-
const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
|
|
364
|
-
let beforeSeq;
|
|
365
|
-
let latest = [];
|
|
366
|
-
let rootPageCount = 0;
|
|
367
|
-
let threadRequestCount = 0;
|
|
368
|
-
let truncated = false;
|
|
369
|
-
while (true) {
|
|
370
|
-
rootPageCount += 1;
|
|
371
|
-
const query = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
372
|
-
if (beforeSeq !== void 0) query.set("before_seq", String(beforeSeq));
|
|
373
|
-
const page = await request(`/api/channels/${encodeURIComponent(channelId)}/messages?${query.toString()}`);
|
|
374
|
-
for (const root of page.messages) {
|
|
375
|
-
latest = keepLatestMessages([...latest, root], boundedLimit);
|
|
376
|
-
const lastReplyAt = root.thread_state?.last_reply_at;
|
|
377
|
-
const cutoff = latest.length === boundedLimit ? latest[0]?.created_at : void 0;
|
|
378
|
-
if (!root.thread_state?.reply_count || lastReplyAt !== void 0 && cutoff !== void 0 && lastReplyAt < cutoff) continue;
|
|
379
|
-
if (threadRequestCount >= CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT) {
|
|
380
|
-
truncated = true;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
threadRequestCount += 1;
|
|
384
|
-
const threadQuery = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
385
|
-
const thread = await request(`/api/messages/${encodeURIComponent(root.id)}/thread?${threadQuery.toString()}`);
|
|
386
|
-
if (thread.replies.length < root.thread_state.reply_count) {
|
|
387
|
-
truncated = true;
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
latest = keepLatestMessages([...latest, ...thread.replies], boundedLimit);
|
|
391
|
-
}
|
|
392
|
-
if (!page.has_older) return {
|
|
393
|
-
messages: latest,
|
|
394
|
-
truncated
|
|
395
|
-
};
|
|
396
|
-
if (rootPageCount >= CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT) return {
|
|
397
|
-
messages: latest,
|
|
398
|
-
truncated: true
|
|
399
|
-
};
|
|
400
|
-
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");
|
|
401
|
-
beforeSeq = page.oldest_seq;
|
|
402
|
-
}
|
|
403
|
-
},
|
|
404
|
-
directMessages: async (conversationId, afterSeq, limit = 20) => {
|
|
405
|
-
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
406
|
-
},
|
|
407
|
-
thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
|
|
408
|
-
message: async (messageId) => {
|
|
409
|
-
return (await request(`/api/messages/${encodeURIComponent(messageId)}`)).message;
|
|
410
|
-
},
|
|
411
|
-
findMessageByNonce: async (params) => {
|
|
412
|
-
const query = new URLSearchParams({
|
|
413
|
-
workspace_id: params.workspaceId,
|
|
414
|
-
nonce: params.nonce
|
|
415
|
-
});
|
|
416
|
-
try {
|
|
417
|
-
return (await request(`/api/messages/by-nonce?${query.toString()}`)).message;
|
|
418
|
-
} catch (error) {
|
|
419
|
-
if (error instanceof ClickClackHttpError && error.status === 404) {
|
|
420
|
-
if (error.headers.get("X-ClickClack-Message-Nonce") === "supported") return;
|
|
421
|
-
throw new Error("ClickClack server does not support durable message nonce lookup", { cause: error });
|
|
422
|
-
}
|
|
423
|
-
throw error;
|
|
424
|
-
}
|
|
425
|
-
},
|
|
426
|
-
createChannelMessage: async (channelId, body, opts) => {
|
|
427
|
-
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages`, {
|
|
428
|
-
method: "POST",
|
|
429
|
-
body: JSON.stringify({
|
|
430
|
-
body,
|
|
431
|
-
...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
|
|
432
|
-
...opts?.nonce ? { nonce: opts.nonce } : {},
|
|
433
|
-
...provenanceFields(opts?.provenance)
|
|
434
|
-
})
|
|
435
|
-
})).message;
|
|
436
|
-
},
|
|
437
|
-
createThreadReply: async (messageId, body, opts) => {
|
|
438
|
-
return (await request(`/api/messages/${encodeURIComponent(messageId)}/thread/replies`, {
|
|
439
|
-
method: "POST",
|
|
440
|
-
body: JSON.stringify({
|
|
441
|
-
body,
|
|
442
|
-
...opts?.nonce ? { nonce: opts.nonce } : {},
|
|
443
|
-
...provenanceFields(opts?.provenance)
|
|
444
|
-
})
|
|
445
|
-
})).message;
|
|
446
|
-
},
|
|
447
|
-
createDirectConversation: async (workspaceId, memberIds) => {
|
|
448
|
-
return (await request("/api/dms", {
|
|
449
|
-
method: "POST",
|
|
450
|
-
body: JSON.stringify({
|
|
451
|
-
workspace_id: workspaceId,
|
|
452
|
-
member_ids: memberIds
|
|
453
|
-
})
|
|
454
|
-
})).conversation;
|
|
455
|
-
},
|
|
456
|
-
createUpload: async (params) => {
|
|
457
|
-
const form = new FormData();
|
|
458
|
-
const bytes = new Uint8Array(params.buffer);
|
|
459
|
-
form.append("file", new Blob([bytes], { type: params.contentType }), params.filename);
|
|
460
|
-
const query = new URLSearchParams({ workspace_id: params.workspaceId });
|
|
461
|
-
if (params.nonce) query.set("nonce", params.nonce);
|
|
462
|
-
return (await request(`/api/uploads?${query.toString()}`, {
|
|
463
|
-
method: "POST",
|
|
464
|
-
body: form
|
|
465
|
-
})).upload;
|
|
466
|
-
},
|
|
467
|
-
findUploadByNonce: async (params) => {
|
|
468
|
-
const query = new URLSearchParams({
|
|
469
|
-
workspace_id: params.workspaceId,
|
|
470
|
-
nonce: params.nonce
|
|
471
|
-
});
|
|
472
|
-
try {
|
|
473
|
-
return (await request(`/api/uploads/by-nonce?${query.toString()}`)).upload;
|
|
474
|
-
} catch (error) {
|
|
475
|
-
if (error instanceof ClickClackHttpError && error.status === 404) {
|
|
476
|
-
if (error.headers.get("X-ClickClack-Upload-Nonce") === "supported") return;
|
|
477
|
-
throw new Error("ClickClack server does not support durable upload nonce lookup", { cause: error });
|
|
478
|
-
}
|
|
479
|
-
throw error;
|
|
480
|
-
}
|
|
481
|
-
},
|
|
482
|
-
attachUpload: async (messageId, uploadId) => {
|
|
483
|
-
await request(`/api/messages/${encodeURIComponent(messageId)}/attachments`, {
|
|
484
|
-
method: "POST",
|
|
485
|
-
body: JSON.stringify({ upload_id: uploadId })
|
|
486
|
-
});
|
|
487
|
-
},
|
|
488
|
-
/**
|
|
489
|
-
* POSTs a durable agent activity row (agent_commentary / agent_tool)
|
|
490
|
-
* through the normal message create path. Requires a bot token carrying
|
|
491
|
-
* the agent_activity:write scope on the ClickClack side.
|
|
492
|
-
*/
|
|
493
|
-
createActivityMessage: async (params) => {
|
|
494
|
-
if (!params.channelId && !params.conversationId) throw new Error("createActivityMessage requires a channelId or conversationId");
|
|
495
|
-
return (await request(params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
|
|
496
|
-
method: "POST",
|
|
497
|
-
body: JSON.stringify({
|
|
498
|
-
body: params.body,
|
|
499
|
-
kind: params.kind,
|
|
500
|
-
turn_id: params.turnId,
|
|
501
|
-
...provenanceFields(params.provenance)
|
|
502
|
-
})
|
|
503
|
-
})).message;
|
|
504
|
-
},
|
|
505
|
-
/** PATCHes the body of an existing message (activity row coalescing). */
|
|
506
|
-
updateMessageBody: async (messageId, body) => {
|
|
507
|
-
return (await request(`/api/messages/${encodeURIComponent(messageId)}`, {
|
|
508
|
-
method: "PATCH",
|
|
509
|
-
body: JSON.stringify({ body })
|
|
510
|
-
})).message;
|
|
511
|
-
},
|
|
512
|
-
createDirectMessage: async (conversationId, body, opts) => {
|
|
513
|
-
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages`, {
|
|
514
|
-
method: "POST",
|
|
515
|
-
body: JSON.stringify({
|
|
516
|
-
body,
|
|
517
|
-
...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
|
|
518
|
-
...opts?.nonce ? { nonce: opts.nonce } : {}
|
|
519
|
-
})
|
|
520
|
-
})).message;
|
|
521
|
-
},
|
|
522
|
-
events: async (workspaceId, afterCursor) => (await fetchEventPage(workspaceId, { afterCursor })).events,
|
|
523
|
-
eventPage: fetchEventPage,
|
|
524
|
-
websocket: (workspaceId, afterCursor) => {
|
|
525
|
-
const url = new URL(`${baseUrl}/api/realtime/ws`);
|
|
526
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
527
|
-
url.searchParams.set("workspace_id", workspaceId);
|
|
528
|
-
if (afterCursor) url.searchParams.set("after_cursor", afterCursor);
|
|
529
|
-
return new WebSocket(url, {
|
|
530
|
-
headers: { Authorization: `Bearer ${options.token}` },
|
|
531
|
-
handshakeTimeout: CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS,
|
|
532
|
-
maxPayload: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
//#endregion
|
|
538
|
-
//#region extensions/clickclack/src/resolve.ts
|
|
539
|
-
/**
|
|
540
|
-
* Resolves a workspace slug/name/id from config to a ClickClack workspace id.
|
|
541
|
-
*/
|
|
542
|
-
async function resolveWorkspaceId(client, workspace) {
|
|
543
|
-
if (workspace.startsWith("wsp_")) return workspace;
|
|
544
|
-
const found = (await client.workspaces()).find((candidate) => candidate.id === workspace || candidate.slug === workspace || candidate.name === workspace);
|
|
545
|
-
if (!found) throw new Error(`ClickClack workspace not found: ${workspace}`);
|
|
546
|
-
return found.id;
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Resolves a channel name/id from config or target input to a ClickClack
|
|
550
|
-
* channel id.
|
|
551
|
-
*/
|
|
552
|
-
async function resolveChannelId(client, workspaceId, channel) {
|
|
553
|
-
if (channel.startsWith("chn_")) return channel;
|
|
554
|
-
const found = (await client.channels(workspaceId)).find((candidate) => candidate.id === channel || candidate.name === channel);
|
|
555
|
-
if (!found) throw new Error(`ClickClack channel not found: ${channel}`);
|
|
556
|
-
return found.id;
|
|
557
|
-
}
|
|
558
|
-
//#endregion
|
|
559
|
-
//#region extensions/clickclack/src/setup-contract.ts
|
|
560
|
-
const CLICKCLACK_SETUP_CODE_CLAIM_PATH = "/api/bot-setup-codes/claim";
|
|
561
|
-
const LOOPBACK_ADDRESSES = new net.BlockList();
|
|
562
|
-
LOOPBACK_ADDRESSES.addSubnet("127.0.0.0", 8, "ipv4");
|
|
563
|
-
LOOPBACK_ADDRESSES.addAddress("::1", "ipv6");
|
|
564
|
-
const BASE_PATH_SEGMENT = /^[A-Za-z0-9._~-]+$/u;
|
|
565
|
-
function isClickClackSetupLoopbackHost(hostname) {
|
|
566
|
-
const normalized = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
567
|
-
if (normalized === "localhost") return true;
|
|
568
|
-
const family = net.isIP(normalized);
|
|
569
|
-
return family !== 0 && LOOPBACK_ADDRESSES.check(normalized, family === 4 ? "ipv4" : "ipv6");
|
|
570
|
-
}
|
|
571
|
-
function requireClickClackSetupApiBaseUrl(value, label) {
|
|
572
|
-
if (!value || value !== value.trim()) throw new Error(`ClickClack ${label} is invalid`);
|
|
573
|
-
let parsed;
|
|
574
|
-
try {
|
|
575
|
-
parsed = new URL(value);
|
|
576
|
-
} catch {
|
|
577
|
-
throw new Error(`ClickClack ${label} is invalid`);
|
|
578
|
-
}
|
|
579
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname || parsed.hostname.endsWith(".") || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error(`ClickClack ${label} is invalid`);
|
|
580
|
-
if (parsed.protocol === "http:" && !isClickClackSetupLoopbackHost(parsed.hostname)) throw new Error(`ClickClack ${label} must use HTTPS unless it is on loopback`);
|
|
581
|
-
const pathname = parsed.pathname;
|
|
582
|
-
let basePath = "";
|
|
583
|
-
if (pathname !== "/") {
|
|
584
|
-
if (pathname.endsWith("/") || pathname.includes("//") || pathname.includes("\\") || pathname.slice(1).split("/").some((segment) => !BASE_PATH_SEGMENT.test(segment))) throw new Error(`ClickClack ${label} is invalid`);
|
|
585
|
-
basePath = pathname;
|
|
586
|
-
}
|
|
587
|
-
const canonical = parsed.origin + basePath;
|
|
588
|
-
if (value !== canonical) throw new Error(`ClickClack ${label} is not canonical`);
|
|
589
|
-
return canonical;
|
|
590
|
-
}
|
|
591
|
-
function requireClickClackSetupClaimUrl(value) {
|
|
592
|
-
let parsed;
|
|
593
|
-
try {
|
|
594
|
-
parsed = new URL(value);
|
|
595
|
-
} catch {
|
|
596
|
-
throw new Error("ClickClack setup URL has an invalid claim endpoint.");
|
|
597
|
-
}
|
|
598
|
-
if (parsed.username || parsed.password || parsed.search || parsed.hash || !parsed.pathname.endsWith("/api/bot-setup-codes/claim")) throw new Error("ClickClack setup URL has an invalid claim endpoint.");
|
|
599
|
-
const basePath = parsed.pathname.slice(0, -26);
|
|
600
|
-
const apiBaseUrl = requireClickClackSetupApiBaseUrl(parsed.origin + basePath, "setup URL API base");
|
|
601
|
-
const claimUrl = apiBaseUrl + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
602
|
-
if (value !== claimUrl) throw new Error("ClickClack setup URL has a non-canonical claim endpoint.");
|
|
603
|
-
return {
|
|
604
|
-
claimUrl,
|
|
605
|
-
apiBaseUrl
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
function buildClickClackSetupClaimUrl(baseUrl) {
|
|
609
|
-
return baseUrl.replace(/\/+$/u, "") + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
610
|
-
}
|
|
611
|
-
//#endregion
|
|
612
|
-
//#region extensions/clickclack/src/setup-core.ts
|
|
613
|
-
const channel$1 = "clickclack";
|
|
614
|
-
const SETUP_CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
615
|
-
const SETUP_CODE_LENGTH = 12;
|
|
616
|
-
const REQUIRED_INPUT_ERROR = "ClickClack requires --token, --base-url, and --workspace (or --use-env).";
|
|
617
|
-
const INVALID_BASE_URL_ERROR = "ClickClack base URL must be a valid http(s) URL.";
|
|
618
|
-
const SETUP_CODE_CONFLICT_ERROR = "ClickClack --code cannot be combined with --token, --token-file, or --use-env.";
|
|
619
|
-
function normalizeClickClackBaseUrl(value) {
|
|
620
|
-
const trimmed = value?.trim();
|
|
621
|
-
if (!trimmed) return;
|
|
622
|
-
try {
|
|
623
|
-
const parsed = new URL(trimmed);
|
|
624
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
|
|
625
|
-
return parsed.toString().replace(/\/+$/, "");
|
|
626
|
-
} catch {
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
function normalizeClickClackSetupCode(value) {
|
|
631
|
-
const normalized = value.trim().toUpperCase().replaceAll("-", "").replaceAll(" ", "");
|
|
632
|
-
if (normalized.length !== SETUP_CODE_LENGTH || Array.from(normalized).some((character) => !SETUP_CODE_ALPHABET.includes(character))) return;
|
|
633
|
-
return normalized;
|
|
634
|
-
}
|
|
635
|
-
function requireClickClackSetupCodeBaseUrl(value) {
|
|
636
|
-
const baseUrl = normalizeClickClackBaseUrl(value);
|
|
637
|
-
if (!baseUrl) throw new Error("ClickClack setup codes require a valid HTTP(S) base URL.");
|
|
638
|
-
return baseUrl;
|
|
639
|
-
}
|
|
640
|
-
function parseClickClackSetupCodeInput(params) {
|
|
641
|
-
const rawCode = params.code.trim();
|
|
642
|
-
if (!rawCode) throw new Error("ClickClack --code must not be empty.");
|
|
643
|
-
let code = rawCode;
|
|
644
|
-
let baseUrl;
|
|
645
|
-
if (/^[a-z][a-z\d+.-]*:\/\//iu.test(rawCode)) {
|
|
646
|
-
let setupUrl;
|
|
647
|
-
try {
|
|
648
|
-
setupUrl = new URL(rawCode);
|
|
649
|
-
} catch {
|
|
650
|
-
throw new Error("ClickClack --code must be a valid HTTP(S) setup URL or a bare setup code.");
|
|
651
|
-
}
|
|
652
|
-
if (setupUrl.protocol !== "http:" && setupUrl.protocol !== "https:") throw new Error("ClickClack setup codes require an HTTP(S) URL.");
|
|
653
|
-
if (setupUrl.username || setupUrl.password) throw new Error("ClickClack setup URLs must not include credentials.");
|
|
654
|
-
if (setupUrl.search) throw new Error("ClickClack setup URLs must not include a query.");
|
|
655
|
-
code = setupUrl.hash.slice(1);
|
|
656
|
-
if (!code) throw new Error("ClickClack setup URL is missing its #CODE fragment.");
|
|
657
|
-
setupUrl.hash = "";
|
|
658
|
-
let exactClaimUrl;
|
|
659
|
-
if (setupUrl.pathname.endsWith("/api/bot-setup-codes/claim")) {
|
|
660
|
-
const exactEndpoint = requireClickClackSetupClaimUrl(setupUrl.toString());
|
|
661
|
-
baseUrl = exactEndpoint.apiBaseUrl;
|
|
662
|
-
exactClaimUrl = exactEndpoint.claimUrl;
|
|
663
|
-
} else baseUrl = requireClickClackSetupCodeBaseUrl(setupUrl.toString());
|
|
664
|
-
if (params.baseUrl) {
|
|
665
|
-
if (requireClickClackSetupCodeBaseUrl(params.baseUrl) !== baseUrl) throw new Error("ClickClack --base-url does not match the server in the setup-code URL.");
|
|
666
|
-
}
|
|
667
|
-
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
668
|
-
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
669
|
-
return {
|
|
670
|
-
code: normalizedCode,
|
|
671
|
-
baseUrl,
|
|
672
|
-
...exactClaimUrl ? { exactClaimUrl } : {}
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
code = code.startsWith("#") ? code.slice(1) : code;
|
|
676
|
-
if (!params.baseUrl) throw new Error("A bare ClickClack setup code requires --base-url.");
|
|
677
|
-
baseUrl = requireClickClackSetupCodeBaseUrl(params.baseUrl);
|
|
678
|
-
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
679
|
-
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
680
|
-
return {
|
|
681
|
-
code: normalizedCode,
|
|
682
|
-
baseUrl
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
function formatClickClackSetupCodeClaimError(error) {
|
|
686
|
-
if (typeof error === "object" && error !== null && "status" in error) {
|
|
687
|
-
const status = error.status;
|
|
688
|
-
if (status === 404) return /* @__PURE__ */ new Error("ClickClack setup code is invalid, expired, or already used. Generate a new code and try again.");
|
|
689
|
-
if (status === 429) return /* @__PURE__ */ new Error("Too many ClickClack setup code attempts. Wait and try again.");
|
|
690
|
-
}
|
|
691
|
-
return /* @__PURE__ */ new Error(`Could not claim ClickClack setup code: ${formatErrorMessage(error)}`);
|
|
692
|
-
}
|
|
693
|
-
function applyClickClackSetupConfigPatch(params) {
|
|
694
|
-
const accountId = normalizeAccountId(params.accountId);
|
|
695
|
-
return patchScopedAccountConfig({
|
|
696
|
-
cfg: prepareScopedSetupConfig({
|
|
697
|
-
cfg: accountId === DEFAULT_ACCOUNT_ID ? params.cfg : moveSingleAccountChannelSectionToDefaultAccount({
|
|
698
|
-
cfg: params.cfg,
|
|
699
|
-
channelKey: channel$1,
|
|
700
|
-
setupSurface: clickClackSetupAdapter
|
|
701
|
-
}),
|
|
702
|
-
channelKey: channel$1,
|
|
703
|
-
accountId,
|
|
704
|
-
name: params.name,
|
|
705
|
-
migrateBaseName: accountId !== DEFAULT_ACCOUNT_ID
|
|
706
|
-
}),
|
|
707
|
-
channelKey: channel$1,
|
|
708
|
-
accountId,
|
|
709
|
-
patch: params.patch,
|
|
710
|
-
...params.clearFields ? { clearFields: params.clearFields } : {}
|
|
711
|
-
});
|
|
712
|
-
}
|
|
713
|
-
function applyClickClackCredentialConfig(params) {
|
|
714
|
-
const fieldsToClear = params.useEnv ? ["token", "tokenFile"] : params.tokenFile ? ["token"] : params.token !== void 0 ? ["tokenFile"] : [];
|
|
715
|
-
return applyClickClackSetupConfigPatch({
|
|
716
|
-
cfg: params.cfg,
|
|
717
|
-
accountId: params.accountId,
|
|
718
|
-
clearFields: fieldsToClear,
|
|
719
|
-
patch: params.useEnv ? {} : params.tokenFile ? { tokenFile: params.tokenFile } : params.token !== void 0 ? { token: params.token } : {}
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
const clickClackSetupAdapter = {
|
|
723
|
-
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
724
|
-
prepareAccountConfigInput: async ({ cfg, accountId, input }) => {
|
|
725
|
-
const setupInput = input;
|
|
726
|
-
if (!setupInput.code?.trim()) return setupInput;
|
|
727
|
-
if (setupInput.token?.trim() || setupInput.tokenFile?.trim() || setupInput.useEnv) throw new Error(SETUP_CODE_CONFLICT_ERROR);
|
|
728
|
-
let setup = parseClickClackSetupCodeInput({
|
|
729
|
-
code: setupInput.code,
|
|
730
|
-
baseUrl: setupInput.baseUrl
|
|
731
|
-
});
|
|
732
|
-
const privateApiBaseUrl = normalizeClickClackBaseUrl(resolveClickClackAccountConfig(cfg, accountId).apiBaseUrl);
|
|
733
|
-
const claimUrl = setup.exactClaimUrl && !privateApiBaseUrl ? setup.exactClaimUrl : buildClickClackSetupClaimUrl(privateApiBaseUrl ?? setup.baseUrl);
|
|
734
|
-
let claim;
|
|
735
|
-
try {
|
|
736
|
-
const { claimClickClackSetupCode } = await import("./setup-claim-CP-QZ5bt.js");
|
|
737
|
-
claim = await claimClickClackSetupCode({
|
|
738
|
-
claimUrl,
|
|
739
|
-
code: setup.code,
|
|
740
|
-
...setup.exactClaimUrl ? { expectedClaimUrl: setup.exactClaimUrl } : {}
|
|
741
|
-
});
|
|
742
|
-
} catch (error) {
|
|
743
|
-
throw formatClickClackSetupCodeClaimError(error);
|
|
744
|
-
}
|
|
745
|
-
setup = {
|
|
746
|
-
...setup,
|
|
747
|
-
baseUrl: claim.api_base_url ?? setup.baseUrl
|
|
748
|
-
};
|
|
749
|
-
const { code: _code, tokenFile: _tokenFile, useEnv: _useEnv, ...remainingInput } = setupInput;
|
|
750
|
-
return {
|
|
751
|
-
...remainingInput,
|
|
752
|
-
baseUrl: setup.baseUrl,
|
|
753
|
-
token: claim.token,
|
|
754
|
-
workspace: claim.workspace.id,
|
|
755
|
-
...claim.defaults.defaultTo !== void 0 ? { defaultTo: claim.defaults.defaultTo } : {},
|
|
756
|
-
...claim.defaults.allowFrom !== void 0 ? { allowFrom: [...claim.defaults.allowFrom] } : {},
|
|
757
|
-
...claim.defaults.agentActivity !== void 0 ? { agentActivity: claim.defaults.agentActivity } : {}
|
|
758
|
-
};
|
|
759
|
-
},
|
|
760
|
-
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
761
|
-
cfg,
|
|
762
|
-
channelKey: channel$1,
|
|
763
|
-
accountId,
|
|
764
|
-
name
|
|
765
|
-
}),
|
|
766
|
-
validateInput: createSetupInputPresenceValidator({
|
|
767
|
-
defaultAccountOnlyEnvError: "CLICKCLACK_BOT_TOKEN can only be used for the default account.",
|
|
768
|
-
whenNotUseEnv: [
|
|
769
|
-
{
|
|
770
|
-
someOf: ["token", "tokenFile"],
|
|
771
|
-
message: REQUIRED_INPUT_ERROR
|
|
772
|
-
},
|
|
773
|
-
{
|
|
774
|
-
someOf: ["baseUrl"],
|
|
775
|
-
message: REQUIRED_INPUT_ERROR
|
|
776
|
-
},
|
|
777
|
-
{
|
|
778
|
-
someOf: ["workspace"],
|
|
779
|
-
message: REQUIRED_INPUT_ERROR
|
|
780
|
-
}
|
|
781
|
-
],
|
|
782
|
-
validate: ({ cfg, accountId, input }) => {
|
|
783
|
-
const setupInput = input;
|
|
784
|
-
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl);
|
|
785
|
-
if (setupInput.baseUrl && !baseUrl) return INVALID_BASE_URL_ERROR;
|
|
786
|
-
if (!setupInput.useEnv) return null;
|
|
787
|
-
const existing = resolveClickClackAccountConfig(cfg, accountId);
|
|
788
|
-
const existingBaseUrl = normalizeClickClackBaseUrl(existing.baseUrl);
|
|
789
|
-
if (!baseUrl && existing.baseUrl?.trim() && !existingBaseUrl) return INVALID_BASE_URL_ERROR;
|
|
790
|
-
if (!baseUrl && !existingBaseUrl) return REQUIRED_INPUT_ERROR;
|
|
791
|
-
if (!setupInput.workspace?.trim() && !existing.workspace?.trim()) return REQUIRED_INPUT_ERROR;
|
|
792
|
-
return null;
|
|
793
|
-
}
|
|
794
|
-
}),
|
|
795
|
-
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
796
|
-
const setupInput = input;
|
|
797
|
-
const existing = setupInput.useEnv ? resolveClickClackAccountConfig(cfg, accountId) : void 0;
|
|
798
|
-
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl ?? existing?.baseUrl);
|
|
799
|
-
const workspace = setupInput.workspace?.trim() || existing?.workspace?.trim();
|
|
800
|
-
const tokenFile = setupInput.tokenFile?.trim();
|
|
801
|
-
const token = setupInput.token?.trim();
|
|
802
|
-
return applyClickClackCredentialConfig({
|
|
803
|
-
cfg: applyClickClackSetupConfigPatch({
|
|
804
|
-
cfg,
|
|
805
|
-
accountId,
|
|
806
|
-
name: setupInput.name,
|
|
807
|
-
patch: {
|
|
808
|
-
...baseUrl ? { baseUrl } : {},
|
|
809
|
-
...workspace ? { workspace } : {},
|
|
810
|
-
...setupInput.defaultTo?.trim() ? { defaultTo: setupInput.defaultTo.trim() } : {},
|
|
811
|
-
...setupInput.allowFrom ? { allowFrom: [...setupInput.allowFrom] } : {},
|
|
812
|
-
...setupInput.agentActivity !== void 0 ? { agentActivity: setupInput.agentActivity } : {}
|
|
813
|
-
}
|
|
814
|
-
}),
|
|
815
|
-
accountId,
|
|
816
|
-
token,
|
|
817
|
-
tokenFile,
|
|
818
|
-
useEnv: setupInput.useEnv
|
|
819
|
-
});
|
|
820
|
-
},
|
|
821
|
-
afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
|
|
822
|
-
const { verifyClickClackAccountAfterSetup } = await Promise.resolve().then(() => setup_verify_exports);
|
|
823
|
-
await verifyClickClackAccountAfterSetup({
|
|
824
|
-
cfg,
|
|
825
|
-
accountId,
|
|
826
|
-
runtime
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
const clickClackSetupContract = defineChannelSetupContract({
|
|
831
|
-
fields: {
|
|
832
|
-
code: {
|
|
833
|
-
kind: "string",
|
|
834
|
-
sensitive: true,
|
|
835
|
-
cli: {
|
|
836
|
-
flags: "--code <code>",
|
|
837
|
-
description: "ClickClack one-time setup code or setup URL"
|
|
838
|
-
}
|
|
839
|
-
},
|
|
840
|
-
token: {
|
|
841
|
-
kind: "string",
|
|
842
|
-
sensitive: true,
|
|
843
|
-
cli: {
|
|
844
|
-
flags: "--token <token>",
|
|
845
|
-
description: "ClickClack bot token"
|
|
846
|
-
}
|
|
847
|
-
},
|
|
848
|
-
tokenFile: {
|
|
849
|
-
kind: "string",
|
|
850
|
-
sensitive: true,
|
|
851
|
-
cli: {
|
|
852
|
-
flags: "--token-file <path>",
|
|
853
|
-
description: "ClickClack bot token file"
|
|
854
|
-
}
|
|
855
|
-
},
|
|
856
|
-
baseUrl: {
|
|
857
|
-
kind: "string",
|
|
858
|
-
cli: {
|
|
859
|
-
flags: "--base-url <url>",
|
|
860
|
-
description: "ClickClack API base URL"
|
|
861
|
-
}
|
|
862
|
-
},
|
|
863
|
-
workspace: {
|
|
864
|
-
kind: "string",
|
|
865
|
-
cli: {
|
|
866
|
-
flags: "--workspace <workspace>",
|
|
867
|
-
description: "ClickClack workspace id, slug, or name"
|
|
868
|
-
}
|
|
869
|
-
},
|
|
870
|
-
defaultTo: {
|
|
871
|
-
kind: "string",
|
|
872
|
-
cli: {
|
|
873
|
-
flags: "--default-to <target>",
|
|
874
|
-
description: "Default ClickClack target"
|
|
875
|
-
}
|
|
876
|
-
},
|
|
877
|
-
allowFrom: {
|
|
878
|
-
kind: "string-list",
|
|
879
|
-
cli: {
|
|
880
|
-
flags: "--allow-from <ids>",
|
|
881
|
-
description: "Allowed ClickClack senders"
|
|
882
|
-
}
|
|
883
|
-
},
|
|
884
|
-
agentActivity: {
|
|
885
|
-
kind: "boolean",
|
|
886
|
-
cli: {
|
|
887
|
-
flags: "--agent-activity",
|
|
888
|
-
description: "Enable ClickClack agent activity"
|
|
889
|
-
}
|
|
890
|
-
},
|
|
891
|
-
useEnv: {
|
|
892
|
-
kind: "boolean",
|
|
893
|
-
cli: {
|
|
894
|
-
flags: "--use-env",
|
|
895
|
-
description: "Use CLICKCLACK_BOT_TOKEN"
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
legacyAdapter: clickClackSetupAdapter
|
|
900
|
-
});
|
|
901
|
-
//#endregion
|
|
902
|
-
//#region extensions/clickclack/src/setup-verify.ts
|
|
903
|
-
var setup_verify_exports = /* @__PURE__ */ __exportAll({
|
|
904
|
-
checkClickClackSetupConnection: () => checkClickClackSetupConnection,
|
|
905
|
-
verifyClickClackAccountAfterSetup: () => verifyClickClackAccountAfterSetup
|
|
906
|
-
});
|
|
907
|
-
const GATEWAY_RUNNING_MESSAGE = "OpenClaw is running — ClickClack will connect automatically.";
|
|
908
|
-
const GATEWAY_NOT_RUNNING_MESSAGE = "Start OpenClaw to connect: openclaw gateway";
|
|
909
|
-
const GATEWAY_UNKNOWN_MESSAGE = "If OpenClaw is running it connects automatically; otherwise start it with: openclaw gateway";
|
|
910
|
-
function isHttpStatus(error, status) {
|
|
911
|
-
return typeof error === "object" && error !== null && "status" in error && error.status === status;
|
|
912
|
-
}
|
|
913
|
-
function isWorkspaceNotFound(error) {
|
|
914
|
-
return error instanceof Error && error.message.startsWith("ClickClack workspace not found:");
|
|
915
|
-
}
|
|
916
|
-
function usesUnavailableImplicitEnvToken(account, tokenOverride) {
|
|
917
|
-
return account.accountId === DEFAULT_ACCOUNT_ID && Boolean(account.baseUrl && account.workspace) && !tokenOverride && !account.token && !hasConfiguredSecretInput(account.config.token) && !account.config.tokenFile?.trim();
|
|
918
|
-
}
|
|
919
|
-
async function checkClickClackSetupConnection(params) {
|
|
920
|
-
let workspaceInput = "";
|
|
921
|
-
try {
|
|
922
|
-
const account = resolveClickClackAccount({
|
|
923
|
-
cfg: params.cfg,
|
|
924
|
-
accountId: params.accountId
|
|
925
|
-
});
|
|
926
|
-
workspaceInput = account.workspace;
|
|
927
|
-
const token = params.token?.trim() || account.token;
|
|
928
|
-
if (usesUnavailableImplicitEnvToken(account, token)) return { status: "skipped-env-token" };
|
|
929
|
-
if (!account.baseUrl || !account.workspace || !token) return { status: "skipped-unconfigured" };
|
|
930
|
-
const client = createClickClackClient({
|
|
931
|
-
baseUrl: account.apiEndpoint,
|
|
932
|
-
token
|
|
933
|
-
});
|
|
934
|
-
const me = await client.me();
|
|
935
|
-
const workspaceId = await resolveWorkspaceId(client, account.workspace);
|
|
936
|
-
const workspace = (await client.workspaces()).find((candidate) => candidate.id === workspaceId);
|
|
937
|
-
if (!workspace) throw new Error(`ClickClack workspace not found: ${account.workspace}`);
|
|
938
|
-
return {
|
|
939
|
-
status: "connected",
|
|
940
|
-
handle: me.handle,
|
|
941
|
-
workspaceName: workspace.name
|
|
942
|
-
};
|
|
943
|
-
} catch (error) {
|
|
944
|
-
if (isHttpStatus(error, 401)) return { status: "invalid-token" };
|
|
945
|
-
if (isWorkspaceNotFound(error)) return {
|
|
946
|
-
status: "workspace-not-found",
|
|
947
|
-
workspace: workspaceInput
|
|
948
|
-
};
|
|
949
|
-
return {
|
|
950
|
-
status: "failed",
|
|
951
|
-
error: formatErrorMessage(error)
|
|
952
|
-
};
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
function isGatewayNotRunningError(error) {
|
|
956
|
-
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;
|
|
957
|
-
const message = formatErrorMessage(error).toLowerCase();
|
|
958
|
-
return message.includes("econnrefused") || message.includes("connection refused");
|
|
959
|
-
}
|
|
960
|
-
async function probeClickClackGatewayStatus() {
|
|
961
|
-
try {
|
|
962
|
-
const { callGatewayFromCli } = await import("openclaw/plugin-sdk/gateway-runtime");
|
|
963
|
-
await callGatewayFromCli("health", {
|
|
964
|
-
timeout: "1000",
|
|
965
|
-
json: true
|
|
966
|
-
}, void 0, {
|
|
967
|
-
expectFinal: false,
|
|
968
|
-
progress: false
|
|
969
|
-
});
|
|
970
|
-
return "running";
|
|
971
|
-
} catch (error) {
|
|
972
|
-
return isGatewayNotRunningError(error) ? "not-running" : "unavailable";
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
function formatClickClackConnectionLog(result) {
|
|
976
|
-
switch (result.status) {
|
|
977
|
-
case "connected": return `Connected as @${result.handle} — workspace ${result.workspaceName} resolved.`;
|
|
978
|
-
case "invalid-token": return "ClickClack rejected the bot token (401). Copy a current token and rerun setup.";
|
|
979
|
-
case "workspace-not-found": return `Workspace "${result.workspace}" was not found. Check the id, slug, or name, list available workspaces, and rerun setup.`;
|
|
980
|
-
case "failed": return `Connection check failed: ${result.error}. Setup was saved; fix the connection and rerun setup.`;
|
|
981
|
-
case "skipped-env-token": return "Token comes from CLICKCLACK_BOT_TOKEN; verification skipped.";
|
|
982
|
-
case "skipped-unconfigured": return;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
function formatClickClackGatewayLog(status) {
|
|
986
|
-
switch (status) {
|
|
987
|
-
case "running": return GATEWAY_RUNNING_MESSAGE;
|
|
988
|
-
case "not-running": return GATEWAY_NOT_RUNNING_MESSAGE;
|
|
989
|
-
case "unavailable": return GATEWAY_UNKNOWN_MESSAGE;
|
|
990
|
-
}
|
|
991
|
-
return GATEWAY_UNKNOWN_MESSAGE;
|
|
992
|
-
}
|
|
993
|
-
async function verifyClickClackAccountAfterSetup(params) {
|
|
994
|
-
try {
|
|
995
|
-
const message = formatClickClackConnectionLog(await checkClickClackSetupConnection({
|
|
996
|
-
cfg: params.cfg,
|
|
997
|
-
accountId: params.accountId
|
|
998
|
-
}));
|
|
999
|
-
if (message) params.runtime.log(message);
|
|
1000
|
-
} catch (error) {
|
|
1001
|
-
params.runtime.log(`Connection check failed: ${formatErrorMessage(error)}. Setup was saved; fix the connection and rerun setup.`);
|
|
1002
|
-
}
|
|
1003
|
-
try {
|
|
1004
|
-
const status = await probeClickClackGatewayStatus();
|
|
1005
|
-
params.runtime.log(formatClickClackGatewayLog(status));
|
|
1006
|
-
} catch {
|
|
1007
|
-
params.runtime.log(GATEWAY_UNKNOWN_MESSAGE);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
//#endregion
|
|
1011
|
-
//#region extensions/clickclack/src/setup-surface.ts
|
|
1012
|
-
const t = createSetupTranslator();
|
|
1013
|
-
const channel = "clickclack";
|
|
1014
|
-
function hasConfiguredClickClackCredential(account) {
|
|
1015
|
-
return hasConfiguredSecretInput(account.config.token) || Boolean(account.config.tokenFile?.trim());
|
|
1016
|
-
}
|
|
1017
|
-
function isClickClackSetupConfigured(account) {
|
|
1018
|
-
return Boolean(account.baseUrl && account.workspace && (account.token || hasConfiguredClickClackCredential(account)));
|
|
1019
|
-
}
|
|
1020
|
-
const clickClackSetupWizard = {
|
|
1021
|
-
channel,
|
|
1022
|
-
status: createStandardChannelSetupStatus({
|
|
1023
|
-
channelLabel: "ClickClack",
|
|
1024
|
-
configuredLabel: t("wizard.channels.statusConfigured"),
|
|
1025
|
-
unconfiguredLabel: t("wizard.channels.statusNeedsSetup"),
|
|
1026
|
-
configuredHint: t("wizard.channels.statusSelfHostedChat"),
|
|
1027
|
-
unconfiguredHint: t("wizard.channels.statusNeedsSetup"),
|
|
1028
|
-
configuredScore: 2,
|
|
1029
|
-
unconfiguredScore: 1,
|
|
1030
|
-
resolveConfigured: ({ cfg, accountId }) => (accountId ? [accountId] : listClickClackAccountIds(cfg)).some((resolvedAccountId) => isClickClackSetupConfigured(resolveClickClackAccount({
|
|
1031
|
-
cfg,
|
|
1032
|
-
accountId: resolvedAccountId
|
|
1033
|
-
})))
|
|
1034
|
-
}),
|
|
1035
|
-
introNote: {
|
|
1036
|
-
title: t("wizard.clickclack.botTokenTitle"),
|
|
1037
|
-
lines: [t("wizard.clickclack.helpCreateToken"), t("wizard.channels.docs", { link: formatDocsLink("/channels/clickclack", "clickclack") })],
|
|
1038
|
-
shouldShow: ({ cfg, accountId }) => !isClickClackSetupConfigured(resolveClickClackAccount({
|
|
1039
|
-
cfg,
|
|
1040
|
-
accountId
|
|
1041
|
-
}))
|
|
1042
|
-
},
|
|
1043
|
-
credentials: [defineTokenCredential({
|
|
1044
|
-
inputKey: "token",
|
|
1045
|
-
configKey: "token",
|
|
1046
|
-
configuredFields: ["token", "tokenFile"],
|
|
1047
|
-
providerHint: channel,
|
|
1048
|
-
credentialLabel: t("wizard.clickclack.botToken"),
|
|
1049
|
-
preferredEnvVar: "CLICKCLACK_BOT_TOKEN",
|
|
1050
|
-
envPrompt: t("wizard.clickclack.envPrompt"),
|
|
1051
|
-
keepPrompt: t("wizard.clickclack.botTokenKeep"),
|
|
1052
|
-
inputPrompt: t("wizard.clickclack.botTokenInput"),
|
|
1053
|
-
allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1,
|
|
1054
|
-
resolveAccount: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
1055
|
-
cfg,
|
|
1056
|
-
accountId
|
|
1057
|
-
}),
|
|
1058
|
-
hasConfiguredValue: hasConfiguredClickClackCredential,
|
|
1059
|
-
resolvedValue: (account) => account.token || void 0,
|
|
1060
|
-
envValue: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1 ? process.env.CLICKCLACK_BOT_TOKEN?.trim() || void 0 : void 0,
|
|
1061
|
-
patchAccount: ({ cfg, accountId, mode, patch }) => mode === "env" ? applyClickClackCredentialConfig({
|
|
1062
|
-
cfg,
|
|
1063
|
-
accountId,
|
|
1064
|
-
useEnv: true
|
|
1065
|
-
}) : applyClickClackCredentialConfig({
|
|
1066
|
-
cfg,
|
|
1067
|
-
accountId,
|
|
1068
|
-
...patch
|
|
1069
|
-
}),
|
|
1070
|
-
useEnv: { clearFields: ["token", "tokenFile"] },
|
|
1071
|
-
set: { clearFields: ["tokenFile"] }
|
|
1072
|
-
})],
|
|
1073
|
-
textInputs: [baseUrlTextInput({
|
|
1074
|
-
inputKey: "baseUrl",
|
|
1075
|
-
configKey: "baseUrl",
|
|
1076
|
-
message: t("wizard.clickclack.baseUrlPrompt"),
|
|
1077
|
-
resolveAccount: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
1078
|
-
cfg,
|
|
1079
|
-
accountId
|
|
1080
|
-
}),
|
|
1081
|
-
currentValue: (account) => account.baseUrl || void 0,
|
|
1082
|
-
includeInitialValue: true,
|
|
1083
|
-
validate: (value) => normalizeClickClackBaseUrl(value) ? void 0 : "ClickClack server URL must be a valid http(s) URL.",
|
|
1084
|
-
normalize: (value) => normalizeClickClackBaseUrl(value) ?? value.trim(),
|
|
1085
|
-
patchAccount: ({ cfg, accountId, patch }) => applyClickClackSetupConfigPatch({
|
|
1086
|
-
cfg,
|
|
1087
|
-
accountId,
|
|
1088
|
-
patch
|
|
1089
|
-
})
|
|
1090
|
-
}), {
|
|
1091
|
-
inputKey: "workspace",
|
|
1092
|
-
message: t("wizard.clickclack.workspacePrompt"),
|
|
1093
|
-
helpTitle: t("wizard.clickclack.workspacePrompt"),
|
|
1094
|
-
helpLines: [t("wizard.clickclack.workspaceHelp")],
|
|
1095
|
-
currentValue: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
1096
|
-
cfg,
|
|
1097
|
-
accountId
|
|
1098
|
-
}).workspace || void 0,
|
|
1099
|
-
initialValue: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
1100
|
-
cfg,
|
|
1101
|
-
accountId
|
|
1102
|
-
}).workspace || void 0,
|
|
1103
|
-
validate: ({ value }) => value.trim() ? void 0 : "Required",
|
|
1104
|
-
normalizeValue: ({ value }) => value.trim(),
|
|
1105
|
-
applySet: async ({ cfg, accountId, value }) => applyClickClackSetupConfigPatch({
|
|
1106
|
-
cfg,
|
|
1107
|
-
accountId,
|
|
1108
|
-
patch: { workspace: value }
|
|
1109
|
-
})
|
|
1110
|
-
}],
|
|
1111
|
-
finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
|
|
1112
|
-
const result = await checkClickClackSetupConnection({
|
|
1113
|
-
cfg,
|
|
1114
|
-
accountId,
|
|
1115
|
-
token: credentialValues.token
|
|
1116
|
-
});
|
|
1117
|
-
if (result.status === "connected") {
|
|
1118
|
-
await prompter.note(t("wizard.clickclack.connected", {
|
|
1119
|
-
handle: result.handle,
|
|
1120
|
-
workspace: result.workspaceName
|
|
1121
|
-
}), t("wizard.clickclack.connectionTitle"));
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
if (result.status === "skipped-env-token" || result.status === "skipped-unconfigured") return;
|
|
1125
|
-
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 });
|
|
1126
|
-
await prompter.note(message, t("wizard.clickclack.validationWarningTitle"));
|
|
1127
|
-
},
|
|
1128
|
-
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
|
|
1129
|
-
};
|
|
1130
|
-
//#endregion
|
|
1131
|
-
export { resolveDefaultClickClackAccountId as S, clickClackMeta as _, buildClickClackSetupClaimUrl as a, listEnabledClickClackAccounts as b, resolveChannelId as c, createClickClackClient as d, isClickClackChannelNameConflict as f, clickClackConfigAdapter as g, CLICKCLACK_CHANNEL_ID as h, CLICKCLACK_SETUP_CODE_CLAIM_PATH as i, resolveWorkspaceId as l, clickClackConfigSchema as m, clickClackSetupAdapter as n, isClickClackSetupLoopbackHost as o, normalizeClickClackCorrelationId as p, clickClackSetupContract as r, requireClickClackSetupApiBaseUrl as s, clickClackSetupWizard as t, ClickClackHttpError as u, DEFAULT_ACCOUNT_ID$2 as v, resolveClickClackAccount as x, listClickClackAccountIds as y };
|