Function bodies 224 total
HeadlessOrchestrator.start method · typescript · L54-L130 (77 LOC)electron/headless/HeadlessOrchestrator.ts
start(payload: StartHeadlessRunPayload): HeadlessRun {
const runId = randomUUID().slice(0, 8)
const startedAt = new Date().toISOString()
const logPath = join(this.baseDir, `${runId}${LOG_EXTENSION}`)
const metaPath = join(this.baseDir, `${runId}${META_EXTENSION}`)
const state: HeadlessRun = {
id: runId,
prompt: payload.prompt,
projectDir: payload.projectDir,
provider: payload.provider,
model: payload.model,
reasoningEffort: payload.reasoningEffort,
resumeSessionId: payload.resumeSessionId ?? null,
status: 'running',
startedAt,
endedAt: null,
sessionId: payload.resumeSessionId ?? null,
error: null
}
const managed: ManagedHeadlessRun = {
state,
process: null,
logPath,
metaPath,
stdoutBuffer: '',
stderrBuffer: ''
}
this.runs.set(runId, managed)
writeFileSync(logPath, '', 'utf-8')
this.persistRun(managed)
const provider = getProvidHeadlessOrchestrator.writeFileSync method · typescript · L84-L125 (42 LOC)electron/headless/HeadlessOrchestrator.ts
writeFileSync(logPath, '', 'utf-8')
this.persistRun(managed)
const provider = getProvider(payload.provider)
const args = provider.buildHeadlessArgs(payload.model, payload.prompt, payload.resumeSessionId ?? null, payload.reasoningEffort)
try {
const child = spawn(provider.command, args, {
cwd: payload.projectDir,
env: {
...process.env,
FORCE_COLOR: '0'
}
})
managed.process = child
child.stdout.on('data', (chunk: Buffer | string) => {
this.handleStdout(managed, chunk.toString('utf-8'))
})
child.stderr.on('data', (chunk: Buffer | string) => {
const text = chunk.toString('utf-8')
managed.stderrBuffer += text
this.emitRunEvent({ runId, data: text })
})
child.on('exit', (code) => {
if (managed.state.status === 'canceled') {
this.finalizeRun(managed, 'canceled', null)
return
}
if (code === 0) {
HeadlessOrchestrator.list method · typescript · L132-L157 (26 LOC)electron/headless/HeadlessOrchestrator.ts
list(options: ListHeadlessRunsOptions = {}): HeadlessRun[] {
const query = options.query?.trim().toLowerCase()
const statusFilter = options.status && options.status !== 'all' ? options.status : null
const filtered = Array.from(this.runs.values())
.map((run) => ({ ...run.state }))
.filter((run) => {
if (statusFilter && run.status !== statusFilter) return false
if (!query) return true
return (
run.id.toLowerCase().includes(query) ||
run.prompt.toLowerCase().includes(query) ||
run.projectDir.toLowerCase().includes(query) ||
run.model.toLowerCase().includes(query) ||
run.status.toLowerCase().includes(query) ||
(run.sessionId ?? '').toLowerCase().includes(query) ||
(run.error ?? '').toLowerCase().includes(query)
)
})
.sort((a, b) => Date.parse(b.startedAt) - Date.parse(a.startedAt))
if (typeof options.limit === 'number' && options.limit > 0) {
HeadlessOrchestrator.getLog method · typescript · L164-L206 (43 LOC)electron/headless/HeadlessOrchestrator.ts
getLog(runId: string, options: HeadlessRunLogOptions = {}): HeadlessRunLogPayload | null {
const managed = this.runs.get(runId)
if (!managed) return null
if (!existsSync(managed.logPath)) {
return {
runId,
content: '',
totalLines: 0,
returnedLines: 0,
truncated: false
}
}
const tailLines =
options.tailLines && options.tailLines > 0
? Math.floor(options.tailLines)
: DEFAULT_LOG_TAIL_LINES
const maxChars =
options.maxChars && options.maxChars > 0
? Math.floor(options.maxChars)
: DEFAULT_LOG_MAX_CHARS
const raw = readFileSync(managed.logPath, 'utf-8')
const allLines = raw.length === 0 ? [] : raw.split('\n').filter((line) => line.length > 0)
const totalLines = allLines.length
const sliced = allLines.slice(Math.max(0, totalLines - tailLines))
let content = sliced.join('\n')
let truncated = totalLines > sliced.length
if (content.length > maxHeadlessOrchestrator.cancel method · typescript · L208-L223 (16 LOC)electron/headless/HeadlessOrchestrator.ts
cancel(runId: string): boolean {
const managed = this.runs.get(runId)
if (!managed || !managed.process) return false
managed.state.status = 'canceled'
this.persistRun(managed)
try {
if (process.platform === 'win32') {
managed.process.kill()
} else {
managed.process.kill('SIGTERM')
}
return true
} catch {
return false
}
}HeadlessOrchestrator.handleStdout method · typescript · L225-L236 (12 LOC)electron/headless/HeadlessOrchestrator.ts
private handleStdout(managed: ManagedHeadlessRun, data: string): void {
managed.stdoutBuffer += data
const lines = managed.stdoutBuffer.split('\n')
managed.stdoutBuffer = lines.pop() || ''
for (const line of lines) {
if (!line.trim()) continue
this.appendLog(managed, line)
this.captureSessionId(managed, line)
this.emitRunEvent({ runId: managed.state.id, data: line })
}
}HeadlessOrchestrator.captureSessionId method · typescript · L238-L252 (15 LOC)electron/headless/HeadlessOrchestrator.ts
private captureSessionId(managed: ManagedHeadlessRun, line: string): void {
try {
const parsed = JSON.parse(line) as Record<string, unknown>
const session =
(typeof parsed.session_id === 'string' && parsed.session_id) ||
(typeof parsed.sessionId === 'string' && parsed.sessionId) ||
null
if (session) {
managed.state.sessionId = session
this.persistRun(managed)
}
} catch {
// Non-JSON line, ignore.
}
}Repobility · code-quality intelligence · https://repobility.com
HeadlessOrchestrator.appendLog method · typescript · L254-L260 (7 LOC)electron/headless/HeadlessOrchestrator.ts
private appendLog(managed: ManagedHeadlessRun, line: string): void {
try {
appendFileSync(managed.logPath, `${line}\n`, 'utf-8')
} catch {
// Best effort log persistence.
}
}HeadlessOrchestrator.finalizeRun method · typescript · L262-L272 (11 LOC)electron/headless/HeadlessOrchestrator.ts
private finalizeRun(
managed: ManagedHeadlessRun,
status: HeadlessRunStatus,
error: string | null
): void {
managed.process = null
managed.state.status = status
managed.state.error = error
managed.state.endedAt = new Date().toISOString()
this.persistRun(managed)
}HeadlessOrchestrator.hydrateRunsFromDisk method · typescript · L278-L342 (65 LOC)electron/headless/HeadlessOrchestrator.ts
private hydrateRunsFromDisk(): void {
const entries = readdirSync(this.baseDir, { withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => entry.name)
const metaFiles = entries.filter((name) => name.endsWith(META_EXTENSION))
for (const metaName of metaFiles) {
const metaPath = join(this.baseDir, metaName)
try {
const raw = readFileSync(metaPath, 'utf-8')
const parsed = JSON.parse(raw) as Partial<PersistedHeadlessRunFile>
const run = parsed.run
if (!run || !this.isHeadlessRun(run)) continue
if (!run.provider) run.provider = 'claude'
const logPath = join(this.baseDir, `${run.id}${LOG_EXTENSION}`)
this.runs.set(run.id, {
state: run,
process: null,
logPath,
metaPath,
stdoutBuffer: '',
stderrBuffer: ''
})
} catch {
// Ignore malformed persisted entries.
}
}
const logFiles = entries.fiHeadlessOrchestrator.discoverSessionIdFromLog method · typescript · L344-L365 (22 LOC)electron/headless/HeadlessOrchestrator.ts
private discoverSessionIdFromLog(logPath: string): string | null {
try {
const raw = readFileSync(logPath, 'utf-8')
const lines = raw.split('\n')
for (const line of lines) {
if (!line.trim()) continue
try {
const parsed = JSON.parse(line) as Record<string, unknown>
const session =
(typeof parsed.session_id === 'string' && parsed.session_id) ||
(typeof parsed.sessionId === 'string' && parsed.sessionId) ||
null
if (session) return session
} catch {
continue
}
}
return null
} catch {
return null
}
}HeadlessOrchestrator.persistRun method · typescript · L367-L377 (11 LOC)electron/headless/HeadlessOrchestrator.ts
private persistRun(managed: ManagedHeadlessRun): void {
const payload: PersistedHeadlessRunFile = {
schemaVersion: META_SCHEMA_VERSION,
run: managed.state
}
try {
writeFileSync(managed.metaPath, JSON.stringify(payload, null, 2), 'utf-8')
} catch {
// Best effort persistence.
}
}HeadlessOrchestrator.isHeadlessRun method · typescript · L379-L405 (27 LOC)electron/headless/HeadlessOrchestrator.ts
private isHeadlessRun(value: unknown): value is HeadlessRun {
if (!value || typeof value !== 'object') return false
const run = value as Record<string, unknown>
const validStatus =
run.status === 'running' ||
run.status === 'completed' ||
run.status === 'errored' ||
run.status === 'canceled'
const validProviders = ['claude', 'codex']
const validProvider = run.provider === undefined || validProviders.includes(run.provider as string)
return (
typeof run.id === 'string' &&
typeof run.prompt === 'string' &&
typeof run.projectDir === 'string' &&
typeof run.model === 'string' &&
validProvider &&
(typeof run.resumeSessionId === 'string' || run.resumeSessionId === null) &&
validStatus &&
typeof run.startedAt === 'string' &&
(typeof run.endedAt === 'string' || run.endedAt === null) &&
(typeof run.sessionId === 'string' || run.sessionId === null) &&
(typeof run.error === 'stringcreateWindow function · typescript · L42-L95 (54 LOC)electron/main.ts
function createWindow(): void {
const isMac = process.platform === 'darwin'
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
show: false,
...(isMac
? {
titleBarStyle: 'hiddenInset' as const,
trafficLightPosition: { x: 16, y: 14 }
}
: {}),
backgroundColor: '#262624',
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: false // Required for node-pty IPC
}
})
mainWindow.on('ready-to-show', () => {
mainWindow?.show()
})
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url)
return { action: 'deny' }
})
// Load renderer
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
mainWsetupManagerWorkspace function · typescript · L4-L47 (44 LOC)electron/mcp/manager-workspace.ts
export function setupManagerWorkspace(baseDir: string, port: number): string {
const workspaceDir = join(baseDir, 'manager-workspace')
mkdirSync(workspaceDir, { recursive: true })
const mcpConfig = {
mcpServers: {
hydra: {
type: 'http',
url: `http://127.0.0.1:${port}/mcp`
}
}
}
writeFileSync(join(workspaceDir, '.mcp.json'), JSON.stringify(mcpConfig, null, 2))
const instructions = `# Hydra Manager Agent
You are a **manager agent** with access to orchestrate other Claude agents running inside Hydra.
## Available MCP Tools
| Tool | Purpose |
|------|---------|
| \`hydra_list_agents\` | List all agents with their current status |
| \`hydra_create_agent\` | Create a new agent in a project directory |
| \`hydra_send_prompt\` | Send a prompt to a specific agent |
| \`hydra_get_output\` | Get recent output from an agent's terminal buffer |
| \`hydra_broadcast\` | Send the same prompt to all agents in a project |
| \`hydra_kill_agent\` | KillPowered by Repobility — scan your code at https://repobility.com
HydraMcpServer.start method · typescript · L33-L59 (27 LOC)electron/mcp/McpServer.ts
async start(): Promise<void> {
const server = createServer((req, res) => {
this.handleHttp(req, res).catch((err) => {
console.error('[MCP] Request handler error:', err)
if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Internal server error' }))
}
})
})
this.port = await new Promise<number>((resolve, reject) => {
server.listen(0, '127.0.0.1', () => {
const addr = server.address()
if (addr && typeof addr === 'object') {
resolve(addr.port)
} else {
reject(new Error('Failed to get server address'))
}
})
server.on('error', reject)
})
this.httpServer = server
this.managerWorkspace = setupManagerWorkspace(this.userDataPath, this.port)
this.error = null
}HydraMcpServer.reject method · typescript · L50-L71 (22 LOC)electron/mcp/McpServer.ts
reject(new Error('Failed to get server address'))
}
})
server.on('error', reject)
})
this.httpServer = server
this.managerWorkspace = setupManagerWorkspace(this.userDataPath, this.port)
this.error = null
}
stop(): void {
for (const [, entry] of this.sessions) {
entry.transport.close().catch(() => {})
}
this.sessions.clear()
if (this.httpServer) {
this.httpServer.close()
this.httpServer = null
}
}HydraMcpServer.getStatus method · typescript · L73-L80 (8 LOC)electron/mcp/McpServer.ts
getStatus(): McpServerStatus {
return {
running: this.httpServer !== null,
port: this.port,
error: this.error,
managerWorkspace: this.managerWorkspace
}
}HydraMcpServer.handleHttp method · typescript · L82-L154 (73 LOC)electron/mcp/McpServer.ts
private async handleHttp(req: IncomingMessage, res: ServerResponse): Promise<void> {
const url = new URL(req.url ?? '/', `http://127.0.0.1:${this.port}`)
// ── Notification endpoints ──────────────────────────────────────────────
if (url.pathname === '/notifications/stream' && req.method === 'GET') {
this.handleNotificationStream(res)
return
}
if (url.pathname === '/notifications' && req.method === 'GET') {
const limitParam = url.searchParams.get('limit')
const limit = limitParam ? Math.min(Math.max(parseInt(limitParam, 10) || 50, 1), 200) : 50
const recent = this.notificationService?.getRecent(limit) ?? []
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(recent))
return
}
if (url.pathname !== '/mcp') {
res.writeHead(404, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Not found' }))
return
}
// Handle DELETE for session HydraMcpServer.createSession method · typescript · L156-L184 (29 LOC)electron/mcp/McpServer.ts
private async createSession(): Promise<SessionEntry> {
const server = new McpServer(
{ name: 'hydra', version: '0.1.0' },
{ capabilities: { logging: {} } }
)
this.registerTools(server)
const entry: SessionEntry = { server, transport: null as unknown as StreamableHTTPServerTransport }
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: true,
onsessioninitialized: (sid: string) => {
this.sessions.set(sid, entry)
}
})
transport.onclose = () => {
if (transport.sessionId) {
this.sessions.delete(transport.sessionId)
}
}
entry.transport = transport
await server.connect(transport)
return entry
}HydraMcpServer.async method · typescript · L191-L206 (16 LOC)electron/mcp/McpServer.ts
async () => {
const agents = this.agentManager.list()
const summary = agents.map((a) => ({
id: a.id,
name: a.name,
status: a.status,
model: a.model,
projectDir: a.projectDir,
isManager: a.isManager,
yolo: a.yolo,
sessionId: a.sessionId
}))
return {
content: [{ type: 'text' as const, text: JSON.stringify(summary, null, 2) }]
}
}HydraMcpServer.async method · typescript · L221-L256 (36 LOC)electron/mcp/McpServer.ts
async ({ name, projectDir, provider, model, reasoningEffort, initialPrompt, yolo }) => {
try {
const state = this.agentManager.create({
name,
projectDir,
provider,
model,
reasoningEffort,
yolo,
initialPrompt,
isManager: false // Prevent recursive managers
})
return {
content: [
{
type: 'text' as const,
text: `Agent created successfully:\n${JSON.stringify(
{ id: state.id, name: state.name, status: state.status, projectDir: state.projectDir },
null,
2
)}`
}
]
}
} catch (err) {
return {
content: [
{
type: 'text' as const,
text: `Failed to create agent: ${err instanceof Error ? err.message : String(err)}`
HydraMcpServer.async method · typescript · L266-L282 (17 LOC)electron/mcp/McpServer.ts
async ({ agentId, prompt }) => {
const success = this.agentManager.sendInput(agentId, prompt)
if (success) {
return {
content: [{ type: 'text' as const, text: `Prompt sent to agent ${agentId}` }]
}
}
return {
content: [
{
type: 'text' as const,
text: `Failed to send prompt to agent ${agentId}. Agent may not exist or not be running.`
}
],
isError: true
}
}Repobility · MCP-ready · https://repobility.com
HydraMcpServer.async method · typescript · L292-L321 (30 LOC)electron/mcp/McpServer.ts
async ({ agentId, lines }) => {
const buffer = this.agentManager.getBuffer(agentId)
if (buffer.length === 0) {
const agent = this.agentManager.get(agentId)
if (!agent) {
return {
content: [{ type: 'text' as const, text: `Agent ${agentId} not found` }],
isError: true
}
}
return {
content: [
{
type: 'text' as const,
text: `Agent ${agentId} (${agent.name}) has no output yet. Status: ${agent.status}`
}
]
}
}
const tail = buffer.slice(-lines)
return {
content: [
{
type: 'text' as const,
text: `Output from agent ${agentId} (last ${tail.length} of ${buffer.length} lines):\n\n${tail.join('\n')}`
}
]
}
}HydraMcpServer.async method · typescript · L331-L352 (22 LOC)electron/mcp/McpServer.ts
async ({ projectDir, prompt }) => {
const sentTo = this.agentManager.broadcast(projectDir, prompt)
if (sentTo.length === 0) {
return {
content: [
{
type: 'text' as const,
text: `No agents found for project directory: ${projectDir}`
}
],
isError: true
}
}
return {
content: [
{
type: 'text' as const,
text: `Broadcast sent to ${sentTo.length} agent(s): ${sentTo.join(', ')}`
}
]
}
}HydraMcpServer.async method · typescript · L361-L372 (12 LOC)electron/mcp/McpServer.ts
async ({ agentId }) => {
const killed = this.agentManager.kill(agentId)
if (killed) {
return {
content: [{ type: 'text' as const, text: `Kill signal sent to agent ${agentId}` }]
}
}
return {
content: [{ type: 'text' as const, text: `Agent ${agentId} not found` }],
isError: true
}
}HydraMcpServer.async method · typescript · L381-L397 (17 LOC)electron/mcp/McpServer.ts
async ({ agentId }) => {
const state = this.agentManager.restart(agentId)
if (state) {
return {
content: [
{
type: 'text' as const,
text: `Agent ${agentId} restarted. Status: ${state.status}, Restart count: ${state.restartCount}`
}
]
}
}
return {
content: [{ type: 'text' as const, text: `Agent ${agentId} not found` }],
isError: true
}
}HydraMcpServer.async method · typescript · L406-L411 (6 LOC)electron/mcp/McpServer.ts
async ({ limit }) => {
const notifications = this.notificationService?.getRecent(limit) ?? []
return {
content: [{ type: 'text' as const, text: JSON.stringify(notifications, null, 2) }]
}
}HydraMcpServer.handleNotificationStream method · typescript · L415-L430 (16 LOC)electron/mcp/McpServer.ts
private handleNotificationStream(res: ServerResponse): void {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
})
res.write(':\n\n') // SSE comment to establish connection
const unsubscribe = this.notificationService?.subscribe((notification) => {
res.write(`data: ${JSON.stringify(notification)}\n\n`)
})
res.on('close', () => {
unsubscribe?.()
})
}HydraMcpServer.readBody method · typescript · L432-L446 (15 LOC)electron/mcp/McpServer.ts
private readBody(req: IncomingMessage): Promise<unknown> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = []
req.on('data', (chunk: Buffer) => chunks.push(chunk))
req.on('end', () => {
try {
const raw = Buffer.concat(chunks).toString('utf-8')
resolve(raw ? JSON.parse(raw) : undefined)
} catch (err) {
reject(err)
}
})
req.on('error', reject)
})
}NotificationService.push method · typescript · L17-L46 (30 LOC)electron/notifications/NotificationService.ts
push(notification: HydraNotification): void {
this.recent.push(notification)
if (this.recent.length > MAX_RECENT) {
this.recent = this.recent.slice(-MAX_RECENT)
}
// Native OS notification
if (Notification.isSupported()) {
const native = new Notification({
title: notification.title,
body: notification.body,
silent: true
})
native.show()
}
// Broadcast to renderer windows
BrowserWindow.getAllWindows().forEach((win) => {
win.webContents.send(IPC.NOTIFICATION, notification)
})
// Broadcast to SSE subscribers
for (const sub of this.subscribers) {
try {
sub(notification)
} catch {
// Best-effort delivery
}
}
}Source: Repobility analyzer · https://repobility.com
NotificationService.subscribe method · typescript · L53-L58 (6 LOC)electron/notifications/NotificationService.ts
subscribe(callback: Subscriber): () => void {
this.subscribers.add(callback)
return () => {
this.subscribers.delete(callback)
}
}NotificationService.connectAgentEvents method · typescript · L60-L150 (91 LOC)electron/notifications/NotificationService.ts
connectAgentEvents(
agentManager: AgentManager,
headlessOrchestrator: HeadlessOrchestrator
): void {
agentManager.on('agent_waiting', (payload: { agentId: string }) => {
const agent = agentManager.get(payload.agentId)
const agentName = agent?.name ?? payload.agentId
this.push({
id: randomUUID().slice(0, 12),
type: 'agent_waiting',
title: 'Agent Finished',
body: `${agentName} is waiting for input`,
agentId: payload.agentId,
timestamp: new Date().toISOString()
})
})
agentManager.on('status', (payload: AgentStatusPayload) => {
// Deduplicate: skip if same agent already emitted this status
const prev = this.lastAgentStatus.get(payload.agentId)
if (prev === payload.status) return
this.lastAgentStatus.set(payload.agentId, payload.status)
const agent = agentManager.get(payload.agentId)
const agentName = agent?.name ?? payload.agentId
let type: NotificatLogger.constructor method · typescript · L34-L39 (6 LOC)electron/observability/Logger.ts
constructor(private readonly options: LoggerOptions) {
mkdirSync(options.logDir, { recursive: true })
this.logPath = join(options.logDir, 'hydra.log.jsonl')
this.maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES
this.maxRotatedFiles = options.maxRotatedFiles ?? DEFAULT_MAX_ROTATED_FILES
}Logger.mkdirSync method · typescript · L35-L43 (9 LOC)electron/observability/Logger.ts
mkdirSync(options.logDir, { recursive: true })
this.logPath = join(options.logDir, 'hydra.log.jsonl')
this.maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES
this.maxRotatedFiles = options.maxRotatedFiles ?? DEFAULT_MAX_ROTATED_FILES
}
getLogPath(): string {
return this.logPath
}Logger.getAllLogPathsNewestFirst method · typescript · L45-L51 (7 LOC)electron/observability/Logger.ts
getAllLogPathsNewestFirst(): string[] {
const files: string[] = [this.logPath]
for (let i = 1; i <= this.maxRotatedFiles; i++) {
files.push(`${this.logPath}.${i}`)
}
return files.filter((file) => existsSync(file))
}Logger.readRecentLines method · typescript · L53-L66 (14 LOC)electron/observability/Logger.ts
readRecentLines(maxLines = 3000): string[] {
const oldestToNewest = this.getAllLogPathsNewestFirst().reverse()
const lines: string[] = []
for (const file of oldestToNewest) {
const chunk = readFileSync(file, 'utf-8')
.split('\n')
.filter((line) => line.trim().length > 0)
lines.push(...chunk)
if (lines.length > maxLines * 2) {
lines.splice(0, lines.length - maxLines * 2)
}
}
return lines.slice(-maxLines)
}Logger.log method · typescript · L84-L102 (19 LOC)electron/observability/Logger.ts
log(input: ObservabilityLogEventPayload): StructuredLogRecord {
const record: StructuredLogRecord = {
ts: new Date().toISOString(),
level: input.level,
service: this.normalizeService(input.service),
event: input.event,
traceId: input.traceId?.trim() || randomUUID().slice(0, 12),
message: input.message,
agentId: input.agentId,
sessionId: input.sessionId,
projectId: input.projectId,
meta: this.normalizeMeta(input.meta)
}
this.rotateIfNeeded()
appendFileSync(this.logPath, `${JSON.stringify(record)}\n`, 'utf-8')
this.mirrorToConsole(record)
return record
}Logger.appendFileSync method · typescript · L99-L109 (11 LOC)electron/observability/Logger.ts
appendFileSync(this.logPath, `${JSON.stringify(record)}\n`, 'utf-8')
this.mirrorToConsole(record)
return record
}
private normalizeService(service: unknown): 'main' | 'renderer' | 'preload' {
if (service === 'renderer' || service === 'preload' || service === 'main') {
return service
}
return this.options.defaultService
}Repobility · code-quality intelligence · https://repobility.com
Logger.normalizeMeta method · typescript · L111-L118 (8 LOC)electron/observability/Logger.ts
private normalizeMeta(meta: unknown): Record<string, unknown> | undefined {
if (!meta || typeof meta !== 'object') return undefined
try {
return JSON.parse(JSON.stringify(meta)) as Record<string, unknown>
} catch {
return { parseError: 'meta-not-serializable' }
}
}Logger.rotateIfNeeded method · typescript · L120-L139 (20 LOC)electron/observability/Logger.ts
private rotateIfNeeded(): void {
if (!existsSync(this.logPath)) return
const size = statSync(this.logPath).size
if (size < this.maxFileBytes) return
const oldest = `${this.logPath}.${this.maxRotatedFiles}`
if (existsSync(oldest)) {
rmSync(oldest, { force: true })
}
for (let i = this.maxRotatedFiles - 1; i >= 1; i--) {
const current = `${this.logPath}.${i}`
const next = `${this.logPath}.${i + 1}`
if (existsSync(current)) {
renameSync(current, next)
}
}
renameSync(this.logPath, `${this.logPath}.1`)
}Logger.mirrorToConsole method · typescript · L141-L152 (12 LOC)electron/observability/Logger.ts
private mirrorToConsole(record: StructuredLogRecord): void {
const line = `[Hydra:${record.service}] ${record.event} ${record.message ?? ''}`.trim()
if (record.level === 'error') {
console.error(line)
return
}
if (record.level === 'warn') {
console.warn(line)
return
}
console.log(line)
}ObservabilityService.constructor method · typescript · L29-L34 (6 LOC)electron/observability/ObservabilityService.ts
constructor(private readonly context: ObservabilityContext) {
this.logger = new Logger({
logDir: join(app.getPath('userData'), 'logs'),
defaultService: 'main'
})
}ObservabilityService.installProcessHandlers method · typescript · L48-L71 (24 LOC)electron/observability/ObservabilityService.ts
installProcessHandlers(): void {
if (this.processHandlersInstalled) return
this.processHandlersInstalled = true
process.on('uncaughtException', (error) => {
this.logMain({
level: 'error',
event: 'process.uncaught-exception',
message: error.message,
meta: { stack: error.stack }
})
})
process.on('unhandledRejection', (reason) => {
const message = reason instanceof Error ? reason.message : String(reason)
const stack = reason instanceof Error ? reason.stack : undefined
this.logMain({
level: 'error',
event: 'process.unhandled-rejection',
message,
meta: { stack }
})
})
}ObservabilityService.exportDiagnostics method · typescript · L73-L133 (61 LOC)electron/observability/ObservabilityService.ts
async exportDiagnostics(): Promise<ExportDiagnosticsResult> {
const defaultPath = join(
app.getPath('documents'),
`hydra-diagnostics-${new Date().toISOString().replace(/[:.]/g, '-')}.json`
)
const ownerWindow = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0]
const saveResult = await dialog.showSaveDialog(ownerWindow ?? undefined, {
title: 'Export Hydra Diagnostics',
defaultPath,
filters: [{ name: 'JSON', extensions: ['json'] }]
})
if (saveResult.canceled || !saveResult.filePath) {
return { path: null, error: null }
}
try {
const config = this.context.getConfig()
const includeSensitive = config.includeSensitiveDiagnostics
const snapshot = {
generatedAt: new Date().toISOString(),
runtimeId: this.runtimeId,
app: {
version: app.getVersion(),
electron: process.versions.electron,
chrome: process.versions.chrome,
node: ObservabilityService.writeFileSync method · typescript · L116-L122 (7 LOC)electron/observability/ObservabilityService.ts
writeFileSync(saveResult.filePath, JSON.stringify(snapshot, null, 2), 'utf-8')
this.logMain({
level: 'info',
event: 'diagnostics.exported',
message: 'Diagnostics bundle exported',
meta: { filePath: saveResult.filePath, includeSensitive }
})ObservabilityService.sanitizeValue method · typescript · L135-L154 (20 LOC)electron/observability/ObservabilityService.ts
private sanitizeValue(value: unknown, includeSensitive: boolean, keyHint = ''): unknown {
if (value === null || value === undefined) return value
if (typeof value === 'string') {
if (!includeSensitive && SENSITIVE_KEY_PATTERN.test(keyHint)) {
return '[redacted]'
}
return value
}
if (Array.isArray(value)) {
return value.map((entry) => this.sanitizeValue(entry, includeSensitive, keyHint))
}
if (typeof value === 'object') {
const out: Record<string, unknown> = {}
for (const [key, next] of Object.entries(value as Record<string, unknown>)) {
out[key] = this.sanitizeValue(next, includeSensitive, key)
}
return out
}
return value
}Powered by Repobility — scan your code at https://repobility.com
ObservabilityService.reportIfNeeded method · typescript · L156-L203 (48 LOC)electron/observability/ObservabilityService.ts
private reportIfNeeded(record: StructuredLogRecord): void {
if (record.level !== 'error') return
if (this.isReporting) return
const config = this.context.getConfig()
const endpoint =
config.errorReportingEndpoint.trim() || process.env.HYDRA_ERROR_REPORT_ENDPOINT?.trim() || ''
if (!config.enableRemoteErrorReporting || !endpoint) return
const payload = {
runtimeId: this.runtimeId,
sentAt: new Date().toISOString(),
appVersion: app.getVersion(),
platform: process.platform,
arch: process.arch,
record: this.sanitizeValue(record, config.includeSensitiveDiagnostics)
}
this.isReporting = true
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 5000)
fetch(endpoint, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal
})
.then((response) => {
if (!respoObservabilityService.fetch method · typescript · L178-L191 (14 LOC)electron/observability/ObservabilityService.ts
fetch(endpoint, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload),
signal: controller.signal
})
.then((response) => {
if (!response.ok) {
this.logger.warn('remote-report.failed', {
service: 'main',
message: `Remote reporter returned ${response.status}`
})
}
})SessionCatalog.listSessions method · typescript · L59-L87 (29 LOC)electron/sessions/SessionCatalog.ts
listSessions(options: ListSessionOptions = {}): ClaudeSessionSummary[] {
const sessions = this.getSessionSnapshot(options.forceRefresh === true)
const hiddenIds = new Set(options.hiddenSessionIds ?? [])
const projectPrefix = options.projectPathPrefix?.trim()
const normalizedProjectPrefix = projectPrefix ? normalizePathForComparison(projectPrefix) : null
const cutoff =
typeof options.maxAgeDays === 'number' && options.maxAgeDays > 0
? Date.now() - options.maxAgeDays * 86_400_000
: 0
const filtered = sessions.filter((session) => {
if (hiddenIds.has(session.sessionId)) return false
if (normalizedProjectPrefix) {
const normalizedSessionPath = normalizePathForComparison(session.projectPath)
if (!normalizedSessionPath.startsWith(normalizedProjectPrefix)) {
return false
}
}
if (cutoff > 0 && Date.parse(session.modifiedAt) < cutoff) return false
return true
})
filtered.sort