Files
paseo/packages/server/src/server/workspace-registry-model.ts
T
Mohamed Boudra 345729c588 refactor: eliminate getCheckoutStatusLite, use WorkspaceGitService
Route all git checkout queries through WorkspaceGitService instead of
shelling out to git on every call. This makes fetch_agents_request
instant when warm (cached in-memory snapshots) and deduplicates
concurrent cold-start refreshes via ensureWorkspaceTarget.

- buildProjectPlacementForCwd now uses workspaceGitService.getSnapshot()
- worktree-session call sites use WorkspaceGitService
- Deleted getCheckoutStatusLite and its 4 return types from checkout-git
- Fixed cold-start thundering herd: getSnapshot deduplicates via
  ensureWorkspaceTarget instead of spawning parallel git processes
- Extracted checkout-diff logic into WorkspaceGitService
2026-04-14 19:05:36 +07:00

235 lines
6.3 KiB
TypeScript

import { resolve } from "node:path";
import type { ProjectCheckoutLitePayload, ProjectPlacementPayload } from "../shared/messages.js";
import type { WorkspaceGitService } from "./workspace-git-service.js";
import type { PersistedWorkspaceRecord } from "./workspace-registry.js";
export type PersistedProjectKind = "git" | "non_git";
export type PersistedWorkspaceKind = "local_checkout" | "worktree" | "directory";
export type DetectStaleWorkspacesInput = {
activeWorkspaces: PersistedWorkspaceRecord[];
checkDirectoryExists: (cwd: string) => Promise<boolean>;
};
export function normalizeWorkspaceId(cwd: string): string {
const trimmed = cwd.trim();
if (!trimmed) {
return cwd;
}
return resolve(trimmed);
}
export function deriveWorkspaceId(cwd: string, checkout: ProjectCheckoutLitePayload): string {
return checkout.worktreeRoot ?? normalizeWorkspaceId(cwd);
}
function deriveRemoteProjectKey(remoteUrl: string | null): string | null {
if (!remoteUrl) {
return null;
}
const trimmed = remoteUrl.trim();
if (!trimmed) {
return null;
}
let host: string | null = null;
let remotePath: string | null = null;
const scpLike = trimmed.match(/^[^@]+@([^:]+):(.+)$/);
if (scpLike) {
host = scpLike[1] ?? null;
remotePath = scpLike[2] ?? null;
} else if (trimmed.includes("://")) {
try {
const parsed = new URL(trimmed);
host = parsed.hostname || null;
remotePath = parsed.pathname ? parsed.pathname.replace(/^\/+/, "") : null;
} catch {
return null;
}
}
if (!host || !remotePath) {
return null;
}
let cleanedPath = remotePath.trim().replace(/^\/+/, "").replace(/\/+$/, "");
if (cleanedPath.endsWith(".git")) {
cleanedPath = cleanedPath.slice(0, -4);
}
if (!cleanedPath.includes("/")) {
return null;
}
const cleanedHost = host.toLowerCase();
if (cleanedHost === "github.com") {
return `remote:github.com/${cleanedPath}`;
}
return `remote:${cleanedHost}/${cleanedPath}`;
}
export function deriveProjectGroupingKey(options: {
cwd: string;
remoteUrl: string | null;
isPaseoOwnedWorktree: boolean;
mainRepoRoot: string | null;
}): string {
const remoteKey = deriveRemoteProjectKey(options.remoteUrl);
if (remoteKey) {
return remoteKey;
}
const mainRepoRoot = options.mainRepoRoot?.trim();
if (options.isPaseoOwnedWorktree && mainRepoRoot) {
return mainRepoRoot;
}
return options.cwd;
}
export function deriveProjectGroupingName(projectKey: string): string {
const githubRemotePrefix = "remote:github.com/";
if (projectKey.startsWith(githubRemotePrefix)) {
return projectKey.slice(githubRemotePrefix.length) || projectKey;
}
const segments = projectKey.split(/[\\/]/).filter(Boolean);
return segments[segments.length - 1] || projectKey;
}
function deriveWorkspaceDirectoryName(cwd: string): string {
const normalized = cwd.replace(/\\/g, "/");
const segments = normalized.split("/").filter(Boolean);
return segments[segments.length - 1] ?? cwd;
}
export function deriveWorkspaceDisplayName(input: {
cwd: string;
checkout: ProjectCheckoutLitePayload;
}): string {
const branch = input.checkout.currentBranch?.trim() ?? null;
if (branch && branch.toUpperCase() !== "HEAD") {
return branch;
}
return deriveWorkspaceDirectoryName(input.cwd);
}
export function deriveProjectRootPath(input: {
cwd: string;
checkout: ProjectCheckoutLitePayload;
}): string {
if (input.checkout.isGit && input.checkout.isPaseoOwnedWorktree) {
return input.checkout.mainRepoRoot;
}
return input.cwd;
}
export function deriveProjectKind(checkout: ProjectCheckoutLitePayload): PersistedProjectKind {
return checkout.isGit ? "git" : "non_git";
}
export function deriveWorkspaceKind(checkout: ProjectCheckoutLitePayload): PersistedWorkspaceKind {
if (!checkout.isGit) {
return "directory";
}
return checkout.isPaseoOwnedWorktree ? "worktree" : "local_checkout";
}
export function checkoutLiteFromGitSnapshot(
cwd: string,
git: {
isGit: boolean;
currentBranch: string | null;
remoteUrl: string | null;
repoRoot: string | null;
isPaseoOwnedWorktree: boolean;
mainRepoRoot: string | null;
},
): ProjectCheckoutLitePayload {
if (!git.isGit) {
return {
cwd,
isGit: false,
currentBranch: null,
remoteUrl: null,
worktreeRoot: null,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
};
}
if (git.isPaseoOwnedWorktree && git.mainRepoRoot) {
return {
cwd,
isGit: true,
currentBranch: git.currentBranch,
remoteUrl: git.remoteUrl,
worktreeRoot: git.repoRoot ?? cwd,
isPaseoOwnedWorktree: true,
mainRepoRoot: git.mainRepoRoot,
};
}
return {
cwd,
isGit: true,
currentBranch: git.currentBranch,
remoteUrl: git.remoteUrl,
worktreeRoot: git.repoRoot ?? cwd,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
};
}
export async function detectStaleWorkspaces(
input: DetectStaleWorkspacesInput,
): Promise<Set<string>> {
const staleWorkspaceIds = new Set<string>();
for (const workspace of input.activeWorkspaces) {
const dirExists = await input.checkDirectoryExists(workspace.cwd);
if (!dirExists) {
staleWorkspaceIds.add(workspace.workspaceId);
}
}
return staleWorkspaceIds;
}
export async function buildProjectPlacementForCwd(input: {
cwd: string;
workspaceGitService: WorkspaceGitService;
}): Promise<ProjectPlacementPayload> {
const normalizedCwd = normalizeWorkspaceId(input.cwd);
const checkout = await input.workspaceGitService
.getSnapshot(normalizedCwd)
.then(
(snapshot): ProjectCheckoutLitePayload =>
checkoutLiteFromGitSnapshot(normalizedCwd, snapshot.git),
)
.catch(
(): ProjectCheckoutLitePayload => ({
cwd: normalizedCwd,
isGit: false,
currentBranch: null,
remoteUrl: null,
worktreeRoot: null,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
}),
);
const projectKey = deriveProjectGroupingKey({
cwd: checkout.worktreeRoot ?? normalizedCwd,
remoteUrl: checkout.remoteUrl,
isPaseoOwnedWorktree: checkout.isPaseoOwnedWorktree,
mainRepoRoot: checkout.mainRepoRoot,
});
return {
projectKey,
projectName: deriveProjectGroupingName(projectKey),
checkout,
};
}