diff --git a/apps/desktop/electron/desktop-log-line.test.ts b/apps/desktop/electron/desktop-log-line.test.ts new file mode 100644 index 0000000000..4adca4c29d --- /dev/null +++ b/apps/desktop/electron/desktop-log-line.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' + +import { formatDesktopLogLine } from './desktop-log-line' + +describe('formatDesktopLogLine', () => { + it('prefixes each line with an ISO-8601 timestamp and the hermes tag', () => { + const line = formatDesktopLogLine('[boot] Resolving Hermes backend') + + // Shape contract (not a snapshot): every desktop log line starts with + // an ISO timestamp so multi-surface logs are chronologically readable. + // See #84405. + expect(line).toMatch( + /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[hermes\] \[boot\] Resolving Hermes backend$/ + ) + }) + + it('keeps the message verbatim after the prefix', () => { + const line = formatDesktopLogLine('Hermes backend exited (0)') + + expect(line).toMatch(/^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\] \[hermes\] Hermes backend exited \(0\)$/) + }) +}) diff --git a/apps/desktop/electron/desktop-log-line.ts b/apps/desktop/electron/desktop-log-line.ts new file mode 100644 index 0000000000..86db90149e --- /dev/null +++ b/apps/desktop/electron/desktop-log-line.ts @@ -0,0 +1,19 @@ +/** + * Desktop log line formatting shared by every desktop log surface: + * `desktop.log`, the in-app "RECENT LOGS" view, and crash forensics. + * + * Historically each line was prefixed with just `[hermes] `, so lines from + * different moments were indistinguishable. Every surface now carries an + * ISO-8601 UTC timestamp, matching the Python-side `agent.log` / + * `gateway.log` convention (`2026-07-12 16:22:17,540 INFO ...`). See #84405. + */ + +/** + * Format one desktop log line with an ISO-8601 UTC timestamp. + * + * `stamp` defaults to now; callers that batch multiple lines (a single + * stdout chunk) pass one shared stamp so the group reads as one event. + */ +export function formatDesktopLogLine(text: string, stamp = new Date().toISOString()): string { + return `[${stamp}] [hermes] ${text}` +} diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index f518d14605..1d843e8147 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -31,6 +31,7 @@ import { import nodePty from 'node-pty' import { classifyActiveRuntime } from './active-runtime-state' +import { formatDesktopLogLine } from './desktop-log-line' import { stopBackendChild as stopBackendChildImpl, stopBackendTreesForUpdate } from './backend-child' import { dashboardFallbackArgs, sourceDeclaresServe } from './backend-command' import { createBackendConnectionState } from './backend-connection-state' @@ -1347,7 +1348,10 @@ function rememberLog(chunk) { return } - const lines = text.split(/\r?\n/).map(line => `[hermes] ${line}`) + // One timestamp per chunk: lines arriving in the same event happened + // at the same moment. ISO-8601 UTC, matching agent.log/gateway.log. + const stamp = new Date().toISOString() + const lines = text.split(/\r?\n/).map(line => formatDesktopLogLine(line, stamp)) hermesLog.push(...lines) if (hermesLog.length > 300) {