@openclaw/clickclack 2026.9.6 → 2026.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,111 @@
1
+ import { h as resolveClickClackAccount } from "./setup-surface-BW1Q06w5.mjs";
2
+ import { i as createClickClackClient, n as resolveWorkspaceId } from "./resolve-BHGLohni.mjs";
3
+ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
4
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
+ import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/setup";
6
+ //#region extensions/clickclack/src/setup-verify.ts
7
+ const GATEWAY_RUNNING_MESSAGE = "OpenClaw is running — ClickClack will connect automatically.";
8
+ const GATEWAY_NOT_RUNNING_MESSAGE = "Start OpenClaw to connect: openclaw gateway";
9
+ const GATEWAY_UNKNOWN_MESSAGE = "If OpenClaw is running it connects automatically; otherwise start it with: openclaw gateway";
10
+ function isHttpStatus(error, status) {
11
+ return typeof error === "object" && error !== null && "status" in error && error.status === status;
12
+ }
13
+ function isWorkspaceNotFound(error) {
14
+ return error instanceof Error && error.message.startsWith("ClickClack workspace not found:");
15
+ }
16
+ function usesUnavailableImplicitEnvToken(account, tokenOverride) {
17
+ return account.accountId === DEFAULT_ACCOUNT_ID && Boolean(account.baseUrl && account.workspace) && !tokenOverride && !account.token && !hasConfiguredSecretInput(account.config.token) && !account.config.tokenFile?.trim();
18
+ }
19
+ async function checkClickClackSetupConnection(params) {
20
+ let workspaceInput = "";
21
+ try {
22
+ const account = resolveClickClackAccount({
23
+ cfg: params.cfg,
24
+ accountId: params.accountId
25
+ });
26
+ workspaceInput = account.workspace;
27
+ const token = params.token?.trim() || account.token;
28
+ if (usesUnavailableImplicitEnvToken(account, token)) return { status: "skipped-env-token" };
29
+ if (!account.baseUrl || !account.workspace || !token) return { status: "skipped-unconfigured" };
30
+ const client = createClickClackClient({
31
+ baseUrl: account.apiEndpoint,
32
+ token
33
+ });
34
+ const me = await client.me();
35
+ const workspaceId = await resolveWorkspaceId(client, account.workspace);
36
+ const workspace = (await client.workspaces()).find((candidate) => candidate.id === workspaceId);
37
+ if (!workspace) throw new Error(`ClickClack workspace not found: ${account.workspace}`);
38
+ return {
39
+ status: "connected",
40
+ handle: me.handle,
41
+ workspaceName: workspace.name
42
+ };
43
+ } catch (error) {
44
+ if (isHttpStatus(error, 401)) return { status: "invalid-token" };
45
+ if (isWorkspaceNotFound(error)) return {
46
+ status: "workspace-not-found",
47
+ workspace: workspaceInput
48
+ };
49
+ return {
50
+ status: "failed",
51
+ error: formatErrorMessage(error)
52
+ };
53
+ }
54
+ }
55
+ function isGatewayNotRunningError(error) {
56
+ 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;
57
+ const message = formatErrorMessage(error).toLowerCase();
58
+ return message.includes("econnrefused") || message.includes("connection refused");
59
+ }
60
+ async function probeClickClackGatewayStatus() {
61
+ try {
62
+ const { callGatewayFromCli } = await import("openclaw/plugin-sdk/gateway-runtime");
63
+ await callGatewayFromCli("health", {
64
+ timeout: "1000",
65
+ json: true
66
+ }, void 0, {
67
+ expectFinal: false,
68
+ progress: false
69
+ });
70
+ return "running";
71
+ } catch (error) {
72
+ return isGatewayNotRunningError(error) ? "not-running" : "unavailable";
73
+ }
74
+ }
75
+ function formatClickClackConnectionLog(result) {
76
+ switch (result.status) {
77
+ case "connected": return `Connected as @${result.handle} — workspace ${result.workspaceName} resolved.`;
78
+ case "invalid-token": return "ClickClack rejected the bot token (401). Copy a current token and rerun setup.";
79
+ case "workspace-not-found": return `Workspace "${result.workspace}" was not found. Check the id, slug, or name, list available workspaces, and rerun setup.`;
80
+ case "failed": return `Connection check failed: ${result.error}. Setup was saved; fix the connection and rerun setup.`;
81
+ case "skipped-env-token": return "Token comes from CLICKCLACK_BOT_TOKEN; verification skipped.";
82
+ case "skipped-unconfigured": return;
83
+ }
84
+ }
85
+ function formatClickClackGatewayLog(status) {
86
+ switch (status) {
87
+ case "running": return GATEWAY_RUNNING_MESSAGE;
88
+ case "not-running": return GATEWAY_NOT_RUNNING_MESSAGE;
89
+ case "unavailable": return GATEWAY_UNKNOWN_MESSAGE;
90
+ }
91
+ return GATEWAY_UNKNOWN_MESSAGE;
92
+ }
93
+ async function verifyClickClackAccountAfterSetup(params) {
94
+ try {
95
+ const message = formatClickClackConnectionLog(await checkClickClackSetupConnection({
96
+ cfg: params.cfg,
97
+ accountId: params.accountId
98
+ }));
99
+ if (message) params.runtime.log(message);
100
+ } catch (error) {
101
+ params.runtime.log(`Connection check failed: ${formatErrorMessage(error)}. Setup was saved; fix the connection and rerun setup.`);
102
+ }
103
+ try {
104
+ const status = await probeClickClackGatewayStatus();
105
+ params.runtime.log(formatClickClackGatewayLog(status));
106
+ } catch {
107
+ params.runtime.log(GATEWAY_UNKNOWN_MESSAGE);
108
+ }
109
+ }
110
+ //#endregion
111
+ export { checkClickClackSetupConnection, verifyClickClackAccountAfterSetup };
package/dist/api.js CHANGED
@@ -1,3 +1,4 @@
1
- import { _ as DEFAULT_ACCOUNT_ID, b as resolveClickClackAccount, p as clickClackConfigSchema, u as createClickClackClient, v as listClickClackAccountIds, x as resolveDefaultClickClackAccountId, y as listEnabledClickClackAccounts } from "./.setup/setup-surface-BBl3rvVx.mjs";
2
- import { a as setClickClackRuntime, i as getClickClackRuntime, n as buildClickClackTarget, r as parseClickClackTarget, t as clickClackPlugin } from "./.setup/channel-CT4CVd4d.mjs";
1
+ import { _ as resolveDefaultClickClackAccountId, d as DEFAULT_ACCOUNT_ID, h as resolveClickClackAccount, m as listEnabledClickClackAccounts, p as listClickClackAccountIds, s as clickClackConfigSchema } from "./.setup/setup-surface-BW1Q06w5.mjs";
2
+ import { a as setClickClackRuntime, i as getClickClackRuntime, n as buildClickClackTarget, r as parseClickClackTarget, t as clickClackPlugin } from "./.setup/channel-Bcwtohk7.mjs";
3
+ import { i as createClickClackClient } from "./.setup/resolve-BHGLohni.mjs";
3
4
  export { DEFAULT_ACCOUNT_ID, buildClickClackTarget, clickClackConfigSchema, clickClackPlugin, createClickClackClient, getClickClackRuntime, listClickClackAccountIds, listEnabledClickClackAccounts, parseClickClackTarget, resolveClickClackAccount, resolveDefaultClickClackAccountId, setClickClackRuntime };
