Phase 2: Task Ingestion Pipeline (#459) (#460)
All checks were successful
ci/woodpecker/push/api Pipeline was successful
ci/woodpecker/push/web Pipeline was successful

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #460.
This commit is contained in:
2026-02-23 00:54:55 +00:00
committed by jason.woltje
parent 07f5225a76
commit 7581d26567
18 changed files with 1043 additions and 254 deletions

View File

@@ -1,5 +1,6 @@
import type { ReactElement } from "react";
import { Card, SectionHeader, Badge } from "@mosaic/ui";
import type { RecentActivity } from "@/lib/api/dashboard";
type BadgeVariantType =
| "badge-amber"
@@ -10,7 +11,7 @@ type BadgeVariantType =
| "badge-purple"
| "badge-pulse";
interface ActivityItem {
interface ActivityDisplayItem {
id: string;
icon: string;
iconBg: string;
@@ -18,82 +19,91 @@ interface ActivityItem {
highlight: string;
rest: string;
timestamp: string;
badge?: {
text: string;
variant: BadgeVariantType;
badge?:
| {
text: string;
variant: BadgeVariantType;
}
| undefined;
}
export interface ActivityFeedProps {
items?: RecentActivity[] | undefined;
}
/* ------------------------------------------------------------------ */
/* Mapping helpers */
/* ------------------------------------------------------------------ */
function getIconForAction(action: string): { icon: string; iconBg: string } {
const lower = action.toLowerCase();
if (lower.includes("complet") || lower.includes("finish") || lower.includes("success")) {
return { icon: "\u2713", iconBg: "rgba(20,184,166,0.15)" };
}
if (lower.includes("fail") || lower.includes("error")) {
return { icon: "\u2717", iconBg: "rgba(229,72,77,0.15)" };
}
if (lower.includes("warn") || lower.includes("limit")) {
return { icon: "\u26A0", iconBg: "rgba(245,158,11,0.15)" };
}
if (lower.includes("start") || lower.includes("creat")) {
return { icon: "\u2191", iconBg: "rgba(47,128,255,0.15)" };
}
if (lower.includes("update") || lower.includes("modif")) {
return { icon: "\u21BB", iconBg: "rgba(139,92,246,0.15)" };
}
return { icon: "\u2022", iconBg: "rgba(100,116,139,0.15)" };
}
function getBadgeForAction(action: string): ActivityDisplayItem["badge"] {
const lower = action.toLowerCase();
if (lower.includes("fail") || lower.includes("error")) {
return { text: "error", variant: "badge-red" };
}
if (lower.includes("warn") || lower.includes("limit")) {
return { text: "warn", variant: "badge-amber" };
}
return undefined;
}
function formatRelativeTime(isoDate: string): string {
const now = Date.now();
const then = new Date(isoDate).getTime();
const diffMs = now - then;
if (Number.isNaN(diffMs) || diffMs < 0) return "just now";
const minutes = Math.floor(diffMs / 60_000);
if (minutes < 1) return "just now";
if (minutes < 60) return `${String(minutes)}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${String(hours)}h ago`;
const days = Math.floor(hours / 24);
return `${String(days)}d ago`;
}
function mapActivityToDisplay(activity: RecentActivity): ActivityDisplayItem {
const { icon, iconBg } = getIconForAction(activity.action);
return {
id: activity.id,
icon,
iconBg,
title: "",
highlight: activity.entityType,
rest: ` ${activity.action} (${activity.entityId})`,
timestamp: formatRelativeTime(activity.createdAt),
badge: getBadgeForAction(activity.action),
};
}
const activityItems: ActivityItem[] = [
{
id: "act-1",
icon: "✓",
iconBg: "rgba(20,184,166,0.15)",
title: "",
highlight: "planner-agent",
rest: " completed task analysis for infra-refactor",
timestamp: "2m ago",
},
{
id: "act-2",
icon: "⚠",
iconBg: "rgba(245,158,11,0.15)",
title: "",
highlight: "executor-agent",
rest: " hit rate limit on Terraform API",
timestamp: "5m ago",
badge: { text: "warn", variant: "badge-amber" },
},
{
id: "act-3",
icon: "↑",
iconBg: "rgba(47,128,255,0.15)",
title: "",
highlight: "ORCH-002",
rest: " session started for api-v3-migration",
timestamp: "12m ago",
},
{
id: "act-4",
icon: "✗",
iconBg: "rgba(229,72,77,0.15)",
title: "",
highlight: "migrator-agent",
rest: " failed to connect to staging database",
timestamp: "18m ago",
badge: { text: "error", variant: "badge-red" },
},
{
id: "act-5",
icon: "✓",
iconBg: "rgba(20,184,166,0.15)",
title: "",
highlight: "reviewer-agent",
rest: " approved PR #214 in infra-refactor",
timestamp: "34m ago",
},
{
id: "act-6",
icon: "⟳",
iconBg: "rgba(139,92,246,0.15)",
title: "Token budget reset for ",
highlight: "gpt-4o",
rest: " model",
timestamp: "1h ago",
},
{
id: "act-7",
icon: "★",
iconBg: "rgba(20,184,166,0.15)",
title: "Project ",
highlight: "data-pipeline",
rest: " marked as completed",
timestamp: "2h ago",
},
];
/* ------------------------------------------------------------------ */
/* Components */
/* ------------------------------------------------------------------ */
interface ActivityItemRowProps {
item: ActivityItem;
item: ActivityDisplayItem;
}
function ActivityItemRow({ item }: ActivityItemRowProps): ReactElement {
@@ -155,14 +165,27 @@ function ActivityItemRow({ item }: ActivityItemRowProps): ReactElement {
);
}
export function ActivityFeed(): ReactElement {
export function ActivityFeed({ items }: ActivityFeedProps): ReactElement {
const displayItems = items ? items.map(mapActivityToDisplay) : [];
return (
<Card>
<SectionHeader title="Activity Feed" subtitle="Recent agent events" />
<div>
{activityItems.map((item) => (
<ActivityItemRow key={item.id} item={item} />
))}
{displayItems.length > 0 ? (
displayItems.map((item) => <ActivityItemRow key={item.id} item={item} />)
) : (
<div
style={{
padding: "24px 0",
textAlign: "center",
fontSize: "0.8rem",
color: "var(--muted)",
}}
>
No recent activity
</div>
)}
</div>
</Card>
);

View File

@@ -1,45 +1,69 @@
import type { ReactElement } from "react";
import { MetricsStrip, type MetricCell } from "@mosaic/ui";
import type { DashboardMetrics as DashboardMetricsData } from "@/lib/api/dashboard";
const cells: MetricCell[] = [
{
label: "Active Agents",
value: "47",
color: "var(--ms-blue-400)",
trend: { direction: "up", text: "↑ +3 from yesterday" },
},
{
label: "Tasks Completed",
value: "1,284",
color: "var(--ms-teal-400)",
trend: { direction: "up", text: "↑ +128 today" },
},
{
label: "Avg Response Time",
value: "2.4s",
color: "var(--ms-purple-400)",
trend: { direction: "down", text: "↓ -0.3s improved" },
},
{
label: "Token Usage",
value: "3.2M",
color: "var(--ms-amber-400)",
trend: { direction: "neutral", text: "78% of budget" },
},
{
label: "Error Rate",
value: "0.4%",
color: "var(--ms-red-400)",
trend: { direction: "down", text: "↓ -0.1% improved" },
},
{
label: "Active Projects",
value: "8",
color: "var(--ms-cyan-500)",
trend: { direction: "neutral", text: "2 deploying" },
},
export interface DashboardMetricsProps {
metrics?: DashboardMetricsData | undefined;
}
function formatNumber(n: number): string {
return n.toLocaleString();
}
function buildCells(metrics: DashboardMetricsData): MetricCell[] {
return [
{
label: "Active Agents",
value: formatNumber(metrics.activeAgents),
color: "var(--ms-blue-400)",
trend: { direction: "neutral", text: "currently active" },
},
{
label: "Tasks Completed",
value: formatNumber(metrics.tasksCompleted),
color: "var(--ms-teal-400)",
trend: { direction: "neutral", text: `of ${formatNumber(metrics.totalTasks)} total` },
},
{
label: "Total Tasks",
value: formatNumber(metrics.totalTasks),
color: "var(--ms-purple-400)",
trend: { direction: "neutral", text: "across workspace" },
},
{
label: "In Progress",
value: formatNumber(metrics.tasksInProgress),
color: "var(--ms-amber-400)",
trend: { direction: "neutral", text: "tasks running" },
},
{
label: "Error Rate",
value: `${String(metrics.errorRate)}%`,
color: "var(--ms-red-400)",
trend: {
direction: metrics.errorRate > 1 ? "up" : "down",
text: metrics.errorRate > 1 ? "above threshold" : "within threshold",
},
},
{
label: "Active Projects",
value: formatNumber(metrics.activeProjects),
color: "var(--ms-cyan-500)",
trend: { direction: "neutral", text: "in workspace" },
},
];
}
const EMPTY_CELLS: MetricCell[] = [
{ label: "Active Agents", value: "0", color: "var(--ms-blue-400)" },
{ label: "Tasks Completed", value: "0", color: "var(--ms-teal-400)" },
{ label: "Total Tasks", value: "0", color: "var(--ms-purple-400)" },
{ label: "In Progress", value: "0", color: "var(--ms-amber-400)" },
{ label: "Error Rate", value: "0%", color: "var(--ms-red-400)" },
{ label: "Active Projects", value: "0", color: "var(--ms-cyan-500)" },
];
export function DashboardMetrics(): ReactElement {
export function DashboardMetrics({ metrics }: DashboardMetricsProps): ReactElement {
const cells = metrics ? buildCells(metrics) : EMPTY_CELLS;
return <MetricsStrip cells={cells} />;
}

View File

@@ -3,6 +3,22 @@
import { useState } from "react";
import type { ReactElement } from "react";
import { Card, SectionHeader, Badge, Dot } from "@mosaic/ui";
import type { ActiveJob } from "@/lib/api/dashboard";
/* ------------------------------------------------------------------ */
/* Internal display types */
/* ------------------------------------------------------------------ */
type DotVariant = "teal" | "blue" | "amber" | "red" | "muted";
type BadgeVariant =
| "badge-teal"
| "badge-amber"
| "badge-red"
| "badge-blue"
| "badge-muted"
| "badge-purple"
| "badge-pulse";
interface AgentNode {
id: string;
@@ -10,7 +26,7 @@ interface AgentNode {
avatarColor: string;
name: string;
task: string;
status: "teal" | "blue" | "amber" | "red" | "muted";
status: DotVariant;
}
interface OrchestratorSession {
@@ -18,73 +34,94 @@ interface OrchestratorSession {
orchId: string;
name: string;
badge: string;
badgeVariant:
| "badge-teal"
| "badge-amber"
| "badge-red"
| "badge-blue"
| "badge-muted"
| "badge-purple"
| "badge-pulse";
badgeVariant: BadgeVariant;
duration: string;
agents: AgentNode[];
}
const sessions: OrchestratorSession[] = [
{
id: "s1",
orchId: "ORCH-001",
name: "infra-refactor",
badge: "running",
badgeVariant: "badge-teal",
duration: "2h 14m",
agents: [
{
id: "a1",
initials: "PL",
avatarColor: "rgba(47,128,255,0.15)",
name: "planner-agent",
task: "Analyzing network topology",
status: "blue",
},
{
id: "a2",
initials: "EX",
avatarColor: "rgba(20,184,166,0.15)",
name: "executor-agent",
task: "Applying Terraform modules",
status: "teal",
},
{
id: "a3",
initials: "QA",
avatarColor: "rgba(245,158,11,0.15)",
name: "reviewer-agent",
task: "Waiting for executor output",
status: "amber",
},
],
},
{
id: "s2",
orchId: "ORCH-002",
name: "api-v3-migration",
badge: "running",
badgeVariant: "badge-teal",
duration: "45m",
agents: [
{
id: "a4",
initials: "MG",
avatarColor: "rgba(139,92,246,0.15)",
name: "migrator-agent",
task: "Rewriting endpoint handlers",
status: "blue",
},
],
},
export interface OrchestratorSessionsProps {
jobs?: ActiveJob[] | undefined;
}
/* ------------------------------------------------------------------ */
/* Mapping helpers */
/* ------------------------------------------------------------------ */
const STEP_COLORS: string[] = [
"rgba(47,128,255,0.15)",
"rgba(20,184,166,0.15)",
"rgba(245,158,11,0.15)",
"rgba(139,92,246,0.15)",
"rgba(229,72,77,0.15)",
];
function statusToDotVariant(status: string): DotVariant {
const lower = status.toLowerCase();
if (lower === "running" || lower === "active" || lower === "completed") return "teal";
if (lower === "pending" || lower === "queued") return "blue";
if (lower === "waiting" || lower === "paused") return "amber";
if (lower === "failed" || lower === "error") return "red";
return "muted";
}
function statusToBadgeVariant(status: string): BadgeVariant {
const lower = status.toLowerCase();
if (lower === "running" || lower === "active") return "badge-teal";
if (lower === "pending" || lower === "queued") return "badge-blue";
if (lower === "waiting" || lower === "paused") return "badge-amber";
if (lower === "failed" || lower === "error") return "badge-red";
if (lower === "completed") return "badge-purple";
return "badge-muted";
}
function formatDuration(isoDate: string): string {
const now = Date.now();
const start = new Date(isoDate).getTime();
const diffMs = now - start;
if (Number.isNaN(diffMs) || diffMs < 0) return "0m";
const totalMinutes = Math.floor(diffMs / 60_000);
if (totalMinutes < 60) return `${String(totalMinutes)}m`;
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return `${String(hours)}h ${String(minutes)}m`;
}
function initials(name: string): string {
return name
.split(/[\s\-_]+/)
.slice(0, 2)
.map((w) => w.charAt(0).toUpperCase())
.join("");
}
function mapJobToSession(job: ActiveJob): OrchestratorSession {
const agents: AgentNode[] = job.steps.map((step, idx) => ({
id: step.id,
initials: initials(step.name),
avatarColor: STEP_COLORS[idx % STEP_COLORS.length] ?? "rgba(100,116,139,0.15)",
name: step.name,
task: `Phase: ${step.phase}`,
status: statusToDotVariant(step.status),
}));
return {
id: job.id,
orchId: job.id.length > 10 ? job.id.slice(0, 10).toUpperCase() : job.id.toUpperCase(),
name: job.type,
badge: job.status,
badgeVariant: statusToBadgeVariant(job.status),
duration: formatDuration(job.createdAt),
agents,
};
}
/* ------------------------------------------------------------------ */
/* Sub-components */
/* ------------------------------------------------------------------ */
interface AgentNodeItemProps {
agent: AgentNode;
}
@@ -182,7 +219,7 @@ function OrchCard({ session }: OrchCardProps): ReactElement {
marginBottom: 10,
}}
>
<Dot variant="teal" />
<Dot variant={statusToDotVariant(session.badge)} />
<span
style={{
fontFamily: "var(--mono)",
@@ -223,18 +260,48 @@ function OrchCard({ session }: OrchCardProps): ReactElement {
);
}
export function OrchestratorSessions(): ReactElement {
/* ------------------------------------------------------------------ */
/* Main export */
/* ------------------------------------------------------------------ */
export function OrchestratorSessions({ jobs }: OrchestratorSessionsProps): ReactElement {
const sessions = jobs ? jobs.map(mapJobToSession) : [];
const activeCount = jobs
? jobs.filter(
(j) => j.status.toLowerCase() === "running" || j.status.toLowerCase() === "active"
).length
: 0;
return (
<Card>
<SectionHeader
title="Active Orchestrator Sessions"
subtitle="3 of 8 projects running"
actions={<Badge variant="badge-teal">3 active</Badge>}
subtitle={
sessions.length > 0
? `${String(activeCount)} of ${String(sessions.length)} jobs running`
: "No active sessions"
}
actions={
sessions.length > 0 ? (
<Badge variant="badge-teal">{String(activeCount)} active</Badge>
) : undefined
}
/>
<div>
{sessions.map((session) => (
<OrchCard key={session.id} session={session} />
))}
{sessions.length > 0 ? (
sessions.map((session) => <OrchCard key={session.id} session={session} />)
) : (
<div
style={{
padding: "24px 0",
textAlign: "center",
fontSize: "0.8rem",
color: "var(--muted)",
}}
>
No active sessions
</div>
)}
</div>
</Card>
);

View File

@@ -1,7 +1,24 @@
import type { ReactElement } from "react";
import { Card, SectionHeader, ProgressBar, type ProgressBarVariant } from "@mosaic/ui";
import type { TokenBudgetEntry } from "@/lib/api/dashboard";
interface ModelBudget {
export interface TokenBudgetProps {
budgets?: TokenBudgetEntry[] | undefined;
}
/* ------------------------------------------------------------------ */
/* Helpers */
/* ------------------------------------------------------------------ */
const VARIANT_CYCLE: ProgressBarVariant[] = ["blue", "teal", "purple", "amber"];
function formatTokenCount(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(0)}K`;
return String(n);
}
interface ModelBudgetDisplay {
id: string;
label: string;
usage: string;
@@ -9,39 +26,28 @@ interface ModelBudget {
variant: ProgressBarVariant;
}
const models: ModelBudget[] = [
{
id: "sonnet",
label: "claude-3-5-sonnet",
usage: "2.1M / 3M",
value: 70,
variant: "blue",
},
{
id: "haiku",
label: "claude-3-haiku",
usage: "890K / 5M",
value: 18,
variant: "teal",
},
{
id: "gpt4o",
label: "gpt-4o",
usage: "320K / 1M",
value: 32,
variant: "purple",
},
{
id: "llama",
label: "local/llama-3.3",
usage: "unlimited",
value: 55,
variant: "amber",
},
];
function mapBudgetToDisplay(entry: TokenBudgetEntry, index: number): ModelBudgetDisplay {
const percent = entry.limit > 0 ? Math.round((entry.used / entry.limit) * 100) : 0;
const usage =
entry.limit > 0
? `${formatTokenCount(entry.used)} / ${formatTokenCount(entry.limit)}`
: "unlimited";
return {
id: entry.model,
label: entry.model,
usage,
value: percent,
variant: VARIANT_CYCLE[index % VARIANT_CYCLE.length] ?? "blue",
};
}
/* ------------------------------------------------------------------ */
/* Components */
/* ------------------------------------------------------------------ */
interface ModelRowProps {
model: ModelBudget;
model: ModelBudgetDisplay;
}
function ModelRow({ model }: ModelRowProps): ReactElement {
@@ -84,14 +90,27 @@ function ModelRow({ model }: ModelRowProps): ReactElement {
);
}
export function TokenBudget(): ReactElement {
export function TokenBudget({ budgets }: TokenBudgetProps): ReactElement {
const displayModels = budgets ? budgets.map(mapBudgetToDisplay) : [];
return (
<Card>
<SectionHeader title="Token Budget" subtitle="Usage by model" />
<div>
{models.map((model) => (
<ModelRow key={model.id} model={model} />
))}
{displayModels.length > 0 ? (
displayModels.map((model) => <ModelRow key={model.id} model={model} />)
) : (
<div
style={{
padding: "24px 0",
textAlign: "center",
fontSize: "0.8rem",
color: "var(--muted)",
}}
>
No budget data
</div>
)}
</div>
</Card>
);