Skip to content
205 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1/**
2 * The audit log's entries, as the workspace's Audit log page, an agent
3 * run's page and a pull request's Agent panel show them.
4 */
5
6import { ShieldAlert, ShieldCheck } from "lucide-react";
7import { useEffect } from "react";
8import { Link, useFetcher } from "react-router";
9
10import type { AuditEntry } from "@g1t/contracts";
11
12import { actionLabel, actorLabel, ruleLabel, targetLabel } from "../lib/audit";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.13import { TimeAgo } from "./ui";
14import { Avatar } from "./ui/avatar";
15import { Card } from "./ui/card";
Merge branch 'worktree-agent-ae1299e92e4462012'16import { Hint } from "./ui/hint";
Merge branch 'worktree-agent-aca4790ea223aa235'17import { Loading, SkeletonRows } from "./ui/skeleton";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API18
19function clock(at: string): string {
20 return new Date(at).toISOString().slice(11, 19);
21}
22
23/** Allowed or denied, and by which rule. */
24export function OutcomeMark({ entry }: { entry: AuditEntry }) {
25 const denied = entry.outcome === "denied";
26 return (
Merge branch 'worktree-agent-ae1299e92e4462012'27 <Hint label={`${entry.outcome}: ${ruleLabel(entry.rule)} (${entry.rule})`}>
28 <span
29 className={`inline-flex shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs ${
30 denied ? "bg-danger/10 text-danger ring-1 ring-danger/30" : "bg-raised text-muted ring-1 ring-line"
31 }`}
32 >
33 {denied ? <ShieldAlert size={11} /> : <ShieldCheck size={11} />}
34 {entry.outcome}
35 </span>
36 </Hint>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API37 );
38}
39
40/** Who acted, with an agent shown as working for someone. */
41export function ActorLine({ entry }: { entry: AuditEntry }) {
42 return (
43 <span className="flex min-w-0 items-center gap-2">
44 <Avatar name={entry.agent ?? entry.actor} size={18} square={entry.actorKind === "workspace"} />
45 <span className="truncate">
46 {entry.onBehalfOf ? (
47 <>
48 <span className="font-medium">{entry.agent ?? entry.actor}</span>
49 <span className="text-muted"> on behalf of </span>
50 <span className="font-medium">{entry.onBehalfOf}</span>
51 </>
52 ) : (
53 <span className="font-medium">{entry.actor}</span>
54 )}
55 </span>
56 </span>
57 );
58}
59
60/** The workspace's log, one row an entry, newest first. */
61export function AuditTable({ entries, base }: { entries: AuditEntry[]; base: string }) {
62 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.63 <Card asChild divided>
64 <ol>
65 {entries.map((entry) => (
66 <li key={entry.id} className="grid gap-x-4 gap-y-1 px-4 py-3 text-sm sm:grid-cols-[9rem_1fr_auto]">
67 <span className="text-xs leading-5 text-faint">
68 <TimeAgo at={entry.time} />
69 </span>
70 <div className="min-w-0">
71 <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
72 <ActorLine entry={entry} />
73 <span className="font-mono text-xs text-fg/85">{actionLabel(entry.action)}</span>
74 <span className="truncate font-mono text-xs text-muted">{targetLabel(entry)}</span>
75 </div>
76 <p className="mt-1 flex flex-wrap gap-x-3 text-xs text-faint">
77 <Hint label={entry.rule}>
78 <span>{ruleLabel(entry.rule)}</span>
79 </Hint>
80 <span>{entry.surface.toUpperCase()}</span>
81 {entry.result && entry.result !== "ok" && <span>result: {entry.result}</span>}
82 {entry.runId && entry.repo && (
83 <Link to={`/${entry.repo}/agents/runs/${entry.runId}`} className="hover:text-fg">
84 {entry.runKind ?? "agent"} run
85 </Link>
86 )}
87 {entry.runId && (
88 <Link to={`${base}?run=${encodeURIComponent(entry.runId)}`} className="hover:text-fg">
89 everything this run did
90 </Link>
91 )}
92 {entry.credentialId && (
93 <Hint label="Credential">
94 <span className="font-mono">
95 <span className="sr-only">Credential </span>
96 {entry.credentialId}
97 </span>
98 </Hint>
99 )}
100 <Hint label="Request id">
Merge branch 'worktree-agent-ae1299e92e4462012'101 <span className="font-mono">
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.102 <span className="sr-only">Request id </span>
103 {entry.requestId}
Merge branch 'worktree-agent-ae1299e92e4462012'104 </span>
105 </Hint>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.106 </p>
107 {entry.outcome === "denied" && entry.message && <p className="mt-1 text-xs text-danger">{entry.message}</p>}
108 </div>
109 <span className="sm:text-right">
110 <OutcomeMark entry={entry} />
111 </span>
112 </li>
113 ))}
114 </ol>
115 </Card>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API116 );
117}
118
119/**
120 * What an agent's run did, oldest first: every call it made and every git
121 * request, allowed or refused. `entries` come from the run's audit log.
122 */
123export function WhatItDid({ entries, compact = false }: { entries: AuditEntry[]; compact?: boolean }) {
124 if (entries.length === 0) {
125 return (
126 <p className="mt-3 text-sm text-muted">
127 Nothing recorded yet. Every call this run makes with its credentials, and every clone and push, is listed here.
128 </p>
129 );
130 }
131 const who = entries.find((entry) => entry.onBehalfOf);
132 const denied = entries.filter((entry) => entry.outcome === "denied").length;
133 const shown = compact ? entries.slice(-8) : entries;
134 return (
135 <div className="mt-3">
136 <p className="text-xs text-muted">
137 {who ? actorLabel(who) : entries[0].actor} · {entries.length} {entries.length === 1 ? "action" : "actions"}
138 {denied > 0 && <span className="text-danger"> · {denied} refused</span>}
139 {compact && entries.length > shown.length && ` · the latest ${shown.length}`}
140 </p>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.141 <Card asChild divided className="mt-2">
142 <ol>
143 {shown.map((entry) => (
144 <li key={entry.id} className="flex items-start gap-3 px-4 py-2 text-sm">
145 <time dateTime={entry.time} className="shrink-0 font-mono text-xs leading-5 text-faint" suppressHydrationWarning>
146 {clock(entry.time)}
147 </time>
148 <div className="min-w-0 grow">
149 <p className="flex min-w-0 flex-wrap items-center gap-x-2 font-mono text-xs leading-5">
150 <span className="text-fg/85">{actionLabel(entry.action)}</span>
151 <span className="truncate text-muted">{targetLabel(entry)}</span>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API152 </p>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.153 {entry.outcome === "denied" && (
154 <p className="text-xs text-danger">
155 {entry.message ?? "Refused."} <span className="text-faint">({ruleLabel(entry.rule)})</span>
156 </p>
157 )}
158 </div>
159 <OutcomeMark entry={entry} />
160 </li>
161 ))}
162 </ol>
163 </Card>
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API164 </div>
165 );
166}
167
168/**
169 * What the runs on a pull request did, fetched from the project's
170 * `audit.json`, for the Agent panel. Shown to members only.
171 */
172export function RunAudit({ owner, repo, runIds, live }: { owner: string; repo: string; runIds: string[]; live: boolean }) {
173 const fetcher = useFetcher<{ entries: AuditEntry[] }>();
174 const search = new URLSearchParams(runIds.map((id) => ["run", id])).toString();
175 const url = `/${owner}/${repo}/audit.json?${search}`;
176 const { load } = fetcher;
177 useEffect(() => {
178 if (runIds.length > 0) load(url);
179 }, [load, url, runIds.length]);
180 useEffect(() => {
181 if (!live) return;
182 const timer = setInterval(() => {
183 if (document.visibilityState === "visible") load(url);
184 }, 8000);
185 return () => clearInterval(timer);
186 }, [live, load, url]);
187 const entries = fetcher.data?.entries;
Merge branch 'worktree-agent-aca4790ea223aa235'188 if (runIds.length === 0) return null;
189 // There from the start, so the panel does not grow when the log arrives.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API190 return (
191 <details className="mt-3 group">
192 <summary className="cursor-pointer text-xs text-muted hover:text-fg">What it did</summary>
Merge branch 'worktree-agent-aca4790ea223aa235'193 {entries ? (
194 <WhatItDid entries={entries} compact />
195 ) : (
196 <Loading className="mt-3 overflow-hidden rounded-xl border border-line bg-surface">
197 <SkeletonRows rows={3} rowClassName="h-9 px-4" />
198 </Loading>
199 )}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API200 <Link to={`/${owner}/-/audit?project=${encodeURIComponent(repo)}&kind=agent`} className="mt-2 inline-block text-xs text-muted hover:text-fg">
201 Open the audit log
202 </Link>
203 </details>
204 );
205}

This file's history is long; its oldest lines are credited to the oldest commit read.