mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(desktop): forward renderer console.* to desktop.log
Mirror all renderer console.log/warn/error/info/debug calls into desktop.log via IPC. A side-effect module (console-forward.ts) monkey- patches console.* at app init to send each call through a fire-and-forget ipcRenderer.send to the main process, which routes it through rememberLog() so the lines land in desktop.log alongside the main process's own [hermes] lines. Forwarded lines are prefixed [renderer:debug], [renderer:warn], [renderer:error], or [renderer:info]. Objects are JSON-serialized (capped at 4KB per line). The original console methods are preserved — devtools still shows the full interactive object inspection. This means the debug trace [trace:*] entries now land in desktop.log too, so you can read them with 'hermes logs desktop' without needing devtools open.
This commit is contained in:
parent
a71a90bc73
commit
4d81fea19e
5 changed files with 105 additions and 0 deletions
|
|
@ -9721,6 +9721,18 @@ ipcMain.handle('hermes:logs:reveal', async () => {
|
|||
|
||||
ipcMain.handle('hermes:logs:recent', async () => ({ path: DESKTOP_LOG_PATH, lines: hermesLog.slice(-200) }))
|
||||
|
||||
// Renderer console forwarding: the renderer monkey-patches console.* to send
|
||||
// each call here. We format and route through rememberLog() so they land in
|
||||
// desktop.log alongside the main process's own [hermes] lines.
|
||||
ipcMain.on('hermes:console:forward', (_event, { level, message }) => {
|
||||
const tag = level === 'error' ? '[renderer:error]'
|
||||
: level === 'warn' ? '[renderer:warn]'
|
||||
: level === 'info' ? '[renderer:info]'
|
||||
: '[renderer:debug]'
|
||||
|
||||
rememberLog(`${tag} ${message}`)
|
||||
})
|
||||
|
||||
function isExecutableFile(filePath) {
|
||||
if (!filePath || !path.isAbsolute(filePath)) {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ contextBridge.exposeInMainWorld('hermesDesktop', {
|
|||
},
|
||||
revealLogs: () => ipcRenderer.invoke('hermes:logs:reveal'),
|
||||
getRecentLogs: () => ipcRenderer.invoke('hermes:logs:recent'),
|
||||
forwardConsole: (level, message) => ipcRenderer.send('hermes:console:forward', { level, message }),
|
||||
readDir: dirPath => ipcRenderer.invoke('hermes:fs:readDir', dirPath),
|
||||
gitRoot: startPath => ipcRenderer.invoke('hermes:fs:gitRoot', startPath),
|
||||
revealPath: targetPath => ipcRenderer.invoke('hermes:fs:reveal', targetPath),
|
||||
|
|
|
|||
1
apps/desktop/src/global.d.ts
vendored
1
apps/desktop/src/global.d.ts
vendored
|
|
@ -116,6 +116,7 @@ declare global {
|
|||
}
|
||||
revealLogs: () => Promise<{ ok: boolean; path: string; error?: string }>
|
||||
getRecentLogs: () => Promise<{ path: string; lines: string[] }>
|
||||
forwardConsole: (level: string, message: string) => void
|
||||
readDir: (path: string) => Promise<HermesReadDirResult>
|
||||
gitRoot?: (path: string) => Promise<string | null>
|
||||
// Reveal a path in the OS file manager (Finder / Explorer).
|
||||
|
|
|
|||
89
apps/desktop/src/lib/console-forward.ts
Normal file
89
apps/desktop/src/lib/console-forward.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Console forwarder — mirrors renderer console.* calls into desktop.log via
|
||||
* IPC. Always on: every console.log/warn/error/info/debug call is forwarded
|
||||
* to the main process, which writes it through rememberLog() so it lands in
|
||||
* desktop.log alongside the main process's own [hermes] lines.
|
||||
*
|
||||
* Objects/arrays are JSON-serialized (best-effort); non-serializable values
|
||||
* fall back to their toString(). Multiple args are space-joined. This is a
|
||||
* one-way fire-and-forget (ipcRenderer.send, no await) so it never blocks the
|
||||
* renderer or affects call timing.
|
||||
*
|
||||
* The original console methods are preserved — devtools still shows the full
|
||||
* object inspection experience. This only adds a parallel write to desktop.log.
|
||||
*/
|
||||
|
||||
type ConsoleMethod = 'log' | 'warn' | 'error' | 'info' | 'debug'
|
||||
|
||||
const LEVEL_MAP: Record<ConsoleMethod, string> = {
|
||||
log: 'info',
|
||||
warn: 'warn',
|
||||
error: 'error',
|
||||
info: 'info',
|
||||
debug: 'debug'
|
||||
}
|
||||
|
||||
function serializeArg(arg: unknown): string {
|
||||
if (arg === null) {
|
||||
return 'null'
|
||||
}
|
||||
|
||||
if (arg === undefined) {
|
||||
return 'undefined'
|
||||
}
|
||||
|
||||
if (typeof arg === 'string') {
|
||||
return arg
|
||||
}
|
||||
|
||||
if (typeof arg === 'number' || typeof arg === 'boolean' || typeof arg === 'bigint') {
|
||||
return String(arg)
|
||||
}
|
||||
|
||||
if (arg instanceof Error) {
|
||||
return `${arg.name}: ${arg.message}${arg.stack ? `\n${arg.stack}` : ''}`
|
||||
}
|
||||
|
||||
// Objects/arrays — try JSON, fall back to String.
|
||||
try {
|
||||
return JSON.stringify(arg)
|
||||
} catch {
|
||||
try {
|
||||
return String(arg)
|
||||
} catch {
|
||||
return '[unserializable]'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function forward(level: ConsoleMethod, args: unknown[]): void {
|
||||
const forwarder = window.hermesDesktop?.forwardConsole
|
||||
|
||||
if (!forwarder) {
|
||||
return
|
||||
}
|
||||
|
||||
const message = args.map(serializeArg).join(' ')
|
||||
|
||||
// Cap at 4KB per line — a single huge object dump shouldn't flood desktop.log.
|
||||
const capped = message.length > 4096 ? `${message.slice(0, 4096)}…(${message.length} chars)` : message
|
||||
|
||||
forwarder(LEVEL_MAP[level], capped)
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
for (const method of Object.keys(LEVEL_MAP) as ConsoleMethod[]) {
|
||||
const original = console[method].bind(console)
|
||||
|
||||
console[method] = (...args: unknown[]) => {
|
||||
original(...args)
|
||||
|
||||
try {
|
||||
forward(method, args)
|
||||
} catch {
|
||||
// Forwarding must never break the original call or throw in the
|
||||
// renderer — it already ran above.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import './store/translucency'
|
|||
// Side-effect: attaches debug trace subscriptions (persistence, gateway
|
||||
// events, session switch watchers). No-ops entirely when tracing is disabled.
|
||||
import './lib/debug-trace'
|
||||
// Side-effect: mirrors renderer console.* calls into desktop.log via IPC.
|
||||
import './lib/console-forward'
|
||||
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { StrictMode } from 'react'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue