/** * System Router — health, status, systemHealthCheck */ import { z } from 'zod'; import { join } from 'node:path'; import { access } from 'node:fs/promises'; import type { ProcedureBuilder } from '../trpc.js'; import { requireAccountRepository, requireProjectRepository } from './_helpers.js'; import { checkAccountHealth } from '../../agent/accounts/usage.js'; import { getProjectCloneDir } from '../../git/project-clones.js'; export const healthResponseSchema = z.object({ status: z.literal('ok'), uptime: z.number().int().nonnegative(), processCount: z.number().int().nonnegative(), }); export type HealthResponse = z.infer; export const processInfoSchema = z.object({ id: z.string(), pid: z.number().int().positive(), command: z.string(), status: z.string(), startedAt: z.string(), }); export type ProcessInfo = z.infer; export const statusResponseSchema = z.object({ server: z.object({ startedAt: z.string(), uptime: z.number().int().nonnegative(), pid: z.number().int().positive(), }), processes: z.array(processInfoSchema), }); export type StatusResponse = z.infer; export function systemProcedures(publicProcedure: ProcedureBuilder) { return { health: publicProcedure .output(healthResponseSchema) .query(({ ctx }): HealthResponse => { const uptime = ctx.serverStartedAt ? Math.floor((Date.now() - ctx.serverStartedAt.getTime()) / 1000) : 0; return { status: 'ok', uptime, processCount: ctx.processCount, }; }), status: publicProcedure .output(statusResponseSchema) .query(({ ctx }): StatusResponse => { const uptime = ctx.serverStartedAt ? Math.floor((Date.now() - ctx.serverStartedAt.getTime()) / 1000) : 0; return { server: { startedAt: ctx.serverStartedAt?.toISOString() ?? '', uptime, pid: process.pid, }, processes: [], }; }), systemHealthCheck: publicProcedure .query(async ({ ctx }) => { const uptime = ctx.serverStartedAt ? Math.floor((Date.now() - ctx.serverStartedAt.getTime()) / 1000) : 0; const accountRepo = requireAccountRepository(ctx); const allAccounts = await accountRepo.findAll(); const allAgents = ctx.agentManager ? await ctx.agentManager.list() : []; const accounts = await Promise.all( allAccounts.map(account => checkAccountHealth(account, allAgents, ctx.credentialManager, ctx.workspaceRoot ?? undefined)) ); const projectRepo = requireProjectRepository(ctx); const allProjects = await projectRepo.findAll(); const projects = await Promise.all( allProjects.map(async project => { let repoExists = false; if (ctx.workspaceRoot) { const clonePath = join(ctx.workspaceRoot, getProjectCloneDir(project.name, project.id)); try { await access(clonePath); repoExists = true; } catch { repoExists = false; } } return { id: project.id, name: project.name, url: project.url, repoExists }; }) ); return { server: { status: 'ok' as const, uptime, startedAt: ctx.serverStartedAt?.toISOString() ?? null }, accounts, projects, }; }), }; }