@openclaw/clickclack 2026.7.2-beta.1 → 2026.7.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/api.js +2 -1
- package/dist/{channel-Ds6d_3nx.js → channel-BnREQIP8.js} +111 -422
- package/dist/channel-plugin-api.js +1 -1
- package/dist/runtime-api.js +2 -1
- package/dist/secret-contract-BKkzzjBA.js +35 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/setup-entry.js +15 -0
- package/dist/setup-plugin-api.js +18 -0
- package/dist/setup-surface-B346XaAu.js +828 -0
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +12 -0
- package/package.json +14 -6
|
@@ -1,158 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
|
4
|
-
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
|
5
|
-
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
|
6
|
-
import { buildSecretInputSchema, normalizeResolvedSecretInputString, normalizeSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
7
|
-
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
1
|
+
import { a as createClickClackClient, c as CLICKCLACK_CHANNEL_ID, d as DEFAULT_ACCOUNT_ID, i as resolveWorkspaceId, l as clickClackConfigAdapter, m as resolveClickClackAccount, n as clickClackSetupAdapter, o as normalizeClickClackCorrelationId, r as resolveChannelId, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-B346XaAu.js";
|
|
2
|
+
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-BKkzzjBA.js";
|
|
8
3
|
import { buildChannelOutboundSessionRoute, buildThreadAwareOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
9
4
|
import { buildChannelProgressDraftLine, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, deriveDurableFinalDeliveryRequirements } from "openclaw/plugin-sdk/channel-outbound";
|
|
10
|
-
import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
11
5
|
import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
12
|
-
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
13
|
-
import { z } from "zod";
|
|
14
6
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
|
+
import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
|
|
15
8
|
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
16
9
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
17
|
-
import {
|
|
18
|
-
import { WebSocket } from "ws";
|
|
10
|
+
import { listNativeCommandSpecsForConfig } from "openclaw/plugin-sdk/native-command-registry";
|
|
19
11
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
20
12
|
import { createHash } from "node:crypto";
|
|
21
13
|
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
|
22
14
|
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
23
|
-
//#region extensions/clickclack/src/accounts.ts
|
|
24
|
-
/**
|
|
25
|
-
* Resolves ClickClack account configuration from root channel config, named
|
|
26
|
-
* account overrides, and secret-provider references.
|
|
27
|
-
*/
|
|
28
|
-
const DEFAULT_RECONNECT_MS = 1500;
|
|
29
|
-
const MIN_RECONNECT_MS = 100;
|
|
30
|
-
const MAX_RECONNECT_MS = 6e4;
|
|
31
|
-
const { listAccountIds: listClickClackAccountIds, resolveDefaultAccountId: resolveDefaultClickClackAccountId } = createAccountListHelpers("clickclack", {
|
|
32
|
-
normalizeAccountId,
|
|
33
|
-
hasImplicitDefaultAccount: (cfg) => {
|
|
34
|
-
const channel = cfg.channels?.clickclack;
|
|
35
|
-
return Boolean(channel?.baseUrl?.trim() && hasConfiguredAccountValue(channel.token) && channel.workspace?.trim());
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
function resolveMergedClickClackAccountConfig(cfg, accountId) {
|
|
39
|
-
return resolveMergedAccountConfig({
|
|
40
|
-
channelConfig: cfg.channels?.clickclack,
|
|
41
|
-
accounts: cfg.channels?.clickclack?.accounts,
|
|
42
|
-
accountId,
|
|
43
|
-
omitKeys: ["defaultAccount"],
|
|
44
|
-
normalizeAccountId
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function resolveClickClackToken(params) {
|
|
48
|
-
const resolved = resolveSecretInputString({
|
|
49
|
-
value: params.value,
|
|
50
|
-
path: params.accountId === DEFAULT_ACCOUNT_ID ? "channels.clickclack.token" : `channels.clickclack.accounts.${params.accountId}.token`,
|
|
51
|
-
defaults: params.cfg.secrets?.defaults,
|
|
52
|
-
mode: "inspect"
|
|
53
|
-
});
|
|
54
|
-
if (resolved.status !== "available") {
|
|
55
|
-
if (resolved.status === "configured_unavailable" && resolved.ref.source === "env") {
|
|
56
|
-
const providerConfig = params.cfg.secrets?.providers?.[resolved.ref.provider];
|
|
57
|
-
if (providerConfig) {
|
|
58
|
-
if (providerConfig.source !== "env") throw new Error(`Secret provider "${resolved.ref.provider}" has source "${providerConfig.source}" but ref requests "env".`);
|
|
59
|
-
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.`);
|
|
60
|
-
} 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}).`);
|
|
61
|
-
return normalizeSecretInputString((params.env ?? process.env)[resolved.ref.id]) ?? "";
|
|
62
|
-
}
|
|
63
|
-
return "";
|
|
64
|
-
}
|
|
65
|
-
return normalizeResolvedSecretInputString({
|
|
66
|
-
value: resolved.value,
|
|
67
|
-
path: "channels.clickclack.token"
|
|
68
|
-
}) ?? "";
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Builds the normalized account snapshot used by gateway, outbound delivery,
|
|
72
|
-
* status reporting, and channel routing.
|
|
73
|
-
*/
|
|
74
|
-
function resolveClickClackAccount(params) {
|
|
75
|
-
const accountId = normalizeAccountId(params.accountId);
|
|
76
|
-
const merged = resolveMergedClickClackAccountConfig(params.cfg, accountId);
|
|
77
|
-
const enabled = params.cfg.channels?.clickclack?.enabled !== false && merged.enabled !== false;
|
|
78
|
-
const baseUrl = merged.baseUrl?.trim().replace(/\/$/, "") ?? "";
|
|
79
|
-
const token = resolveClickClackToken({
|
|
80
|
-
cfg: params.cfg,
|
|
81
|
-
value: merged.token,
|
|
82
|
-
accountId,
|
|
83
|
-
env: params.env
|
|
84
|
-
});
|
|
85
|
-
const workspace = merged.workspace?.trim() ?? "";
|
|
86
|
-
return {
|
|
87
|
-
accountId,
|
|
88
|
-
enabled,
|
|
89
|
-
configured: Boolean(baseUrl && token && workspace),
|
|
90
|
-
name: normalizeOptionalString(merged.name),
|
|
91
|
-
baseUrl,
|
|
92
|
-
token,
|
|
93
|
-
workspace,
|
|
94
|
-
botUserId: normalizeOptionalString(merged.botUserId),
|
|
95
|
-
agentId: normalizeOptionalString(merged.agentId),
|
|
96
|
-
replyMode: merged.replyMode === "model" ? "model" : "agent",
|
|
97
|
-
model: normalizeOptionalString(merged.model),
|
|
98
|
-
systemPrompt: normalizeOptionalString(merged.systemPrompt),
|
|
99
|
-
timeoutSeconds: merged.timeoutSeconds,
|
|
100
|
-
toolsAllow: merged.toolsAllow,
|
|
101
|
-
defaultTo: merged.defaultTo?.trim() || "channel:general",
|
|
102
|
-
allowFrom: merged.allowFrom ?? ["*"],
|
|
103
|
-
reconnectMs: resolveIntegerOption(merged.reconnectMs, DEFAULT_RECONNECT_MS, {
|
|
104
|
-
min: MIN_RECONNECT_MS,
|
|
105
|
-
max: MAX_RECONNECT_MS
|
|
106
|
-
}),
|
|
107
|
-
agentActivity: merged.agentActivity === true,
|
|
108
|
-
config: {
|
|
109
|
-
...merged,
|
|
110
|
-
allowFrom: merged.allowFrom ?? ["*"]
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Returns all enabled accounts, including the implicit default account when
|
|
116
|
-
* legacy top-level ClickClack config is present.
|
|
117
|
-
*/
|
|
118
|
-
function listEnabledClickClackAccounts(cfg) {
|
|
119
|
-
return listClickClackAccountIds(cfg).map((accountId) => resolveClickClackAccount({
|
|
120
|
-
cfg,
|
|
121
|
-
accountId
|
|
122
|
-
})).filter((account) => account.enabled);
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region extensions/clickclack/src/config-schema.ts
|
|
126
|
-
/**
|
|
127
|
-
* Zod-backed config schema for ClickClack channel accounts.
|
|
128
|
-
*/
|
|
129
|
-
const ClickClackAccountConfigSchema = z.object({
|
|
130
|
-
name: z.string().optional(),
|
|
131
|
-
enabled: z.boolean().optional(),
|
|
132
|
-
baseUrl: z.string().url().optional(),
|
|
133
|
-
token: buildSecretInputSchema().optional(),
|
|
134
|
-
workspace: z.string().optional(),
|
|
135
|
-
botUserId: z.string().optional(),
|
|
136
|
-
agentId: z.string().optional(),
|
|
137
|
-
replyMode: z.enum(["agent", "model"]).optional(),
|
|
138
|
-
model: z.string().optional(),
|
|
139
|
-
systemPrompt: z.string().optional(),
|
|
140
|
-
timeoutSeconds: z.number().int().min(1).max(3600).optional(),
|
|
141
|
-
toolsAllow: z.array(z.string()).optional(),
|
|
142
|
-
defaultTo: z.string().optional(),
|
|
143
|
-
allowFrom: z.array(z.string()).optional(),
|
|
144
|
-
reconnectMs: z.number().int().min(100).max(6e4).optional(),
|
|
145
|
-
agentActivity: z.boolean().optional()
|
|
146
|
-
}).strict();
|
|
147
|
-
/**
|
|
148
|
-
* Config schema exported to core so `openclaw doctor` and config validation
|
|
149
|
-
* understand both default and named ClickClack accounts.
|
|
150
|
-
*/
|
|
151
|
-
const clickClackConfigSchema = buildChannelConfigSchema(ClickClackAccountConfigSchema.extend({
|
|
152
|
-
accounts: z.record(z.string(), ClickClackAccountConfigSchema.partial()).optional(),
|
|
153
|
-
defaultAccount: z.string().optional()
|
|
154
|
-
}).strict());
|
|
155
|
-
//#endregion
|
|
156
15
|
//#region extensions/clickclack/src/runtime.ts
|
|
157
16
|
/**
|
|
158
17
|
* Runtime store for host-provided OpenClaw services used by the ClickClack
|
|
@@ -215,223 +74,62 @@ async function resolveClickClackInboundAccess(params) {
|
|
|
215
74
|
};
|
|
216
75
|
}
|
|
217
76
|
//#endregion
|
|
218
|
-
//#region extensions/clickclack/src/
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
* are ignored by servers without the provenance columns, so these are safe
|
|
226
|
-
* to send unconditionally when present.
|
|
227
|
-
*/
|
|
228
|
-
function provenanceFields(provenance) {
|
|
229
|
-
const fields = {};
|
|
230
|
-
if (provenance?.model?.trim()) fields.author_model = provenance.model.trim();
|
|
231
|
-
if (provenance?.thinking?.trim()) fields.author_thinking = provenance.thinking.trim();
|
|
232
|
-
if (provenance?.runtime?.trim()) fields.author_runtime = provenance.runtime.trim();
|
|
233
|
-
return fields;
|
|
77
|
+
//#region extensions/clickclack/src/command-menu.ts
|
|
78
|
+
const CLICKCLACK_COMMAND_PATTERN = /^[a-z0-9_-]{1,32}$/u;
|
|
79
|
+
const CLICKCLACK_MAX_COMMANDS = 100;
|
|
80
|
+
const CLICKCLACK_MAX_DESCRIPTION_LENGTH = 100;
|
|
81
|
+
const CLICKCLACK_MAX_ARGS_HINT_LENGTH = 100;
|
|
82
|
+
function truncateCodePoints(value, maxLength) {
|
|
83
|
+
return Array.from(value).slice(0, maxLength).join("");
|
|
234
84
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const CLICKCLACK_CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
239
|
-
const CLICKCLACK_INBOUND_JSON_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
240
|
-
var ClickClackHttpError = class extends Error {
|
|
241
|
-
constructor(status, detail, headers) {
|
|
242
|
-
super(`ClickClack ${status}: ${detail}`);
|
|
243
|
-
this.status = status;
|
|
244
|
-
this.headers = headers;
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
/** Accepts the same bounded request-correlation shape as the ClickClack API. */
|
|
248
|
-
function normalizeClickClackCorrelationId(value) {
|
|
249
|
-
if (typeof value !== "string") return;
|
|
250
|
-
const normalized = value.trim();
|
|
251
|
-
if (!normalized || normalized.length > CLICKCLACK_CORRELATION_ID_MAX_LENGTH || !CLICKCLACK_CORRELATION_ID_PATTERN.test(normalized)) return;
|
|
252
|
-
return normalized;
|
|
85
|
+
function commandArgsHint(spec) {
|
|
86
|
+
if (spec.args?.length) return truncateCodePoints(spec.args.map((arg) => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(" "), CLICKCLACK_MAX_ARGS_HINT_LENGTH);
|
|
87
|
+
return spec.acceptsArgs ? "[args]" : "";
|
|
253
88
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (init.body && !(init.body instanceof FormData)) requestHeaders.set("Content-Type", "application/json");
|
|
270
|
-
const response = await fetcher(`${baseUrl}${path}`, {
|
|
271
|
-
...init,
|
|
272
|
-
headers: requestHeaders
|
|
273
|
-
});
|
|
274
|
-
if (!response.ok) {
|
|
275
|
-
const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
|
|
276
|
-
throw new ClickClackHttpError(response.status, detail, new Headers(response.headers));
|
|
89
|
+
function errorStatus(error) {
|
|
90
|
+
if (!error || typeof error !== "object" || !("status" in error)) return;
|
|
91
|
+
const status = error.status;
|
|
92
|
+
return typeof status === "number" ? status : void 0;
|
|
93
|
+
}
|
|
94
|
+
function mapNativeCommandSpecsToClickClackMenu(specs, log) {
|
|
95
|
+
const commands = [];
|
|
96
|
+
const seen = /* @__PURE__ */ new Set();
|
|
97
|
+
const invalidNames = /* @__PURE__ */ new Set();
|
|
98
|
+
for (const spec of specs) {
|
|
99
|
+
if (spec.isAlias) continue;
|
|
100
|
+
const command = spec.name.trim().toLowerCase();
|
|
101
|
+
if (!CLICKCLACK_COMMAND_PATTERN.test(command)) {
|
|
102
|
+
invalidNames.add(spec.name.trim() || "<empty>");
|
|
103
|
+
continue;
|
|
277
104
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
events: data.events,
|
|
288
|
-
...typeof data.tail_cursor === "string" ? { tailCursor: data.tail_cursor } : {}
|
|
289
|
-
};
|
|
105
|
+
if (seen.has(command)) continue;
|
|
106
|
+
seen.add(command);
|
|
107
|
+
const description = spec.description.trim() || command;
|
|
108
|
+
commands.push({
|
|
109
|
+
command,
|
|
110
|
+
description: truncateCodePoints(description, CLICKCLACK_MAX_DESCRIPTION_LENGTH),
|
|
111
|
+
args_hint: commandArgsHint(spec)
|
|
112
|
+
});
|
|
290
113
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return
|
|
303
|
-
},
|
|
304
|
-
directMessages: async (conversationId, afterSeq, limit = 20) => {
|
|
305
|
-
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
|
|
306
|
-
},
|
|
307
|
-
thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
|
|
308
|
-
message: async (messageId) => {
|
|
309
|
-
return (await request(`/api/messages/${encodeURIComponent(messageId)}`)).message;
|
|
310
|
-
},
|
|
311
|
-
findMessageByNonce: async (params) => {
|
|
312
|
-
const query = new URLSearchParams({
|
|
313
|
-
workspace_id: params.workspaceId,
|
|
314
|
-
nonce: params.nonce
|
|
315
|
-
});
|
|
316
|
-
try {
|
|
317
|
-
return (await request(`/api/messages/by-nonce?${query.toString()}`)).message;
|
|
318
|
-
} catch (error) {
|
|
319
|
-
if (error instanceof ClickClackHttpError && error.status === 404) {
|
|
320
|
-
if (error.headers.get("X-ClickClack-Message-Nonce") === "supported") return;
|
|
321
|
-
throw new Error("ClickClack server does not support durable message nonce lookup", { cause: error });
|
|
322
|
-
}
|
|
323
|
-
throw error;
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
|
-
createChannelMessage: async (channelId, body, opts) => {
|
|
327
|
-
return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages`, {
|
|
328
|
-
method: "POST",
|
|
329
|
-
body: JSON.stringify({
|
|
330
|
-
body,
|
|
331
|
-
...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
|
|
332
|
-
...opts?.nonce ? { nonce: opts.nonce } : {},
|
|
333
|
-
...provenanceFields(opts?.provenance)
|
|
334
|
-
})
|
|
335
|
-
})).message;
|
|
336
|
-
},
|
|
337
|
-
createThreadReply: async (messageId, body, opts) => {
|
|
338
|
-
return (await request(`/api/messages/${encodeURIComponent(messageId)}/thread/replies`, {
|
|
339
|
-
method: "POST",
|
|
340
|
-
body: JSON.stringify({
|
|
341
|
-
body,
|
|
342
|
-
...opts?.nonce ? { nonce: opts.nonce } : {},
|
|
343
|
-
...provenanceFields(opts?.provenance)
|
|
344
|
-
})
|
|
345
|
-
})).message;
|
|
346
|
-
},
|
|
347
|
-
createDirectConversation: async (workspaceId, memberIds) => {
|
|
348
|
-
return (await request("/api/dms", {
|
|
349
|
-
method: "POST",
|
|
350
|
-
body: JSON.stringify({
|
|
351
|
-
workspace_id: workspaceId,
|
|
352
|
-
member_ids: memberIds
|
|
353
|
-
})
|
|
354
|
-
})).conversation;
|
|
355
|
-
},
|
|
356
|
-
createUpload: async (params) => {
|
|
357
|
-
const form = new FormData();
|
|
358
|
-
const bytes = new Uint8Array(params.buffer);
|
|
359
|
-
form.append("file", new Blob([bytes], { type: params.contentType }), params.filename);
|
|
360
|
-
const query = new URLSearchParams({ workspace_id: params.workspaceId });
|
|
361
|
-
if (params.nonce) query.set("nonce", params.nonce);
|
|
362
|
-
return (await request(`/api/uploads?${query.toString()}`, {
|
|
363
|
-
method: "POST",
|
|
364
|
-
body: form
|
|
365
|
-
})).upload;
|
|
366
|
-
},
|
|
367
|
-
findUploadByNonce: async (params) => {
|
|
368
|
-
const query = new URLSearchParams({
|
|
369
|
-
workspace_id: params.workspaceId,
|
|
370
|
-
nonce: params.nonce
|
|
371
|
-
});
|
|
372
|
-
try {
|
|
373
|
-
return (await request(`/api/uploads/by-nonce?${query.toString()}`)).upload;
|
|
374
|
-
} catch (error) {
|
|
375
|
-
if (error instanceof ClickClackHttpError && error.status === 404) {
|
|
376
|
-
if (error.headers.get("X-ClickClack-Upload-Nonce") === "supported") return;
|
|
377
|
-
throw new Error("ClickClack server does not support durable upload nonce lookup", { cause: error });
|
|
378
|
-
}
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
attachUpload: async (messageId, uploadId) => {
|
|
383
|
-
await request(`/api/messages/${encodeURIComponent(messageId)}/attachments`, {
|
|
384
|
-
method: "POST",
|
|
385
|
-
body: JSON.stringify({ upload_id: uploadId })
|
|
386
|
-
});
|
|
387
|
-
},
|
|
388
|
-
/**
|
|
389
|
-
* POSTs a durable agent activity row (agent_commentary / agent_tool)
|
|
390
|
-
* through the normal message create path. Requires a bot token carrying
|
|
391
|
-
* the agent_activity:write scope on the ClickClack side.
|
|
392
|
-
*/
|
|
393
|
-
createActivityMessage: async (params) => {
|
|
394
|
-
if (!params.channelId && !params.conversationId) throw new Error("createActivityMessage requires a channelId or conversationId");
|
|
395
|
-
return (await request(params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
|
|
396
|
-
method: "POST",
|
|
397
|
-
body: JSON.stringify({
|
|
398
|
-
body: params.body,
|
|
399
|
-
kind: params.kind,
|
|
400
|
-
turn_id: params.turnId,
|
|
401
|
-
...provenanceFields(params.provenance)
|
|
402
|
-
})
|
|
403
|
-
})).message;
|
|
404
|
-
},
|
|
405
|
-
/** PATCHes the body of an existing message (activity row coalescing). */
|
|
406
|
-
updateMessageBody: async (messageId, body) => {
|
|
407
|
-
return (await request(`/api/messages/${encodeURIComponent(messageId)}`, {
|
|
408
|
-
method: "PATCH",
|
|
409
|
-
body: JSON.stringify({ body })
|
|
410
|
-
})).message;
|
|
411
|
-
},
|
|
412
|
-
createDirectMessage: async (conversationId, body, opts) => {
|
|
413
|
-
return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages`, {
|
|
414
|
-
method: "POST",
|
|
415
|
-
body: JSON.stringify({
|
|
416
|
-
body,
|
|
417
|
-
...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
|
|
418
|
-
...opts?.nonce ? { nonce: opts.nonce } : {}
|
|
419
|
-
})
|
|
420
|
-
})).message;
|
|
421
|
-
},
|
|
422
|
-
events: async (workspaceId, afterCursor) => (await fetchEventPage(workspaceId, { afterCursor })).events,
|
|
423
|
-
eventPage: fetchEventPage,
|
|
424
|
-
websocket: (workspaceId, afterCursor) => {
|
|
425
|
-
const url = new URL(`${baseUrl}/api/realtime/ws`);
|
|
426
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
427
|
-
url.searchParams.set("workspace_id", workspaceId);
|
|
428
|
-
if (afterCursor) url.searchParams.set("after_cursor", afterCursor);
|
|
429
|
-
return new WebSocket(url, {
|
|
430
|
-
headers: { Authorization: `Bearer ${options.token}` },
|
|
431
|
-
maxPayload: CLICKCLACK_INBOUND_JSON_LIMIT_BYTES
|
|
432
|
-
});
|
|
114
|
+
if (invalidNames.size > 0) log?.warn?.(`ClickClack command menu skipped invalid native command names: ${[...invalidNames].map((name) => JSON.stringify(name)).join(", ")}`);
|
|
115
|
+
return commands.slice(0, CLICKCLACK_MAX_COMMANDS);
|
|
116
|
+
}
|
|
117
|
+
async function syncClickClackCommandMenu(params) {
|
|
118
|
+
try {
|
|
119
|
+
const commands = mapNativeCommandSpecsToClickClackMenu(listNativeCommandSpecsForConfig(params.cfg, { provider: "clickclack" }), params.log);
|
|
120
|
+
await params.client.setBotCommands(commands);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const status = errorStatus(error);
|
|
123
|
+
if (status === 403) {
|
|
124
|
+
params.log?.warn?.("ClickClack command menu sync skipped: bot token lacks commands:write");
|
|
125
|
+
return;
|
|
433
126
|
}
|
|
434
|
-
|
|
127
|
+
if (status === 404) {
|
|
128
|
+
params.log?.debug?.("ClickClack command menu sync skipped: server does not support /api/bots/self/commands");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
params.log?.warn?.(`ClickClack command menu sync failed: ${formatErrorMessage(error)}`);
|
|
132
|
+
}
|
|
435
133
|
}
|
|
436
134
|
//#endregion
|
|
437
135
|
//#region extensions/clickclack/src/activity.ts
|
|
@@ -629,27 +327,6 @@ function createClickClackActivityPublisher(params) {
|
|
|
629
327
|
};
|
|
630
328
|
}
|
|
631
329
|
//#endregion
|
|
632
|
-
//#region extensions/clickclack/src/resolve.ts
|
|
633
|
-
/**
|
|
634
|
-
* Resolves a workspace slug/name/id from config to a ClickClack workspace id.
|
|
635
|
-
*/
|
|
636
|
-
async function resolveWorkspaceId(client, workspace) {
|
|
637
|
-
if (workspace.startsWith("wsp_")) return workspace;
|
|
638
|
-
const found = (await client.workspaces()).find((candidate) => candidate.id === workspace || candidate.slug === workspace || candidate.name === workspace);
|
|
639
|
-
if (!found) throw new Error(`ClickClack workspace not found: ${workspace}`);
|
|
640
|
-
return found.id;
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Resolves a channel name/id from config or target input to a ClickClack
|
|
644
|
-
* channel id.
|
|
645
|
-
*/
|
|
646
|
-
async function resolveChannelId(client, workspaceId, channel) {
|
|
647
|
-
if (channel.startsWith("chn_")) return channel;
|
|
648
|
-
const found = (await client.channels(workspaceId)).find((candidate) => candidate.id === channel || candidate.name === channel);
|
|
649
|
-
if (!found) throw new Error(`ClickClack channel not found: ${channel}`);
|
|
650
|
-
return found.id;
|
|
651
|
-
}
|
|
652
|
-
//#endregion
|
|
653
330
|
//#region extensions/clickclack/src/target.ts
|
|
654
331
|
/**
|
|
655
332
|
* Parses `channel:name`, `thread:msg_id`, `dm:usr_id`, or a bare channel name.
|
|
@@ -1322,6 +999,18 @@ async function startClickClackGatewayAccount(ctx) {
|
|
|
1322
999
|
workspace: workspaceId,
|
|
1323
1000
|
botUserId: configuredAccount.botUserId ?? me.id
|
|
1324
1001
|
};
|
|
1002
|
+
const processIncomingEvent = (event) => processEvent({
|
|
1003
|
+
account,
|
|
1004
|
+
config: ctx.cfg,
|
|
1005
|
+
client,
|
|
1006
|
+
event,
|
|
1007
|
+
botUserId: account.botUserId
|
|
1008
|
+
});
|
|
1009
|
+
if (account.commandMenu) await syncClickClackCommandMenu({
|
|
1010
|
+
cfg: ctx.cfg,
|
|
1011
|
+
client,
|
|
1012
|
+
log: ctx.log
|
|
1013
|
+
});
|
|
1325
1014
|
ctx.setStatus({
|
|
1326
1015
|
accountId: account.accountId,
|
|
1327
1016
|
running: true,
|
|
@@ -1343,20 +1032,15 @@ async function startClickClackGatewayAccount(ctx) {
|
|
|
1343
1032
|
workspaceId,
|
|
1344
1033
|
afterCursor,
|
|
1345
1034
|
abortSignal: ctx.abortSignal,
|
|
1346
|
-
onEvent:
|
|
1347
|
-
await processEvent({
|
|
1348
|
-
account,
|
|
1349
|
-
config: ctx.cfg,
|
|
1350
|
-
client,
|
|
1351
|
-
event,
|
|
1352
|
-
botUserId: account.botUserId
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1035
|
+
onEvent: processIncomingEvent
|
|
1355
1036
|
});
|
|
1356
1037
|
if (ctx.abortSignal.aborted) break;
|
|
1357
1038
|
const socket = client.websocket(workspaceId, afterCursor);
|
|
1358
|
-
await new Promise((resolve
|
|
1039
|
+
await new Promise((resolve) => {
|
|
1359
1040
|
let settled = false;
|
|
1041
|
+
let closing = false;
|
|
1042
|
+
let loggedMessageFailure = false;
|
|
1043
|
+
let messageQueue = Promise.resolve();
|
|
1360
1044
|
let removeAbortListener;
|
|
1361
1045
|
const finishSocketCycle = () => {
|
|
1362
1046
|
if (settled) return;
|
|
@@ -1365,6 +1049,20 @@ async function startClickClackGatewayAccount(ctx) {
|
|
|
1365
1049
|
removeAbortListener = void 0;
|
|
1366
1050
|
resolve();
|
|
1367
1051
|
};
|
|
1052
|
+
const finishAfterQueuedMessages = () => {
|
|
1053
|
+
messageQueue.then(() => finishSocketCycle(), () => finishSocketCycle());
|
|
1054
|
+
};
|
|
1055
|
+
const reconnectAfterMessageFailure = (error) => {
|
|
1056
|
+
if (settled || ctx.abortSignal.aborted) return;
|
|
1057
|
+
if (!loggedMessageFailure) {
|
|
1058
|
+
loggedMessageFailure = true;
|
|
1059
|
+
ctx.log?.warn?.(`[${account.accountId}] ClickClack event processing failed; reconnecting: ${error instanceof Error ? error.message : formatErrorMessage(error)}`);
|
|
1060
|
+
}
|
|
1061
|
+
if (!closing) {
|
|
1062
|
+
closing = true;
|
|
1063
|
+
socket.close();
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1368
1066
|
const abort = () => {
|
|
1369
1067
|
socket.close();
|
|
1370
1068
|
finishSocketCycle();
|
|
@@ -1372,36 +1070,38 @@ async function startClickClackGatewayAccount(ctx) {
|
|
|
1372
1070
|
ctx.abortSignal.addEventListener("abort", abort, { once: true });
|
|
1373
1071
|
removeAbortListener = () => ctx.abortSignal.removeEventListener("abort", abort);
|
|
1374
1072
|
socket.on("message", (data) => {
|
|
1375
|
-
(
|
|
1073
|
+
if (closing || settled) return;
|
|
1074
|
+
messageQueue = messageQueue.then(async () => {
|
|
1376
1075
|
const event = parseSocketEvent(data);
|
|
1377
1076
|
if (!event) {
|
|
1378
1077
|
ctx.log?.warn?.(`[${account.accountId}] skipped malformed ClickClack websocket event`);
|
|
1379
1078
|
return;
|
|
1380
1079
|
}
|
|
1080
|
+
await processIncomingEvent(event);
|
|
1381
1081
|
afterCursor = event.cursor || afterCursor;
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
});
|
|
1389
|
-
})().catch((e) => reject(e instanceof Error ? e : new Error(`ClickClack ws message failed: ${formatErrorMessage(e)}`, { cause: e })));
|
|
1082
|
+
});
|
|
1083
|
+
messageQueue.catch(reconnectAfterMessageFailure);
|
|
1084
|
+
});
|
|
1085
|
+
socket.on("close", () => {
|
|
1086
|
+
closing = true;
|
|
1087
|
+
finishAfterQueuedMessages();
|
|
1390
1088
|
});
|
|
1391
|
-
socket.on("close", finishSocketCycle);
|
|
1392
1089
|
socket.on("error", (error) => {
|
|
1393
1090
|
if (settled || ctx.abortSignal.aborted) {
|
|
1394
1091
|
finishSocketCycle();
|
|
1395
1092
|
return;
|
|
1396
1093
|
}
|
|
1094
|
+
if (closing) return;
|
|
1397
1095
|
ctx.log?.warn?.(`[${account.accountId}] ClickClack websocket error; reconnecting: ${error instanceof Error ? error.message : String(error)}`);
|
|
1398
|
-
|
|
1096
|
+
closing = true;
|
|
1399
1097
|
socket.close();
|
|
1400
1098
|
});
|
|
1401
1099
|
});
|
|
1402
|
-
if (!ctx.abortSignal.aborted)
|
|
1403
|
-
|
|
1404
|
-
})
|
|
1100
|
+
if (!ctx.abortSignal.aborted) try {
|
|
1101
|
+
await sleepWithAbort(account.reconnectMs, ctx.abortSignal);
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
if (!ctx.abortSignal.aborted) throw error;
|
|
1104
|
+
}
|
|
1405
1105
|
}
|
|
1406
1106
|
} finally {
|
|
1407
1107
|
ctx.setStatus({
|
|
@@ -1416,8 +1116,7 @@ async function startClickClackGatewayAccount(ctx) {
|
|
|
1416
1116
|
* ClickClack channel plugin definition: target parsing, account config, status,
|
|
1417
1117
|
* gateway startup, and outbound delivery wiring.
|
|
1418
1118
|
*/
|
|
1419
|
-
const CHANNEL_ID =
|
|
1420
|
-
const meta = { ...getChatChannelMeta(CHANNEL_ID) };
|
|
1119
|
+
const CHANNEL_ID = CLICKCLACK_CHANNEL_ID;
|
|
1421
1120
|
const clickClackMessageAdapter = defineChannelMessageAdapter({
|
|
1422
1121
|
id: CHANNEL_ID,
|
|
1423
1122
|
durableFinal: {
|
|
@@ -1502,7 +1201,7 @@ const clickClackMessageAdapter = defineChannelMessageAdapter({
|
|
|
1502
1201
|
const clickClackPlugin = createChatChannelPlugin({
|
|
1503
1202
|
base: {
|
|
1504
1203
|
id: CHANNEL_ID,
|
|
1505
|
-
meta,
|
|
1204
|
+
meta: clickClackMeta,
|
|
1506
1205
|
capabilities: {
|
|
1507
1206
|
chatTypes: ["direct", "group"],
|
|
1508
1207
|
threads: true,
|
|
@@ -1510,22 +1209,12 @@ const clickClackPlugin = createChatChannelPlugin({
|
|
|
1510
1209
|
},
|
|
1511
1210
|
reload: { configPrefixes: ["channels.clickclack"] },
|
|
1512
1211
|
configSchema: clickClackConfigSchema,
|
|
1513
|
-
config:
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
defaultAccountId: (cfg) => resolveDefaultClickClackAccountId(cfg),
|
|
1520
|
-
isConfigured: (account) => account.configured,
|
|
1521
|
-
resolveAllowFrom: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
1522
|
-
cfg,
|
|
1523
|
-
accountId
|
|
1524
|
-
}).allowFrom,
|
|
1525
|
-
resolveDefaultTo: ({ cfg, accountId }) => resolveClickClackAccount({
|
|
1526
|
-
cfg,
|
|
1527
|
-
accountId
|
|
1528
|
-
}).defaultTo
|
|
1212
|
+
config: clickClackConfigAdapter,
|
|
1213
|
+
setup: clickClackSetupAdapter,
|
|
1214
|
+
setupWizard: clickClackSetupWizard,
|
|
1215
|
+
secrets: {
|
|
1216
|
+
secretTargetRegistryEntries,
|
|
1217
|
+
collectRuntimeConfigAssignments
|
|
1529
1218
|
},
|
|
1530
1219
|
messaging: {
|
|
1531
1220
|
targetPrefixes: ["clickclack", "cc"],
|
|
@@ -1627,4 +1316,4 @@ const clickClackPlugin = createChatChannelPlugin({
|
|
|
1627
1316
|
}
|
|
1628
1317
|
});
|
|
1629
1318
|
//#endregion
|
|
1630
|
-
export {
|
|
1319
|
+
export { setClickClackRuntime as a, getClickClackRuntime as i, buildClickClackTarget as n, parseClickClackTarget as r, clickClackPlugin as t };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as clickClackPlugin } from "./channel-
|
|
1
|
+
import { t as clickClackPlugin } from "./channel-BnREQIP8.js";
|
|
2
2
|
export { clickClackPlugin };
|
package/dist/runtime-api.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as createClickClackClient, m as resolveClickClackAccount } from "./setup-surface-B346XaAu.js";
|
|
2
|
+
import { a as setClickClackRuntime, r as parseClickClackTarget } from "./channel-BnREQIP8.js";
|
|
2
3
|
import "./api.js";
|
|
3
4
|
export { createClickClackClient, parseClickClackTarget, resolveClickClackAccount, setClickClackRuntime };
|