import React from "react"; import { CheckCircle2, AlertTriangle, ShieldAlert, Bot, ScrollText, Flame, Coins, Wrench, RefreshCw, Info, Clock, Cpu, GitBranch, Layers, MessageSquare, Network, Map, Code2, Sparkles, ArrowRight, Link2, ChevronRight, Lightbulb, ShieldCheck, Rocket, Database, Boxes, Server, Gauge, Scissors, } from "lucide-react"; import { C, FD, FB, FM, fmt, fmtWhen } from "../theme.js"; import { BinaryLogo, GeneratedTag } from "./Primitives.jsx"; // SESSION REPORT — the executive cover page for a session (the simplified default // view). It unifies every deterministic signal Her produces — cost, outcome, the // real binaries run, the high-impact actions + risk, timestamps, and the cited // recommendations — and is the jumping-off point to the Journey Graph (Mode A) and // the turn-by-turn detail (Mode B). Numbers are the engine; the only GENERATED // prose is the "What happened" overview (labelled). Suggest, never assert. const COST_W = { in: 1.0, cacheCreate: 1.25, cacheRead: 0.1, out: 5.0 }; const RISK = { High: { c: C.red, note: "high-impact actions detected" }, Medium: { c: C.amber, note: "review the flagged actions" }, Low: { c: C.cyan, note: "minor actions only" }, None: { c: C.muted, note: "nothing high-impact" }, }; const TAG = { LIVE: { c: C.red, icon: Rocket }, PRODUCTION: { c: C.red, icon: Rocket }, // legacy alias SECURITY: { c: C.orange, icon: ShieldAlert }, DATA: { c: "#f472b6", icon: Database }, NETWORK: { c: C.cyan, icon: Network }, CONFIG: { c: C.amber, icon: Code2 }, DEV: { c: C.blue, icon: Server }, }; function durationStr(a, b) { if (!a || !b) return "—"; const ms = new Date(b).getTime() - new Date(a).getTime(); if (isNaN(ms) || ms < 0) return "—"; const s = Math.round(ms / 1000); const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), ss = s % 60; return h ? `${h}h ${m}m` : m ? `${m}m ${ss}s` : `${ss}s`; } // A friendly type tag for a binary in "Tools Discovered". function binKind(b) { const blob = `${b.product || ""} ${b.blurb || ""} ${b.security || ""}`.toLowerCase(); if (/\b(database|postgres|sql|mongo|redis|sqlite|db client)\b/.test(blob)) return "DB CLI"; if (b.via && b.via !== "direct") return "pkg"; if (/\bcli\b/.test(blob) || b.identified) return "CLI"; return "binary"; } export default function SessionReport({ session, turns, binaries, entities, impact, recommendations, advice, overview, narrated, onOpenTurn, onOpenJourney, onOpenRaw, onAskHer, chatOpen, }) { const S = session; const tk = S.tokens || {}; // point-in-time window occupancy ("fuel gauge") — distinct from the cumulative tk.* const ctx = S.context || { peak: 0, limit: 1e6, peakPct: 0, trajectory: [], compactions: [], overLimit: [] }; const reqs = turns.reduce((s, t) => s + (t.reqs || 0), 0); const im = impact || { riskLevel: "None", actions: [], outcome: null }; const outcome = im.outcome || { label: "—", detail: "" }; const risk = RISK[im.riskLevel] || RISK.None; const agentPct = Math.round(100 * (S.indirectRatio || 0)); // cost breakdown (by COST contribution — this is "why this COST so much") const parts = [ { label: "Cache re-reads", raw: tk.cacheRead || 0, cost: (tk.cacheRead || 0) * COST_W.cacheRead, c: C.orange }, { label: "Generated (agent output)", raw: tk.out || 0, cost: (tk.out || 0) * COST_W.out, c: C.cyan }, { label: "Cache write", raw: tk.cacheCreate || 0, cost: (tk.cacheCreate || 0) * COST_W.cacheCreate, c: C.amber }, { label: "Fresh input", raw: tk.in || 0, cost: (tk.in || 0) * COST_W.in, c: C.blue }, ]; const totalCost = parts.reduce((s, p) => s + p.cost, 0) || 1; const driver = parts.reduce((a, b) => (b.cost > a.cost ? b : a), parts[0]); // most important turns — by cost const ranked = [...turns] .filter((t) => (t.tokens?.cost ?? 0) > 0) .sort((a, b) => (b.tokens.cost ?? 0) - (a.tokens.cost ?? 0)) .slice(0, 4); // tools discovered = binaries (real CLIs/pkgs) + skills/plugins + sub-agents + MCP const bins = (binaries || []).map((b) => ({ ...b, glyph: "binary", kind: binKind(b) })); const skills = (entities?.skills || []).map((s) => ({ name: s.name, turns: s.turns, glyph: "skill", kind: "skill" })); const agents = (entities?.subAgents || []).map((a) => ({ name: a.name, turns: a.turns, glyph: "agent", kind: a.via === "workflow" ? "workflow" : "agent" })); const mcp = (entities?.mcpServers || []).map((m) => ({ name: m.name, turns: m.turns, glyph: "mcp", kind: "MCP" })); const tools = [...bins, ...skills, ...agents, ...mcp]; const recs = (advice?.recommendations?.length ? advice.recommendations : recommendations) || []; function copySummary() { const lines = [ `Her · session report — ${S.sessionId ? S.sessionId.slice(0, 8) : ""}`, `${S.cwd || ""}`, `Outcome: ${outcome.label} (${outcome.detail})`, `Token cost: ${fmt(S.cost || 0)} cost-weighted tokens (not $) · cache re-reads ${fmt(tk.cacheRead || 0)} cumulative across ${reqs} round-trips · agent-driven ${agentPct}%`, `Peak context: ${fmt(ctx.peak || 0)} / ${fmt(ctx.limit || 0)} (${Math.round((ctx.peakPct || 0) * 100)}% of the window)${ctx.compactions?.length ? ` · ${ctx.compactions.length} compaction(s)` : ""}`, `Risk: ${im.riskLevel} (${im.riskReason || ""})`, `Tools: ${tools.map((t) => t.product || t.name).join(", ")}`, im.actions?.length ? `Actions: ${im.actions.map((a) => `${a.title} [${a.tag}]`).join("; ")}` : "", `Started ${fmtWhen(S.startedAt)} · ${durationStr(S.startedAt, S.endedAt)} · ${S.model || ""}`, ].filter(Boolean); try { navigator.clipboard.writeText(lines.join("\n")); } catch { /* ignore */ } } return (
{/* title + actions */}
Session Report
Executive summary for this coding-agent session — what happened, why it cost, and what to review.
{/* stat band */}
{/* main grid */}
{/* WHAT HAPPENED */} {overview?.text ? ( <>
    {sentences(overview.text).map((s, i) => (
  • {s}
  • ))}
) : (
{S.turns} queries · {S.tools} tool calls · {S.humanTurns} human / {S.systemTurns} system turns. (Plain-English summary appears when the local model is running.)
)}
{/* ACTIONS WORTH REVIEWING */} {im.actions?.length ? im.actions.map((a, i) => { const tg = TAG[a.tag] || { c: C.muted, icon: Info }; const Ic = tg.icon; return (
a.turns?.length && onOpenTurn(a.turns[0])} style={{ display: "flex", gap: 10, alignItems: "center", cursor: a.turns?.length ? "pointer" : "default", background: C.card, border: `1px solid ${C.borderSoft}`, borderRadius: 8, padding: "9px 11px", marginTop: i ? 7 : 0 }}>
{a.title}
{a.detail}
{a.tag}
); }) : (
No high-impact actions detected — no deploys, config/secret changes, tunnels, or privilege changes.
)}
{/* TOOLS DISCOVERED */} {tools.length ? ( <>
{tools.map((t) => { const clickable = t.turns && t.turns.length; return ( clickable && onOpenTurn(t.turns[0])} className={clickable ? "lift" : ""} title={`${t.product || t.name}${t.blurb ? " — " + t.blurb : ""}${t.security ? " ⚠ " + t.security : ""}${t.turns?.length ? " · turn(s) " + t.turns.join(", ") : ""}`} style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: FM, fontSize: 10.5, color: C.text2, border: `1px solid ${C.borderSoft}`, borderRadius: 7, padding: "4px 8px 4px 6px", cursor: clickable ? "pointer" : "default" }}> {toolGlyph(t)} {t.product || t.name} ({t.kind}) {t.security && } ); })}
See all {S.tools} tool calls
) : }
{/* COST & CONTEXT — one widget, two halves of the same token story: cumulative spend (left, no ceiling) vs the point-in-time window gauge (right, ≤1M). Side by side so the two quantities can't be mistaken for each other. */} {/* bottom grid */}
{/* MOST IMPORTANT TURNS */}
{ranked.map((t) => { const errs = (t.tools || []).filter((tc) => tc.errored).length; const title = t.guide?.head || (t.heavy ? "Heaviest turn" : firstClause(t.prompt)); const detail = t.guide?.body ? clip(t.guide.body, 90) : `${(t.tools || []).length} tools${errs ? `, ${errs} errored` : ""}`; const pct = Math.round((100 * (t.tokens.cost ?? 0)) / Math.max(1, S.cost || 1)); return (
onOpenTurn(t.i)} style={{ display: "flex", gap: 10, alignItems: "flex-start", cursor: "pointer", background: C.card, border: `1px solid ${C.borderSoft}`, borderRadius: 8, padding: "9px 11px" }}> #{String(t.i).padStart(2, "0")}
{title}
{detail}
{fmt(t.tokens.cost ?? 0)}
{pct}% of cost
); })} {ranked.length === 0 && }
{/* RECOMMENDED NEXT RUN */} {recs.length ? recs.slice(0, 4).map((r, i) => (
r.turns?.length && onOpenTurn(r.turns[0])} style={{ display: "flex", gap: 10, alignItems: "flex-start", cursor: r.turns?.length ? "pointer" : "default", background: C.card, border: `1px solid ${C.borderSoft}`, borderRadius: 8, padding: "9px 11px", marginTop: i ? 7 : 0 }}>
{r.headline}
{clip(r.scoped || r.advice || "", 120)}
)) : (
Nothing stands out to change — expensive but clean. No loops, avoidable re-reads, or CLI flailing.
)}
{/* SESSION AT A GLANCE */}
Copy summary
); } // ---------- small presentational atoms (Tactical Grey) ---------------------- function ReportBtn({ icon: Icon, text, onClick, primary, active }) { // `active` (panel open) gives a pressed look so it reads as a toggle — click again to close. const bg = primary ? (active ? C.orangeHi : C.orange) : (active ? C.orangeMut : "transparent"); const fg = primary ? "#fff" : (active ? C.orange : C.text2); return (
{text}
); } function StatCard({ label, icon: Icon, iconColor, value, valueColor, sub, grad, big }) { return (
{label} {Icon && }
{big && Icon && }{value}
{sub &&
{sub}
}
); } // PEAK CONTEXT — the "fuel gauge" headline: the fullest the live window ever got, // over the model's limit. Point-in-time, bounded by the window (≤1M) — the opposite // kind of number from the cumulative cost/cache stats beside it. function PeakContextCard({ ctx }) { const pct = Math.round((ctx.peakPct || 0) * 100); const over = (ctx.overLimit || []).length > 0; const near = pct >= 80; const c = over ? C.red : near ? C.amber : C.cyan; return (
PEAK CONTEXT
{fmt(ctx.peak || 0)} / {fmt(ctx.limit || 0)}
{over ? "⚠ a request exceeded the window — data suspect" : `${pct}% of the window · point-in-time, not cumulative`}
); } // COST & CONTEXT — the unified token story in one card. LEFT: cumulative cost // breakdown (no ceiling). RIGHT: the point-in-time window gauge + trajectory (≤1M). // The side-by-side contrast IS the point — cumulative ≠ window occupancy. function CostContextCard({ S, parts, totalCost, driver, ctx, reqs, onOpenTurn }) { return (
{/* LEFT — cumulative spend */}
{/* Whole phrase is the hover target — the native `title` lives on this (a `title` on an inline icon does NOT show a tooltip in Chrome). Dotted underline signals it's hoverable. */}
Total: {fmt(S.cost || 0)}{" "} cost-weighted tokens (not $)
{parts.map((p) => { const pct = Math.round((100 * p.cost) / totalCost); return (
{p.label} {fmt(p.raw)} ({pct}%)
); })}
{driver.label} dominates cost ({Math.round((100 * driver.cost) / totalCost)}%){driver.label === "Cache re-reads" ? ` — the conversation re-read from cache on every round-trip, summed across all ${reqs}. Cumulative spend, not window size.` : "."}
{/* RIGHT — point-in-time window */}
); } function SubHead({ text }) { return
{text}
; } // CONTEXT WINDOW trajectory — an area chart of how full the live window got per turn // (the gauge over time), with compaction markers (sharp drops) and the 1M ceiling. function ContextTrajectory({ ctx, reqs, onOpenTurn }) { const traj = ctx.trajectory || []; const limit = ctx.limit || 1e6; const compactTurns = new Set((ctx.compactions || []).map((c) => c.atTurn)); const W = 300, H = 84, n = traj.length; if (!n) return ; const x = (i) => (n === 1 ? W / 2 : (i / (n - 1)) * W); const y = (v) => H - Math.min(1, v / limit) * H; const pts = traj.map((e, i) => `${x(i).toFixed(1)},${y(e.end).toFixed(1)}`); const area = `M0,${H} L${pts.join(" L")} L${W},${H} Z`; const line = `M${pts.join(" L")}`; const over = (ctx.overLimit || []).length > 0; const peakPct = Math.round((ctx.peakPct || 0) * 100); return (
Peak fill = 80 ? C.amber : C.cyan }}>{fmt(ctx.peak || 0)} of {fmt(limit)} ({peakPct}%). The live window — bounded by {fmt(limit)} — across {reqs} round-trips. Not the cumulative totals.
{/* 1M ceiling */} {/* 80% line */} {/* compaction markers */} {traj.map((e, i) => compactTurns.has(e.i) ? ( ) : null)}
turn 00 {fmt(limit)} ceiling turn {String(traj[n - 1].i).padStart(2, "0")}
{ctx.compactions?.length ? ( {ctx.compactions.length} compaction{ctx.compactions.length === 1 ? "" : "s"} detected — the window was trimmed where it dips.{" "} {ctx.compactions.map((c) => ( onOpenTurn(c.atTurn)} className="lift" style={{ cursor: "pointer", color: C.orange, fontFamily: FM }}> #{String(c.atTurn).padStart(2, "0")} ({fmt(c.before)}→{fmt(c.after)}){" "} ))} ) : ( No compactions — the window climbed to {peakPct}% and never had to be trimmed. )}
); } function Card({ icon: Icon, title, accent, children }) { return (
{title}
{children}
); } function Glance({ icon: Icon, label, value }) { return (
{label} {value}
); } function AttrBadge({ attribution, scoped }) { const generally = attribution && attribution !== "Anthropic"; return ( {generally ? "GENERALLY RECOMMENDED" : "ANTHROPIC"} ); } function Empty({ text }) { return
{text}
; } // ---------- helpers --------------------------------------------------------- function sentences(text) { return String(text || "") .split(/(?<=[.!?])\s+/) .map((s) => s.trim()) .filter((s) => s.length > 2) .slice(0, 6); } function firstClause(p) { const s = String(p || "").replace(/\s+/g, " ").trim(); return s.length > 48 ? s.slice(0, 48) + "…" : s || "(turn)"; } function clip(s, n) { s = String(s || ""); return s.length > n ? s.slice(0, n).trimEnd() + "…" : s; } function modelLabel(m) { if (!m) return "—"; return String(m).replace(/^claude-/, "Claude ").replace(/-(\d{8})$/, "").replace(/-/g, " "); } // Leading glyph for a Tools-Discovered chip: binaries get their logo (monogram // fallback); skills/plugins, sub-agents, and MCP servers get a typed icon. function toolGlyph(t) { if (t.glyph === "binary") return ; if (t.glyph === "mcp") return ; if (t.glyph === "skill") return ; return ; // agent / workflow }