fix(qr): replace qrcode-terminal with qrcode-tui

Replace legacy qrcode-terminal usage with shared qrcode-tui media helpers, bound QR PNG rendering options, and raise bundled plugin host floors for the new SDK runtime surface.
This commit is contained in:
Vincent Koc
2026-04-23 13:06:14 -07:00
committed by GitHub
parent 6f74763f1d
commit ea25d7ed5b
29 changed files with 336 additions and 193 deletions

View File

@@ -10,9 +10,7 @@ const mocks = vi.hoisted(() => ({
resolvedConfig: config,
diagnostics: [] as string[],
})),
qrGenerate: vi.fn((_input: unknown, _opts: unknown, cb: (output: string) => void) => {
cb("ASCII-QR");
}),
renderTerminal: vi.fn(async () => "ASCII-QR"),
}));
const { defaultRuntime: runtime, resetRuntimeCapture } = createCliRuntimeCapture();
const runtimeLog = runtime.log;
@@ -27,6 +25,9 @@ vi.mock("../runtime.js", async () => {
});
vi.mock("../config/config.js", () => ({ loadConfig: mocks.loadConfig }));
vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: mocks.runCommandWithTimeout }));
vi.mock("../media/qr-terminal.ts", () => ({
renderQrTerminal: mocks.renderTerminal,
}));
vi.mock("./command-secret-gateway.js", () => ({
resolveCommandSecretRefsViaGateway: mocks.resolveCommandSecretRefsViaGateway,
}));
@@ -36,16 +37,10 @@ vi.mock("../infra/device-bootstrap.js", () => ({
expiresAtMs: 123,
})),
}));
vi.mock("qrcode-terminal", () => ({
default: {
generate: mocks.qrGenerate,
},
}));
const loadConfig = mocks.loadConfig;
const runCommandWithTimeout = mocks.runCommandWithTimeout;
const resolveCommandSecretRefsViaGateway = mocks.resolveCommandSecretRefsViaGateway;
const qrGenerate = mocks.qrGenerate;
const renderTerminal = mocks.renderTerminal;
const { registerQrCli } = await import("./qr-cli.js");
@@ -196,7 +191,7 @@ describe("registerQrCli", () => {
bootstrapToken: "bootstrap-123",
});
expect(runtime.log).toHaveBeenCalledWith(expected);
expect(qrGenerate).not.toHaveBeenCalled();
expect(renderTerminal).not.toHaveBeenCalled();
expect(resolveCommandSecretRefsViaGateway).not.toHaveBeenCalled();
});
@@ -211,7 +206,7 @@ describe("registerQrCli", () => {
await runQr([]);
expect(qrGenerate).toHaveBeenCalledTimes(1);
expect(renderTerminal).toHaveBeenCalledTimes(1);
const output = runtimeLog.mock.calls.map((call) => readRuntimeCallText(call)).join("\n");
expect(output).toContain("Pairing QR");
expect(output).toContain("ASCII-QR");

View File

@@ -4,6 +4,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js";
import { hasConfiguredSecretInput } from "../config/types.secrets.js";
import { trimToUndefined } from "../gateway/credentials.js";
import { resolveRequiredConfiguredSecretRefInputString } from "../gateway/resolve-configured-secret-input-string.js";
import { renderQrTerminal } from "../media/qr-terminal.ts";
import { resolvePairingSetupFromConfig, encodePairingSetupCode } from "../pairing/setup-code.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { defaultRuntime } from "../runtime.js";
@@ -23,20 +24,9 @@ type QrCliOptions = {
password?: string;
};
async function loadQrTerminal() {
const mod = await import("qrcode-terminal");
return mod.default ?? mod;
function renderQrAscii(data: string): Promise<string> {
return renderQrTerminal(data, { small: true });
}
async function renderQrAscii(data: string): Promise<string> {
const qrcode = await loadQrTerminal();
return new Promise((resolve) => {
qrcode.generate(data, { small: true }, (output: string) => {
resolve(output);
});
});
}
function readDevicePairPublicUrlFromConfig(cfg: OpenClawConfig): string | undefined {
const value = cfg.plugins?.entries?.["device-pair"]?.config?.["publicUrl"];
if (typeof value !== "string") {