fix(files): pass directory into context panel binary open guard

Scope context-file open validation to the active project directory so
binary opens resolve against the correct workspace root.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-07-30 08:22:10 +00:00
co-authored by Serhii Dziupin
parent 4f30a00814
commit d35cf957ac
5 changed files with 42 additions and 22 deletions
+25 -6
View File
@@ -2,11 +2,11 @@ import type { FilesAPI } from '@/lib/api/types';
import { MAX_OPEN_FILE_LINES, countLinesWithLimit } from '@/lib/fileOpenLimits';
import { getCurrentIntlLocale } from '@/lib/i18n';
import { formatMessage, useI18nStore } from '@/lib/i18n/store';
import { runtimeFetch } from '@/lib/runtime-fetch';
import { isBinaryFile, isImageFile, isPdfFile, looksLikeBinaryText } from '@/lib/toolHelpers';
const t = (key: Parameters<typeof formatMessage>[1], params?: Parameters<typeof formatMessage>[2]) =>
formatMessage(useI18nStore.getState().dictionary, key, params);
import { runtimeFetch } from '@/lib/runtime-fetch';
import { isBinaryFile, isImageFile, isPdfFile, looksLikeBinaryText } from '@/lib/toolHelpers';
export type ContextFileOpenFailureReason = 'too-large' | 'missing' | 'unreadable' | 'binary';
@@ -14,6 +14,10 @@ export type ContextFileOpenValidationResult =
| { ok: true }
| { ok: false; reason: ContextFileOpenFailureReason };
export type ContextFileOpenOptions = {
directory?: string;
};
const classifyReadError = (error: unknown): ContextFileOpenFailureReason => {
const message = error instanceof Error ? error.message : String(error ?? '');
const normalized = message.toLowerCase();
@@ -31,16 +35,27 @@ const classifyReadError = (error: unknown): ContextFileOpenFailureReason => {
return 'unreadable';
};
const readFileContent = async (files: FilesAPI, path: string): Promise<string> => {
const readFileContent = async (
files: FilesAPI,
path: string,
options?: ContextFileOpenOptions,
): Promise<string> => {
if (files.readFile) {
const result = await files.readFile(path, { optional: true });
const result = await files.readFile(path, {
optional: true,
directory: options?.directory,
});
return result.content ?? '';
}
const params = new URLSearchParams({ path, optional: 'true' });
if (options?.directory) {
params.set('directory', options.directory);
}
const response = await runtimeFetch(`/api/fs/read?${params.toString()}`, {
// Avoid conditional requests (304 + empty body).
cache: 'no-store',
headers: options?.directory ? { 'x-opencode-directory': options.directory } : undefined,
});
if (!response.ok) {
const errorPayload = await response.json().catch(() => ({ error: response.statusText }));
@@ -55,13 +70,17 @@ const readFileContent = async (files: FilesAPI, path: string): Promise<string> =
* Previewable/non-text binaries are allowed through so FilesView can show image/PDF
* preview or the cannot-preview empty state — never by decoding them as editable text here.
*/
export const validateContextFileOpen = async (files: FilesAPI, path: string): Promise<ContextFileOpenValidationResult> => {
export const validateContextFileOpen = async (
files: FilesAPI,
path: string,
options?: ContextFileOpenOptions,
): Promise<ContextFileOpenValidationResult> => {
if (isBinaryFile(path) || isPdfFile(path) || isImageFile(path)) {
return { ok: true };
}
try {
const content = await readFileContent(files, path);
const content = await readFileContent(files, path, options);
if (looksLikeBinaryText(content)) {
return { ok: false, reason: 'binary' };
}