Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
96 linesCodeBlameRaw
1/**
2 * What a session recorded, step by step: prompts folded, messages in full,
3 * tool calls in a line each and their results folded. The pull request's
4 * Sessions page and a harness session's page both list them.
5 */
6import { MessageSquare, Sparkles, StickyNote, Terminal, Wrench } from "lucide-react";
7
8import type { SessionEntry } from "@g1t/contracts";
9
10import { TimeAgo } from "./ui";
11import { Badge } from "./ui/badge";
12import { Card } from "./ui/card";
13import { Hint } from "./ui/hint";
14
15function Entry({ entry }: { entry: SessionEntry }) {
16 const at = (
17 <span className="shrink-0 text-xs text-faint">
18 <TimeAgo at={entry.at} />
19 </span>
20 );
21 if (entry.kind === "prompt") {
22 return (
23 <li className="px-4 py-3">
24 <details>
25 <summary className="flex cursor-pointer items-center gap-2 text-sm font-medium">
26 <Terminal size={14} className="text-accent" />
27 <span className="grow">Prompt</span>
28 {at}
29 </summary>
30 <pre className="mt-2 max-h-96 overflow-auto rounded-lg bg-bg p-3 text-xs whitespace-pre-wrap text-muted">{entry.text}</pre>
31 </details>
32 </li>
33 );
34 }
35 if (entry.kind === "message") {
36 return (
37 <li className="flex gap-2.5 px-4 py-3 text-sm">
38 <MessageSquare size={14} className="mt-0.5 shrink-0 text-faint" />
39 <p className="min-w-0 grow whitespace-pre-wrap wrap-break-word">{entry.text}</p>
40 {at}
41 </li>
42 );
43 }
44 if (entry.kind === "tool_call") {
45 return (
46 <li className="flex gap-2.5 px-4 py-2 text-xs">
47 <Wrench size={13} className="mt-0.5 shrink-0 text-faint" />
48 <span className="shrink-0 font-medium text-muted">{entry.tool}</span>
49 <Hint label={entry.text}>
50 <code className="min-w-0 grow truncate font-mono text-fg/85">{entry.text}</code>
51 </Hint>
52 </li>
53 );
54 }
55 if (entry.kind === "tool_result") {
56 return (
57 <li className="px-4 py-1.5 pl-10">
58 <details>
59 <summary className="cursor-pointer text-xs text-faint">Result</summary>
60 <pre className="mt-1.5 max-h-72 overflow-auto rounded-lg bg-bg p-3 text-xs text-muted">{entry.text}</pre>
61 </details>
62 </li>
63 );
64 }
65 if (entry.kind === "skill") {
66 return (
67 <li className="flex items-center gap-2.5 px-4 py-2 text-xs text-muted">
68 <Sparkles size={13} className="shrink-0 text-accent" />
69 <span className="min-w-0 grow">
70 Used skill <Badge shape="tag" className="font-mono">{entry.text}</Badge>
71 </span>
72 {at}
73 </li>
74 );
75 }
76 return (
77 <li className="flex gap-2.5 px-4 py-2.5 text-xs text-muted">
78 <StickyNote size={13} className="mt-0.5 shrink-0 text-faint" />
79 <span className="min-w-0 grow">{entry.text}</span>
80 {at}
81 </li>
82 );
83}
84
85/** A session's entries in a card, oldest first. */
86export function SessionEntries({ entries }: { entries: SessionEntry[] }) {
87 return (
88 <Card asChild divided>
89 <ol>
90 {entries.map((entry) => (
91 <Entry key={entry.seq} entry={entry} />
92 ))}
93 </ol>
94 </Card>
95 );
96}