refactor(tui): remove dead exports (#107241)

This commit is contained in:
Peter Steinberger
2026-07-14 00:07:41 -07:00
committed by GitHub
parent 5a7184e3e3
commit e91d0fc8b7
17 changed files with 117 additions and 68 deletions
-12
View File
@@ -864,18 +864,6 @@ export const KNIP_UNUSED_EXPORT_BASELINE = [
"src/tasks/task-registry.ts: setTaskRegistryControlRuntimeForTests",
"src/tasks/task-registry.ts: setTaskRegistryDeliveryRuntimeForTests",
"src/tasks/task-retention.ts: resolveTaskRetentionMs",
"src/tui/components/filterable-select-list.ts: FilterableSelectListTheme",
"src/tui/gateway-chat.ts: resolveBoundGatewayConnection",
"src/tui/gateway-chat.ts: resolveGatewayConnection",
"src/tui/theme/theme.ts: lightMode",
"src/tui/theme/theme.ts: lightPalette",
"src/tui/theme/theme.ts: palette",
"src/tui/tui-busy-notice.ts: TUI_AGENT_BUSY_MESSAGE",
"src/tui/tui-last-session.ts: isHeartbeatLikeTuiSession",
"src/tui/tui-last-session.ts: resolveTuiLastSessionStatePath",
"src/tui/tui-plugin-approvals.ts: parseTuiPluginApproval",
"src/tui/tui-task-suggestions.ts: parseTuiTaskSuggestion",
"src/tui/tui-waiting.ts: pickWaitingPhrase",
"src/wizard/clack-navigation-prompts.ts: formatNavigationFooter",
"src/wizard/i18n/index.ts: listWizardI18nKeys",
"src/wizard/i18n/index.ts: resolveWizardLocale",
@@ -1,10 +1,8 @@
// Filterable select list tests cover keyboard filtering and cursor behavior.
import { describe, expect, it } from "vitest";
import {
FilterableSelectList,
type FilterableSelectItem,
type FilterableSelectListTheme,
} from "./filterable-select-list.js";
import { FilterableSelectList, type FilterableSelectItem } from "./filterable-select-list.js";
type FilterableSelectListTheme = ConstructorParameters<typeof FilterableSelectList>[2];
const mockTheme: FilterableSelectListTheme = {
selectedPrefix: (t) => `[${t}]`,
+1 -1
View File
@@ -18,7 +18,7 @@ export interface FilterableSelectItem extends SelectItem {
searchTextLower?: string;
}
export interface FilterableSelectListTheme extends SelectListTheme {
interface FilterableSelectListTheme extends SelectListTheme {
filterLabel: (text: string) => string;
}
+8 -2
View File
@@ -37,10 +37,16 @@ vi.mock("../infra/gateway-lock.js", () => ({
readActiveGatewayLockPort: readActiveGatewayLockPortMock,
}));
const { GatewayChatClient, resolveBoundGatewayConnection, resolveGatewayConnection } =
await import("./gateway-chat.js");
const { GatewayChatClient } = await import("./gateway-chat.js");
const { GatewayClientRequestError } = await import("../gateway/client.js");
const resolveBoundGatewayConnection = (
opts: Parameters<typeof GatewayChatClient.connectBound>[0],
) => GatewayChatClient.connectBound(opts).connection;
const resolveGatewayConnection = async (opts: Parameters<typeof GatewayChatClient.connect>[0]) =>
(await GatewayChatClient.connect(opts)).connection;
async function fileExists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
+2 -2
View File
@@ -394,7 +394,7 @@ export class GatewayChatClient implements TuiBackend {
* deliberately ignores global config and Gateway env overrides, including
* credentials, while still applying the normal remote URL safety policy.
*/
export function resolveBoundGatewayConnection(
function resolveBoundGatewayConnection(
opts: GatewayConnectionOptions & { config: OpenClawConfig; url: string },
): ResolvedGatewayConnection {
const url = buildGatewayConnectionDetails({
@@ -413,7 +413,7 @@ export function resolveBoundGatewayConnection(
};
}
export async function resolveGatewayConnection(
async function resolveGatewayConnection(
opts: GatewayConnectionOptions,
): Promise<ResolvedGatewayConnection> {
const config = getRuntimeConfig();
+59 -3
View File
@@ -1,8 +1,12 @@
// TUI theme tests cover theme defaults and environment-driven variants.
import { expectDefined } from "@openclaw/normalization-core";
import chalk from "chalk";
import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures";
import { afterEach, describe, expect, it } from "vitest";
import { afterAll, afterEach, describe, expect, it } from "vitest";
const originalChalkLevel = chalk.level;
chalk.level = 3;
const { markdownTheme, searchableSelectListTheme, selectListTheme, theme } =
await import("./theme.js");
@@ -13,6 +17,10 @@ const stripAnsi = (str: string) =>
let themeImportCase = 0;
const originalEnv = { ...process.env };
afterAll(() => {
chalk.level = originalChalkLevel;
});
afterEach(() => {
process.env = { ...originalEnv };
});
@@ -22,6 +30,48 @@ type ThemeEnvOverrides = {
COLORFGBG?: string | undefined;
};
type ThemeModule = typeof import("./theme.js");
const ansiRgbPattern = new RegExp(
`${String.fromCharCode(27)}\\[(38|48);2;(\\d+);(\\d+);(\\d+)m`,
"u",
);
function colorFromStyle(style: (text: string) => string, layer: 38 | 48): string {
const match = style("x").match(ansiRgbPattern);
if (!match || Number(match[1]) !== layer) {
throw new Error(`expected ${layer === 38 ? "foreground" : "background"} RGB style`);
}
return `#${match
.slice(2, 5)
.map((channel) => Number(channel).toString(16).padStart(2, "0"))
.join("")}`.toUpperCase();
}
function readActivePalette(mod: ThemeModule) {
return {
text: colorFromStyle(mod.theme.fg, 38),
dim: colorFromStyle(mod.theme.dim, 38),
accent: colorFromStyle(mod.theme.accent, 38),
accentSoft: colorFromStyle(mod.theme.accentSoft, 38),
border: colorFromStyle(mod.theme.border, 38),
userBg: colorFromStyle(mod.theme.userBg, 48),
userText: colorFromStyle(mod.theme.userText, 38),
systemText: colorFromStyle(mod.theme.system, 38),
toolPendingBg: colorFromStyle(mod.theme.toolPendingBg, 48),
toolSuccessBg: colorFromStyle(mod.theme.toolSuccessBg, 48),
toolErrorBg: colorFromStyle(mod.theme.toolErrorBg, 48),
toolTitle: colorFromStyle(mod.theme.toolTitle, 38),
toolOutput: colorFromStyle(mod.theme.toolOutput, 38),
quote: colorFromStyle(mod.markdownTheme.quote, 38),
quoteBorder: colorFromStyle(mod.markdownTheme.quoteBorder, 38),
code: colorFromStyle(mod.markdownTheme.code, 38),
codeBorder: colorFromStyle(mod.markdownTheme.codeBlockBorder, 38),
link: colorFromStyle(mod.markdownTheme.link, 38),
error: colorFromStyle(mod.theme.error, 38),
success: colorFromStyle(mod.theme.success, 38),
};
}
async function importThemeWithEnv(env: ThemeEnvOverrides) {
if (Object.hasOwn(env, "OPENCLAW_THEME")) {
if (env.OPENCLAW_THEME === undefined) {
@@ -37,10 +87,16 @@ async function importThemeWithEnv(env: ThemeEnvOverrides) {
process.env.COLORFGBG = env.COLORFGBG;
}
}
return importFreshModule<typeof import("./theme.js")>(
const mod = await importFreshModule<ThemeModule>(
import.meta.url,
`./theme.js?env=${++themeImportCase}`,
);
const lightPalette = readActivePalette(mod);
return {
...mod,
lightMode: lightPalette.text === "#1E1E1E",
lightPalette,
};
}
function relativeLuminance(hex: string): number {
@@ -235,7 +291,7 @@ describe("light palette accessibility", () => {
pending: mod.lightPalette.toolPendingBg,
success: mod.lightPalette.toolSuccessBg,
error: mod.lightPalette.toolErrorBg,
code: mod.lightPalette.codeBlock,
code: "#FFFFFF",
};
const textPairs = [
+3 -6
View File
@@ -85,8 +85,7 @@ function isLightBackground(): boolean {
return false;
}
/** Whether the terminal has a light background. Exported for testing only. */
export const lightMode = isLightBackground();
const lightMode = isLightBackground();
const darkPalette = {
text: "#E8E3D5",
@@ -105,14 +104,13 @@ const darkPalette = {
quote: "#8CC8FF",
quoteBorder: "#3B4D6B",
code: "#F0C987",
codeBlock: "#1E232A",
codeBorder: "#343A45",
link: "#7DD3A5",
error: "#F97066",
success: "#7DD3A5",
} as const;
export const lightPalette = {
const lightPalette = {
text: "#1E1E1E",
dim: "#5B6472",
accent: "#B45309",
@@ -129,14 +127,13 @@ export const lightPalette = {
quote: "#1D4ED8",
quoteBorder: "#2563EB",
code: "#92400E",
codeBlock: "#F9FAFB",
codeBorder: "#92400E",
link: "#047857",
error: "#DC2626",
success: "#047857",
} as const;
export const palette = lightMode ? lightPalette : darkPalette;
const palette = lightMode ? lightPalette : darkPalette;
const fg = (hex: string) => (text: string) => chalk.hex(hex)(text);
const bg = (hex: string) => (text: string) => chalk.bgHex(hex)(text);
+4 -2
View File
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { normalizeTestText } from "../../test/helpers/normalize-text.js";
import { ChatLog } from "./components/chat-log.js";
import { addBlockedChatSubmitNotice, TUI_AGENT_BUSY_MESSAGE } from "./tui-busy-notice.js";
import { addBlockedChatSubmitNotice } from "./tui-busy-notice.js";
describe("addBlockedChatSubmitNotice", () => {
it("coalesces repeated busy submit notices", () => {
@@ -13,6 +13,8 @@ describe("addBlockedChatSubmitNotice", () => {
const rendered = normalizeTestText(chatLog.render(120).join("\n"));
expect(chatLog.children.length).toBe(1);
expect(rendered).toContain(`${TUI_AGENT_BUSY_MESSAGE} x3`);
expect(rendered).toContain(
"agent is busy — press Esc to abort before sending a new message x3",
);
});
});
+1 -2
View File
@@ -1,7 +1,6 @@
import type { ChatLog } from "./components/chat-log.js";
export const TUI_AGENT_BUSY_MESSAGE =
"agent is busy — press Esc to abort before sending a new message";
const TUI_AGENT_BUSY_MESSAGE = "agent is busy — press Esc to abort before sending a new message";
export function addBlockedChatSubmitNotice(chatLog: Pick<ChatLog, "addSystem">) {
chatLog.addSystem(TUI_AGENT_BUSY_MESSAGE, { coalesceConsecutive: true });
+1 -7
View File
@@ -2,14 +2,11 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { expectDefined } from "@openclaw/normalization-core";
import { afterEach, describe, expect, it } from "vitest";
import {
buildTuiLastSessionScopeKey,
isHeartbeatLikeTuiSession,
readTuiLastSessionKey,
resolveRememberedTuiSessionKey,
resolveTuiLastSessionStatePath,
writeTuiLastSessionKey,
} from "./tui-last-session.js";
@@ -41,7 +38,7 @@ describe("tui last session state", () => {
});
await expect(readTuiLastSessionKey({ scopeKey, stateDir })).resolves.toBe("agent:main:tui-123");
const raw = await fs.readFile(resolveTuiLastSessionStatePath(stateDir), "utf8");
const raw = await fs.readFile(path.join(stateDir, "tui", "last-session.json"), "utf8");
expect(raw).not.toContain("127.0.0.1");
});
@@ -108,9 +105,6 @@ describe("tui last session state", () => {
{ key: "agent:main:tui-123" },
];
expect(
isHeartbeatLikeTuiSession(expectDefined(sessions[0], "sessions[0] test invariant")),
).toBe(true);
expect(
resolveRememberedTuiSessionKey({
rememberedKey: "agent:main:main",
+2 -2
View File
@@ -16,7 +16,7 @@ type LastSessionRecord = {
type LastSessionStore = Record<string, LastSessionRecord>;
/** Resolves the private state file for remembered TUI sessions. */
export function resolveTuiLastSessionStatePath(stateDir = resolveStateDir()): string {
function resolveTuiLastSessionStatePath(stateDir = resolveStateDir()): string {
return path.join(stateDir, "tui", "last-session.json");
}
@@ -56,7 +56,7 @@ function isHeartbeatSessionKey(sessionKey: string): boolean {
}
/** Detects heartbeat/system sessions that should not become the remembered human session. */
export function isHeartbeatLikeTuiSession(session: TuiSessionList["sessions"][number]): boolean {
function isHeartbeatLikeTuiSession(session: TuiSessionList["sessions"][number]): boolean {
if (isHeartbeatSessionKey(session.key)) {
return true;
}
+7 -7
View File
@@ -2,10 +2,7 @@ import type { Component, OverlayHandle, SelectItem } from "@earendil-works/pi-tu
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it, vi } from "vitest";
import { stripAnsi } from "../../packages/terminal-core/src/ansi.js";
import {
createTuiPluginApprovalController,
parseTuiPluginApproval,
} from "./tui-plugin-approvals.js";
import { createTuiPluginApprovalController } from "./tui-plugin-approvals.js";
type TestSelector = Component & {
items: SelectItem[];
@@ -121,9 +118,12 @@ function createHarness() {
}
describe("TUI plugin approvals", () => {
it("parses the pending plugin approval gateway shape", () => {
expect(parseTuiPluginApproval(approvalPayload())).toEqual(approvalPayload());
expect(parseTuiPluginApproval({ id: "plugin:missing-request" })).toBeNull();
it("ignores malformed plugin approval gateway payloads", () => {
const harness = createHarness();
harness.controller.handleEvent("plugin.approval.requested", {
id: "plugin:missing-request",
});
expect(harness.openOverlay).not.toHaveBeenCalled();
});
it("shows workspace skill approvals for the active session and resolves the selection", async () => {
+1 -1
View File
@@ -153,7 +153,7 @@ function parseSeverity(value: unknown): TuiPluginApproval["request"]["severity"]
}
/** Parses the gateway event/list shape used for pending plugin approvals. */
export function parseTuiPluginApproval(payload: unknown): TuiPluginApproval | null {
function parseTuiPluginApproval(payload: unknown): TuiPluginApproval | null {
if (!isRecord(payload) || !isRecord(payload.request)) {
return null;
}
+8 -7
View File
@@ -2,10 +2,7 @@ import type { Component, OverlayHandle, SelectItem } from "@earendil-works/pi-tu
import { expectDefined } from "@openclaw/normalization-core";
import { describe, expect, it, vi } from "vitest";
import { stripAnsi } from "../../packages/terminal-core/src/ansi.js";
import {
createTuiTaskSuggestionController,
parseTuiTaskSuggestion,
} from "./tui-task-suggestions.js";
import { createTuiTaskSuggestionController } from "./tui-task-suggestions.js";
type TestSelector = Component & {
items: SelectItem[];
@@ -115,9 +112,13 @@ function createHarness() {
}
describe("TUI task suggestions", () => {
it("parses the Gateway suggestion shape", () => {
expect(parseTuiTaskSuggestion(suggestionPayload())).toEqual(suggestionPayload());
expect(parseTuiTaskSuggestion({ id: "task_missing_fields" })).toBeNull();
it("ignores malformed Gateway suggestion payloads", () => {
const harness = createHarness();
harness.controller.handleEvent("task.suggestion", {
action: "created",
suggestion: { id: "task_missing_fields" },
});
expect(harness.openOverlay).not.toHaveBeenCalled();
});
it("shows an active-session suggestion and starts it after confirmation", async () => {
+1 -1
View File
@@ -69,7 +69,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
}
/** Parses the task suggestion shape carried by Gateway list and event payloads. */
export function parseTuiTaskSuggestion(value: unknown): TaskSuggestion | null {
function parseTuiTaskSuggestion(value: unknown): TaskSuggestion | null {
if (!isRecord(value)) {
return null;
}
+15 -7
View File
@@ -1,6 +1,6 @@
// Verifies TUI waiting indicators and elapsed-time rendering.
import { describe, expect, it } from "vitest";
import { buildWaitingStatusMessage, pickWaitingPhrase } from "./tui-waiting.js";
import { buildWaitingStatusMessage } from "./tui-waiting.js";
const theme = {
dim: (s: string) => `<d>${s}</d>`,
@@ -9,13 +9,21 @@ const theme = {
} satisfies Parameters<typeof buildWaitingStatusMessage>[0]["theme"];
describe("tui-waiting", () => {
it("pickWaitingPhrase rotates every 10 ticks", () => {
it("rotates the rendered waiting phrase every 10 ticks", () => {
const phrases = ["a", "b", "c"];
expect(pickWaitingPhrase(0, phrases)).toBe("a");
expect(pickWaitingPhrase(9, phrases)).toBe("a");
expect(pickWaitingPhrase(10, phrases)).toBe("b");
expect(pickWaitingPhrase(20, phrases)).toBe("c");
expect(pickWaitingPhrase(30, phrases)).toBe("a");
const renderPhrase = (tick: number) =>
buildWaitingStatusMessage({
theme,
tick,
elapsed: "3s",
connectionStatus: "connected",
phrases,
}).replace(/<[^>]+>/g, "");
expect(renderPhrase(0)).toMatch(/^a…/);
expect(renderPhrase(9)).toMatch(/^a…/);
expect(renderPhrase(10)).toMatch(/^b…/);
expect(renderPhrase(20)).toMatch(/^c…/);
expect(renderPhrase(30)).toMatch(/^a…/);
});
it("buildWaitingStatusMessage includes shimmer markup and metadata", () => {
+1 -1
View File
@@ -20,7 +20,7 @@ export const defaultWaitingPhrases = [
];
/** Picks a stable phrase for a timer tick. */
export function pickWaitingPhrase(tick: number, phrases = defaultWaitingPhrases) {
function pickWaitingPhrase(tick: number, phrases = defaultWaitingPhrases) {
const idx = Math.floor(tick / 10) % phrases.length;
return phrases[idx] ?? phrases[0] ?? "waiting";
}