WIP: snapshot workspace execution refactor baseline

This commit is contained in:
Mohamed Boudra
2026-04-01 10:35:06 +07:00
parent e026223c80
commit d8243bfb72
47 changed files with 2219 additions and 169 deletions
@@ -81,7 +81,7 @@ describe("workspace agent visibility", () => {
const result = deriveWorkspaceAgentVisibility({
sessionAgents,
workspaceId,
workspaceDirectory: workspaceId,
});
expect(result.activeAgentIds).toEqual(new Set(["visible-agent"]));
@@ -142,13 +142,33 @@ describe("workspace agent visibility", () => {
const result = deriveWorkspaceAgentVisibility({
sessionAgents,
workspaceId: "/Users/moboudra/.paseo/worktrees/1luy0po7/normal-squid",
workspaceDirectory: "/Users/moboudra/.paseo/worktrees/1luy0po7/normal-squid",
});
expect(result.activeAgentIds).toEqual(new Set(["slash-agent"]));
expect(result.knownAgentIds.has("slash-agent")).toBe(true);
});
it("matches workspace agents using the workspace directory even when the route uses a numeric workspace id", () => {
const sessionAgents = new Map<string, Agent>([
[
"terminal-agent",
makeAgent({
id: "terminal-agent",
cwd: "/tmp/workspace-lifecycle-main",
}),
],
]);
const result = deriveWorkspaceAgentVisibility({
sessionAgents,
workspaceDirectory: "/tmp/workspace-lifecycle-main",
});
expect(result.activeAgentIds).toEqual(new Set(["terminal-agent"]));
expect(result.knownAgentIds).toEqual(new Set(["terminal-agent"]));
});
describe("workspaceAgentVisibilityEqual", () => {
it("returns true for identical sets", () => {
const a = { activeAgentIds: new Set(["a", "b"]), knownAgentIds: new Set(["a", "b", "c"]) };
@@ -12,11 +12,11 @@ export interface WorkspaceAgentVisibility {
export function deriveWorkspaceAgentVisibility(input: {
sessionAgents: Map<string, Agent> | undefined;
workspaceId: string;
workspaceDirectory: string | null | undefined;
}): WorkspaceAgentVisibility {
const { sessionAgents, workspaceId } = input;
const normalizedWorkspaceId = normalizeWorkspaceId(workspaceId);
if (!sessionAgents || !workspaceId) {
const { sessionAgents, workspaceDirectory } = input;
const normalizedWorkspaceDirectory = normalizeWorkspaceId(workspaceDirectory);
if (!sessionAgents || !normalizedWorkspaceDirectory) {
return {
activeAgentIds: new Set<string>(),
knownAgentIds: new Set<string>(),
@@ -26,7 +26,7 @@ export function deriveWorkspaceAgentVisibility(input: {
const activeAgentIds = new Set<string>();
const knownAgentIds = new Set<string>();
for (const agent of sessionAgents.values()) {
if (normalizeWorkspaceId(agent.cwd) !== normalizedWorkspaceId) {
if (normalizeWorkspaceId(agent.cwd) !== normalizedWorkspaceDirectory) {
continue;
}
knownAgentIds.add(agent.id);
@@ -1,6 +1,7 @@
import { useCallback, useMemo, useRef } from "react";
import { Keyboard, Platform, ScrollView, Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import invariant from "tiny-invariant";
import { Composer } from "@/components/composer";
import { FileDropZone } from "@/components/file-drop-zone";
import { AgentStreamView } from "@/components/agent-stream-view";
@@ -10,7 +11,7 @@ import { useDraftAgentCreateFlow } from "@/hooks/use-draft-agent-create-flow";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { buildWorkspaceDraftAgentConfig } from "@/screens/workspace/workspace-draft-agent-config";
import { buildDraftStoreKey } from "@/stores/draft-keys";
import type { Agent } from "@/stores/session-store";
import { type Agent, useSessionStore } from "@/stores/session-store";
import { encodeImages } from "@/utils/encode-images";
import { shouldAutoFocusWorkspaceDraftComposer } from "@/screens/workspace/workspace-draft-pane-focus";
import type { AgentCapabilityFlags } from "@server/server/agent/agent-sdk-types";
@@ -48,6 +49,11 @@ export function WorkspaceDraftAgentTab({
}: WorkspaceDraftAgentTabProps) {
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 addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
const draftStoreKey = useMemo(
() =>
@@ -63,10 +69,10 @@ export function WorkspaceDraftAgentTab({
draftKey: draftStoreKey,
composer: {
initialServerId: serverId,
initialValues: { workingDir: workspaceId },
initialValues: { workingDir: workspaceDirectory },
isVisible: true,
onlineServerIds: isConnected ? [serverId] : [],
lockedWorkingDir: workspaceId,
lockedWorkingDir: workspaceDirectory,
},
},
);
@@ -97,6 +103,9 @@ export function WorkspaceDraftAgentTab({
if (!composerState.effectiveModelId) {
return "No model is available for the selected provider";
}
if (!workspaceDirectory) {
return "Workspace directory not found";
}
if (!client) {
return "Host is not connected";
}
@@ -110,6 +119,7 @@ export function WorkspaceDraftAgentTab({
Keyboard.dismiss();
},
buildDraftAgent: (attempt) => {
invariant(workspaceDirectory, "Workspace directory is required");
const now = attempt.timestamp;
const model = composerState.effectiveModelId || null;
const thinkingOptionId = composerState.effectiveThinkingOptionId || null;
@@ -134,20 +144,21 @@ export function WorkspaceDraftAgentTab({
persistence: null,
runtimeInfo: { provider: composerState.selectedProvider, sessionId: null, model, modeId },
title: "Agent",
cwd: workspaceId,
cwd: workspaceDirectory,
model,
thinkingOptionId,
labels: {},
};
},
createRequest: async ({ attempt, text, images }) => {
invariant(workspaceDirectory, "Workspace directory is required");
if (!client) {
throw new Error("Host is not connected");
}
const config = buildWorkspaceDraftAgentConfig({
provider: composerState.selectedProvider,
cwd: workspaceId,
cwd: workspaceDirectory,
...(composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
? { modeId: composerState.selectedMode }
: {}),
@@ -1,5 +1,4 @@
import type { WorkspaceDescriptor } from "@/stores/session-store";
import { projectDisplayNameFromProjectId } from "@/utils/project-display-name";
export function resolveWorkspaceHeader(input: { workspace: WorkspaceDescriptor }): {
title: string;
@@ -7,7 +6,7 @@ export function resolveWorkspaceHeader(input: { workspace: WorkspaceDescriptor }
} {
return {
title: input.workspace.name,
subtitle: projectDisplayNameFromProjectId(input.workspace.projectId),
subtitle: input.workspace.projectDisplayName,
};
}
@@ -554,8 +554,24 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const isFocusModeEnabled = usePanelStore((state) => state.desktop.focusModeEnabled);
const normalizedServerId = trimNonEmpty(decodeSegment(serverId)) ?? "";
const normalizedWorkspaceId =
const rawWorkspaceIdentifier =
normalizeWorkspaceIdentity(decodeWorkspaceIdFromPathSegment(workspaceId)) ?? "";
// Resolve the workspace ID: first try direct map key, then fall back to path-based lookup.
// This lets URLs that encode a filesystem path (e.g. from deep links or older bookmarks)
// resolve correctly even though the map is keyed by numeric DB IDs.
const normalizedWorkspaceId = useSessionStore((state) => {
if (!normalizedServerId || !rawWorkspaceIdentifier) return "";
const workspaces = state.sessions[normalizedServerId]?.workspaces;
if (!workspaces) return rawWorkspaceIdentifier;
if (workspaces.has(rawWorkspaceIdentifier)) return rawWorkspaceIdentifier;
for (const [id, ws] of workspaces.entries()) {
if (normalizeWorkspaceIdentity(ws.workspaceDirectory) === rawWorkspaceIdentifier) return id;
if (normalizeWorkspaceIdentity(ws.projectRootPath) === rawWorkspaceIdentifier) return id;
}
return rawWorkspaceIdentifier;
});
const workspaceTerminalScopeKey =
normalizedServerId && normalizedWorkspaceId
? `${normalizedServerId}:${normalizedWorkspaceId}`
@@ -567,43 +583,47 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const queryClient = useQueryClient();
const client = useHostRuntimeClient(normalizedServerId);
const isConnected = useHostRuntimeIsConnected(normalizedServerId);
const workspaceDescriptor = useSessionStore(
(state) => state.sessions[normalizedServerId]?.workspaces.get(normalizedWorkspaceId) ?? null,
);
const workspaceDirectory =
workspaceDescriptor?.workspaceDirectory ?? workspaceDescriptor?.projectRootPath ?? null;
const workspaceAgentVisibility = useStoreWithEqualityFn(
useSessionStore,
(state) =>
deriveWorkspaceAgentVisibility({
sessionAgents: state.sessions[normalizedServerId]?.agents,
workspaceId: normalizedWorkspaceId,
workspaceDirectory,
}),
workspaceAgentVisibilityEqual,
);
const terminalsQueryKey = useMemo(
() => ["terminals", normalizedServerId, normalizedWorkspaceId] as const,
[normalizedServerId, normalizedWorkspaceId],
() => ["terminals", normalizedServerId, workspaceDirectory] as const,
[normalizedServerId, workspaceDirectory],
);
type ListTerminalsPayload = ListTerminalsResponse["payload"];
const terminalsQuery = useQuery({
queryKey: terminalsQueryKey,
enabled:
Boolean(client && isConnected) &&
normalizedWorkspaceId.length > 0 &&
normalizedWorkspaceId.startsWith("/"),
Boolean(workspaceDirectory),
queryFn: async () => {
if (!client) {
if (!client || !workspaceDirectory) {
throw new Error("Host is not connected");
}
return await client.listTerminals(normalizedWorkspaceId);
return await client.listTerminals(workspaceDirectory);
},
staleTime: TERMINALS_QUERY_STALE_TIME,
});
const terminals = terminalsQuery.data?.terminals ?? [];
const createTerminalMutation = useMutation({
mutationFn: async (input?: { paneId?: string }) => {
if (!client) {
if (!client || !workspaceDirectory) {
throw new Error("Host is not connected");
}
return await client.createTerminal(normalizedWorkspaceId);
return await client.createTerminal(workspaceDirectory);
},
onSuccess: (payload, input) => {
const createdTerminal = payload.terminal;
@@ -613,8 +633,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
terminals: current?.terminals ?? [],
terminal: createdTerminal,
});
const cwd = current?.cwd ?? workspaceDirectory ?? undefined;
return {
cwd: current?.cwd ?? normalizedWorkspaceId,
...(cwd ? { cwd } : {}),
terminals: nextTerminals,
requestId: current?.requestId ?? `terminal-create-${createdTerminal.id}`,
};
@@ -657,7 +678,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const { archiveAgent } = useArchiveAgent();
useEffect(() => {
if (!client || !isConnected || !normalizedWorkspaceId.startsWith("/")) {
if (!client || !isConnected || !workspaceDirectory) {
return;
}
@@ -665,7 +686,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (message.type !== "terminals_changed") {
return;
}
if (message.payload.cwd !== normalizedWorkspaceId) {
if (message.payload.cwd !== workspaceDirectory) {
return;
}
@@ -676,32 +697,27 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
}));
});
client.subscribeTerminals({ cwd: normalizedWorkspaceId });
client.subscribeTerminals({ cwd: workspaceDirectory });
return () => {
unsubscribeChanged();
client.unsubscribeTerminals({ cwd: normalizedWorkspaceId });
client.unsubscribeTerminals({ cwd: workspaceDirectory });
};
}, [client, isConnected, normalizedWorkspaceId, queryClient, terminalsQueryKey]);
}, [client, isConnected, queryClient, terminalsQueryKey, workspaceDirectory]);
const checkoutQuery = useQuery({
queryKey: checkoutStatusQueryKey(normalizedServerId, normalizedWorkspaceId),
queryKey: checkoutStatusQueryKey(normalizedServerId, workspaceDirectory ?? ""),
enabled:
Boolean(client && isConnected) &&
normalizedWorkspaceId.length > 0 &&
normalizedWorkspaceId.startsWith("/"),
Boolean(workspaceDirectory),
queryFn: async () => {
if (!client) {
if (!client || !workspaceDirectory) {
throw new Error("Host is not connected");
}
return (await client.getCheckoutStatus(normalizedWorkspaceId)) as CheckoutStatusPayload;
return (await client.getCheckoutStatus(workspaceDirectory)) as CheckoutStatusPayload;
},
staleTime: 15_000,
});
const workspaceDescriptor = useSessionStore(
(state) => state.sessions[normalizedServerId]?.workspaces.get(normalizedWorkspaceId) ?? null,
);
const hasHydratedWorkspaces = useSessionStore(
(state) => state.sessions[normalizedServerId]?.hasHydratedWorkspaces ?? false,
);
@@ -731,15 +747,15 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const isExplorerOpen = isMobile ? mobileView === "file-explorer" : desktopFileExplorerOpen;
const activeExplorerCheckout = useMemo<ExplorerCheckoutContext | null>(() => {
if (!normalizedServerId || !normalizedWorkspaceId.startsWith("/")) {
if (!normalizedServerId || !workspaceDirectory) {
return null;
}
return {
serverId: normalizedServerId,
cwd: normalizedWorkspaceId,
cwd: workspaceDirectory,
isGit: isGitCheckout,
};
}, [isGitCheckout, normalizedServerId, normalizedWorkspaceId]);
}, [isGitCheckout, normalizedServerId, workspaceDirectory]);
useEffect(() => {
setActiveExplorerCheckout(activeExplorerCheckout);
@@ -1081,12 +1097,12 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (createTerminalMutation.isPending) {
return;
}
if (!normalizedWorkspaceId.startsWith("/")) {
if (!workspaceDirectory) {
return;
}
createTerminalMutation.mutate(input);
},
[createTerminalMutation, normalizedWorkspaceId],
[createTerminalMutation, workspaceDirectory],
);
const handleSelectSwitcherTab = useCallback(
@@ -1270,18 +1286,18 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
);
const handleCopyWorkspacePath = useCallback(async () => {
if (!normalizedWorkspaceId.startsWith("/")) {
if (!workspaceDirectory) {
toast.error("Workspace path not available");
return;
}
try {
await Clipboard.setStringAsync(normalizedWorkspaceId);
await Clipboard.setStringAsync(workspaceDirectory);
toast.copied("Workspace path");
} catch {
toast.error("Copy failed");
}
}, [normalizedWorkspaceId, toast]);
}, [toast, workspaceDirectory]);
const handleCopyBranchName = useCallback(async () => {
if (!currentBranchName) {
@@ -1927,7 +1943,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
<DropdownMenuItem
testID="workspace-header-copy-path"
leading={<Copy size={16} color={theme.colors.foregroundMuted} />}
disabled={!normalizedWorkspaceId.startsWith("/")}
disabled={!workspaceDirectory}
onSelect={handleCopyWorkspacePath}
>
Copy workspace path
@@ -1952,7 +1968,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
<>
<WorkspaceGitActions
serverId={normalizedServerId}
cwd={normalizedWorkspaceId}
cwd={workspaceDirectory ?? ""}
/>
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
<TooltipTrigger asChild>
@@ -2135,7 +2151,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
<ExplorerSidebar
serverId={normalizedServerId}
workspaceId={normalizedWorkspaceId}
workspaceRoot={normalizedWorkspaceId}
workspaceRoot={workspaceDirectory ?? ""}
isGit={isGitCheckout}
onOpenFile={handleOpenFileFromExplorer}
/>
@@ -13,6 +13,7 @@ describe("workspace source of truth consumption", () => {
projectId: "remote:github.com/getpaseo/paseo",
projectDisplayName: "getpaseo/paseo",
projectRootPath: "/repo/main",
workspaceDirectory: "/repo/main",
projectKind: "git",
workspaceKind: "checkout",
name: "feat/workspace-sot",