mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(coding-agent): validate client settings before opening runtime
This commit is contained in:
parent
16977d6ff2
commit
b21935b2eb
2 changed files with 88 additions and 48 deletions
|
|
@ -739,57 +739,62 @@ export async function runClientTui(command: ClientCommand, options: RunClientTui
|
|||
});
|
||||
await resourceLoader.reload();
|
||||
setRegisteredThemes(resourceLoader.getThemes().themes);
|
||||
const mouseWheel = settingsManager.getMouseWheelSettings();
|
||||
const runtime = await openClientRuntime(command, options);
|
||||
const tui = createInteractiveTui({
|
||||
tuiMode: "fullscreen",
|
||||
showHardwareCursor: settingsManager.getShowHardwareCursor(),
|
||||
logDirectory: agentDir,
|
||||
});
|
||||
tui.setClearOnShrink(settingsManager.getClearOnShrink());
|
||||
let component: ExperimentalClientTui | undefined;
|
||||
let tuiStarted = false;
|
||||
const themeController = new InteractiveThemeController(tui, {
|
||||
getSettingsManager: () => settingsManager,
|
||||
showError: (error) => component?.showError(error),
|
||||
onChanged: () => component?.refreshTheme(),
|
||||
});
|
||||
try {
|
||||
let finish!: () => void;
|
||||
const finished = new Promise<void>((resolve) => {
|
||||
finish = () => {
|
||||
themeController.disableAutoSync();
|
||||
if (tuiStarted) {
|
||||
tui.stop();
|
||||
tuiStarted = false;
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
const tui = createInteractiveTui({
|
||||
tuiMode: "fullscreen",
|
||||
mouseWheel,
|
||||
showHardwareCursor: settingsManager.getShowHardwareCursor(),
|
||||
logDirectory: agentDir,
|
||||
});
|
||||
component = await ExperimentalClientTui.create({
|
||||
command,
|
||||
ui: tui,
|
||||
servers: runtime.servers.map((server) => ({
|
||||
serverId: server.route.serverId,
|
||||
radius: server.route.transport === "radius",
|
||||
server: server.server,
|
||||
session: server.session,
|
||||
})),
|
||||
facetLoader: options.facetLoader,
|
||||
requestRender: () => tui.requestRender(),
|
||||
finish,
|
||||
tui.setClearOnShrink(settingsManager.getClearOnShrink());
|
||||
let component: ExperimentalClientTui | undefined;
|
||||
let tuiStarted = false;
|
||||
const themeController = new InteractiveThemeController(tui, {
|
||||
getSettingsManager: () => settingsManager,
|
||||
showError: (error) => component?.showError(error),
|
||||
onChanged: () => component?.refreshTheme(),
|
||||
});
|
||||
tui.addChild(component);
|
||||
tui.setLayoutRoot(component.layoutRoot);
|
||||
tui.setFocus(component);
|
||||
tuiStarted = true;
|
||||
tui.start();
|
||||
await themeController.applyFromSettings();
|
||||
await finished;
|
||||
try {
|
||||
let finish!: () => void;
|
||||
const finished = new Promise<void>((resolve) => {
|
||||
finish = () => {
|
||||
themeController.disableAutoSync();
|
||||
if (tuiStarted) {
|
||||
tui.stop();
|
||||
tuiStarted = false;
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
component = await ExperimentalClientTui.create({
|
||||
command,
|
||||
ui: tui,
|
||||
servers: runtime.servers.map((server) => ({
|
||||
serverId: server.route.serverId,
|
||||
radius: server.route.transport === "radius",
|
||||
server: server.server,
|
||||
session: server.session,
|
||||
})),
|
||||
facetLoader: options.facetLoader,
|
||||
requestRender: () => tui.requestRender(),
|
||||
finish,
|
||||
});
|
||||
tui.addChild(component);
|
||||
tui.setLayoutRoot(component.layoutRoot);
|
||||
tui.setFocus(component);
|
||||
tuiStarted = true;
|
||||
tui.start();
|
||||
await themeController.applyFromSettings();
|
||||
await finished;
|
||||
} finally {
|
||||
themeController.dispose();
|
||||
stopThemeWatcher();
|
||||
if (tuiStarted) tui.stop();
|
||||
await component?.close();
|
||||
}
|
||||
} finally {
|
||||
themeController.dispose();
|
||||
stopThemeWatcher();
|
||||
if (tuiStarted) tui.stop();
|
||||
await component?.close();
|
||||
await runtime.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,11 @@ import {
|
|||
reduceLaneSnapshot,
|
||||
} from "@earendil-works/pi-agent-core";
|
||||
import { ProcessTerminal, TuiMainScreen } from "@earendil-works/pi-tui";
|
||||
import { beforeAll, describe, expect, test, vi } from "vitest";
|
||||
import { type ClientTuiServer, ExperimentalClientTui } from "../src/experimental/client-tui.ts";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { DefaultResourceLoader } from "../src/core/resource-loader.ts";
|
||||
import { SettingsManager } from "../src/core/settings-manager.ts";
|
||||
import * as clientRuntime from "../src/experimental/client-runtime.ts";
|
||||
import { type ClientTuiServer, ExperimentalClientTui, runClientTui } from "../src/experimental/client-tui.ts";
|
||||
import { createPresentationFacetData } from "../src/experimental/plugins/bundled.ts";
|
||||
import { AgentController } from "../src/experimental/services/agent-controller.ts";
|
||||
import { createAgentController } from "../src/experimental/services/agent-controller-provider.ts";
|
||||
|
|
@ -42,6 +45,7 @@ import {
|
|||
} from "../src/experimental/services/sessions.ts";
|
||||
import { Transcript, type TranscriptState } from "../src/experimental/services/transcript.ts";
|
||||
import { initTheme } from "../src/modes/interactive/theme/theme.ts";
|
||||
import * as tuiRenderer from "../src/modes/interactive/tui-renderer.ts";
|
||||
|
||||
const serverId = "00000000-0000-4000-8000-000000000001";
|
||||
|
||||
|
|
@ -94,6 +98,37 @@ function laneSnapshot(): LaneSnapshot {
|
|||
};
|
||||
}
|
||||
|
||||
// #9758: validation and startup failures must not leak client connections.
|
||||
describe("runClientTui startup", () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(DefaultResourceLoader.prototype, "reload").mockResolvedValue(undefined);
|
||||
});
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
test("rejects invalid mouse wheel settings before acquiring a runtime", async () => {
|
||||
vi.spyOn(SettingsManager, "create").mockReturnValue(
|
||||
SettingsManager.inMemory({ mouseWheel: { normalLines: Number.MAX_VALUE } }),
|
||||
);
|
||||
const open = vi.spyOn(clientRuntime, "openClientRuntime").mockRejectedValue(new Error("unexpected acquisition"));
|
||||
|
||||
await expect(runClientTui({ command: "client" })).rejects.toThrow("Invalid mouseWheel.normalLines setting");
|
||||
expect(open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("disposes the runtime when TUI construction throws", async () => {
|
||||
vi.spyOn(SettingsManager, "create").mockReturnValue(SettingsManager.inMemory());
|
||||
const dispose = vi.fn(async () => {});
|
||||
vi.spyOn(clientRuntime, "openClientRuntime").mockResolvedValue({ servers: [], dispose });
|
||||
const error = new Error("TUI startup failed");
|
||||
vi.spyOn(tuiRenderer, "createInteractiveTui").mockImplementation(() => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await expect(runClientTui({ command: "client" })).rejects.toBe(error);
|
||||
expect(dispose).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe("experimental client TUI", () => {
|
||||
beforeAll(() => initTheme("dark"));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue