fix(desktop): widen local plugin-root fix — profile-aware root + dir-watch path

Follow-ups on top of #66911's salvaged commit:
- hermes:fs:desktopPluginsRoot now resolves the ACTIVE desktop profile
  (readActiveDesktopProfile) so named profiles keep their own
  profiles/<name>/desktop-plugins root instead of sharing the global one
  (profile-scope concern raised on the PR thread).
- startDirWatch in runtime-loader.ts was a third sibling site still
  deriving the watch path from the backend's hermes_home (added by the
  later fs-watch commit); routed through the same Electron-local
  resolver, with regression coverage.
This commit is contained in:
Teknium 2026-07-28 23:20:37 -07:00
parent e614876c63
commit eaecca4a71
3 changed files with 42 additions and 5 deletions

View file

@ -10868,7 +10868,12 @@ ipcMain.handle('hermes:fs:openDir', async (_event, dirPath) => {
// on-disk plugin door silently breaks (#66899). Electron owns this resolution
// so it stays valid in every connection mode. Created on demand, like openDir.
ipcMain.handle('hermes:fs:desktopPluginsRoot', async () => {
const dir = path.join(HERMES_HOME, 'desktop-plugins')
// Profile-aware: a named Desktop profile gets its own plugin root under
// profiles/<name>/, matching the profile-scoped hermes_home the backend
// reported before this resolver existed. 'default'/unset pins the global root.
const profile = readActiveDesktopProfile()
const base = profile && profile !== 'default' ? path.join(HERMES_HOME, 'profiles', profile) : HERMES_HOME
const dir = path.join(base, 'desktop-plugins')
try {
await fs.promises.mkdir(dir, { recursive: true })

View file

@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { HermesReadDirResult } from '@/global'
import type * as HermesModule from '@/hermes'
import { discoverRuntimePlugins } from './runtime-loader'
import { discoverRuntimePlugins, watchRuntimePlugins } from './runtime-loader'
// getStatus would supply the connected backend's hermes_home — a REMOTE path in
// remote mode. The disk scanner must NOT derive the plugin root from it (#66899).
@ -16,12 +16,21 @@ vi.mock('@/hermes', async importActual => ({
const desktopPluginsRoot = vi.fn<() => Promise<string>>()
const readDir = vi.fn<(path: string) => Promise<HermesReadDirResult>>()
const watchDirectory = vi.fn<(path: string) => Promise<{ id: string }>>()
const onPreviewFileChanged = vi.fn()
beforeEach(() => {
desktopPluginsRoot.mockReset()
readDir.mockReset()
watchDirectory.mockReset()
onPreviewFileChanged.mockReset()
getStatus.mockClear()
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = { desktopPluginsRoot, readDir }
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
desktopPluginsRoot,
onPreviewFileChanged,
readDir,
watchDirectory
}
})
afterEach(() => {
@ -50,3 +59,19 @@ describe('scanDiskPlugins (#66899)', () => {
expect(readDir).not.toHaveBeenCalled()
})
})
describe('watchRuntimePlugins dir watch (#66899)', () => {
it('watches the Electron-resolved local root, never the backend hermes_home', async () => {
desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins')
readDir.mockResolvedValue({ entries: [] })
watchDirectory.mockResolvedValue({ id: 'watch-1' })
watchRuntimePlugins()
// Drain the async scan + startDirWatch chains.
await vi.waitFor(() => expect(watchDirectory).toHaveBeenCalled())
expect(watchDirectory).toHaveBeenCalledWith('/local/.hermes/desktop-plugins')
expect(watchDirectory).not.toHaveBeenCalledWith('/remote/box/.hermes/desktop-plugins')
expect(getStatus).not.toHaveBeenCalled()
})
})

View file

@ -350,8 +350,15 @@ export function watchRuntimePlugins(): void {
}
try {
const { hermes_home } = await getStatus()
dirWatchId = (await desktop.watchDirectory(`${hermes_home}/desktop-plugins`)).id
// Same Electron-local root as the scanner — never the backend's
// hermes_home, which is a remote path in remote mode (#66899).
const root = await desktop.desktopPluginsRoot?.()
if (!root) {
return false
}
dirWatchId = (await desktop.watchDirectory(root)).id
return true
} catch {