Skip to content
1,607 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.

Acceptance checks in sandboxes, line comments and review verdicts1import { env } from "cloudflare:workers";
Issues and pull requests replace intents and attempts2import {
3 Bot,
4 ChevronRight,
5 FileDiff,
Pull requests from branches6 GitBranch,
Issues and pull requests replace intents and attempts7 GitCommitHorizontal,
Acceptance checks in sandboxes, line comments and review verdicts8 CircleCheck,
9 CircleSlash,
Issues and pull requests replace intents and attempts10 GitMerge,
Agents as a team: lifecycle, merge queue, billing and a new shell11 Layers,
12 GitPullRequestArrow,
13 Hand,
14 Loader,
15 Radar,
16 Sparkles,
Issues and pull requests replace intents and attempts17 MessageSquare,
Pull requests say where they are live; the project menu shows what is coming18 ArrowUpRight,
Issues and pull requests replace intents and attempts19 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming20 Rocket,
Project dependencies: addresses, preview stacks, Affects, and agents who know21 Network,
Issues and pull requests replace intents and attempts22 StickyNote,
23 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar24 Users,
Issues and pull requests replace intents and attempts25 Wrench,
26} from "lucide-react";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending27import { Suspense } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'28import { Await, Form, Link, redirect } from "react-router";
Issues and pull requests replace intents and attempts29
Pull requests say where they are live; the project menu shows what is coming30import {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look31 type Capability,
Pull requests say where they are live; the project menu shows what is coming32 type Comparison,
33 type Deployment,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains34 type Job,
Pull requests say where they are live; the project menu shows what is coming35 type LiveApp,
36 type SessionEntry,
37 type Viewer,
38 pullComparison,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights39 workOwner,
Pull requests say where they are live; the project menu shows what is coming40} from "@g1t/contracts";
Issues and pull requests replace intents and attempts41
42import type { Route } from "./+types/pull";
Social cards for every page: og.g1t.sh43import { excerpt, page } from "../../lib/meta";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent44import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'45import { cloneUrl, useAddresses } from "../../lib/addresses";
Issues and pull requests replace intents and attempts46import { DiffView } from "../../components/diff-view";
Agents as a team: lifecycle, merge queue, billing and a new shell47import { LifecyclePanel } from "../../components/lifecycle";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains48import { AgentPanel } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar49import { BaseBranch } from "../../components/base-branch";
50import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Issues and pull requests replace intents and attempts51import { Markdown } from "../../components/markdown";
52import {
53 Avatar,
Agents as a team: lifecycle, merge queue, billing and a new shell54 ButtonLink,
Issues and pull requests replace intents and attempts55 CopyLine,
56 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look57 ComputeNote,
Issues and pull requests replace intents and attempts58 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'59 SubmitButton,
Issues and pull requests replace intents and attempts60 Textarea,
61 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'62 usePending,
Issues and pull requests replace intents and attempts63} from "../../components/ui";
Workspace names and icons, and a component kit for every control64import { CheckboxOption } from "../../components/ui/checkbox";
Merge branch 'worktree-agent-aca4790ea223aa235'65import { Loading, SkeletonLine } from "../../components/ui/skeleton";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs66import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents67import { AddCiPrompt } from "../../components/add-ci";
Agents as a team: lifecycle, merge queue, billing and a new shell68import {
69 CommentForm,
70 CommentList,
71 IssueIcon,
72 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains73 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell74 PullState,
75 TimelineItem,
76 verdicts,
77} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files78import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar79import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files80import { CATCH_UP_TIMEOUT_MS } from "../../lib/catch-up";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put81import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look82import { computeNoteFor } from "../../lib/compute.server";
Merge branch 'worktree-agent-aca4790ea223aa235'83import { actions, agents, deployments, identity, inbox, projects, repos, work } from "../../lib/services.server";
Issues and pull requests replace intents and attempts84import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings85import { accessTo, refusal, repoFor } from "../../lib/access.server";
86import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending87import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Issues and pull requests replace intents and attempts88
89const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
90const TABS = ["conversation", "session", "changes"] as const;
91type Tab = (typeof TABS)[number];
92
Social cards for every page: og.g1t.sh93export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
94 const pull = loaderData?.pull;
95 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
96 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
97 const body = excerpt(pull?.body);
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent98 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh99 return page(args, {
100 title: `${title}${params.owner}/${params.repo} · g1t`,
101 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent102 ? `${state} pull request #${pull.number} on ${params.owner}/${params.repo} by ${opener!.name}${opener!.requestedBy ? `, requested by ${opener!.requestedBy}` : ""}.${body ? ` ${body}` : ""}`
Social cards for every page: og.g1t.sh103 : null,
104 // The card shows the title and the state.
105 version: pull ? [pull.title, pull.status] : undefined,
106 type: "article",
107 });
Issues and pull requests replace intents and attempts108}
109
110export async function loader({ params, context, request }: Route.LoaderArgs) {
111 const viewer = getViewer(context);
112 const path = { namespace: params.owner, name: params.repo };
113 const number = Number(params.number);
114 const asked = new URL(request.url).searchParams.get("tab");
115 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
116
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look117 // Members of the workspace pick reviewers and assignees from its people;
118 // what the viewer may do here goes by their role on the repository.
Agents as a team: lifecycle, merge queue, billing and a new shell119 const member = (viewer?.workspaces ?? []).some(
120 (membership) => membership.slug === params.owner,
121 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents122 // Everything starts at once. Only what the viewer's role decides waits
123 // for the repository, which the layout is looking up in this request too
124 // (lib/access.server.ts), and only what the pull request decides waits
125 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know126 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents127 const access = accessTo(context, params);
128 const pullFound = work.getPull(path, number, viewer);
129 const deps = projects.dependencies(params.owner, params.repo, viewer);
130 // Awaited below, unless the pull request is missing first.
131 deps.catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar132 // Its labels and milestone, with the repository's to choose from.
133 const labelsFound = work.listLabels(path, viewer);
134 const milestonesFound = work.listMilestones(path, viewer);
135 labelsFound.catch(() => null);
136 milestonesFound.catch(() => null);
137 const [{ can }, found, repo, settings, agentsEnabled, members, computeNote, session, deployed, subscription, runs, teamList] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents138 access,
139 pullFound,
Issues and pull requests replace intents and attempts140 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell141 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing142 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell143 // A member picks reviewers and assignees from the workspace's people.
144 member ? identity.listMembers(params.owner, viewer) : null,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent145 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents146 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
147 tab === "session" ? work.readSession(path, number, viewer) : null,
148 // Its preview, for people with a role here: beside the rest, not after.
149 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings150 // Whether the viewer hears of it, for the sidebar's Notifications.
151 viewer
152 ? repoFor(context, params).then((found) =>
153 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
154 )
155 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'156 // Its agent's latest runs, for the Agent panel: with the page, not after it.
157 agents.listRuns(viewer, { repo: path, number, limit: 5 }).catch(() => null),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar158 // A member asks the workspace's teams to review, and mentions them.
159 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Issues and pull requests replace intents and attempts160 ]);
161 if (!found.ok) {
162 // Issues and pull requests share numbers; this one may be an issue.
163 const issue = await work.getIssue(path, number, viewer);
164 if (issue.ok) throw redirect(`/${params.owner}/${params.repo}/issues/${number}`);
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put165 throw notFound("pull");
Issues and pull requests replace intents and attempts166 }
167 const { pull } = found.value;
Pull requests from branches168 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains169 // The jobs of each workflow run on its head, to list checks job by job.
170 // Read as the viewer: a run they cannot see is listed by its status alone.
Fast pages, required checks on the branch, self-hosted runners, honest incidents171 // Streamed: the checks show by status first, then job by job.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains172 const runIds = [...new Set((found.value.statuses ?? []).map(runIdOf).filter((id) => id != null))].slice(0, 10);
Fast pages, required checks on the branch, self-hosted runners, honest incidents173 const workflowJobs =
174 tab === "conversation" && pull.status === "open" && runIds.length > 0
175 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
176 const jobs: Record<string, Job[]> = {};
177 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
178 return jobs;
179 })
180 .catch(() => ({}) as Record<string, Job[]>)
181 : Promise.resolve({} as Record<string, Job[]>);
182 // No checks at all on an open pull request: whether that is because the
183 // repository has no workflows, to offer a starter one.
184 const unchecked =
185 tab === "conversation" &&
186 pull.status === "open" &&
187 (found.value.statuses ?? []).length === 0 &&
188 (found.value.requiredChecks ?? []).length === 0;
189 const [comparison, used, noChecks] = await Promise.all([
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar190 tab === "changes" ? repos.compare(range.repoId, viewer, range.base, range.head, range.baseBranch) : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents191 deps,
192 unchecked
193 ? actions
194 .workflows(path, viewer)
195 .then((found) => found.ok && found.value.length === 0)
196 .catch(() => false)
197 : false,
Issues and pull requests replace intents and attempts198 ]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents199 const affects = used.ok ? used.value.usedBy : [];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar200 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
201 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
202 // The default branch's protection covers pull requests into it only.
203 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Issues and pull requests replace intents and attempts204 return {
205 ...found.value,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar206 labels: labels.ok ? labels.value : [],
207 milestones: milestones.ok ? milestones.value : [],
208 // Labelling and milestones: Triage and up; its author, its labels.
209 canTriage: can.triage,
210 // The branch it merges into: Write and up.
211 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains212 workflowJobs,
Issues and pull requests replace intents and attempts213 tab,
214 session: session?.ok ? session.value : [],
215 // An empty comparison if it could not be made.
216 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
217 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look218 // Write and up can merge.
219 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights220 // Triage and up manage anyone's pull request; its author, their own,
221 // and whoever asked g1t for one, that one.
222 canManage: can.triage || (viewer != null && viewer.id === workOwner(pull).id),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look223 // Telling its agent things, and re-running checks, spend compute: Write and up.
224 canRun: can.run,
Agents as a team: lifecycle, merge queue, billing and a new shell225 // A catch-up is pushed as the viewer: a fork takes pushes only from
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights226 // whoever it is for (who asked g1t for it, or its author), a branch
227 // from anyone who can push.
228 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell229 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look230 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings231 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'232 // As the project's agents.json has them; left out, the panel fetches them.
233 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell234 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar235 // `workspace/slug` and name of each team the viewer can see.
236 teams: teamList?.ok ? teamList.value.map((team) => ({ ref: `${team.workspace}/${team.slug}`, name: team.name })) : [],
237 requireUpToDate: protectedBase && settings.ok && settings.value.requireUpToDate,
238 mergeQueue: protectedBase && settings.ok && settings.value.mergeQueue,
239 requiredApprovals: protectedBase && settings.ok ? settings.value.requiredApprovals : 0,
Agents as a team: lifecycle, merge queue, billing and a new shell240 canIgnoreChecks: !settings.ok || settings.value.allowIgnoringChecks,
Fast pages, required checks on the branch, self-hosted runners, honest incidents241 noChecks,
242 // Who may open the pull request that adds CI: anyone who can push.
243 canAddCi: can.push,
244 // Who may choose the required checks.
245 canProtect: can.manage_protection,
Issues and pull requests replace intents and attempts246 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Fast pages, required checks on the branch, self-hosted runners, honest incidents247 affects,
248 // Streamed: the conversation shows first, the preview card after.
249 ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer),
Issues and pull requests replace intents and attempts250 };
251}
252
Pull requests say where they are live; the project menu shows what is coming253/**
254 * Where this pull request is live: its preview (or its latest build, while
255 * one is going or after one failed), and the previews of the projects
256 * that use this one, built against it.
257 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents258function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming259 list: { deployments: Deployment[]; live: LiveApp[] } | null,
260 number: number,
261 owner: string,
262 affects: { slug: string; name: string }[],
263 viewer: Viewer,
Fast pages, required checks on the branch, self-hosted runners, honest incidents264): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } {
265 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
266 const build = list?.deployments.find((d) => d.kind === "preview" && d.number === number) ?? null;
Pull requests say where they are live; the project menu shows what is coming267 const branch = preview?.branch ?? build?.branch ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents268 // Streamed: another lookup per project, shown in the card once known.
269 const stacked: Promise<Stacked[]> = branch
270 ? Promise.all(
271 affects.slice(0, 5).map(async (project) => {
272 const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null);
273 const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
274 return app ? { name: project.name, slug: project.slug, url: app.url } : null;
275 }),
276 ).then((found) => found.filter((entry) => entry != null))
277 : Promise.resolve([]);
Pull requests say where they are live; the project menu shows what is coming278 return { preview, build, stacked };
279}
280
Fast pages, required checks on the branch, self-hosted runners, honest incidents281/** A project that uses this one, with its preview built against this change. */
282type Stacked = { name: string; slug: string; url: string };
283
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look284const PULL_NEEDS: Record<string, Capability> = {
285 stack: "run",
286 "agent-review": "run",
287 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents288 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look289 message: "run",
290 merge: "merge",
291 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar292 milestone: "triage",
293 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look294};
295
Issues and pull requests replace intents and attempts296export async function action({ request, params, context }: Route.ActionArgs) {
297 assertSameOrigin(request);
298 const user = requireUser(context, request);
299 const form = await request.formData();
300 const path = { namespace: params.owner, name: params.repo };
301 const number = Number(params.number);
302 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look303 // What each form needs beyond reading; work checks the rest.
304 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
305 const refused = needed ? await refusal(context, params, needed) : null;
306 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts307 const verdict = form.get("verdict");
308 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell309 /** Names ticked in a people picker, plus those typed beside it. */
310 const picked = (field: string) => [
311 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar312 // `@acme/backend` is asked as `acme/backend`.
313 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell314 ];
Project dependencies: addresses, preview stacks, Affects, and agents who know315 // The projects that use this one, built against this pull request's preview.
316 if (action === "stack") {
317 const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? ""));
318 return built.ok
319 ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` }
320 : { action, error: built.error.message };
321 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent322 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell323 if (action === "agent-review") {
324 const asked = await work.updatePull(user, path, number, {
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent325 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell326 });
327 if (!asked.ok) return { error: asked.error.message, action };
328 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar329 // Its labels, milestone and base, from the sidebar's menus and the header.
330 if (action === "labels") {
331 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
332 return set.ok ? null : { error: set.error.message, action };
333 }
334 if (action === "milestone" || action === "base") {
335 const changed = await work.updatePull(
336 user,
337 path,
338 number,
339 action === "milestone"
340 ? { milestone: Number(form.get("milestone")) || 0 }
341 : { base: String(form.get("base") ?? "") },
342 );
343 return changed.ok ? null : { error: changed.error.message, action };
344 }
Catching up with main takes seconds when the two sides touched different files345 // Catching up: merged and pushed in seconds when the two sides changed
346 // different files; otherwise handed to a sandbox, which takes a minute.
347 if (action === "update") {
348 const caught = await work.catchUpPull(user, path, number);
349 if (!caught.ok) return { action, error: caught.error.message };
350 const update = caught.value;
351 if (update.outcome !== "needs_agent") {
352 return {
353 action,
354 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
355 };
356 }
357 const started = await env.RUNNER.update(user, path, number);
358 if (!started.ok) return { action, error: started.error.message };
359 return { action, agent: { update, startedAt: Date.now() } };
360 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains361 // A workflow run's failed jobs, run again. Who may is the actions service's call.
362 if (action === "rerun-workflow") {
363 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
364 return rerun.ok ? null : { error: rerun.error.message, action };
365 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents366 // Every failed run on its head, run again: Mission control's quick action.
367 if (action === "rerun-failed") {
368 const found = await work.getPull(path, number, user);
369 if (!found.ok) return { error: found.error.message, action };
370 const failed = [
371 ...new Set(
372 (found.value.statuses ?? [])
373 .filter((status) => status.state === "failure" || status.state === "error")
374 .map(runIdOf)
375 .filter((id) => id != null),
376 ),
377 ];
378 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
379 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
380 const refused = reruns.find((rerun) => !rerun.ok);
381 return refused && !refused.ok ? { error: refused.error.message, action } : null;
382 }
Issues and pull requests replace intents and attempts383 const result =
384 action === "merge"
Acceptance checks in sandboxes, line comments and review verdicts385 ? await work.mergePull(user, path, number, {
386 keepIssueOpen: form.get("keepIssueOpen") === "on",
387 ignoreChecks: form.get("ignoreChecks") === "on",
388 })
Agents as a team: lifecycle, merge queue, billing and a new shell389 : action === "unqueue"
390 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request391 : action === "message"
392 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Issues and pull requests replace intents and attempts393 : action === "close"
394 ? await work.closePull(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents395 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell396 ? await env.RUNNER.review(user, path, number)
397 : action === "reviewers"
398 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
399 : action === "assign"
400 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts401 : action === "comment"
402 ? await work.addComment(user, path, number, {
403 body: String(form.get("body") ?? ""),
404 path: String(form.get("path") ?? "") || undefined,
405 line: line > 0 ? line : undefined,
406 verdict:
407 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
408 })
409 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
Issues and pull requests replace intents and attempts410 return result.ok ? null : { error: result.error.message, action };
411}
412
Pull requests: title and tabs across the full width, with counts and the size of the change413/** How many, beside a tab's name, when there are any. */
414function TabCount({ n }: { n: number }) {
415 if (n <= 0) return null;
416 return (
417 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
418 {n.toLocaleString("en-US")}
419 </span>
420 );
421}
422
Issues and pull requests replace intents and attempts423function TabLink({
424 to,
425 active,
426 children,
427}: {
428 to: string;
429 active: boolean;
430 children: React.ReactNode;
431}) {
432 return (
433 <Link
434 to={to}
435 preventScrollReset
436 className={
Fast pages, required checks on the branch, self-hosted runners, honest incidents437 "-mb-px flex items-center gap-2 border-b-2 px-3 pb-2.5 text-sm transition-colors " +
Issues and pull requests replace intents and attempts438 (active
439 ? "border-accent font-medium text-fg"
440 : "border-transparent text-muted hover:text-fg")
441 }
442 >
443 {children}
444 </Link>
445 );
446}
447
448/** One step of the session, on the timeline's rail. */
449function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
450 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
451 const Icon =
452 entry.kind === "prompt"
453 ? User
454 : entry.kind === "note"
455 ? StickyNote
456 : isTool
457 ? Wrench
458 : Bot;
459 return (
460 <li className="relative pl-10">
461 <span
462 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
463 entry.kind === "prompt"
464 ? "border-accent/50 text-accent"
465 : "border-line text-faint"
466 }`}
467 >
468 <Icon size={14} />
469 </span>
470 {isTool ? (
471 <details className="group rounded-lg border border-line bg-surface">
472 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
473 <ChevronRight
474 size={14}
475 className="text-faint transition-transform group-open:rotate-90"
476 />
477 <span className="font-mono text-xs text-accent">
478 {entry.tool ?? "tool"}
479 </span>
480 <span className="truncate font-mono text-xs text-muted">
481 {entry.kind === "tool_result" ? "→ " : ""}
482 {entry.text.split("\n")[0]}
483 </span>
484 </summary>
485 <pre className="overflow-x-auto border-t border-line p-3 font-mono text-xs whitespace-pre-wrap text-muted">
Fix a hydration mismatch in output that starts with a blank line486 <code>{entry.text}</code>
Issues and pull requests replace intents and attempts487 </pre>
488 </details>
489 ) : (
490 <div>
491 <p className="text-xs font-medium text-faint">
492 {entry.kind === "prompt"
493 ? "Prompt"
494 : entry.kind === "note"
495 ? "Note"
496 : agent}
497 </p>
498 {entry.kind === "message" ? (
499 <div className="mt-1">
500 <Markdown source={entry.text} />
501 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell502 ) : entry.kind === "prompt" ? (
503 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
504 <Markdown source={entry.text} />
505 </div>
Issues and pull requests replace intents and attempts506 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell507 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Issues and pull requests replace intents and attempts508 {entry.text}
509 </p>
510 )}
511 </div>
512 )}
513 {entry.commit && (
514 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
515 <GitCommitHorizontal size={12} />
516 {entry.commit.slice(0, 7)}
517 </p>
518 )}
519 </li>
520 );
521}
522
Agents as a team: lifecycle, merge queue, billing and a new shell523/** The box at the foot of the conversation saying what stands before a merge. */
524function StatusBox({ children }: { children: React.ReactNode }) {
525 return (
526 <div className="flex gap-3">
527 <span className="hidden w-8 shrink-0 sm:block" />
528 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
529 {children}
530 </section>
531 </div>
532 );
533}
534
535function StatusRow({
536 icon,
537 title,
538 children,
539}: {
540 icon: React.ReactNode;
541 title: string;
542 children?: React.ReactNode;
543}) {
544 return (
545 <div className="flex gap-3 px-4 py-3 text-sm">
546 <span className="mt-0.5 shrink-0">{icon}</span>
547 <div className="min-w-0">
548 <p className="font-medium">{title}</p>
549 {children && <div className="mt-0.5 text-muted">{children}</div>}
550 </div>
551 </div>
552 );
553}
554
Issues and pull requests replace intents and attempts555export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
556 const {
557 pull,
558 issue,
559 comments,
Acceptance checks in sandboxes, line comments and review verdicts560 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell561 overlaps,
562 behind,
563 reviewPending,
564 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request565 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs566 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents567 requiredChecks = [],
568 noChecks,
569 canAddCi,
570 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains571 mergeable = "unknown",
572 conflicts = [],
573 earlierChecks = [],
574 workflowJobs,
Project dependencies: addresses, preview stacks, Affects, and agents who know575 affects,
576 preview,
Pull requests say where they are live; the project menu shows what is coming577 build,
578 stacked,
Agents as a team: lifecycle, merge queue, billing and a new shell579 landing,
580 stalled,
581 requireUpToDate,
582 mergeQueue,
583 requiredApprovals,
584 canIgnoreChecks,
585 canUpdate,
586 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'587 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell588 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar589 teams,
590 codeOwners,
Issues and pull requests replace intents and attempts591 tab,
592 session,
593 comparison,
594 viewer,
595 canMerge,
596 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look597 canRun,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar598 defaultBranch: repoDefault,
Issues and pull requests replace intents and attempts599 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar600 // The branch it merges into: what every line below names.
601 const defaultBranch = pull.base ?? repoDefault;
Issues and pull requests replace intents and attempts602 const base = `/${params.owner}/${params.repo}`;
603 const here = `${base}/pull/${pull.number}`;
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent604 // g1t, on a pull request it made: the person who asked for it is a line below.
605 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'606 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents607 <ChecksSection
608 run={checks}
609 required={requiredChecks}
610 statuses={statuses}
611 jobs={jobs}
612 pull={pull}
613 base={base}
614 earlier={earlierChecks}
615 canRerunWorkflows={canMerge}
616 settingsUrl={canProtect ? `${base}/settings/branches` : null}
617 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'618 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents619 />
620 );
Merge branch 'worktree-agent-aaf03bdceac799c89'621 const addresses = useAddresses();
Pull requests from branches622 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'623 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
624 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Issues and pull requests replace intents and attempts625 const active = pull.status === "draft" || pull.status === "open";
626
Acceptance checks in sandboxes, line comments and review verdicts627 // Follow an agent at work, or checks in progress, without a manual reload.
Issues and pull requests replace intents and attempts628 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents629 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts630 const reviews = verdicts(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents631 // What stands between this pull request and a merge, if anything: a
632 // required check that has not passed, or the merge queue taking it out.
633 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
634 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell635 // Pull requests for other issues changing the same files will conflict;
636 // ones for the same issue are alternatives, and expected to.
637 const collisions = overlaps.filter((other) => other.issue == null || other.issue !== pull.issue);
638 const review = {
639 changesUrl: here + "?tab=changes",
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights640 // Nobody reviews their own pull request, nor one g1t made for them.
641 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell642 };
643 // Everyone whose review was asked for, then anyone who reviewed unasked.
644 const reviewerNames = [
645 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
646 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar647 const teamReviewers = pull.teamReviewers ?? [];
648 // Teams to ask: the workspace's, and any asked already.
649 const teamChoices = [
650 ...teams,
651 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
652 ];
653 // Branch protection wants code owners' approval it does not have yet.
654 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files655 // A catch-up: the click shows at once; the answer says whether it is
656 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'657 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files658 const catchUp = actionData?.action === "update" ? actionData : null;
659 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
660 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
661 // Pushed already: followed until the pull request's head is the merge,
662 // so its checks show starting again, for a minute at most.
663 const settling =
664 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
665 // Followed until it is pushed, or for so long, never longer.
666 const catchingUp =
667 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell668 // g1t is taking a step of its own accord, so the page will change.
669 const moving =
670 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains671 // Whether it merges cleanly is being worked out, so the box will change.
672 const probing = active && mergeable === "checking";
673 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending674 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains675 // Why the merge button cannot be pressed, if it cannot.
676 const mergeBlocked = conflicting
677 ? "Resolve the conflicts first."
678 : probing
679 ? "Waiting to find out whether it merges cleanly."
680 : behind && requireUpToDate
681 ? `This repository requires it to be up to date with ${defaultBranch} first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents682 : unchecked && !canIgnoreChecks
683 ? `The checks ${defaultBranch} requires have to pass first.`
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar684 : ownersMissing
685 ? "Code owners have to approve first."
686 : null;
Issues and pull requests replace intents and attempts687
688 return (
Agents as a team: lifecycle, merge queue, billing and a new shell689 // The changes get the whole width; people and settings are a tab away.
Pull requests: title and tabs across the full width, with counts and the size of the change690 <div>
691 {/* The title and the tabs run the full width; what is happening on
692 the pull request sits at the top of Conversation. */}
693 <header>
Issues and pull requests replace intents and attempts694 <h2 className="text-2xl font-semibold tracking-tight text-balance">
695 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
696 </h2>
697 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
698 <PullState status={pull.status} />
699 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell700 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent701 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell702 size={18}
703 />
Issues and pull requests replace intents and attempts704 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains705 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent706 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains707 className="font-medium text-fg hover:underline"
708 />{" "}
Pull requests from branches709 {pull.status === "merged" ? "merged" : "wants to merge"}
710 {pull.branch && (
711 <>
712 {" "}
713 <span className="font-mono text-fg">{pull.branch}</span>
714 </>
715 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar716 into{" "}
717 <BaseBranch
718 base={defaultBranch}
719 head={pull.fork ? null : pull.branch}
720 canChange={loaderData.canChangeBase && active}
721 branchesUrl={`${base}/branches.json`}
722 />
Issues and pull requests replace intents and attempts723 </span>
724 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar725 {(pull.labels ?? []).map((name) => (
726 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
727 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent728 {opener.requestedBy && (
729 <span className="text-xs">
730 requested by{" "}
731 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
732 </span>
733 )}
734 {/* A pull request from a branch was made by its author, not an agent;
735 one g1t made already says so. */}
736 {!pull.branch && !opener.requestedBy && (
Pull requests from branches737 <span className="flex items-center gap-1.5 font-mono text-xs">
738 <Bot size={14} />
739 {pull.agent}
740 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
741 </span>
742 )}
Issues and pull requests replace intents and attempts743 </div>
744
Acceptance checks in sandboxes, line comments and review verdicts745 {reviews.length > 0 && (
746 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
747 {reviews.map(({ reviewer, verdict }) => (
748 <span
749 key={reviewer}
750 className={`flex items-center gap-1.5 ${
751 verdict === "approve" ? "text-accent" : "text-danger"
752 }`}
753 >
754 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
755 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
756 <span className="font-medium">{reviewer}</span>
757 </span>
758 ))}
759 </p>
760 )}
761
Tab bars on pull requests, search and profiles scroll without showing a scrollbar762 <nav className="mt-6 flex items-end gap-1 overflow-x-auto overflow-y-hidden border-b border-line [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
Issues and pull requests replace intents and attempts763 <TabLink to={here} active={tab === "conversation"}>
764 <MessageSquare size={15} />
765 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change766 <TabCount n={comments.length} />
Issues and pull requests replace intents and attempts767 </TabLink>
768 <TabLink to={here + "?tab=session"} active={tab === "session"}>
769 <MessagesSquare size={15} />
770 Session
771 </TabLink>
772 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
773 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change774 Files changed
775 <TabCount n={pull.files.length} />
Issues and pull requests replace intents and attempts776 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change777 {pull.files.length > 0 && (
778 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
779 <span className="text-accent">+{pull.files.reduce((sum, file) => sum + file.additions, 0).toLocaleString("en-US")}</span>{" "}
780 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
781 </span>
782 )}
Issues and pull requests replace intents and attempts783 </nav>
Pull requests: title and tabs across the full width, with counts and the size of the change784 </header>
785 <div className={`mt-6 grid gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
786 <div className="min-w-0">
787 {/* What is happening on it now: its stage, its agent, what people told
788 the agent, and what else touches the same files. On the conversation,
789 so the tabs sit right under the title. */}
790 {tab === "conversation" && (
791 <div>
792 {issue && (
793 <Link
794 to={`${base}/issues/${issue.number}`}
795 className="mt-4 flex items-center gap-2.5 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm transition-colors hover:border-line-strong"
796 >
797 <IssueIcon issue={issue} />
798 <span className="text-muted">
799 {pull.status === "merged" && issue.resolvedBy === pull.number
800 ? "Resolved"
801 : "For issue"}
802 </span>
803 <span className="truncate font-medium">{issue.title}</span>
804 <span className="text-faint">#{issue.number}</span>
805 {issue.pullCount > 1 && (
806 <span className="ml-auto shrink-0 text-xs text-faint">
807 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation808 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change809 )}
810 </Link>
811 )}
812 {pull.status === "merged" && (
813 <p className="mt-4 flex items-center gap-2.5 rounded-xl border border-merged/40 bg-merged/5 px-4 py-2.5 text-sm">
814 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell815 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change816 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
817 <span className="font-medium">{pull.mergedBy}</span>{" "}
818 {/* Not an account: the repository's settings said to merge it. */}
819 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
820 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
821 {pull.headCommit && (
822 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell823 )}
824 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change825 </p>
826 )}
827 {pull.supersededBy != null && (
828 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
829 Closed because{" "}
830 <Link
831 to={`${base}/pull/${pull.supersededBy}`}
832 className="font-medium text-fg hover:underline"
833 >
834 #{pull.supersededBy}
835 </Link>{" "}
836 was merged for this issue instead.
837 </p>
838 )}
839 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
840 {/* The agent on it: who, doing what this minute, for how long, at what cost. */}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step841 <AgentPanel
842 owner={params.owner}
843 repo={params.repo}
844 number={pull.number}
845 stage={lifecycle?.stage ?? null}
846 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'847 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step848 />
Pull requests: title and tabs across the full width, with counts and the size of the change849
850 {/* Steering: while its agent works, people can tell it things. */}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look851 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change852 pull.runtime === "hosted" &&
853 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'854 // Keyed by the messages so the box empties once one shows below.
855 <Form method="post" className="mt-4 rounded-2xl bg-surface p-4 ring-1 ring-merged/30" key={messages.length}>
Pull requests: title and tabs across the full width, with counts and the size of the change856 <p className="flex items-center gap-2 text-sm font-medium">
857 <Sparkles size={15} className="text-merged" />
858 Message the agent
859 </p>
860 <p className="mt-1 text-xs text-muted">
861 A correction, a hint, a change of plan. It reads it at its next step, without
862 starting over.
863 </p>
864 <div className="mt-3 flex gap-2">
865 <input type="hidden" name="action" value="message" />
866 <input
867 name="body"
868 required
869 autoComplete="off"
870 data-1p-ignore
871 placeholder="Keep the old flag working too…"
872 className="h-9 min-w-0 grow rounded-md bg-bg px-3 text-sm ring-1 ring-line outline-none placeholder:text-faint focus:ring-merged/60"
Agents as a team: lifecycle, merge queue, billing and a new shell873 />
Merge branch 'worktree-agent-a985c01f43538af01'874 <SubmitButton match={{ action: "message" }} pending="Sending…">
875 Send
876 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change877 </div>
878 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell879 )}
Pull requests: title and tabs across the full width, with counts and the size of the change880 {messages.length > 0 && (
881 <ul className="mt-3 space-y-1.5">
882 {messages.map((message) => (
883 <li key={message.id} className="flex items-start gap-2 text-sm">
884 <Avatar name={message.author} size={18} />
885 <span className="min-w-0 grow">
886 <span className="font-medium">
887 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
888 </span>{" "}
889 <span className="text-muted">
890 {message.kind === "question"
891 ? "asked:"
892 : message.kind === "handoff"
893 ? "handed over:"
894 : message.kind === "answer"
895 ? "answered:"
896 : "to the agent:"}
897 </span>{" "}
898 {message.body}
899 {message.answer && (
900 <span className="mt-1 block border-l-2 border-merged/40 pl-2 text-muted">
901 {message.declined ? "Declined: " : "Answer: "}
902 {message.answer}
903 </span>
904 )}
905 </span>
906 <span className={`shrink-0 text-xs ${message.deliveredAt ? "text-accent" : "text-faint"}`}>
907 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
908 </span>
909 </li>
910 ))}
911 </ul>
912 )}
913 {collisions.length > 0 && active && (
914 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
915 <p className="flex items-center gap-2.5 font-medium">
916 <Radar size={16} className="shrink-0 text-info" />
917 Other work is changing the same files
918 </p>
919 <ul className="mt-2 space-y-1.5">
920 {collisions.map((other) => (
921 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
922 <Link
923 to={`${base}/pull/${other.number}`}
924 className="font-medium text-fg hover:underline"
925 >
926 {other.title} <span className="font-normal text-faint">#{other.number}</span>
927 </Link>
928 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
929 </li>
930 ))}
931 </ul>
932 <p className="mt-2 text-xs text-faint">
933 Whichever merges second will have to catch up, and may conflict.
934 </p>
935 </div>
936 )}
937 </div>
938 )}
939 <div className="mt-5">
940 {comparison ? (
941 <DiffView
942 comparison={comparison}
943 review={{
944 comments: comments.filter((comment) => comment.path),
945 canComment: Boolean(viewer),
946 }}
947 />
948 ) : tab === "session" ? (
949 session.length === 0 ? (
950 <EmptyState title="Nothing recorded yet">
951 The agent's prompts, reasoning and tool calls appear here as it
952 works.
953 </EmptyState>
954 ) : (
955 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
956 {session.map((entry) => (
957 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
958 ))}
959 </ol>
960 )
961 ) : (
962 <div className="space-y-4">
963 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent964 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change965 at={pull.createdAt}
966 action={
967 <span>
968 opened this pull request
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent969 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change970 <>
971 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent972 for{" "}
973 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
974 </>
975 ) : !pull.branch && (
976 <>
977 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change978 with <span className="font-mono text-xs">{pull.agent}</span>
979 </>
980 )}
981 </span>
982 }
983 >
984 {pull.body ? (
985 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
986 ) : (
987 <p className="text-sm text-muted">
988 {working
989 ? "No description yet. It is written when the pull request is marked ready for review."
990 : "No description."}
991 </p>
992 )}
993 {canManage && pull.status === "open" && (
994 <details className="mt-3 border-t border-line pt-3 text-sm">
995 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
996 Edit description
997 </summary>
998 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'999 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1000 <Textarea
1001 name="summary"
1002 rows={6}
1003 placeholder="What changed and why"
1004 defaultValue={pull.body ?? ""}
1005 />
Merge branch 'worktree-agent-a985c01f43538af01'1006 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1007 Save
1008 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1009 </Form>
1010 </details>
1011 )}
1012 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1013
Pull requests: title and tabs across the full width, with counts and the size of the change1014 <CommentList comments={comments} review={review} base={base} />
Agents as a team: lifecycle, merge queue, billing and a new shell1015
Merge branch 'worktree-agent-aca4790ea223aa235'1016 {(preview || build) && <DeploymentCard preview={preview} build={build} stacked={stacked} stacking={affects.length > 0} base={base} />}
Pull requests say where they are live; the project menu shows what is coming1017
Pull requests: title and tabs across the full width, with counts and the size of the change1018 {pull.status === "draft" && (
1019 <StatusBox>
1020 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1021 It is still being worked on. It can be reviewed and merged once it is
1022 marked ready.
1023 </StatusRow>
1024 {canManage && (
1025 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1026 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1027 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1028 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1029 Mark ready for review
1030 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1031 </Form>
1032 )}
1033 </StatusBox>
1034 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1035
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1036 {codeOwners && active && (
1037 <div className="flex gap-3">
1038 <span className="hidden w-8 shrink-0 sm:block" />
1039 <div className="min-w-0 grow">
1040 <PullCodeOwnersPanel
1041 owners={codeOwners}
1042 base={base}
1043 branch={defaultBranch}
1044 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1045 />
1046 </div>
1047 </div>
1048 )}
1049
Pull requests: title and tabs across the full width, with counts and the size of the change1050 {pull.status === "open" && (
1051 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1052 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1053 <Suspense fallback={checksSection({}, true)}>
1054 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1055 </Suspense>
1056 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1057 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1058 icon={
1059 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1060 <CircleSlash size={16} className="text-danger" />
1061 ) : reviews.length > 0 ? (
1062 <CircleCheck size={16} className="text-accent" />
1063 ) : (
1064 <MessageSquare size={16} className="text-faint" />
1065 )
1066 }
1067 title={
1068 reviews.length === 0
1069 ? "No reviews yet"
1070 : reviews
1071 .map(
1072 ({ reviewer, verdict }) =>
1073 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1074 )
1075 .join(" · ")
1076 }
Agents as a team: lifecycle, merge queue, billing and a new shell1077 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1078 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1079 {requiredApprovals > 0 &&
1080 `This repository requires ${requiredApprovals} approving ${
1081 requiredApprovals === 1 ? "review" : "reviews"
1082 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1083 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1084 {codeOwners?.required && (
1085 <StatusRow
1086 icon={
1087 ownersMissing ? (
1088 <CircleSlash size={16} className="text-warn" />
1089 ) : (
1090 <CircleCheck size={16} className="text-accent" />
1091 )
1092 }
1093 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1094 >
1095 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1096 {ownersMissing && "This repository requires their review before merging."}
1097 </StatusRow>
1098 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1099 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1100 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1101 icon={<Loader size={16} className="animate-spin text-accent" />}
1102 title="Merging"
1103 >
1104 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1105 lands as soon as that is done. Watch it in the Session tab.
1106 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1107 ) : agentCatchUp && behind ? (
1108 <CatchUpProgress
1109 owner={params.owner}
1110 repo={params.repo}
1111 number={pull.number}
1112 defaultBranch={defaultBranch}
1113 behind={behind}
1114 update={agentCatchUp.update}
1115 startedAt={agentCatchUp.startedAt}
1116 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1117 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1118 />
Pull requests: title and tabs across the full width, with counts and the size of the change1119 ) : conflicting ? (
1120 <ConflictsSection
1121 conflicts={conflicts}
1122 pull={pull}
1123 owner={params.owner}
1124 repo={params.repo}
1125 defaultBranch={defaultBranch}
1126 changesUrl={here + "?tab=changes"}
1127 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1128 resolving={catchUpPending}
1129 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1130 />
Pull requests: title and tabs across the full width, with counts and the size of the change1131 ) : probing ? (
1132 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1133 ) : behind ? (
1134 <StatusRow
1135 icon={<GitPullRequestArrow size={16} className="text-info" />}
1136 title={`${defaultBranch} has moved since this was made`}
1137 >
1138 {mergeable === "clean" && "It has no conflicts with it. "}
1139 {requireUpToDate
1140 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1141 : "That does not stop it merging: it is brought up to date as part of the merge."}
Catching up with main takes seconds when the two sides touched different files1142 {canUpdate && (
1143 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1144 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1145 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1146 Catch up with {defaultBranch} now
1147 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1148 </Form>
1149 )}
Catching up with main takes seconds when the two sides touched different files1150 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1151 </StatusRow>
1152 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1153 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1154 <StatusRow
1155 icon={<CircleCheck size={16} className="text-accent" />}
Catching up with main takes seconds when the two sides touched different files1156 title={
1157 (caughtUp && !caughtUp.already) || agentCatchUp
1158 ? `Brought up to date with ${defaultBranch}`
1159 : `Up to date with ${defaultBranch}`
1160 }
1161 >
1162 {caughtUp && !caughtUp.already && (
1163 <>
1164 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1165 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1166 again on the new commit.
1167 </>
1168 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1169 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1170 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1171 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1172 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1173 )
1174 )}
1175 {stalled && !lifecycle && (
1176 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1177 {stalled}
1178 </StatusRow>
1179 )}
1180 {canMerge && lifecycle?.stage === "queued" && (
1181 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1182 <ButtonLink to={`${base}/queue`}>
1183 <Layers size={15} />
1184 See the queue
1185 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1186 <SubmitButton variant="quiet" name="action" value="unqueue" pending="Removing…">
Pull requests: title and tabs across the full width, with counts and the size of the change1187 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1188 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1189 </Form>
1190 )}
1191 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1192 <Form method="post" className="space-y-3 px-4 py-3">
1193 {issue?.state === "open" && (
1194 <CheckboxOption
1195 name="keepIssueOpen"
1196 label={`Keep #${issue.number} open. This is only part of the work.`}
1197 labelClassName="text-xs text-muted"
1198 />
1199 )}
1200 {unchecked && canIgnoreChecks && (
1201 <CheckboxOption
1202 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1203 label={`Bypass the required checks: merge although ${requiredFailed ? "one failed" : "they have not all passed"}.`}
Pull requests: title and tabs across the full width, with counts and the size of the change1204 labelClassName="text-xs text-muted"
1205 />
1206 )}
1207 <div className="flex flex-wrap items-center gap-3">
Merge branch 'worktree-agent-a985c01f43538af01'1208 <SubmitButton
Pull requests: title and tabs across the full width, with counts and the size of the change1209 variant="accent"
1210 name="action"
1211 value="merge"
1212 disabled={mergeBlocked != null}
1213 title={mergeBlocked ?? undefined}
Merge branch 'worktree-agent-a985c01f43538af01'1214 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
Pull requests: title and tabs across the full width, with counts and the size of the change1215 >
1216 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1217 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
Merge branch 'worktree-agent-a985c01f43538af01'1218 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1219 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1220 {mergeBlocked ?? (mergeQueue
1221 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1222 : issue?.state === "open"
1223 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1224 : `Lands its commits on ${defaultBranch}.`)}
1225 </span>
1226 </div>
1227 {actionData?.action === "merge" && (
1228 <ErrorText>{actionData.error}</ErrorText>
1229 )}
1230 </Form>
1231 )}
1232 </StatusBox>
1233 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1234
Pull requests: title and tabs across the full width, with counts and the size of the change1235 <CommentForm
1236 author={viewer?.username ?? null}
1237 resetKey={comments.length}
1238 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1239 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1240 />
1241 {canManage && active && (
1242 <Form method="post" className="flex justify-end">
Merge branch 'worktree-agent-a985c01f43538af01'1243 <SubmitButton variant="quiet" name="action" value="close" pending="Closing…">
Pull requests: title and tabs across the full width, with counts and the size of the change1244 Close pull request
Merge branch 'worktree-agent-a985c01f43538af01'1245 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1246 </Form>
1247 )}
1248 {actionData &&
Merge branch 'worktree-agent-a985c01f43538af01'1249 !["merge", "comment", "stack", "rerun-failed", "rerun-workflow", "update", "agent-review", "reviewers", "assign"].includes(
Pull requests: title and tabs across the full width, with counts and the size of the change1250 String(actionData.action),
1251 ) && <ErrorText>{actionData.error}</ErrorText>}
1252 </div>
1253 )}
1254 {actionData?.action === "comment" && (
1255 <div className="mt-2">
1256 <ErrorText>{actionData.error}</ErrorText>
1257 </div>
1258 )}
1259 </div>
1260 </div>
1261
1262 <aside className={tab === "changes" ? "hidden" : "space-y-6"}>
1263 {/* An open pull request shows its checks in full in the merge box. */}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1264 {pull.status !== "open" && <WorkflowStatuses statuses={statuses} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1265 {affects.length > 0 && (
1266 <section>
1267 <h3 className="flex items-center gap-1.5 text-sm font-medium">
1268 <Network size={14} className="text-faint" />
1269 Affects
1270 </h3>
1271 <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1272 <ul className="mt-2 space-y-1 text-sm">
1273 {affects.map((project) => (
1274 <li key={project.slug} className="flex items-center justify-between gap-2">
1275 <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1276 {project.name}
1277 </Link>
1278 {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1279 </li>
1280 ))}
1281 </ul>
1282 {preview?.branch && canMerge && (
1283 <Form method="post" className="mt-3">
1284 <input type="hidden" name="action" value="stack" />
1285 <input type="hidden" name="branch" value={preview.branch} />
Merge branch 'worktree-agent-a985c01f43538af01'1286 <SubmitButton
1287 match={{ action: "stack" }}
1288 pending="Starting the builds…"
1289 className="flex w-full items-center justify-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-xs text-muted transition-colors hover:border-line-strong hover:text-fg disabled:opacity-50"
Pull requests: title and tabs across the full width, with counts and the size of the change1290 >
1291 Preview them against this change
Merge branch 'worktree-agent-a985c01f43538af01'1292 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell1293 </Form>
1294 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1295 {actionData?.action === "stack" &&
1296 ("notice" in actionData ? (
1297 <p className="mt-2 text-xs text-accent">{String(actionData.notice)}</p>
1298 ) : (
1299 <ErrorText>{actionData.error}</ErrorText>
1300 ))}
1301 </section>
Acceptance checks in sandboxes, line comments and review verdicts1302 )}
Issues and pull requests replace intents and attempts1303
Project dependencies: addresses, preview stacks, Affects, and agents who know1304 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1305 <h3 className="text-sm font-medium">Reviewers</h3>
1306 <ul className="mt-2 space-y-1.5 text-sm">
1307 {reviewerNames.map((name) => {
1308 const verdict = reviews.find(({ reviewer }) => reviewer === name)?.verdict;
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1309 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1310 return (
1311 <li key={name} className="flex items-center gap-2 px-1">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1312 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1313 <Sparkles size={16} className="shrink-0 text-accent" />
1314 ) : (
1315 <Avatar name={name} size={20} />
1316 )}
1317 <span className="grow truncate font-mono text-xs">{name}</span>
1318 {pending ? (
1319 <span className="flex items-center gap-1.5 text-xs text-muted">
1320 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1321 Reviewing
1322 </span>
1323 ) : verdict === "approve" ? (
1324 <span className="flex items-center gap-1 text-xs text-accent">
1325 <CircleCheck size={13} /> Approved
1326 </span>
1327 ) : verdict === "request_changes" ? (
1328 <span className="flex items-center gap-1 text-xs text-danger">
1329 <CircleSlash size={13} /> Changes requested
1330 </span>
1331 ) : (
1332 <span className="text-xs text-faint">Review requested</span>
1333 )}
1334 </li>
1335 );
1336 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1337 {teamReviewers.map((team) => (
1338 <TeamReviewer key={team} team={team} />
1339 ))}
1340 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1341 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1342 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1343 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1344 {pull.status === "open" && canManage && (
1345 <div className="mt-3 space-y-2">
1346 {agentsEnabled && !reviewPending && (
1347 <Form method="post">
1348 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1349 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1350 <input key={name} type="hidden" name="reviewer" value={name} />
1351 ))}
1352 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1353 <SubmitButton variant="quiet" match={{ action: "agent-review" }} pending="Asking g1t…">
Pull requests: title and tabs across the full width, with counts and the size of the change1354 <Sparkles size={14} className="text-accent" />
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1355 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1356 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1357 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1358 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1359 </Form>
1360 )}
1361 <details>
1362 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1363 {teamChoices.length > 0 ? "Request review from people or teams" : "Request review from people"}
Pull requests: title and tabs across the full width, with counts and the size of the change1364 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1365 <Form method="post" className="mt-2 space-y-2" key={[...pull.reviewers, ...teamReviewers].join()}>
Pull requests: title and tabs across the full width, with counts and the size of the change1366 <input type="hidden" name="action" value="reviewers" />
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1367 {pull.reviewers.includes("g1t") && (
1368 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1369 )}
1370 <PeoplePicker
1371 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1372 members={members.filter((name) => name !== workOwner(pull).username)}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1373 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1374 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1375 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1376 {teamChoices.length > 0 && (
1377 <div className="space-y-1.5 border-t border-line pt-2">
1378 <p className="text-xs text-faint">Teams</p>
1379 {teamChoices.map((team) => (
1380 <CheckboxOption
1381 key={team.ref}
1382 name="reviewer"
1383 value={team.ref}
1384 defaultChecked={teamReviewers.includes(team.ref)}
1385 className="items-center"
1386 labelClassName="flex min-w-0 items-center gap-2"
1387 label={
1388 <>
1389 <Users size={14} className="shrink-0 text-faint" />
1390 <span className="truncate font-mono text-xs">@{team.ref}</span>
1391 </>
1392 }
1393 />
1394 ))}
1395 </div>
1396 )}
Merge branch 'worktree-agent-a985c01f43538af01'1397 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1398 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1399 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1400 </Form>
1401 </details>
1402 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1403 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1404 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1405 <ErrorText>{actionData?.error}</ErrorText>
1406 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1407 </section>
Acceptance checks in sandboxes, line comments and review verdicts1408
Pull requests: title and tabs across the full width, with counts and the size of the change1409 <section>
1410 <h3 className="text-sm font-medium">Assignees</h3>
1411 <ul className="mt-2 space-y-1.5 text-sm">
1412 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1413 <li key={name} className="flex items-center gap-2 px-1">
Pull requests: title and tabs across the full width, with counts and the size of the change1414 <Avatar name={name} size={20} />
Agents as a team: lifecycle, merge queue, billing and a new shell1415 <span className="grow truncate font-mono text-xs">{name}</span>
1416 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1417 ))}
1418 {pull.assignees.length === 0 && (
1419 <li className="px-1 text-xs text-faint">No one yet.</li>
1420 )}
1421 </ul>
1422 {viewer && canManage && active && (
1423 <div className="mt-3 space-y-2">
1424 {!pull.assignees.includes(viewer.username) && (
1425 <Form method="post">
1426 <input type="hidden" name="action" value="assign" />
1427 {pull.assignees.map((name) => (
1428 <input key={name} type="hidden" name="assignee" value={name} />
1429 ))}
1430 <input type="hidden" name="assignee" value={viewer.username} />
1431 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1432 <SubmitButton variant="quiet" name="who" value="self" pending="Assigning…">
Pull requests: title and tabs across the full width, with counts and the size of the change1433 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1434 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1435 </div>
1436 </Form>
1437 )}
1438 <details>
1439 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1440 Assign people
1441 </summary>
1442 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1443 <input type="hidden" name="action" value="assign" />
1444 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1445 <SubmitButton variant="quiet" name="who" value="picked" pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1446 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1447 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1448 </Form>
1449 </details>
1450 </div>
Issues and pull requests replace intents and attempts1451 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1452 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1453 </section>
Issues and pull requests replace intents and attempts1454
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1455 <LabelsBox
1456 labels={loaderData.labels}
1457 chosen={pull.labels ?? []}
1458 canEdit={canManage && active}
1459 canCreate={loaderData.canTriage}
1460 manageUrl={loaderData.canTriage ? `${base}/labels` : undefined}
1461 />
1462 <MilestoneBox
1463 milestones={loaderData.milestones}
1464 current={pull.milestone}
1465 canEdit={loaderData.canTriage}
1466 base={base}
1467 />
1468
Pull requests: title and tabs across the full width, with counts and the size of the change1469 <section>
1470 <h3 className="text-sm font-medium">Working copy</h3>
1471 {pull.branch ? (
1472 <>
1473 <p className="mt-1 text-xs text-muted">
1474 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1475 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1476 repository. Pushes to it show up here.
1477 </p>
1478 <div className="mt-2">
1479 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1480 </div>
1481 </>
1482 ) : (
1483 <>
1484 <p className="mt-1 text-xs text-muted">
1485 This fork belongs to the pull request. Pushes to it show up here.
1486 </p>
1487 <div className="mt-2">
1488 <CopyLine text={`git clone ${remote}`} />
1489 </div>
1490 </>
1491 )}
1492 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1493 <GitCommitHorizontal size={13} />
1494 {pull.headCommit?.slice(0, 12) ?? "no commits pushed yet"}
1495 </p>
1496 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1497 {loaderData.viewer && (
1498 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1499 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1500 </aside>
1501 </div>
Issues and pull requests replace intents and attempts1502 </div>
1503 );
1504}
Pull requests say where they are live; the project menu shows what is coming1505
1506/** Where the pull request's change is live, as GitHub shows a deployment. */
1507function DeploymentCard({
1508 preview,
1509 build,
1510 stacked,
Merge branch 'worktree-agent-aca4790ea223aa235'1511 stacking,
Pull requests say where they are live; the project menu shows what is coming1512 base,
1513}: {
1514 preview: LiveApp | null;
1515 build: Deployment | null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents1516 stacked: Promise<Stacked[]>;
Merge branch 'worktree-agent-aca4790ea223aa235'1517 /** Projects use this one, so previews built against it may be on their way. */
1518 stacking: boolean;
Pull requests say where they are live; the project menu shows what is coming1519 base: string;
1520}) {
1521 const building = build?.status === "queued" || build?.status === "building";
1522 const failed = build?.status === "failed";
1523 return (
1524 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1525 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1526 <span
1527 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
1528 failed ? "text-danger ring-danger/40" : building ? "text-warn ring-warn/40" : "text-accent ring-accent/40"
1529 }`}
1530 >
1531 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1532 </span>
1533 <div className="min-w-0 grow">
1534 <p className="text-sm font-medium">
1535 {building
1536 ? preview
1537 ? "Deploying the latest push. The last preview is still live."
1538 : "Deploying a preview"
1539 : failed
1540 ? "The preview failed to deploy"
1541 : "This branch is live"}
1542 </p>
1543 {preview ? (
1544 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1545 {preview.url.replace(/^https?:[/][/]/, "")}
1546 </a>
1547 ) : (
1548 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1549 )}
1550 </div>
1551 <div className="flex shrink-0 items-center gap-2">
1552 {build && (
1553 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1554 {failed ? "See why" : "Build log"}
1555 </Link>
1556 )}
1557 {preview && (
1558 <a
1559 href={preview.url}
1560 className="inline-flex items-center gap-1.5 rounded-md bg-accent px-3 py-1.5 text-xs font-medium text-bg hover:opacity-90"
1561 >
1562 Visit preview
1563 <ArrowUpRight size={13} />
1564 </a>
1565 )}
1566 </div>
1567 </div>
1568 {preview && (
1569 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1570 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1571 {preview.branch && ` · from ${preview.branch}`}
1572 </p>
1573 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1574 {/* Read after the page: the card's own part shows at once. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1575 <Suspense
1576 fallback={
1577 stacking && (
1578 <Loading className="border-t border-line px-4 py-2.5 text-xs">
1579 <SkeletonLine className="w-40" />
1580 <SkeletonLine className="mt-1.5 w-64 max-w-full" />
1581 </Loading>
1582 )
1583 }
1584 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1585 <Await resolve={stacked}>
1586 {(stacked) =>
1587 stacked.length > 0 && (
Merge branch 'worktree-agent-aca4790ea223aa235'1588 <div className="animate-fade-in border-t border-line px-4 py-2.5">
Fast pages, required checks on the branch, self-hosted runners, honest incidents1589 <p className="text-xs text-muted">Built against this change:</p>
1590 <ul className="mt-1.5 space-y-1">
1591 {stacked.map((entry) => (
1592 <li key={entry.slug} className="flex items-center gap-2 text-xs">
1593 <span className="font-medium">{entry.name}</span>
1594 <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1595 {entry.url.replace(/^https?:[/][/]/, "")}
1596 </a>
1597 </li>
1598 ))}
1599 </ul>
1600 </div>
1601 )
1602 }
1603 </Await>
1604 </Suspense>
Pull requests say where they are live; the project menu shows what is coming1605 </section>
1606 );
1607}

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