mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-20 10:11:58 +00:00
fix(desktop): tighten remote filesystem wiring
This commit is contained in:
parent
8878484f85
commit
56a0f48ba6
6 changed files with 95 additions and 4 deletions
|
|
@ -1,11 +1,50 @@
|
|||
import { act, cleanup, render } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { $connection } from '@/store/session'
|
||||
|
||||
import { PreviewPane } from './preview-pane'
|
||||
|
||||
describe('PreviewPane console state', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => window.setTimeout(() => callback(Date.now()), 0))
|
||||
vi.stubGlobal('cancelAnimationFrame', (id: number) => window.clearTimeout(id))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
$connection.set(null)
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('does not watch backend-only remote filesystem previews locally', () => {
|
||||
const watchPreviewFile = vi.fn(async () => ({ id: 'watch-1', path: '/remote/file.txt' }))
|
||||
const onPreviewFileChanged = vi.fn(() => vi.fn())
|
||||
$connection.set({ mode: 'remote' } as never)
|
||||
vi.stubGlobal('window', {
|
||||
...window,
|
||||
hermesDesktop: {
|
||||
onPreviewFileChanged,
|
||||
watchPreviewFile
|
||||
}
|
||||
})
|
||||
|
||||
render(
|
||||
<PreviewPane
|
||||
setTitlebarToolGroup={vi.fn()}
|
||||
target={{
|
||||
kind: 'file',
|
||||
label: 'file.txt',
|
||||
path: '/remote/file.txt',
|
||||
previewKind: 'text',
|
||||
source: '/remote/file.txt',
|
||||
url: 'file:///remote/file.txt'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(watchPreviewFile).not.toHaveBeenCalled()
|
||||
expect(onPreviewFileChanged).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not rebuild the pane titlebar group for streamed console logs', () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|||
import type { SetTitlebarToolGroup, TitlebarTool } from '@/app/shell/titlebar-controls'
|
||||
import { Tip } from '@/components/ui/tooltip'
|
||||
import { type Translations, useI18n } from '@/i18n'
|
||||
import { isDesktopFsRemoteMode } from '@/lib/desktop-fs'
|
||||
import { Bug } from '@/lib/icons'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { notify, notifyError } from '@/store/notifications'
|
||||
|
|
@ -406,6 +407,7 @@ export function PreviewPane({
|
|||
useEffect(() => {
|
||||
if (
|
||||
target.kind !== 'file' ||
|
||||
isDesktopFsRemoteMode() ||
|
||||
!window.hermesDesktop?.watchPreviewFile ||
|
||||
!window.hermesDesktop?.onPreviewFileChanged
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ async function gitignoreFor(dir: string) {
|
|||
let cached = gitignoreCache.get(key)
|
||||
|
||||
if (!cached) {
|
||||
cached = readGitignore(key)
|
||||
cached = readGitignore(clean(dir))
|
||||
gitignoreCache.set(key, cached)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
import { act, renderHook, waitFor } from '@testing-library/react'
|
||||
import { act, cleanup, renderHook, waitFor } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { $connection } from '@/store/session'
|
||||
import type { HermesReadDirResult } from '@/global'
|
||||
|
||||
import { clearProjectDirCache, readProjectDir } from './ipc'
|
||||
import { resetProjectTreeState, useProjectTree } from './use-project-tree'
|
||||
|
||||
const readDir = vi.fn<(path: string) => Promise<HermesReadDirResult>>()
|
||||
|
||||
beforeEach(() => {
|
||||
$connection.set(null)
|
||||
resetProjectTreeState()
|
||||
readDir.mockReset()
|
||||
;(window as unknown as { hermesDesktop: { readDir: typeof readDir } }).hermesDesktop = { readDir }
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
$connection.set(null)
|
||||
resetProjectTreeState()
|
||||
delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop
|
||||
})
|
||||
|
|
@ -106,6 +111,36 @@ describe('useProjectTree', () => {
|
|||
expect(readDir).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('reads gitignore from the real path while caching per connection', async () => {
|
||||
const readFileDataUrl = vi.fn(async () => `data:text/plain;base64,${btoa('ignored.log\n')}`)
|
||||
const gitRoot = vi.fn(async () => '/repo')
|
||||
readDir.mockImplementation(async path => {
|
||||
if (path === '/repo') return ok([{ name: '.gitignore', path: '/repo/.gitignore', isDirectory: false }])
|
||||
if (path === '/repo/src') {
|
||||
return ok([
|
||||
{ name: 'app.ts', path: '/repo/src/app.ts', isDirectory: false },
|
||||
{ name: 'ignored.log', path: '/repo/src/ignored.log', isDirectory: false }
|
||||
])
|
||||
}
|
||||
throw new Error(`unexpected path ${path}`)
|
||||
})
|
||||
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = { gitRoot, readDir, readFileDataUrl }
|
||||
|
||||
$connection.set({ baseUrl: 'local-a', mode: 'local' } as never)
|
||||
await expect(readProjectDir('/repo/src', '/repo')).resolves.toMatchObject({
|
||||
entries: [{ name: 'app.ts', path: '/repo/src/app.ts', isDirectory: false }]
|
||||
})
|
||||
expect(readDir).toHaveBeenCalledWith('/repo')
|
||||
expect(readDir).not.toHaveBeenCalledWith(expect.stringContaining('local-a'))
|
||||
|
||||
$connection.set({ baseUrl: 'local-b', mode: 'local' } as never)
|
||||
clearProjectDirCache()
|
||||
await expect(readProjectDir('/repo/src', '/repo')).resolves.toMatchObject({
|
||||
entries: [{ name: 'app.ts', path: '/repo/src/app.ts', isDirectory: false }]
|
||||
})
|
||||
expect(readDir.mock.calls.filter(([path]) => path === '/repo')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('captures per-folder error code and shows an error placeholder child', async () => {
|
||||
readDir.mockResolvedValueOnce(ok([{ name: 'priv', path: '/p/priv', isDirectory: true }]))
|
||||
readDir.mockResolvedValueOnce({ entries: [], error: 'EACCES' })
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ describe('desktop filesystem facade', () => {
|
|||
expect(gitRoot).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses the registered in-app picker in remote mode', async () => {
|
||||
it('uses the registered in-app directory picker in remote mode', async () => {
|
||||
const remoteSelect = vi.fn(async () => ['/remote/project'])
|
||||
$connection.set({ mode: 'remote' } as never)
|
||||
setDesktopFsRemotePicker({ selectPaths: remoteSelect })
|
||||
|
|
@ -97,4 +97,16 @@ describe('desktop filesystem facade', () => {
|
|||
expect(remoteSelect).toHaveBeenCalledWith({ defaultPath: '/remote', directories: true, multiple: false })
|
||||
expect(selectPaths).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not treat the remote directory picker as a general file picker', async () => {
|
||||
const remoteSelect = vi.fn(async () => ['/remote/project'])
|
||||
$connection.set({ mode: 'remote' } as never)
|
||||
setDesktopFsRemotePicker({ selectPaths: remoteSelect })
|
||||
|
||||
await expect(selectDesktopPaths({ directories: false, multiple: false })).resolves.toEqual([])
|
||||
await expect(selectDesktopPaths({ directories: true, multiple: true })).resolves.toEqual([])
|
||||
|
||||
expect(remoteSelect).not.toHaveBeenCalled()
|
||||
expect(selectPaths).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -80,5 +80,8 @@ export async function selectDesktopPaths(options?: HermesSelectPathsOptions): Pr
|
|||
if (!isDesktopFsRemoteMode()) {
|
||||
return desktop.selectPaths(options)
|
||||
}
|
||||
if (!options?.directories || options.multiple !== false) {
|
||||
return []
|
||||
}
|
||||
return remotePicker ? remotePicker.selectPaths(options) : []
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue