Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 15 additions & 56 deletions apps/server/src/routes/activity/history-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<string, unknown> | null;
} | null;
totalTokens: number;
createdAt: string;
updatedAt: string;
};
const childrenByParent = new Map<string, ChildAgent[]>();
const childrenByParent = new Map<string, HistoryChildAgent[]>();

if (parentIds.length > 0) {
const childResult = await deps.pool.query<
ChildAgent & { parentAgentId: string }
HistoryChildAgent & { parentAgentId: string }
>(
`SELECT
a.id,
Expand Down Expand Up @@ -290,24 +283,12 @@ async function handleHistoryAgentDetail(
feedbackResult,
messagesResult,
] = await Promise.all([
deps.pool.query<{
id: number;
event_type: string;
message: string;
metadata: Record<string, unknown>;
created_at: string;
}>(
deps.pool.query<HistoryEvent>(
`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<HistoryTokenTotals>(
`SELECT
COALESCE(SUM(input_tokens), 0) AS total_input,
COALESCE(SUM(cache_creation_tokens), 0) AS total_cache_creation,
Expand All @@ -317,42 +298,20 @@ 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<HistoryTokenByModel>(
`SELECT model,
SUM(input_tokens + cache_creation_tokens + cache_read_tokens) AS input_tokens,
SUM(output_tokens) AS output_tokens
FROM agent_token_usage WHERE agent_id = $1
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<HistoryMedia>(
`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<HistoryFeedbackItem>(
`SELECT f.id, r.reviewer_agent_id AS "agentId",
COALESCE(ra.persona, r.reviewer_type) AS persona,
'info' AS severity,
Expand Down
73 changes: 73 additions & 0 deletions apps/server/src/routes/activity/history-wire.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> | 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<string, unknown>;
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;
};
80 changes: 17 additions & 63 deletions apps/web/src/hooks/use-agent-history.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<string, unknown> | null;
} | null;
createdAt: string;
updatedAt: string;
export type {
HistoryChildAgent,
HistoryEvent,
HistoryFeedbackItem,
HistoryMedia,
HistoryTokenUsage,
};

export type HistoryAgent = {
Expand All @@ -34,12 +36,7 @@ export type HistoryAgent = {
cwd: string;
worktreePath: string | null;
worktreeBranch: string | null;
latestEvent: {
type: string;
message: string;
updatedAt: string;
metadata: Record<string, unknown> | null;
} | null;
latestEvent: HistoryLatestEvent | null;
gitContext: {
repoRoot: string;
branch: string;
Expand All @@ -62,49 +59,6 @@ export type HistoryAgentsResponse = {
offset: number;
};

export type HistoryEvent = {
id: number;
event_type: string;
message: string;
metadata: Record<string, unknown>;
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<HistoryAgent, "durationMs" | "totalTokens"> & {
pins: AgentPin[];
Expand Down
Loading