feat(filesystem): enhance local mount path normalization and improve virtual path handling in agent filesystem

This commit is contained in:
Anish Sarkar 2026-04-24 02:12:30 +05:30
parent c1a07a093e
commit 1e9db6f26f
3 changed files with 96 additions and 57 deletions

View file

@ -782,6 +782,27 @@ class SurfSenseFilesystemMiddleware(FilesystemMiddleware):
return f"/{backend.default_mount()}"
return ""
def _normalize_local_mount_path(
self, candidate: str, runtime: ToolRuntime[None, FilesystemState]
) -> str:
backend = self._get_backend(runtime)
mount_prefix = self._default_mount_prefix(runtime)
if not mount_prefix or not isinstance(backend, MultiRootLocalFolderBackend):
return candidate if candidate.startswith("/") else f"/{candidate.lstrip('/')}"
mount_names = set(backend.list_mounts())
if candidate.startswith("/"):
first_segment = candidate.lstrip("/").split("/", 1)[0]
if first_segment in mount_names:
return candidate
return f"{mount_prefix}{candidate}"
relative = candidate.lstrip("/")
first_segment = relative.split("/", 1)[0]
if first_segment in mount_names:
return f"/{relative}"
return f"{mount_prefix}/{relative}"
def _get_contract_suggested_path(
self, runtime: ToolRuntime[None, FilesystemState]
) -> str:
@ -790,11 +811,7 @@ class SurfSenseFilesystemMiddleware(FilesystemMiddleware):
if isinstance(suggested, str) and suggested.strip():
cleaned = suggested.strip()
if self._filesystem_mode == FilesystemMode.DESKTOP_LOCAL_FOLDER:
mount_prefix = self._default_mount_prefix(runtime)
if mount_prefix and cleaned.startswith("/") and not cleaned.startswith(
f"{mount_prefix}/"
):
return f"{mount_prefix}{cleaned}"
return self._normalize_local_mount_path(cleaned, runtime)
return cleaned
if self._filesystem_mode == FilesystemMode.DESKTOP_LOCAL_FOLDER:
mount_prefix = self._default_mount_prefix(runtime)
@ -811,19 +828,7 @@ class SurfSenseFilesystemMiddleware(FilesystemMiddleware):
if not candidate:
return self._get_contract_suggested_path(runtime)
if self._filesystem_mode == FilesystemMode.DESKTOP_LOCAL_FOLDER:
backend = self._get_backend(runtime)
mount_prefix = self._default_mount_prefix(runtime)
if mount_prefix and not candidate.startswith("/"):
return f"{mount_prefix}/{candidate.lstrip('/')}"
if (
mount_prefix
and isinstance(backend, MultiRootLocalFolderBackend)
and candidate.startswith("/")
):
mount_names = backend.list_mounts()
first_segment = candidate.lstrip("/").split("/", 1)[0]
if first_segment not in mount_names:
return f"{mount_prefix}{candidate}"
return self._normalize_local_mount_path(candidate, runtime)
if not candidate.startswith("/"):
return f"/{candidate.lstrip('/')}"
return candidate

View file

