fix(desktop): recover from macOS directory permission failures

This commit is contained in:
deatheros
2026-08-07 01:49:44 +03:00
parent 7e0e22f6e2
commit d8518bf053
30 changed files with 497 additions and 106 deletions
+48 -45
View File
@@ -13,6 +13,7 @@ import type {
FilePartInput,
} from "@opencode-ai/sdk/v2";
import { isAmbiguousTransportFailure, markAmbiguousTransportFailure } from "@/lib/relay/transport-error";
import { FilesystemError, parseFilesystemErrorReason } from "@/lib/api/files-errors";
import type { PermissionRequest } from "@/types/permission";
import type { QuestionRequest } from "@/types/question";
@@ -1751,20 +1752,55 @@ class OpencodeService {
}
const task = (async () => {
const desktopFiles = getDesktopFilesApi();
if (desktopFiles) {
const desktopFiles = getDesktopFilesApi();
try {
const result = await desktopFiles.listDirectory(directoryPath || '', options);
if (!result || !Array.isArray(result.entries)) {
return [];
if (desktopFiles) {
const result = await desktopFiles.listDirectory(directoryPath || '', options);
if (!result || !Array.isArray(result.entries)) {
throw new FilesystemError('Directory listing returned an invalid response', {
reason: 'invalid-response',
});
}
const entries = result.entries.map<FilesystemEntry>((entry) => ({
name: entry.name,
path: normalizeFsPath(entry.path),
isDirectory: !!entry.isDirectory,
isFile: !entry.isDirectory,
isSymbolicLink: false,
}));
this.listDirectoryCache.set(cacheKey, {
entries,
expiresAt: Date.now() + FS_LIST_CACHE_TTL_MS,
});
return entries;
}
const entries = result.entries.map<FilesystemEntry>((entry) => ({
name: entry.name,
path: normalizeFsPath(entry.path),
isDirectory: !!entry.isDirectory,
isFile: !entry.isDirectory,
isSymbolicLink: false,
}));
const params = new URLSearchParams();
if (directoryPath && directoryPath.trim().length > 0) {
params.set('path', directoryPath);
}
if (options?.respectGitignore) {
params.set('respectGitignore', 'true');
}
const query = params.toString();
const response = await runtimeFetch(`${this.baseUrl}/fs/list${query ? `?${query}` : ''}`);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
const message = typeof error.error === 'string' ? error.error : 'Failed to list directory';
throw new FilesystemError(message, {
reason: parseFilesystemErrorReason((error as { reason?: unknown }).reason),
status: response.status,
});
}
const result = await response.json();
if (!result || !Array.isArray(result.entries)) {
throw new FilesystemError('Directory listing returned an invalid response', {
reason: 'invalid-response',
});
}
const entries = result.entries as FilesystemEntry[];
this.listDirectoryCache.set(cacheKey, {
entries,
expiresAt: Date.now() + FS_LIST_CACHE_TTL_MS,
@@ -1774,39 +1810,6 @@ class OpencodeService {
console.error('Failed to list directory contents:', error);
throw error;
}
}
try {
const params = new URLSearchParams();
if (directoryPath && directoryPath.trim().length > 0) {
params.set('path', directoryPath);
}
if (options?.respectGitignore) {
params.set('respectGitignore', 'true');
}
const query = params.toString();
const response = await runtimeFetch(`${this.baseUrl}/fs/list${query ? `?${query}` : ''}`);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
const message = typeof error.error === 'string' ? error.error : 'Failed to list directory';
throw new Error(message);
}
const result = await response.json();
if (!result || !Array.isArray(result.entries)) {
return [];
}
const entries = result.entries as FilesystemEntry[];
this.listDirectoryCache.set(cacheKey, {
entries,
expiresAt: Date.now() + FS_LIST_CACHE_TTL_MS,
});
return entries;
} catch (error) {
console.error('Failed to list directory contents:', error);
throw error;
}
})();
const trackedTask = task.finally(() => {