fix(cli): keep channel status checks off plugin runtimes (#69479)

Merged via squash.

Prepared head SHA: 63f6e416a9
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Gustavo Madeira Santana
2026-04-21 13:53:08 -04:00
committed by GitHub
parent 09c5669299
commit 24db09a19b
85 changed files with 3176 additions and 366 deletions

View File

@@ -209,7 +209,7 @@ describe("registerPreActionHooks", () => {
runtime: runtimeMock,
commandPath: ["status"],
});
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels" });
expect(ensurePluginRegistryLoadedMock).not.toHaveBeenCalled();
expect(processTitleSetSpy).toHaveBeenCalledWith("openclaw-status");
vi.clearAllMocks();

View File

@@ -242,3 +242,22 @@ export function parseModelsStatusRouteArgs(argv: string[]) {
probe: hasFlag(argv, "--probe"),
};
}
export function parseChannelsListRouteArgs(argv: string[]) {
return {
json: hasFlag(argv, "--json"),
usage: !hasFlag(argv, "--no-usage"),
};
}
export function parseChannelsStatusRouteArgs(argv: string[]) {
const timeout = parseOptionalFlagValue(argv, "--timeout");
if (!timeout.ok) {
return null;
}
return {
json: hasFlag(argv, "--json"),
probe: hasFlag(argv, "--probe"),
timeout: timeout.value,
};
}

View File

@@ -1,6 +1,8 @@
import { defaultRuntime } from "../../runtime.js";
import {
parseAgentsListRouteArgs,
parseChannelsListRouteArgs,
parseChannelsStatusRouteArgs,
parseConfigGetRouteArgs,
parseConfigUnsetRouteArgs,
parseGatewayStatusRouteArgs,
@@ -123,4 +125,18 @@ export const routedCommandDefinitions = {
await modelsStatusCommand(args, defaultRuntime);
},
}),
"channels-list": defineRoutedCommand({
parseArgs: parseChannelsListRouteArgs,
runParsedArgs: async (args) => {
const { channelsListCommand } = await import("../../commands/channels/list.js");
await channelsListCommand(args, defaultRuntime);
},
}),
"channels-status": defineRoutedCommand({
parseArgs: parseChannelsStatusRouteArgs,
runParsedArgs: async (args) => {
const { channelsStatusCommand } = await import("../../commands/channels/status.js");
await channelsStatusCommand(args, defaultRuntime);
},
}),
};

View File

@@ -7,6 +7,8 @@ const modelsListCommandMock = vi.hoisted(() => vi.fn(async () => {}));
const modelsStatusCommandMock = vi.hoisted(() => vi.fn(async () => {}));
const runDaemonStatusMock = vi.hoisted(() => vi.fn(async () => {}));
const statusJsonCommandMock = vi.hoisted(() => vi.fn(async () => {}));
const channelsListCommandMock = vi.hoisted(() => vi.fn(async () => {}));
const channelsStatusCommandMock = vi.hoisted(() => vi.fn(async () => {}));
vi.mock("../config-cli.js", () => ({
runConfigGet: runConfigGetMock,
@@ -26,6 +28,14 @@ vi.mock("../../commands/status-json.js", () => ({
statusJsonCommand: statusJsonCommandMock,
}));
vi.mock("../../commands/channels/list.js", () => ({
channelsListCommand: channelsListCommandMock,
}));
vi.mock("../../commands/channels/status.js", () => ({
channelsStatusCommand: channelsStatusCommandMock,
}));
describe("program routes", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -42,20 +52,48 @@ describe("program routes", () => {
await expect(route?.run(argv)).resolves.toBe(false);
}
it("matches status route and preloads plugins only for text output", () => {
it("matches status route without plugin preload", () => {
const route = expectRoute(["status"]);
expect(typeof route?.loadPlugins).toBe("function");
const shouldLoad = route?.loadPlugins as (argv: string[]) => boolean;
expect(shouldLoad(["node", "openclaw", "status"])).toBe(true);
expect(shouldLoad(["node", "openclaw", "status", "--json"])).toBe(false);
expect(route?.loadPlugins).toBeUndefined();
});
it("matches health route and preloads plugins only for text output", () => {
it("matches health route without plugin preload", () => {
const route = expectRoute(["health"]);
expect(typeof route?.loadPlugins).toBe("function");
const shouldLoad = route?.loadPlugins as (argv: string[]) => boolean;
expect(shouldLoad(["node", "openclaw", "health"])).toBe(true);
expect(shouldLoad(["node", "openclaw", "health", "--json"])).toBe(false);
expect(route?.loadPlugins).toBeUndefined();
});
it("matches channel read-only routes without plugin preload", () => {
expect(expectRoute(["channels", "list"])?.loadPlugins).toBeUndefined();
expect(expectRoute(["channels", "status"])?.loadPlugins).toBeUndefined();
});
it("passes parsed channel read-only route flags through", async () => {
const listRoute = expectRoute(["channels", "list"]);
await expect(
listRoute?.run(["node", "openclaw", "channels", "list", "--json", "--no-usage"]),
).resolves.toBe(true);
expect(channelsListCommandMock).toHaveBeenCalledWith(
{ json: true, usage: false },
expect.any(Object),
);
const statusRoute = expectRoute(["channels", "status"]);
await expect(
statusRoute?.run([
"node",
"openclaw",
"channels",
"status",
"--json",
"--probe",
"--timeout",
"5000",
]),
).resolves.toBe(true);
expect(channelsStatusCommandMock).toHaveBeenCalledWith(
{ json: true, probe: true, timeout: "5000" },
expect.any(Object),
);
});
it("matches gateway status route without plugin preload", () => {