WIP: workspace execution refactor + notification routing + setup store updates

This commit is contained in:
Mohamed Boudra
2026-04-01 21:28:07 +07:00
parent d8243bfb72
commit a7d5ab2497
29 changed files with 1064 additions and 197 deletions
+23 -1
View File
@@ -1,8 +1,11 @@
import { Text, View } from "react-native";
import { FileText } from "lucide-react-native";
import invariant from "tiny-invariant";
import { FilePane } from "@/components/file-pane";
import { usePaneContext } from "@/panels/pane-context";
import type { PanelRegistration } from "@/panels/panel-registry";
import { useSessionStore } from "@/stores/session-store";
import { resolveWorkspaceExecutionAuthority } from "@/utils/workspace-execution";
function useFilePanelDescriptor(target: { kind: "file"; path: string }) {
const fileName = target.path.split("/").filter(Boolean).pop() ?? target.path;
@@ -17,8 +20,27 @@ function useFilePanelDescriptor(target: { kind: "file"; path: string }) {
function FilePanel() {
const { serverId, workspaceId, target } = usePaneContext();
const authority = useSessionStore((state) =>
resolveWorkspaceExecutionAuthority({
workspaces: state.sessions[serverId]?.workspaces,
workspaceId,
}),
);
invariant(target.kind === "file", "FilePanel requires file target");
return <FilePane serverId={serverId} workspaceRoot={workspaceId} filePath={target.path} />;
if (!authority) {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center", padding: 16 }}>
<Text>Workspace execution directory not found.</Text>
</View>
);
}
return (
<FilePane
serverId={serverId}
workspaceRoot={authority.workspaceDirectory}
filePath={target.path}
/>
);
}
export const filePanelRegistration: PanelRegistration<"file"> = {
+28 -7
View File
@@ -19,6 +19,10 @@ import { useProviderRecency } from "@/stores/provider-recency-store";
import { useSessionStore } from "@/stores/session-store";
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
import { toErrorMessage } from "@/utils/error-messages";
import {
getWorkspaceExecutionAuthority,
requireWorkspaceRecordId,
} from "@/utils/workspace-execution";
const MAX_VISIBLE_PROVIDER_TILES = 4;
@@ -36,11 +40,11 @@ function LauncherPanel() {
const { serverId, workspaceId, target, retargetCurrentTab, isPaneFocused } = usePaneContext();
const client = useHostRuntimeClient(serverId);
const isConnected = useHostRuntimeIsConnected(serverId);
const workspaceDirectory = useSessionStore(
(state) =>
state.sessions[serverId]?.workspaces.get(workspaceId)?.workspaceDirectory ??
state.sessions[serverId]?.workspaces.get(workspaceId)?.projectRootPath,
);
const workspaces = useSessionStore((state) => state.sessions[serverId]?.workspaces);
const workspaceAuthority = getWorkspaceExecutionAuthority({ workspaces, workspaceId });
const workspaceDirectory = workspaceAuthority.ok
? workspaceAuthority.authority.workspaceDirectory
: null;
const { providers, recordUsage } = useProviderRecency();
const setAgents = useSessionStore((state) => state.setAgents);
const [pendingAction, setPendingAction] = useState<string | null>(null);
@@ -62,6 +66,11 @@ function LauncherPanel() {
setErrorMessage(!workspaceDirectory ? "Workspace directory not found" : "Host is not connected");
return;
}
if (!workspaceAuthority.ok) {
setErrorMessage(workspaceAuthority.message);
return;
}
const persistedWorkspaceId = requireWorkspaceRecordId(workspaceAuthority.authority.workspaceId);
setPendingAction(providerId);
setErrorMessage(null);
@@ -70,6 +79,7 @@ function LauncherPanel() {
const agent = await client.createAgent({
provider: providerId,
cwd: workspaceDirectory,
workspaceId: persistedWorkspaceId,
terminal: true,
});
recordUsage(providerId);
@@ -87,7 +97,16 @@ function LauncherPanel() {
setPendingAction((current) => (current === providerId ? null : current));
}
},
[client, isConnected, recordUsage, retargetCurrentTab, serverId, setAgents, workspaceDirectory],
[
client,
isConnected,
recordUsage,
retargetCurrentTab,
serverId,
setAgents,
workspaceAuthority,
workspaceDirectory,
],
);
const openDraftTab = useCallback(() => {
@@ -131,7 +150,9 @@ function LauncherPanel() {
return (
<View style={styles.container}>
<View style={[styles.content, styles.loadingContent]}>
<ActivityIndicator />
<Text style={styles.errorText}>
{workspaceAuthority.ok ? "Workspace execution directory not found." : workspaceAuthority.message}
</Text>
</View>
</View>
);
+29 -15
View File
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { Terminal } from "lucide-react-native";
import { View } from "react-native";
import { Text, View } from "react-native";
import { useIsFocused } from "@react-navigation/native";
import invariant from "tiny-invariant";
import type { ListTerminalsResponse } from "@server/shared/messages";
@@ -8,6 +8,7 @@ import { TerminalPane } from "@/components/terminal-pane";
import { usePaneContext } from "@/panels/pane-context";
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
import { useSessionStore } from "@/stores/session-store";
import { getWorkspaceExecutionAuthority } from "@/utils/workspace-execution";
type ListTerminalsPayload = ListTerminalsResponse["payload"];
@@ -24,18 +25,22 @@ function useTerminalPanelDescriptor(
context: { serverId: string; workspaceId: string },
): PanelDescriptor {
const client = useSessionStore((state) => state.sessions[context.serverId]?.client ?? null);
const workspaceDirectory = useSessionStore(
(state) =>
state.sessions[context.serverId]?.workspaces.get(context.workspaceId)?.workspaceDirectory ??
state.sessions[context.serverId]?.workspaces.get(context.workspaceId)?.projectRootPath ??
null,
);
const workspaces = useSessionStore((state) => state.sessions[context.serverId]?.workspaces);
const workspaceAuthority = getWorkspaceExecutionAuthority({
workspaces,
workspaceId: context.workspaceId,
});
const workspaceDirectory = workspaceAuthority.ok
? workspaceAuthority.authority.workspaceDirectory
: null;
const terminalsQuery = useQuery({
queryKey: ["terminals", context.serverId, workspaceDirectory] as const,
enabled: Boolean(client && workspaceDirectory),
queryFn: async (): Promise<ListTerminalsPayload> => {
if (!client || !workspaceDirectory) {
return { cwd: workspaceDirectory ?? "", terminals: [], requestId: "missing-client" };
throw new Error(
workspaceAuthority.ok ? "Workspace execution directory not found" : workspaceAuthority.message,
);
}
return client.listTerminals(workspaceDirectory);
},
@@ -56,18 +61,27 @@ function useTerminalPanelDescriptor(
function TerminalPanel() {
const isFocused = useIsFocused();
const { serverId, workspaceId, target, isPaneFocused } = usePaneContext();
const workspaceDirectory = useSessionStore(
(state) =>
state.sessions[serverId]?.workspaces.get(workspaceId)?.workspaceDirectory ??
state.sessions[serverId]?.workspaces.get(workspaceId)?.projectRootPath ??
null,
);
const workspaces = useSessionStore((state) => state.sessions[serverId]?.workspaces);
const workspaceAuthority = getWorkspaceExecutionAuthority({ workspaces, workspaceId });
const workspaceDirectory = workspaceAuthority.ok
? workspaceAuthority.authority.workspaceDirectory
: null;
invariant(target.kind === "terminal", "TerminalPanel requires terminal target");
if (!isFocused || !workspaceDirectory) {
if (!isFocused) {
return <View style={{ flex: 1 }} />;
}
if (!workspaceDirectory) {
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center", padding: 16 }}>
<Text>
{workspaceAuthority.ok ? "Workspace execution directory not found." : workspaceAuthority.message}
</Text>
</View>
);
}
return (
<TerminalPane
serverId={serverId}