fix(cli): protect protocol stdout during startup (#89997)

* fix(cli): suppress mcp serve startup stdout

* fix(cli): suppress mcp serve startup stdout

* fix(cli): protect protocol stdout during startup

* fix(cli): match ACP protocol options at startup

* fix(cli): derive protocol ownership from command action

---------

Co-authored-by: JARVIS <kenners22@users.noreply.github.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Vincent Koc <25068+vincentkoc@users.noreply.github.com>
This commit is contained in:
kenners22
2026-07-07 01:55:50 +01:00
committed by GitHub
parent c1b9e36221
commit ffa6ebda4c
10 changed files with 120 additions and 3 deletions

View File

@@ -146,6 +146,19 @@ describe("registerPreActionHooks", () => {
.command("status")
.option("--json")
.action(() => {});
const acp = programLocal
.command("acp")
.option("--token <token>")
.option("--verbose")
.action(() => {});
acp
.command("client")
.option("--cwd <dir>")
.action(() => {});
programLocal
.command("mcp")
.command("serve")
.action(() => {});
const gateway = programLocal
.command("gateway")
.option("--force")
@@ -607,6 +620,45 @@ describe("registerPreActionHooks", () => {
expect(routeLogsToStderrMock).not.toHaveBeenCalled();
});
it("uses the Commander action path for protocol stdout ownership", async () => {
await runPreAction({
parseArgv: ["acp"],
processArgv: ["node", "openclaw", "acp", "--token", "-secret"],
});
expect(routeLogsToStderrMock).toHaveBeenCalledOnce();
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
runtime: runtimeMock,
commandPath: ["acp"],
suppressDoctorStdout: true,
});
vi.clearAllMocks();
await runPreAction({
parseArgv: ["acp", "client"],
processArgv: ["node", "openclaw", "acp", "--verbose", "client"],
});
expect(routeLogsToStderrMock).not.toHaveBeenCalled();
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
runtime: runtimeMock,
commandPath: ["acp", "client"],
});
vi.clearAllMocks();
await runPreAction({
parseArgv: ["mcp", "serve"],
processArgv: ["node", "openclaw", "mcp", "serve"],
});
expect(routeLogsToStderrMock).toHaveBeenCalledOnce();
expect(ensureConfigReadyMock).toHaveBeenCalledWith({
runtime: runtimeMock,
commandPath: ["mcp", "serve"],
suppressDoctorStdout: true,
});
});
it("does not preload plugins for agents list JSON output", async () => {
await runPreAction({
parseArgv: ["agents", "list"],

View File

@@ -53,6 +53,16 @@ function getRootCommand(command: Command): Command {
return current;
}
function getActionCommandPath(actionCommand: Command): string[] {
const commandPath: string[] = [];
let current: Command | null = actionCommand;
while (current.parent) {
commandPath.unshift(current.name());
current = current.parent;
}
return commandPath;
}
function getCliLogLevel(actionCommand: Command): LogLevel | undefined {
const root = getRootCommand(actionCommand);
if (typeof root.getOptionValueSource !== "function") {
@@ -119,6 +129,7 @@ export function registerPreActionHooks(program: Command, programVersion: string)
const jsonOutputMode = isCommandJsonOutputMode(actionCommand, argv);
const { commandPath, startupPolicy } = resolveCliExecutionStartupContext({
argv,
protocolCommandPath: getActionCommandPath(actionCommand),
jsonOutputMode,
env: process.env,
});