Skip to content
694 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.

Issues and pull requests replace intents and attempts1import { env } from "cloudflare:workers";
Fast pages, required checks on the branch, self-hosted runners, honest incidents2import { Bot, ExternalLink, GitCommitHorizontal, GitMerge, Play, Sparkles } from "lucide-react";
Merge branch 'worktree-agent-a985c01f43538af01'3import { Form, Link, redirect } from "react-router";
Issues and pull requests replace intents and attempts4
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights5import { type Pull, PROVIDERS, workOwner } from "@g1t/contracts";
Issues and pull requests replace intents and attempts6
7import type { Route } from "./+types/issue";
Social cards for every page: og.g1t.sh8import { excerpt, page } from "../../lib/meta";
Issues and pull requests replace intents and attempts9import { Markdown } from "../../components/markdown";
Agents and memory, checks and conflicts, profiles, slug renames, custom domains10import { AgentStepLine } from "../../components/agents";
Issues and pull requests replace intents and attempts11import {
12 Avatar,
13 CopyLine,
14 EmptyState,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look15 ComputeNote,
Issues and pull requests replace intents and attempts16 ErrorText,
17 Input,
Merge branch 'worktree-agent-a985c01f43538af01'18 SubmitButton,
Issues and pull requests replace intents and attempts19 Textarea,
20 TimeAgo,
21} from "../../components/ui";
Acceptance checks in sandboxes, line comments and review verdicts22import { CheckBadge } from "../../components/checks";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar23import { LabelChip, LabelsBox, MilestoneBox } from "../../components/labels";
Agents as a team: lifecycle, merge queue, billing and a new shell24import {
25 Assignee,
26 AssigneeStack,
27 ChangeSize,
28 CommentForm,
29 CommentList,
30 PeoplePicker,
31 IssueState,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains32 PersonLink,
Agents as a team: lifecycle, merge queue, billing and a new shell33 PullIcon,
34 plainText,
35} from "../../components/work";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put36import { notFound } from "../../lib/not-found.server";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent37import { openedBy } from "../../lib/opened-by";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look38import { computeNoteFor } from "../../lib/compute.server";
Merge branch 'worktree-agent-aca4790ea223aa235'39import { agents, identity, inbox, integrations, work } from "../../lib/services.server";
Agents as a team: lifecycle, merge queue, billing and a new shell40import { assertSameOrigin, getViewer, requireUser, roleIn } from "../../lib/session.server";
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings41import { accessTo, refusal, repoFor } from "../../lib/access.server";
42import { SubscriptionBox } from "../../components/notifications";
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending43import { useRefreshWhile } from "../../lib/refresh";
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)44import { DetailsDisclosure, detailsSummary } from "../../components/details-disclosure";
Issues and pull requests replace intents and attempts45
46
Social cards for every page: og.g1t.sh47export function meta({ loaderData, params, ...args }: Route.MetaArgs) {
48 const issue = loaderData?.issue;
49 const title = issue ? `${issue.title} · Issue #${issue.number} · ` : "";
50 const state = issue?.state === "open" ? "Open" : issue?.reason === "not_planned" ? "Closed as not planned" : "Closed";
51 const body = excerpt(issue?.body);
52 return page(args, {
53 title: `${title}${params.owner}/${params.repo} · g1t`,
54 description: issue
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights55 ? `${state} issue #${issue.number} on ${params.owner}/${params.repo}, opened by ${issue.author.username}${issue.requestedBy ? ` for ${issue.requestedBy.username}` : ""}.${body ? ` ${body}` : ""}`
Social cards for every page: og.g1t.sh56 : null,
57 // The card shows the title and the state.
58 version: issue ? [issue.title, issue.state, issue.reason] : undefined,
59 type: "article",
60 });
Issues and pull requests replace intents and attempts61}
62
63export async function loader({ params, context }: Route.LoaderArgs) {
64 const viewer = getViewer(context);
65 const path = { namespace: params.owner, name: params.repo };
66 const number = Number(params.number);
Fast pages, required checks on the branch, self-hosted runners, honest incidents67 // At once: only the plan's note waits for the viewer's role. Putting an
68 // agent on it needs Write: Read cannot spend compute.
69 const access = accessTo(context, params);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar70 const [{ can }, found, labels, milestones, agentsEnabled, members, links, computeNote, subscription, active, teams] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents71 access,
Agents as a team: lifecycle, merge queue, billing and a new shell72 work.getIssue(path, number, viewer),
73 work.listLabels(path, viewer),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar74 // Open ones to choose from, and the one it is in, with its progress.
75 work.listMilestones(path, viewer),
g1t's agents only for listed workspaces, whatever the state of billing76 env.RUNNER.enabled(viewer, path),
Agents as a team: lifecycle, merge queue, billing and a new shell77 // A member picks assignees from the workspace's people.
78 roleIn(viewer, params.owner) ? identity.listMembers(params.owner, viewer) : null,
Integrations: your own model provider, alerts that open issues, tickets agents read79 // What it is tied to outside g1t. Shown only once the issue is known visible.
80 integrations.links(path, number).catch(() => []),
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent81 // Before a member assigns g1t: whether the workspace's plan lets it start.
Fast pages, required checks on the branch, self-hosted runners, honest incidents82 access.then(({ can }) => (can.run ? computeNoteFor(params.owner, "agent") : null)),
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings83 // Whether the viewer hears of it, for the sidebar's Notifications.
84 viewer
85 ? repoFor(context, params).then((repo) =>
86 repo.ok ? inbox.subscription(viewer, { repoId: repo.value.id, number }).catch(() => null) : null,
87 )
88 : null,
Merge branch 'worktree-agent-aca4790ea223aa235'89 // The project's agents at work, for the line under the pull request
90 // one is on for this issue: with the page, not after it.
91 agents.listRuns(viewer, { repo: path, active: true, limit: 50 }).catch(() => null),
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar92 // The teams a member can mention, for the comment box.
93 roleIn(viewer, params.owner) ? identity.listTeams(viewer, params.owner).catch(() => null) : null,
Agents as a team: lifecycle, merge queue, billing and a new shell94 ]);
Issues and pull requests replace intents and attempts95 if (!found.ok) {
96 // Issues and pull requests share numbers; this one may be a pull request.
97 const pull = await work.getPull(path, number, viewer);
98 if (pull.ok) throw redirect(`/${params.owner}/${params.repo}/pull/${number}`);
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put99 throw notFound("issue");
Issues and pull requests replace intents and attempts100 }
101 const { issue } = found.value;
102 return {
103 ...found.value,
104 viewer,
105 labels: labels.ok ? labels.value : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar106 milestones: milestones.ok ? milestones.value : [],
Agents as a team: lifecycle, merge queue, billing and a new shell107 agentsEnabled,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look108 computeNote,
Integrations: your own model provider, alerts that open issues, tickets agents read109 links,
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings110 subscription,
Merge branch 'worktree-agent-aca4790ea223aa235'111 // As the project's agents.json has them; left out, the line fetches them.
112 activeRuns: active?.ok ? { runs: active.value, member: can.run } : undefined,
Agents as a team: lifecycle, merge queue, billing and a new shell113 members: members?.ok ? members.value.map((member) => member.username) : [],
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar114 handles: teams?.ok ? teams.value.map((team) => `@${team.workspace}/${team.slug}`) : [],
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights115 // The author can close and reopen their own issue, and whoever g1t's
116 // agent filed one for, that one; Triage and up, anyone's.
117 canManage: viewer != null && (viewer.id === workOwner(issue).id || can.triage),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts118 // Maintain and up edit and delete anyone's comments; everyone, their own.
119 canModerate: can.manage_settings,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look120 can,
Issues and pull requests replace intents and attempts121 };
122}
123
124export async function action({ request, params, context }: Route.ActionArgs) {
125 assertSameOrigin(request);
126 const user = requireUser(context, request);
127 const form = await request.formData();
128 const path = { namespace: params.owner, name: params.repo };
129 const number = Number(params.number);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look130 // What each form needs; closing your own issue is checked by work.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar131 // Labels: its author may use the repository's on their own issue, and
132 // work says so; anyone else, and every milestone, needs Triage.
133 const needed =
134 form.get("action") === "run-hosted"
135 ? "run"
136 : form.get("action") === "assign" || form.get("action") === "milestone"
137 ? "triage"
138 : null;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look139 const refused = needed ? await refusal(context, params, needed) : null;
140 if (refused) return { error: refused, action: String(form.get("action")) };
Issues and pull requests replace intents and attempts141
142 switch (form.get("action")) {
143 case "run-hosted": {
144 const result = await env.RUNNER.run(user, path, number, {
145 instructions: String(form.get("instructions") ?? ""),
146 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look147 return result.ok ? null : { error: result.error.message, action: "run-hosted" };
Issues and pull requests replace intents and attempts148 }
149 case "open-pull": {
150 const result = await work.openPull(user, path, {
151 issue: number,
152 agent: String(form.get("agent") ?? ""),
153 runtime: "external",
154 });
155 if (!result.ok) return { error: result.error.message };
156 throw redirect(`/${params.owner}/${params.repo}/pull/${result.value.number}`);
157 }
158 case "comment": {
Acceptance checks in sandboxes, line comments and review verdicts159 const result = await work.addComment(user, path, number, {
160 body: String(form.get("body") ?? ""),
161 });
Issues and pull requests replace intents and attempts162 return result.ok ? null : { error: result.error.message };
163 }
Agents as a team: lifecycle, merge queue, billing and a new shell164 case "assign": {
165 const result = await work.updateIssue(user, path, number, {
166 assignees: [
167 ...form.getAll("assignee").map(String),
168 ...String(form.get("others") ?? "").split(/[\s,]+/),
169 ],
170 });
171 return result.ok ? null : { error: result.error.message };
172 }
Issues and pull requests replace intents and attempts173 case "labels": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar174 const result = await work.setLabels(user, path, number, form.getAll("label").map(String));
175 return result.ok ? null : { error: result.error.message, action: "labels" };
Issues and pull requests replace intents and attempts176 }
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar177 case "milestone": {
178 const result = await work.updateIssue(user, path, number, { milestone: Number(form.get("milestone")) || 0 });
179 return result.ok ? null : { error: result.error.message, action: "milestone" };
180 }
Issues and pull requests replace intents and attempts181 case "reopen": {
182 const result = await work.reopenIssue(user, path, number);
183 return result.ok ? null : { error: result.error.message };
184 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts185 // A comment's own text, changed or taken away: its author's to do, or
186 // a maintainer's, which work checks.
187 case "edit-comment":
188 case "delete-comment": {
189 const action = String(form.get("action"));
190 const id = String(form.get("comment") ?? "");
191 const result =
192 action === "edit-comment"
193 ? await work.editComment(user, path, id, String(form.get("body") ?? ""))
194 : await work.deleteComment(user, path, id);
195 return result.ok ? null : { error: result.error.message, action, comment: id };
196 }
Issues and pull requests replace intents and attempts197 default: {
198 const result = await work.closeIssue(
199 user,
200 path,
201 number,
202 form.get("action") === "close-not-planned" ? "not_planned" : "completed",
203 );
204 return result.ok ? null : { error: result.error.message };
205 }
206 }
207}
208
209/** What became of a pull request, in a few words. */
210function outcome(pull: Pull): string {
211 if (pull.status === "merged") return `Merged by ${pull.mergedBy ?? "someone"}`;
212 if (pull.supersededBy != null) return `Closed · #${pull.supersededBy} was merged instead`;
213 if (pull.status === "closed") return "Closed without merging";
214 if (pull.status === "draft") return "Draft · in progress";
215 return "Ready for review";
216}
217
218function PullRow({ pull, base }: { pull: Pull; base: string }) {
219 const merged = pull.status === "merged";
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent220 const opener = openedBy(pull);
Issues and pull requests replace intents and attempts221 return (
222 <li>
223 <Link
224 to={`${base}/pull/${pull.number}`}
225 className={`block rounded-xl border p-4 transition-colors ${
226 merged
227 ? "border-merged/40 bg-merged/5 hover:border-merged/70"
228 : "border-line bg-surface hover:border-line-strong"
229 } ${pull.status === "closed" ? "opacity-70" : ""}`}
230 >
231 <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
232 <PullIcon status={pull.status} />
233 <span className="font-medium">
234 {pull.title} <span className="font-normal text-faint">#{pull.number}</span>
235 </span>
236 {pull.status === "draft" && (
237 <span className="size-1.5 animate-pulse rounded-full bg-accent" />
238 )}
239 <span className="ml-auto flex items-center gap-3 text-xs text-faint">
Agents as a team: lifecycle, merge queue, billing and a new shell240 <ChangeSize files={pull.files} />
Acceptance checks in sandboxes, line comments and review verdicts241 <CheckBadge status={pull.checkStatus} />
Issues and pull requests replace intents and attempts242 <span className="flex items-center gap-1 font-mono">
243 <GitCommitHorizontal size={13} />
244 {pull.headCommit?.slice(0, 7) ?? "no commits"}
245 </span>
246 <TimeAgo at={pull.updatedAt} />
247 </span>
248 </div>
249 <p className="mt-1.5 flex flex-wrap items-center gap-x-2 text-xs text-muted">
250 <span className={merged ? "font-medium text-merged" : ""}>{outcome(pull)}</span>
251 <span className="text-faint">·</span>
252 <span className="flex items-center gap-1 font-mono">
253 <Bot size={12} />
254 {pull.agent}
255 </span>
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent256 {opener.requestedBy ? (
257 <span className="text-faint">· requested by {opener.requestedBy}</span>
258 ) : (
259 <>
260 {pull.runtime === "hosted" && <span className="text-faint">on g1t</span>}
261 <span className="text-faint">· opened by {opener.name}</span>
262 </>
263 )}
Issues and pull requests replace intents and attempts264 </p>
Agents as a team: lifecycle, merge queue, billing and a new shell265 {pull.body && (
266 <p className="mt-2 line-clamp-2 text-sm text-muted">{plainText(pull.body)}</p>
267 )}
Issues and pull requests replace intents and attempts268 </Link>
269 </li>
270 );
271}
272
273export default function IssuePage({ loaderData, actionData, params }: Route.ComponentProps) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look274 const { issue, pulls, comments, viewer, labels, agentsEnabled, members, canManage, can } = loaderData;
Issues and pull requests replace intents and attempts275
276 // Follow agents at work without a manual reload.
Acceptance checks in sandboxes, line comments and review verdicts277 const running = pulls.some(
278 (pull) =>
279 pull.status === "draft" ||
280 pull.checkStatus === "queued" ||
281 pull.checkStatus === "running",
282 );
Agents as a team: lifecycle, merge queue, billing and a new shell283 // The pull request g1t's agent has in progress for this issue, if any.
284 const assigned = [...pulls]
285 .reverse()
286 .find(
287 (pull) =>
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent288 pull.agent === "g1t" &&
Agents as a team: lifecycle, merge queue, billing and a new shell289 pull.runtime === "hosted" &&
290 (pull.status === "draft" || pull.status === "open"),
291 );
A click away from a live page arrives: pages stop refreshing themselves while a navigation is pending292 useRefreshWhile(Boolean(running || assigned));
Issues and pull requests replace intents and attempts293
Merge branch 'worktree-agent-a985c01f43538af01'294 // A refusal to start g1t shows by its button; every other error below the discussion.
295 const runError = actionData && "action" in actionData && actionData.action === "run-hosted";
Issues and pull requests replace intents and attempts296 const base = `/${params.owner}/${params.repo}`;
297 const open = issue.state === "open";
298 const reference = `${params.owner}/${params.repo}#${issue.number}`;
299 const resolver = pulls.find((pull) => pull.number === issue.resolvedBy);
300 // The merged pull request first, then the ones still in play, then the rest.
301 const rank = { merged: 0, open: 1, draft: 2, closed: 3 } as const;
302 const ordered = [...pulls].sort(
303 (a, b) => rank[a.status] - rank[b.status] || a.number - b.number,
304 );
305
306 return (
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)307 // On a phone: the title, then the sidebar as a Details disclosure, then
308 // the issue; beside it from lg up.
309 <div className="grid gap-x-8 gap-y-5 lg:grid-cols-[1fr_19rem] lg:grid-rows-[auto_1fr] lg:gap-y-0">
310 <header className="min-w-0 lg:col-start-1">
311 <h2 className="text-xl font-semibold tracking-tight text-balance wrap-break-word sm:text-2xl">
Issues and pull requests replace intents and attempts312 {issue.title} <span className="font-normal text-faint">#{issue.number}</span>
313 </h2>
314 <div className="mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 text-sm text-muted">
315 <IssueState issue={issue} />
316 <span className="flex items-center gap-2">
317 <Avatar name={issue.author.username} size={18} />
318 <span>
Agents and memory, checks and conflicts, profiles, slug renames, custom domains319 <PersonLink name={issue.author.username} className="font-medium text-fg hover:underline" /> opened this{" "}
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights320 {issue.requestedBy && (
321 <>
322 for{" "}
323 <PersonLink name={issue.requestedBy.username} className="font-medium text-fg-soft hover:underline" />{" "}
324 </>
325 )}
Issues and pull requests replace intents and attempts326 <TimeAgo at={issue.createdAt} />
327 </span>
328 </span>
329 {issue.labels.map((name) => (
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar330 <LabelChip key={name} name={name} color={labels.find((label) => label.name === name)?.color} />
Issues and pull requests replace intents and attempts331 ))}
Agents as a team: lifecycle, merge queue, billing and a new shell332 {open && issue.agent && <Assignee agent={issue.agent} />}
Issues and pull requests replace intents and attempts333 </div>
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)334 </header>
Issues and pull requests replace intents and attempts335
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)336 <div className="min-w-0 max-lg:order-1 lg:col-start-1">
337
Issues and pull requests replace intents and attempts338 {issue.resolvedBy != null && (
339 <Link
340 to={`${base}/pull/${issue.resolvedBy}`}
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)341 className="flex lg:mt-5 items-center gap-3 rounded-xl border border-merged/40 bg-merged/5 px-4 py-3 text-sm transition-colors hover:border-merged/70"
Issues and pull requests replace intents and attempts342 >
343 <GitMerge size={18} className="shrink-0 text-merged" />
344 <span>
345 Resolved by{" "}
346 <span className="font-medium">
347 {resolver?.title ?? "pull request"} #{issue.resolvedBy}
348 </span>
349 {resolver?.mergedBy && (
350 <span className="text-muted">
351 , merged by {resolver.mergedBy}{" "}
352 {resolver.mergedAt && <TimeAgo at={resolver.mergedAt} />}
353 </span>
354 )}
355 </span>
356 </Link>
357 )}
358
359 {issue.body && (
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)360 <div className={`${issue.resolvedBy != null ? "mt-5" : "lg:mt-5"} rounded-xl border border-line bg-surface p-4 sm:p-5`}>
Agents as a team: lifecycle, merge queue, billing and a new shell361 <Markdown source={issue.body} repo={{ namespace: params.owner, name: params.repo }} />
Issues and pull requests replace intents and attempts362 </div>
363 )}
364
365 <div className="mt-10 flex items-baseline justify-between">
366 <h3 className="font-semibold tracking-tight">Pull requests</h3>
367 <p className="text-sm text-muted">
368 {pulls.length === 0
369 ? "None yet"
370 : `${pulls.length} for this issue${
371 pulls.some((pull) => pull.status === "merged") ? "" : ", none merged"
372 }`}
373 </p>
374 </div>
375 <div className="mt-3">
376 {pulls.length === 0 ? (
377 <EmptyState title="Nobody has worked on this yet">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent378 Put g1t on it, or point your own agent at{" "}
Issues and pull requests replace intents and attempts379 <code className="font-mono">{reference}</code>.
380 </EmptyState>
381 ) : (
382 <ol className="space-y-3">
383 {ordered.map((pull) => (
384 <PullRow key={pull.id} pull={pull} base={base} />
385 ))}
386 </ol>
387 )}
388 </div>
389
390 <h3 className="mt-10 font-semibold tracking-tight">Discussion</h3>
391 <div className="mt-3">
Agents as a team: lifecycle, merge queue, billing and a new shell392 <div className="space-y-4">
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts393 <CommentList
394 comments={comments}
395 base={base}
396 viewerId={viewer?.id ?? null}
397 canModerate={loaderData.canModerate}
398 failed={actionData && "comment" in actionData ? { comment: String(actionData.comment), error: actionData.error } : null}
399 />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar400 <CommentForm author={viewer?.username ?? null} resetKey={comments.length} handles={loaderData.handles} />
Agents as a team: lifecycle, merge queue, billing and a new shell401 {canManage && (
402 <Form method="post" className="flex flex-wrap justify-end gap-2">
403 {open ? (
404 <>
Merge branch 'worktree-agent-a985c01f43538af01'405 <SubmitButton variant="quiet" name="action" value="close-not-planned" pending="Closing…">
Agents as a team: lifecycle, merge queue, billing and a new shell406 Close as not planned
Merge branch 'worktree-agent-a985c01f43538af01'407 </SubmitButton>
408 <SubmitButton variant="quiet" name="action" value="close-completed" pending="Closing…">
Agents as a team: lifecycle, merge queue, billing and a new shell409 Close issue
Merge branch 'worktree-agent-a985c01f43538af01'410 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell411 </>
412 ) : (
Merge branch 'worktree-agent-a985c01f43538af01'413 <SubmitButton variant="quiet" name="action" value="reopen" pending="Reopening…">
Agents as a team: lifecycle, merge queue, billing and a new shell414 Reopen issue
Merge branch 'worktree-agent-a985c01f43538af01'415 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell416 )}
417 </Form>
418 )}
419 </div>
Issues and pull requests replace intents and attempts420 </div>
421 <div className="mt-2">
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts422 {/* A refused comment edit says so under the comment. */}
423 {!runError && !(actionData && "comment" in actionData) && <ErrorText>{actionData?.error}</ErrorText>}
Issues and pull requests replace intents and attempts424 </div>
425 </div>
426
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)427 <DetailsDisclosure
428 className="lg:col-start-2 lg:row-span-2 lg:row-start-1"
429 summary={detailsSummary([
430 [issue.assignees.length, "assignee", "assignees"],
431 [issue.labels.length, "label", "labels"],
432 [issue.milestone ? 1 : 0, "milestone", "milestones"],
433 ])}
434 >
Integrations: your own model provider, alerts that open issues, tickets agents read435 {loaderData.links.length > 0 && (
436 <section>
437 <h3 className="text-sm font-medium">From outside g1t</h3>
438 <ul className="mt-2 space-y-1.5 text-sm">
439 {loaderData.links.map((link) => (
440 <li key={`${link.connectionId}-${link.key}`}>
441 <a
442 href={link.url}
443 target="_blank"
444 rel="noreferrer"
445 className="group flex items-center gap-2 rounded-lg border border-line px-2.5 py-2 transition-colors hover:border-line-strong"
446 >
447 <span className="min-w-0 grow">
448 <span className="block truncate font-medium">
449 {PROVIDERS[link.provider].label} <span className="font-mono text-muted">{link.key}</span>
450 </span>
451 <span className="block text-xs text-faint">
452 {link.count > 1 ? `Seen ${link.count} times, last ` : "Linked "}
453 <TimeAgo at={link.count > 1 ? link.lastSeen : link.firstSeen} />
454 </span>
455 </span>
456 <ExternalLink size={13} className="shrink-0 text-faint group-hover:text-fg" />
457 </a>
458 </li>
459 ))}
460 </ul>
461 </section>
462 )}
Agents as a team: lifecycle, merge queue, billing and a new shell463 <section>
464 <h3 className="text-sm font-medium">Assignees</h3>
465 <ul className="mt-2 space-y-1.5 text-sm">
466 {open && assigned && (
467 <li>
468 <Link
469 to={`${base}/pull/${assigned.number}`}
470 className="flex items-center gap-2 rounded-lg border border-accent/30 bg-accent/5 px-2.5 py-2 transition-colors hover:border-accent/60"
471 >
472 <Sparkles size={15} className="shrink-0 text-accent" />
473 <span className="min-w-0 grow">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent474 <span className="block font-mono text-xs font-medium">g1t</span>
Agents as a team: lifecycle, merge queue, billing and a new shell475 <span className="block truncate text-xs text-muted">
476 {assigned.status === "draft"
477 ? "Making the change"
478 : "Seeing it through checks and review"}{" "}
479 · #{assigned.number}
480 </span>
Merge branch 'worktree-agent-aca4790ea223aa235'481 <AgentStepLine owner={params.owner} repo={params.repo} number={assigned.number} runs={loaderData.activeRuns} />
Agents as a team: lifecycle, merge queue, billing and a new shell482 </span>
483 <span className="size-1.5 shrink-0 animate-pulse rounded-full bg-accent" />
484 </Link>
485 </li>
486 )}
487 {issue.assignees.map((name) => (
488 <li key={name} className="flex items-center gap-2 px-1">
489 <Avatar name={name} size={20} />
Merge commit authors and hovercards: commits show their accounts, every name opens a card (repos 0016)490 <span className="grow truncate font-mono text-xs">
491 <PersonLink name={name} className="hover:underline" />
492 </span>
Agents as a team: lifecycle, merge queue, billing and a new shell493 </li>
494 ))}
495 {open && issue.queued && !assigned && (
496 <li className="flex items-center gap-2 rounded-lg border border-line bg-surface px-2.5 py-2">
497 <Sparkles size={15} className="shrink-0 text-faint" />
498 <span className="min-w-0 grow">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent499 <span className="block font-mono text-xs font-medium">g1t</span>
Agents as a team: lifecycle, merge queue, billing and a new shell500 <span className="block text-xs text-muted">
501 {issue.blockedBy.length > 0
502 ? "Queued. Starts when what this depends on has merged."
503 : "Queued. Starts as soon as there is room."}
504 </span>
505 </span>
506 </li>
507 )}
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent508 {!open && resolver && resolver.agent === "g1t" && (
Agents as a team: lifecycle, merge queue, billing and a new shell509 <li>
510 <Link
511 to={`${base}/pull/${resolver.number}`}
512 className="flex items-center gap-2 rounded-lg border border-line bg-surface px-2.5 py-2 transition-colors hover:border-line-strong"
513 >
Merge branch 'worktree-agent-ad7c6d88d93adc817'514 <Sparkles size={15} className="shrink-0 text-accent" />
Agents as a team: lifecycle, merge queue, billing and a new shell515 <span className="min-w-0 grow">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent516 <span className="block font-mono text-xs font-medium">g1t</span>
Agents as a team: lifecycle, merge queue, billing and a new shell517 <span className="block truncate text-xs text-muted">
518 Resolved it with #{resolver.number}
519 </span>
520 </span>
521 </Link>
522 </li>
523 )}
524 {issue.assignees.length === 0 &&
525 !(open && (assigned || issue.queued)) &&
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent526 !(!open && resolver?.agent === "g1t") && (
Agents as a team: lifecycle, merge queue, billing and a new shell527 <li className="px-1 text-xs text-faint">No one yet.</li>
528 )}
529 </ul>
530 {issue.blockedBy.length > 0 && (
531 <p className="mt-3 text-xs text-muted">
532 Depends on{" "}
533 {issue.blockedBy.map((number, index) => (
534 <span key={number}>
535 {index > 0 && ", "}
536 <Link
537 to={`${base}/issues/${number}`}
538 className="font-medium text-fg hover:underline"
539 >
540 #{number}
541 </Link>
542 </span>
543 ))}
544 {open ? ", which has to merge first." : "."}
Issues and pull requests replace intents and attempts545 </p>
Agents as a team: lifecycle, merge queue, billing and a new shell546 )}
547
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look548 {open && agentsEnabled && can.run && !assigned && !issue.queued && (
Issues and pull requests replace intents and attempts549 <Form method="post" className="mt-3 space-y-2">
550 <input type="hidden" name="action" value="run-hosted" />
551 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'552 <SubmitButton variant="accent" match={{ action: "run-hosted" }} pending="Starting a sandbox…">
Agents as a team: lifecycle, merge queue, billing and a new shell553 <Sparkles size={14} />
Merge branch 'worktree-agent-a985c01f43538af01'554 Assign to g1t
555 </SubmitButton>
Issues and pull requests replace intents and attempts556 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell557 <details>
558 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
559 Add guidance for this run
560 </summary>
561 <div className="mt-2">
562 <Textarea
563 name="instructions"
564 rows={2}
565 placeholder="On top of the issue's description"
566 />
567 </div>
568 </details>
569 <p className="text-xs text-muted">
570 It opens a pull request and sees it through checks, a review by another
571 agent and fixes. You get it back ready to merge.
572 </p>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look573 <ComputeNote note={loaderData.computeNote} />
Merge branch 'worktree-agent-a985c01f43538af01'574 {runError && <ErrorText>{actionData.error}</ErrorText>}
Issues and pull requests replace intents and attempts575 </Form>
Agents as a team: lifecycle, merge queue, billing and a new shell576 )}
577
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look578 {open && !agentsEnabled && can.run && !assigned && !issue.queued && (
New workspaces are told to connect a model, before anything fails579 // Where g1t's agent would be, and what makes it appear.
580 <div className="mt-3 rounded-lg border border-dashed border-line p-3 text-sm">
581 <p className="flex items-center gap-1.5 font-medium">
582 <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-agent583 g1t
New workspaces are told to connect a model, before anything fails584 </p>
585 <p className="mt-1 text-xs text-muted">
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent586 Connect a model provider and g1t can take this issue: it opens a pull request and sees it
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look587 through checks, review and fixes. Agents run on a paid workspace, or on the free trial.
New workspaces are told to connect a model, before anything fails588 </p>
589 <Link
590 to={`/${params.owner}/-/integrations`}
591 className="mt-2 inline-block text-xs text-accent hover:underline"
592 >
593 Connect a model
594 </Link>
595 </div>
596 )}
597
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look598 {viewer && can.triage && open && (
Agents as a team: lifecycle, merge queue, billing and a new shell599 <div className="mt-3 space-y-2">
600 {!issue.assignees.includes(viewer.username) && (
601 <Form method="post">
602 <input type="hidden" name="action" value="assign" />
603 {issue.assignees.map((name) => (
604 <input key={name} type="hidden" name="assignee" value={name} />
605 ))}
606 <input type="hidden" name="assignee" value={viewer.username} />
607 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'608 <SubmitButton variant="quiet" name="who" value="self" pending="Assigning…">
Agents as a team: lifecycle, merge queue, billing and a new shell609 Assign yourself
Merge branch 'worktree-agent-a985c01f43538af01'610 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell611 </div>
612 </Form>
613 )}
614 <details className="group">
615 <summary className="cursor-pointer text-xs text-faint hover:text-fg">
616 Assign people
617 </summary>
618 <Form method="post" className="mt-2 space-y-2" key={issue.assignees.join()}>
619 <input type="hidden" name="action" value="assign" />
620 <PeoplePicker name="assignee" members={members} chosen={issue.assignees} />
Merge branch 'worktree-agent-a985c01f43538af01'621 <SubmitButton variant="quiet" name="who" value="picked" pending="Saving…">
Agents as a team: lifecycle, merge queue, billing and a new shell622 Save assignees
Merge branch 'worktree-agent-a985c01f43538af01'623 </SubmitButton>
Agents as a team: lifecycle, merge queue, billing and a new shell624 </Form>
625 </details>
626 </div>
627 )}
628 </section>
Issues and pull requests replace intents and attempts629
An issue being worked on no longer offers Bring your own agent beside the pull request doing it630 {/* Another way to start the work: not once a pull request is doing it. */}
631 {open && !pulls.some((pull) => pull.status === "open" || pull.status === "draft") && (
Issues and pull requests replace intents and attempts632 <section className="rounded-xl border border-line bg-surface p-4">
633 <h3 className="text-sm font-medium">Bring your own agent</h3>
634 <p className="mt-1 text-xs text-muted">
635 With g1t connected to your agent, ask it to work on this issue.
636 </p>
637 <div className="mt-3">
638 <CopyLine text={reference} />
639 </div>
640 {viewer ? (
641 <Form method="post" className="mt-4 space-y-2 border-t border-line pt-4">
642 <input type="hidden" name="action" value="open-pull" />
643 <p className="text-xs text-muted">
644 Or open a draft pull request yourself and get a fork to push to.
645 </p>
646 <Input name="agent" placeholder="Who is working, e.g. claude-code" maxLength={60} />
647 <div className="*:w-full">
Merge branch 'worktree-agent-a985c01f43538af01'648 <SubmitButton match={{ action: "open-pull" }} pending="Opening…">
649 Open pull request
650 </SubmitButton>
Issues and pull requests replace intents and attempts651 </div>
Pull requests from branches652 <p className="text-xs text-muted">
653 Already pushed a branch?{" "}
654 <Link
655 to={`${base}/pulls/new?issue=${issue.number}`}
656 className="text-fg underline underline-offset-4"
657 >
658 Open a pull request from it
659 </Link>
660 .
661 </p>
Issues and pull requests replace intents and attempts662 </Form>
663 ) : (
664 <p className="mt-4 border-t border-line pt-4 text-sm text-muted">
665 <Link to="/login" className="text-fg underline underline-offset-4">
666 Sign in
667 </Link>{" "}
668 to open a pull request.
669 </p>
670 )}
671 </section>
672 )}
673
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar674 <LabelsBox
675 labels={labels}
676 chosen={issue.labels}
677 canEdit={loaderData.canManage}
Merge main (membership, two-factor, GitHub repo roles) into tokens678 canCreate={can.manage_labels}
679 manageUrl={can.manage_labels ? `${base}/labels` : undefined}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar680 />
681 <MilestoneBox
682 milestones={loaderData.milestones}
683 current={issue.milestone}
684 canEdit={can.triage}
685 base={base}
686 />
Issues and pull requests replace intents and attempts687
Inbox on the site: reasons, a reason filter, Subscribe, Watch and settings688 {viewer && (
689 <SubscriptionBox action={`${base}/notifications`} number={issue.number} kind="issue" subscription={loaderData.subscription} />
690 )}
Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002)691 </DetailsDisclosure>
Issues and pull requests replace intents and attempts692 </div>
693 );
694}

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