mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:20:44 +00:00
CLI: reserve channel removal exit choice
This commit is contained in:
@@ -25,6 +25,9 @@ vi.mock("./configure.shared.js", () => ({
|
||||
|
||||
import { removeChannelConfigWizard } from "./configure.channels.js";
|
||||
|
||||
const channelChoice = (id: string) => ({ kind: "channel" as const, id });
|
||||
const doneChoice = { kind: "done" as const };
|
||||
|
||||
describe("removeChannelConfigWizard", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
@@ -36,7 +39,7 @@ describe("removeChannelConfigWizard", () => {
|
||||
});
|
||||
|
||||
it("lists configured channels from openclaw.json even when no plugins are loaded", async () => {
|
||||
select.mockResolvedValue("done");
|
||||
select.mockResolvedValue(doneChoice);
|
||||
|
||||
await removeChannelConfigWizard(
|
||||
{
|
||||
@@ -55,17 +58,17 @@ describe("removeChannelConfigWizard", () => {
|
||||
expect.objectContaining({
|
||||
message: "Remove which channel config?",
|
||||
options: [
|
||||
expect.objectContaining({ value: "telegram", label: "Telegram" }),
|
||||
expect.objectContaining({ value: "twitch", label: "Twitch" }),
|
||||
expect.objectContaining({ value: "unknown", label: "unknown" }),
|
||||
{ value: "done", label: "Done" },
|
||||
expect.objectContaining({ value: channelChoice("telegram"), label: "Telegram" }),
|
||||
expect.objectContaining({ value: channelChoice("twitch"), label: "Twitch" }),
|
||||
expect.objectContaining({ value: channelChoice("unknown"), label: "unknown" }),
|
||||
{ value: doneChoice, label: "Done" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("deletes the selected channel block from openclaw.json", async () => {
|
||||
select.mockResolvedValueOnce("telegram").mockResolvedValueOnce("done");
|
||||
select.mockResolvedValueOnce(channelChoice("telegram")).mockResolvedValueOnce(doneChoice);
|
||||
|
||||
const next = await removeChannelConfigWizard(
|
||||
{
|
||||
@@ -89,8 +92,33 @@ describe("removeChannelConfigWizard", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("deletes a real channel block named done", async () => {
|
||||
select.mockResolvedValueOnce(channelChoice("done")).mockResolvedValueOnce(doneChoice);
|
||||
|
||||
const next = await removeChannelConfigWizard(
|
||||
{
|
||||
channels: {
|
||||
done: { token: "secret" },
|
||||
telegram: { token: "secret" },
|
||||
},
|
||||
} as never,
|
||||
{} as never,
|
||||
);
|
||||
|
||||
expect(confirm).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: "Delete done configuration from ~/.openclaw/openclaw.json?",
|
||||
}),
|
||||
);
|
||||
expect(next.channels).toEqual({ telegram: { token: "secret" } });
|
||||
expect(note).toHaveBeenCalledWith(
|
||||
"done removed from config.\nNote: credentials/sessions on disk are unchanged.",
|
||||
"Channel removed",
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves channel-wide defaults when deleting the last channel block", async () => {
|
||||
select.mockResolvedValueOnce("telegram").mockResolvedValueOnce("done");
|
||||
select.mockResolvedValueOnce(channelChoice("telegram")).mockResolvedValueOnce(doneChoice);
|
||||
|
||||
const next = await removeChannelConfigWizard(
|
||||
{
|
||||
@@ -110,7 +138,7 @@ describe("removeChannelConfigWizard", () => {
|
||||
});
|
||||
|
||||
it("does not list blocked object keys as removable channels", async () => {
|
||||
select.mockResolvedValue("done");
|
||||
select.mockResolvedValue(doneChoice);
|
||||
|
||||
await removeChannelConfigWizard(
|
||||
{
|
||||
@@ -127,8 +155,8 @@ describe("removeChannelConfigWizard", () => {
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: [
|
||||
expect.objectContaining({ value: "telegram", label: "Telegram" }),
|
||||
{ value: "done", label: "Done" },
|
||||
expect.objectContaining({ value: channelChoice("telegram"), label: "Telegram" }),
|
||||
{ value: doneChoice, label: "Done" },
|
||||
],
|
||||
}),
|
||||
);
|
||||
@@ -139,7 +167,7 @@ describe("removeChannelConfigWizard", () => {
|
||||
{ id: "telegram", label: "Telegram\u001B[31m\nBot\u0007" },
|
||||
{ id: "twitch", label: "Twitch" },
|
||||
]);
|
||||
select.mockResolvedValueOnce("telegram").mockResolvedValueOnce("done");
|
||||
select.mockResolvedValueOnce(channelChoice("telegram")).mockResolvedValueOnce(doneChoice);
|
||||
|
||||
await removeChannelConfigWizard(
|
||||
{
|
||||
@@ -153,7 +181,7 @@ describe("removeChannelConfigWizard", () => {
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({ value: "telegram", label: "Telegram\\nBot" }),
|
||||
expect.objectContaining({ value: channelChoice("telegram"), label: "Telegram\\nBot" }),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
@@ -170,7 +198,7 @@ describe("removeChannelConfigWizard", () => {
|
||||
|
||||
it("sanitizes unknown channel keys before rendering prompts", async () => {
|
||||
const unsafeChannel = "bad\u001B[31m\nkey\u0007";
|
||||
select.mockResolvedValueOnce(unsafeChannel).mockResolvedValueOnce("done");
|
||||
select.mockResolvedValueOnce(channelChoice(unsafeChannel)).mockResolvedValueOnce(doneChoice);
|
||||
|
||||
const next = await removeChannelConfigWizard(
|
||||
{
|
||||
@@ -185,7 +213,7 @@ describe("removeChannelConfigWizard", () => {
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({ value: unsafeChannel, label: "bad\\nkey" }),
|
||||
expect.objectContaining({ value: channelChoice(unsafeChannel), label: "bad\\nkey" }),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
@@ -203,7 +231,7 @@ describe("removeChannelConfigWizard", () => {
|
||||
|
||||
it("uses a placeholder when an unknown channel key sanitizes to empty", async () => {
|
||||
const unsafeChannel = "\u001B[31m\u0007";
|
||||
select.mockResolvedValueOnce(unsafeChannel).mockResolvedValueOnce("done");
|
||||
select.mockResolvedValueOnce(channelChoice(unsafeChannel)).mockResolvedValueOnce(doneChoice);
|
||||
|
||||
const next = await removeChannelConfigWizard(
|
||||
{
|
||||
@@ -218,7 +246,10 @@ describe("removeChannelConfigWizard", () => {
|
||||
expect(select).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({ value: unsafeChannel, label: "<invalid channel key>" }),
|
||||
expect.objectContaining({
|
||||
value: channelChoice(unsafeChannel),
|
||||
label: "<invalid channel key>",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -15,7 +15,10 @@ type ConfiguredChannelRemovalChoice = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
type ChannelRemovalSelectValue = { kind: "channel"; id: string } | { kind: "done" };
|
||||
|
||||
const RESERVED_CHANNEL_CONFIG_KEYS = new Set(["defaults", "modelByChannel"]);
|
||||
const DONE_VALUE: ChannelRemovalSelectValue = { kind: "done" };
|
||||
|
||||
function listConfiguredChannelRemovalChoices(
|
||||
cfg: OpenClawConfig,
|
||||
@@ -74,25 +77,26 @@ export async function removeChannelConfigWizard(
|
||||
return next;
|
||||
}
|
||||
|
||||
const channel = guardCancel(
|
||||
await select({
|
||||
const choice = guardCancel(
|
||||
await select<ChannelRemovalSelectValue>({
|
||||
message: "Remove which channel config?",
|
||||
options: [
|
||||
...configured.map((meta) => ({
|
||||
value: meta.id,
|
||||
value: { kind: "channel" as const, id: meta.id },
|
||||
label: meta.label,
|
||||
hint: "Deletes tokens + settings from config (credentials stay on disk)",
|
||||
})),
|
||||
{ value: "done", label: "Done" },
|
||||
{ value: DONE_VALUE, label: "Done" },
|
||||
],
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
|
||||
if (channel === "done") {
|
||||
if (choice.kind === "done") {
|
||||
return next;
|
||||
}
|
||||
|
||||
const channel = choice.id;
|
||||
const label = configured.find((entry) => entry.id === channel)?.label ?? channel;
|
||||
const confirmed = guardCancel(
|
||||
await confirm({
|
||||
|
||||
Reference in New Issue
Block a user