Download apps/desktop/src/contrib/runtime-loader.test.ts from SaylorTwift/hermes-agent: direct link, hf CLI and curl.
- Browser
- Download file 15 kB
-
https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/apps/desktop/src/contrib/runtime-loader.test.ts
- Command line
-
hf download hf://SaylorTwift/hermes-agent/apps/desktop/src/contrib/runtime-loader.test.ts
-
curl -L -o runtime-loader.test.ts https://huggingface.co/SaylorTwift/hermes-agent/resolve/main/apps/desktop/src/contrib/runtime-loader.test.ts
15 kB
| import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' | |
| import type { HermesReadDirResult } from '@/global' | |
| import type * as HermesModule from '@/hermes' | |
| import { $pluginRecords, publishPlugin, setPluginEnabled } from './plugins-store' | |
| import { discoverRuntimePlugins, loadRuntimePlugin, 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). | |
| const getStatus = vi.fn(async () => ({ hermes_home: '/remote/box/.hermes' })) | |
| vi.mock('@/hermes', async importActual => ({ | |
| ...(await importActual<typeof HermesModule>()), | |
| getStatus: () => getStatus() | |
| })) | |
| const desktopPluginsRoot = vi.fn<() => Promise<string>>() | |
| const readDir = vi.fn<(path: string) => Promise<HermesReadDirResult>>() | |
| const readFileText = vi.fn<(path: string) => Promise<{ text: string; truncated?: boolean }>>() | |
| const readPluginSource = vi.fn<(path: string) => Promise<{ text: string; truncated?: boolean }>>() | |
| const watchDirectory = vi.fn<(path: string) => Promise<{ id: string }>>() | |
| const watchPreviewFile = vi.fn<(path: string) => Promise<{ id: string }>>() | |
| const stopPreviewFileWatch = vi.fn<(id: string) => Promise<boolean>>() | |
| const onPreviewFileChanged = vi.fn() | |
| beforeEach(() => { | |
| desktopPluginsRoot.mockReset() | |
| readDir.mockReset() | |
| readFileText.mockReset() | |
| readPluginSource.mockReset() | |
| watchDirectory.mockReset() | |
| watchPreviewFile.mockReset() | |
| stopPreviewFileWatch.mockReset() | |
| stopPreviewFileWatch.mockResolvedValue(true) | |
| onPreviewFileChanged.mockReset() | |
| getStatus.mockClear() | |
| ;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = { | |
| desktopPluginsRoot, | |
| onPreviewFileChanged, | |
| readDir, | |
| readFileText, | |
| stopPreviewFileWatch, | |
| watchDirectory, | |
| watchPreviewFile | |
| } | |
| }) | |
| afterEach(() => { | |
| delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop | |
| }) | |
| describe('scanDiskPlugins (#66899)', () => { | |
| it('scans the Electron-resolved local roots, never the backend hermes_home', async () => { | |
| desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins') | |
| readDir.mockResolvedValue({ entries: [] }) | |
| await discoverRuntimePlugins() | |
| expect(desktopPluginsRoot).toHaveBeenCalled() | |
| expect(readDir).toHaveBeenCalledWith('/local/.hermes/desktop-plugins') | |
| // Unified halves are COPIED into the app root by Electron; the renderer | |
| // never scans the (profile-shaped) agent-plugins root itself. | |
| expect(readDir).not.toHaveBeenCalledWith('/local/.hermes/plugins') | |
| // The remote backend's hermes_home must never feed the local plugin scan. | |
| expect(getStatus).not.toHaveBeenCalled() | |
| expect(readDir).not.toHaveBeenCalledWith('/remote/box/.hermes/desktop-plugins') | |
| }) | |
| it('no-ops when the resolvers yield no local root', async () => { | |
| desktopPluginsRoot.mockResolvedValue('') | |
| await discoverRuntimePlugins() | |
| expect(readDir).not.toHaveBeenCalled() | |
| }) | |
| it('treats a folder without plugin.js as metadata, not a throwing file read', async () => { | |
| desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins') | |
| readDir.mockImplementation(async dir => { | |
| if (dir === '/local/.hermes/desktop-plugins') { | |
| return { | |
| entries: [{ isDirectory: true, name: 'my-feature', path: '/local/.hermes/desktop-plugins/my-feature' }] | |
| } | |
| } | |
| if (dir === '/local/.hermes/desktop-plugins/my-feature') { | |
| return { | |
| entries: [ | |
| { isDirectory: false, name: 'README.md', path: '/local/.hermes/desktop-plugins/my-feature/README.md' } | |
| ] | |
| } | |
| } | |
| return { entries: [] } | |
| }) | |
| await discoverRuntimePlugins() | |
| expect(readDir).toHaveBeenCalledWith('/local/.hermes/desktop-plugins/my-feature') | |
| expect(readFileText).not.toHaveBeenCalled() | |
| }) | |
| it('a DIRECTORY named plugin.js is not a plugin entry (metadata walk rejects it)', async () => { | |
| desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins') | |
| readDir.mockImplementation(async dir => { | |
| if (dir === '/local/.hermes/desktop-plugins') { | |
| return { entries: [{ isDirectory: true, name: 'odd', path: '/local/.hermes/desktop-plugins/odd' }] } | |
| } | |
| if (dir === '/local/.hermes/desktop-plugins/odd') { | |
| // A folder literally named plugin.js — must resolve to "no entry". | |
| return { | |
| entries: [{ isDirectory: true, name: 'plugin.js', path: '/local/.hermes/desktop-plugins/odd/plugin.js' }] | |
| } | |
| } | |
| return { entries: [] } | |
| }) | |
| await discoverRuntimePlugins() | |
| expect(readFileText).not.toHaveBeenCalled() | |
| expect($pluginRecords.get().odd).toBeUndefined() | |
| }) | |
| it('loads a unified desktop half (app-root copy + package marker) OPT-IN and tags it with its package', async () => { | |
| desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins') | |
| let desktopEntryPresent = true | |
| const root = '/local/.hermes/desktop-plugins' | |
| readDir.mockImplementation(async dir => { | |
| if (dir === root) { | |
| return { entries: desktopEntryPresent ? [{ isDirectory: true, name: 'uni', path: `${root}/uni` }] : [] } | |
| } | |
| if (dir === `${root}/uni`) { | |
| return { | |
| entries: [ | |
| { isDirectory: false, name: '.hermes-package.json', path: `${root}/uni/.hermes-package.json` }, | |
| { isDirectory: false, name: 'plugin.js', path: `${root}/uni/plugin.js` } | |
| ] | |
| } | |
| } | |
| return { entries: [] } | |
| }) | |
| const register = vi.fn() | |
| ;(globalThis as unknown as { __uniRegister: unknown }).__uniRegister = register | |
| readFileText.mockImplementation(async file => | |
| file.endsWith('.hermes-package.json') | |
| ? { text: JSON.stringify({ package: 'uni-pkg', source: '/x/plugins/uni-pkg/desktop', sourceMtimeMs: 1 }) } | |
| : { text: 'export default { id: "uni", register: globalThis.__uniRegister }' } | |
| ) | |
| watchPreviewFile.mockResolvedValue({ id: 'w-uni' }) | |
| // The loader evaluates plugins via blob-URL import(), which vite's module | |
| // runner can't resolve in tests — reroute to a data: URL, which node's | |
| // native ESM loader handles. | |
| const createObjectURL = vi | |
| .spyOn(URL, 'createObjectURL') | |
| .mockImplementation( | |
| blob => | |
| `data:text/javascript;base64,${Buffer.from((blob as unknown as { parts: string[] }).parts.join('')).toString('base64')}` | |
| ) | |
| const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => undefined) | |
| const RealBlob = globalThis.Blob | |
| vi.stubGlobal( | |
| 'Blob', | |
| class { | |
| parts: string[] | |
| constructor(parts: string[]) { | |
| this.parts = parts | |
| } | |
| } | |
| ) | |
| try { | |
| await discoverRuntimePlugins() | |
| // Inventoried for Capabilities → Plugins with its package identity, but | |
| // the unified posture wins: installed-but-inert until the user toggles. | |
| expect($pluginRecords.get().uni).toMatchObject({ kind: 'disk', status: 'disabled', packageName: 'uni-pkg' }) | |
| expect(register).not.toHaveBeenCalled() | |
| // The user's explicit enable still activates it. | |
| await setPluginEnabled('uni', true) | |
| expect(register).toHaveBeenCalledTimes(1) | |
| expect($pluginRecords.get().uni.status).toBe('loaded') | |
| // Electron removing the copy (package uninstalled) unloads the previous | |
| // Desktop registration instead of leaving a live ghost behind. | |
| desktopEntryPresent = false | |
| await discoverRuntimePlugins() | |
| expect($pluginRecords.get().uni).toBeUndefined() | |
| expect(stopPreviewFileWatch).toHaveBeenCalledWith('w-uni') | |
| } finally { | |
| createObjectURL.mockRestore() | |
| revokeObjectURL.mockRestore() | |
| vi.stubGlobal('Blob', RealBlob) | |
| delete (globalThis as unknown as { __uniRegister?: unknown }).__uniRegister | |
| } | |
| }) | |
| }) | |
| describe('watchRuntimePlugins dir watch (#66899)', () => { | |
| it('watches the Electron-resolved app 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 + startDirWatches chains. | |
| await vi.waitFor(() => expect(watchDirectory).toHaveBeenCalledTimes(1)) | |
| expect(watchDirectory).toHaveBeenCalledWith('/local/.hermes/desktop-plugins') | |
| expect(watchDirectory).not.toHaveBeenCalledWith('/remote/box/.hermes/desktop-plugins') | |
| expect(getStatus).not.toHaveBeenCalled() | |
| }) | |
| }) | |
| describe('plugin source reads (512 KiB preview-cap bug)', () => { | |
| const blobToDataUrl = () => { | |
| const createObjectURL = vi | |
| .spyOn(URL, 'createObjectURL') | |
| .mockImplementation( | |
| blob => | |
| `data:text/javascript;base64,${Buffer.from((blob as unknown as { parts: string[] }).parts.join('')).toString('base64')}` | |
| ) | |
| const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => undefined) | |
| const RealBlob = globalThis.Blob | |
| vi.stubGlobal( | |
| 'Blob', | |
| class { | |
| parts: string[] | |
| constructor(parts: string[]) { | |
| this.parts = parts | |
| } | |
| } | |
| ) | |
| return () => { | |
| createObjectURL.mockRestore() | |
| revokeObjectURL.mockRestore() | |
| vi.stubGlobal('Blob', RealBlob) | |
| } | |
| } | |
| /** Two-level standalone-root listing the metadata-walk probe needs: | |
| * the root lists the package folder, the folder lists plugin.js. */ | |
| const standaloneRootWith = (name: string) => { | |
| const folder = `/local/.hermes/desktop-plugins/${name}` | |
| readDir.mockImplementation(async dir => { | |
| if (dir === '/local/.hermes/desktop-plugins') { | |
| return { entries: [{ isDirectory: true, name, path: folder }] } | |
| } | |
| if (dir === folder) { | |
| return { entries: [{ isDirectory: false, name: 'plugin.js', path: `${folder}/plugin.js` }] } | |
| } | |
| return { entries: [] } | |
| }) | |
| } | |
| it('loads the full source via readPluginSource when the shell offers it', async () => { | |
| ;(window.hermesDesktop as unknown as { readPluginSource: unknown }).readPluginSource = readPluginSource | |
| desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins') | |
| standaloneRootWith('big') | |
| // The preview read would truncate this source — it must never be used. | |
| readFileText.mockResolvedValue({ text: '// first 512 KiB only', truncated: true }) | |
| const register = vi.fn() | |
| ;(globalThis as unknown as { __bigRegister: unknown }).__bigRegister = register | |
| readPluginSource.mockResolvedValue({ | |
| text: 'export default { id: "big", register: globalThis.__bigRegister }' | |
| }) | |
| watchPreviewFile.mockResolvedValue({ id: 'w-big' }) | |
| const restore = blobToDataUrl() | |
| try { | |
| await discoverRuntimePlugins() | |
| // The EVALUATED source came from the full read, not the truncated preview. | |
| expect(readPluginSource).toHaveBeenCalledWith('/local/.hermes/desktop-plugins/big/plugin.js') | |
| expect(register).toHaveBeenCalledTimes(1) | |
| expect($pluginRecords.get().big).toMatchObject({ kind: 'disk', status: 'loaded' }) | |
| } finally { | |
| restore() | |
| delete (globalThis as unknown as { __bigRegister?: unknown }).__bigRegister | |
| } | |
| }) | |
| it('older shell without readPluginSource: a truncated preview read fails LOUDLY, never evaluates', async () => { | |
| desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins') | |
| standaloneRootWith('huge') | |
| // 512 KiB window of a larger file — parses fine, but is NOT the plugin. | |
| readFileText.mockResolvedValue({ | |
| text: 'export default { id: "huge", register: () => { throw new Error("must never evaluate") } }', | |
| truncated: true | |
| }) | |
| watchPreviewFile.mockResolvedValue({ id: 'w-huge' }) | |
| const restore = blobToDataUrl() | |
| try { | |
| await discoverRuntimePlugins() | |
| // No live plugin — an error inventory row names the folder instead. | |
| expect($pluginRecords.get().huge).toMatchObject({ | |
| kind: 'disk', | |
| status: 'error', | |
| file: '/local/.hermes/desktop-plugins/huge/plugin.js' | |
| }) | |
| expect($pluginRecords.get().huge.error).toMatch(/512 KiB/) | |
| } finally { | |
| restore() | |
| } | |
| }) | |
| it('older shell, small plugin (not truncated): still loads through readFileText', async () => { | |
| desktopPluginsRoot.mockResolvedValue('/local/.hermes/desktop-plugins') | |
| standaloneRootWith('small') | |
| const register = vi.fn() | |
| ;(globalThis as unknown as { __smallRegister: unknown }).__smallRegister = register | |
| readFileText.mockResolvedValue({ | |
| text: 'export default { id: "small", register: globalThis.__smallRegister }' | |
| }) | |
| watchPreviewFile.mockResolvedValue({ id: 'w-small' }) | |
| const restore = blobToDataUrl() | |
| try { | |
| await discoverRuntimePlugins() | |
| expect(register).toHaveBeenCalledTimes(1) | |
| expect($pluginRecords.get().small).toMatchObject({ kind: 'disk', status: 'loaded' }) | |
| } finally { | |
| restore() | |
| delete (globalThis as unknown as { __smallRegister?: unknown }).__smallRegister | |
| } | |
| }) | |
| }) | |
| describe('bundled-shadowed disk copies', () => { | |
| it('skips a disk copy of a bundled plugin but publishes a visible inventory row', async () => { | |
| // The bundled twin is already registered (build-time glob). | |
| publishPlugin({ id: 'hermes-bots', name: 'Bot Mode', kind: 'bundled', status: 'loaded' }) | |
| // Same blob→data: URL reroute as the opt-in test above. | |
| const createObjectURL = vi | |
| .spyOn(URL, 'createObjectURL') | |
| .mockImplementation( | |
| blob => | |
| `data:text/javascript;base64,${Buffer.from((blob as unknown as { parts: string[] }).parts.join('')).toString('base64')}` | |
| ) | |
| const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => undefined) | |
| const RealBlob = globalThis.Blob | |
| vi.stubGlobal( | |
| 'Blob', | |
| class { | |
| parts: string[] | |
| constructor(parts: string[]) { | |
| this.parts = parts | |
| } | |
| } | |
| ) | |
| try { | |
| const id = await loadRuntimePlugin( | |
| 'export default { id: "hermes-bots", name: "Bot Mode", register() {} }', | |
| 'hermes-bots', | |
| { file: '/local/.hermes/desktop-plugins/hermes-bots/plugin.js' } | |
| ) | |
| // Skipped — the bundled copy stays the only live registration... | |
| expect(id).toBeNull() | |
| expect($pluginRecords.get()['hermes-bots']).toMatchObject({ kind: 'bundled', status: 'loaded' }) | |
| // ...but the stale folder is DISCOVERABLE: an inventory row names it, | |
| // carries its path (reveal/delete affordance), and can never activate. | |
| expect($pluginRecords.get()['hermes-bots:disk-shadowed']).toMatchObject({ | |
| kind: 'disk', | |
| status: 'disabled', | |
| file: '/local/.hermes/desktop-plugins/hermes-bots/plugin.js' | |
| }) | |
| } finally { | |
| createObjectURL.mockRestore() | |
| revokeObjectURL.mockRestore() | |
| vi.stubGlobal('Blob', RealBlob) | |
| } | |
| }) | |
| }) | |