Skip to content
1,685 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,
Rules on the site: rulesets for a repository and a workspace, and the merge box12 Scale,
Agents as a team: lifecycle, merge queue, billing and a new shell13 GitPullRequestArrow,
14 Hand,
15 Loader,
16 Radar,
17 Sparkles,
Issues and pull requests replace intents and attempts18 MessageSquare,
Pull requests say where they are live; the project menu shows what is coming19 ArrowUpRight,
Issues and pull requests replace intents and attempts20 MessagesSquare,
Pull requests say where they are live; the project menu shows what is coming21 Rocket,
Project dependencies: addresses, preview stacks, Affects, and agents who know22 Network,
Issues and pull requests replace intents and attempts23 StickyNote,
24 User,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar25 Users,
Issues and pull requests replace intents and attempts26 Wrench,
27} from "lucide-react";
Rules on the site: rulesets for a repository and a workspace, and the merge box28import { Suspense, useState } from "react";
Merge branch 'worktree-agent-a985c01f43538af01'29import { Await, Form, Link, redirect } from "react-router";
Issues and pull requests replace intents and attempts30
Pull requests say where they are live; the project menu shows what is coming31import {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look32 type Capability,
Pull requests say where they are live; the project menu shows what is coming33 type Comparison,
34 type Deployment,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains35 type Job,
Pull requests say where they are live; the project menu shows what is coming36 type LiveApp,
37 type SessionEntry,
38 type Viewer,
39 pullComparison,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights40 workOwner,
Pull requests say where they are live; the project menu shows what is coming41} from "@g1t/contracts";
Issues and pull requests replace intents and attempts42
43import type { Route } from "./+types/pull";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit44import { highlightFirstFiles } from "../../lib/highlight.server";
Social cards for every page: og.g1t.sh45import { 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-agent46import { openedBy } from "../../lib/opened-by";
Merge branch 'worktree-agent-aaf03bdceac799c89'47import { cloneUrl, useAddresses } from "../../lib/addresses";
Issues and pull requests replace intents and attempts48import { DiffView } from "../../components/diff-view";
Agents as a team: lifecycle, merge queue, billing and a new shell49import { LifecyclePanel } from "../../components/lifecycle";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains50import { AgentPanel } from "../../components/agents";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar51import { BaseBranch } from "../../components/base-branch";
52import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Issues and pull requests replace intents and attempts53import { Markdown } from "../../components/markdown";
54import {
55 Avatar,
Agents as a team: lifecycle, merge queue, billing and a new shell56 ButtonLink,
Issues and pull requests replace intents and attempts57 CopyLine,
58 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look59 ComputeNote,
Issues and pull requests replace intents and attempts60 ErrorText,
Merge branch 'worktree-agent-a985c01f43538af01'61 SubmitButton,
Issues and pull requests replace intents and attempts62 Textarea,
63 TimeAgo,
Merge branch 'worktree-agent-a985c01f43538af01'64 usePending,
Issues and pull requests replace intents and attempts65} from "../../components/ui";
Workspace names and icons, and a component kit for every control66import { CheckboxOption } from "../../components/ui/checkbox";
Merge branch 'worktree-agent-aca4790ea223aa235'67import { Loading, SkeletonLine } from "../../components/ui/skeleton";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit68import { TabStrip } from "../../components/ui/tab-strip";
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs69import { WorkflowStatuses } from "../../components/actions";
Fast pages, required checks on the branch, self-hosted runners, honest incidents70import { AddCiPrompt } from "../../components/add-ci";
Agents as a team: lifecycle, merge queue, billing and a new shell71import {
72 CommentForm,
73 CommentList,
74 IssueIcon,
75 PeoplePicker,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains76 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell77 PullState,
78 TimelineItem,
79 verdicts,
80} from "../../components/work";
Catching up with main takes seconds when the two sides touched different files81import { CatchUpProgress, ChecksSection, ConflictsSection, MergeabilityRow, runIdOf } from "../../components/merge-box";
Rules on the site: rulesets for a repository and a workspace, and the merge box82import { ViolationList } from "../../components/rules";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar83import { PullCodeOwnersPanel, TeamReviewer } from "../../components/codeowners";
Catching up with main takes seconds when the two sides touched different files84import { 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 put85import { notFound } from "../../lib/not-found.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look86import { computeNoteFor } from "../../lib/compute.server";
Merge branch 'worktree-agent-aca4790ea223aa235'87import { actions, agents, deployments, identity, inbox, projects, repos, work } from "../../lib/services.server";
Issues and pull requests replace intents and attempts88import { assertSameOrigin, getViewer, requireUser } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings89import { accessTo, refusal, repoFor } from "../../lib/access.server";
90import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending91import { REFRESH_MS, useRefreshWhile } from "../../lib/refresh";
Issues and pull requests replace intents and attempts92
93const EMPTY_COMPARISON: Comparison = { base: null, head: "", files: [], truncated: false };
94const TABS = ["conversation", "session", "changes"] as const;
95type Tab = (typeof TABS)[number];
96
Social cards for every page: og.g1t.sh97export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
98 const pull = loaderData?.pull;
99 const title = pull ? `${pull.title} · Pull request #${pull.number} · ` : "";
100 const state = { draft: "Draft", open: "Open", merged: "Merged", closed: "Closed" }[pull?.status ?? "open"];
101 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-agent102 const opener = pull ? openedBy(pull) : null;
Social cards for every page: og.g1t.sh103 return page(args, {
104 title: `${title}${params.owner}/${params.repo} · g1t`,
105 description: pull
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent106 ? `${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.sh107 : null,
108 // The card shows the title and the state.
109 version: pull ? [pull.title, pull.status] : undefined,
110 type: "article",
111 });
Issues and pull requests replace intents and attempts112}
113
114export async function loader({ params, context, request }: Route.LoaderArgs) {
115 const viewer = getViewer(context);
116 const path = { namespace: params.owner, name: params.repo };
117 const number = Number(params.number);
118 const asked = new URL(request.url).searchParams.get("tab");
119 const tab: Tab = TABS.find((name) => name === asked) ?? "conversation";
120
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look121 // Members of the workspace pick reviewers and assignees from its people;
122 // what the viewer may do here goes by their role on the repository.
Agents as a team: lifecycle, merge queue, billing and a new shell123 const member = (viewer?.workspaces ?? []).some(
124 (membership) => membership.slug === params.owner,
125 );
Fast pages, required checks on the branch, self-hosted runners, honest incidents126 // Everything starts at once. Only what the viewer's role decides waits
127 // for the repository, which the layout is looking up in this request too
128 // (lib/access.server.ts), and only what the pull request decides waits
129 // for it.
Project dependencies: addresses, preview stacks, Affects, and agents who know130 const ref = { workspace: params.owner, slug: params.repo };
Fast pages, required checks on the branch, self-hosted runners, honest incidents131 const access = accessTo(context, params);
132 const pullFound = work.getPull(path, number, viewer);
133 const deps = projects.dependencies(params.owner, params.repo, viewer);
134 // Awaited below, unless the pull request is missing first.
135 deps.catch(() => null);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar136 // Its labels and milestone, with the repository's to choose from.
137 const labelsFound = work.listLabels(path, viewer);
138 const milestonesFound = work.listMilestones(path, viewer);
139 labelsFound.catch(() => null);
140 milestonesFound.catch(() => null);
141 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 incidents142 access,
143 pullFound,
Issues and pull requests replace intents and attempts144 repos.get(path, viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell145 work.getSettings(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing146 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell147 // A member picks reviewers and assignees from the workspace's people.
148 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-agent149 // Before someone asks g1t for something: whether the plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents150 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
151 tab === "session" ? work.readSession(path, number, viewer) : null,
152 // Its preview, for people with a role here: beside the rest, not after.
153 access.then(({ insider }) => (insider ? deployments.list(ref, viewer).catch(() => null) : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings154 // Whether the viewer hears of it, for the sidebar's Notifications.
155 viewer
156 ? repoFor(context, params).then((found) =>
157 found.ok ? inbox.subscription(viewer, { repoId: found.value.id, number }).catch(() => null) : null,
158 )
159 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'160 // Its agent's latest runs, for the Agent panel: with the page, not after it.
161 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 bar162 // A member asks the workspace's teams to review, and mentions them.
163 member ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Issues and pull requests replace intents and attempts164 ]);
165 if (!found.ok) {
166 // Issues and pull requests share numbers; this one may be an issue.
167 const issue = await work.getIssue(path, number, viewer);
168 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 put169 throw notFound("pull");
Issues and pull requests replace intents and attempts170 }
171 const { pull } = found.value;
Pull requests from branches172 const range = pullComparison(pull);
Agents and memory, checks and conflicts, profiles, slug renames, custom domains173 // The jobs of each workflow run on its head, to list checks job by job.
174 // 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 incidents175 // Streamed: the checks show by status first, then job by job.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains176 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 incidents177 const workflowJobs =
178 tab === "conversation" && pull.status === "open" && runIds.length > 0
179 ? Promise.all(runIds.map((id) => actions.run(path, viewer, id).catch(() => null))).then((runs) => {
180 const jobs: Record<string, Job[]> = {};
181 for (const run of runs) if (run?.ok) jobs[run.value.run.id] = run.value.jobs;
182 return jobs;
183 })
184 .catch(() => ({}) as Record<string, Job[]>)
185 : Promise.resolve({} as Record<string, Job[]>);
186 // No checks at all on an open pull request: whether that is because the
187 // repository has no workflows, to offer a starter one.
188 const unchecked =
189 tab === "conversation" &&
190 pull.status === "open" &&
191 (found.value.statuses ?? []).length === 0 &&
192 (found.value.requiredChecks ?? []).length === 0;
193 const [comparison, used, noChecks] = await Promise.all([
Usage, Billing settings and prepaid AI credit; fixes from the UX audit194 // The first screens of it highlighted here, so their colours do not pop in.
195 tab === "changes"
196 ? repos
197 .compare(range.repoId, viewer, range.base, range.head, range.baseBranch)
198 .then(async (found) => (found.ok ? { ok: true as const, value: await highlightFirstFiles(found.value) } : found))
199 : null,
Fast pages, required checks on the branch, self-hosted runners, honest incidents200 deps,
201 unchecked
202 ? actions
203 .workflows(path, viewer)
204 .then((found) => found.ok && found.value.length === 0)
205 .catch(() => false)
206 : false,
Issues and pull requests replace intents and attempts207 ]);
Fast pages, required checks on the branch, self-hosted runners, honest incidents208 const affects = used.ok ? used.value.usedBy : [];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar209 const [labels, milestones] = await Promise.all([labelsFound, milestonesFound]);
210 const repoDefault = repo.ok ? repo.value.defaultBranch : "main";
211 // The default branch's protection covers pull requests into it only.
212 const protectedBase = (pull.base ?? repoDefault) === repoDefault;
Issues and pull requests replace intents and attempts213 return {
214 ...found.value,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar215 labels: labels.ok ? labels.value : [],
216 milestones: milestones.ok ? milestones.value : [],
217 // Labelling and milestones: Triage and up; its author, its labels.
218 canTriage: can.triage,
219 // The branch it merges into: Write and up.
220 canChangeBase: can.push,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains221 workflowJobs,
Issues and pull requests replace intents and attempts222 tab,
223 session: session?.ok ? session.value : [],
224 // An empty comparison if it could not be made.
225 comparison: comparison && (comparison.ok ? comparison.value : EMPTY_COMPARISON),
226 viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look227 // Write and up can merge.
228 canMerge: can.merge,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights229 // Triage and up manage anyone's pull request; its author, their own,
230 // and whoever asked g1t for one, that one.
231 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 look232 // Telling its agent things, and re-running checks, spend compute: Write and up.
233 canRun: can.run,
Agents as a team: lifecycle, merge queue, billing and a new shell234 // 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 rights235 // whoever it is for (who asked g1t for it, or its author), a branch
236 // from anyone who can push.
237 canUpdate: pull.fork ? viewer?.id === workOwner(pull).id : can.push,
Agents as a team: lifecycle, merge queue, billing and a new shell238 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look239 computeNote,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings240 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'241 // As the project's agents.json has them; left out, the panel fetches them.
242 agentRuns: runs?.ok ? { runs: runs.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell243 members: members?.ok ? members.value.map((person) => person.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar244 // `workspace/slug` and name of each team the viewer can see.
245 teams: teamList?.ok ? teamList.value.map((team) => ({ ref: `${team.workspace}/${team.slug}`, name: team.name })) : [],
Rules on the site: rulesets for a repository and a workspace, and the merge box246 // What the rules of the branch it merges into ask, as they stack; the
247 // default branch's settings where the rules are not known.
248 requireUpToDate: found.value.rules ? found.value.rules.strict : protectedBase && settings.ok && settings.value.requireUpToDate,
249 mergeQueue: found.value.rules ? found.value.rules.merge_queue : protectedBase && settings.ok && settings.value.mergeQueue,
250 requiredApprovals: found.value.rules
251 ? found.value.rules.required_approvals
252 : protectedBase && settings.ok
253 ? settings.value.requiredApprovals
254 : 0,
255 canIgnoreChecks: found.value.rules ? found.value.rules.allow_bypass_on_merge : !settings.ok || settings.value.allowIgnoringChecks,
Fast pages, required checks on the branch, self-hosted runners, honest incidents256 noChecks,
257 // Who may open the pull request that adds CI: anyone who can push.
258 canAddCi: can.push,
259 // Who may choose the required checks.
260 canProtect: can.manage_protection,
Issues and pull requests replace intents and attempts261 defaultBranch: repo.ok ? repo.value.defaultBranch : "main",
Fast pages, required checks on the branch, self-hosted runners, honest incidents262 affects,
263 // Streamed: the conversation shows first, the preview card after.
264 ...deploymentOf(deployed?.ok ? deployed.value : null, number, params.owner, affects, viewer),
Issues and pull requests replace intents and attempts265 };
266}
267
Pull requests say where they are live; the project menu shows what is coming268/**
269 * Where this pull request is live: its preview (or its latest build, while
270 * one is going or after one failed), and the previews of the projects
271 * that use this one, built against it.
272 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents273function deploymentOf(
Pull requests say where they are live; the project menu shows what is coming274 list: { deployments: Deployment[]; live: LiveApp[] } | null,
275 number: number,
276 owner: string,
277 affects: { slug: string; name: string }[],
278 viewer: Viewer,
Fast pages, required checks on the branch, self-hosted runners, honest incidents279): { preview: LiveApp | null; build: Deployment | null; stacked: Promise<Stacked[]> } {
280 const preview = list?.live.find((app) => app.kind === "preview" && app.number === number) ?? null;
281 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 coming282 const branch = preview?.branch ?? build?.branch ?? null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents283 // Streamed: another lookup per project, shown in the card once known.
284 const stacked: Promise<Stacked[]> = branch
285 ? Promise.all(
286 affects.slice(0, 5).map(async (project) => {
287 const theirs = await deployments.list({ workspace: owner, slug: project.slug }, viewer).catch(() => null);
288 const app = theirs?.ok ? theirs.value.live.find((a) => a.kind === "preview" && a.branch === branch) : undefined;
289 return app ? { name: project.name, slug: project.slug, url: app.url } : null;
290 }),
291 ).then((found) => found.filter((entry) => entry != null))
292 : Promise.resolve([]);
Pull requests say where they are live; the project menu shows what is coming293 return { preview, build, stacked };
294}
295
Fast pages, required checks on the branch, self-hosted runners, honest incidents296/** A project that uses this one, with its preview built against this change. */
297type Stacked = { name: string; slug: string; url: string };
298
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look299const PULL_NEEDS: Record<string, Capability> = {
300 stack: "run",
301 "agent-review": "run",
302 "rerun-workflow": "run",
Fast pages, required checks on the branch, self-hosted runners, honest incidents303 "rerun-failed": "run",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look304 message: "run",
305 merge: "merge",
306 unqueue: "merge",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar307 milestone: "triage",
308 base: "push",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look309};
310
Issues and pull requests replace intents and attempts311export async function action({ request, params, context }: Route.ActionArgs) {
312 assertSameOrigin(request);
313 const user = requireUser(context, request);
314 const form = await request.formData();
315 const path = { namespace: params.owner, name: params.repo };
316 const number = Number(params.number);
317 const action = form.get("action");
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look318 // What each form needs beyond reading; work checks the rest.
319 const needed = typeof action === "string" ? PULL_NEEDS[action] : undefined;
320 const refused = needed ? await refusal(context, params, needed) : null;
321 if (refused) return { error: refused, action: String(action) };
Acceptance checks in sandboxes, line comments and review verdicts322 const verdict = form.get("verdict");
323 const line = Number(form.get("line"));
Agents as a team: lifecycle, merge queue, billing and a new shell324 /** Names ticked in a people picker, plus those typed beside it. */
325 const picked = (field: string) => [
326 ...form.getAll(field).map(String),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar327 // `@acme/backend` is asked as `acme/backend`.
328 ...String(form.get("others") ?? "").split(/[\s,]+/).map((name) => name.replace(/^@/, "")),
Agents as a team: lifecycle, merge queue, billing and a new shell329 ];
Project dependencies: addresses, preview stacks, Affects, and agents who know330 // The projects that use this one, built against this pull request's preview.
331 if (action === "stack") {
332 const built = await deployments.stack(user, { workspace: params.owner, slug: params.repo }, String(form.get("branch") ?? ""));
333 return built.ok
334 ? { action, notice: `Building ${built.value.join(", ")} against this preview. They appear on their Deployments pages.` }
335 : { action, error: built.error.message };
336 }
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent337 // Asking g1t for its review records the request, then starts it.
Agents as a team: lifecycle, merge queue, billing and a new shell338 if (action === "agent-review") {
339 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-agent340 reviewers: [...form.getAll("reviewer").map(String), "g1t"],
Agents as a team: lifecycle, merge queue, billing and a new shell341 });
342 if (!asked.ok) return { error: asked.error.message, action };
343 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar344 // Its labels, milestone and base, from the sidebar's menus and the header.
345 if (action === "labels") {
346 const set = await work.setLabels(user, path, number, form.getAll("label").map(String));
347 return set.ok ? null : { error: set.error.message, action };
348 }
349 if (action === "milestone" || action === "base") {
350 const changed = await work.updatePull(
351 user,
352 path,
353 number,
354 action === "milestone"
355 ? { milestone: Number(form.get("milestone")) || 0 }
356 : { base: String(form.get("base") ?? "") },
357 );
358 return changed.ok ? null : { error: changed.error.message, action };
359 }
Catching up with main takes seconds when the two sides touched different files360 // Catching up: merged and pushed in seconds when the two sides changed
361 // different files; otherwise handed to a sandbox, which takes a minute.
362 if (action === "update") {
363 const caught = await work.catchUpPull(user, path, number);
364 if (!caught.ok) return { action, error: caught.error.message };
365 const update = caught.value;
366 if (update.outcome !== "needs_agent") {
367 return {
368 action,
369 updated: { commit: update.commit, already: update.outcome === "up_to_date", at: Date.now() },
370 };
371 }
372 const started = await env.RUNNER.update(user, path, number);
373 if (!started.ok) return { action, error: started.error.message };
374 return { action, agent: { update, startedAt: Date.now() } };
375 }
Agents and memory, checks and conflicts, profiles, slug renames, custom domains376 // A workflow run's failed jobs, run again. Who may is the actions service's call.
377 if (action === "rerun-workflow") {
378 const rerun = await actions.rerun(user, path, String(form.get("run") ?? ""), true);
379 return rerun.ok ? null : { error: rerun.error.message, action };
380 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents381 // Every failed run on its head, run again: Mission control's quick action.
382 if (action === "rerun-failed") {
383 const found = await work.getPull(path, number, user);
384 if (!found.ok) return { error: found.error.message, action };
385 const failed = [
386 ...new Set(
387 (found.value.statuses ?? [])
388 .filter((status) => status.state === "failure" || status.state === "error")
389 .map(runIdOf)
390 .filter((id) => id != null),
391 ),
392 ];
393 if (failed.length === 0) return { error: "No workflow run failed on its latest commit.", action };
394 const reruns = await Promise.all(failed.map((id) => actions.rerun(user, path, id, true)));
395 const refused = reruns.find((rerun) => !rerun.ok);
396 return refused && !refused.ok ? { error: refused.error.message, action } : null;
397 }
Issues and pull requests replace intents and attempts398 const result =
399 action === "merge"
Acceptance checks in sandboxes, line comments and review verdicts400 ? await work.mergePull(user, path, number, {
401 keepIssueOpen: form.get("keepIssueOpen") === "on",
402 ignoreChecks: form.get("ignoreChecks") === "on",
Rules on the site: rulesets for a repository and a workspace, and the merge box403 bypassRules: form.get("bypassRules") === "on",
Acceptance checks in sandboxes, line comments and review verdicts404 })
Agents as a team: lifecycle, merge queue, billing and a new shell405 : action === "unqueue"
406 ? await work.removeFromQueue(user, path, number)
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request407 : action === "message"
408 ? await work.messageAgent(user, path, number, String(form.get("body") ?? ""))
Issues and pull requests replace intents and attempts409 : action === "close"
410 ? await work.closePull(user, path, number)
Fast pages, required checks on the branch, self-hosted runners, honest incidents411 : action === "agent-review"
Agents as a team: lifecycle, merge queue, billing and a new shell412 ? await env.RUNNER.review(user, path, number)
413 : action === "reviewers"
414 ? await work.updatePull(user, path, number, { reviewers: picked("reviewer") })
415 : action === "assign"
416 ? await work.updatePull(user, path, number, { assignees: picked("assignee") })
Acceptance checks in sandboxes, line comments and review verdicts417 : action === "comment"
418 ? await work.addComment(user, path, number, {
419 body: String(form.get("body") ?? ""),
420 path: String(form.get("path") ?? "") || undefined,
421 line: line > 0 ? line : undefined,
422 verdict:
423 verdict === "approve" || verdict === "request_changes" ? verdict : undefined,
424 })
425 : await work.readyPull(user, path, number, String(form.get("summary") ?? ""));
Issues and pull requests replace intents and attempts426 return result.ok ? null : { error: result.error.message, action };
427}
428
Pull requests: title and tabs across the full width, with counts and the size of the change429/** How many, beside a tab's name, when there are any. */
430function TabCount({ n }: { n: number }) {
431 if (n <= 0) return null;
432 return (
433 <span className="rounded-full bg-raised px-1.5 py-px text-xs tabular-nums text-muted ring-1 ring-line">
434 {n.toLocaleString("en-US")}
435 </span>
436 );
437}
438
Issues and pull requests replace intents and attempts439function TabLink({
440 to,
441 active,
442 children,
443}: {
444 to: string;
445 active: boolean;
446 children: React.ReactNode;
447}) {
448 return (
449 <Link
450 to={to}
451 preventScrollReset
Usage, Billing settings and prepaid AI credit; fixes from the UX audit452 aria-current={active ? "page" : undefined}
Issues and pull requests replace intents and attempts453 className={
Usage, Billing settings and prepaid AI credit; fixes from the UX audit454 "-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 attempts455 (active
456 ? "border-accent font-medium text-fg"
457 : "border-transparent text-muted hover:text-fg")
458 }
459 >
460 {children}
461 </Link>
462 );
463}
464
465/** One step of the session, on the timeline's rail. */
466function Entry({ entry, agent }: { entry: SessionEntry; agent: string }) {
467 const isTool = entry.kind === "tool_call" || entry.kind === "tool_result";
468 const Icon =
469 entry.kind === "prompt"
470 ? User
471 : entry.kind === "note"
472 ? StickyNote
473 : isTool
474 ? Wrench
475 : Bot;
476 return (
477 <li className="relative pl-10">
478 <span
479 className={`absolute top-0.5 left-0 flex size-7 items-center justify-center rounded-full border bg-bg ${
480 entry.kind === "prompt"
481 ? "border-accent/50 text-accent"
482 : "border-line text-faint"
483 }`}
484 >
485 <Icon size={14} />
486 </span>
487 {isTool ? (
488 <details className="group rounded-lg border border-line bg-surface">
489 <summary className="flex cursor-pointer list-none items-center gap-2 px-3 py-1.5 text-sm">
490 <ChevronRight
491 size={14}
492 className="text-faint transition-transform group-open:rotate-90"
493 />
494 <span className="font-mono text-xs text-accent">
495 {entry.tool ?? "tool"}
496 </span>
497 <span className="truncate font-mono text-xs text-muted">
498 {entry.kind === "tool_result" ? "→ " : ""}
499 {entry.text.split("\n")[0]}
500 </span>
501 </summary>
502 <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 line503 <code>{entry.text}</code>
Issues and pull requests replace intents and attempts504 </pre>
505 </details>
506 ) : (
507 <div>
508 <p className="text-xs font-medium text-faint">
509 {entry.kind === "prompt"
510 ? "Prompt"
511 : entry.kind === "note"
512 ? "Note"
513 : agent}
514 </p>
515 {entry.kind === "message" ? (
516 <div className="mt-1">
517 <Markdown source={entry.text} />
518 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell519 ) : entry.kind === "prompt" ? (
520 <div className="mt-1.5 max-h-[32rem] overflow-y-auto rounded-xl border border-line bg-surface p-4">
521 <Markdown source={entry.text} />
522 </div>
Issues and pull requests replace intents and attempts523 ) : (
Agents as a team: lifecycle, merge queue, billing and a new shell524 <p className="mt-1 text-[0.9375rem] leading-relaxed wrap-break-word whitespace-pre-wrap">
Issues and pull requests replace intents and attempts525 {entry.text}
526 </p>
527 )}
528 </div>
529 )}
530 {entry.commit && (
531 <p className="mt-1.5 flex items-center gap-1 font-mono text-xs text-faint">
532 <GitCommitHorizontal size={12} />
533 {entry.commit.slice(0, 7)}
534 </p>
535 )}
536 </li>
537 );
538}
539
Agents as a team: lifecycle, merge queue, billing and a new shell540/** The box at the foot of the conversation saying what stands before a merge. */
541function StatusBox({ children }: { children: React.ReactNode }) {
542 return (
543 <div className="flex gap-3">
544 <span className="hidden w-8 shrink-0 sm:block" />
545 <section className="min-w-0 grow divide-y divide-line overflow-hidden rounded-xl border border-line-strong bg-surface">
546 {children}
547 </section>
548 </div>
549 );
550}
551
552function StatusRow({
553 icon,
554 title,
555 children,
556}: {
557 icon: React.ReactNode;
558 title: string;
559 children?: React.ReactNode;
560}) {
561 return (
562 <div className="flex gap-3 px-4 py-3 text-sm">
563 <span className="mt-0.5 shrink-0">{icon}</span>
564 <div className="min-w-0">
565 <p className="font-medium">{title}</p>
566 {children && <div className="mt-0.5 text-muted">{children}</div>}
567 </div>
568 </div>
569 );
570}
571
Issues and pull requests replace intents and attempts572export default function PullPage({ loaderData, actionData, params }: Route.ComponentProps) {
573 const {
574 pull,
575 issue,
576 comments,
Acceptance checks in sandboxes, line comments and review verdicts577 checks,
Agents as a team: lifecycle, merge queue, billing and a new shell578 overlaps,
579 behind,
580 reviewPending,
581 lifecycle,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request582 messages,
GitHub Actions on g1t, part three: .g1t/workflows, the pages, the docs583 statuses = [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents584 requiredChecks = [],
585 noChecks,
586 canAddCi,
587 canProtect,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains588 mergeable = "unknown",
589 conflicts = [],
590 earlierChecks = [],
591 workflowJobs,
Project dependencies: addresses, preview stacks, Affects, and agents who know592 affects,
593 preview,
Pull requests say where they are live; the project menu shows what is coming594 build,
595 stacked,
Agents as a team: lifecycle, merge queue, billing and a new shell596 landing,
597 stalled,
598 requireUpToDate,
599 mergeQueue,
600 requiredApprovals,
601 canIgnoreChecks,
602 canUpdate,
603 agentsEnabled,
Merge branch 'worktree-agent-aca4790ea223aa235'604 agentRuns,
Agents as a team: lifecycle, merge queue, billing and a new shell605 members,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar606 teams,
607 codeOwners,
Rules on the site: rulesets for a repository and a workspace, and the merge box608 rules,
Issues and pull requests replace intents and attempts609 tab,
610 session,
611 comparison,
612 viewer,
613 canMerge,
614 canManage,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look615 canRun,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar616 defaultBranch: repoDefault,
Issues and pull requests replace intents and attempts617 } = loaderData;
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar618 // The branch it merges into: what every line below names.
619 const defaultBranch = pull.base ?? repoDefault;
Issues and pull requests replace intents and attempts620 const base = `/${params.owner}/${params.repo}`;
621 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-agent622 // g1t, on a pull request it made: the person who asked for it is a line below.
623 const opener = openedBy(pull);
Merge branch 'worktree-agent-aca4790ea223aa235'624 const checksSection = (jobs: Record<string, Job[]>, loading = false) => (
Fast pages, required checks on the branch, self-hosted runners, honest incidents625 <ChecksSection
626 run={checks}
627 required={requiredChecks}
628 statuses={statuses}
629 jobs={jobs}
630 pull={pull}
631 base={base}
632 earlier={earlierChecks}
633 canRerunWorkflows={canMerge}
634 settingsUrl={canProtect ? `${base}/settings/branches` : null}
635 error={actionData?.action === "rerun-failed" || actionData?.action === "rerun-workflow" ? actionData.error : null}
Merge branch 'worktree-agent-aca4790ea223aa235'636 loading={loading}
Fast pages, required checks on the branch, self-hosted runners, honest incidents637 />
638 );
Merge branch 'worktree-agent-aaf03bdceac799c89'639 const addresses = useAddresses();
Pull requests from branches640 const remote = pull.fork
Merge branch 'worktree-agent-aaf03bdceac799c89'641 ? cloneUrl(addresses, `${pull.fork.namespace}/${pull.fork.name}`)
642 : cloneUrl(addresses, `${params.owner}/${params.repo}`);
Issues and pull requests replace intents and attempts643 const active = pull.status === "draft" || pull.status === "open";
644
Acceptance checks in sandboxes, line comments and review verdicts645 // Follow an agent at work, or checks in progress, without a manual reload.
Issues and pull requests replace intents and attempts646 const working = pull.status === "draft";
Fast pages, required checks on the branch, self-hosted runners, honest incidents647 const checking = statuses.some((status) => status.state === "pending");
Acceptance checks in sandboxes, line comments and review verdicts648 const reviews = verdicts(comments);
Fast pages, required checks on the branch, self-hosted runners, honest incidents649 // What stands between this pull request and a merge, if anything: a
650 // required check that has not passed, or the merge queue taking it out.
651 const unchecked = requiredChecks.some((check) => check.state !== "success") || checks?.status === "failed";
652 const requiredFailed = requiredChecks.some((check) => check.state === "failure") || checks?.status === "failed";
Agents as a team: lifecycle, merge queue, billing and a new shell653 // Pull requests for other issues changing the same files will conflict;
654 // ones for the same issue are alternatives, and expected to.
655 const collisions = overlaps.filter((other) => other.issue == null || other.issue !== pull.issue);
656 const review = {
657 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 rights658 // Nobody reviews their own pull request, nor one g1t made for them.
659 canJudge: active && viewer != null && viewer.id !== workOwner(pull).id,
Agents as a team: lifecycle, merge queue, billing and a new shell660 };
661 // Everyone whose review was asked for, then anyone who reviewed unasked.
662 const reviewerNames = [
663 ...new Set([...pull.reviewers, ...reviews.map(({ reviewer }) => reviewer)]),
664 ];
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar665 const teamReviewers = pull.teamReviewers ?? [];
666 // Teams to ask: the workspace's, and any asked already.
667 const teamChoices = [
668 ...teams,
669 ...teamReviewers.filter((ref) => !teams.some((team) => team.ref === ref)).map((ref) => ({ ref, name: ref })),
670 ];
671 // Branch protection wants code owners' approval it does not have yet.
672 const ownersMissing = codeOwners?.required && codeOwners.missing ? codeOwners.missing : null;
Catching up with main takes seconds when the two sides touched different files673 // A catch-up: the click shows at once; the answer says whether it is
674 // done already or a sandbox is on it.
Merge branch 'worktree-agent-a985c01f43538af01'675 const catchUpPending = usePending({ action: "update" });
Catching up with main takes seconds when the two sides touched different files676 const catchUp = actionData?.action === "update" ? actionData : null;
677 const caughtUp = catchUp && "updated" in catchUp ? catchUp.updated : null;
678 const agentCatchUp = catchUp && "agent" in catchUp ? catchUp.agent : null;
679 // Pushed already: followed until the pull request's head is the merge,
680 // so its checks show starting again, for a minute at most.
681 const settling =
682 caughtUp != null && !caughtUp.already && pull.headCommit !== caughtUp.commit && Date.now() - caughtUp.at < 60_000;
683 // Followed until it is pushed, or for so long, never longer.
684 const catchingUp =
685 agentCatchUp != null && behind && Date.now() - agentCatchUp.startedAt < CATCH_UP_TIMEOUT_MS + REFRESH_MS;
Agents as a team: lifecycle, merge queue, billing and a new shell686 // g1t is taking a step of its own accord, so the page will change.
687 const moving =
688 lifecycle != null && lifecycle.stage !== "ready" && lifecycle.stage !== "needs_you";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains689 // Whether it merges cleanly is being worked out, so the box will change.
690 const probing = active && mergeable === "checking";
691 const conflicting = active && mergeable === "conflicting";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending692 useRefreshWhile(working || checking || reviewPending || catchingUp || settling || moving || landing || probing);
Rules on the site: rulesets for a repository and a workspace, and the merge box693 // The rules of its base it does not meet: checks show in their own
694 // section, so the rest stop the button here. Someone a ruleset lets
695 // bypass it may tick a box and merge past them.
696 const [bypassing, setBypassing] = useState(false);
697 const rulesUnmet = (rules?.unmet ?? []).filter((violation) => violation.rule !== "required_status_checks");
698 const rulesBypassable = rules?.bypassable ?? [];
Agents and memory, checks and conflicts, profiles, slug renames, custom domains699 // Why the merge button cannot be pressed, if it cannot.
700 const mergeBlocked = conflicting
701 ? "Resolve the conflicts first."
702 : probing
703 ? "Waiting to find out whether it merges cleanly."
704 : behind && requireUpToDate
Rules on the site: rulesets for a repository and a workspace, and the merge box705 ? `The rules for ${defaultBranch} require it to be up to date first.`
Fast pages, required checks on the branch, self-hosted runners, honest incidents706 : unchecked && !canIgnoreChecks
707 ? `The checks ${defaultBranch} requires have to pass first.`
Rules on the site: rulesets for a repository and a workspace, and the merge box708 : rulesUnmet.length > 0
709 ? rulesUnmet[0]!.message
710 : rulesBypassable.length > 0 && !bypassing
711 ? `Rules for ${defaultBranch} are not met. You may bypass them.`
712 : ownersMissing
713 ? "Code owners have to approve first."
714 : null;
Issues and pull requests replace intents and attempts715
716 return (
Agents as a team: lifecycle, merge queue, billing and a new shell717 // 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 change718 <div>
719 {/* The title and the tabs run the full width; what is happening on
720 the pull request sits at the top of Conversation. */}
721 <header>
Issues and pull requests replace intents and attempts722 <h2 className="text-2xl font-semibold tracking-tight text-balance">
723 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
724 </h2>
725 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
726 <PullState status={pull.status} />
727 <span className="flex items-center gap-2">
Agents as a team: lifecycle, merge queue, billing and a new shell728 <Avatar
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent729 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents as a team: lifecycle, merge queue, billing and a new shell730 size={18}
731 />
Issues and pull requests replace intents and attempts732 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains733 <PersonLink
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent734 name={(pull.status === "merged" && pull.mergedBy) || opener.name}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains735 className="font-medium text-fg hover:underline"
736 />{" "}
Pull requests from branches737 {pull.status === "merged" ? "merged" : "wants to merge"}
738 {pull.branch && (
739 <>
740 {" "}
741 <span className="font-mono text-fg">{pull.branch}</span>
742 </>
743 )}{" "}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar744 into{" "}
745 <BaseBranch
746 base={defaultBranch}
747 head={pull.fork ? null : pull.branch}
748 canChange={loaderData.canChangeBase && active}
749 branchesUrl={`${base}/branches.json`}
750 />
Issues and pull requests replace intents and attempts751 </span>
752 </span>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar753 {(pull.labels ?? []).map((name) => (
754 <LabelChip key={name} name={name} color={loaderData.labels.find((label) => label.name === name)?.color} />
755 ))}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent756 {opener.requestedBy && (
757 <span className="text-xs">
758 requested by{" "}
759 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
760 </span>
761 )}
762 {/* A pull request from a branch was made by its author, not an agent;
763 one g1t made already says so. */}
764 {!pull.branch && !opener.requestedBy && (
Pull requests from branches765 <span className="flex items-center gap-1.5 font-mono text-xs">
766 <Bot size={14} />
767 {pull.agent}
768 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
769 </span>
770 )}
Issues and pull requests replace intents and attempts771 </div>
772
Acceptance checks in sandboxes, line comments and review verdicts773 {reviews.length > 0 && (
774 <p className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
775 {reviews.map(({ reviewer, verdict }) => (
776 <span
777 key={reviewer}
778 className={`flex items-center gap-1.5 ${
779 verdict === "approve" ? "text-accent" : "text-danger"
780 }`}
781 >
782 {verdict === "approve" ? <CircleCheck size={15} /> : <CircleSlash size={15} />}
783 {verdict === "approve" ? "Approved by" : "Changes requested by"}{" "}
784 <span className="font-medium">{reviewer}</span>
785 </span>
786 ))}
787 </p>
788 )}
789
Usage, Billing settings and prepaid AI credit; fixes from the UX audit790 <TabStrip label="Pull request" className="mt-6 items-end gap-1 border-b border-line">
Issues and pull requests replace intents and attempts791 <TabLink to={here} active={tab === "conversation"}>
792 <MessageSquare size={15} />
793 Conversation
Pull requests: title and tabs across the full width, with counts and the size of the change794 <TabCount n={comments.length} />
Issues and pull requests replace intents and attempts795 </TabLink>
796 <TabLink to={here + "?tab=session"} active={tab === "session"}>
797 <MessagesSquare size={15} />
798 Session
799 </TabLink>
800 <TabLink to={here + "?tab=changes"} active={tab === "changes"}>
801 <FileDiff size={15} />
Pull requests: title and tabs across the full width, with counts and the size of the change802 Files changed
803 <TabCount n={pull.files.length} />
Issues and pull requests replace intents and attempts804 </TabLink>
Pull requests: title and tabs across the full width, with counts and the size of the change805 {pull.files.length > 0 && (
806 <span className="ml-auto hidden pb-2.5 font-mono text-xs sm:inline">
807 <span className="text-accent">+{pull.files.reduce((sum, file) => sum + file.additions, 0).toLocaleString("en-US")}</span>{" "}
808 <span className="text-danger">−{pull.files.reduce((sum, file) => sum + file.deletions, 0).toLocaleString("en-US")}</span>
809 </span>
810 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit811 </TabStrip>
Pull requests: title and tabs across the full width, with counts and the size of the change812 </header>
813 <div className={`mt-6 grid gap-8 ${tab === "changes" ? "" : "lg:grid-cols-[1fr_19rem]"}`}>
814 <div className="min-w-0">
815 {/* What is happening on it now: its stage, its agent, what people told
816 the agent, and what else touches the same files. On the conversation,
817 so the tabs sit right under the title. */}
818 {tab === "conversation" && (
819 <div>
820 {issue && (
821 <Link
822 to={`${base}/issues/${issue.number}`}
823 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"
824 >
825 <IssueIcon issue={issue} />
826 <span className="text-muted">
827 {pull.status === "merged" && issue.resolvedBy === pull.number
828 ? "Resolved"
829 : "For issue"}
830 </span>
831 <span className="truncate font-medium">{issue.title}</span>
832 <span className="text-faint">#{issue.number}</span>
833 {issue.pullCount > 1 && (
834 <span className="ml-auto shrink-0 text-xs text-faint">
835 1 of {issue.pullCount} pull requests
Pull requests show their tabs right under the title; agent panels move into Conversation836 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change837 )}
838 </Link>
839 )}
840 {pull.status === "merged" && (
841 <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">
842 <GitMerge size={16} className="shrink-0 text-merged" />
Agents as a team: lifecycle, merge queue, billing and a new shell843 <span>
Pull requests: title and tabs across the full width, with counts and the size of the change844 Merged into <span className="font-mono">{defaultBranch}</span> by{" "}
845 <span className="font-medium">{pull.mergedBy}</span>{" "}
846 {/* Not an account: the repository's settings said to merge it. */}
847 {pull.mergedBy === "g1t" && "automatically, once it was ready, "}
848 {pull.mergedAt && <TimeAgo at={pull.mergedAt} />}
849 {pull.headCommit && (
850 <span className="font-mono text-muted"> · {pull.headCommit.slice(0, 7)}</span>
Agents as a team: lifecycle, merge queue, billing and a new shell851 )}
852 </span>
Pull requests: title and tabs across the full width, with counts and the size of the change853 </p>
854 )}
855 {pull.supersededBy != null && (
856 <p className="mt-4 rounded-xl border border-line bg-surface px-4 py-2.5 text-sm text-muted">
857 Closed because{" "}
858 <Link
859 to={`${base}/pull/${pull.supersededBy}`}
860 className="font-medium text-fg hover:underline"
861 >
862 #{pull.supersededBy}
863 </Link>{" "}
864 was merged for this issue instead.
865 </p>
866 )}
867 {lifecycle && <LifecyclePanel lifecycle={lifecycle} />}
868 {/* 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 step869 <AgentPanel
870 owner={params.owner}
871 repo={params.repo}
872 number={pull.number}
873 stage={lifecycle?.stage ?? null}
874 confidence={pull.confidence ?? null}
Merge branch 'worktree-agent-aca4790ea223aa235'875 runs={agentRuns}
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step876 />
Pull requests: title and tabs across the full width, with counts and the size of the change877
878 {/* 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 look879 {canRun &&
Pull requests: title and tabs across the full width, with counts and the size of the change880 pull.runtime === "hosted" &&
881 (working || ["working", "revising", "catching_up", "answering"].includes(lifecycle?.stage ?? "")) && (
Merge branch 'worktree-agent-a985c01f43538af01'882 // Keyed by the messages so the box empties once one shows below.
883 <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 change884 <p className="flex items-center gap-2 text-sm font-medium">
885 <Sparkles size={15} className="text-merged" />
886 Message the agent
887 </p>
888 <p className="mt-1 text-xs text-muted">
889 A correction, a hint, a change of plan. It reads it at its next step, without
890 starting over.
891 </p>
892 <div className="mt-3 flex gap-2">
893 <input type="hidden" name="action" value="message" />
894 <input
895 name="body"
896 required
897 autoComplete="off"
898 data-1p-ignore
899 placeholder="Keep the old flag working too…"
900 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 shell901 />
Merge branch 'worktree-agent-a985c01f43538af01'902 <SubmitButton match={{ action: "message" }} pending="Sending…">
903 Send
904 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change905 </div>
906 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell907 )}
Pull requests: title and tabs across the full width, with counts and the size of the change908 {messages.length > 0 && (
909 <ul className="mt-3 space-y-1.5">
910 {messages.map((message) => (
911 <li key={message.id} className="flex items-start gap-2 text-sm">
912 <Avatar name={message.author} size={18} />
913 <span className="min-w-0 grow">
914 <span className="font-medium">
915 {message.fromNumber != null ? `The agent on #${message.fromNumber}` : message.author}
916 </span>{" "}
917 <span className="text-muted">
918 {message.kind === "question"
919 ? "asked:"
920 : message.kind === "handoff"
921 ? "handed over:"
922 : message.kind === "answer"
923 ? "answered:"
924 : "to the agent:"}
925 </span>{" "}
926 {message.body}
927 {message.answer && (
928 <span className="mt-1 block border-l-2 border-merged/40 pl-2 text-muted">
929 {message.declined ? "Declined: " : "Answer: "}
930 {message.answer}
931 </span>
932 )}
933 </span>
934 <span className={`shrink-0 text-xs ${message.deliveredAt ? "text-accent" : "text-faint"}`}>
935 {message.deliveredAt ? "read by the agent" : "waiting for its next step"}
936 </span>
937 </li>
938 ))}
939 </ul>
940 )}
941 {collisions.length > 0 && active && (
942 <div className="mt-4 rounded-xl border border-line bg-surface px-4 py-3 text-sm">
943 <p className="flex items-center gap-2.5 font-medium">
944 <Radar size={16} className="shrink-0 text-info" />
945 Other work is changing the same files
946 </p>
947 <ul className="mt-2 space-y-1.5">
948 {collisions.map((other) => (
949 <li key={other.number} className="flex flex-wrap items-baseline gap-x-2 text-muted">
950 <Link
951 to={`${base}/pull/${other.number}`}
952 className="font-medium text-fg hover:underline"
953 >
954 {other.title} <span className="font-normal text-faint">#{other.number}</span>
955 </Link>
956 <span className="font-mono text-xs">{other.paths.join(", ")}</span>
957 </li>
958 ))}
959 </ul>
960 <p className="mt-2 text-xs text-faint">
961 Whichever merges second will have to catch up, and may conflict.
962 </p>
963 </div>
964 )}
965 </div>
966 )}
967 <div className="mt-5">
968 {comparison ? (
969 <DiffView
970 comparison={comparison}
971 review={{
972 comments: comments.filter((comment) => comment.path),
973 canComment: Boolean(viewer),
974 }}
975 />
976 ) : tab === "session" ? (
977 session.length === 0 ? (
978 <EmptyState title="Nothing recorded yet">
979 The agent's prompts, reasoning and tool calls appear here as it
980 works.
981 </EmptyState>
982 ) : (
983 <ol className="relative space-y-5 before:absolute before:top-2 before:bottom-2 before:left-3.25 before:w-px before:bg-line">
984 {session.map((entry) => (
985 <Entry key={entry.seq} entry={entry} agent={pull.agent} />
986 ))}
987 </ol>
988 )
989 ) : (
990 <div className="space-y-4">
991 <TimelineItem
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent992 author={opener.name}
Pull requests: title and tabs across the full width, with counts and the size of the change993 at={pull.createdAt}
994 action={
995 <span>
996 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-agent997 {opener.requestedBy ? (
Pull requests: title and tabs across the full width, with counts and the size of the change998 <>
999 {" "}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1000 for{" "}
1001 <PersonLink name={opener.requestedBy} className="font-medium text-fg-soft hover:underline" />
1002 </>
1003 ) : !pull.branch && (
1004 <>
1005 {" "}
Pull requests: title and tabs across the full width, with counts and the size of the change1006 with <span className="font-mono text-xs">{pull.agent}</span>
1007 </>
1008 )}
1009 </span>
1010 }
1011 >
1012 {pull.body ? (
1013 <Markdown source={pull.body} repo={{ namespace: params.owner, name: params.repo }} />
1014 ) : (
1015 <p className="text-sm text-muted">
1016 {working
1017 ? "No description yet. It is written when the pull request is marked ready for review."
1018 : "No description."}
1019 </p>
1020 )}
1021 {canManage && pull.status === "open" && (
1022 <details className="mt-3 border-t border-line pt-3 text-sm">
1023 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1024 Edit description
1025 </summary>
1026 <Form method="post" className="mt-3 space-y-2">
Merge branch 'worktree-agent-a985c01f43538af01'1027 <input type="hidden" name="action" value="describe" />
Pull requests: title and tabs across the full width, with counts and the size of the change1028 <Textarea
1029 name="summary"
1030 rows={6}
1031 placeholder="What changed and why"
1032 defaultValue={pull.body ?? ""}
1033 />
Merge branch 'worktree-agent-a985c01f43538af01'1034 <SubmitButton match={{ action: "describe" }} pending="Saving…">
1035 Save
1036 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1037 </Form>
1038 </details>
1039 )}
1040 </TimelineItem>
Agents as a team: lifecycle, merge queue, billing and a new shell1041
Pull requests: title and tabs across the full width, with counts and the size of the change1042 <CommentList comments={comments} review={review} base={base} />
Agents as a team: lifecycle, merge queue, billing and a new shell1043
Merge branch 'worktree-agent-aca4790ea223aa235'1044 {(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 coming1045
Pull requests: title and tabs across the full width, with counts and the size of the change1046 {pull.status === "draft" && (
1047 <StatusBox>
1048 <StatusRow icon={<Loader size={16} className="text-faint" />} title="This is a draft">
1049 It is still being worked on. It can be reviewed and merged once it is
1050 marked ready.
1051 </StatusRow>
1052 {canManage && (
1053 <Form method="post" className="space-y-2 px-4 py-3">
Merge branch 'worktree-agent-a985c01f43538af01'1054 <input type="hidden" name="action" value="ready" />
Pull requests: title and tabs across the full width, with counts and the size of the change1055 <Textarea name="summary" rows={3} placeholder="What changed and why" />
Merge branch 'worktree-agent-a985c01f43538af01'1056 <SubmitButton match={{ action: "ready" }} pending="Marking ready…">
1057 Mark ready for review
1058 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1059 </Form>
1060 )}
1061 </StatusBox>
1062 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1063
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1064 {codeOwners && active && (
1065 <div className="flex gap-3">
1066 <span className="hidden w-8 shrink-0 sm:block" />
1067 <div className="min-w-0 grow">
1068 <PullCodeOwnersPanel
1069 owners={codeOwners}
1070 base={base}
1071 branch={defaultBranch}
1072 errorsHref={canProtect ? `${base}/settings/branches#codeowners` : `${base}/blob/${encodeURIComponent(defaultBranch)}/${codeOwners.path}`}
1073 />
1074 </div>
1075 </div>
1076 )}
1077
Pull requests: title and tabs across the full width, with counts and the size of the change1078 {pull.status === "open" && (
1079 <StatusBox>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1080 {/* Checks by status at once; job by job when the runs are read. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1081 <Suspense fallback={checksSection({}, true)}>
1082 <Await resolve={workflowJobs}>{(jobs) => checksSection(jobs)}</Await>
Fast pages, required checks on the branch, self-hosted runners, honest incidents1083 </Suspense>
1084 {noChecks && <AddCiPrompt owner={params.owner} repo={params.repo} canAdd={canAddCi} compact />}
Agents as a team: lifecycle, merge queue, billing and a new shell1085 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1086 icon={
1087 reviews.some(({ verdict }) => verdict === "request_changes") ? (
1088 <CircleSlash size={16} className="text-danger" />
1089 ) : reviews.length > 0 ? (
1090 <CircleCheck size={16} className="text-accent" />
1091 ) : (
1092 <MessageSquare size={16} className="text-faint" />
1093 )
1094 }
1095 title={
1096 reviews.length === 0
1097 ? "No reviews yet"
1098 : reviews
1099 .map(
1100 ({ reviewer, verdict }) =>
1101 `${verdict === "approve" ? "Approved by" : "Changes requested by"} ${reviewer}`,
1102 )
1103 .join(" · ")
1104 }
Agents as a team: lifecycle, merge queue, billing and a new shell1105 >
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1106 {reviewPending && "g1t is reviewing it now. "}
Pull requests: title and tabs across the full width, with counts and the size of the change1107 {requiredApprovals > 0 &&
1108 `This repository requires ${requiredApprovals} approving ${
1109 requiredApprovals === 1 ? "review" : "reviews"
1110 } before merging.`}
Agents as a team: lifecycle, merge queue, billing and a new shell1111 </StatusRow>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1112 {codeOwners?.required && (
1113 <StatusRow
1114 icon={
1115 ownersMissing ? (
1116 <CircleSlash size={16} className="text-warn" />
1117 ) : (
1118 <CircleCheck size={16} className="text-accent" />
1119 )
1120 }
1121 title={ownersMissing ? "Waiting for code owners" : "Code owners approved"}
1122 >
1123 {ownersMissing ?? `Every file it changes has its owners' approval.`}{" "}
1124 {ownersMissing && "This repository requires their review before merging."}
1125 </StatusRow>
1126 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1127 {landing ? (
Agents as a team: lifecycle, merge queue, billing and a new shell1128 <StatusRow
Pull requests: title and tabs across the full width, with counts and the size of the change1129 icon={<Loader size={16} className="animate-spin text-accent" />}
1130 title="Merging"
1131 >
1132 {defaultBranch} has moved, so g1t is bringing this up to date first. It
1133 lands as soon as that is done. Watch it in the Session tab.
1134 </StatusRow>
Catching up with main takes seconds when the two sides touched different files1135 ) : agentCatchUp && behind ? (
1136 <CatchUpProgress
1137 owner={params.owner}
1138 repo={params.repo}
1139 number={pull.number}
1140 defaultBranch={defaultBranch}
1141 behind={behind}
1142 update={agentCatchUp.update}
1143 startedAt={agentCatchUp.startedAt}
1144 retrying={catchUpPending}
Merge branch 'worktree-agent-aca4790ea223aa235'1145 runs={agentRuns}
Catching up with main takes seconds when the two sides touched different files1146 />
Pull requests: title and tabs across the full width, with counts and the size of the change1147 ) : conflicting ? (
1148 <ConflictsSection
1149 conflicts={conflicts}
1150 pull={pull}
1151 owner={params.owner}
1152 repo={params.repo}
1153 defaultBranch={defaultBranch}
1154 changesUrl={here + "?tab=changes"}
1155 canResolve={canUpdate && agentsEnabled}
Catching up with main takes seconds when the two sides touched different files1156 resolving={catchUpPending}
1157 error={catchUp && "error" in catchUp ? catchUp.error : null}
Agents as a team: lifecycle, merge queue, billing and a new shell1158 />
Pull requests: title and tabs across the full width, with counts and the size of the change1159 ) : probing ? (
1160 <MergeabilityRow mergeable={mergeable} defaultBranch={defaultBranch} />
1161 ) : behind ? (
1162 <StatusRow
1163 icon={<GitPullRequestArrow size={16} className="text-info" />}
1164 title={`${defaultBranch} has moved since this was made`}
1165 >
1166 {mergeable === "clean" && "It has no conflicts with it. "}
1167 {requireUpToDate
1168 ? "This repository requires pull requests to be up to date, so it has to catch up before it can merge."
1169 : "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 files1170 {canUpdate && (
1171 <Form method="post" className="mt-2">
Pull requests: title and tabs across the full width, with counts and the size of the change1172 <input type="hidden" name="action" value="update" />
Merge branch 'worktree-agent-a985c01f43538af01'1173 <SubmitButton variant="quiet" match={{ action: "update" }} pending={`Merging ${defaultBranch} in…`}>
1174 Catch up with {defaultBranch} now
1175 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1176 </Form>
1177 )}
Catching up with main takes seconds when the two sides touched different files1178 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Pull requests: title and tabs across the full width, with counts and the size of the change1179 </StatusRow>
1180 ) : (
Merge branch 'worktree-agent-af9f0f386d52b6a73'1181 (pull.headCommit || (catchUp && "error" in catchUp)) && (
Pull requests: title and tabs across the full width, with counts and the size of the change1182 <StatusRow
1183 icon={<CircleCheck size={16} className="text-accent" />}
Catching up with main takes seconds when the two sides touched different files1184 title={
1185 (caughtUp && !caughtUp.already) || agentCatchUp
1186 ? `Brought up to date with ${defaultBranch}`
1187 : `Up to date with ${defaultBranch}`
1188 }
1189 >
1190 {caughtUp && !caughtUp.already && (
1191 <>
1192 Merged <span className="font-mono">{defaultBranch}</span> in as{" "}
1193 <span className="font-mono text-fg">{caughtUp.commit.slice(0, 7)}</span>. Its checks run
1194 again on the new commit.
1195 </>
1196 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1197 {agentCatchUp && "g1t merged it in and pushed the result. Its checks run again on the new commit."}
Merge branch 'worktree-agent-af9f0f386d52b6a73'1198 {/* A catch-up refused or failed after the pull request stopped being behind. */}
1199 {catchUp && "error" in catchUp && <ErrorText>{catchUp.error}</ErrorText>}
Catching up with main takes seconds when the two sides touched different files1200 </StatusRow>
Pull requests: title and tabs across the full width, with counts and the size of the change1201 )
1202 )}
Rules on the site: rulesets for a repository and a workspace, and the merge box1203 {active && rules && (rulesUnmet.length > 0 || rulesBypassable.length > 0 || rules.evaluate.length > 0) && (
1204 <StatusRow
1205 icon={
1206 <Scale
1207 size={16}
1208 className={rulesUnmet.length > 0 ? "text-danger" : rulesBypassable.length > 0 ? "text-warn" : "text-info"}
1209 />
1210 }
1211 title={
1212 rulesUnmet.length > 0
1213 ? `Rules for ${defaultBranch} are not met yet`
1214 : rulesBypassable.length > 0
1215 ? `You may bypass the rules for ${defaultBranch}`
1216 : "Rulesets being evaluated would refuse this merge"
1217 }
1218 >
1219 <div className="mt-2 space-y-3">
1220 {rulesUnmet.length > 0 && <ViolationList violations={rulesUnmet} />}
1221 {rulesBypassable.length > 0 && (
1222 <div>
1223 <p className="mb-1.5 text-xs text-muted">Not met, but a ruleset lets you bypass it:</p>
1224 <ViolationList violations={rulesBypassable} tone="warn" />
1225 </div>
1226 )}
1227 {rules.evaluate.length > 0 && (
1228 <div>
1229 <p className="mb-1.5 text-xs text-muted">Rulesets in evaluate would refuse it for this; nothing is held up:</p>
1230 <ViolationList violations={rules.evaluate} tone="info" />
1231 </div>
1232 )}
1233 {canProtect && (
1234 <Link
1235 to={`${base}/settings/rules?branch=${encodeURIComponent(defaultBranch)}`}
1236 className="inline-block text-xs text-muted underline underline-offset-2 hover:text-fg"
1237 >
1238 See every rule for {defaultBranch}
1239 </Link>
1240 )}
1241 </div>
1242 </StatusRow>
1243 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1244 {stalled && !lifecycle && (
1245 <StatusRow icon={<Hand size={16} className="text-warn" />} title="Needs you">
1246 {stalled}
1247 </StatusRow>
1248 )}
1249 {canMerge && lifecycle?.stage === "queued" && (
1250 <Form method="post" className="flex flex-wrap items-center gap-3 px-4 py-3">
1251 <ButtonLink to={`${base}/queue`}>
1252 <Layers size={15} />
1253 See the queue
1254 </ButtonLink>
Merge branch 'worktree-agent-a985c01f43538af01'1255 <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 change1256 Remove from the queue
Merge branch 'worktree-agent-a985c01f43538af01'1257 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1258 </Form>
1259 )}
1260 {canMerge && !landing && lifecycle?.stage !== "queued" && (
1261 <Form method="post" className="space-y-3 px-4 py-3">
1262 {issue?.state === "open" && (
1263 <CheckboxOption
1264 name="keepIssueOpen"
1265 label={`Keep #${issue.number} open. This is only part of the work.`}
1266 labelClassName="text-xs text-muted"
1267 />
1268 )}
1269 {unchecked && canIgnoreChecks && (
1270 <CheckboxOption
1271 name="ignoreChecks"
Fast pages, required checks on the branch, self-hosted runners, honest incidents1272 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 change1273 labelClassName="text-xs text-muted"
1274 />
1275 )}
Rules on the site: rulesets for a repository and a workspace, and the merge box1276 {rulesBypassable.length > 0 && rulesUnmet.length === 0 && (
1277 <CheckboxOption
1278 name="bypassRules"
1279 checked={bypassing}
1280 onCheckedChange={(checked) => setBypassing(checked === true)}
1281 label={`Bypass the rules for ${defaultBranch}: merge although they are not met. It is recorded.`}
1282 labelClassName="text-xs text-muted"
1283 />
1284 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1285 <div className="flex flex-wrap items-center gap-3">
Merge branch 'worktree-agent-a985c01f43538af01'1286 <SubmitButton
Pull requests: title and tabs across the full width, with counts and the size of the change1287 variant="accent"
1288 name="action"
1289 value="merge"
1290 disabled={mergeBlocked != null}
1291 title={mergeBlocked ?? undefined}
Merge branch 'worktree-agent-a985c01f43538af01'1292 pending={mergeQueue ? "Adding to the queue…" : "Merging…"}
Pull requests: title and tabs across the full width, with counts and the size of the change1293 >
1294 {mergeQueue ? <Layers size={15} /> : <GitMerge size={15} />}
1295 {mergeQueue ? "Add to the merge queue" : `Merge into ${defaultBranch}`}
Merge branch 'worktree-agent-a985c01f43538af01'1296 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1297 <span className={`text-xs ${mergeBlocked ? "text-danger" : "text-muted"}`}>
1298 {mergeBlocked ?? (mergeQueue
1299 ? `Tested together with everything ahead of it, then lands on ${defaultBranch}.`
1300 : issue?.state === "open"
1301 ? `Closes issue #${issue.number}, and any other pull requests still open for it.`
1302 : `Lands its commits on ${defaultBranch}.`)}
1303 </span>
1304 </div>
1305 {actionData?.action === "merge" && (
1306 <ErrorText>{actionData.error}</ErrorText>
1307 )}
1308 </Form>
1309 )}
1310 </StatusBox>
1311 )}
Agents as a team: lifecycle, merge queue, billing and a new shell1312
Pull requests: title and tabs across the full width, with counts and the size of the change1313 <CommentForm
1314 author={viewer?.username ?? null}
1315 resetKey={comments.length}
1316 review={review}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1317 handles={teams.map((team) => `@${team.ref}`)}
Pull requests: title and tabs across the full width, with counts and the size of the change1318 />
1319 {canManage && active && (
1320 <Form method="post" className="flex justify-end">
Merge branch 'worktree-agent-a985c01f43538af01'1321 <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 change1322 Close pull request
Merge branch 'worktree-agent-a985c01f43538af01'1323 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1324 </Form>
1325 )}
1326 {actionData &&
Merge branch 'worktree-agent-a985c01f43538af01'1327 !["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 change1328 String(actionData.action),
1329 ) && <ErrorText>{actionData.error}</ErrorText>}
1330 </div>
1331 )}
1332 {actionData?.action === "comment" && (
1333 <div className="mt-2">
1334 <ErrorText>{actionData.error}</ErrorText>
1335 </div>
1336 )}
1337 </div>
1338 </div>
1339
1340 <aside className={tab === "changes" ? "hidden" : "space-y-6"}>
1341 {/* An open pull request shows its checks in full in the merge box. */}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1342 {pull.status !== "open" && <WorkflowStatuses statuses={statuses} />}
Pull requests: title and tabs across the full width, with counts and the size of the change1343 {affects.length > 0 && (
1344 <section>
1345 <h3 className="flex items-center gap-1.5 text-sm font-medium">
1346 <Network size={14} className="text-faint" />
1347 Affects
1348 </h3>
1349 <p className="mt-1 text-xs text-muted">Projects that use this one, and so may feel this change:</p>
1350 <ul className="mt-2 space-y-1 text-sm">
1351 {affects.map((project) => (
1352 <li key={project.slug} className="flex items-center justify-between gap-2">
1353 <Link to={`/${params.owner}/${project.slug}`} className="hover:underline">
1354 {project.name}
1355 </Link>
1356 {project.as && <code className="font-mono text-xs text-faint">{project.as}</code>}
1357 </li>
1358 ))}
1359 </ul>
1360 {preview?.branch && canMerge && (
1361 <Form method="post" className="mt-3">
1362 <input type="hidden" name="action" value="stack" />
1363 <input type="hidden" name="branch" value={preview.branch} />
Merge branch 'worktree-agent-a985c01f43538af01'1364 <SubmitButton
1365 match={{ action: "stack" }}
1366 pending="Starting the builds…"
1367 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 change1368 >
1369 Preview them against this change
Merge branch 'worktree-agent-a985c01f43538af01'1370 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell1371 </Form>
1372 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1373 {actionData?.action === "stack" &&
1374 ("notice" in actionData ? (
1375 <p className="mt-2 text-xs text-accent">{String(actionData.notice)}</p>
1376 ) : (
1377 <ErrorText>{actionData.error}</ErrorText>
1378 ))}
1379 </section>
Acceptance checks in sandboxes, line comments and review verdicts1380 )}
Issues and pull requests replace intents and attempts1381
Project dependencies: addresses, preview stacks, Affects, and agents who know1382 <section>
Pull requests: title and tabs across the full width, with counts and the size of the change1383 <h3 className="text-sm font-medium">Reviewers</h3>
1384 <ul className="mt-2 space-y-1.5 text-sm">
1385 {reviewerNames.map((name) => {
1386 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-agent1387 const pending = name === "g1t" && reviewPending;
Pull requests: title and tabs across the full width, with counts and the size of the change1388 return (
1389 <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-agent1390 {name === "g1t" ? (
Pull requests: title and tabs across the full width, with counts and the size of the change1391 <Sparkles size={16} className="shrink-0 text-accent" />
1392 ) : (
1393 <Avatar name={name} size={20} />
1394 )}
1395 <span className="grow truncate font-mono text-xs">{name}</span>
1396 {pending ? (
1397 <span className="flex items-center gap-1.5 text-xs text-muted">
1398 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
1399 Reviewing
1400 </span>
1401 ) : verdict === "approve" ? (
1402 <span className="flex items-center gap-1 text-xs text-accent">
1403 <CircleCheck size={13} /> Approved
1404 </span>
1405 ) : verdict === "request_changes" ? (
1406 <span className="flex items-center gap-1 text-xs text-danger">
1407 <CircleSlash size={13} /> Changes requested
1408 </span>
1409 ) : (
1410 <span className="text-xs text-faint">Review requested</span>
1411 )}
1412 </li>
1413 );
1414 })}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1415 {teamReviewers.map((team) => (
1416 <TeamReviewer key={team} team={team} />
1417 ))}
1418 {reviewerNames.length === 0 && teamReviewers.length === 0 && (
Pull requests: title and tabs across the full width, with counts and the size of the change1419 <li className="px-1 text-xs text-faint">No reviews requested yet.</li>
1420 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1421 </ul>
Pull requests: title and tabs across the full width, with counts and the size of the change1422 {pull.status === "open" && canManage && (
1423 <div className="mt-3 space-y-2">
1424 {agentsEnabled && !reviewPending && (
1425 <Form method="post">
1426 <input type="hidden" name="action" value="agent-review" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1427 {[...pull.reviewers, ...teamReviewers].map((name) => (
Pull requests: title and tabs across the full width, with counts and the size of the change1428 <input key={name} type="hidden" name="reviewer" value={name} />
1429 ))}
1430 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1431 <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 change1432 <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-agent1433 Request review from g1t
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>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1436 <ComputeNote note={loaderData.computeNote} />
Pull requests: title and tabs across the full width, with counts and the size of the change1437 </Form>
1438 )}
1439 <details>
1440 <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 bar1441 {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 change1442 </summary>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1443 <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 change1444 <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-agent1445 {pull.reviewers.includes("g1t") && (
1446 <input type="hidden" name="reviewer" value="g1t" />
Pull requests: title and tabs across the full width, with counts and the size of the change1447 )}
1448 <PeoplePicker
1449 name="reviewer"
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1450 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-agent1451 chosen={pull.reviewers.filter((name) => name !== "g1t")}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1452 placeholder="Other usernames or teams (acme/backend)"
Pull requests: title and tabs across the full width, with counts and the size of the change1453 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1454 {teamChoices.length > 0 && (
1455 <div className="space-y-1.5 border-t border-line pt-2">
1456 <p className="text-xs text-faint">Teams</p>
1457 {teamChoices.map((team) => (
1458 <CheckboxOption
1459 key={team.ref}
1460 name="reviewer"
1461 value={team.ref}
1462 defaultChecked={teamReviewers.includes(team.ref)}
1463 className="items-center"
1464 labelClassName="flex min-w-0 items-center gap-2"
1465 label={
1466 <>
1467 <Users size={14} className="shrink-0 text-faint" />
1468 <span className="truncate font-mono text-xs">@{team.ref}</span>
1469 </>
1470 }
1471 />
1472 ))}
1473 </div>
1474 )}
Merge branch 'worktree-agent-a985c01f43538af01'1475 <SubmitButton variant="quiet" match={{ action: "reviewers" }} pending="Saving…">
Pull requests: title and tabs across the full width, with counts and the size of the change1476 Save reviewers
Merge branch 'worktree-agent-a985c01f43538af01'1477 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1478 </Form>
1479 </details>
1480 </div>
Project dependencies: addresses, preview stacks, Affects, and agents who know1481 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1482 {["agent-review", "reviewers"].includes(String(actionData?.action)) && (
1483 <ErrorText>{actionData?.error}</ErrorText>
1484 )}
Project dependencies: addresses, preview stacks, Affects, and agents who know1485 </section>
Acceptance checks in sandboxes, line comments and review verdicts1486
Pull requests: title and tabs across the full width, with counts and the size of the change1487 <section>
1488 <h3 className="text-sm font-medium">Assignees</h3>
1489 <ul className="mt-2 space-y-1.5 text-sm">
1490 {pull.assignees.map((name) => (
Agents as a team: lifecycle, merge queue, billing and a new shell1491 <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 change1492 <Avatar name={name} size={20} />
Agents as a team: lifecycle, merge queue, billing and a new shell1493 <span className="grow truncate font-mono text-xs">{name}</span>
1494 </li>
Pull requests: title and tabs across the full width, with counts and the size of the change1495 ))}
1496 {pull.assignees.length === 0 && (
1497 <li className="px-1 text-xs text-faint">No one yet.</li>
1498 )}
1499 </ul>
1500 {viewer && canManage && active && (
1501 <div className="mt-3 space-y-2">
1502 {!pull.assignees.includes(viewer.username) && (
1503 <Form method="post">
1504 <input type="hidden" name="action" value="assign" />
1505 {pull.assignees.map((name) => (
1506 <input key={name} type="hidden" name="assignee" value={name} />
1507 ))}
1508 <input type="hidden" name="assignee" value={viewer.username} />
1509 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'1510 <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 change1511 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'1512 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1513 </div>
1514 </Form>
1515 )}
1516 <details>
1517 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
1518 Assign people
1519 </summary>
1520 <Form method="post" className="mt-2 space-y-2" key={pull.assignees.join()}>
1521 <input type="hidden" name="action" value="assign" />
1522 <PeoplePicker name="assignee" members={members} chosen={pull.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'1523 <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 change1524 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'1525 </SubmitButton>
Pull requests: title and tabs across the full width, with counts and the size of the change1526 </Form>
1527 </details>
1528 </div>
Issues and pull requests replace intents and attempts1529 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1530 {actionData?.action === "assign" && <ErrorText>{actionData.error}</ErrorText>}
1531 </section>
Issues and pull requests replace intents and attempts1532
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1533 <LabelsBox
1534 labels={loaderData.labels}
1535 chosen={pull.labels ?? []}
1536 canEdit={canManage && active}
1537 canCreate={loaderData.canTriage}
1538 manageUrl={loaderData.canTriage ? `${base}/labels` : undefined}
1539 />
1540 <MilestoneBox
1541 milestones={loaderData.milestones}
1542 current={pull.milestone}
1543 canEdit={loaderData.canTriage}
1544 base={base}
1545 />
1546
Pull requests: title and tabs across the full width, with counts and the size of the change1547 <section>
1548 <h3 className="text-sm font-medium">Working copy</h3>
1549 {pull.branch ? (
1550 <>
1551 <p className="mt-1 text-xs text-muted">
1552 <GitBranch size={13} className="mr-1 inline align-[-2px]" />
1553 Branch <span className="font-mono text-fg">{pull.branch}</span> of this
1554 repository. Pushes to it show up here.
1555 </p>
1556 <div className="mt-2">
1557 <CopyLine text={`git clone -b ${pull.branch} ${remote}`} />
1558 </div>
1559 </>
1560 ) : (
1561 <>
1562 <p className="mt-1 text-xs text-muted">
1563 This fork belongs to the pull request. Pushes to it show up here.
1564 </p>
1565 <div className="mt-2">
1566 <CopyLine text={`git clone ${remote}`} />
1567 </div>
1568 </>
1569 )}
1570 <p className="mt-3 flex items-center gap-1.5 font-mono text-xs text-faint">
1571 <GitCommitHorizontal size={13} />
1572 {pull.headCommit?.slice(0, 12) ?? "no commits pushed yet"}
1573 </p>
1574 </section>
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings1575 {loaderData.viewer && (
1576 <SubscriptionBox action={`${base}/notifications`} number={pull.number} kind="pull" subscription={loaderData.subscription} />
1577 )}
Pull requests: title and tabs across the full width, with counts and the size of the change1578 </aside>
1579 </div>
Issues and pull requests replace intents and attempts1580 </div>
1581 );
1582}
Pull requests say where they are live; the project menu shows what is coming1583
1584/** Where the pull request's change is live, as GitHub shows a deployment. */
1585function DeploymentCard({
1586 preview,
1587 build,
1588 stacked,
Merge branch 'worktree-agent-aca4790ea223aa235'1589 stacking,
Pull requests say where they are live; the project menu shows what is coming1590 base,
1591}: {
1592 preview: LiveApp | null;
1593 build: Deployment | null;
Fast pages, required checks on the branch, self-hosted runners, honest incidents1594 stacked: Promise<Stacked[]>;
Merge branch 'worktree-agent-aca4790ea223aa235'1595 /** Projects use this one, so previews built against it may be on their way. */
1596 stacking: boolean;
Pull requests say where they are live; the project menu shows what is coming1597 base: string;
1598}) {
1599 const building = build?.status === "queued" || build?.status === "building";
1600 const failed = build?.status === "failed";
1601 return (
1602 <section className="ml-12 overflow-hidden rounded-xl border border-line bg-surface">
1603 <div className="flex flex-wrap items-center gap-3 px-4 py-3">
1604 <span
1605 className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ${
1606 failed ? "text-danger ring-danger/40" : building ? "text-warn ring-warn/40" : "text-accent ring-accent/40"
1607 }`}
1608 >
1609 {building ? <Loader size={15} className="animate-spin" /> : <Rocket size={15} />}
1610 </span>
1611 <div className="min-w-0 grow">
1612 <p className="text-sm font-medium">
1613 {building
1614 ? preview
1615 ? "Deploying the latest push. The last preview is still live."
1616 : "Deploying a preview"
1617 : failed
1618 ? "The preview failed to deploy"
1619 : "This branch is live"}
1620 </p>
1621 {preview ? (
1622 <a href={preview.url} className="mt-0.5 block truncate font-mono text-xs text-muted hover:text-accent">
1623 {preview.url.replace(/^https?:[/][/]/, "")}
1624 </a>
1625 ) : (
1626 build?.error && <p className="mt-0.5 truncate text-xs text-muted">{build.error}</p>
1627 )}
1628 </div>
1629 <div className="flex shrink-0 items-center gap-2">
1630 {build && (
1631 <Link to={`${base}/deployments/${build.id}`} className="text-xs text-muted hover:text-fg">
1632 {failed ? "See why" : "Build log"}
1633 </Link>
1634 )}
1635 {preview && (
1636 <a
1637 href={preview.url}
1638 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"
1639 >
1640 Visit preview
1641 <ArrowUpRight size={13} />
1642 </a>
1643 )}
1644 </div>
1645 </div>
1646 {preview && (
1647 <p className="border-t border-line px-4 py-2 text-xs text-faint">
1648 <span className="font-mono">{preview.commit.slice(0, 7)}</span> · deployed <TimeAgo at={preview.deployedAt} />
1649 {preview.branch && ` · from ${preview.branch}`}
1650 </p>
1651 )}
Fast pages, required checks on the branch, self-hosted runners, honest incidents1652 {/* Read after the page: the card's own part shows at once. */}
Merge branch 'worktree-agent-aca4790ea223aa235'1653 <Suspense
1654 fallback={
1655 stacking && (
1656 <Loading className="border-t border-line px-4 py-2.5 text-xs">
1657 <SkeletonLine className="w-40" />
1658 <SkeletonLine className="mt-1.5 w-64 max-w-full" />
1659 </Loading>
1660 )
1661 }
1662 >
Fast pages, required checks on the branch, self-hosted runners, honest incidents1663 <Await resolve={stacked}>
1664 {(stacked) =>
1665 stacked.length > 0 && (
Merge branch 'worktree-agent-aca4790ea223aa235'1666 <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 incidents1667 <p className="text-xs text-muted">Built against this change:</p>
1668 <ul className="mt-1.5 space-y-1">
1669 {stacked.map((entry) => (
1670 <li key={entry.slug} className="flex items-center gap-2 text-xs">
1671 <span className="font-medium">{entry.name}</span>
1672 <a href={entry.url} className="truncate font-mono text-muted hover:text-accent">
1673 {entry.url.replace(/^https?:[/][/]/, "")}
1674 </a>
1675 </li>
1676 ))}
1677 </ul>
1678 </div>
1679 )
1680 }
1681 </Await>
1682 </Suspense>
Pull requests say where they are live; the project menu shows what is coming1683 </section>
1684 );
1685}

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