@@ -1,2 +1,2 @@
1
- import { t as clickClackPlugin } from "./.setup/channel-CT4CVd4d.mjs";
1
+ import { t as clickClackPlugin } from "./.setup/channel-Bcwtohk7.mjs";
2
2
  export { clickClackPlugin };
@@ -1,7 +1,164 @@
1
- import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor-migrations";
1
+ import { n as __reExport, t as __exportAll } from "./.setup/rolldown-runtime-B4FMCO8f.mjs";
2
+ //#region packages/normalization-core/src/record-coerce.ts
3
+ /** Type guard for non-array object records at browser-safe boundaries. */
4
+ function isRecord(value) {
5
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ /** Returns a non-array record or null. */
8
+ function asNullableRecord(value) {
9
+ return isRecord(value) ? value : null;
10
+ }
11
+ //#endregion
12
+ //#region src/config/channel-config-normalization.ts
13
+ /** Applies one channel-specific doctor migration to every object-shaped account. */
14
+ function normalizeChannelAccounts(params) {
15
+ const rawAccounts = asNullableRecord(params.entry.accounts);
16
+ if (!rawAccounts) return {
17
+ entry: params.entry,
18
+ changed: false
19
+ };
20
+ let changed = false;
21
+ const accounts = { ...rawAccounts };
22
+ for (const [accountId, value] of Object.entries(rawAccounts)) {
23
+ const account = asNullableRecord(value);
24
+ if (!account) continue;
25
+ const normalized = params.normalizeAccount({
26
+ account,
27
+ accountId,
28
+ pathPrefix: `${params.pathPrefix}.accounts.${accountId}`,
29
+ changes: params.changes
30
+ });
31
+ if (normalized.changed) {
32
+ accounts[accountId] = normalized.entry;
33
+ changed = true;
34
+ }
35
+ }
36
+ return changed ? {
37
+ entry: {
38
+ ...params.entry,
39
+ accounts
40
+ },
41
+ changed: true
42
+ } : {
43
+ entry: params.entry,
44
+ changed: false
45
+ };
46
+ }
47
+ /** Applies the same channel-specific doctor migration at root and account scope. */
48
+ function normalizeChannelConfigEntries(params) {
49
+ const changes = params.changes ?? [];
50
+ const channels = params.cfg.channels;
51
+ const entry = asNullableRecord(channels?.[params.channelId]);
52
+ if (!entry) return {
53
+ config: params.cfg,
54
+ changes
55
+ };
56
+ const channelPath = `channels.${params.channelId}`;
57
+ const root = params.normalizeEntry({
58
+ entry,
59
+ pathPrefix: channelPath,
60
+ changes
61
+ });
62
+ const accounts = normalizeChannelAccounts({
63
+ entry: root.entry,
64
+ pathPrefix: channelPath,
65
+ changes,
66
+ normalizeAccount: (accountParams) => params.normalizeEntry({
67
+ entry: accountParams.account,
68
+ accountId: accountParams.accountId,
69
+ pathPrefix: accountParams.pathPrefix,
70
+ changes: accountParams.changes
71
+ })
72
+ });
73
+ if (!root.changed && !accounts.changed) return {
74
+ config: params.cfg,
75
+ changes
76
+ };
77
+ return {
78
+ config: {
79
+ ...params.cfg,
80
+ channels: {
81
+ ...channels,
82
+ [params.channelId]: accounts.entry
83
+ }
84
+ },
85
+ changes
86
+ };
87
+ }
88
+ //#endregion
89
+ //#region src/plugin-sdk/legacy-webhook-listener-migration.ts
90
+ /** Preserve explicitly configured webhook listeners while moving ingress onto Gateway routes. */
91
+ function createLegacyWebhookListenerDoctorContract(params) {
92
+ const portKey = params.portKey ?? "webhookPort";
93
+ const hostKey = params.hostKey === void 0 ? "webhookHost" : params.hostKey;
94
+ const source = (entry) => params.webhookKey ? asNullableRecord(entry[params.webhookKey]) : entry;
95
+ const hasLegacy = (value) => {
96
+ const entry = asNullableRecord(value);
97
+ const listener = entry && source(entry);
98
+ return Boolean(listener && (Object.hasOwn(listener, portKey) || hostKey && Object.hasOwn(listener, hostKey)));
99
+ };
100
+ const prefix = `channels.${params.channelKey}`;
101
+ return {
102
+ legacyConfigRules: [{
103
+ path: ["channels", params.channelKey],
104
+ message: `${prefix} webhook listeners moved to Gateway routes. Run "openclaw doctor --fix" to preserve explicitly configured listener settings as legacyWebhook.`,
105
+ match: (value) => {
106
+ const accounts = asNullableRecord(asNullableRecord(value)?.accounts);
107
+ return hasLegacy(value) || Object.values(accounts ?? {}).some(hasLegacy);
108
+ }
109
+ }],
110
+ normalizeCompatibilityConfig: ({ cfg }) => {
111
+ const root = asNullableRecord(asNullableRecord(cfg.channels)?.[params.channelKey]);
112
+ const inherited = root && source(root);
113
+ const canonicalRoot = asNullableRecord(root?.legacyWebhook);
114
+ return normalizeChannelConfigEntries({
115
+ cfg,
116
+ channelId: params.channelKey,
117
+ normalizeEntry: ({ entry, accountId, pathPrefix, changes }) => {
118
+ const listener = source(entry);
119
+ if (!listener || !hasLegacy(entry)) return {
120
+ entry,
121
+ changed: false
122
+ };
123
+ const next = { ...entry };
124
+ const port = Object.hasOwn(listener, portKey) ? listener[portKey] : (accountId ? canonicalRoot?.port ?? inherited?.[portKey] : void 0) ?? params.defaultPort;
125
+ const inheritedHost = accountId ? canonicalRoot ? canonicalRoot.host : (hostKey ? inherited?.[hostKey] : void 0) ?? params.defaultHost : params.defaultHost;
126
+ const host = hostKey ? listener[hostKey] ?? inheritedHost : inheritedHost;
127
+ const legacyPath = [pathPrefix, params.webhookKey].filter(Boolean).join(".");
128
+ if (Object.hasOwn(entry, "legacyWebhook")) changes.push(`Removed ${legacyPath} legacy listener keys; ${pathPrefix}.legacyWebhook is already configured.`);
129
+ else if (accountId && root?.legacyWebhook === false) changes.push(`Removed ${legacyPath} legacy listener keys; ${prefix}.legacyWebhook: false keeps this account's inherited listener disabled.`);
130
+ else {
131
+ next.legacyWebhook = {
132
+ port,
133
+ ...host !== void 0 ? { host } : {}
134
+ };
135
+ changes.push(`Moved ${legacyPath} listener settings to ${pathPrefix}.legacyWebhook. Point the external callback or reverse proxy at the Gateway port and webhook path, verify delivery, then set legacyWebhook: false to disable legacy forwarding.`);
136
+ }
137
+ const updated = params.webhookKey ? { ...listener } : next;
138
+ delete updated[portKey];
139
+ if (hostKey) delete updated[hostKey];
140
+ if (params.webhookKey) {
141
+ if (Object.keys(updated).length) next[params.webhookKey] = updated;
142
+ else delete next[params.webhookKey];
143
+ }
144
+ return {
145
+ entry: next,
146
+ changed: true
147
+ };
148
+ }
149
+ });
150
+ }
151
+ };
152
+ }
153
+ //#endregion
154
+ //#region \0openclaw:bundled-sdk:openclaw/plugin-sdk/runtime-doctor-migrations
155
+ var runtime_doctor_migrations_exports = /* @__PURE__ */ __exportAll({ createLegacyWebhookListenerDoctorContract: () => createLegacyWebhookListenerDoctorContract });
156
+ import * as import_openclaw_plugin_sdk_runtime_doctor_migrations from "openclaw/plugin-sdk/runtime-doctor-migrations";
157
+ __reExport(runtime_doctor_migrations_exports, import_openclaw_plugin_sdk_runtime_doctor_migrations);
158
+ //#endregion
2
159
  //#region extensions/clickclack/src/doctor-contract.ts
3
160
  function stripTimeoutSeconds(value) {
4
- const record = asObjectRecord(value);
161
+ const record = (0, runtime_doctor_migrations_exports.asObjectRecord)(value);
5
162
  if (!record) return {
6
163
  value,
7
164
  changed: false
@@ -23,7 +180,7 @@ function stripTimeoutSeconds(value) {
23
180
  };
24
181
  }
25
182
  function normalizeCompatibilityConfig({ cfg }) {
26
- const rawEntry = asObjectRecord(cfg.channels?.clickclack);
183
+ const rawEntry = (0, runtime_doctor_migrations_exports.asObjectRecord)(cfg.channels?.clickclack);
27
184
  if (!rawEntry) return {
28
185
  config: cfg,
29
186
  changes: []
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as registerClickClackDiscussions } from "./.setup/runtime-api-A9r1b12d.mjs";
1
+ import { t as registerClickClackDiscussions } from "./.setup/runtime-api-BvP8DrPQ.mjs";
2
2
  import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
3
3
  //#region extensions/clickclack/index.ts
4
4
  /**
@@ -1,4 +1,5 @@
1
- import { b as resolveClickClackAccount, u as createClickClackClient } from "./.setup/setup-surface-BBl3rvVx.mjs";
2
- import { a as setClickClackRuntime, r as parseClickClackTarget } from "./.setup/channel-CT4CVd4d.mjs";
3
- import { t as registerClickClackDiscussions } from "./.setup/runtime-api-A9r1b12d.mjs";
1
+ import { h as resolveClickClackAccount } from "./.setup/setup-surface-BW1Q06w5.mjs";
2
+ import { a as setClickClackRuntime, r as parseClickClackTarget } from "./.setup/channel-Bcwtohk7.mjs";
3
+ import { i as createClickClackClient } from "./.setup/resolve-BHGLohni.mjs";
4
+ import { t as registerClickClackDiscussions } from "./.setup/runtime-api-BvP8DrPQ.mjs";
4
5
  export { createClickClackClient, parseClickClackTarget, registerClickClackDiscussions, resolveClickClackAccount, setClickClackRuntime };
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  var setup_entry_default = defineBundledChannelSetupEntry({
5
5
  importMetaUrl: import.meta.url,
6
6
  plugin: {
7
- specifier: fileURLToPath(new URL(".setup/setup-plugin-api-B19mjQvl.mjs", import.meta.url).href),
7
+ specifier: fileURLToPath(new URL(".setup/setup-plugin-api-BH_nw-AP.mjs", import.meta.url).href),
8
8
  exportName: "clickClackSetupPlugin"
9
9
  },
10
10
  secrets: {
@@ -1,2 +1,2 @@
1
- import { t as clickClackSetupPlugin } from "./.setup/setup-plugin-api-BUwBPYpy.mjs";
1
+ import { t as clickClackSetupPlugin } from "./.setup/setup-plugin-api-C9QcBJuW.mjs";
2
2
  export { clickClackSetupPlugin };
@@ -237,6 +237,9 @@
237
237
  "requireMention": {
238
238
  "type": "boolean"
239
239
  },
240
+ "requireMentionInBotThreads": {
241
+ "type": "boolean"
242
+ },
240
243
  "mentionPatterns": {
241
244
  "type": "array",
242
245
  "items": {
@@ -254,6 +257,9 @@
254
257
  "requireMention": {
255
258
  "type": "boolean"
256
259
  },
260
+ "requireMentionInBotThreads": {
261
+ "type": "boolean"
262
+ },
257
263
  "mentionPatterns": {
258
264
  "type": "array",
259
265
  "items": {
@@ -548,6 +554,9 @@
548
554
  "requireMention": {
549
555
  "type": "boolean"
550
556
  },
557
+ "requireMentionInBotThreads": {
558
+ "type": "boolean"
559
+ },
551
560
  "mentionPatterns": {
552
561
  "type": "array",
553
562
  "items": {
@@ -565,6 +574,9 @@
565
574
  "requireMention": {
566
575
  "type": "boolean"
567
576
  },
577
+ "requireMentionInBotThreads": {
578
+ "type": "boolean"
579
+ },
568
580
  "mentionPatterns": {
569
581
  "type": "array",
570
582
  "items": {
@@ -640,7 +652,25 @@
640
652
  "additionalProperties": false
641
653
  },
642
654
  "label": "ClickClack",
643
- "description": "ClickClack channel accounts and group activation policy."
655
+ "description": "ClickClack channel accounts and group activation policy.",
656
+ "uiHints": {
657
+ "requireMentionInBotThreads": {
658
+ "label": "Require Mention in Bot Threads",
659
+ "help": "Override mention requirements in threads rooted in this bot's messages. Unset keeps the normal mention policy."
660
+ },
661
+ "groups.*.requireMentionInBotThreads": {
662
+ "label": "Require Mention in Bot Threads",
663
+ "help": "Override the account policy for threads rooted in this bot's messages in this channel."
664
+ },
665
+ "accounts.*.requireMentionInBotThreads": {
666
+ "label": "Require Mention in Bot Threads",
667
+ "help": "Override mention requirements in threads rooted in this bot's messages. Unset inherits the channel configuration."
668
+ },
669
+ "accounts.*.groups.*.requireMentionInBotThreads": {
670
+ "label": "Require Mention in Bot Threads",
671
+ "help": "Override the account policy for threads rooted in this bot's messages in this channel."
672
+ }
673
+ }
644
674
  }
645
675
  },
646
676
  "contracts": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/clickclack",
3
- "version": "2026.9.6",
3
+ "version": "2026.9.8",
4
4
  "description": "OpenClaw ClickClack channel plugin",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  "zod": "4.6.5"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.9.6"
16
+ "openclaw": ">=2026.9.8"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -139,10 +139,10 @@
139
139
  "allowInvalidConfigRecovery": true
140
140
  },
141
141
  "compat": {
142
- "pluginApi": ">=2026.9.6"
142
+ "pluginApi": ">=2026.9.8"
143
143
  },
144
144
  "build": {
145
- "openclawVersion": "2026.9.6",
145
+ "openclawVersion": "2026.9.8",
146
146
  "bundledDist": false
147
147
  },
148
148
  "release": {
@@ -1,2 +0,0 @@
1
- import { t as clickClackSetupPlugin } from "./setup-plugin-api-BUwBPYpy.mjs";
2
- export { clickClackSetupPlugin };