122 lines5,539 bytesCodeBlame

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 API1import { env } from "cloudflare:workers";
2import { AtSign, Tag } from "lucide-react";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look3import { Form, useNavigation } from "react-router";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API4
5import { AGENT_HANDLE, mentionsClient } from "@g1t/contracts";
6
7import type { Route } from "./+types/settings-agents";
8import { page } from "../../lib/meta";
Project settings move into the sidebar, grouped, with a heading per page9import { RepoSettingsHeading } from "../../components/repo-settings-heading";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API10import { Button, ErrorText, Field, Input, TimeAgo } from "../../components/ui";
11import { work } from "../../lib/services.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look12import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
13import { requireCapability, requireInsider } from "../../lib/access.server";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API14
15const mentions = mentionsClient(env.WORK);
16
17export function meta({ params, ...args }: Route.MetaArgs) {
18 return page(args, { title: `Agent settings · ${params.owner}/${params.repo} · g1t` });
19}
20
21export async function loader({ params, context }: Route.LoaderArgs) {
22 const viewer = getViewer(context);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look23 // Maintain and up; to anyone without a role here the page does not exist.
24 await requireInsider(context, params, "manage_settings");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API25 const path = { namespace: params.owner, name: params.repo };
26 const [rules, labels] = await Promise.all([mentions.getAgentRules(path, viewer), work.listLabels(path, viewer)]);
27 return { rules: unwrap(rules), labels: labels.ok ? labels.value : [] };
28}
29
30export async function action({ request, params, context }: Route.ActionArgs) {
31 assertSameOrigin(request);
32 const user = requireUser(context, request);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look33 await requireCapability(context, params, "manage_settings");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API34 const form = await request.formData();
35 const path = { namespace: params.owner, name: params.repo };
36 const label = form.get("intent") === "off" ? null : String(form.get("label") ?? "").trim() || null;
37 const saved = await mentions.setAgentRules(user, path, { label });
38 if (!saved.ok) return { error: saved.error.message, notice: null };
39 return {
40 error: null,
41 notice: saved.value.label
42 ? `Issues labelled ${saved.value.label} now go to g1t-agent.`
43 : "Labels no longer put g1t-agent to work.",
44 };
45}
46
47export default function AgentSettings({ loaderData, actionData, params }: Route.ComponentProps) {
48 const { rules, labels } = loaderData;
49 const busy = useNavigation().state === "submitting";
50 const base = `/${params.owner}/${params.repo}`;
51 return (
52 <div className="max-w-4xl">
Project settings move into the sidebar, grouped, with a heading per page53 <RepoSettingsHeading base={base} />
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API54 <div className="min-h-6">
55 {actionData?.notice && <p className="text-sm text-accent">{actionData.notice}</p>}
56 <ErrorText>{actionData?.error ?? null}</ErrorText>
57 </div>
58
59 <section className="grid gap-x-10 gap-y-4 lg:grid-cols-[16rem_1fr]">
60 <div>
61 <h2 className="flex items-center gap-2 font-medium">
62 <Tag size={15} className="text-muted" />
63 Label rule
64 </h2>
65 <p className="mt-1 text-sm text-muted">
66 When a member gives an issue this label, g1t-agent takes it, as if they had assigned it. It starts as soon as
67 the project has room for another agent and nothing the issue depends on is still open.
68 </p>
69 </div>
70 <Form method="post" className="space-y-3 rounded-xl border border-line bg-surface p-4">
71 <Field label="Label" hint="An issue that already has it is not affected; adding it again is.">
72 <Input name="label" list="known-labels" defaultValue={rules.label ?? ""} placeholder="agent" maxLength={40} />
73 </Field>
74 <datalist id="known-labels">
75 {labels.map((label) => (
76 <option key={label} value={label} />
77 ))}
78 </datalist>
79 <div className="flex flex-wrap items-center gap-3">
80 <Button type="submit" name="intent" value="save" disabled={busy}>
81 Save
82 </Button>
83 {rules.label && (
84 <Button type="submit" variant="quiet" name="intent" value="off" disabled={busy}>
85 Turn off
86 </Button>
87 )}
88 {rules.updatedBy && rules.updatedAt && (
89 <span className="text-xs text-faint">
90 Changed by {rules.updatedBy} <TimeAgo at={rules.updatedAt} />
91 </span>
92 )}
93 </div>
94 </Form>
95 </section>
96
97 <section className="mt-8 grid gap-x-10 gap-y-4 border-t border-line pt-8 lg:grid-cols-[16rem_1fr]">
98 <div>
99 <h2 className="flex items-center gap-2 font-medium">
100 <AtSign size={15} className="text-muted" />
101 Mentions
102 </h2>
103 <p className="mt-1 text-sm text-muted">Always on, for members of the workspace.</p>
104 </div>
105 <ul className="space-y-2 text-sm text-muted">
106 <li>
107 <span className="font-mono text-fg">{AGENT_HANDLE} take this</span> on an issue assigns it to g1t-agent. A
108 question gets an answer in the thread instead.
109 </li>
110 <li>
111 On a pull request g1t-agent made, a request sends it back to make the change. On any pull request,{" "}
112 <span className="font-mono text-fg">{AGENT_HANDLE} review</span> starts a review and a question is answered.
113 </li>
114 <li>
115 Mentions in code, in quotes or from people outside the workspace start nothing. Each comment starts one run at
116 most, and g1t-agent replies saying what it did, or why it could not.
117 </li>
118 </ul>
119 </section>
120 </div>
121 );
122}