@ -122,12 +122,55 @@ function toVirtualPath(rootPath: string, absolutePath: string): string {
return `/${rel.replace(/\\/g, "/")}`;
}
async function resolveCurrentRootPath(): Promise<string> {
const settings = await getAgentFilesystemSettings();
if (settings.localRootPaths.length === 0) {
throw new Error("No local filesystem roots selected");
type LocalRootMount = {
mount: string;
rootPath: string;
};
function buildRootMounts(rootPaths: string[]): LocalRootMount[] {
const mounts: LocalRootMount[] = [];
const usedMounts = new Set<string>();
for (const rawRootPath of rootPaths) {
const normalizedRoot = resolve(rawRootPath);
const baseMount = normalizedRoot.split(/[\\/]/).at(-1) || "root";
let mount = baseMount;
let suffix = 2;
while (usedMounts.has(mount)) {
mount = `${baseMount}-${suffix}`;
suffix += 1;
}
usedMounts.add(mount);
mounts.push({ mount, rootPath: normalizedRoot });
}
return settings.localRootPaths[0];
return mounts;
}
function parseMountedVirtualPath(virtualPath: string): {
mount: string;
subPath: string;
} {
if (!virtualPath.startsWith("/")) {
throw new Error("Path must start with '/'");
}
const trimmed = virtualPath.replace(/^\/+/, "");
if (!trimmed) {
throw new Error("Path must include a mounted root segment");
}
const [mount, ...rest] = trimmed.split("/");
const remainder = rest.join("/");
if (!remainder) {
throw new Error("Path must include a file path under the mounted root");
}
return { mount, subPath: `/${remainder}` };
}
function findMountByName(mounts: LocalRootMount[], mountName: string): LocalRootMount | undefined {
return mounts.find((entry) => entry.mount === mountName);
}
function toMountedVirtualPath(mount: string, rootPath: string, absolutePath: string): string {
const relativePath = toVirtualPath(rootPath, absolutePath);
return `/${mount}${relativePath}`;
}
async function resolveCurrentRootPaths(): Promise<string[]> {
@ -142,27 +185,18 @@ export async function readAgentLocalFileText(
virtualPath: string
): Promise<{ path: string; content: string }> {
const rootPaths = await resolveCurrentRootPaths();
for (const rootPath of rootPaths) {
const absolutePath = resolveVirtualPath(rootPath, virtualPath);
try {
const content = await readFile(absolutePath, "utf8");
return {
path: toVirtualPath(rootPath, absolutePath),
content,
};
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
continue;
}
throw error;
}
const mounts = buildRootMounts(rootPaths);
const { mount, subPath } = parseMountedVirtualPath(virtualPath);
const rootMount = findMountByName(mounts, mount);
if (!rootMount) {
throw new Error(
`Unknown mounted root '${mount}'. Available roots: ${mounts.map((entry) => `/${entry.mount}`).join(", ")}`
);
}
// Keep the same relative virtual path in the error context.
const fallbackRootPath = await resolveCurrentRootPath();
const fallbackAbsolutePath = resolveVirtualPath(fallbackRootPath, virtualPath);
const content = await readFile(fallbackAbsolutePath, "utf8");
const absolutePath = resolveVirtualPath(rootMount.rootPath, subPath);
const content = await readFile(absolutePath, "utf8");
return {
path: toVirtualPath(fallbackRootPath, fallbackAbsolutePath),
path: toMountedVirtualPath(rootMount.mount, rootMount.rootPath, absolutePath),
content,
};
}
@ -172,24 +206,24 @@ export async function writeAgentLocalFileText(
content: string
): Promise<{ path: string }> {
const rootPaths = await resolveCurrentRootPaths();
let selectedRootPath = rootPaths[0];
let selectedAbsolutePath = resolveVirtualPath(selectedRootPath, virtualPath);
for (const rootPath of rootPaths) {
const absolutePath = resolveVirtualPath(rootPath, virtualPath);
try {
await access(absolutePath);
selectedRootPath = rootPath;
selectedAbsolutePath = absolutePath;
break;
} catch {
// Keep searching for an existing file path across selected roots.
}
const mounts = buildRootMounts(rootPaths);
const { mount, subPath } = parseMountedVirtualPath(virtualPath);
const rootMount = findMountByName(mounts, mount);
if (!rootMount) {
throw new Error(
`Unknown mounted root '${mount}'. Available roots: ${mounts.map((entry) => `/${entry.mount}`).join(", ")}`
);
}
let selectedAbsolutePath = resolveVirtualPath(rootMount.rootPath, subPath);
try {
await access(selectedAbsolutePath);
} catch {
// New files are created under the selected mounted root.
}
await mkdir(dirname(selectedAbsolutePath), { recursive: true });
await writeFile(selectedAbsolutePath, content, "utf8");
return {
path: toVirtualPath(selectedRootPath, selectedAbsolutePath),
path: toMountedVirtualPath(rootMount.mount, rootMount.rootPath, selectedAbsolutePath),
};
}

View file

@ -89,7 +89,7 @@ export function SourceCodeEditor({
onChange={(next) => onChange(next ?? "")}
loading={
<div className="flex h-full w-full items-center justify-center">
<Spinner size="sm" className="text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
}
beforeMount={(monaco) => {