mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-11 21:02:12 +08:00
Compare commits
2 Commits
bb/main-lo
...
bb/main-vs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a48d54193 | ||
|
|
e167ed7bb1 |
@@ -41,7 +41,6 @@ const { adoptServedDashboardToken } = require('./dashboard-token.cjs')
|
||||
const { waitForDashboardPortAnnouncement } = require('./backend-ready.cjs')
|
||||
const { dashboardFallbackArgs, sourceDeclaresServe } = require('./backend-command.cjs')
|
||||
const { serializeJsonBody, setJsonRequestHeaders } = require('./oauth-net-request.cjs')
|
||||
const { fetchMarketplaceThemes, searchMarketplaceThemes } = require('./vscode-marketplace.cjs')
|
||||
const { buildDesktopBackendEnv, normalizeHermesHomeRoot } = require('./backend-env.cjs')
|
||||
const { readWindowsUserEnvVar } = require('./windows-user-env.cjs')
|
||||
const { readWslWindowsClipboardImage } = require('./wsl-clipboard-image.cjs')
|
||||
@@ -61,6 +60,8 @@ const { registerFsIpc } = require('./fs-ipc.cjs')
|
||||
const { registerTerminalIpc } = require('./terminal-ipc.cjs')
|
||||
const { registerUpdatesIpc } = require('./updates-ipc.cjs')
|
||||
const { registerLogsIpc } = require('./logs-ipc.cjs')
|
||||
const { registerProjectDirIpc } = require('./project-dir-ipc.cjs')
|
||||
const { registerVscodeThemeIpc } = require('./vscode-theme-ipc.cjs')
|
||||
const { OFFICIAL_REPO_HTTPS_URL, isOfficialSshRemote } = require('./update-remote.cjs')
|
||||
const { resolveBehindCount, shouldCountCommits } = require('./update-count.cjs')
|
||||
const { runRebuildWithRetry } = require('./update-rebuild.cjs')
|
||||
@@ -6663,42 +6664,14 @@ ipcMain.handle('hermes:openPreviewInBrowser', async (_event, url) => {
|
||||
// settings mount and seeds the value into the picker; writing back persists
|
||||
// it via writeDefaultProjectDir so resolveHermesCwd picks it up on the next
|
||||
// session spawn (no app restart needed).
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:get', async () => ({
|
||||
dir: readDefaultProjectDir(),
|
||||
defaultLabel: app.getPath('home'),
|
||||
resolvedCwd: resolveHermesCwd()
|
||||
}))
|
||||
|
||||
ipcMain.handle('hermes:workspace:sanitize', async (_event, cwd) => sanitizeWorkspaceCwd(cwd))
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:set', async (_event, dir) => {
|
||||
const next = typeof dir === 'string' && dir.trim() ? dir.trim() : null
|
||||
|
||||
if (next) {
|
||||
try {
|
||||
fs.mkdirSync(next, { recursive: true })
|
||||
} catch (error) {
|
||||
throw new Error(`Could not create directory: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
writeDefaultProjectDir(next)
|
||||
|
||||
return { dir: next }
|
||||
})
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:pick', async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
title: 'Choose default project directory',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
defaultPath: readDefaultProjectDir() || app.getPath('home')
|
||||
})
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return { canceled: true, dir: null }
|
||||
}
|
||||
|
||||
return { canceled: false, dir: result.filePaths[0] }
|
||||
// Default-project-dir + workspace settings IPC lives in project-dir-ipc.cjs;
|
||||
// config readers/writers + cwd resolvers are injected.
|
||||
registerProjectDirIpc({
|
||||
ipcMain,
|
||||
readDefaultProjectDir,
|
||||
resolveHermesCwd,
|
||||
sanitizeWorkspaceCwd,
|
||||
writeDefaultProjectDir
|
||||
})
|
||||
|
||||
ipcMain.handle('hermes:fetchLinkTitle', (_event, url) => fetchLinkTitle(url))
|
||||
@@ -7160,12 +7133,8 @@ ipcMain.handle('hermes:uninstall:run', async (_event, payload) => {
|
||||
return runDesktopUninstall(String(mode || ''))
|
||||
})
|
||||
|
||||
// Download a VS Code Marketplace extension and return the raw color-theme JSON
|
||||
// it contributes. No theme code is executed — we only read JSON from the .vsix.
|
||||
ipcMain.handle('hermes:vscode-theme:fetch', async (_event, id) => fetchMarketplaceThemes(String(id || '')))
|
||||
|
||||
// Search the Marketplace for color-theme extensions (empty query = top installs).
|
||||
ipcMain.handle('hermes:vscode-theme:search', async (_event, query) => searchMarketplaceThemes(String(query || ''), 20))
|
||||
// VS Code Marketplace theme IPC lives in vscode-theme-ipc.cjs.
|
||||
registerVscodeThemeIpc({ ipcMain })
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// hermes:// deep links (e.g. hermes://blueprint/morning-brief?time=08:00).
|
||||
|
||||
55
apps/desktop/electron/project-dir-ipc.cjs
Normal file
55
apps/desktop/electron/project-dir-ipc.cjs
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict'
|
||||
|
||||
const { app, dialog } = require('electron')
|
||||
const fs = require('fs')
|
||||
|
||||
// Default-project-directory + workspace-cwd settings IPC: read / write / native
|
||||
// directory picker, plus workspace-cwd sanitize. The config readers/writers and
|
||||
// cwd resolvers live in the main process and are injected.
|
||||
function registerProjectDirIpc({
|
||||
ipcMain,
|
||||
readDefaultProjectDir,
|
||||
resolveHermesCwd,
|
||||
sanitizeWorkspaceCwd,
|
||||
writeDefaultProjectDir
|
||||
}) {
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:get', async () => ({
|
||||
dir: readDefaultProjectDir(),
|
||||
defaultLabel: app.getPath('home'),
|
||||
resolvedCwd: resolveHermesCwd()
|
||||
}))
|
||||
|
||||
ipcMain.handle('hermes:workspace:sanitize', async (_event, cwd) => sanitizeWorkspaceCwd(cwd))
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:set', async (_event, dir) => {
|
||||
const next = typeof dir === 'string' && dir.trim() ? dir.trim() : null
|
||||
|
||||
if (next) {
|
||||
try {
|
||||
fs.mkdirSync(next, { recursive: true })
|
||||
} catch (error) {
|
||||
throw new Error(`Could not create directory: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
writeDefaultProjectDir(next)
|
||||
|
||||
return { dir: next }
|
||||
})
|
||||
|
||||
ipcMain.handle('hermes:setting:defaultProjectDir:pick', async () => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
title: 'Choose default project directory',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
defaultPath: readDefaultProjectDir() || app.getPath('home')
|
||||
})
|
||||
|
||||
if (result.canceled || result.filePaths.length === 0) {
|
||||
return { canceled: true, dir: null }
|
||||
}
|
||||
|
||||
return { canceled: false, dir: result.filePaths[0] }
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { registerProjectDirIpc }
|
||||
63
apps/desktop/electron/project-dir-ipc.test.cjs
Normal file
63
apps/desktop/electron/project-dir-ipc.test.cjs
Normal file
@@ -0,0 +1,63 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('node:assert/strict')
|
||||
const test = require('node:test')
|
||||
|
||||
const { registerProjectDirIpc } = require('./project-dir-ipc.cjs')
|
||||
|
||||
function fakeIpcMain() {
|
||||
const handlers = new Map()
|
||||
|
||||
return {
|
||||
handlers,
|
||||
handle(channel, handler) {
|
||||
assert.ok(!handlers.has(channel), `duplicate registration for ${channel}`)
|
||||
handlers.set(channel, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deps(overrides = {}) {
|
||||
return {
|
||||
readDefaultProjectDir: () => '/projects',
|
||||
resolveHermesCwd: () => '/cwd',
|
||||
sanitizeWorkspaceCwd: cwd => `safe:${cwd}`,
|
||||
writeDefaultProjectDir: () => {},
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
test('registerProjectDirIpc wires the project-dir + workspace settings channels', () => {
|
||||
const ipcMain = fakeIpcMain()
|
||||
|
||||
registerProjectDirIpc({ ipcMain, ...deps() })
|
||||
|
||||
assert.deepEqual([...ipcMain.handlers.keys()].sort(), [
|
||||
'hermes:setting:defaultProjectDir:get',
|
||||
'hermes:setting:defaultProjectDir:pick',
|
||||
'hermes:setting:defaultProjectDir:set',
|
||||
'hermes:workspace:sanitize'
|
||||
])
|
||||
})
|
||||
|
||||
// `get` / `pick` touch Electron's `app` / `dialog`, which are unavailable under
|
||||
// `node --test` (require('electron') is a path stub), so they're exercised in-app
|
||||
// only. The wiring of all four channels is covered by the surface test above.
|
||||
|
||||
test('set normalizes a blank dir to null and persists that (clears the override)', async () => {
|
||||
const ipcMain = fakeIpcMain()
|
||||
const writes = []
|
||||
|
||||
registerProjectDirIpc({ ipcMain, ...deps({ writeDefaultProjectDir: d => writes.push(d) }) })
|
||||
|
||||
assert.deepEqual(await ipcMain.handlers.get('hermes:setting:defaultProjectDir:set')({}, ' '), { dir: null })
|
||||
assert.deepEqual(writes, [null])
|
||||
})
|
||||
|
||||
test('workspace:sanitize delegates to the injected sanitizer', async () => {
|
||||
const ipcMain = fakeIpcMain()
|
||||
|
||||
registerProjectDirIpc({ ipcMain, ...deps() })
|
||||
|
||||
assert.equal(await ipcMain.handlers.get('hermes:workspace:sanitize')({}, '/x'), 'safe:/x')
|
||||
})
|
||||
19
apps/desktop/electron/vscode-theme-ipc.cjs
Normal file
19
apps/desktop/electron/vscode-theme-ipc.cjs
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
const { fetchMarketplaceThemes, searchMarketplaceThemes } = require('./vscode-marketplace.cjs')
|
||||
|
||||
// VS Code Marketplace color-theme IPC: fetch a theme by extension id + search the
|
||||
// marketplace. Both delegate to the vscode-marketplace sibling module; no theme
|
||||
// code is ever executed (only JSON is read from the .vsix).
|
||||
function registerVscodeThemeIpc({ ipcMain }) {
|
||||
// Download a VS Code Marketplace extension and return the raw color-theme JSON
|
||||
// it contributes. No theme code is executed — we only read JSON from the .vsix.
|
||||
ipcMain.handle('hermes:vscode-theme:fetch', async (_event, id) => fetchMarketplaceThemes(String(id || '')))
|
||||
|
||||
// Search the Marketplace for color-theme extensions (empty query = top installs).
|
||||
ipcMain.handle('hermes:vscode-theme:search', async (_event, query) =>
|
||||
searchMarketplaceThemes(String(query || ''), 20)
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = { registerVscodeThemeIpc }
|
||||
30
apps/desktop/electron/vscode-theme-ipc.test.cjs
Normal file
30
apps/desktop/electron/vscode-theme-ipc.test.cjs
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('node:assert/strict')
|
||||
const test = require('node:test')
|
||||
|
||||
const { registerVscodeThemeIpc } = require('./vscode-theme-ipc.cjs')
|
||||
|
||||
function fakeIpcMain() {
|
||||
const handlers = new Map()
|
||||
|
||||
return {
|
||||
handlers,
|
||||
handle(channel, handler) {
|
||||
assert.ok(!handlers.has(channel), `duplicate registration for ${channel}`)
|
||||
handlers.set(channel, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('registerVscodeThemeIpc wires only hermes:vscode-theme:* channels, each to a handler fn', () => {
|
||||
const ipcMain = fakeIpcMain()
|
||||
|
||||
registerVscodeThemeIpc({ ipcMain })
|
||||
|
||||
assert.deepEqual([...ipcMain.handlers.keys()].sort(), ['hermes:vscode-theme:fetch', 'hermes:vscode-theme:search'])
|
||||
|
||||
for (const handler of ipcMain.handlers.values()) {
|
||||
assert.equal(typeof handler, 'function')
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user