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
+48 -10
View File
@@ -24,6 +24,28 @@ function messageText(message: RuntimeView["messages"][number]) {
.join("");
}
function reconcilePendingSubmissions(
pendingSubmissions: RuntimeView["pendingSubmissions"],
messages: RuntimeView["messages"],
) {
const userMessages = messages
.filter((message) => message.role === "user")
.map(messageText);
const consumed = new Set<number>();
return pendingSubmissions.filter((pending) => {
for (
let index = pending.userMessageCount;
index < userMessages.length;
index++
) {
if (consumed.has(index) || userMessages[index] !== pending.text) continue;
consumed.add(index);
return false;
}
return true;
});
}
export function runtimeView(
summary: RuntimeSummary,
previous?: RuntimeView,
@@ -84,6 +106,7 @@ export const initialWorkspaceState: WorkspaceState = {
runtimeOrderByDirectory: {},
windowActive: true,
resourceWarning: false,
defaultNewSessionWorktree: undefined,
dismissedCollisionWarning: false,
interfaceScale: 1,
};
@@ -169,6 +192,8 @@ export function reconcileWorkspace(
selectedDirectoryPath,
dismissedCollisionWarning:
persisted?.dismissedCollisionWarning ?? state.dismissedCollisionWarning,
defaultNewSessionWorktree:
persisted?.defaultNewSessionWorktree ?? state.defaultNewSessionWorktree,
interfaceScale: persisted?.interfaceScale ?? state.interfaceScale,
};
}
@@ -319,9 +344,11 @@ export function applyWorkspaceEvent(
(String(payload.type ?? "").endsWith("_start") ? 1 : 0),
};
} else if (type === "queue") {
const queueCount = Array.isArray(payload.followUp)
? payload.followUp.length
: view.summary.queueCount;
const queueCount =
typeof payload.pendingMessageCount === "number"
? payload.pendingMessageCount
: (Array.isArray(payload.followUp) ? payload.followUp.length : 0) +
(Array.isArray(payload.steering) ? payload.steering.length : 0);
view.summary = { ...view.summary, queueCount };
view.progress = {
...view.progress,
@@ -372,13 +399,20 @@ export type WorkspaceAction =
| { type: "setWindowActive"; active: boolean }
| { type: "setScale"; scale: number }
| { type: "setResourceWarning"; value: boolean }
| { type: "setDefaultNewSessionWorktree"; worktreePath?: string }
| { type: "newSessionLaunch"; launch?: WorkspaceState["newSessionLaunch"] }
| { type: "dismissCollisionWarning" }
| {
type: "submissionAdded";
runtimeId: string;
submission: RuntimeView["pendingSubmissions"][number];
}
| { type: "submissionSent"; runtimeId: string; id: number }
| {
type: "submissionSent";
runtimeId: string;
id: number;
delivery: "prompt" | "follow_up";
}
| { type: "submissionRemoved"; runtimeId: string; id: number };
export function workspaceReducer(
@@ -438,6 +472,10 @@ export function workspaceReducer(
return { ...state, interfaceScale: action.scale };
if (action.type === "setResourceWarning")
return { ...state, resourceWarning: action.value };
if (action.type === "setDefaultNewSessionWorktree")
return { ...state, defaultNewSessionWorktree: action.worktreePath };
if (action.type === "newSessionLaunch")
return { ...state, newSessionLaunch: action.launch };
if (action.type === "dismissCollisionWarning")
return { ...state, dismissedCollisionWarning: true };
const runtimeId = "runtimeId" in action ? action.runtimeId : undefined;
@@ -463,11 +501,9 @@ export function workspaceReducer(
view.state = action.snapshot.state?.data ?? {};
view.stats = action.snapshot.stats?.data ?? {};
view.messages = action.snapshot.transcript?.data?.messages ?? [];
const receivedUserMessages = view.messages
.filter((message) => message.role === "user")
.map(messageText);
view.pendingSubmissions = view.pendingSubmissions.filter(
(pending) => !receivedUserMessages.includes(pending.text),
view.pendingSubmissions = reconcilePendingSubmissions(
view.pendingSubmissions,
view.messages,
);
view.commands = action.snapshot.commands?.data?.commands ?? [];
view.models = action.snapshot.models?.data?.models ?? [];
@@ -494,7 +530,9 @@ export function workspaceReducer(
view.pendingSubmissions = [...view.pendingSubmissions, action.submission];
else if (action.type === "submissionSent")
view.pendingSubmissions = view.pendingSubmissions.map((item) =>
item.id === action.id ? { ...item, phase: "sent" } : item,
item.id === action.id
? { ...item, phase: "sent", delivery: action.delivery }
: item,
);
else if (action.type === "submissionRemoved")
view.pendingSubmissions = view.pendingSubmissions.filter(