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
+3 -3
View File
@@ -18,7 +18,7 @@ import { useSessionStore } from "@/stores/session-store";
import { Archive, SquareTerminal } from "lucide-react-native";
import { getProviderIcon } from "@/components/provider-icons";
import { buildHostAgentDetailRoute } from "@/utils/host-routes";
import { resolveHydratedWorkspaceId } from "@/utils/resolve-hydrated-workspace-id";
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
interface AgentListProps {
@@ -244,9 +244,9 @@ export function AgentList({
const serverId = agent.serverId;
const agentId = agent.id;
const workspaceId = resolveHydratedWorkspaceId({
const workspaceId = resolveWorkspaceIdByExecutionDirectory({
workspaces: useSessionStore.getState().sessions[serverId]?.workspaces?.values(),
path: agent.cwd,
workspaceDirectory: agent.cwd,
});
onAgentSelect?.();
@@ -63,7 +63,7 @@ import { createMarkdownStyles } from "@/styles/markdown-styles";
import { MAX_CONTENT_WIDTH } from "@/constants/layout";
import { getMarkdownListMarker } from "@/utils/markdown-list";
import { normalizeInlinePathTarget } from "@/utils/inline-path";
import { resolveHydratedWorkspaceId } from "@/utils/resolve-hydrated-workspace-id";
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation";
import { useStableEvent } from "@/hooks/use-stable-event";
import {
@@ -133,10 +133,9 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
);
const workspaceRoot = agent.cwd?.trim() || "";
const workspacePath = agent.projectPlacement?.checkout?.cwd?.trim() || workspaceRoot;
const workspaceId = resolveHydratedWorkspaceId({
const workspaceId = resolveWorkspaceIdByExecutionDirectory({
workspaces: useSessionStore.getState().sessions[resolvedServerId]?.workspaces?.values(),
path: workspacePath,
workspaceDirectory: workspaceRoot,
});
const { requestDirectoryListing } = useFileExplorerActions({
serverId: resolvedServerId,
@@ -86,6 +86,10 @@ import { useSessionStore } from "@/stores/session-store";
import { useWorkspaceSetupStore } from "@/stores/workspace-setup-store";
import { buildWorkspaceArchiveRedirectRoute } from "@/utils/workspace-archive-navigation";
import { openExternalUrl } from "@/utils/open-external-url";
import {
requireWorkspaceExecutionDirectory,
resolveWorkspaceExecutionDirectory,
} from "@/utils/workspace-execution";
function toProjectIconDataUri(icon: { mimeType: string; data: string } | null): string | null {
if (!icon) {
@@ -674,8 +678,8 @@ function ProjectHeaderRow({
onWorkspacePress?.();
beginWorkspaceSetup({
serverId,
projectPath: project.iconWorkingDir,
projectName: displayName,
sourceDirectory: project.iconWorkingDir,
displayName,
creationMethod: "create_worktree",
navigationMethod: "navigate",
});
@@ -815,10 +819,13 @@ function WorkspaceRowInner({
const { theme } = useUnistyles();
const [isHovered, setIsHovered] = useState(false);
const isMobile = Platform.OS !== "web";
const workspaceDirectory = resolveWorkspaceExecutionDirectory({
workspaceDirectory: workspace.workspaceDirectory,
});
const prHint = useWorkspacePrHint({
serverId: workspace.serverId,
cwd: workspace.projectRootPath ?? "",
enabled: workspace.projectKind === "git" && Boolean(workspace.projectRootPath),
cwd: workspaceDirectory ?? "",
enabled: workspace.projectKind === "git" && Boolean(workspaceDirectory),
});
const interaction = useLongPressDragInteraction({
drag,
@@ -982,12 +989,17 @@ function WorkspaceRowWithMenu({
(state) => state.sessions[workspace.serverId]?.workspaces ?? EMPTY_WORKSPACES,
);
const [isArchivingWorkspace, setIsArchivingWorkspace] = useState(false);
const workspaceDirectory = resolveWorkspaceExecutionDirectory({
workspaceDirectory: workspace.workspaceDirectory,
});
const archiveStatus = useCheckoutGitActionsStore((state) =>
state.getStatus({
serverId: workspace.serverId,
cwd: workspace.workspaceDirectory ?? workspace.projectRootPath ?? "",
actionId: "archive-worktree",
}),
workspaceDirectory
? state.getStatus({
serverId: workspace.serverId,
cwd: workspaceDirectory,
actionId: "archive-worktree",
})
: "idle",
);
const isWorktree = workspace.workspaceKind === "worktree";
const isArchiving = isWorktree ? archiveStatus === "pending" : isArchivingWorkspace;
@@ -1025,7 +1037,17 @@ function WorkspaceRowWithMenu({
if (!confirmed) {
return;
}
const workspaceDirectory = workspace.workspaceDirectory ?? workspace.projectRootPath;
let workspaceDirectory: string;
try {
workspaceDirectory = requireWorkspaceExecutionDirectory({
workspaceId: workspace.workspaceId,
workspaceDirectory: workspace.workspaceDirectory,
});
} catch (error) {
toast.error(error instanceof Error ? error.message : "Workspace path not available");
return;
}
if (!workspaceDirectory) {
toast.error("Workspace path not available");
return;
@@ -1050,7 +1072,6 @@ function WorkspaceRowWithMenu({
redirectAfterArchive,
toast,
workspace.name,
workspace.projectRootPath,
workspace.workspaceDirectory,
workspace.serverId,
workspace.workspaceId,
@@ -1102,14 +1123,19 @@ function WorkspaceRowWithMenu({
]);
const handleCopyPath = useCallback(() => {
const workspaceDirectory = workspace.workspaceDirectory ?? workspace.projectRootPath;
if (!workspaceDirectory) {
toast.error("Workspace path not available");
let workspaceDirectory: string;
try {
workspaceDirectory = requireWorkspaceExecutionDirectory({
workspaceId: workspace.workspaceId,
workspaceDirectory: workspace.workspaceDirectory,
});
} catch (error) {
toast.error(error instanceof Error ? error.message : "Workspace path not available");
return;
}
void Clipboard.setStringAsync(workspaceDirectory);
toast.copied("Path copied");
}, [toast, workspace.projectRootPath, workspace.workspaceDirectory]);
}, [toast, workspace.workspaceDirectory, workspace.workspaceId]);
const handleCopyBranchName = useCallback(() => {
void Clipboard.setStringAsync(workspace.name);
@@ -17,6 +17,10 @@ import { useWorkspaceSetupStore } from "@/stores/workspace-setup-store";
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
import { encodeImages } from "@/utils/encode-images";
import { toErrorMessage } from "@/utils/error-messages";
import {
requireWorkspaceExecutionAuthority,
requireWorkspaceRecordId,
} from "@/utils/workspace-execution";
import { navigateToPreparedWorkspaceTab } from "@/utils/workspace-navigation";
import type { MessagePayload } from "./message-input";
@@ -41,19 +45,21 @@ export function WorkspaceSetupDialog() {
);
const serverId = pendingWorkspaceSetup?.serverId ?? "";
const projectPath = pendingWorkspaceSetup?.projectPath ?? "";
const projectName = pendingWorkspaceSetup?.projectName?.trim() ?? "";
const sourceDirectory = pendingWorkspaceSetup?.sourceDirectory ?? "";
const displayName = pendingWorkspaceSetup?.displayName?.trim() ?? "";
const workspace = createdWorkspace;
const client = useHostRuntimeClient(serverId);
const isConnected = useHostRuntimeIsConnected(serverId);
const chatDraft = useAgentInputDraft({
draftKey: `workspace-setup:${serverId}:${projectPath}`,
draftKey: `workspace-setup:${serverId}:${sourceDirectory}`,
composer: {
initialServerId: serverId || null,
initialValues: projectPath ? { workingDir: projectPath } : undefined,
initialValues: workspace?.workspaceDirectory
? { workingDir: workspace.workspaceDirectory }
: undefined,
isVisible: pendingWorkspaceSetup !== null,
onlineServerIds: isConnected && serverId ? [serverId] : [],
lockedWorkingDir: workspace?.projectRootPath ?? projectPath,
lockedWorkingDir: workspace?.workspaceDirectory || undefined,
},
});
const composerState = chatDraft.composerState;
@@ -70,7 +76,7 @@ export function WorkspaceSetupDialog() {
setErrorMessage(null);
setCreatedWorkspace(null);
setPendingAction(null);
}, [pendingWorkspaceSetup?.creationMethod, projectPath, serverId]);
}, [pendingWorkspaceSetup?.creationMethod, serverId, sourceDirectory]);
const handleClose = useCallback(() => {
clearWorkspaceSetup();
@@ -116,10 +122,10 @@ export function WorkspaceSetupDialog() {
const payload =
pendingWorkspaceSetup.creationMethod === "create_worktree"
? await connectedClient.createPaseoWorktree({
cwd: pendingWorkspaceSetup.projectPath,
cwd: pendingWorkspaceSetup.sourceDirectory,
worktreeSlug: createNameId(),
})
: await connectedClient.openProject(pendingWorkspaceSetup.projectPath);
: await connectedClient.openProject(pendingWorkspaceSetup.sourceDirectory);
if (payload.error || !payload.workspace) {
throw new Error(
@@ -149,13 +155,13 @@ export function WorkspaceSetupDialog() {
const current = useWorkspaceSetupStore.getState().pendingWorkspaceSetup;
return (
current?.serverId === pendingWorkspaceSetup?.serverId &&
current?.projectPath === pendingWorkspaceSetup?.projectPath &&
current?.sourceDirectory === pendingWorkspaceSetup?.sourceDirectory &&
current?.creationMethod === pendingWorkspaceSetup?.creationMethod
);
}, [
pendingWorkspaceSetup?.creationMethod,
pendingWorkspaceSetup?.projectPath,
pendingWorkspaceSetup?.serverId,
pendingWorkspaceSetup?.sourceDirectory,
]);
const handleCreateChatAgent = useCallback(
@@ -170,10 +176,11 @@ export function WorkspaceSetupDialog() {
}
const encodedImages = await encodeImages(images);
const workspaceDirectory = workspace.projectRootPath ?? projectPath;
const workspaceDirectory = requireWorkspaceExecutionAuthority({ workspace }).workspaceDirectory;
const agent = await connectedClient.createAgent({
provider: composerState.selectedProvider,
cwd: workspaceDirectory,
workspaceId: requireWorkspaceRecordId(workspace.id),
...(composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
? { modeId: composerState.selectedMode }
: {}),
@@ -227,10 +234,11 @@ export function WorkspaceSetupDialog() {
throw new Error("Workspace setup composer state is required");
}
const workspaceDirectory = workspace.projectRootPath ?? projectPath;
const workspaceDirectory = requireWorkspaceExecutionAuthority({ workspace }).workspaceDirectory;
const agent = await connectedClient.createAgent({
provider: composerState.selectedProvider,
cwd: workspaceDirectory,
workspaceId: requireWorkspaceRecordId(workspace.id),
terminal: true,
...(terminalPrompt.trim() ? { initialPrompt: terminalPrompt.trim() } : {}),
});
@@ -274,11 +282,7 @@ export function WorkspaceSetupDialog() {
setErrorMessage(null);
const workspace = await ensureWorkspace();
const connectedClient = withConnectedClient();
const workspaceDirectory = workspace.projectRootPath ?? projectPath;
if (!workspaceDirectory) {
throw new Error("Workspace directory not found");
}
const workspaceDirectory = requireWorkspaceExecutionAuthority({ workspace }).workspaceDirectory;
const payload = await connectedClient.createTerminal(workspaceDirectory);
if (payload.error || !payload.terminal) {
@@ -304,12 +308,12 @@ export function WorkspaceSetupDialog() {
const workspaceTitle =
workspace?.name ||
workspace?.projectDisplayName ||
projectName ||
projectPath.split(/[\\/]/).filter(Boolean).pop() ||
projectPath;
const workspacePath = workspace?.projectRootPath || projectPath;
displayName ||
sourceDirectory.split(/[\\/]/).filter(Boolean).pop() ||
sourceDirectory;
const workspacePath = workspace?.workspaceDirectory || "Workspace will be created before launch.";
if (!pendingWorkspaceSetup || !projectPath) {
if (!pendingWorkspaceSetup || !sourceDirectory) {
return null;
}
@@ -378,7 +382,7 @@ export function WorkspaceSetupDialog() {
</Text>
<View style={styles.composerCard}>
<Composer
agentId={`workspace-setup:${serverId}:${projectPath}`}
agentId={`workspace-setup:${serverId}:${sourceDirectory}`}
serverId={serverId}
isInputActive={true}
onSubmitMessage={handleCreateChatAgent}