@openclaw/clickclack 2026.7.2-beta.3 → 2026.7.2-beta.4
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 +120 -0
- package/dist/api.js +2 -2
- package/dist/{channel-CEYthZ9N.js → channel-BDq0KWDB.js} +399 -13
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +48 -0
- package/dist/index.js +3 -1
- package/dist/runtime-api-CV1cOqFO.js +782 -0
- package/dist/runtime-api.js +4 -4
- package/dist/{setup-claim-BUWvZjtu.js → setup-claim-CJIUfZS7.js} +33 -11
- package/dist/setup-plugin-api.js +2 -1
- package/dist/{setup-surface-CNjp76Xx.js → setup-surface-Baf26N-D.js} +275 -37
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/npm-shrinkwrap.json +7 -7
- package/openclaw.plugin.json +49 -13
- package/package.json +91 -15
package/dist/runtime-api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./api.js";
|
|
4
|
-
export { createClickClackClient, parseClickClackTarget, resolveClickClackAccount, setClickClackRuntime };
|
|
1
|
+
import { d as createClickClackClient, x as resolveClickClackAccount } from "./setup-surface-Baf26N-D.js";
|
|
2
|
+
import { T as setClickClackRuntime, r as parseClickClackTarget } from "./channel-BDq0KWDB.js";
|
|
3
|
+
import { t as registerClickClackDiscussions } from "./runtime-api-CV1cOqFO.js";
|
|
4
|
+
export { createClickClackClient, parseClickClackTarget, registerClickClackDiscussions, resolveClickClackAccount, setClickClackRuntime };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { a as buildClickClackSetupClaimUrl, o as isClickClackSetupLoopbackHost, s as requireClickClackSetupApiBaseUrl } from "./setup-surface-Baf26N-D.js";
|
|
1
2
|
import { createProviderOperationDeadline, readProviderJsonResponse, readResponseTextLimited, resolveProviderOperationTimeoutMs } from "openclaw/plugin-sdk/provider-http";
|
|
2
|
-
import { fetchWithSsrFGuard,
|
|
3
|
+
import { fetchWithSsrFGuard, resolvePinnedHostnameWithPolicy, ssrfPolicyFromHttpBaseUrlAllowedHostname } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
3
4
|
import { withTimeout } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
4
5
|
//#region extensions/clickclack/src/setup-claim.ts
|
|
5
6
|
const CLICKCLACK_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
|
@@ -20,7 +21,7 @@ function requireString(record, key, label) {
|
|
|
20
21
|
if (typeof value !== "string" || !value.trim()) throw new Error(`ClickClack setup code claim returned invalid ${label}.${key}`);
|
|
21
22
|
return value;
|
|
22
23
|
}
|
|
23
|
-
function parseClickClackSetupCodeClaim(value) {
|
|
24
|
+
function parseClickClackSetupCodeClaim(value, expectedClaimUrl) {
|
|
24
25
|
const claim = requireRecord(value, "response");
|
|
25
26
|
const bot = requireRecord(claim.bot, "bot");
|
|
26
27
|
const workspace = requireRecord(claim.workspace, "workspace");
|
|
@@ -31,7 +32,23 @@ function parseClickClackSetupCodeClaim(value) {
|
|
|
31
32
|
if (defaultTo !== void 0 && typeof defaultTo !== "string") throw new Error("ClickClack setup code claim returned invalid defaults.defaultTo");
|
|
32
33
|
if (allowFrom !== void 0 && (!Array.isArray(allowFrom) || !allowFrom.every((entry) => typeof entry === "string"))) throw new Error("ClickClack setup code claim returned invalid defaults.allowFrom");
|
|
33
34
|
if (agentActivity !== void 0 && typeof agentActivity !== "boolean") throw new Error("ClickClack setup code claim returned invalid defaults.agentActivity");
|
|
35
|
+
const contractVersion = claim.contract_version;
|
|
36
|
+
const apiBaseUrlValue = claim.api_base_url;
|
|
37
|
+
const hasContractMetadata = contractVersion !== void 0 || apiBaseUrlValue !== void 0;
|
|
38
|
+
if (expectedClaimUrl && !hasContractMetadata) throw new Error("ClickClack setup code claim returned a legacy response for an exact endpoint");
|
|
39
|
+
let contract = {};
|
|
40
|
+
if (hasContractMetadata) {
|
|
41
|
+
if (contractVersion !== 1 || typeof apiBaseUrlValue !== "string") throw new Error("ClickClack setup code claim returned invalid v1 contract metadata");
|
|
42
|
+
const apiBaseUrl = requireClickClackSetupApiBaseUrl(apiBaseUrlValue, "setup code claim response.api_base_url");
|
|
43
|
+
const canonicalClaimUrl = buildClickClackSetupClaimUrl(apiBaseUrl);
|
|
44
|
+
if (expectedClaimUrl && expectedClaimUrl !== canonicalClaimUrl) throw new Error("ClickClack setup code claim returned an API base that does not match the claim URL");
|
|
45
|
+
contract = {
|
|
46
|
+
contract_version: 1,
|
|
47
|
+
api_base_url: apiBaseUrl
|
|
48
|
+
};
|
|
49
|
+
}
|
|
34
50
|
return {
|
|
51
|
+
...contract,
|
|
35
52
|
token: requireString(claim, "token", "response"),
|
|
36
53
|
bot: {
|
|
37
54
|
id: requireString(bot, "id", "bot"),
|
|
@@ -53,30 +70,35 @@ function parseClickClackSetupCodeClaim(value) {
|
|
|
53
70
|
}
|
|
54
71
|
/** Claims a one-time setup code without sending any existing bot credential. */
|
|
55
72
|
async function claimClickClackSetupCode(params) {
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
let parsedClaimUrl;
|
|
74
|
+
try {
|
|
75
|
+
parsedClaimUrl = new URL(params.claimUrl);
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error("ClickClack setup code claim URL must be a valid HTTP(S) endpoint.");
|
|
78
|
+
}
|
|
79
|
+
if (parsedClaimUrl.protocol !== "http:" && parsedClaimUrl.protocol !== "https:" || parsedClaimUrl.username || parsedClaimUrl.password || parsedClaimUrl.search || parsedClaimUrl.hash || !parsedClaimUrl.pathname.endsWith("/api/bot-setup-codes/claim")) throw new Error("ClickClack setup code claim URL must be a valid HTTP(S) endpoint.");
|
|
58
80
|
const deadline = createProviderOperationDeadline({
|
|
59
81
|
label: "ClickClack setup code claim",
|
|
60
82
|
timeoutMs: CLICKCLACK_SETUP_CODE_CLAIM_TIMEOUT_MS
|
|
61
83
|
});
|
|
62
84
|
let pinnedHttpTarget;
|
|
63
|
-
if (
|
|
85
|
+
if (parsedClaimUrl.protocol === "http:") {
|
|
64
86
|
const resolveTimeoutMs = resolveProviderOperationTimeoutMs({
|
|
65
87
|
deadline,
|
|
66
88
|
defaultTimeoutMs: CLICKCLACK_SETUP_CODE_CLAIM_TIMEOUT_MS
|
|
67
89
|
});
|
|
68
|
-
const pinned = await withTimeout(resolvePinnedHostnameWithPolicy(
|
|
90
|
+
const pinned = await withTimeout(resolvePinnedHostnameWithPolicy(parsedClaimUrl.hostname, {
|
|
69
91
|
lookupFn: params.lookupFn,
|
|
70
92
|
policy: { dangerouslyAllowPrivateNetwork: true }
|
|
71
93
|
}), resolveTimeoutMs, { message: `ClickClack setup code claim timed out after ${CLICKCLACK_SETUP_CODE_CLAIM_TIMEOUT_MS}ms` });
|
|
72
|
-
if (!pinned.addresses.every((address) =>
|
|
94
|
+
if (!pinned.addresses.every((address) => isClickClackSetupLoopbackHost(address))) throw new Error("ClickClack setup codes require HTTPS unless the server is on loopback.");
|
|
73
95
|
pinnedHttpTarget = {
|
|
74
96
|
hostname: pinned.hostname,
|
|
75
97
|
addresses: pinned.addresses
|
|
76
98
|
};
|
|
77
99
|
}
|
|
78
100
|
const { response, release } = await fetchWithSsrFGuard({
|
|
79
|
-
url:
|
|
101
|
+
url: params.claimUrl,
|
|
80
102
|
fetchImpl: params.fetch,
|
|
81
103
|
init: {
|
|
82
104
|
method: "POST",
|
|
@@ -91,8 +113,8 @@ async function claimClickClackSetupCode(params) {
|
|
|
91
113
|
deadline,
|
|
92
114
|
defaultTimeoutMs: CLICKCLACK_SETUP_CODE_CLAIM_TIMEOUT_MS
|
|
93
115
|
}),
|
|
94
|
-
requireHttps:
|
|
95
|
-
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(
|
|
116
|
+
requireHttps: parsedClaimUrl.protocol === "https:",
|
|
117
|
+
policy: ssrfPolicyFromHttpBaseUrlAllowedHostname(params.claimUrl),
|
|
96
118
|
lookupFn: params.lookupFn,
|
|
97
119
|
...pinnedHttpTarget ? { dispatcherPolicy: {
|
|
98
120
|
mode: "direct",
|
|
@@ -105,7 +127,7 @@ async function claimClickClackSetupCode(params) {
|
|
|
105
127
|
const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
|
|
106
128
|
throw new ClickClackSetupCodeClaimError(response.status, detail);
|
|
107
129
|
}
|
|
108
|
-
return parseClickClackSetupCodeClaim(await readProviderJsonResponse(response, "ClickClack setup code claim", { maxBytes: CLICKCLACK_SETUP_CODE_CLAIM_JSON_LIMIT_BYTES }));
|
|
130
|
+
return parseClickClackSetupCodeClaim(await readProviderJsonResponse(response, "ClickClack setup code claim", { maxBytes: CLICKCLACK_SETUP_CODE_CLAIM_JSON_LIMIT_BYTES }), params.expectedClaimUrl);
|
|
109
131
|
} finally {
|
|
110
132
|
await release();
|
|
111
133
|
}
|
package/dist/setup-plugin-api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as clickClackMeta, g as clickClackConfigAdapter, m as clickClackConfigSchema, n as clickClackSetupAdapter, r as clickClackSetupContract, t as clickClackSetupWizard } from "./setup-surface-Baf26N-D.js";
|
|
2
2
|
//#region extensions/clickclack/src/channel.setup.ts
|
|
3
3
|
const clickClackSetupPlugin = {
|
|
4
4
|
id: "clickclack",
|
|
@@ -12,6 +12,7 @@ const clickClackSetupPlugin = {
|
|
|
12
12
|
configSchema: clickClackConfigSchema,
|
|
13
13
|
config: clickClackConfigAdapter,
|
|
14
14
|
setup: clickClackSetupAdapter,
|
|
15
|
+
setupContract: clickClackSetupContract,
|
|
15
16
|
setupWizard: clickClackSetupWizard
|
|
16
17
|
};
|
|
17
18
|
//#endregion
|
|
@@ -11,10 +11,13 @@ import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
|
11
11
|
import { buildChannelConfigSchema, buildMultiAccountChannelSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
14
|
+
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
14
15
|
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
15
16
|
import { WebSocket } from "ws";
|
|
17
|
+
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
16
18
|
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
19
|
import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime";
|
|
20
|
+
import net from "node:net";
|
|
18
21
|
//#region \0rolldown/runtime.js
|
|
19
22
|
var __defProp = Object.defineProperty;
|
|
20
23
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -35,6 +38,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
35
38
|
const DEFAULT_RECONNECT_MS = 1500;
|
|
36
39
|
const MIN_RECONNECT_MS = 100;
|
|
37
40
|
const MAX_RECONNECT_MS = 6e4;
|
|
41
|
+
const DEFAULT_DISCUSSIONS_SECTION = "Sessions";
|
|
38
42
|
const { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resolveDefaultClickClackAccountId } = createAccountListHelpers("clickclack", {
|
|
39
43
|
normalizeAccountId,
|
|
40
44
|
hasImplicitDefaultAccount: (cfg) => {
|
|
@@ -49,6 +53,7 @@ function resolveClickClackAccountConfig(cfg, accountId) {
|
|
|
49
53
|
accounts: channel?.accounts,
|
|
50
54
|
accountId,
|
|
51
55
|
omitKeys: ["defaultAccount"],
|
|
56
|
+
nestedObjectKeys: ["discussions"],
|
|
52
57
|
normalizeAccountId
|
|
53
58
|
});
|
|
54
59
|
const account = resolveNormalizedAccountEntry(channel?.accounts, accountId, normalizeAccountId);
|
|
@@ -108,6 +113,9 @@ function resolveClickClackAccount(params) {
|
|
|
108
113
|
env: params.env
|
|
109
114
|
});
|
|
110
115
|
const workspace = merged.workspace?.trim() ?? "";
|
|
116
|
+
const discussionsWorkspace = merged.discussions?.workspace?.trim() || workspace;
|
|
117
|
+
const controlUrlBase = merged.discussions?.controlUrlBase?.trim();
|
|
118
|
+
const apiEndpoint = merged.apiBaseUrl?.trim().replace(/\/$/, "") || baseUrl;
|
|
111
119
|
return {
|
|
112
120
|
accountId,
|
|
113
121
|
enabled,
|
|
@@ -121,7 +129,6 @@ function resolveClickClackAccount(params) {
|
|
|
121
129
|
replyMode: merged.replyMode === "model" ? "model" : "agent",
|
|
122
130
|
model: normalizeOptionalString(merged.model),
|
|
123
131
|
systemPrompt: normalizeOptionalString(merged.systemPrompt),
|
|
124
|
-
timeoutSeconds: merged.timeoutSeconds,
|
|
125
132
|
toolsAllow: merged.toolsAllow,
|
|
126
133
|
defaultTo: merged.defaultTo?.trim() || "channel:general",
|
|
127
134
|
allowFrom: merged.allowFrom ?? ["*"],
|
|
@@ -131,10 +138,17 @@ function resolveClickClackAccount(params) {
|
|
|
131
138
|
}),
|
|
132
139
|
agentActivity: merged.agentActivity === true,
|
|
133
140
|
commandMenu: merged.commandMenu !== false,
|
|
141
|
+
discussions: {
|
|
142
|
+
enabled: merged.discussions?.enabled === true,
|
|
143
|
+
workspace: discussionsWorkspace,
|
|
144
|
+
...controlUrlBase ? { controlUrlBase } : {},
|
|
145
|
+
section: merged.discussions?.section?.trim() || DEFAULT_DISCUSSIONS_SECTION
|
|
146
|
+
},
|
|
134
147
|
config: {
|
|
135
148
|
...merged,
|
|
136
149
|
allowFrom: merged.allowFrom ?? ["*"]
|
|
137
|
-
}
|
|
150
|
+
},
|
|
151
|
+
apiEndpoint
|
|
138
152
|
};
|
|
139
153
|
}
|
|
140
154
|
/**
|
|
@@ -177,6 +191,7 @@ const ClickClackAccountConfigSchema = z.object({
|
|
|
177
191
|
name: z.string().optional(),
|
|
178
192
|
enabled: z.boolean().optional(),
|
|
179
193
|
baseUrl: z.string().url().optional(),
|
|
194
|
+
apiBaseUrl: z.string().url().optional(),
|
|
180
195
|
token: buildSecretInputSchema().optional(),
|
|
181
196
|
tokenFile: z.string().optional(),
|
|
182
197
|
workspace: z.string().optional(),
|
|
@@ -185,13 +200,18 @@ const ClickClackAccountConfigSchema = z.object({
|
|
|
185
200
|
replyMode: z.enum(["agent", "model"]).optional(),
|
|
186
201
|
model: z.string().optional(),
|
|
187
202
|
systemPrompt: z.string().optional(),
|
|
188
|
-
timeoutSeconds: z.number().int().min(1).max(3600).optional(),
|
|
189
203
|
toolsAllow: z.array(z.string()).optional(),
|
|
190
204
|
defaultTo: z.string().optional(),
|
|
191
205
|
allowFrom: z.array(z.string()).optional(),
|
|
192
206
|
reconnectMs: z.number().int().min(100).max(6e4).optional(),
|
|
193
207
|
agentActivity: z.boolean().optional(),
|
|
194
|
-
commandMenu: z.boolean().optional()
|
|
208
|
+
commandMenu: z.boolean().optional(),
|
|
209
|
+
discussions: z.object({
|
|
210
|
+
enabled: z.boolean().optional(),
|
|
211
|
+
workspace: z.string().optional(),
|
|
212
|
+
controlUrlBase: z.string().url().optional(),
|
|
213
|
+
section: z.string().optional()
|
|
214
|
+
}).strict().optional()
|
|
195
215
|
}).strict();
|
|
196
216
|
/**
|
|
197
217
|
* Config schema exported to core so `openclaw doctor` and config validation
|
|
@@ -222,6 +242,15 @@ const CLICKCLACK_CORRELATION_ID_PATTERN = /^[A-Za-z0-9._:-]+$/u;
|
|
|
222
242
|
const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
223
243
|
const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
224
244
|
const CLICKCLACK_WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 3e4;
|
|
245
|
+
const CLICKCLACK_MESSAGE_PAGE_LIMIT = 200;
|
|
246
|
+
const CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT = 8;
|
|
247
|
+
const CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT = 24;
|
|
248
|
+
function compareMessages(left, right) {
|
|
249
|
+
return left.created_at.localeCompare(right.created_at) || left.id.localeCompare(right.id);
|
|
250
|
+
}
|
|
251
|
+
function keepLatestMessages(messages, limit) {
|
|
252
|
+
return messages.toSorted(compareMessages).slice(-limit);
|
|
253
|
+
}
|
|
225
254
|
var ClickClackHttpError = class extends Error {
|
|
226
255
|
constructor(status, detail, headers) {
|
|
227
256
|
super(`ClickClack ${status}: ${detail}`);
|
|
@@ -229,6 +258,12 @@ var ClickClackHttpError = class extends Error {
|
|
|
229
258
|
this.headers = headers;
|
|
230
259
|
}
|
|
231
260
|
};
|
|
261
|
+
/** Matches the workspace/name uniqueness error returned by current ClickClack servers. */
|
|
262
|
+
function isClickClackChannelNameConflict(error) {
|
|
263
|
+
if (!(error instanceof ClickClackHttpError) || error.status !== 400 && error.status !== 409) return false;
|
|
264
|
+
const message = error.message.toLowerCase();
|
|
265
|
+
return (message.includes("unique") || message.includes("duplicate")) && message.includes("channel") && /workspace.*name|name.*workspace/u.test(message);
|
|
266
|
+
}
|
|
232
267
|
/** Accepts the same bounded request-correlation shape as the ClickClack API. */
|
|
233
268
|
function normalizeClickClackCorrelationId(value) {
|
|
234
269
|
if (typeof value !== "string") return;
|
|
@@ -258,7 +293,7 @@ function createClickClackClient(options) {
|
|
|
258
293
|
});
|
|
259
294
|
if (!response.ok) {
|
|
260
295
|
const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
|
|
261
|
-
throw new ClickClackHttpError(response.status, detail, new Headers(response.headers));
|
|
296
|
+
throw new ClickClackHttpError(response.status, redactToolPayloadText(detail), new Headers(response.headers));
|
|
262
297
|
}
|
|
263
298
|
return await readProviderJsonResponse(response, "ClickClack response", { maxBytes: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES });
|
|
264
299
|
}
|
|
@@ -289,9 +324,63 @@ function createClickClackClient(options) {
|
|
|
289
324
|
channels: async (workspaceId) => {
|
|
290
325
|
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`)).channels;
|
|
291
326
|
},
|
|
327
|
+
createChannel: async (workspaceId, channel) => {
|
|
328
|
+
return (await request(`/api/workspaces/${encodeURIComponent(workspaceId)}/channels`, {
|
|
329
|
+
method: "POST",
|
|
330
|
+
body: JSON.stringify(channel)
|
|
331
|
+
})).channel;
|
|
332
|
+
},
|
|
333
|
+
updateChannel: async (channelId, patch) => {
|
|
334
|
+
return (await request(`/api/channels/${encodeURIComponent(channelId)}`, {
|
|
335
|
+
method: "PATCH",
|
|
336
|
+
body: JSON.stringify(patch)
|
|
337
|
+
})).channel;
|
|
338
|
+
},
|
|
292
339
|
channelMessages: async (channelId, afterSeq, limit = 20) => {
|
|
293
340
|
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
294
341
|
},
|
|
342
|
+
latestChannelMessages: async (channelId, limit = 30) => {
|
|
343
|
+
const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
|
|
344
|
+
let beforeSeq;
|
|
345
|
+
let latest = [];
|
|
346
|
+
let rootPageCount = 0;
|
|
347
|
+
let threadRequestCount = 0;
|
|
348
|
+
let truncated = false;
|
|
349
|
+
while (true) {
|
|
350
|
+
rootPageCount += 1;
|
|
351
|
+
const query = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
352
|
+
if (beforeSeq !== void 0) query.set("before_seq", String(beforeSeq));
|
|
353
|
+
const page = await request(`/api/channels/${encodeURIComponent(channelId)}/messages?${query.toString()}`);
|
|
354
|
+
for (const root of page.messages) {
|
|
355
|
+
latest = keepLatestMessages([...latest, root], boundedLimit);
|
|
356
|
+
const lastReplyAt = root.thread_state?.last_reply_at;
|
|
357
|
+
const cutoff = latest.length === boundedLimit ? latest[0]?.created_at : void 0;
|
|
358
|
+
if (!root.thread_state?.reply_count || lastReplyAt !== void 0 && cutoff !== void 0 && lastReplyAt < cutoff) continue;
|
|
359
|
+
if (threadRequestCount >= CLICKCLACK_DISCUSSION_THREAD_REQUEST_LIMIT) {
|
|
360
|
+
truncated = true;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
threadRequestCount += 1;
|
|
364
|
+
const threadQuery = new URLSearchParams({ limit: String(CLICKCLACK_MESSAGE_PAGE_LIMIT) });
|
|
365
|
+
const thread = await request(`/api/messages/${encodeURIComponent(root.id)}/thread?${threadQuery.toString()}`);
|
|
366
|
+
if (thread.replies.length < root.thread_state.reply_count) {
|
|
367
|
+
truncated = true;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
latest = keepLatestMessages([...latest, ...thread.replies], boundedLimit);
|
|
371
|
+
}
|
|
372
|
+
if (!page.has_older) return {
|
|
373
|
+
messages: latest,
|
|
374
|
+
truncated
|
|
375
|
+
};
|
|
376
|
+
if (rootPageCount >= CLICKCLACK_DISCUSSION_ROOT_PAGE_LIMIT) return {
|
|
377
|
+
messages: latest,
|
|
378
|
+
truncated: true
|
|
379
|
+
};
|
|
380
|
+
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");
|
|
381
|
+
beforeSeq = page.oldest_seq;
|
|
382
|
+
}
|
|
383
|
+
},
|
|
295
384
|
directMessages: async (conversationId, afterSeq, limit = 20) => {
|
|
296
385
|
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
297
386
|
},
|
|
@@ -447,6 +536,59 @@ async function resolveChannelId(client, workspaceId, channel) {
|
|
|
447
536
|
return found.id;
|
|
448
537
|
}
|
|
449
538
|
//#endregion
|
|
539
|
+
//#region extensions/clickclack/src/setup-contract.ts
|
|
540
|
+
const CLICKCLACK_SETUP_CODE_CLAIM_PATH = "/api/bot-setup-codes/claim";
|
|
541
|
+
const LOOPBACK_ADDRESSES = new net.BlockList();
|
|
542
|
+
LOOPBACK_ADDRESSES.addSubnet("127.0.0.0", 8, "ipv4");
|
|
543
|
+
LOOPBACK_ADDRESSES.addAddress("::1", "ipv6");
|
|
544
|
+
const BASE_PATH_SEGMENT = /^[A-Za-z0-9._~-]+$/u;
|
|
545
|
+
function isClickClackSetupLoopbackHost(hostname) {
|
|
546
|
+
const normalized = hostname.toLowerCase().replace(/^\[|\]$/gu, "");
|
|
547
|
+
if (normalized === "localhost") return true;
|
|
548
|
+
const family = net.isIP(normalized);
|
|
549
|
+
return family !== 0 && LOOPBACK_ADDRESSES.check(normalized, family === 4 ? "ipv4" : "ipv6");
|
|
550
|
+
}
|
|
551
|
+
function requireClickClackSetupApiBaseUrl(value, label) {
|
|
552
|
+
if (!value || value !== value.trim()) throw new Error(`ClickClack ${label} is invalid`);
|
|
553
|
+
let parsed;
|
|
554
|
+
try {
|
|
555
|
+
parsed = new URL(value);
|
|
556
|
+
} catch {
|
|
557
|
+
throw new Error(`ClickClack ${label} is invalid`);
|
|
558
|
+
}
|
|
559
|
+
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`);
|
|
560
|
+
if (parsed.protocol === "http:" && !isClickClackSetupLoopbackHost(parsed.hostname)) throw new Error(`ClickClack ${label} must use HTTPS unless it is on loopback`);
|
|
561
|
+
const pathname = parsed.pathname;
|
|
562
|
+
let basePath = "";
|
|
563
|
+
if (pathname !== "/") {
|
|
564
|
+
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`);
|
|
565
|
+
basePath = pathname;
|
|
566
|
+
}
|
|
567
|
+
const canonical = parsed.origin + basePath;
|
|
568
|
+
if (value !== canonical) throw new Error(`ClickClack ${label} is not canonical`);
|
|
569
|
+
return canonical;
|
|
570
|
+
}
|
|
571
|
+
function requireClickClackSetupClaimUrl(value) {
|
|
572
|
+
let parsed;
|
|
573
|
+
try {
|
|
574
|
+
parsed = new URL(value);
|
|
575
|
+
} catch {
|
|
576
|
+
throw new Error("ClickClack setup URL has an invalid claim endpoint.");
|
|
577
|
+
}
|
|
578
|
+
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.");
|
|
579
|
+
const basePath = parsed.pathname.slice(0, -26);
|
|
580
|
+
const apiBaseUrl = requireClickClackSetupApiBaseUrl(parsed.origin + basePath, "setup URL API base");
|
|
581
|
+
const claimUrl = apiBaseUrl + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
582
|
+
if (value !== claimUrl) throw new Error("ClickClack setup URL has a non-canonical claim endpoint.");
|
|
583
|
+
return {
|
|
584
|
+
claimUrl,
|
|
585
|
+
apiBaseUrl
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function buildClickClackSetupClaimUrl(baseUrl) {
|
|
589
|
+
return baseUrl.replace(/\/+$/u, "") + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
|
|
590
|
+
}
|
|
591
|
+
//#endregion
|
|
450
592
|
//#region extensions/clickclack/src/setup-core.ts
|
|
451
593
|
const channel$1 = "clickclack";
|
|
452
594
|
const SETUP_CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
@@ -489,19 +631,30 @@ function parseClickClackSetupCodeInput(params) {
|
|
|
489
631
|
}
|
|
490
632
|
if (setupUrl.protocol !== "http:" && setupUrl.protocol !== "https:") throw new Error("ClickClack setup codes require an HTTP(S) URL.");
|
|
491
633
|
if (setupUrl.username || setupUrl.password) throw new Error("ClickClack setup URLs must not include credentials.");
|
|
634
|
+
if (setupUrl.search) throw new Error("ClickClack setup URLs must not include a query.");
|
|
492
635
|
code = setupUrl.hash.slice(1);
|
|
493
636
|
if (!code) throw new Error("ClickClack setup URL is missing its #CODE fragment.");
|
|
494
637
|
setupUrl.hash = "";
|
|
495
|
-
|
|
496
|
-
|
|
638
|
+
let exactClaimUrl;
|
|
639
|
+
if (setupUrl.pathname.endsWith("/api/bot-setup-codes/claim")) {
|
|
640
|
+
const exactEndpoint = requireClickClackSetupClaimUrl(setupUrl.toString());
|
|
641
|
+
baseUrl = exactEndpoint.apiBaseUrl;
|
|
642
|
+
exactClaimUrl = exactEndpoint.claimUrl;
|
|
643
|
+
} else baseUrl = requireClickClackSetupCodeBaseUrl(setupUrl.toString());
|
|
497
644
|
if (params.baseUrl) {
|
|
498
645
|
if (requireClickClackSetupCodeBaseUrl(params.baseUrl) !== baseUrl) throw new Error("ClickClack --base-url does not match the server in the setup-code URL.");
|
|
499
646
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
647
|
+
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
648
|
+
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
649
|
+
return {
|
|
650
|
+
code: normalizedCode,
|
|
651
|
+
baseUrl,
|
|
652
|
+
...exactClaimUrl ? { exactClaimUrl } : {}
|
|
653
|
+
};
|
|
504
654
|
}
|
|
655
|
+
code = code.startsWith("#") ? code.slice(1) : code;
|
|
656
|
+
if (!params.baseUrl) throw new Error("A bare ClickClack setup code requires --base-url.");
|
|
657
|
+
baseUrl = requireClickClackSetupCodeBaseUrl(params.baseUrl);
|
|
505
658
|
const normalizedCode = normalizeClickClackSetupCode(code);
|
|
506
659
|
if (!normalizedCode) throw new Error("ClickClack setup code must contain 12 valid base32 characters.");
|
|
507
660
|
return {
|
|
@@ -522,7 +675,8 @@ function applyClickClackSetupConfigPatch(params) {
|
|
|
522
675
|
const namedConfig = applyAccountNameToChannelSection({
|
|
523
676
|
cfg: accountId === DEFAULT_ACCOUNT_ID ? params.cfg : moveSingleAccountChannelSectionToDefaultAccount({
|
|
524
677
|
cfg: params.cfg,
|
|
525
|
-
channelKey: channel$1
|
|
678
|
+
channelKey: channel$1,
|
|
679
|
+
setupSurface: clickClackSetupAdapter
|
|
526
680
|
}),
|
|
527
681
|
channelKey: channel$1,
|
|
528
682
|
accountId,
|
|
@@ -585,21 +739,32 @@ function applyClickClackCredentialConfig(params) {
|
|
|
585
739
|
}
|
|
586
740
|
const clickClackSetupAdapter = {
|
|
587
741
|
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
588
|
-
prepareAccountConfigInput: async ({ input }) => {
|
|
589
|
-
|
|
590
|
-
if (
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
742
|
+
prepareAccountConfigInput: async ({ cfg, accountId, input }) => {
|
|
743
|
+
const setupInput = input;
|
|
744
|
+
if (!setupInput.code?.trim()) return setupInput;
|
|
745
|
+
if (setupInput.token?.trim() || setupInput.tokenFile?.trim() || setupInput.useEnv) throw new Error(SETUP_CODE_CONFLICT_ERROR);
|
|
746
|
+
let setup = parseClickClackSetupCodeInput({
|
|
747
|
+
code: setupInput.code,
|
|
748
|
+
baseUrl: setupInput.baseUrl
|
|
594
749
|
});
|
|
750
|
+
const privateApiBaseUrl = normalizeClickClackBaseUrl(resolveClickClackAccountConfig(cfg, accountId).apiBaseUrl);
|
|
751
|
+
const claimUrl = setup.exactClaimUrl && !privateApiBaseUrl ? setup.exactClaimUrl : buildClickClackSetupClaimUrl(privateApiBaseUrl ?? setup.baseUrl);
|
|
595
752
|
let claim;
|
|
596
753
|
try {
|
|
597
|
-
const { claimClickClackSetupCode } = await import("./setup-claim-
|
|
598
|
-
claim = await claimClickClackSetupCode(
|
|
754
|
+
const { claimClickClackSetupCode } = await import("./setup-claim-CJIUfZS7.js");
|
|
755
|
+
claim = await claimClickClackSetupCode({
|
|
756
|
+
claimUrl,
|
|
757
|
+
code: setup.code,
|
|
758
|
+
...setup.exactClaimUrl ? { expectedClaimUrl: setup.exactClaimUrl } : {}
|
|
759
|
+
});
|
|
599
760
|
} catch (error) {
|
|
600
761
|
throw formatClickClackSetupCodeClaimError(error);
|
|
601
762
|
}
|
|
602
|
-
|
|
763
|
+
setup = {
|
|
764
|
+
...setup,
|
|
765
|
+
baseUrl: claim.api_base_url ?? setup.baseUrl
|
|
766
|
+
};
|
|
767
|
+
const { code: _code, tokenFile: _tokenFile, useEnv: _useEnv, ...remainingInput } = setupInput;
|
|
603
768
|
return {
|
|
604
769
|
...remainingInput,
|
|
605
770
|
baseUrl: setup.baseUrl,
|
|
@@ -633,40 +798,42 @@ const clickClackSetupAdapter = {
|
|
|
633
798
|
}
|
|
634
799
|
],
|
|
635
800
|
validate: ({ cfg, accountId, input }) => {
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
if (!
|
|
801
|
+
const setupInput = input;
|
|
802
|
+
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl);
|
|
803
|
+
if (setupInput.baseUrl && !baseUrl) return INVALID_BASE_URL_ERROR;
|
|
804
|
+
if (!setupInput.useEnv) return null;
|
|
639
805
|
const existing = resolveClickClackAccountConfig(cfg, accountId);
|
|
640
806
|
const existingBaseUrl = normalizeClickClackBaseUrl(existing.baseUrl);
|
|
641
807
|
if (!baseUrl && existing.baseUrl?.trim() && !existingBaseUrl) return INVALID_BASE_URL_ERROR;
|
|
642
808
|
if (!baseUrl && !existingBaseUrl) return REQUIRED_INPUT_ERROR;
|
|
643
|
-
if (!
|
|
809
|
+
if (!setupInput.workspace?.trim() && !existing.workspace?.trim()) return REQUIRED_INPUT_ERROR;
|
|
644
810
|
return null;
|
|
645
811
|
}
|
|
646
812
|
}),
|
|
647
813
|
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
648
|
-
const
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
const
|
|
814
|
+
const setupInput = input;
|
|
815
|
+
const existing = setupInput.useEnv ? resolveClickClackAccountConfig(cfg, accountId) : void 0;
|
|
816
|
+
const baseUrl = normalizeClickClackBaseUrl(setupInput.baseUrl ?? existing?.baseUrl);
|
|
817
|
+
const workspace = setupInput.workspace?.trim() || existing?.workspace?.trim();
|
|
818
|
+
const tokenFile = setupInput.tokenFile?.trim();
|
|
819
|
+
const token = setupInput.token?.trim();
|
|
653
820
|
return applyClickClackCredentialConfig({
|
|
654
821
|
cfg: applyClickClackSetupConfigPatch({
|
|
655
822
|
cfg,
|
|
656
823
|
accountId,
|
|
657
|
-
name:
|
|
824
|
+
name: setupInput.name,
|
|
658
825
|
patch: {
|
|
659
826
|
...baseUrl ? { baseUrl } : {},
|
|
660
827
|
...workspace ? { workspace } : {},
|
|
661
|
-
...
|
|
662
|
-
...
|
|
663
|
-
...
|
|
828
|
+
...setupInput.defaultTo?.trim() ? { defaultTo: setupInput.defaultTo.trim() } : {},
|
|
829
|
+
...setupInput.allowFrom ? { allowFrom: [...setupInput.allowFrom] } : {},
|
|
830
|
+
...setupInput.agentActivity !== void 0 ? { agentActivity: setupInput.agentActivity } : {}
|
|
664
831
|
}
|
|
665
832
|
}),
|
|
666
833
|
accountId,
|
|
667
834
|
token,
|
|
668
835
|
tokenFile,
|
|
669
|
-
useEnv:
|
|
836
|
+
useEnv: setupInput.useEnv
|
|
670
837
|
});
|
|
671
838
|
},
|
|
672
839
|
afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
|
|
@@ -678,6 +845,77 @@ const clickClackSetupAdapter = {
|
|
|
678
845
|
});
|
|
679
846
|
}
|
|
680
847
|
};
|
|
848
|
+
const clickClackSetupContract = defineChannelSetupContract({
|
|
849
|
+
fields: {
|
|
850
|
+
code: {
|
|
851
|
+
kind: "string",
|
|
852
|
+
sensitive: true,
|
|
853
|
+
cli: {
|
|
854
|
+
flags: "--code <code>",
|
|
855
|
+
description: "ClickClack one-time setup code or setup URL"
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
token: {
|
|
859
|
+
kind: "string",
|
|
860
|
+
sensitive: true,
|
|
861
|
+
cli: {
|
|
862
|
+
flags: "--token <token>",
|
|
863
|
+
description: "ClickClack bot token"
|
|
864
|
+
}
|
|
865
|
+
},
|
|
866
|
+
tokenFile: {
|
|
867
|
+
kind: "string",
|
|
868
|
+
sensitive: true,
|
|
869
|
+
cli: {
|
|
870
|
+
flags: "--token-file <path>",
|
|
871
|
+
description: "ClickClack bot token file"
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
baseUrl: {
|
|
875
|
+
kind: "string",
|
|
876
|
+
cli: {
|
|
877
|
+
flags: "--base-url <url>",
|
|
878
|
+
description: "ClickClack API base URL"
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
workspace: {
|
|
882
|
+
kind: "string",
|
|
883
|
+
cli: {
|
|
884
|
+
flags: "--workspace <workspace>",
|
|
885
|
+
description: "ClickClack workspace id, slug, or name"
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
defaultTo: {
|
|
889
|
+
kind: "string",
|
|
890
|
+
cli: {
|
|
891
|
+
flags: "--default-to <target>",
|
|
892
|
+
description: "Default ClickClack target"
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
allowFrom: {
|
|
896
|
+
kind: "string-list",
|
|
897
|
+
cli: {
|
|
898
|
+
flags: "--allow-from <ids>",
|
|
899
|
+
description: "Allowed ClickClack senders"
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
agentActivity: {
|
|
903
|
+
kind: "boolean",
|
|
904
|
+
cli: {
|
|
905
|
+
flags: "--agent-activity",
|
|
906
|
+
description: "Enable ClickClack agent activity"
|
|
907
|
+
}
|
|
908
|
+
},
|
|
909
|
+
useEnv: {
|
|
910
|
+
kind: "boolean",
|
|
911
|
+
cli: {
|
|
912
|
+
flags: "--use-env",
|
|
913
|
+
description: "Use CLICKCLACK_BOT_TOKEN"
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
legacyAdapter: clickClackSetupAdapter
|
|
918
|
+
});
|
|
681
919
|
//#endregion
|
|
682
920
|
//#region extensions/clickclack/src/setup-verify.ts
|
|
683
921
|
var setup_verify_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -708,7 +946,7 @@ async function checkClickClackSetupConnection(params) {
|
|
|
708
946
|
if (usesUnavailableImplicitEnvToken(account, token)) return { status: "skipped-env-token" };
|
|
709
947
|
if (!account.baseUrl || !account.workspace || !token) return { status: "skipped-unconfigured" };
|
|
710
948
|
const client = createClickClackClient({
|
|
711
|
-
baseUrl: account.
|
|
949
|
+
baseUrl: account.apiEndpoint,
|
|
712
950
|
token
|
|
713
951
|
});
|
|
714
952
|
const me = await client.me();
|
|
@@ -908,4 +1146,4 @@ const clickClackSetupWizard = {
|
|
|
908
1146
|
disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
|
|
909
1147
|
};
|
|
910
1148
|
//#endregion
|
|
911
|
-
export {
|
|
1149
|
+
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 };
|