diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa8815b6853..d707101d22b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Slack/setup: print the generated app manifest as plain JSON instead of embedding it inside the framed setup note, so it can be copied into Slack without deleting border characters. Fixes #65751. Thanks @theDanielJLewis.
+- Channels/WhatsApp: route CLI logout through the live Gateway and stop runtime-backed listeners before channel removal, so removing a WhatsApp account does not leave the old socket replying until restart. Fixes #67746. Thanks @123Mismail.
- Agents/Codex: stop prompting message-tool-only source turns to finish with `NO_REPLY`, so quiet turns are represented by not calling the visible message tool instead of conflicting final-text instructions. Thanks @pashpashpash.
- Gateway/config: report failed backup restores as failed in logs and config observe audit records instead of marking them valid. (#70515) Thanks @davidangularme.
- Compaction: use the active session model fallback chain for implicit summarization failures without persisting fallback model selection, so Azure content-filter 400s can recover. Fixes #64960. (#74470) Thanks @jalehman and @OpenCodeEngineer.
diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md
index 53fb9341224..572b041159e 100644
--- a/docs/channels/whatsapp.md
+++ b/docs/channels/whatsapp.md
@@ -492,6 +492,8 @@ Behavior notes:
`openclaw channels logout --channel whatsapp [--account ]` clears WhatsApp auth state for that account.
+ When a Gateway is reachable, logout first stops the live WhatsApp listener for the selected account so the linked session does not keep receiving messages until the next restart. `openclaw channels remove --channel whatsapp` also stops the live listener before disabling or deleting account config.
+
In legacy auth directories, `oauth.json` is preserved while Baileys auth files are removed.
diff --git a/docs/cli/channels.md b/docs/cli/channels.md
index 438cb3ee949..12edbb14336 100644
--- a/docs/cli/channels.md
+++ b/docs/cli/channels.md
@@ -52,6 +52,7 @@ openclaw channels remove --channel telegram --delete
`channels remove` only operates on installed/configured channel plugins. Use `channels add` first for installable catalog channels.
+For runtime-backed channel plugins, `channels remove` also asks the running Gateway to stop the selected account before it updates config, so disabling or deleting an account does not leave the old listener active until restart.
Common non-interactive add surfaces include:
@@ -94,6 +95,7 @@ openclaw channels logout --channel whatsapp
- `channels login` supports `--verbose`.
- `channels login` and `logout` can infer the channel when only one supported login target is configured.
+- `channels logout` prefers the live Gateway path when reachable, so logout stops any active listener before clearing channel auth state. If a local Gateway is not reachable, it falls back to local auth cleanup.
- Run `channels login` from a terminal on the gateway host. Agent `exec` blocks this interactive login flow; channel-native agent login tools, such as `whatsapp_login`, should be used from chat when available.
## Troubleshooting
diff --git a/src/cli/channel-auth.test.ts b/src/cli/channel-auth.test.ts
index a4c544779d6..a0a4402a347 100644
--- a/src/cli/channel-auth.test.ts
+++ b/src/cli/channel-auth.test.ts
@@ -260,9 +260,10 @@ describe("channel-auth", () => {
await runChannelLogout({}, runtime);
- expect(mocks.logoutAccount).toHaveBeenCalledWith(
+ expect(mocks.callGateway).toHaveBeenCalledWith(
expect.objectContaining({
- cfg: autoEnabledCfg,
+ config: autoEnabledCfg,
+ method: "channels.logout",
}),
);
expect(mocks.replaceConfigFile).toHaveBeenCalledWith({
@@ -518,7 +519,28 @@ describe("channel-auth", () => {
);
});
- it("runs logout with resolved account and explicit account id", async () => {
+ it("runs logout through the live gateway with resolved account and explicit account id", async () => {
+ await runChannelLogout({ channel: "whatsapp", account: " acct-2 " }, runtime);
+
+ expect(mocks.callGateway).toHaveBeenCalledWith({
+ config: { channels: { whatsapp: {} } },
+ method: "channels.logout",
+ params: {
+ channel: "whatsapp",
+ accountId: "acct-2",
+ },
+ mode: "backend",
+ clientName: "gateway-client",
+ deviceIdentity: null,
+ });
+ expect(mocks.resolveAccount).not.toHaveBeenCalled();
+ expect(mocks.logoutAccount).not.toHaveBeenCalled();
+ expect(mocks.setVerbose).not.toHaveBeenCalled();
+ });
+
+ it("falls back to local auth cleanup when a local gateway logout is unreachable", async () => {
+ mocks.callGateway.mockRejectedValue(new Error("gateway unreachable"));
+
await runChannelLogout({ channel: "whatsapp", account: " acct-2 " }, runtime);
expect(mocks.resolveAccount).toHaveBeenCalledWith({ channels: { whatsapp: {} } }, "acct-2");
@@ -528,6 +550,9 @@ describe("channel-auth", () => {
account: { id: "resolved-account" },
runtime,
});
+ expect(runtime.log).toHaveBeenCalledWith(
+ expect.stringContaining("running gateway did not stop it: gateway unreachable"),
+ );
expect(mocks.setVerbose).not.toHaveBeenCalled();
});
diff --git a/src/cli/channel-auth.ts b/src/cli/channel-auth.ts
index 41dedefc580..5ab295129dd 100644
--- a/src/cli/channel-auth.ts
+++ b/src/cli/channel-auth.ts
@@ -168,6 +168,36 @@ async function reconcileGatewayRuntimeAfterLocalLogin(params: {
}
}
+async function logoutViaGatewayRuntime(params: {
+ cfg: OpenClawConfig;
+ channelId: string;
+ accountId: string;
+ runtime: RuntimeEnv;
+}): Promise {
+ try {
+ await callGateway({
+ config: params.cfg,
+ method: "channels.logout",
+ params: {
+ channel: params.channelId,
+ accountId: params.accountId,
+ },
+ mode: GATEWAY_CLIENT_MODES.BACKEND,
+ clientName: GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
+ deviceIdentity: null,
+ });
+ return true;
+ } catch (error) {
+ if (params.cfg.gateway?.mode === "remote") {
+ throw error;
+ }
+ params.runtime.log(
+ `Local logout will clear auth for ${params.channelId}/${params.accountId}, but the running gateway did not stop it: ${formatErrorMessage(error)}`,
+ );
+ return false;
+ }
+}
+
export async function runChannelLogin(
opts: ChannelAuthOptions,
runtime: RuntimeEnv = defaultRuntime,
@@ -235,8 +265,18 @@ export async function runChannelLogout(
if (!logoutAccount) {
throw new Error(`Channel ${channelInput} does not support logout`);
}
- // Auth-only flow: resolve account + clear session state only.
+ // Prefer the live gateway so logout also stops any active channel runtime.
const { accountId } = resolveAccountContext(plugin, opts, cfg);
+ if (
+ await logoutViaGatewayRuntime({
+ cfg,
+ channelId: plugin.id,
+ accountId,
+ runtime,
+ })
+ ) {
+ return;
+ }
const account = plugin.config.resolveAccount(cfg, accountId);
await logoutAccount({
cfg,
diff --git a/src/commands/channels.remove.test.ts b/src/commands/channels.remove.test.ts
index e901042382e..dbc1cf5a595 100644
--- a/src/commands/channels.remove.test.ts
+++ b/src/commands/channels.remove.test.ts
@@ -1,5 +1,6 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { ChannelPluginCatalogEntry } from "../channels/plugins/catalog.js";
+import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js";
import { createTestRegistry } from "../test-utils/channel-plugins.js";
import {
@@ -23,6 +24,10 @@ const registryRefreshMocks = vi.hoisted(() => ({
refreshPluginRegistryAfterConfigMutation: vi.fn(async () => undefined),
}));
+const gatewayMocks = vi.hoisted(() => ({
+ callGateway: vi.fn(async () => ({ stopped: true })),
+}));
+
vi.mock("../channels/plugins/catalog.js", async () => {
const actual = await vi.importActual(
"../channels/plugins/catalog.js",
@@ -54,6 +59,10 @@ vi.mock("./channel-setup/plugin-install.js", async () => {
vi.mock("../cli/plugins-registry-refresh.js", () => registryRefreshMocks);
+vi.mock("../gateway/call.js", () => ({
+ callGateway: gatewayMocks.callGateway,
+}));
+
const runtime = createTestRuntime();
describe("channelsRemoveCommand", () => {
@@ -86,6 +95,8 @@ describe("channelsRemoveCommand", () => {
createTestRegistry(),
);
registryRefreshMocks.refreshPluginRegistryAfterConfigMutation.mockClear();
+ gatewayMocks.callGateway.mockClear();
+ gatewayMocks.callGateway.mockResolvedValue({ stopped: true });
setActivePluginRegistry(createTestRegistry());
});
@@ -170,4 +181,71 @@ describe("channelsRemoveCommand", () => {
expect(runtime.error).not.toHaveBeenCalled();
expect(runtime.exit).not.toHaveBeenCalled();
});
+
+ it("stops an active gateway channel runtime before deleting a runtime-backed account", async () => {
+ configMocks.readConfigFileSnapshot.mockResolvedValue({
+ ...baseConfigSnapshot,
+ config: {
+ channels: {
+ "external-chat": {
+ enabled: true,
+ token: "token-1",
+ },
+ },
+ },
+ });
+ const catalogEntry: ChannelPluginCatalogEntry = createExternalChatCatalogEntry();
+ catalogMocks.listChannelPluginCatalogEntries.mockReturnValue([catalogEntry]);
+ const scopedPlugin = {
+ ...createExternalChatDeletePlugin(),
+ gateway: {
+ startAccount: vi.fn(),
+ },
+ } as ChannelPlugin;
+ vi.mocked(loadChannelSetupPluginRegistrySnapshotForChannel).mockReturnValue(
+ createTestRegistry([
+ {
+ pluginId: "@vendor/external-chat-plugin",
+ plugin: scopedPlugin,
+ source: "test",
+ },
+ ]),
+ );
+
+ await channelsRemoveCommand(
+ {
+ channel: "external-chat",
+ account: "default",
+ delete: true,
+ },
+ runtime,
+ { hasFlags: true },
+ );
+
+ expect(gatewayMocks.callGateway).toHaveBeenCalledWith({
+ config: {
+ channels: {
+ "external-chat": {
+ enabled: true,
+ token: "token-1",
+ },
+ },
+ },
+ method: "channels.stop",
+ params: {
+ channel: "external-chat",
+ accountId: "default",
+ },
+ mode: "backend",
+ clientName: "gateway-client",
+ deviceIdentity: null,
+ });
+ expect(configMocks.writeConfigFile).toHaveBeenCalledWith(
+ expect.not.objectContaining({
+ channels: expect.objectContaining({
+ "external-chat": expect.anything(),
+ }),
+ }),
+ );
+ });
});
diff --git a/src/commands/channels/remove.ts b/src/commands/channels/remove.ts
index 62d6dcf9102..18745c6ca75 100644
--- a/src/commands/channels/remove.ts
+++ b/src/commands/channels/remove.ts
@@ -5,9 +5,12 @@ import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
import { commitConfigWithPendingPluginInstalls } from "../../cli/plugins-install-record-commit.js";
import { refreshPluginRegistryAfterConfigMutation } from "../../cli/plugins-registry-refresh.js";
import { replaceConfigFile, type OpenClawConfig } from "../../config/config.js";
+import { callGateway } from "../../gateway/call.js";
+import { formatErrorMessage } from "../../infra/errors.js";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
import { defaultRuntime, type RuntimeEnv } from "../../runtime.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
+import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
import { createClackPrompter } from "../../wizard/clack-prompter.js";
import { channelLabel } from "./runtime-label.js";
import { type ChatChannel, requireValidConfigFileSnapshot, shouldUseWizard } from "./shared.js";
@@ -30,6 +33,35 @@ function listAccountIds(
return plugin.config.listAccountIds(cfg);
}
+async function stopGatewayRuntimeBeforeRemove(params: {
+ cfg: OpenClawConfig;
+ channel: ChatChannel;
+ accountId: string;
+ plugin: ChannelPlugin;
+ runtime: RuntimeEnv;
+}) {
+ if (!params.plugin.gateway?.startAccount && !params.plugin.gateway?.logoutAccount) {
+ return;
+ }
+ try {
+ await callGateway({
+ config: params.cfg,
+ method: "channels.stop",
+ params: {
+ channel: params.channel,
+ accountId: params.accountId,
+ },
+ mode: GATEWAY_CLIENT_MODES.BACKEND,
+ clientName: GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
+ deviceIdentity: null,
+ });
+ } catch (error) {
+ params.runtime.log(
+ `Could not stop running ${channelLabel(params.channel)} account "${params.accountId}" before removing it: ${formatErrorMessage(error)}`,
+ );
+ }
+}
+
export async function channelsRemoveCommand(
opts: ChannelsRemoveOptions,
runtime: RuntimeEnv = defaultRuntime,
@@ -147,6 +179,14 @@ export async function channelsRemoveCommand(
normalizeAccountId(accountId) ?? resolveChannelDefaultAccountId({ plugin, cfg });
const accountKey = resolvedAccountId || DEFAULT_ACCOUNT_ID;
+ await stopGatewayRuntimeBeforeRemove({
+ cfg,
+ channel: resolvedChannelId,
+ accountId: accountKey,
+ plugin,
+ runtime,
+ });
+
let next = { ...cfg };
const prevCfg = cfg;
if (deleteConfig) {
diff --git a/src/gateway/method-scopes.ts b/src/gateway/method-scopes.ts
index 8f8be884816..78ea6c6770f 100644
--- a/src/gateway/method-scopes.ts
+++ b/src/gateway/method-scopes.ts
@@ -169,6 +169,7 @@ const METHOD_SCOPE_GROUPS: Record = {
],
[ADMIN_SCOPE]: [
"channels.start",
+ "channels.stop",
"channels.logout",
"agents.create",
"agents.update",
diff --git a/src/gateway/protocol/index.ts b/src/gateway/protocol/index.ts
index 9cded24410c..806355c30c8 100644
--- a/src/gateway/protocol/index.ts
+++ b/src/gateway/protocol/index.ts
@@ -57,6 +57,8 @@ import {
AgentWaitParamsSchema,
type ChannelsStartParams,
ChannelsStartParamsSchema,
+ type ChannelsStopParams,
+ ChannelsStopParamsSchema,
type ChannelsLogoutParams,
ChannelsLogoutParamsSchema,
type TalkConfigParams,
@@ -528,6 +530,7 @@ export const validateChannelsStatusParams = ajv.compile(
);
export const validateChannelsStartParams =
ajv.compile(ChannelsStartParamsSchema);
+export const validateChannelsStopParams = ajv.compile(ChannelsStopParamsSchema);
export const validateChannelsLogoutParams = ajv.compile(
ChannelsLogoutParamsSchema,
);
@@ -740,6 +743,7 @@ export {
ChannelsStatusParamsSchema,
ChannelsStatusResultSchema,
ChannelsStartParamsSchema,
+ ChannelsStopParamsSchema,
ChannelsLogoutParamsSchema,
WebLoginStartParamsSchema,
WebLoginWaitParamsSchema,
@@ -854,6 +858,7 @@ export type {
ChannelsStatusParams,
ChannelsStatusResult,
ChannelsStartParams,
+ ChannelsStopParams,
ChannelsLogoutParams,
WebLoginStartParams,
WebLoginWaitParams,
diff --git a/src/gateway/protocol/schema/channels.ts b/src/gateway/protocol/schema/channels.ts
index 6cd7a33f95e..cd831b50ac1 100644
--- a/src/gateway/protocol/schema/channels.ts
+++ b/src/gateway/protocol/schema/channels.ts
@@ -310,6 +310,14 @@ export const ChannelsLogoutParamsSchema = Type.Object(
{ additionalProperties: false },
);
+export const ChannelsStopParamsSchema = Type.Object(
+ {
+ channel: NonEmptyString,
+ accountId: Type.Optional(Type.String()),
+ },
+ { additionalProperties: false },
+);
+
export const ChannelsStartParamsSchema = Type.Object(
{
channel: NonEmptyString,
diff --git a/src/gateway/protocol/schema/protocol-schemas.ts b/src/gateway/protocol/schema/protocol-schemas.ts
index 0a7d2dfa361..19a27eb3eb6 100644
--- a/src/gateway/protocol/schema/protocol-schemas.ts
+++ b/src/gateway/protocol/schema/protocol-schemas.ts
@@ -63,6 +63,7 @@ import {
} from "./artifacts.js";
import {
ChannelsStartParamsSchema,
+ ChannelsStopParamsSchema,
ChannelsLogoutParamsSchema,
TalkConfigParamsSchema,
TalkConfigResultSchema,
@@ -328,6 +329,7 @@ export const ProtocolSchemas = {
ChannelsStatusParams: ChannelsStatusParamsSchema,
ChannelsStatusResult: ChannelsStatusResultSchema,
ChannelsStartParams: ChannelsStartParamsSchema,
+ ChannelsStopParams: ChannelsStopParamsSchema,
ChannelsLogoutParams: ChannelsLogoutParamsSchema,
WebLoginStartParams: WebLoginStartParamsSchema,
WebLoginWaitParams: WebLoginWaitParamsSchema,
diff --git a/src/gateway/protocol/schema/types.ts b/src/gateway/protocol/schema/types.ts
index f92596bdebf..153353792ad 100644
--- a/src/gateway/protocol/schema/types.ts
+++ b/src/gateway/protocol/schema/types.ts
@@ -99,6 +99,7 @@ export type TalkSpeakResult = SchemaType<"TalkSpeakResult">;
export type ChannelsStatusParams = SchemaType<"ChannelsStatusParams">;
export type ChannelsStatusResult = SchemaType<"ChannelsStatusResult">;
export type ChannelsStartParams = SchemaType<"ChannelsStartParams">;
+export type ChannelsStopParams = SchemaType<"ChannelsStopParams">;
export type ChannelsLogoutParams = SchemaType<"ChannelsLogoutParams">;
export type WebLoginStartParams = SchemaType<"WebLoginStartParams">;
export type WebLoginWaitParams = SchemaType<"WebLoginWaitParams">;
diff --git a/src/gateway/server-methods-list.ts b/src/gateway/server-methods-list.ts
index 66fb1721a57..160c84579b2 100644
--- a/src/gateway/server-methods-list.ts
+++ b/src/gateway/server-methods-list.ts
@@ -15,6 +15,7 @@ const BASE_METHODS = [
"logs.tail",
"channels.status",
"channels.start",
+ "channels.stop",
"channels.logout",
"status",
"usage.status",
diff --git a/src/gateway/server-methods/channels.start.test.ts b/src/gateway/server-methods/channels.start.test.ts
index 97371d645c8..c30b18bf5da 100644
--- a/src/gateway/server-methods/channels.start.test.ts
+++ b/src/gateway/server-methods/channels.start.test.ts
@@ -40,6 +40,7 @@ function createOptions(
getRuntimeConfig: mocks.getRuntimeConfig,
startChannel: vi.fn(),
stopChannel: vi.fn(),
+ markChannelLoggedOut: vi.fn(),
getRuntimeSnapshot: vi.fn(
(): ChannelRuntimeSnapshot => ({
channels: {
@@ -178,6 +179,63 @@ describe("channelsHandlers channels.start", () => {
});
});
+describe("channelsHandlers channels.stop", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mocks.getRuntimeConfig.mockReturnValue({});
+ mocks.getChannelPlugin.mockReturnValue({
+ id: "whatsapp",
+ config: {
+ defaultAccountId: () => "default-account",
+ listAccountIds: () => ["default-account"],
+ resolveAccount: () => ({}),
+ },
+ });
+ });
+
+ it("stops a channel account without clearing auth state", async () => {
+ const stopChannel = vi.fn(async () => undefined);
+ const respond = vi.fn();
+
+ await channelsHandlers["channels.stop"](
+ createOptions(
+ { channel: "whatsapp" },
+ {
+ respond,
+ context: {
+ getRuntimeConfig: mocks.getRuntimeConfig,
+ stopChannel,
+ getRuntimeSnapshot: vi.fn(
+ (): ChannelRuntimeSnapshot => ({
+ channels: {},
+ channelAccounts: {
+ whatsapp: {
+ "default-account": {
+ accountId: "default-account",
+ running: false,
+ },
+ },
+ },
+ }),
+ ),
+ } as unknown as GatewayRequestHandlerOptions["context"],
+ },
+ ),
+ );
+
+ expect(stopChannel).toHaveBeenCalledWith("whatsapp", "default-account");
+ expect(respond).toHaveBeenCalledWith(
+ true,
+ {
+ channel: "whatsapp",
+ accountId: "default-account",
+ stopped: true,
+ },
+ undefined,
+ );
+ });
+});
+
describe("channelsHandlers channels.logout", () => {
beforeEach(() => {
vi.clearAllMocks();
diff --git a/src/gateway/server-methods/channels.ts b/src/gateway/server-methods/channels.ts
index c979442d005..095dface47f 100644
--- a/src/gateway/server-methods/channels.ts
+++ b/src/gateway/server-methods/channels.ts
@@ -22,6 +22,7 @@ import {
errorShape,
formatValidationErrors,
validateChannelsStartParams,
+ validateChannelsStopParams,
validateChannelsLogoutParams,
validateChannelsStatusParams,
} from "../protocol/index.js";
@@ -42,6 +43,12 @@ type ChannelStartPayload = {
started: boolean;
};
+type ChannelStopPayload = {
+ channel: ChannelId;
+ accountId: string;
+ stopped: boolean;
+};
+
const CHANNEL_STATUS_MAX_TIMEOUT_MS = 30_000;
const CHANNEL_STATUS_PROBE_CONCURRENCY = 5;
@@ -138,6 +145,29 @@ export async function startChannelAccount(params: {
};
}
+export async function stopChannelAccount(params: {
+ channelId: ChannelId;
+ accountId?: string | null;
+ cfg: OpenClawConfig;
+ context: GatewayRequestContext;
+ plugin: ChannelPlugin;
+}): Promise {
+ const resolvedAccountId = resolveChannelGatewayAccountId(params);
+ await params.context.stopChannel(params.channelId, resolvedAccountId);
+ const runtime = params.context.getRuntimeSnapshot();
+ const stopped =
+ resolveRuntimeAccountSnapshot({
+ runtime,
+ channelId: params.channelId,
+ accountId: resolvedAccountId,
+ })?.running !== true;
+ return {
+ channel: params.channelId,
+ accountId: resolvedAccountId,
+ stopped,
+ };
+}
+
export const channelsHandlers: GatewayRequestHandlers = {
"channels.status": async ({ params, respond, context }) => {
if (!validateChannelsStatusParams(params)) {
@@ -391,6 +421,52 @@ export const channelsHandlers: GatewayRequestHandlers = {
respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, formatForLog(error)));
}
},
+ "channels.stop": async ({ params, respond, context }) => {
+ if (!validateChannelsStopParams(params)) {
+ respond(
+ false,
+ undefined,
+ errorShape(
+ ErrorCodes.INVALID_REQUEST,
+ `invalid channels.stop params: ${formatValidationErrors(validateChannelsStopParams.errors)}`,
+ ),
+ );
+ return;
+ }
+ const rawChannel = (params as { channel?: unknown }).channel;
+ const channelId = typeof rawChannel === "string" ? normalizeChannelId(rawChannel) : null;
+ if (!channelId) {
+ respond(
+ false,
+ undefined,
+ errorShape(ErrorCodes.INVALID_REQUEST, "invalid channels.stop channel"),
+ );
+ return;
+ }
+ const plugin = getChannelPlugin(channelId);
+ if (!plugin) {
+ respond(
+ false,
+ undefined,
+ errorShape(ErrorCodes.INVALID_REQUEST, `unknown channel ${channelId}`),
+ );
+ return;
+ }
+ const accountIdRaw = (params as { accountId?: unknown }).accountId;
+ const accountId = normalizeOptionalString(accountIdRaw);
+ try {
+ const payload = await stopChannelAccount({
+ channelId,
+ accountId,
+ cfg: context.getRuntimeConfig(),
+ context,
+ plugin,
+ });
+ respond(true, payload, undefined);
+ } catch (error) {
+ respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, formatForLog(error)));
+ }
+ },
"channels.logout": async ({ params, respond, context }) => {
if (!validateChannelsLogoutParams(params)) {
respond(