mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
feat: add ClickClack setup inputs
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-entry.ts",
|
||||
"channel": {
|
||||
"id": "clickclack",
|
||||
"label": "ClickClack",
|
||||
@@ -43,7 +44,13 @@
|
||||
"commands": {
|
||||
"nativeCommandsAutoEnabled": false,
|
||||
"nativeSkillsAutoEnabled": false
|
||||
}
|
||||
},
|
||||
"cliAddOptions": [
|
||||
{
|
||||
"flags": "--workspace <workspace>",
|
||||
"description": "ClickClack workspace id, slug, or name"
|
||||
}
|
||||
]
|
||||
},
|
||||
"install": {
|
||||
"clawhubSpec": "clawhub:@openclaw/clickclack",
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
// Clickclack tests cover accounts plugin behavior.
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
listClickClackAccountIds,
|
||||
@@ -120,6 +123,48 @@ describe("ClickClack account resolution", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the default ClickClack env token only for the default account", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
clickclack: {
|
||||
enabled: true,
|
||||
baseUrl: "https://app.clickclack.chat",
|
||||
workspace: "wsp_1",
|
||||
accounts: {
|
||||
work: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies CoreConfig;
|
||||
const env = { CLICKCLACK_BOT_TOKEN: " default-env-token " };
|
||||
|
||||
expect(resolveClickClackAccount({ cfg, env }).token).toBe("default-env-token");
|
||||
expect(resolveClickClackAccount({ cfg, accountId: "work", env }).token).toBe("");
|
||||
});
|
||||
|
||||
it("reads tokenFile credentials and treats them as an implicit default account", () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "clickclack-token-"));
|
||||
const tokenFile = path.join(tempDir, "token");
|
||||
fs.writeFileSync(tokenFile, " file-token \n", "utf8");
|
||||
try {
|
||||
const cfg = {
|
||||
channels: {
|
||||
clickclack: {
|
||||
enabled: true,
|
||||
baseUrl: "https://app.clickclack.chat",
|
||||
workspace: "wsp_1",
|
||||
tokenFile,
|
||||
},
|
||||
},
|
||||
} satisfies CoreConfig;
|
||||
|
||||
expect(listClickClackAccountIds(cfg)).toEqual(["default"]);
|
||||
expect(resolveClickClackAccount({ cfg }).token).toBe("file-token");
|
||||
} finally {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves model-mode bot account policy", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/acco
|
||||
import { resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
||||
import { resolveIntegerOption } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
||||
import {
|
||||
normalizeSecretInputString,
|
||||
normalizeResolvedSecretInputString,
|
||||
@@ -31,7 +32,7 @@ const {
|
||||
const channel = cfg.channels?.clickclack;
|
||||
return Boolean(
|
||||
channel?.baseUrl?.trim() &&
|
||||
hasConfiguredAccountValue(channel.token) &&
|
||||
(hasConfiguredAccountValue(channel.token) || Boolean(channel.tokenFile?.trim())) &&
|
||||
channel.workspace?.trim(),
|
||||
);
|
||||
},
|
||||
@@ -55,9 +56,22 @@ function resolveMergedClickClackAccountConfig(
|
||||
function resolveClickClackToken(params: {
|
||||
cfg: CoreConfig;
|
||||
value: unknown;
|
||||
tokenFile?: string;
|
||||
accountId: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): string {
|
||||
const tokenFile = params.tokenFile?.trim();
|
||||
if (tokenFile) {
|
||||
return (
|
||||
tryReadSecretFileSync(
|
||||
tokenFile,
|
||||
params.accountId === DEFAULT_ACCOUNT_ID
|
||||
? "channels.clickclack.tokenFile"
|
||||
: `channels.clickclack.accounts.${params.accountId}.tokenFile`,
|
||||
{ rejectSymlink: true },
|
||||
) ?? ""
|
||||
);
|
||||
}
|
||||
const resolved = resolveSecretInputString({
|
||||
value: params.value,
|
||||
path:
|
||||
@@ -68,6 +82,9 @@ function resolveClickClackToken(params: {
|
||||
mode: "inspect",
|
||||
});
|
||||
if (resolved.status !== "available") {
|
||||
if (resolved.status === "missing" && params.accountId === DEFAULT_ACCOUNT_ID) {
|
||||
return normalizeSecretInputString((params.env ?? process.env).CLICKCLACK_BOT_TOKEN) ?? "";
|
||||
}
|
||||
if (resolved.status === "configured_unavailable" && resolved.ref.source === "env") {
|
||||
const providerConfig = params.cfg.secrets?.providers?.[resolved.ref.provider];
|
||||
if (providerConfig) {
|
||||
@@ -118,6 +135,7 @@ export function resolveClickClackAccount(params: {
|
||||
const token = resolveClickClackToken({
|
||||
cfg: params.cfg,
|
||||
value: merged.token,
|
||||
tokenFile: merged.tokenFile,
|
||||
accountId,
|
||||
env: params.env,
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ const ClickClackAccountConfigSchema = z
|
||||
enabled: z.boolean().optional(),
|
||||
baseUrl: z.string().url().optional(),
|
||||
token: buildSecretInputSchema().optional(),
|
||||
tokenFile: z.string().optional(),
|
||||
workspace: z.string().optional(),
|
||||
botUserId: z.string().optional(),
|
||||
agentId: z.string().optional(),
|
||||
|
||||
@@ -9,6 +9,7 @@ export type ClickClackAccountConfig = {
|
||||
enabled?: boolean;
|
||||
baseUrl?: string;
|
||||
token?: unknown;
|
||||
tokenFile?: string;
|
||||
workspace?: string;
|
||||
botUserId?: string;
|
||||
agentId?: string;
|
||||
|
||||
@@ -139,6 +139,7 @@ export type ChannelSetupInput = {
|
||||
groupChannels?: string[];
|
||||
dmAllowlist?: string[];
|
||||
autoDiscoverChannels?: boolean;
|
||||
workspace?: string;
|
||||
};
|
||||
|
||||
export type ChannelStatusIssue = {
|
||||
|
||||
Reference in New Issue
Block a user