diff --git a/apps/server/src/routes/activity/history-routes.ts b/apps/server/src/routes/activity/history-routes.ts index 8170f3e8..8febfae3 100644 --- a/apps/server/src/routes/activity/history-routes.ts +++ b/apps/server/src/routes/activity/history-routes.ts @@ -4,6 +4,14 @@ import { computeActivityStats, type ActivityEventRow, } from "../../activity-metrics.js"; +import type { + HistoryChildAgent, + HistoryEvent, + HistoryFeedbackItem, + HistoryMedia, + HistoryTokenByModel, + HistoryTokenTotals, +} from "./history-wire.js"; import type { ActivityRouteDeps } from "./shared.js"; async function handleHistoryProjects( @@ -168,26 +176,11 @@ async function handleHistoryAgents( ]); const parentIds = agentsResult.rows.map((agent: { id: string }) => agent.id); - type ChildAgent = { - id: string; - name: string; - persona: string | null; - status: string; - latestEvent: { - type: string; - message: string; - updatedAt: string; - metadata: Record | null; - } | null; - totalTokens: number; - createdAt: string; - updatedAt: string; - }; - const childrenByParent = new Map(); + const childrenByParent = new Map(); if (parentIds.length > 0) { const childResult = await deps.pool.query< - ChildAgent & { parentAgentId: string } + HistoryChildAgent & { parentAgentId: string } >( `SELECT a.id, @@ -290,24 +283,12 @@ async function handleHistoryAgentDetail( feedbackResult, messagesResult, ] = await Promise.all([ - deps.pool.query<{ - id: number; - event_type: string; - message: string; - metadata: Record; - created_at: string; - }>( + deps.pool.query( `SELECT id, event_type, message, metadata, created_at FROM agent_events WHERE agent_id = $1 ORDER BY created_at ASC`, [id] ), - deps.pool.query<{ - total_input: number; - total_cache_creation: number; - total_cache_read: number; - total_output: number; - total_messages: number; - }>( + deps.pool.query( `SELECT COALESCE(SUM(input_tokens), 0) AS total_input, COALESCE(SUM(cache_creation_tokens), 0) AS total_cache_creation, @@ -317,11 +298,7 @@ async function handleHistoryAgentDetail( FROM agent_token_usage WHERE agent_id = $1`, [id] ), - deps.pool.query<{ - model: string; - input_tokens: number; - output_tokens: number; - }>( + deps.pool.query( `SELECT model, SUM(input_tokens + cache_creation_tokens + cache_read_tokens) AS input_tokens, SUM(output_tokens) AS output_tokens @@ -329,30 +306,12 @@ async function handleHistoryAgentDetail( GROUP BY model ORDER BY (SUM(input_tokens + cache_creation_tokens + cache_read_tokens) + SUM(output_tokens)) DESC`, [id] ), - deps.pool.query<{ - file_name: string; - source: string; - size_bytes: number; - description: string | null; - created_at: string; - }>( + deps.pool.query( `SELECT file_name, source, size_bytes, description, created_at FROM media WHERE agent_id = $1 ORDER BY created_at`, [id] ), - deps.pool.query<{ - id: number; - agentId: string; - persona: string | null; - severity: string; - filePath: string | null; - lineNumber: number | null; - description: string; - suggestion: string | null; - mediaRef: string | null; - status: string; - createdAt: string; - }>( + deps.pool.query( `SELECT f.id, r.reviewer_agent_id AS "agentId", COALESCE(ra.persona, r.reviewer_type) AS persona, 'info' AS severity, diff --git a/apps/server/src/routes/activity/history-wire.ts b/apps/server/src/routes/activity/history-wire.ts new file mode 100644 index 00000000..326a7def --- /dev/null +++ b/apps/server/src/routes/activity/history-wire.ts @@ -0,0 +1,73 @@ +/** + * Wire types for the `/api/v1/history/*` routes. + * + * Kept dependency-free on purpose: apps/web imports these directly (type-only) + * so each response shape is declared once instead of being hand-mirrored in + * apps/web/src/hooks/use-agent-history.ts. + */ + +export type HistoryLatestEvent = { + type: string; + message: string; + updatedAt: string; + metadata: Record | null; +}; + +export type HistoryChildAgent = { + id: string; + name: string; + persona: string | null; + status: string; + latestEvent: HistoryLatestEvent | null; + totalTokens: number; + createdAt: string; + updatedAt: string; +}; + +export type HistoryEvent = { + id: number; + event_type: string; + message: string; + metadata: Record; + created_at: string; +}; + +export type HistoryTokenTotals = { + total_input: number; + total_cache_creation: number; + total_cache_read: number; + total_output: number; + total_messages: number; +}; + +export type HistoryTokenByModel = { + model: string; + input_tokens: number; + output_tokens: number; +}; + +export type HistoryTokenUsage = HistoryTokenTotals & { + by_model: HistoryTokenByModel[]; +}; + +export type HistoryMedia = { + file_name: string; + source: string; + size_bytes: number; + description: string | null; + created_at: string; +}; + +export type HistoryFeedbackItem = { + id: number; + agentId: string; + persona: string | null; + severity: string; + filePath: string | null; + lineNumber: number | null; + description: string; + suggestion: string | null; + mediaRef: string | null; + status: string; + createdAt: string; +}; diff --git a/apps/web/src/hooks/use-agent-history.ts b/apps/web/src/hooks/use-agent-history.ts index 2f85829b..b79eac5d 100644 --- a/apps/web/src/hooks/use-agent-history.ts +++ b/apps/web/src/hooks/use-agent-history.ts @@ -1,4 +1,14 @@ import { useQuery } from "@tanstack/react-query"; + +import type { + HistoryChildAgent, + HistoryEvent, + HistoryFeedbackItem, + HistoryLatestEvent, + HistoryMedia, + HistoryTokenUsage, +} from "../../../server/src/routes/activity/history-wire"; + import { api } from "@/lib/api"; import { getRangeBounds, type ActivityRange } from "@/hooks/use-activity"; import { type AgentPin } from "@/components/app/types"; @@ -10,20 +20,12 @@ const HISTORY_QUERY_OPTIONS = { // ── Types ────────────────────────────────────────────────────────── -export type HistoryChildAgent = { - id: string; - name: string; - persona: string | null; - status: string; - totalTokens: number; - latestEvent: { - type: string; - message: string; - updatedAt: string; - metadata: Record | null; - } | null; - createdAt: string; - updatedAt: string; +export type { + HistoryChildAgent, + HistoryEvent, + HistoryFeedbackItem, + HistoryMedia, + HistoryTokenUsage, }; export type HistoryAgent = { @@ -34,12 +36,7 @@ export type HistoryAgent = { cwd: string; worktreePath: string | null; worktreeBranch: string | null; - latestEvent: { - type: string; - message: string; - updatedAt: string; - metadata: Record | null; - } | null; + latestEvent: HistoryLatestEvent | null; gitContext: { repoRoot: string; branch: string; @@ -62,49 +59,6 @@ export type HistoryAgentsResponse = { offset: number; }; -export type HistoryEvent = { - id: number; - event_type: string; - message: string; - metadata: Record; - created_at: string; -}; - -export type HistoryTokenUsage = { - total_input: number; - total_cache_creation: number; - total_cache_read: number; - total_output: number; - total_messages: number; - by_model: Array<{ - model: string; - input_tokens: number; - output_tokens: number; - }>; -}; - -export type HistoryMedia = { - file_name: string; - source: string; - size_bytes: number; - description: string | null; - created_at: string; -}; - -export type HistoryFeedbackItem = { - id: number; - agentId: string; - persona: string | null; - severity: string; - filePath: string | null; - lineNumber: number | null; - description: string; - suggestion: string | null; - mediaRef: string | null; - status: string; - createdAt: string; -}; - export type HistoryAgentDetail = { agent: Omit & { pins: AgentPin[];