mirror of
https://fastgit.cc/github.com/openclaw/openclaw
synced 2026-04-30 14:02:56 +08:00
fix(channels): accept setup aliases for add
This commit is contained in:
@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Codex harness: route native `request_user_input` prompts back to the originating chat, preserve queued follow-up answers, and honor newer app-server command approval amendment decisions.
|
||||
- Codex status: report Codex CLI OAuth as `oauth (codex-cli)` for native `codex/*` sessions instead of showing unknown auth. Fixes #70688. Thanks @jb510.
|
||||
- Codex harness/context-engine: redact context-engine assembly failures before logging, so fallback warnings do not serialize raw error objects. (#70809) Thanks @jalehman.
|
||||
- Channels/CLI: accept explicit shared-secret, base-URL, and auth-directory setup flags, and map legacy Nextcloud Talk `--url`/`--token` add commands to the bundled plugin setup input. Fixes #61759 and #61923.
|
||||
- WhatsApp/onboarding: keep first-run setup entry loading off the Baileys runtime dependency path, so packaged QuickStart installs can show WhatsApp setup before runtime deps are staged. Fixes #70932.
|
||||
- Block streaming: suppress final assembled text after partial block-delivery aborts when the already-sent text chunks exactly cover the final reply, preventing duplicate replies without dropping unrelated short messages. Fixes #70921.
|
||||
- Codex harness/Windows: resolve npm-installed `codex.cmd` shims through PATHEXT before starting the native app-server, so `codex/*` models work without a manual `.exe` shim. Fixes #70913.
|
||||
|
||||
@@ -44,6 +44,31 @@ Details: [Plugins](/tools/plugin)
|
||||
4. Configure OpenClaw:
|
||||
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
||||
- Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only)
|
||||
|
||||
CLI setup:
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel nextcloud-talk \
|
||||
--url https://cloud.example.com \
|
||||
--token "<shared-secret>"
|
||||
```
|
||||
|
||||
Equivalent explicit fields:
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel nextcloud-talk \
|
||||
--base-url https://cloud.example.com \
|
||||
--secret "<shared-secret>"
|
||||
```
|
||||
|
||||
File-backed secret:
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel nextcloud-talk \
|
||||
--base-url https://cloud.example.com \
|
||||
--secret-file /path/to/nextcloud-talk-secret
|
||||
```
|
||||
|
||||
5. Restart the gateway (or finish setup).
|
||||
|
||||
Minimal config:
|
||||
|
||||
@@ -64,6 +64,13 @@ openclaw channels login --channel whatsapp
|
||||
|
||||
```bash
|
||||
openclaw channels login --channel whatsapp --account work
|
||||
```
|
||||
|
||||
To attach an existing/custom WhatsApp Web auth directory before login:
|
||||
|
||||
```bash
|
||||
openclaw channels add --channel whatsapp --account work --auth-dir /path/to/wa-auth
|
||||
openclaw channels login --channel whatsapp --account work
|
||||
```
|
||||
|
||||
</Step>
|
||||
|
||||
@@ -86,6 +86,8 @@ export type ChannelSetupInput = {
|
||||
token?: string;
|
||||
privateKey?: string;
|
||||
tokenFile?: string;
|
||||
secret?: string;
|
||||
secretFile?: string;
|
||||
botToken?: string;
|
||||
appToken?: string;
|
||||
signalNumber?: string;
|
||||
@@ -115,6 +117,7 @@ export type ChannelSetupInput = {
|
||||
initialSyncLimit?: number;
|
||||
ship?: string;
|
||||
url?: string;
|
||||
baseUrl?: string;
|
||||
relayUrls?: string;
|
||||
code?: string;
|
||||
groupChannels?: string[];
|
||||
|
||||
@@ -169,12 +169,16 @@ export function registerChannelsCli(program: Command) {
|
||||
.option("--name <name>", "Display name for this account")
|
||||
.option("--token <token>", "Channel token or credential payload")
|
||||
.option("--token-file <path>", "Read channel token or credential payload from file")
|
||||
.option("--secret <secret>", "Channel shared secret")
|
||||
.option("--secret-file <path>", "Read channel shared secret from file")
|
||||
.option("--bot-token <token>", "Bot token")
|
||||
.option("--app-token <token>", "App token")
|
||||
.option("--password <password>", "Channel password or login secret")
|
||||
.option("--cli-path <path>", "Channel CLI path")
|
||||
.option("--url <url>", "Channel setup URL")
|
||||
.option("--base-url <url>", "Channel base URL")
|
||||
.option("--http-url <url>", "Channel HTTP service URL")
|
||||
.option("--auth-dir <path>", "Channel auth directory override")
|
||||
.option("--use-env", "Use env-backed credentials when supported", false),
|
||||
).action(async (opts, command) => {
|
||||
await runChannelsCommand(async () => {
|
||||
|
||||
@@ -193,6 +193,9 @@ function registerExternalChatSetupPlugin(pluginId = "@vendor/external-chat-plugi
|
||||
type SignalAfterAccountConfigWritten = NonNullable<
|
||||
NonNullable<ChannelPlugin["setup"]>["afterAccountConfigWritten"]
|
||||
>;
|
||||
type ApplyAccountConfigParams = Parameters<
|
||||
NonNullable<NonNullable<ChannelPlugin["setup"]>["applyAccountConfig"]>
|
||||
>[0];
|
||||
|
||||
function createSignalPlugin(
|
||||
afterAccountConfigWritten: SignalAfterAccountConfigWritten,
|
||||
@@ -306,6 +309,154 @@ describe("channelsAddCommand", () => {
|
||||
expect(lifecycleMocks.onAccountConfigChanged).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("maps legacy Nextcloud Talk add flags to setup input fields", async () => {
|
||||
const applyAccountConfig = vi.fn(({ cfg, input }) => ({
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
"nextcloud-talk": {
|
||||
enabled: true,
|
||||
baseUrl: input.baseUrl,
|
||||
botSecret: input.secret,
|
||||
botSecretFile: input.secretFile,
|
||||
},
|
||||
},
|
||||
}));
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "nextcloud-talk",
|
||||
plugin: {
|
||||
...createChannelTestPluginBase({
|
||||
id: "nextcloud-talk",
|
||||
label: "Nextcloud Talk",
|
||||
}),
|
||||
setup: { applyAccountConfig },
|
||||
},
|
||||
source: "test",
|
||||
},
|
||||
]),
|
||||
);
|
||||
configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot });
|
||||
|
||||
await channelsAddCommand(
|
||||
{
|
||||
channel: "nextcloud-talk",
|
||||
account: "default",
|
||||
url: "https://cloud.example.com/",
|
||||
token: "shared-secret",
|
||||
},
|
||||
runtime,
|
||||
{ hasFlags: true },
|
||||
);
|
||||
|
||||
expect(applyAccountConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
input: expect.objectContaining({
|
||||
url: "https://cloud.example.com/",
|
||||
token: "shared-secret",
|
||||
baseUrl: "https://cloud.example.com/",
|
||||
secret: "shared-secret",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channels: {
|
||||
"nextcloud-talk": {
|
||||
enabled: true,
|
||||
baseUrl: "https://cloud.example.com/",
|
||||
botSecret: "shared-secret",
|
||||
botSecretFile: undefined,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
configMocks.writeConfigFile.mockClear();
|
||||
applyAccountConfig.mockClear();
|
||||
await channelsAddCommand(
|
||||
{
|
||||
channel: "nextcloud-talk",
|
||||
account: "default",
|
||||
url: "https://cloud.example.com",
|
||||
tokenFile: "/tmp/nextcloud-secret",
|
||||
},
|
||||
runtime,
|
||||
{ hasFlags: true },
|
||||
);
|
||||
|
||||
expect(applyAccountConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
input: expect.objectContaining({
|
||||
baseUrl: "https://cloud.example.com",
|
||||
secretFile: "/tmp/nextcloud-secret",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("passes channel auth directory overrides through add setup input", async () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "whatsapp",
|
||||
plugin: {
|
||||
...createChannelTestPluginBase({
|
||||
id: "whatsapp",
|
||||
label: "WhatsApp",
|
||||
}),
|
||||
setup: {
|
||||
applyAccountConfig: (params: ApplyAccountConfigParams) => ({
|
||||
...params.cfg,
|
||||
channels: {
|
||||
...params.cfg.channels,
|
||||
whatsapp: {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
[params.accountId]: {
|
||||
enabled: true,
|
||||
authDir: params.input.authDir,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
source: "test",
|
||||
},
|
||||
]),
|
||||
);
|
||||
configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot });
|
||||
|
||||
await channelsAddCommand(
|
||||
{
|
||||
channel: "whatsapp",
|
||||
account: "work",
|
||||
authDir: "/tmp/openclaw-wa-auth",
|
||||
},
|
||||
runtime,
|
||||
{ hasFlags: true },
|
||||
);
|
||||
|
||||
expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channels: {
|
||||
whatsapp: {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
work: {
|
||||
enabled: true,
|
||||
authDir: "/tmp/openclaw-wa-auth",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("loads external channel setup snapshots for newly installed and existing plugins", async () => {
|
||||
configMocks.readConfigFileSnapshot.mockResolvedValue({ ...baseConfigSnapshot });
|
||||
setActivePluginRegistry(createTestRegistry());
|
||||
|
||||
@@ -37,6 +37,7 @@ export type ChannelsAddOptions = {
|
||||
} & Record<string, unknown>;
|
||||
|
||||
const CHANNEL_ADD_CONTROL_OPTION_KEYS = new Set(["channel", "account"]);
|
||||
const NEXTCLOUD_TALK_CLI_ALIASES = new Set(["nextcloud-talk", "nc-talk", "nc"]);
|
||||
|
||||
async function resolveCatalogChannelEntry(raw: string, cfg: OpenClawConfig | null) {
|
||||
const trimmed = normalizeOptionalLowercaseString(raw);
|
||||
@@ -72,6 +73,10 @@ function parseOptionalDelimitedInput(value: unknown): string[] | undefined {
|
||||
return parseOptionalDelimitedEntries(typeof value === "string" ? value : undefined);
|
||||
}
|
||||
|
||||
function readOptionalString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.length > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
function buildChannelSetupInput(opts: ChannelsAddOptions): ChannelSetupInput {
|
||||
const input: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(opts)) {
|
||||
@@ -81,6 +86,13 @@ function buildChannelSetupInput(opts: ChannelsAddOptions): ChannelSetupInput {
|
||||
input[key] = value;
|
||||
}
|
||||
|
||||
const rawChannel = readOptionalString(opts.channel)?.trim().toLowerCase();
|
||||
if (rawChannel && NEXTCLOUD_TALK_CLI_ALIASES.has(rawChannel)) {
|
||||
input.baseUrl ??= readOptionalString(input.url);
|
||||
input.secret ??= readOptionalString(input.token) ?? readOptionalString(input.password);
|
||||
input.secretFile ??= readOptionalString(input.tokenFile);
|
||||
}
|
||||
|
||||
input.initialSyncLimit = parseOptionalInt(opts.initialSyncLimit);
|
||||
input.groupChannels = parseOptionalDelimitedInput(opts.groupChannels);
|
||||
input.dmAllowlist = parseOptionalDelimitedInput(opts.dmAllowlist);
|
||||
|
||||
Reference in New Issue
Block a user