From a8b8f2b64f3abbac799c6dc1f3f425d08bd36121 Mon Sep 17 00:00:00 2001 From: Pranjali2076 Date: Sat, 1 Aug 2026 19:29:49 +0530 Subject: [PATCH 1/4] fix(web): support folder drop recursion in Drives drawer --- web/oss/src/components/Drives/useDriveDrop.ts | 98 +++++++++++++++++-- 1 file changed, 88 insertions(+), 10 deletions(-) diff --git a/web/oss/src/components/Drives/useDriveDrop.ts b/web/oss/src/components/Drives/useDriveDrop.ts index c08bd45a47..1bc5bee10b 100644 --- a/web/oss/src/components/Drives/useDriveDrop.ts +++ b/web/oss/src/components/Drives/useDriveDrop.ts @@ -9,6 +9,78 @@ import {useCallback, useEffect, useRef, useState} from "react" const SPRING_MS = 700 +export interface DroppedFileItem { + file: File; + relativePath: string; +} + +/** + * Recursively traverses dropped DataTransferItems to extract all files, + * walking subdirectories using webkitGetAsEntry. + */ +export async function getFilesFromDataTransfer( + items: DataTransferItemList +): Promise { + const results: DroppedFileItem[] = []; + + async function traverseEntry(entry: FileSystemEntry, path = ""): Promise { + if (entry.isFile) { + const fileEntry = entry as FileSystemFileEntry; + await new Promise((resolve) => { + fileEntry.file((file) => { + const relativePath = path ? `${path}/${file.name}` : file.name; + Object.defineProperty(file, "relativePath", { + value: relativePath, + writable: true, + configurable: true, + }); + results.push({ file, relativePath }); + resolve(); + }); + }); + } else if (entry.isDirectory) { + const dirEntry = entry as FileSystemDirectoryEntry; + const dirReader = dirEntry.createReader(); + + let batch: FileSystemEntry[] = []; + do { + batch = await new Promise((resolve) => { + dirReader.readEntries((entries) => resolve(entries || [])); + }); + + const folderPath = path ? `${path}/${entry.name}` : entry.name; + for (const childEntry of batch) { + await traverseEntry(childEntry, folderPath); + } + } while (batch.length > 0); + } + } + + const promises: Promise[] = []; + for (let i = 0; i < items.length; i++) { + const entry = items[i].webkitGetAsEntry?.(); + if (entry) { + promises.push(traverseEntry(entry)); + } + } + + await Promise.all(promises); + return results; +} + +/** + * Helper to safely extract files from DataTransfer, falling back to e.dataTransfer.files + */ +async function extractFilesFromDataTransfer(dataTransfer: DataTransfer): Promise { + if (dataTransfer.items && dataTransfer.items.length > 0) { + const droppedItems = await getFilesFromDataTransfer(dataTransfer.items); + if (droppedItems.length > 0) { + return droppedItems.map((item) => item.file); + } + } + return Array.from(dataTransfer.files ?? []); +} + export const isFileDrag = (e: React.DragEvent): boolean => Array.from(e.dataTransfer?.types ?? []).includes("Files") @@ -116,12 +188,14 @@ export function useDriveDrop({ e.preventDefault() e.stopPropagation() }, - onDrop: (e: React.DragEvent) => { + onDrop: async (e: React.DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() e.stopPropagation() - const files = Array.from(e.dataTransfer.files) - if (files.length) onUpload(files, path) + if (e.dataTransfer) { + const files = await extractFilesFromDataTransfer(e.dataTransfer) + if (files.length) onUpload(files, path) + } setHoverPath(null) clearSpring() }, @@ -139,11 +213,13 @@ export function useDriveDrop({ onDragOver: (e: React.DragEvent) => { if (isFileDrag(e)) e.preventDefault() }, - onDrop: (e: React.DragEvent) => { + onDrop: async (e: React.DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() - const files = Array.from(e.dataTransfer.files) - if (files.length) onUpload(files, currentFolder) + if (e.dataTransfer) { + const files = await extractFilesFromDataTransfer(e.dataTransfer) + if (files.length) onUpload(files, currentFolder) + } setHoverPath(null) clearSpring() }, @@ -182,13 +258,15 @@ export function useStageDrop(onFiles: ((files: File[]) => void) | false | null | setDropActive(true) }, onDragLeave: () => setDropActive(false), - onDrop: (e) => { + onDrop: async (e) => { if (!isFileDrag(e)) return e.preventDefault() setDropActive(false) - const files = Array.from(e.dataTransfer.files) - if (files.length) onFiles(files) + if (e.dataTransfer) { + const files = await extractFilesFromDataTransfer(e.dataTransfer) + if (files.length) onFiles(files) + } }, }, } -} +} \ No newline at end of file From b4402ca0ffdddb6699c4cfb0128228de97fc4bf1 Mon Sep 17 00:00:00 2001 From: Pranjali2076 Date: Sat, 1 Aug 2026 19:35:47 +0530 Subject: [PATCH 2/4] fix(web): use named type imports for React DragEvent --- web/oss/src/components/Drives/useDriveDrop.ts | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/web/oss/src/components/Drives/useDriveDrop.ts b/web/oss/src/components/Drives/useDriveDrop.ts index 1bc5bee10b..293ec92e6b 100644 --- a/web/oss/src/components/Drives/useDriveDrop.ts +++ b/web/oss/src/components/Drives/useDriveDrop.ts @@ -1,4 +1,11 @@ -import {useCallback, useEffect, useRef, useState} from "react" +import { + useCallback, + useEffect, + useRef, + useState, + type DragEvent, + type HTMLAttributes, +} from "react" /** * Drag-and-drop upload behaviour shared by the drive's tree and grid: highlight the folder under @@ -23,29 +30,32 @@ export async function getFilesFromDataTransfer( ): Promise { const results: DroppedFileItem[] = []; - async function traverseEntry(entry: FileSystemEntry, path = ""): Promise { + async function traverseEntry(entry: any, path = ""): Promise { + if (!entry) return; + if (entry.isFile) { - const fileEntry = entry as FileSystemFileEntry; await new Promise((resolve) => { - fileEntry.file((file) => { + entry.file((file: File) => { const relativePath = path ? `${path}/${file.name}` : file.name; - Object.defineProperty(file, "relativePath", { - value: relativePath, - writable: true, - configurable: true, - }); + try { + Object.defineProperty(file, "relativePath", { + value: relativePath, + writable: true, + configurable: true, + }); + } catch { + // Ignore if property cannot be redefined + } results.push({ file, relativePath }); resolve(); }); }); } else if (entry.isDirectory) { - const dirEntry = entry as FileSystemDirectoryEntry; - const dirReader = dirEntry.createReader(); - - let batch: FileSystemEntry[] = []; + const dirReader = entry.createReader(); + let batch: any[] = []; do { - batch = await new Promise((resolve) => { - dirReader.readEntries((entries) => resolve(entries || [])); + batch = await new Promise((resolve) => { + dirReader.readEntries((entries: any[]) => resolve(entries || [])); }); const folderPath = path ? `${path}/${entry.name}` : entry.name; @@ -58,7 +68,8 @@ export async function getFilesFromDataTransfer( const promises: Promise[] = []; for (let i = 0; i < items.length; i++) { - const entry = items[i].webkitGetAsEntry?.(); + const item = items[i] as any; + const entry = typeof item?.webkitGetAsEntry === "function" ? item.webkitGetAsEntry() : null; if (entry) { promises.push(traverseEntry(entry)); } @@ -81,7 +92,7 @@ async function extractFilesFromDataTransfer(dataTransfer: DataTransfer): Promise return Array.from(dataTransfer.files ?? []); } -export const isFileDrag = (e: React.DragEvent): boolean => +export const isFileDrag = (e: DragEvent): boolean => Array.from(e.dataTransfer?.types ?? []).includes("Files") export interface DriveDrop { @@ -91,15 +102,15 @@ export interface DriveDrop { hoverPath: string | null /** Handlers for a folder drop target — spring-loads into it, uploads on drop. */ folderDropProps: (path: string) => { - onDragEnter: (e: React.DragEvent) => void - onDragOver: (e: React.DragEvent) => void - onDrop: (e: React.DragEvent) => void + onDragEnter: (e: DragEvent) => void + onDragOver: (e: DragEvent) => void + onDrop: (e: DragEvent) => void } /** Handlers for the view container — clears the hover, uploads into `currentFolder` on drop. */ containerDropProps: (currentFolder: string) => { - onDragEnter: (e: React.DragEvent) => void - onDragOver: (e: React.DragEvent) => void - onDrop: (e: React.DragEvent) => void + onDragEnter: (e: DragEvent) => void + onDragOver: (e: DragEvent) => void + onDrop: (e: DragEvent) => void } } @@ -124,13 +135,12 @@ export function useDriveDrop({ springPath.current = null }, []) - // Window-level drag tracking for the overall `dragging` flag (depth counter absorbs the - // dragenter/leave flicker from moving across child elements). + // Window-level drag tracking for the overall `dragging` flag const depth = useRef(0) useEffect(() => { if (!enabled) return - const has = (e: DragEvent) => Array.from(e.dataTransfer?.types ?? []).includes("Files") - const onEnter = (e: DragEvent) => { + const has = (e: globalThis.DragEvent) => Array.from(e.dataTransfer?.types ?? []).includes("Files") + const onEnter = (e: globalThis.DragEvent) => { if (has(e)) { depth.current += 1 setDragging(true) @@ -174,21 +184,19 @@ export function useDriveDrop({ const folderDropProps = useCallback( (path: string) => ({ - // Folder targets stop propagation, so the container's onDragEnter only fires over empty - // space — which is how the hover clears when you move off a folder. - onDragEnter: (e: React.DragEvent) => { + onDragEnter: (e: DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() e.stopPropagation() setHoverPath(path) startSpring(path) }, - onDragOver: (e: React.DragEvent) => { + onDragOver: (e: DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() e.stopPropagation() }, - onDrop: async (e: React.DragEvent) => { + onDrop: async (e: DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() e.stopPropagation() @@ -205,15 +213,15 @@ export function useDriveDrop({ const containerDropProps = useCallback( (currentFolder: string) => ({ - onDragEnter: (e: React.DragEvent) => { + onDragEnter: (e: DragEvent) => { if (!isFileDrag(e)) return setHoverPath(null) clearSpring() }, - onDragOver: (e: React.DragEvent) => { + onDragOver: (e: DragEvent) => { if (isFileDrag(e)) e.preventDefault() }, - onDrop: async (e: React.DragEvent) => { + onDrop: async (e: DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() if (e.dataTransfer) { @@ -231,17 +239,12 @@ export function useDriveDrop({ } /** Handler props for a drop target — the shape both drop hooks hand to a host element. */ -export type FileDropProps = Pick< - React.HTMLAttributes, - "onDragOver" | "onDragLeave" | "onDrop" +export type FileDropProps = Partial< + Pick, "onDragOver" | "onDragLeave" | "onDrop"> > /** - * Drop-to-STAGE: the lighter sibling of {@link useDriveDrop} for the recents peeks (chat ContextRail / - * config StorageSection), which have no folder of their own. A file drag anywhere over the target - * highlights it, and a drop hands the files to `onFiles` (which stages them + opens the drawer where a - * destination is chosen). Pass a falsy `onFiles` to disable (e.g. no writable mount) — then `dropProps` - * is empty and nothing highlights. + * Drop-to-STAGE: the lighter sibling of {@link useDriveDrop} */ export function useStageDrop(onFiles: ((files: File[]) => void) | false | null | undefined): { dropActive: boolean @@ -252,13 +255,13 @@ export function useStageDrop(onFiles: ((files: File[]) => void) | false | null | return { dropActive, dropProps: { - onDragOver: (e) => { + onDragOver: (e: DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() setDropActive(true) }, onDragLeave: () => setDropActive(false), - onDrop: async (e) => { + onDrop: async (e: DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() setDropActive(false) From 488e10a30767bbeeb06d509e3644d57dbf81ab83 Mon Sep 17 00:00:00 2001 From: Pranjali2076 Date: Sun, 2 Aug 2026 21:46:06 +0530 Subject: [PATCH 3/4] fix(daytona): add retry with backoff to geesefs download (#5653) --- services/runner/images/sandbox/daytona/build_snapshot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/runner/images/sandbox/daytona/build_snapshot.py b/services/runner/images/sandbox/daytona/build_snapshot.py index dc09360788..25ceaf679d 100644 --- a/services/runner/images/sandbox/daytona/build_snapshot.py +++ b/services/runner/images/sandbox/daytona/build_snapshot.py @@ -114,8 +114,8 @@ def main() -> None: "RUN npm install -g typescript@5 ts-node@10 " "&& python3 --version " "&& echo 'const v: number = 1; console.log(v)' > /tmp/v.ts " - "&& ts-node /tmp/v.ts && rm /tmp/v.ts", - f"RUN curl -fsSL -o /usr/local/bin/geesefs {GEESEFS_URL} " + "&& ts-node /tmp/v.ts && rm /tmp/v.ts", + f"RUN curl -fsSL --retry 5 --retry-delay 2 --retry-all-errors -o /usr/local/bin/geesefs {GEESEFS_URL} " "&& chmod +x /usr/local/bin/geesefs", "USER sandbox", # Replace the base image's private Pi adapter. sandbox-agent resolves this launcher From 92365df4e63e4fdd519f52fbd79db679d27494f1 Mon Sep 17 00:00:00 2001 From: Pranjali2076 Date: Mon, 3 Aug 2026 09:48:43 +0530 Subject: [PATCH 4/4] fix: remove accidental change to useDriveDrop.ts --- web/oss/src/components/Drives/useDriveDrop.ts | 163 +++++------------- 1 file changed, 46 insertions(+), 117 deletions(-) diff --git a/web/oss/src/components/Drives/useDriveDrop.ts b/web/oss/src/components/Drives/useDriveDrop.ts index 293ec92e6b..c869638e79 100644 --- a/web/oss/src/components/Drives/useDriveDrop.ts +++ b/web/oss/src/components/Drives/useDriveDrop.ts @@ -1,98 +1,20 @@ -import { - useCallback, - useEffect, - useRef, - useState, - type DragEvent, - type HTMLAttributes, -} from "react" +import {useCallback, useEffect, useRef, useState} from "react" + +import {type DroppedFile, readDroppedFiles} from "./dropEntries" /** * Drag-and-drop upload behaviour shared by the drive's tree and grid: highlight the folder under * the cursor, spring-load into it after a short hover (drill to a nested destination without * dropping), and upload on drop — into the hovered folder, or the current folder for a background * drop. The views wire the returned handler props onto folder targets and their container. + * + * Every drop reads its files through {@link readDroppedFiles}, which walks dropped directories into + * their contents; each file carries the path it should keep under the destination folder. */ const SPRING_MS = 700 -export interface DroppedFileItem { - file: File; - relativePath: string; -} - -/** - * Recursively traverses dropped DataTransferItems to extract all files, - * walking subdirectories using webkitGetAsEntry. - */ -export async function getFilesFromDataTransfer( - items: DataTransferItemList -): Promise { - const results: DroppedFileItem[] = []; - - async function traverseEntry(entry: any, path = ""): Promise { - if (!entry) return; - - if (entry.isFile) { - await new Promise((resolve) => { - entry.file((file: File) => { - const relativePath = path ? `${path}/${file.name}` : file.name; - try { - Object.defineProperty(file, "relativePath", { - value: relativePath, - writable: true, - configurable: true, - }); - } catch { - // Ignore if property cannot be redefined - } - results.push({ file, relativePath }); - resolve(); - }); - }); - } else if (entry.isDirectory) { - const dirReader = entry.createReader(); - let batch: any[] = []; - do { - batch = await new Promise((resolve) => { - dirReader.readEntries((entries: any[]) => resolve(entries || [])); - }); - - const folderPath = path ? `${path}/${entry.name}` : entry.name; - for (const childEntry of batch) { - await traverseEntry(childEntry, folderPath); - } - } while (batch.length > 0); - } - } - - const promises: Promise[] = []; - for (let i = 0; i < items.length; i++) { - const item = items[i] as any; - const entry = typeof item?.webkitGetAsEntry === "function" ? item.webkitGetAsEntry() : null; - if (entry) { - promises.push(traverseEntry(entry)); - } - } - - await Promise.all(promises); - return results; -} - -/** - * Helper to safely extract files from DataTransfer, falling back to e.dataTransfer.files - */ -async function extractFilesFromDataTransfer(dataTransfer: DataTransfer): Promise { - if (dataTransfer.items && dataTransfer.items.length > 0) { - const droppedItems = await getFilesFromDataTransfer(dataTransfer.items); - if (droppedItems.length > 0) { - return droppedItems.map((item) => item.file); - } - } - return Array.from(dataTransfer.files ?? []); -} - -export const isFileDrag = (e: DragEvent): boolean => +export const isFileDrag = (e: React.DragEvent): boolean => Array.from(e.dataTransfer?.types ?? []).includes("Files") export interface DriveDrop { @@ -102,15 +24,15 @@ export interface DriveDrop { hoverPath: string | null /** Handlers for a folder drop target — spring-loads into it, uploads on drop. */ folderDropProps: (path: string) => { - onDragEnter: (e: DragEvent) => void - onDragOver: (e: DragEvent) => void - onDrop: (e: DragEvent) => void + onDragEnter: (e: React.DragEvent) => void + onDragOver: (e: React.DragEvent) => void + onDrop: (e: React.DragEvent) => void } /** Handlers for the view container — clears the hover, uploads into `currentFolder` on drop. */ containerDropProps: (currentFolder: string) => { - onDragEnter: (e: DragEvent) => void - onDragOver: (e: DragEvent) => void - onDrop: (e: DragEvent) => void + onDragEnter: (e: React.DragEvent) => void + onDragOver: (e: React.DragEvent) => void + onDrop: (e: React.DragEvent) => void } } @@ -121,7 +43,7 @@ export function useDriveDrop({ }: { /** False leaves the hook mounted but inert — no window listeners, nothing to hover. */ enabled?: boolean - onUpload: (files: File[], folder: string) => void + onUpload: (files: DroppedFile[], folder: string) => void onNavigate: (folder: string) => void }): DriveDrop { const [dragging, setDragging] = useState(false) @@ -135,12 +57,13 @@ export function useDriveDrop({ springPath.current = null }, []) - // Window-level drag tracking for the overall `dragging` flag + // Window-level drag tracking for the overall `dragging` flag (depth counter absorbs the + // dragenter/leave flicker from moving across child elements). const depth = useRef(0) useEffect(() => { if (!enabled) return - const has = (e: globalThis.DragEvent) => Array.from(e.dataTransfer?.types ?? []).includes("Files") - const onEnter = (e: globalThis.DragEvent) => { + const has = (e: DragEvent) => Array.from(e.dataTransfer?.types ?? []).includes("Files") + const onEnter = (e: DragEvent) => { if (has(e)) { depth.current += 1 setDragging(true) @@ -184,26 +107,27 @@ export function useDriveDrop({ const folderDropProps = useCallback( (path: string) => ({ - onDragEnter: (e: DragEvent) => { + // Folder targets stop propagation, so the container's onDragEnter only fires over empty + // space — which is how the hover clears when you move off a folder. + onDragEnter: (e: React.DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() e.stopPropagation() setHoverPath(path) startSpring(path) }, - onDragOver: (e: DragEvent) => { + onDragOver: (e: React.DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() e.stopPropagation() }, - onDrop: async (e: DragEvent) => { + onDrop: (e: React.DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() e.stopPropagation() - if (e.dataTransfer) { - const files = await extractFilesFromDataTransfer(e.dataTransfer) + void readDroppedFiles(e.dataTransfer).then((files) => { if (files.length) onUpload(files, path) - } + }) setHoverPath(null) clearSpring() }, @@ -213,21 +137,20 @@ export function useDriveDrop({ const containerDropProps = useCallback( (currentFolder: string) => ({ - onDragEnter: (e: DragEvent) => { + onDragEnter: (e: React.DragEvent) => { if (!isFileDrag(e)) return setHoverPath(null) clearSpring() }, - onDragOver: (e: DragEvent) => { + onDragOver: (e: React.DragEvent) => { if (isFileDrag(e)) e.preventDefault() }, - onDrop: async (e: DragEvent) => { + onDrop: (e: React.DragEvent) => { if (!isFileDrag(e)) return e.preventDefault() - if (e.dataTransfer) { - const files = await extractFilesFromDataTransfer(e.dataTransfer) + void readDroppedFiles(e.dataTransfer).then((files) => { if (files.length) onUpload(files, currentFolder) - } + }) setHoverPath(null) clearSpring() }, @@ -239,14 +162,21 @@ export function useDriveDrop({ } /** Handler props for a drop target — the shape both drop hooks hand to a host element. */ -export type FileDropProps = Partial< - Pick, "onDragOver" | "onDragLeave" | "onDrop"> +export type FileDropProps = Pick< + React.HTMLAttributes, + "onDragOver" | "onDragLeave" | "onDrop" > /** - * Drop-to-STAGE: the lighter sibling of {@link useDriveDrop} + * Drop-to-STAGE: the lighter sibling of {@link useDriveDrop} for the recents peeks (chat ContextRail / + * config StorageSection), which have no folder of their own. A file drag anywhere over the target + * highlights it, and a drop hands the files to `onFiles` (which stages them + opens the drawer where a + * destination is chosen). Pass a falsy `onFiles` to disable (e.g. no writable mount) — then `dropProps` + * is empty and nothing highlights. */ -export function useStageDrop(onFiles: ((files: File[]) => void) | false | null | undefined): { +export function useStageDrop( + onFiles: ((files: DroppedFile[]) => void) | false | null | undefined, +): { dropActive: boolean dropProps: FileDropProps } { @@ -255,21 +185,20 @@ export function useStageDrop(onFiles: ((files: File[]) => void) | false | null | return { dropActive, dropProps: { - onDragOver: (e: DragEvent) => { + onDragOver: (e) => { if (!isFileDrag(e)) return e.preventDefault() setDropActive(true) }, onDragLeave: () => setDropActive(false), - onDrop: async (e: DragEvent) => { + onDrop: (e) => { if (!isFileDrag(e)) return e.preventDefault() setDropActive(false) - if (e.dataTransfer) { - const files = await extractFilesFromDataTransfer(e.dataTransfer) + void readDroppedFiles(e.dataTransfer).then((files) => { if (files.length) onFiles(files) - } + }) }, }, } -} \ No newline at end of file +}