feat(rpc): add validated desktop Pi RPC command palette

This commit is contained in:
2026-08-24 20:16:51 +02:00
parent 475f95ba70
commit 99084cc5a5
25 changed files with 2170 additions and 344 deletions
+69 -12
View File
@@ -1,16 +1,73 @@
import { describe, expect, it, vi } from "vitest";
import { legacyPreferences, removeLegacyPreferences, presentationState } from "./persistence";
import { initialWorkspaceState, reconcileWorkspace, workspaceReducer } from "./reducer";
import {
legacyPreferences,
removeLegacyPreferences,
presentationState,
} from "./persistence";
import {
initialWorkspaceState,
reconcileWorkspace,
workspaceReducer,
} from "./reducer";
import type { Workspace } from "./types";
const workspace: Workspace = { bridgeInstanceId: "e", latestSeq: 7, directories: [{ worktreePath: "/repo", isHome: true, openCount: 1, workingCount: 0, attentionCount: 0, recoveringCount: 0, errorCount: 0, runtimes: [{ runtimeId: "r", worktreePath: "/repo", state: "idle", label: "r", attention: false, queueCount: 0, lastActivity: "now", openedAt: "now", agentId: "ephemeral" }] }] };
const workspace: Workspace = {
bridgeInstanceId: "e",
latestSeq: 7,
directories: [
{
worktreePath: "/repo",
isHome: true,
openCount: 1,
workingCount: 0,
attentionCount: 0,
recoveringCount: 0,
errorCount: 0,
runtimes: [
{
runtimeId: "r",
worktreePath: "/repo",
state: "idle",
label: "r",
attention: false,
queueCount: 0,
lastActivity: "now",
openedAt: "now",
agentId: "ephemeral",
},
],
},
],
};
describe("presentation persistence", () => {
it("persists only allowlisted presentation state, never agent or session payloads", () => {
let state = reconcileWorkspace(initialWorkspaceState, workspace); state = workspaceReducer(state, { type: "draftChanged", runtimeId: "r", draft: "safe draft" });
const persisted = presentationState(state); const encoded = JSON.stringify(persisted);
expect(persisted.drafts.r).toBe("safe draft"); expect(encoded).not.toContain("ephemeral"); expect(encoded).not.toContain("messages"); expect(encoded).not.toContain("extensions");
});
it("reads and removes legacy worktree/scale only after caller saves", () => {
const getItem = vi.fn((key: string) => key.includes("selected") ? "/repo" : "1.2"); expect(legacyPreferences({ getItem })).toEqual({ path: "/repo", scale: 1.2 });
const removeItem = vi.fn(); removeLegacyPreferences({ removeItem }); expect(removeItem).toHaveBeenCalledTimes(2);
});
it("persists only allowlisted presentation state, never agent or session payloads", () => {
let state = reconcileWorkspace(initialWorkspaceState, workspace);
state = workspaceReducer(state, {
type: "draftChanged",
runtimeId: "r",
draft: "safe draft",
});
state = workspaceReducer(state, {
type: "setDefaultNewSessionWorktree",
worktreePath: "/repo",
});
const persisted = presentationState(state);
const encoded = JSON.stringify(persisted);
expect(persisted.drafts.r).toBe("safe draft");
expect(persisted.defaultNewSessionWorktree).toBe("/repo");
expect(encoded).not.toContain("ephemeral");
expect(encoded).not.toContain("messages");
expect(encoded).not.toContain("extensions");
});
it("reads and removes legacy worktree/scale only after caller saves", () => {
const getItem = vi.fn((key: string) =>
key.includes("selected") ? "/repo" : "1.2",
);
expect(legacyPreferences({ getItem })).toEqual({
path: "/repo",
scale: 1.2,
});
const removeItem = vi.fn();
removeLegacyPreferences({ removeItem });
expect(removeItem).toHaveBeenCalledTimes(2);
});
});