Skip to content
1,615 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";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit43import { highlightFirstFiles } from "../../lib/highlight.server";
Social cards for every page: og.g1t.sh44import { 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-agent45import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'46import { cloneUrl, useAddresses } from "../../lib/addresses";
Issues and pull requests replace intents and attempts47import { DiffView } from "../../components/diff-view";
Agents as a team: lifecycle, merge queue, billing and a new shell48import { LifecyclePanel } from "../../components/lifecycle";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains49import { AgentPanel } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar50import { BaseBranch } from "../../components/base-branch";
51import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Issues and pull requests replace intents and attempts52import { Markdown } from "../../components/markdown";
53import {
54 Avatar,
Agents as a team: lifecycle, merge queue, billing and a new shell55 ButtonLink,
Issues and pull requests replace intents and attempts56 CopyLine,
57 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look58 ComputeNote,
Issues and pull requests replace intents and attempts59 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'60 SubmitButton,
Issues and pull requests replace intents and attempts61 Textarea,
62 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'63 usePending,
Issues and pull requests replace intents and attempts64} from "../../components/ui";
Workspace names and icons, and a component kit for every control65import { CheckboxOption } from "../../components/ui/checkbox";
Merge branch 'worktree-agent-aca4790ea223aa235'66import { Loading, SkeletonLine } from "../../components/ui/skeleton";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit67import { TabStrip } from "../../components/ui/tab-strip";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs68import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents69import { AddCiPrompt } from "../../components/add-ci";
Agents as a team: lifecycle, merge queue, billing and a new shell70import {
71 CommentForm,
72 CommentList,
73 IssueIcon,
74 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains75 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell76 PullState,
77 TimelineItem,
78 verdicts,
79} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files80import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar81import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files82import { 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 put83import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look84import { computeNoteFor } from "../../lib/compute.server";
Merge branch 'worktree-agent-aca4790ea223aa235'85import { actions, agents, deployments, identity, inbox, projects, repos, work } from "../../lib/services.server";
Issues and pull requests replace intents and attempts86import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings87import { accessTo, refusal, repoFor } from "../../lib/access.server";
88import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending89import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Issues and pull requests replace intents and attempts90
91const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
92const TABS = ["conversation", "session", "changes"] as const;
93type Tab = (typeof TABS)[number];
94
Social cards for every page: og.g1t.sh95export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
96 const pull = loaderData?.pull;
97 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
98 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
99 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-agent100 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh101 return page(args, {
102 title: `${title}${params.owner}/${params.repo} · g1t`,
103 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent104 ? `${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.sh105 : null,
106 // The card shows the title and the state.
107 version: pull ? [pull.title, pull.status] : undefined,
108 type: "article",
109 });
Issues and pull requests replace intents and attempts110}
111
112export async function loader({ params, context, request }: Route.LoaderArgs) {
113 const viewer = getViewer(context);
114 const path = { namespace: params.owner, name: params.repo };
115 const number = Number(params.number);
116 const asked = new URL(request.url).searchParams.get("tab");
117 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
118
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look119 // Members of the workspace pick reviewers and assignees from its people;
120 // what the viewer may do here goes by their role on the repository.
Agents as a team: lifecycle, merge queue, billing and a new shell121 const member = (viewer?.workspaces ?? []).some(
122 (membership) => membership.slug === params.owner,
123 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents124 // Everything starts at once. Only what the viewer's role decides waits
125 // for the repository, which the layout is looking up in this request too
126 // (lib/access.server.ts), and only what the pull request decides waits
127 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know128 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents129 const access = accessTo(context, params);
130 const pullFound = work.getPull(path, number, viewer);
131 const deps = projects.dependencies(params.owner, params.repo, viewer);
132 // Awaited below, unless the pull request is missing first.
133 deps.catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar134 // Its labels and milestone, with the repository's to choose from.
135 const labelsFound = work.listLabels(path, viewer);
136 const milestonesFound = work.listMilestones(path, viewer);
137 labelsFound.catch(() => null);
138 milestonesFound.catch(() => null);
139 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 incidents140 access,
141 pullFound,
Issues and pull requests replace intents and attempts142 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell143 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing144 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell145 // A member picks reviewers and assignees from the workspace's people.
146 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-agent147 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents148 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
149 tab === "session" ? work.readSession(path, number, viewer) : null,
150 // Its preview, for people with a role here: beside the rest, not after.
151 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings152 // Whether the viewer hears of it, for the sidebar's Notifications.
153 viewer
154 ? repoFor(context, params).then((found) =>
155 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
156 )
157 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'158 // Its agent's latest runs, for the Agent panel: with the page, not after it.
159 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 bar160 // A member asks the workspace's teams to review, and mentions them.
161 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Issues and pull requests replace intents and attempts162 ]);
163 if (!found.ok) {
164 // Issues and pull requests share numbers; this one may be an issue.
165 const issue = await work.getIssue(path, number, viewer);
166 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 put167 throw notFound("pull");
Issues and pull requests replace intents and attempts168 }
169 const { pull } = found.value;
Pull requests from branches170 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains171 // The jobs of each workflow run on its head, to list checks job by job.
172 // 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 incidents173 // Streamed: the checks show by status first, then job by job.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains174 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 incidents175 const workflowJobs =
176 tab === "conversation" && pull.status === "open" && runIds.length > 0
177 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
178 const jobs: Record<string, Job[]> = {};
179 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
180 return jobs;
181 })
182 .catch(() => ({}) as Record<string, Job[]>)
183 : Promise.resolve({} as Record<string, Job[]>);
184 // No checks at all on an open pull request: whether that is because the
185 // repository has no workflows, to offer a starter one.
186 const unchecked =
187 tab === "conversation" &&
188 pull.status === "open" &&
189 (found.value.statuses ?? []).length === 0 &&
190 (found.value.requiredChecks ?? []).length === 0;
191 const [comparison, used, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit192 // The first screens of it highlighted here, so their colours do not pop in.
193 tab === "changes"
194 ? repos
195 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
196 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
197 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents198 deps,
199 unchecked
200 ? actions
201 .workflows(path, viewer)
202 .then((found) => found.ok && found.value.length === 0)
203 .catch(() => false)
204 : false,
Issues and pull requests replace intents and attempts205 ]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents206 const affects = used.ok ? used.value.usedBy : [];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar207 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
208 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
209 // The default branch's protection covers pull requests into it only.
210 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Issues and pull requests replace intents and attempts211 return {
212 ...found.value,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar213 labels: labels.ok ? labels.value : [],
214 milestones: milestones.ok ? milestones.value : [],
215 // Labelling and milestones: Triage and up; its author, its labels.
216 canTriage: can.triage,
217 // The branch it merges into: Write and up.
218 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains219 workflowJobs,
Issues and pull requests replace intents and attempts220 tab,
221 session: session?.ok ? session.value : [],
222 // An empty comparison if it could not be made.
223 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
224 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look225 // Write and up can merge.
226 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights227 // Triage and up manage anyone's pull request; its author, their own,
228 // and whoever asked g1t for one, that one.
229 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 look230 // Telling its agent things, and re-running checks, spend compute: Write and up.
231 canRun: can.run,
Agents as a team: lifecycle, merge queue, billing and a new shell232 // 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 rights233 // whoever it is for (who asked g1t for it, or its author), a branch
234 // from anyone who can push.
235 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell236 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look237 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings238 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'239 // As the project's agents.json has them; left out, the panel fetches them.
240 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell241 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar242 // `workspace/slug` and name of each team the viewer can see.
243 teams: teamList?.ok ? teamList.value.map((team) => ({ ref: `${team.workspace}/${team.slug}`, name: team.name })) : [],
244 requireUpToDate: protectedBase && settings.ok && settings.value.requireUpToDate,
245 mergeQueue: protectedBase && settings.ok && settings.value.mergeQueue,
246 requiredApprovals: protectedBase && settings.ok ? settings.value.requiredApprovals : 0,
Agents as a team: lifecycle, merge queue, billing and a new shell247 canIgnoreChecks: !settings.ok || settings.value.allowIgnoringChecks,
Fast pages, required checks on the branch, self-hosted runners, honest incidents248 noChecks,
249 // Who may open the pull request that adds CI: anyone who can push.
250 canAddCi: can.push,
251 // Who may choose the required checks.
252 canProtect: can.manage_protection,
Issues and pull requests replace intents and attempts253 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Fast pages, required checks on the branch, self-hosted runners, honest incidents254 affects,
255 // Streamed: the conversation shows first, the preview card after.
256 ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer),
Issues and pull requests replace intents and attempts257 };
258}
259
Pull requests say where they are live; the project menu shows what is coming260/**
261 * Where this pull request is live: its preview (or its latest build, while
262 * one is going or after one failed), and the previews of the projects
263 * that use this one, built against it.
264 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents265function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming266 list: { deployments: Deployment[]; live: LiveApp[] } | null,
267 number: number,
268 owner: string,
269 affects: { slug: string; name: string }[],
270 viewer: Viewer,
Fast pages, required checks on the branch, self-hosted runners, honest incidents271): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } {
272 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
273 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 coming274 const branch = preview?.branch ?? build?.branch ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents275 // Streamed: another lookup per project, shown in the card once known.
276 const stacked: Promise<Stacked[]> = branch
277 ? Promise.all(
278 affects.slice(0, 5).map(async (project) => {
279 const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null);
280 const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
281 return app ? { name: project.name, slug: project.slug, url: app.url } : null;
282 }),
283 ).then((found) => found.filter((entry) => entry != null))
284 : Promise.resolve([]);
Pull requests say where they are live; the project menu shows what is coming285 return { preview, build, stacked };
286}
287
Fast pages, required checks on the branch, self-hosted runners, honest incidents288/** A project that uses this one, with its preview built against this change. */
289type Stacked = { name: string; slug: string; url: string };
290
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look291const PULL_NEEDS: Record<string, Capability> = {
292 stack: "run",
293 "agent-review": "run",
294 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents295 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look296 message: "run",
297 merge: "merge",
298 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar299 milestone: "triage",
300 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look301};
302
Issues and pull requests replace intents and attempts303export async function action({ request, params, context }: Route.ActionArgs) {
304 assertSameOrigin(request);
305 const user = requireUser(context, request);
306 const form = await request.formData();
307 const path = { namespace: params.owner, name: params.repo };
308 const number = Number(params.number);
309 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look310 // What each form needs beyond reading; work checks the rest.
311 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
312 const refused = needed ? await refusal(context, params, needed) : null;
313 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts314 const verdict = form.get("verdict");
315 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell316 /** Names ticked in a people picker, plus those typed beside it. */
317 const picked = (field: string) => [
318 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar319 // `@acme/backend` is asked as `acme/backend`.
320 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell321 ];
Project dependencies: addresses, preview stacks, Affects, and agents who know322 // The projects that use this one, built against this pull request's preview.
323 if (action === "stack") {
324 const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? ""));
325 return built.ok
326 ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` }
327 : { action, error: built.error.message };
328 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent329 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell330 if (action === "agent-review") {
331 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-agent332 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell333 });
334 if (!asked.ok) return { error: asked.error.message, action };
335 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar336 // Its labels, milestone and base, from the sidebar's menus and the header.
337 if (action === "labels") {
338 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
339 return set.ok ? null : { error: set.error.message, action };
340 }
341 if (action === "milestone" || action === "base") {
342 const changed = await work.updatePull(
343 user,
344 path,
345 number,
346 action === "milestone"
347 ? { milestone: Number(form.get("milestone")) || 0 }
348 : { base: String(form.get("base") ?? "") },
349 );
350 return changed.ok ? null : { error: changed.error.message, action };
351 }
Catching up with main takes seconds when the two sides touched different files352 // Catching up: merged and pushed in seconds when the two sides changed
353 // different files; otherwise handed to a sandbox, which takes a minute.
354 if (action === "update") {
355 const caught = await work.catchUpPull(user, path, number);
356 if (!caught.ok) return { action, error: caught.error.message };
357 const update = caught.value;
358 if (update.outcome !== "needs_agent") {
359 return {
360 action,
361 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
362 };
363 }
364 const started = await env.RUNNER.update(user, path, number);
365 if (!started.ok) return { action, error: started.error.message };
366 return { action, agent: { update, startedAt: Date.now() } };
367 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains368 // A workflow run's failed jobs, run again. Who may is the actions service's call.
369 if (action === "rerun-workflow") {
370 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
371 return rerun.ok ? null : { error: rerun.error.message, action };
372 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents373 // Every failed run on its head, run again: Mission control's quick action.
374 if (action === "rerun-failed") {
375 const found = await work.getPull(path, number, user);
376 if (!found.ok) return { error: found.error.message, action };
377 const failed = [
378 ...new Set(
379 (found.value.statuses ?? [])
380 .filter((status) => status.state === "failure" || status.state === "error")
381 .map(runIdOf)
382 .filter((id) => id != null),
383 ),
384 ];
385 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
386 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
387 const refused = reruns.find((rerun) => !rerun.ok);
388 return refused && !refused.ok ? { error: refused.error.message, action } : null;
389 }
Issues and pull requests replace intents and attempts390 const result =
391 action === "merge"
Acceptance checks in sandboxes, line comments and review verdicts392 ? await work.mergePull(user, path, number, {
393 keepIssueOpen: form.get("keepIssueOpen") === "on",
394 ignoreChecks: form.get("ignoreChecks") === "on",
395 })
Agents as a team: lifecycle, merge queue, billing and a new shell396 : action === "unqueue"
397 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request398 : action === "message"
399 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Issues and pull requests replace intents and attempts400 : action === "close"
401 ? await work.closePull(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents402 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell403 ? await env.RUNNER.review(user, path, number)
404 : action === "reviewers"
405 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
406 : action === "assign"
407 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts408 : action === "comment"
409 ? await work.addComment(user, path, number, {
410 body: String(form.get("body") ?? ""),
411 path: String(form.get("path") ?? "") || undefined,
412 line: line > 0 ? line : undefined,
413 verdict:
414 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
415 })
416 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
Issues and pull requests replace intents and attempts417 return result.ok ? null : { error: result.error.message, action };
418}
419
Pull requests: title and tabs across the full width, with counts and the size of the change420/** How many, beside a tab's name, when there are any. */
421function TabCount({ n }: { n: number }) {
422 if (n <= 0) return null;
423 return (
424 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
425 {n.toLocaleString("en-US")}
426 </span>
427 );
428}
429
Issues and pull requests replace intents and attempts430function TabLink({
431 to,
432 active,
433 children,
434}: {
435 to: string;
436 active: boolean;
437 children: React.ReactNode;
438}) {
439 return (
440 <Link
441 to={to}
442 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit443 aria-current={active ? "page" : undefined}
Issues and pull requests replace intents and attempts444 className={
Usage, Billing settings and prepaid AI credit; fixes from the UX audit445 "-mb-px flex items-center gap-2 border-b-2 px-3 pb-2.5 text-sm whitespace-nowrap transition-colors " +
Issues and pull requests replace intents and attempts446 (active
447 ? "border-accent font-medium text-fg"
448 : "border-transparent text-muted hover:text-fg")
449 }
450 >
451 {children}
452 </Link>
453 );
454}
455
456/** One step of the session, on the timeline's rail. */
457function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
458 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
459 const Icon =
460 entry.kind === "prompt"
461 ? User
462 : entry.kind === "note"
463 ? StickyNote
464 : isTool
465 ? Wrench
466 : Bot;
467 return (
468 <li className="relative pl-10">
469 <span
470 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
471 entry.kind === "prompt"
472 ? "border-accent/50 text-accent"
473 : "border-line text-faint"
474 }`}
475 >
476 <Icon size={14} />
477 </span>
478 {isTool ? (
479 <details className="group rounded-lg border border-line bg-surface">
480 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
481 <ChevronRight
482 size={14}
483 className="text-faint transition-transform group-open:rotate-90"
484 />
485 <span className="font-mono text-xs text-accent">
486 {entry.tool ?? "tool"}
487 </span>
488 <span className="truncate font-mono text-xs text-muted">
489 {entry.kind === "tool_result" ? "→ " : ""}
490 {entry.text.split("\n")[0]}
491 </span>
492 </summary>
493 <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 line494 <code>{entry.text}</code>
Issues and pull requests replace intents and attempts495 </pre>
496 </details>
497 ) : (
498 <div>
499 <p className="text-xs font-medium text-faint">
500 {entry.kind === "prompt"
501 ? "Prompt"
502 : entry.kind === "note"
503 ? "Note"
504 : agent}
505 </p>
506 {entry.kind === "message" ? (
507 <div className="mt-1">
508 <Markdown source={entry.text} />
509 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell510 ) : entry.kind === "prompt" ? (
511 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
512 <Markdown source={entry.text} />
513 </div>
Issues and pull requests replace intents and attempts514 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell515 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Issues and pull requests replace intents and attempts516 {entry.text}
517 </p>
518 )}
519 </div>
520 )}
521 {entry.commit && (
522 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
523 <GitCommitHorizontal size={12} />
524 {entry.commit.slice(0, 7)}
525 </p>
526 )}
527 </li>
528 );
529}
530
Agents as a team: lifecycle, merge queue, billing and a new shell531/** The box at the foot of the conversation saying what stands before a merge. */
532function StatusBox({ children }: { children: React.ReactNode }) {
533 return (
534 <div className="flex gap-3">
535 <span className="hidden w-8 shrink-0 sm:block" />
536 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
537 {children}
538 </section>
539 </div>
540 );
541}
542
543function StatusRow({
544 icon,
545 title,
546 children,
547}: {
548 icon: React.ReactNode;
549 title: string;
550 children?: React.ReactNode;
551}) {
552 return (
553 <div className="flex gap-3 px-4 py-3 text-sm">
554 <span className="mt-0.5 shrink-0">{icon}</span>
555 <div className="min-w-0">
556 <p className="font-medium">{title}</p>
557 {children && <div className="mt-0.5 text-muted">{children}</div>}
558 </div>
559 </div>
560 );
561}
562
Issues and pull requests replace intents and attempts563export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
564 const {
565 pull,
566 issue,
567 comments,
Acceptance checks in sandboxes, line comments and review verdicts568 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell569 overlaps,
570 behind,
571 reviewPending,
572 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request573 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs574 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents575 requiredChecks = [],
576 noChecks,
577 canAddCi,
578 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains579 mergeable = "unknown",
580 conflicts = [],
581 earlierChecks = [],
582 workflowJobs,
Project dependencies: addresses, preview stacks, Affects, and agents who know583 affects,
584 preview,
Pull requests say where they are live; the project menu shows what is coming585 build,
586 stacked,
Agents as a team: lifecycle, merge queue, billing and a new shell587 landing,
588 stalled,
589 requireUpToDate,
590 mergeQueue,
591 requiredApprovals,
592 canIgnoreChecks,
593 canUpdate,
594 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'595 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell596 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar597 teams,
598 codeOwners,
Issues and pull requests replace intents and attempts599 tab,
600 session,
601 comparison,
602 viewer,
603 canMerge,
604 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look605 canRun,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar606 defaultBranch: repoDefault,
Issues and pull requests replace intents and attempts607 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar608 // The branch it merges into: what every line below names.
609 const defaultBranch = pull.base ?? repoDefault;
Issues and pull requests replace intents and attempts610 const base = `/${params.owner}/${params.repo}`;
611 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-agent612 // g1t, on a pull request it made: the person who asked for it is a line below.
613 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'614 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents615 <ChecksSection
616 run={checks}
617 required={requiredChecks}
618 statuses={statuses}
619 jobs={jobs}
620 pull={pull}
621 base={base}
622 earlier={earlierChecks}
623 canRerunWorkflows={canMerge}
624 settingsUrl={canProtect ? `${base}/settings/branches` : null}
625 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'626 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents627 />
628 );
Merge branch 'worktree-agent-aaf03bdceac799c89'629 const addresses = useAddresses();
Pull requests from branches630 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'631 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
632 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Issues and pull requests replace intents and attempts633 const active = pull.status === "draft" || pull.status === "open";
634
Acceptance checks in sandboxes, line comments and review verdicts635 // Follow an agent at work, or checks in progress, without a manual reload.
Issues and pull requests replace intents and attempts636 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents637 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts638 const reviews = verdicts(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents639 // What stands between this pull request and a merge, if anything: a
640 // required check that has not passed, or the merge queue taking it out.
641 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
642 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell643 // Pull requests for other issues changing the same files will conflict;
644 // ones for the same issue are alternatives, and expected to.
645 const collisions = overlaps.filter((other) => other.issue == null || other.issue !== pull.issue);
646 const review = {
647 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 rights648 // Nobody reviews their own pull request, nor one g1t made for them.
649 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell650 };
651 // Everyone whose review was asked for, then anyone who reviewed unasked.
652 const reviewerNames = [
653 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
654 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar655 const teamReviewers = pull.teamReviewers ?? [];
656 // Teams to ask: the workspace's, and any asked already.
657 const teamChoices = [
658 ...teams,
659 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
660 ];
661 // Branch protection wants code owners' approval it does not have yet.
662 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files663 // A catch-up: the click shows at once; the answer says whether it is
664 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'665 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files666 const catchUp = actionData?.action === "update" ? actionData : null;
667 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
668 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
669 // Pushed already: followed until the pull request's head is the merge,
670 // so its checks show starting again, for a minute at most.
671 const settling =
672 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
673 // Followed until it is pushed, or for so long, never longer.
674 const catchingUp =
675 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell676 // g1t is taking a step of its own accord, so the page will change.
677 const moving =
678 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains679 // Whether it merges cleanly is being worked out, so the box will change.
680 const probing = active && mergeable === "checking";
681 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending682 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains683 // Why the merge button cannot be pressed, if it cannot.
684 const mergeBlocked = conflicting
685 ? "Resolve the conflicts first."
686 : probing
687 ? "Waiting to find out whether it merges cleanly."
688 : behind && requireUpToDate
689 ? `This repository requires it to be up to date with ${defaultBranch} first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents690 : unchecked && !canIgnoreChecks
691 ? `The checks ${defaultBranch} requires have to pass first.`
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar692 : ownersMissing
693 ? "Code owners have to approve first."
694 : null;
Issues and pull requests replace intents and attempts695
696 return (
Agents as a team: lifecycle, merge queue, billing and a new shell697 // 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 change698 <div>
699 {/* The title and the tabs run the full width; what is happening on
700 the pull request sits at the top of Conversation. */}
701 <header>
Issues and pull requests replace intents and attempts702 <h2 className="text-2xl font-semibold tracking-tight text-balance">
703 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
704 </h2>
705 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
706 <PullState status={pull.status} />
707 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell708 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent709 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell710 size={18}
711 />
Issues and pull requests replace intents and attempts712 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains713 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent714 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains715 className="font-medium text-fg hover:underline"
716 />{" "}
Pull requests from branches717 {pull.status === "merged" ? "merged" : "wants to merge"}
718 {pull.branch && (
719 <>
720 {" "}
721 <span className="font-mono text-fg">{pull.branch}</span>
722 </>
723 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar724 into{" "}
725 <BaseBranch
726 base={defaultBranch}
727 head={pull.fork ? null : pull.branch}
728 canChange={loaderData.canChangeBase && active}
729 branchesUrl={`${base}/branches.json`}
730 />
Issues and pull requests replace intents and attempts731 </span>
732 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar733 {(pull.labels ?? []).map((name) => (
734 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
735 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent736 {opener.requestedBy && (
737 <span className="text-xs">
738 requested by{" "}
739 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
740 </span>
741 )}
742 {/* A pull request from a branch was made by its author, not an agent;
743 one g1t made already says so. */}
744 {!pull.branch && !opener.requestedBy && (
Pull requests from branches745 <span className="flex items-center gap-1.5 font-mono text-xs">
746 <Bot size={14} />
747 {pull.agent}
748 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
749 </span>
750 )}
Issues and pull requests replace intents and attempts751 </div>
752
Acceptance checks in sandboxes, line comments and review verdicts753 {reviews.length > 0 && (
754 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
755 {reviews.map(({ reviewer, verdict }) => (
756 <span
757 key={reviewer}
758 className={`flex items-center gap-1.5 ${
759 verdict === "approve" ? "text-accent" : "text-danger"
760 }`}
761 >
762 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
763 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
764 <span className="font-medium">{reviewer}</span>
765 </span>
766 ))}
767 </p>
768 )}
769
Usage, Billing settings and prepaid AI credit; fixes from the UX audit770 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Issues and pull requests replace intents and attempts771 <TabLink to={here} active={tab === "conversation"}>
772 <MessageSquare size={15} />
773 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change774 <TabCount n={comments.length} />
Issues and pull requests replace intents and attempts775 </TabLink>
776 <TabLink to={here + "?tab=session"} active={tab === "session"}>
777 <MessagesSquare size={15} />
778 Session
779 </TabLink>
780 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
781 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change782 Files changed
783 <TabCount n={pull.files.length} />
Issues and pull requests replace intents and attempts784 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change785 {pull.files.length > 0 && (
786 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
787 <span className="text-accent">+{pull.files.reduce((sum, file) => sum + file.additions, 0).toLocaleString("en-US")}</span>{" "}
788 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
789 </span>
790 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit791 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change792 </header>
793 <div className={`mt-6 grid gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
794 <div className="min-w-0">
795 {/* What is happening on it now: its stage, its agent, what people told
796 the agent, and what else touches the same files. On the conversation,
797 so the tabs sit right under the title. */}
798 {tab === "conversation" && (
799 <div>
800 {issue && (
801 <Link
802 to={`${base}/issues/${issue.number}`}
803 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"
804 >
805 <IssueIcon issue={issue} />
806 <span className="text-muted">
807 {pull.status === "merged" && issue.resolvedBy === pull.number
808 ? "Resolved"
809 : "For issue"}
810 </span>
811 <span className="truncate font-medium">{issue.title}</span>
812 <span className="text-faint">#{issue.number}</span>
813 {issue.pullCount > 1 && (
814 <span className="ml-auto shrink-0 text-xs text-faint">
815 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation816 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change817 )}
818 </Link>
819 )}
820 {pull.status === "merged" && (
821 <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">
822 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell823 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change824 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
825 <span className="font-medium">{pull.mergedBy}</span>{" "}
826 {/* Not an account: the repository's settings said to merge it. */}
827 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
828 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
829 {pull.headCommit && (
830 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell831 )}
832 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change833 </p>
834 )}
835 {pull.supersededBy != null && (
836 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
837 Closed because{" "}
838 <Link
839 to={`${base}/pull/${pull.supersededBy}`}
840 className="font-medium text-fg hover:underline"
841 >
842 #{pull.supersededBy}
843 </Link>{" "}
844 was merged for this issue instead.
845 </p>
846 )}
847 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
848 {/* 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 step849 <AgentPanel
850 owner={params.owner}
851 repo={params.repo}
852 number={pull.number}
853 stage={lifecycle?.stage ?? null}
854 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'855 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step856 />
Pull requests: title and tabs across the full width, with counts and the size of the change857
858 {/* 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 look859 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change860 pull.runtime === "hosted" &&
861 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'862 // Keyed by the messages so the box empties once one shows below.
863 <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 change864 <p className="flex items-center gap-2 text-sm font-medium">
865 <Sparkles size={15} className="text-merged" />
866 Message the agent
867 </p>
868 <p className="mt-1 text-xs text-muted">
869 A correction, a hint, a change of plan. It reads it at its next step, without
870 starting over.
871 </p>
872 <div className="mt-3 flex gap-2">
873 <input type="hidden" name="action" value="message" />
874 <input
875 name="body"
876 required
877 autoComplete="off"
878 data-1p-ignore
879 placeholder="Keep the old flag working too…"
880 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 shell881 />
Merge branch 'worktree-agent-a985c01f43538af01'882 <SubmitButton match={{ action: "message" }} pending="Sending…">
883 Send
884 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change885 </div>
886 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell887 )}
Pull requests: title and tabs across the full width, with counts and the size of the change888 {messages.length > 0 && (
889 <ul className="mt-3 space-y-1.5">
890 {messages.map((message) => (
891 <li key={message.id} className="flex items-start gap-2 text-sm">
892 <Avatar name={message.author} size={18} />
893 <span className="min-w-0 grow">
894 <span className="font-medium">
895 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
896 </span>{" "}
897 <span className="text-muted">
898 {message.kind === "question"
899 ? "asked:"
900 : message.kind === "handoff"
901 ? "handed over:"
902 : message.kind === "answer"
903 ? "answered:"
904 : "to the agent:"}
905 </span>{" "}
906 {message.body}
907 {message.answer && (
908 <span className="mt-1 block border-l-2 border-merged/40 pl-2 text-muted">
909 {message.declined ? "Declined: " : "Answer: "}
910 {message.answer}
911 </span>
912 )}
913 </span>
914 <span className={`shrink-0 text-xs ${message.deliveredAt ? "text-accent" : "text-faint"}`}>
915 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
916 </span>
917 </li>
918 ))}
919 </ul>
920 )}
921 {collisions.length > 0 && active && (
922 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
923 <p className="flex items-center gap-2.5 font-medium">
924 <Radar size={16} className="shrink-0 text-info" />
925 Other work is changing the same files
926 </p>
927 <ul className="mt-2 space-y-1.5">
928 {collisions.map((other) => (
929 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
930 <Link
931 to={`${base}/pull/${other.number}`}
932 className="font-medium text-fg hover:underline"
933 >
934 {other.title} <span className="font-normal text-faint">#{other.number}</span>
935 </Link>
936 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
937 </li>
938 ))}
939 </ul>
940 <p className="mt-2 text-xs text-faint">
941 Whichever merges second will have to catch up, and may conflict.
942 </p>
943 </div>
944 )}
945 </div>
946 )}
947 <div className="mt-5">
948 {comparison ? (
949 <DiffView
950 comparison={comparison}
951 review={{
952 comments: comments.filter((comment) => comment.path),
953 canComment: Boolean(viewer),
954 }}
955 />
956 ) : tab === "session" ? (
957 session.length === 0 ? (
958 <EmptyState title="Nothing recorded yet">
959 The agent's prompts, reasoning and tool calls appear here as it
960 works.
961 </EmptyState>
962 ) : (
963 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
964 {session.map((entry) => (
965 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
966 ))}
967 </ol>
968 )
969 ) : (
970 <div className="space-y-4">
971 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent972 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change973 at={pull.createdAt}
974 action={
975 <span>
976 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-agent977 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change978 <>
979 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent980 for{" "}
981 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
982 </>
983 ) : !pull.branch && (
984 <>
985 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change986 with <span className="font-mono text-xs">{pull.agent}</span>
987 </>
988 )}
989 </span>
990 }
991 >
992 {pull.body ? (
993 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
994 ) : (
995 <p className="text-sm text-muted">
996 {working
997 ? "No description yet. It is written when the pull request is marked ready for review."
998 : "No description."}
999 </p>
1000 )}
1001 {canManage && pull.status === "open" && (
1002 <details className="mt-3 border-t border-line pt-3 text-sm">
1003 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1004 Edit description
1005 </summary>
1006 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1007 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1008 <Textarea
1009 name="summary"
1010 rows={6}
1011 placeholder="What changed and why"
1012 defaultValue={pull.body ?? ""}
1013 />
Merge branch 'worktree-agent-a985c01f43538af01'1014 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1015 Save
1016 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1017 </Form>
1018 </details>
1019 )}
1020 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1021
Pull requests: title and tabs across the full width, with counts and the size of the change1022 <CommentList comments={comments} review={review} base={base} />
Agents as a team: lifecycle, merge queue, billing and a new shell1023
Merge branch 'worktree-agent-aca4790ea223aa235'1024 {(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 coming1025
Pull requests: title and tabs across the full width, with counts and the size of the change1026 {pull.status === "draft" && (
1027 <StatusBox>
1028 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1029 It is still being worked on. It can be reviewed and merged once it is
1030 marked ready.
1031 </StatusRow>
1032 {canManage && (
1033 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1034 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1035 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1036 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1037 Mark ready for review
1038 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1039 </Form>
1040 )}
1041 </StatusBox>
1042 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1043
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1044 {codeOwners && active && (
1045 <div className="flex gap-3">
1046 <span className="hidden w-8 shrink-0 sm:block" />
1047 <div className="min-w-0 grow">
1048 <PullCodeOwnersPanel
1049 owners={codeOwners}
1050 base={base}
1051 branch={defaultBranch}
1052 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1053 />
1054 </div>
1055 </div>
1056 )}
1057
Pull requests: title and tabs across the full width, with counts and the size of the change1058 {pull.status === "open" && (
1059 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1060 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1061 <Suspense fallback={checksSection({}, true)}>
1062 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1063 </Suspense>
1064 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1065 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1066 icon={
1067 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1068 <CircleSlash size={16} className="text-danger" />
1069 ) : reviews.length > 0 ? (
1070 <CircleCheck size={16} className="text-accent" />
1071 ) : (
1072 <MessageSquare size={16} className="text-faint" />
1073 )
1074 }
1075 title={
1076 reviews.length === 0
1077 ? "No reviews yet"
1078 : reviews
1079 .map(
1080 ({ reviewer, verdict }) =>
1081 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1082 )
1083 .join(" · ")
1084 }
Agents as a team: lifecycle, merge queue, billing and a new shell1085 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1086 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1087 {requiredApprovals > 0 &&
1088 `This repository requires ${requiredApprovals} approving ${
1089 requiredApprovals === 1 ? "review" : "reviews"
1090 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1091 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1092 {codeOwners?.required && (
1093 <StatusRow
1094 icon={
1095 ownersMissing ? (
1096 <CircleSlash size={16} className="text-warn" />
1097 ) : (
1098 <CircleCheck size={16} className="text-accent" />
1099 )
1100 }
1101 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1102 >
1103 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1104 {ownersMissing && "This repository requires their review before merging."}
1105 </StatusRow>
1106 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1107 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1108 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1109 icon={<Loader size={16} className="animate-spin text-accent" />}
1110 title="Merging"
1111 >
1112 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1113 lands as soon as that is done. Watch it in the Session tab.
1114 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1115 ) : agentCatchUp && behind ? (
1116 <CatchUpProgress
1117 owner={params.owner}
1118 repo={params.repo}
1119 number={pull.number}
1120 defaultBranch={defaultBranch}
1121 behind={behind}
1122 update={agentCatchUp.update}
1123 startedAt={agentCatchUp.startedAt}
1124 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1125 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1126 />
Pull requests: title and tabs across the full width, with counts and the size of the change1127 ) : conflicting ? (
1128 <ConflictsSection
1129 conflicts={conflicts}
1130 pull={pull}
1131 owner={params.owner}
1132 repo={params.repo}
1133 defaultBranch={defaultBranch}
1134 changesUrl={here + "?tab=changes"}
1135 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1136 resolving={catchUpPending}
1137 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1138 />
Pull requests: title and tabs across the full width, with counts and the size of the change1139 ) : probing ? (
1140 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1141 ) : behind ? (
1142 <StatusRow
1143 icon={<GitPullRequestArrow size={16} className="text-info" />}
1144 title={`${defaultBranch} has moved since this was made`}
1145 >
1146 {mergeable === "clean" && "It has no conflicts with it. "}
1147 {requireUpToDate
1148 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1149 : "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 files1150 {canUpdate && (
1151 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1152 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1153 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1154 Catch up with {defaultBranch} now
1155 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1156 </Form>
1157 )}
Catching up with main takes seconds when the two sides touched different files1158 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1159 </StatusRow>
1160 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1161 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1162 <StatusRow
1163 icon={<CircleCheck size={16} className="text-accent" />}
Catching up with main takes seconds when the two sides touched different files1164 title={
1165 (caughtUp && !caughtUp.already) || agentCatchUp
1166 ? `Brought up to date with ${defaultBranch}`
1167 : `Up to date with ${defaultBranch}`
1168 }
1169 >
1170 {caughtUp && !caughtUp.already && (
1171 <>
1172 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1173 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1174 again on the new commit.
1175 </>
1176 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1177 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1178 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1179 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1180 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1181 )
1182 )}
1183 {stalled && !lifecycle && (
1184 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1185 {stalled}
1186 </StatusRow>
1187 )}
1188 {canMerge && lifecycle?.stage === "queued" && (
1189 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1190 <ButtonLink to={`${base}/queue`}>
1191 <Layers size={15} />
1192 See the queue
1193 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1194 <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 change1195 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1196 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1197 </Form>
1198 )}
1199 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1200 <Form method="post" className="space-y-3 px-4 py-3">
1201 {issue?.state === "open" && (
1202 <CheckboxOption
1203 name="keepIssueOpen"
1204 label={`Keep #${issue.number} open. This is only part of the work.`}
1205 labelClassName="text-xs text-muted"
1206 />
1207 )}
1208 {unchecked && canIgnoreChecks && (
1209 <CheckboxOption
1210 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1211 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 change1212 labelClassName="text-xs text-muted"
1213 />
1214 )}
1215 <div className="flex flex-wrap items-center gap-3">
Merge branch 'worktree-agent-a985c01f43538af01'1216 <SubmitButton
Pull requests: title and tabs across the full width, with counts and the size of the change1217 variant="accent"
1218 name="action"
1219 value="merge"
1220 disabled={mergeBlocked != null}
1221 title={mergeBlocked ?? undefined}
Merge branch 'worktree-agent-a985c01f43538af01'1222 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
Pull requests: title and tabs across the full width, with counts and the size of the change1223 >
1224 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1225 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
Merge branch 'worktree-agent-a985c01f43538af01'1226 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1227 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1228 {mergeBlocked ?? (mergeQueue
1229 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1230 : issue?.state === "open"
1231 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1232 : `Lands its commits on ${defaultBranch}.`)}
1233 </span>
1234 </div>
1235 {actionData?.action === "merge" && (
1236 <ErrorText>{actionData.error}</ErrorText>
1237 )}
1238 </Form>
1239 )}
1240 </StatusBox>
1241 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1242
Pull requests: title and tabs across the full width, with counts and the size of the change1243 <CommentForm
1244 author={viewer?.username ?? null}
1245 resetKey={comments.length}
1246 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1247 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1248 />
1249 {canManage && active && (
1250 <Form method="post" className="flex justify-end">
Merge branch 'worktree-agent-a985c01f43538af01'1251 <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 change1252 Close pull request
Merge branch 'worktree-agent-a985c01f43538af01'1253 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1254 </Form>
1255 )}
1256 {actionData &&
Merge branch 'worktree-agent-a985c01f43538af01'1257 !["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 change1258 String(actionData.action),
1259 ) && <ErrorText>{actionData.error}</ErrorText>}
1260 </div>
1261 )}
1262 {actionData?.action === "comment" && (
1263 <div className="mt-2">
1264 <ErrorText>{actionData.error}</ErrorText>
1265 </div>
1266 )}
1267 </div>
1268 </div>
1269
1270 <aside className={tab === "changes" ? "hidden" : "space-y-6"}>
1271 {/* An open pull request shows its checks in full in the merge box. */}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1272 {pull.status !== "open" && <WorkflowStatuses statuses={statuses} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1273 {affects.length > 0 && (
1274 <section>
1275 <h3 className="flex items-center gap-1.5 text-sm font-medium">
1276 <Network size={14} className="text-faint" />
1277 Affects
1278 </h3>
1279 <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1280 <ul className="mt-2 space-y-1 text-sm">
1281 {affects.map((project) => (
1282 <li key={project.slug} className="flex items-center justify-between gap-2">
1283 <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1284 {project.name}
1285 </Link>
1286 {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1287 </li>
1288 ))}
1289 </ul>
1290 {preview?.branch && canMerge && (
1291 <Form method="post" className="mt-3">
1292 <input type="hidden" name="action" value="stack" />
1293 <input type="hidden" name="branch" value={preview.branch} />
Merge branch 'worktree-agent-a985c01f43538af01'1294 <SubmitButton
1295 match={{ action: "stack" }}
1296 pending="Starting the builds…"
1297 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 change1298 >
1299 Preview them against this change
Merge branch 'worktree-agent-a985c01f43538af01'1300 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell1301 </Form>
1302 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1303 {actionData?.action === "stack" &&
1304 ("notice" in actionData ? (
1305 <p className="mt-2 text-xs text-accent">{String(actionData.notice)}</p>
1306 ) : (
1307 <ErrorText>{actionData.error}</ErrorText>
1308 ))}
1309 </section>
Acceptance checks in sandboxes, line comments and review verdicts1310 )}
Issues and pull requests replace intents and attempts1311
Project dependencies: addresses, preview stacks, Affects, and agents who know1312 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1313 <h3 className="text-sm font-medium">Reviewers</h3>
1314 <ul className="mt-2 space-y-1.5 text-sm">
1315 {reviewerNames.map((name) => {
1316 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-agent1317 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1318 return (
1319 <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-agent1320 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1321 <Sparkles size={16} className="shrink-0 text-accent" />
1322 ) : (
1323 <Avatar name={name} size={20} />
1324 )}
1325 <span className="grow truncate font-mono text-xs">{name}</span>
1326 {pending ? (
1327 <span className="flex items-center gap-1.5 text-xs text-muted">
1328 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1329 Reviewing
1330 </span>
1331 ) : verdict === "approve" ? (
1332 <span className="flex items-center gap-1 text-xs text-accent">
1333 <CircleCheck size={13} /> Approved
1334 </span>
1335 ) : verdict === "request_changes" ? (
1336 <span className="flex items-center gap-1 text-xs text-danger">
1337 <CircleSlash size={13} /> Changes requested
1338 </span>
1339 ) : (
1340 <span className="text-xs text-faint">Review requested</span>
1341 )}
1342 </li>
1343 );
1344 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1345 {teamReviewers.map((team) => (
1346 <TeamReviewer key={team} team={team} />
1347 ))}
1348 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1349 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1350 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1351 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1352 {pull.status === "open" && canManage && (
1353 <div className="mt-3 space-y-2">
1354 {agentsEnabled && !reviewPending && (
1355 <Form method="post">
1356 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1357 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1358 <input key={name} type="hidden" name="reviewer" value={name} />
1359 ))}
1360 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1361 <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 change1362 <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-agent1363 Request review from g1t
Merge branch 'worktree-agent-a985c01f43538af01'1364 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1365 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1366 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1367 </Form>
1368 )}
1369 <details>
1370 <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 bar1371 {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 change1372 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1373 <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 change1374 <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-agent1375 {pull.reviewers.includes("g1t") && (
1376 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1377 )}
1378 <PeoplePicker
1379 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1380 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-agent1381 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1382 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1383 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1384 {teamChoices.length > 0 && (
1385 <div className="space-y-1.5 border-t border-line pt-2">
1386 <p className="text-xs text-faint">Teams</p>
1387 {teamChoices.map((team) => (
1388 <CheckboxOption
1389 key={team.ref}
1390 name="reviewer"
1391 value={team.ref}
1392 defaultChecked={teamReviewers.includes(team.ref)}
1393 className="items-center"
1394 labelClassName="flex min-w-0 items-center gap-2"
1395 label={
1396 <>
1397 <Users size={14} className="shrink-0 text-faint" />
1398 <span className="truncate font-mono text-xs">@{team.ref}</span>
1399 </>
1400 }
1401 />
1402 ))}
1403 </div>
1404 )}
Merge branch 'worktree-agent-a985c01f43538af01'1405 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1406 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1407 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1408 </Form>
1409 </details>
1410 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1411 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1412 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1413 <ErrorText>{actionData?.error}</ErrorText>
1414 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1415 </section>
Acceptance checks in sandboxes, line comments and review verdicts1416
Pull requests: title and tabs across the full width, with counts and the size of the change1417 <section>
1418 <h3 className="text-sm font-medium">Assignees</h3>
1419 <ul className="mt-2 space-y-1.5 text-sm">
1420 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1421 <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 change1422 <Avatar name={name} size={20} />
Agents as a team: lifecycle, merge queue, billing and a new shell1423 <span className="grow truncate font-mono text-xs">{name}</span>
1424 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1425 ))}
1426 {pull.assignees.length === 0 && (
1427 <li className="px-1 text-xs text-faint">No one yet.</li>
1428 )}
1429 </ul>
1430 {viewer && canManage && active && (
1431 <div className="mt-3 space-y-2">
1432 {!pull.assignees.includes(viewer.username) && (
1433 <Form method="post">
1434 <input type="hidden" name="action" value="assign" />
1435 {pull.assignees.map((name) => (
1436 <input key={name} type="hidden" name="assignee" value={name} />
1437 ))}
1438 <input type="hidden" name="assignee" value={viewer.username} />
1439 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1440 <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 change1441 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1442 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1443 </div>
1444 </Form>
1445 )}
1446 <details>
1447 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1448 Assign people
1449 </summary>
1450 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1451 <input type="hidden" name="action" value="assign" />
1452 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1453 <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 change1454 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1455 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1456 </Form>
1457 </details>
1458 </div>
Issues and pull requests replace intents and attempts1459 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1460 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1461 </section>
Issues and pull requests replace intents and attempts1462
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1463 <LabelsBox
1464 labels={loaderData.labels}
1465 chosen={pull.labels ?? []}
1466 canEdit={canManage && active}
1467 canCreate={loaderData.canTriage}
1468 manageUrl={loaderData.canTriage ? `${base}/labels` : undefined}
1469 />
1470 <MilestoneBox
1471 milestones={loaderData.milestones}
1472 current={pull.milestone}
1473 canEdit={loaderData.canTriage}
1474 base={base}
1475 />
1476
Pull requests: title and tabs across the full width, with counts and the size of the change1477 <section>
1478 <h3 className="text-sm font-medium">Working copy</h3>
1479 {pull.branch ? (
1480 <>
1481 <p className="mt-1 text-xs text-muted">
1482 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1483 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1484 repository. Pushes to it show up here.
1485 </p>
1486 <div className="mt-2">
1487 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1488 </div>
1489 </>
1490 ) : (
1491 <>
1492 <p className="mt-1 text-xs text-muted">
1493 This fork belongs to the pull request. Pushes to it show up here.
1494 </p>
1495 <div className="mt-2">
1496 <CopyLine text={`git clone ${remote}`} />
1497 </div>
1498 </>
1499 )}
1500 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1501 <GitCommitHorizontal size={13} />
1502 {pull.headCommit?.slice(0, 12) ?? "no commits pushed yet"}
1503 </p>
1504 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1505 {loaderData.viewer && (
1506 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1507 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1508 </aside>
1509 </div>
Issues and pull requests replace intents and attempts1510 </div>
1511 );
1512}
Pull requests say where they are live; the project menu shows what is coming1513
1514/** Where the pull request's change is live, as GitHub shows a deployment. */
1515function DeploymentCard({
1516 preview,
1517 build,
1518 stacked,
Merge branch 'worktree-agent-aca4790ea223aa235'1519 stacking,
Pull requests say where they are live; the project menu shows what is coming1520 base,
1521}: {
1522 preview: LiveApp | null;
1523 build: Deployment | null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents1524 stacked: Promise<Stacked[]>;
Merge branch 'worktree-agent-aca4790ea223aa235'1525 /** Projects use this one, so previews built against it may be on their way. */
1526 stacking: boolean;
Pull requests say where they are live; the project menu shows what is coming1527 base: string;
1528}) {
1529 const building = build?.status === "queued" || build?.status === "building";
1530 const failed = build?.status === "failed";
1531 return (
1532 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1533 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1534 <span
1535 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
1536 failed ? "text-danger ring-danger/40" : building ? "text-warn ring-warn/40" : "text-accent ring-accent/40"
1537 }`}
1538 >
1539 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1540 </span>
1541 <div className="min-w-0 grow">
1542 <p className="text-sm font-medium">
1543 {building
1544 ? preview
1545 ? "Deploying the latest push. The last preview is still live."
1546 : "Deploying a preview"
1547 : failed
1548 ? "The preview failed to deploy"
1549 : "This branch is live"}
1550 </p>
1551 {preview ? (
1552 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1553 {preview.url.replace(/^https?:[/][/]/, "")}
1554 </a>
1555 ) : (
1556 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1557 )}
1558 </div>
1559 <div className="flex shrink-0 items-center gap-2">
1560 {build && (
1561 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1562 {failed ? "See why" : "Build log"}
1563 </Link>
1564 )}
1565 {preview && (
1566 <a
1567 href={preview.url}
1568 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"
1569 >
1570 Visit preview
1571 <ArrowUpRight size={13} />
1572 </a>
1573 )}
1574 </div>
1575 </div>
1576 {preview && (
1577 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1578 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1579 {preview.branch && ` · from ${preview.branch}`}
1580 </p>
1581 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1582 {/* Read after the page: the card's own part shows at once. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1583 <Suspense
1584 fallback={
1585 stacking && (
1586 <Loading className="border-t border-line px-4 py-2.5 text-xs">
1587 <SkeletonLine className="w-40" />
1588 <SkeletonLine className="mt-1.5 w-64 max-w-full" />
1589 </Loading>
1590 )
1591 }
1592 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1593 <Await resolve={stacked}>
1594 {(stacked) =>
1595 stacked.length > 0 && (
Merge branch 'worktree-agent-aca4790ea223aa235'1596 <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 incidents1597 <p className="text-xs text-muted">Built against this change:</p>
1598 <ul className="mt-1.5 space-y-1">
1599 {stacked.map((entry) => (
1600 <li key={entry.slug} className="flex items-center gap-2 text-xs">
1601 <span className="font-medium">{entry.name}</span>
1602 <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1603 {entry.url.replace(/^https?:[/][/]/, "")}
1604 </a>
1605 </li>
1606 ))}
1607 </ul>
1608 </div>
1609 )
1610 }
1611 </Await>
1612 </Suspense>
Pull requests say where they are live; the project menu shows what is coming1613 </section>
1614 );
1615}

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