feat(filesystem): implement filesystem tree watch functionality using chokidar for real-time updates on local folder changes

This commit is contained in:
Anish Sarkar 2026-04-27 23:08:32 +05:30
parent 3fa8c790f5
commit f330d1431c
8 changed files with 583 additions and 23 deletions

View file

@ -47,6 +47,42 @@ interface EditorContent {
const EDITABLE_DOCUMENT_TYPES = new Set(["FILE", "NOTE"]);
type EditorRenderMode = "rich_markdown" | "source_code";
type AgentFilesystemMount = {
mount: string;
rootPath: string;
};
function normalizeLocalVirtualPathForEditor(
candidatePath: string,
mounts: AgentFilesystemMount[]
): string {
const normalizedCandidate = candidatePath.trim().replace(/\\/g, "/").replace(/\/+/g, "/");
if (!normalizedCandidate) return candidatePath;
const defaultMount = mounts[0]?.mount;
if (!defaultMount) {
return normalizedCandidate.startsWith("/")
? normalizedCandidate
: `/${normalizedCandidate.replace(/^\/+/, "")}`;
}
const mountNames = new Set(mounts.map((entry) => entry.mount));
if (normalizedCandidate.startsWith("/")) {
const relative = normalizedCandidate.replace(/^\/+/, "");
const [firstSegment] = relative.split("/", 1);
if (mountNames.has(firstSegment)) {
return `/${relative}`;
}
return `/${defaultMount}/${relative}`;
}
const relative = normalizedCandidate.replace(/^\/+/, "");
const [firstSegment] = relative.split("/", 1);
if (mountNames.has(firstSegment)) {
return `/${relative}`;
}
return `/${defaultMount}/${relative}`;
}
function EditorPanelSkeleton() {
return (
<div className="space-y-6 p-6">
@ -100,6 +136,22 @@ export function EditorPanelContent({
const [displayTitle, setDisplayTitle] = useState(title || "Untitled");
const isLocalFileMode = kind === "local_file";
const editorRenderMode: EditorRenderMode = isLocalFileMode ? "source_code" : "rich_markdown";
const resolveLocalVirtualPath = useCallback(
async (candidatePath: string): Promise<string> => {
if (!electronAPI?.getAgentFilesystemMounts) {
return candidatePath;
}
try {
const mounts = (await electronAPI.getAgentFilesystemMounts(
searchSpaceId
)) as AgentFilesystemMount[];
return normalizeLocalVirtualPathForEditor(candidatePath, mounts);
} catch {
return candidatePath;
}
},
[electronAPI, searchSpaceId]
);
const isLargeDocument = (editorDoc?.content_size_bytes ?? 0) > LARGE_DOCUMENT_THRESHOLD;
@ -124,14 +176,15 @@ export function EditorPanelContent({
if (!electronAPI?.readAgentLocalFileText) {
throw new Error("Local file editor is available only in desktop mode.");
}
const resolvedLocalPath = await resolveLocalVirtualPath(localFilePath);
const readResult = await electronAPI.readAgentLocalFileText(
localFilePath,
resolvedLocalPath,
searchSpaceId
);
if (!readResult.ok) {
throw new Error(readResult.error || "Failed to read local file");
}
const inferredTitle = localFilePath.split("/").pop() || localFilePath;
const inferredTitle = resolvedLocalPath.split("/").pop() || resolvedLocalPath;
const content: EditorContent = {
document_id: -1,
title: inferredTitle,
@ -195,7 +248,7 @@ export function EditorPanelContent({
doFetch().catch(() => {});
return () => controller.abort();
}, [documentId, electronAPI, isLocalFileMode, localFilePath, searchSpaceId, title]);
}, [documentId, electronAPI, isLocalFileMode, localFilePath, resolveLocalVirtualPath, searchSpaceId, title]);
useEffect(() => {
return () => {
@ -239,9 +292,10 @@ export function EditorPanelContent({
if (!electronAPI?.writeAgentLocalFileText) {
throw new Error("Local file editor is available only in desktop mode.");
}
const resolvedLocalPath = await resolveLocalVirtualPath(localFilePath);
const contentToSave = markdownRef.current;
const writeResult = await electronAPI.writeAgentLocalFileText(
localFilePath,
resolvedLocalPath,
contentToSave,
searchSpaceId
);
@ -290,7 +344,7 @@ export function EditorPanelContent({
} finally {
setSaving(false);
}
}, [documentId, electronAPI, isLocalFileMode, localFilePath, searchSpaceId]);
}, [documentId, electronAPI, isLocalFileMode, localFilePath, resolveLocalVirtualPath, searchSpaceId]);
const isEditableType = editorDoc
? (editorRenderMode === "source_code" ||