fix(tui): honor file reference boundaries

This commit is contained in:
Yichen Jiang
2026-07-23 19:21:02 +08:00
parent c37892f753
commit 01dcc7920c
4 changed files with 60 additions and 13 deletions
+20 -3
View File
@@ -6,7 +6,7 @@
* @module @deepseek-ai/dsh-tui/file-autocomplete
*/
import { readdir } from 'node:fs/promises'
import { lstat, readdir } from 'node:fs/promises'
import { isAbsolute, join, relative, resolve, sep } from 'node:path'
/** Default maximum file and directory candidates rendered for one query. */
@@ -205,7 +205,7 @@ export class WorkspaceFileSearch {
signal: AbortSignal,
): Promise<FileSearchCandidate[]> {
if (displayDirectory.split('/').some(segment => this.excludedDirectories.has(segment))) return []
const absolute = resolveDisplayDirectory(this.root, displayDirectory)
const absolute = await resolveDisplayDirectory(this.root, displayDirectory, signal)
if (absolute === undefined) return []
const entries = await readDirectory(absolute, signal)
const candidates: FileSearchCandidate[] = []
@@ -222,13 +222,30 @@ export class WorkspaceFileSearch {
}
}
function resolveDisplayDirectory(root: string, displayDirectory: string): string | undefined {
async function resolveDisplayDirectory(
root: string,
displayDirectory: string,
signal: AbortSignal,
): Promise<string | undefined> {
const resolvedRoot = resolve(root)
const absolute = resolve(resolvedRoot, displayDirectory === '' ? '.' : displayDirectory)
const fromRoot = relative(resolvedRoot, absolute)
if (fromRoot === '..' || fromRoot.startsWith(`..${sep}`)) return undefined
/* v8 ignore next -- only Windows can produce a cross-volume absolute relative path */
if (isAbsolute(fromRoot)) return undefined
let current = resolvedRoot
for (const segment of fromRoot.split(sep).filter(Boolean)) {
signal.throwIfAborted()
current = join(current, segment)
try {
const status = await lstat(current)
signal.throwIfAborted()
if (status.isSymbolicLink() || !status.isDirectory()) return undefined
} catch (_error: unknown) {
signal.throwIfAborted()
return undefined
}
}
return absolute
}
+1 -1
View File
@@ -2485,7 +2485,7 @@ export function createTuiChat(
// Tool visibility can change dynamically or by agent scope. Empty
// sections are omitted by renderPrompt, so guidance never names a tool
// that this agent cannot call.
text: () => agent.ctx.tools.get('read') === undefined ? '' : FILE_REFERENCE_PROMPT,
text: () => agent.ctx.tools.get('read', agent) === undefined ? '' : FILE_REFERENCE_PROMPT,
})
})