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

New workspaces are told to connect a model, before anything fails1import { env } from "cloudflare:workers";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look2import { Suspense, lazy } from "react";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step3import { type ShouldRevalidateFunctionArgs, data, redirect } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell4
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API5import {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step6 type Confidence,
Mission control counts people's direct pushes to the default branch7 type G1tEvent,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API8 type Lifecycle,
9 type Pull,
10 type Repo,
11 type RepoPath,
Mission control counts people's direct pushes to the default branch12 type Viewer,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look13 REPO_ROLE_LABELS,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API14 isActiveRun,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights15 workOwner,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API16} from "@g1t/contracts";
Initial g1t: services, event bus, intents and attempts17
18import type { Route } from "./+types/home";
Social cards for every page: og.g1t.sh19import { page } from "../lib/meta";
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put20import { WORKSPACE_COOKIE, chosenWorkspace } from "../lib/workspace-choice";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API21import {
22 type ActivityItem,
23 type Need,
24 TIME,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit25 actorIds,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API26 agentHours,
27 dailyBuckets,
28 eventItem,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md29 FEED_EVENT_TYPES,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API30 greetingFor,
31 groupActivity,
32 hourIn,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms33 isAgent,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit34 nameActor,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API35 rankNeeds,
36 readCookie,
37 stuckMinutes,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit38 waitedFor,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API39} from "../lib/mission";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms40import {
41 type Fact,
42 type Merged,
Merge branch 'main' into actions-toolkit-oidc-artifacts43 placePushes,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms44 type NeedRow,
45 type QuickAction,
46 RUN_LABEL,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step47 confidenceAsk,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms48 dateLine,
49 dayKey,
50 landedToday,
51 needPathKey,
52 pullFacts,
53 reachesBack,
54 reasonFor,
55 summaryLine,
56 usd,
57 waitingRows,
58 weekOf,
59 who,
60 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step61 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms62} from "../lib/mission-control";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step63import { type NotStarted, chosenRepo, delegateForm, issuePath, notStarted } from "../lib/delegate";
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all64import { needsYou } from "../lib/inbox";
API and MCP server, Rust identity service, registration, site redesign65import { Landing } from "../components/landing";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit66import { Skeleton, SkeletonCard, SkeletonLine, SkeletonStat } from "../components/ui/skeleton";
API and MCP server, Rust identity service, registration, site redesign67import {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API68 agents,
69 billing,
70 deployments,
71 events as eventLog,
72 identity,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all73 inbox,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API74 projects as projectsApi,
75 repos as reposApi,
76 work,
77} from "../lib/services.server";
Runners are owner-only: the page, its actions, the Settings entry and the links to it78import { assertSameOrigin, getViewer, requireUser, roleIn } from "../lib/session.server";
Fast pages, required checks on the branch, self-hosted runners, honest incidents79import { runners } from "../lib/runners.server";
80import { readableRepos } from "../lib/access.server";
Initial g1t: services, event bus, intents and attempts81
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look82/** The viewer's time zone, which mission control sets, so the greeting fits their day. */
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API83const TZ_COOKIE = "g1t_tz";
84/** The most projects whose pull requests and events are read for the page. */
85const MAX_PROJECTS = 10;
86const EVENTS_PER_PROJECT = 80;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms87/** How many pull requests work lists at once (`LIST_PAGE` in services/work). */
88const PULL_PAGE = 100;
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all89/** Unread inbox items read for the Needs you card, and how many it shows. */
90const INBOX_READ = 50;
91const INBOX_SHOWN = 4;
A free allowance on g1t's models, so anyone can try its agents92
Social cards for every page: og.g1t.sh93export function meta(args: Route.MetaArgs) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit94 // Someone signed in is on mission control; visitors get the landing page's title.
95 if (args.loaderData?.signedIn) return page(args, { title: "Mission control · g1t" });
Social cards for every page: og.g1t.sh96 return page(args, {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look97 title: "g1t — where people and agents ship software together",
Social cards for every page: og.g1t.sh98 description:
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look99 "The open-source git platform for people and agents: issues, pull requests and review, agents you assign like teammates, a merge queue that keeps main green, and deployments to the edge. Priced at cost plus 20%, never per seat.",
Social cards for every page: og.g1t.sh100 });
Initial g1t: services, event bus, intents and attempts101}
102
Agents as a team: lifecycle, merge queue, billing and a new shell103export function headers({ loaderHeaders }: Route.HeadersArgs) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API104 const out = new Headers();
105 const timing = loaderHeaders.get("Server-Timing");
106 if (timing) out.set("Server-Timing", timing);
107 const cookie = loaderHeaders.get("Set-Cookie");
108 if (cookie) out.set("Set-Cookie", cookie);
109 return out;
Agents as a team: lifecycle, merge queue, billing and a new shell110}
111
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms112/**
113 * Switching tabs, the sort or the activity list changes only the address:
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all114 * everything is already loaded. Marking an inbox item read or done changes
115 * nothing else here: the card steps out by itself, and the bell's count
116 * comes with the shell (root.tsx). A refresh, a form, or anything else
117 * loads again as usual.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms118 */
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all119export function shouldRevalidate({ currentUrl, nextUrl, formMethod, formAction, defaultShouldRevalidate }: ShouldRevalidateFunctionArgs) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms120 if (!formMethod && currentUrl.pathname === nextUrl.pathname && currentUrl.search !== nextUrl.search) return false;
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all121 if (formAction === "/inbox") return false;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms122 return defaultShouldRevalidate;
123}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API124
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms125/** Where a need came from, so its row can say what is known about it. */
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step126type Extra = Partial<Pick<NeedRow, "repo" | "ref" | "by" | "for" | "facts" | "quick" | "link" | "open">> & {
127 /** How sure g1t is of the agent's change, for a pull request. */
128 confidence?: Confidence | null;
129};
130
131/** What the composer said back, when the issue opened but the agent did not start, or nothing opened. */
132export type DelegateResult = { error: string; notStarted: null } | { error: null; notStarted: NotStarted };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API133
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step134/**
135 * "Put an agent on it": opens an issue in one of the viewer's projects and
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent136 * puts g1t on it, then lands on the issue. When the agent could not
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step137 * start, the issue is still open, and the composer says why and where to
138 * fix it.
139 */
140export async function action({ request, context }: Route.ActionArgs) {
141 assertSameOrigin(request);
142 const user = requireUser(context, request);
143 const form = await request.formData();
144 if (form.get("intent") !== "delegate") return data<DelegateResult>({ error: "Nothing to do.", notStarted: null }, { status: 400 });
145 const projects = await reposApi.list(user, { memberOnly: true }).catch(() => []);
146 const repo = chosenRepo(form.get("repo"), projects.map((repo) => ({ namespace: repo.namespace, name: repo.name })));
147 if (!repo) return data<DelegateResult>({ error: "Choose one of your projects.", notStarted: null }, { status: 400 });
148 const result = await env.RUNNER.delegate(user, repo, delegateForm(form));
149 if (!result.ok) return data<DelegateResult>({ error: result.error.message, notStarted: null }, { status: 400 });
150 const { issue, agent } = result.value;
151 const refused = notStarted(agent, repo, issue.number);
152 if (!refused) throw redirect(issuePath(repo, issue.number));
153 return data<DelegateResult>({ error: null, notStarted: refused });
154}
155
Merge branch 'main' into actions-toolkit-oidc-artifacts156/**
157 * Push events read per project, a page at a time, back two weeks. Pushes
158 * to every branch are in the log, so a fixed count (it was 40) lost a busy
159 * week's earlier days to agents' branches.
160 */
161const PUSH_PAGE = 200;
162const PUSH_PAGES = 5;
163/** Commits of the default branch read once, to place each push's commits. */
164const HISTORY_READ = 1000;
Mission control counts people's direct pushes to the default branch165
166/**
167 * The commits people pushed straight to a project's default branch in the
168 * last two weeks, each with when its push landed. Merges and agents'
169 * commits are left out: pull requests count those.
170 */
171async function directCommits(repo: Repo, viewer: Viewer): Promise<{ hash: string; at: string }[]> {
172 const since = Date.now() - 14 * TIME.DAY;
Merge branch 'main' into actions-toolkit-oidc-artifacts173 const pushes: G1tEvent<"git.push">[] = [];
174 let before: string | undefined;
175 for (let page = 0; page < PUSH_PAGES; page++) {
176 const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit: PUSH_PAGE, ...(before ? { before } : {}) });
177 for (const event of batch) {
178 if (event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since) pushes.push(event as G1tEvent<"git.push">);
179 }
180 const oldest = batch.at(-1);
181 if (batch.length < PUSH_PAGE || !oldest || Date.parse(oldest.time) < since) break;
182 before = oldest.id;
183 }
184 if (pushes.length === 0) return [];
185 // One read of the branch from the newest push back; each push brought
186 // what lies between its `after` and its `before` in that history.
Mission control counts people's direct pushes to the default branch187 const path = { namespace: repo.namespace, name: repo.name };
Merge branch 'main' into actions-toolkit-oidc-artifacts188 const history = await reposApi.log(path, viewer, pushes[0].data.after, HISTORY_READ).catch(() => null);
189 if (!history?.ok) return [];
190 return placePushes(history.value, pushes);
Mission control counts people's direct pushes to the default branch191}
192
Merge branch 'main' into actions-toolkit-oidc-artifacts193
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API194export async function loader({ context, request }: Route.LoaderArgs) {
Initial g1t: services, event bus, intents and attempts195 const viewer = getViewer(context);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API196 if (!viewer) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look197 // The signed-out home page is about signing up; it lists no repositories.
198 return data({ signedIn: false as const });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API199 }
200
201 const now = Date.now();
202 const weekAgo = now - 7 * TIME.DAY;
Agents as a team: lifecycle, merge queue, billing and a new shell203 const started = Date.now();
204 const times: Record<string, number> = {};
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API205 // Each call is timed, and one that fails leaves its section empty rather
206 // than taking the page down.
207 const soft = <T,>(name: string, promise: Promise<T>): Promise<T | null> =>
208 promise.then(
209 (value) => ((times[name] = Date.now() - started), value),
210 (error) => {
211 console.warn(`home: ${name} failed`, error);
212 times[name] = Date.now() - started;
213 return null;
214 },
215 );
216 const okOr = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null);
217
218 const cookies = request.headers.get("cookie");
219 const tz = readCookie(cookies, TZ_COOKIE);
220 const memberships = viewer.workspaces ?? [];
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put221 // The workspace you chose, as the sidebar shows it (lib/workspace-choice.ts).
222 const slug = chosenWorkspace(memberships, readCookie(cookies, WORKSPACE_COOKIE))?.slug ?? null;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API223 const username = viewer.username;
224
225 const reposP = soft("repos", reposApi.list(viewer, { memberOnly: true }));
Fast pages, required checks on the branch, self-hosted runners, honest incidents226 // Workflow jobs stuck waiting for a self-hosted runner that is not there.
227 const stuckJobsP = soft("stuck_jobs", runners.stuck(viewer));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms228 // The chosen workspace's projects: their open and recently merged pull
Fast pages, required checks on the branch, self-hosted runners, honest incidents229 // requests, all in one call to work, and their recent events, read as
230 // soon as the projects are known.
231 const perRepoP = reposP.then(async (repos) => {
232 const chosen = (repos ?? [])
233 .filter((repo) => slug != null && repo.namespace.toLowerCase() === slug.toLowerCase())
234 .slice(0, MAX_PROJECTS);
Mission control counts people's direct pushes to the default branch235 const [batch, logs, pushes] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents236 work.pullsForRepos(chosen.map((repo) => repo.id), viewer, PULL_PAGE).catch(() => []),
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md237 Promise.all(chosen.map((repo) => eventLog.list({ repoId: repo.id, types: [...FEED_EVENT_TYPES], limit: EVENTS_PER_PROJECT }).catch(() => null))),
Mission control counts people's direct pushes to the default branch238 // People's pushes straight to the default branch, which no pull
239 // request counts: the commits each brought, for the week.
240 Promise.all(chosen.map((repo) => directCommits(repo, viewer).catch(() => []))),
Fast pages, required checks on the branch, self-hosted runners, honest incidents241 ]);
242 const byId = new Map(batch.map((entry) => [entry.repoId, entry]));
243 return Promise.all(
244 chosen.map(async (repo, index) => {
245 const found = byId.get(repo.id);
Mission control counts people's direct pushes to the default branch246 if (found) return { repo, pulls: found.open.slice(0, 60), closed: found.closed, events: logs[index], direct: pushes[index] ?? [] };
Fast pages, required checks on the branch, self-hosted runners, honest incidents247 // A fork, which the batch leaves out: asked on its own.
248 const path = { namespace: repo.namespace, name: repo.name };
249 const [pulls, closed] = await Promise.all([
250 work.listPulls(path, viewer, "open").catch(() => null),
251 work.listPulls(path, viewer, "closed").catch(() => null),
252 ]);
253 return {
254 repo,
255 pulls: pulls?.ok ? pulls.value.slice(0, 60) : null,
256 closed: closed?.ok ? closed.value : null,
257 events: logs[index],
Mission control counts people's direct pushes to the default branch258 direct: pushes[index] ?? [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents259 };
260 }),
261 );
262 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API263
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all264 const [repos, perRepo, active, models, profile, runs, overview, usage, projectList, memories, invitations, tokens, myTokens, unread] = await Promise.all([
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms265 reposP,
266 soft("projects", perRepoP),
267 soft("pulls", work.listActivePulls(viewer)),
268 slug ? soft("models", env.RUNNER.modelAccess(slug)) : null,
269 soft("profile", identity.profile(username)),
270 slug ? soft("runs", agents.listRuns(viewer, { workspace: slug, limit: 150 })) : null,
271 slug ? soft("deploys", deployments.overview(slug, viewer)) : null,
272 slug ? soft("usage", billing.usage(slug, viewer, new Date(weekAgo - TIME.DAY).toISOString())) : null,
273 slug ? soft("projectList", projectsApi.list(slug, viewer)) : null,
274 slug ? soft("memories", agents.listMemories(viewer, slug, null)) : null,
275 // Repositories someone has invited the viewer to.
276 soft("invitations", identity.myRepoInvitations(viewer)),
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix277 // Model tokens over the last six weeks: the workspace's, and yours.
278 slug ? soft("tokens", billing.tokenUsage(slug, viewer)) : null,
279 slug ? soft("my_tokens", billing.tokenUsage(slug, viewer, { person: username })) : null,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all280 // What in their inbox is unread: an agent waiting on them, or a failure, goes on the Needs you card.
281 soft("inbox", inbox.list(viewer, { unread: true, limit: INBOX_READ })),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms282 ]);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API283
284 const repoList = repos ?? [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms285 // Each pull request is shown under its repository. Most are in the
286 // viewer's own, already listed; the rest are looked up once each.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API287 const known = new Map<string, Repo>(repoList.map((repo) => [repo.id, repo]));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms288 const missing = [...new Set((active ?? []).map(({ pull }) => pull.repoId))].filter((id) => !known.has(id));
Fast pages, required checks on the branch, self-hosted runners, honest incidents289 for (const repo of await readableRepos(missing, viewer)) known.set(repo.id, repo);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API290 const pathOf = (repo: Repo): RepoPath => ({ namespace: repo.namespace, name: repo.name });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms291 const inWorkspace = (repo: RepoPath) => slug != null && repo.namespace.toLowerCase() === slug.toLowerCase();
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API292
293 const activeList = (active ?? []).flatMap((item) => {
294 const repo = known.get(item.pull.repoId);
295 return repo ? [{ ...item, repo }] : [];
296 });
297 const runList = okOr(runs ?? null) ?? [];
298 const liveRuns = runList.filter((run) => isActiveRun(run.status));
299 const overviewList = okOr(overview ?? null);
300 const projectsOk = okOr(projectList ?? null);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms301 const runsOn = (repo: RepoPath, number: number) =>
302 runList.filter(
303 (run) => run.number === number && `${run.repo.namespace}/${run.repo.name}`.toLowerCase() === `${repo.namespace}/${repo.name}`.toLowerCase(),
304 );
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API305
306 const lower = username.toLowerCase();
307 const openPulls = (perRepo ?? []).flatMap(({ repo, pulls }) => (pulls ?? []).map((pull) => ({ pull, repo })));
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights308 // Never your own, nor one g1t made for you.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API309 const reviewRequested = openPulls.filter(
310 ({ pull }) =>
311 pull.status === "open" &&
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights312 workOwner(pull).username.toLowerCase() !== lower &&
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API313 pull.reviewers.some((name) => name.toLowerCase() === lower),
314 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms315
316 // --- Needs you ------------------------------------------------------------
317 const needs: Need[] = [];
318 const extras = new Map<string, Extra>();
319 /** What every pull request's row shares: where it is, who is on it, what is known. */
320 const pullExtra = (pull: Pull, repo: Repo, lifecycle: Lifecycle | null): Extra => {
321 const base = `/${repo.namespace}/${repo.name}`;
322 const agentWork = isAgent(pull.agent);
323 return {
324 repo: pathOf(repo),
325 ref: `#${pull.number}`,
326 by: agentWork ? who(pull.agent) : who(pull.author.username),
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights327 for: agentWork ? workOwner(pull).username : null,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step328 facts: pullFacts({
329 checkStatus: pull.checkStatus,
330 files: pull.files,
331 lifecycle,
332 runs: runsOn(repo, pull.number),
333 confidence: pull.confidence,
334 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms335 open: pull.issue != null ? `${base}/issues/${pull.issue}` : `${base}/pull/${pull.number}?tab=changes`,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step336 confidence: pull.confidence ?? null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms337 };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API338 };
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms339 const pullAction = (repo: Repo, pull: Pull, quick: Omit<QuickAction, "to">): QuickAction => ({
340 ...quick,
341 to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API342 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms343 const approve = (repo: Repo, pull: Pull) =>
344 pullAction(repo, pull, {
345 label: isAgent(pull.agent) ? "Approve the agent's change" : "Approve the change",
346 fields: { action: "comment", verdict: "approve", body: "" },
347 done: "Approved",
348 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API349
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look350 for (const invitation of invitations ?? []) {
351 if (invitation.status !== "pending") continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms352 const key = `invitation:${invitation.id}`;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look353 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms354 key,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look355 kind: "invitation",
356 title: `${invitation.invited_by ?? "Someone"} invited you to ${invitation.repo}`,
357 detail: `With the ${REPO_ROLE_LABELS[invitation.role]} role. The invitation expires ${new Date(invitation.expires_at).toISOString().slice(0, 10)}.`,
358 to: `/${invitation.repo}/invitations`,
359 action: "Respond",
360 at: Date.parse(invitation.created_at),
361 where: invitation.repo,
362 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms363 const [namespace, name] = invitation.repo.split("/");
364 extras.set(key, {
365 repo: namespace && name ? { namespace, name } : null,
366 by: who(invitation.invited_by),
367 facts: [
368 { label: "Role", value: REPO_ROLE_LABELS[invitation.role], tone: null },
369 { label: "Expires", value: new Date(invitation.expires_at).toISOString().slice(0, 10), tone: null },
370 ],
371 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look372 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API373 for (const entry of overviewList ?? []) {
374 const latest = entry.latest;
375 if (latest?.kind === "production" && latest.status === "failed" && slug) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms376 const key = `deploy:${entry.slug}`;
377 const to = `/${slug}/${entry.slug}/deployments/${latest.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API378 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms379 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API380 kind: "deploy",
381 title: `Production build of ${entry.slug} failed`,
382 detail: latest.error ?? "The last build of the default branch failed. Production still serves the build before it.",
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms383 to,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API384 action: "See the build",
385 at: Date.parse(latest.finishedAt ?? latest.createdAt),
386 where: `${slug}/${entry.slug}`,
387 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms388 const facts: Fact[] = [
389 { label: "Commit", value: latest.commit.slice(0, 7), tone: null },
390 {
391 label: "Production",
392 value: entry.production ? "Serving the build before" : "Not live yet",
393 tone: entry.production ? "good" : "warn",
394 },
395 ];
396 extras.set(key, {
397 repo: { namespace: slug, name: entry.slug },
398 by: who(latest.createdBy),
399 facts,
400 link: { label: "Deployment settings", to: `/${slug}/${entry.slug}/settings/deployments` },
401 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API402 }
403 }
404 for (const { pull, lifecycle, repo } of activeList) {
405 const where = `${repo.namespace}/${repo.name}#${pull.number}`;
406 const to = `/${repo.namespace}/${repo.name}/pull/${pull.number}`;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms407 const key = `pull:${pull.id}`;
408 const extra = pullExtra(pull, repo, lifecycle);
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step409 // Held for its low confidence: the ask says so in a sentence of its own.
410 const lowConfidence = pull.confidence?.level === "low" ? pull.confidence : null;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API411 if (lifecycle?.stage === "needs_you") {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step412 const held = lowConfidence != null && /confidence in this change is low/i.test(lifecycle.detail);
413 const conflict = !held && /conflict/i.test(lifecycle.detail);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms414 const need: Need = {
415 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API416 kind: conflict ? "conflict" : "stalled",
417 title: pull.title,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step418 detail: held && lowConfidence ? confidenceAsk(lowConfidence) : lifecycle.detail,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API419 to,
420 action: conflict ? "Resolve" : "Decide",
421 at: Date.parse(pull.updatedAt),
422 where,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms423 };
424 needs.push(need);
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step425 const reason = held ? "low_confidence" : reasonFor(need);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms426 extras.set(key, {
427 ...extra,
428 quick:
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step429 reason === "needs_review" || reason === "low_confidence"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms430 ? approve(repo, pull)
Fast pages, required checks on the branch, self-hosted runners, honest incidents431 : /required checks? .*still fails?\b/i.test(lifecycle.detail)
432 ? pullAction(repo, pull, { label: "Re-run failed jobs", fields: { action: "rerun-failed" }, done: "Re-running" })
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms433 : null,
434 link: reason === "outside_guardrails" ? { label: "Raise the cap", to: `/${repo.namespace}/${repo.name}/settings/guardrails` } : null,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API435 });
436 } else if (lifecycle?.stage === "ready") {
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step437 needs.push({
438 key,
439 kind: "ready",
440 title: pull.title,
Fast pages, required checks on the branch, self-hosted runners, honest incidents441 detail: lowConfidence ? confidenceAsk(lowConfidence) : "Its required checks passed and it was approved. It lands when you merge it.",
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step442 to,
443 action: "Merge",
444 at: Date.parse(pull.updatedAt),
445 where,
446 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms447 extras.set(key, {
448 ...extra,
449 quick: pullAction(repo, pull, {
450 label: isAgent(pull.agent) ? "Merge the agent's change" : "Merge it",
451 fields: { action: "merge" },
452 done: "Merging",
453 }),
454 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API455 } else if (!lifecycle && pull.status === "open" && pull.checkStatus === "failed") {
Fast pages, required checks on the branch, self-hosted runners, honest incidents456 // Taken out of the merge queue: its change failed combined with what was ahead.
457 needs.push({ key, kind: "checks", title: pull.title, detail: "It failed in the merge queue. Push a fix, then merge it again.", to, action: "See checks", at: Date.parse(pull.updatedAt), where });
458 extras.set(key, { ...extra, quick: null });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API459 }
460 }
461 for (const { pull, repo } of reviewRequested) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms462 const key = `review:${pull.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API463 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms464 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API465 kind: "review",
466 title: pull.title,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights467 detail: `${workOwner(pull).username} asked for your review.`,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API468 to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`,
469 action: "Review",
470 at: Date.parse(pull.updatedAt),
471 where: `${repo.namespace}/${repo.name}#${pull.number}`,
472 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms473 extras.set(key, { ...pullExtra(pull, repo, null), quick: approve(repo, pull) });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API474 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents475 for (const job of (await stuckJobsP) ?? []) {
476 const key = `runner:${job.id}`;
477 const minutes = Math.max(10, Math.round((now - Date.parse(job.queuedAt)) / 60_000));
478 const [namespace, name] = job.repo.split("/");
479 needs.push({
480 key,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit481 kind: "runner",
Fast pages, required checks on the branch, self-hosted runners, honest incidents482 title: `${job.name} is waiting for a self-hosted runner`,
483 detail: `No runner with labels ${job.labels} is online. Start one, or change the job's runs-on.`,
484 to: `/${job.repo}/actions/runs/${job.runId}`,
485 action: "Look",
486 at: Date.parse(job.queuedAt),
487 where: job.repo,
488 });
489 extras.set(key, {
490 repo: namespace && name ? { namespace, name } : undefined,
491 facts: [
492 { label: "Labels", value: job.labels, tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit493 { label: "Waiting", value: waitedFor(minutes), tone: "warn" },
Fast pages, required checks on the branch, self-hosted runners, honest incidents494 ],
Runners are owner-only: the page, its actions, the Settings entry and the links to it495 // Runners are the owners' to see to.
496 ...(namespace && roleIn(viewer, namespace) === "owner" ? { link: { label: "Runners", to: `/${namespace}/-/runners` } } : {}),
Fast pages, required checks on the branch, self-hosted runners, honest incidents497 });
498 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API499 for (const run of liveRuns) {
500 const minutes = stuckMinutes(run, now);
501 if (minutes == null) continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms502 const key = `run:${run.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API503 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms504 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API505 kind: "stuck",
506 title: run.title ?? `${run.agent}'s run`,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit507 detail: `${run.agent} has reported nothing for ${waitedFor(minutes)}${run.step ? `. Last: ${run.step}` : ""}.`,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API508 to: `/${run.repo.namespace}/${run.repo.name}/agents/runs/${run.id}`,
509 action: "Look",
510 at: Date.parse(run.updatedAt),
511 where: `${run.repo.namespace}/${run.repo.name}${run.number != null ? `#${run.number}` : ""}`,
512 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms513 extras.set(key, {
514 repo: run.repo,
515 ref: run.number != null ? `#${run.number}` : null,
516 by: who(run.agent),
517 facts: [
518 { label: "Run", value: RUN_LABEL[run.kind], tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit519 { label: "Quiet for", value: waitedFor(minutes), tone: "warn" },
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms520 { label: "Steps so far", value: String(run.stepCount), tone: null },
521 ...(run.costUsd != null ? [{ label: "Cost so far", value: usd(run.costUsd), tone: null }] : []),
522 ],
523 open: run.number != null ? `/${run.repo.namespace}/${run.repo.name}/pull/${run.number}` : undefined,
524 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API525 }
526
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms527 const needRows: NeedRow[] = rankNeeds(needs).map((need) => {
528 const extra = extras.get(need.key) ?? {};
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step529 const reason = withConfidence(reasonFor(need), extra.confidence);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms530 return {
531 key: need.key,
532 reason,
533 repo: extra.repo ?? null,
534 ref: extra.ref ?? null,
535 title: need.title,
536 ask: need.detail,
537 by: extra.by ?? null,
538 for: extra.for ?? null,
539 at: need.at,
540 to: need.to,
541 open: extra.open ?? need.to,
542 facts: extra.facts ?? [],
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step543 why: whyFor(reason, need, extra.confidence),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms544 quick: extra.quick ?? null,
545 link: extra.link ?? null,
546 };
547 });
548
549 // --- Waiting on agents ----------------------------------------------------
550 const needKeys = new Set(
551 needRows.flatMap((row) => (row.repo && row.ref ? [needPathKey(row.repo, Number(row.ref.slice(1)))] : [])),
552 );
553 const waiting = waitingRows({
554 active: activeList.filter(({ repo }) => inWorkspace(repo)).map(({ pull, lifecycle, repo }) => ({ pull, lifecycle, repo: pathOf(repo) })),
555 live: liveRuns,
556 drafts: openPulls.filter(({ pull }) => pull.status === "draft").map(({ pull, repo }) => ({ ...pull, repo: pathOf(repo) })),
557 needKeys,
558 });
559
560 // --- Landed ---------------------------------------------------------------
561 const merged: Merged[] = (perRepo ?? []).flatMap(({ repo, closed }) =>
562 (closed ?? []).flatMap((pull) =>
563 pull.status === "merged" && pull.mergedAt
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar564 ? [
565 {
566 repo: pathOf(repo),
567 number: pull.number,
568 title: pull.title,
569 agent: pull.agent,
570 // Who wrote it, not who merged it: a person's change that g1t
571 // auto-merged is still theirs.
572 authoredByAgent: pull.author.kind === "agent" || isAgent(pull.author.username),
573 mergedBy: pull.mergedBy,
574 mergedAt: pull.mergedAt,
575 files: pull.files,
576 },
577 ]
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms578 : [],
579 ),
580 );
581 const twoWeeksAgo = now - 14 * TIME.DAY;
582 const complete = perRepo != null && perRepo.every(({ closed }) => closed != null && reachesBack(closed, twoWeeksAgo, PULL_PAGE));
Mission control counts people's direct pushes to the default branch583 // A person's commits pushed straight to the default branch are their own
584 // changes too, on the day they landed.
585 const direct = (perRepo ?? []).flatMap(({ direct }) =>
586 direct.map((commit) => ({ mergedAt: commit.at, mergedBy: null, authoredByAgent: false })),
587 );
588 const week = weekOf([...merged, ...direct], now, tz, complete);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms589 const landed = landedToday(merged, now, tz);
590
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API591 // --- Activity -------------------------------------------------------------
592 const items: ActivityItem[] = [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms593 const titles: Record<string, string> = {};
594 const titleKey = (repo: RepoPath, number: number) => `${repo.namespace}/${repo.name}#${number}`.toLowerCase();
595 for (const { repo, pulls, closed, events } of perRepo ?? []) {
596 for (const pull of [...(pulls ?? []), ...(closed ?? [])]) titles[titleKey(repo, pull.number)] = pull.title;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API597 for (const event of events ?? []) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms598 if (event.type === "issue.opened") titles[titleKey(repo, event.data.number)] ??= event.data.title;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API599 const item = eventItem(event, pathOf(repo));
600 if (item) items.push(item);
601 }
602 }
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md603 // Production deploys come from the log too (deployment_status.created),
604 // wherever they ran: g1t.page, g1t Actions or the API.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API605 for (const memory of okOr(memories ?? null)?.workspace ?? []) {
606 const repo = memory.repo ?? memory.source.repo;
607 if (!repo || !slug) continue;
608 items.push({
609 id: `memory:${memory.id}`,
610 at: Date.parse(memory.createdAt),
611 repo,
612 actor: memory.createdBy,
613 verb: "learned",
614 number: null,
615 text: memory.text.length > 90 ? `${memory.text.slice(0, 88)}…` : memory.text,
616 to: `/${slug}/-/memory`,
617 });
618 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit619 // The log names people by account id: their usernames, in one lookup.
620 const ids = actorIds(items.map((item) => item.actor));
621 const names = ids.length > 0 ? await soft("usernames", identity.usernames(ids)) : {};
622 const groups = groupActivity(items.map((item) => ({ ...item, actor: nameActor(item.actor, names) }))).slice(0, 40);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms623 // Only the titles the feed names travel to the page.
624 const shownTitles: Record<string, string> = {};
625 for (const group of groups) {
626 for (const part of group.parts) {
627 for (const number of part.numbers) {
628 const key = titleKey(group.repo, number);
629 if (titles[key]) shownTitles[key] = titles[key];
630 }
631 }
632 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API633
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms634 // --- The strip ------------------------------------------------------------
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API635 const usageOk = okOr(usage ?? null);
636 const costPoints = usageOk
637 ? usageOk.byDay.map((slice) => ({ at: Date.parse(`${slice.key.split("/")[0]}T12:00:00Z`), value: slice.micros / 1_000_000 }))
638 : runList.filter((run) => run.costUsd != null).map((run) => ({ at: Date.parse(run.createdAt), value: run.costUsd ?? 0 }));
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms639 const weekCost = dailyBuckets(costPoints, 7, now).reduce((sum, v) => sum + v, 0);
640 const month = dayKey(now, tz).slice(0, 7);
641 const projectCount = projectsOk?.length ?? (perRepo != null ? perRepo.length : null);
642 const projectsThisMonth = projectsOk ? projectsOk.filter((project) => dayKey(Date.parse(project.createdAt), tz).slice(0, 7) === month).length : null;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API643
644 const models_ = models ?? null;
645 times.total = Date.now() - started;
Agents as a team: lifecycle, merge queue, billing and a new shell646 const serverTiming = Object.entries(times)
647 .map(([name, ms]) => `${name};dur=${ms}`)
648 .join(", ");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API649 return data(
650 {
651 signedIn: true as const,
652 viewer,
653 name: profile?.name?.trim() || username,
654 greeting: greetingFor(hourIn(now, tz)),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms655 date: dateLine(now, tz),
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar656 summary: summaryLine({
657 total: week.total,
658 byAgents: week.byAgents,
659 agentChanges: week.agentChanges,
660 people: week.people,
661 live: liveRuns.length,
662 needs: needRows.length,
663 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms664 workspace: slug,
665 repos: repoList.filter((repo) => inWorkspace(repo)).map(pathOf),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API666 canRunAgents: models_ == null || models_.hosted || models_.own != null,
667 trial: models_?.own == null ? (models_?.trial ?? null) : null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms668 handedOff: activeList.length > 0 || runList.length > 0 || merged.length > 0,
669 needs: needRows,
670 waiting,
671 landed,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API672 liveTotal: liveRuns.length,
673 runsLoaded: runs != null && runs.ok,
674 perRepoLoaded: perRepo != null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms675 stats: {
676 projects: projectCount,
677 projectsThisMonth,
678 agentHours: agentHours(runList, weekAgo, now),
679 weekCost,
680 },
681 week,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix682 tokens: { workspace: okOr(tokens), mine: okOr(myTokens) },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API683 groups,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms684 titles: shownTitles,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all685 inboxNeeds: needsYou(unread?.items ?? [], INBOX_SHOWN),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms686 // A run that is going makes the page worth refreshing on its own.
687 changing:
688 liveRuns.length > 0 ||
689 activeList.some((item) => item.lifecycle && item.lifecycle.stage !== "needs_you" && item.lifecycle.stage !== "ready"),
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API690 },
691 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms692 headers: { "Server-Timing": serverTiming },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API693 },
694 );
Agents as a team: lifecycle, merge queue, billing and a new shell695}
Get started on mission control696
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look697export type Loaded = Extract<Route.ComponentProps["loaderData"], { signedIn: true }>;
Get started on mission control698
Fast pages, required checks on the branch, self-hosted runners, honest incidents699/**
700 * Mission control, loaded only for someone signed in (components/mission-control.tsx).
701 * The app shell starts loading it once the browser is idle, so the
702 * skeleton below is rarely seen.
703 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look704const MissionControl = lazy(() => import("../components/mission-control"));
Agents as a team: lifecycle, merge queue, billing and a new shell705
Usage, Billing settings and prepaid AI credit; fixes from the UX audit706/**
707 * Mission control's outline while its code arrives, piece for piece as
708 * components/mission-control.tsx lays it out: the greeting, the Agent box,
709 * the actions under it, the stats, then the work list beside the week. Each
710 * piece has the padding and line heights of the real one, so nothing moves
711 * when it arrives.
712 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents713function MissionControlSkeleton() {
714 return (
715 <main aria-busy="true" className="mx-auto max-w-7xl space-y-6 px-4 py-8 sm:px-6 sm:py-10">
Merge branch 'worktree-agent-aca4790ea223aa235'716 <span role="status" className="sr-only">
717 Loading…
718 </span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit719 <header>
720 <SkeletonLine className="w-72 max-w-full text-[1.75rem] leading-tight sm:text-[2rem]" barClassName="h-8" />
721 {/* The date and the week's summary: one line wide, three on a phone. */}
722 <div className="mt-1.5 text-sm">
723 <SkeletonLine className="w-[36rem] max-w-full" barClassName="h-3.5" />
724 <SkeletonLine className="w-full sm:hidden" barClassName="h-3.5" />
725 <SkeletonLine className="w-2/3 sm:hidden" barClassName="h-3.5" />
Merge branch 'worktree-agent-aca4790ea223aa235'726 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents727 </header>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit728 {/* The Agent box (components/ask-composer.tsx), then the actions under it. */}
729 <div className="space-y-3">
730 <div className="rounded-xl border border-line bg-surface">
731 <SkeletonLine className="mx-4 mt-3 w-36 text-sm" />
732 <div className="px-4 pt-3.5 pb-2 text-sm">
733 <SkeletonLine className="w-[30rem] max-w-full" />
734 <span className="block h-[1lh]" />
735 </div>
736 <div className="flex flex-wrap items-center gap-2 px-3 pb-3">
737 <Skeleton className="h-8 w-[4.5rem] rounded-md" />
738 <Skeleton className="h-8 w-[9.5rem] rounded-md" />
739 <Skeleton className="size-8 rounded-md" />
740 <Skeleton className="size-8 rounded-md" />
741 <Skeleton className="ml-auto size-8 rounded-md" />
742 </div>
743 </div>
744 <div className="flex flex-wrap items-center gap-2">
745 <Skeleton className="h-[38px] w-60 rounded-md" />
746 <Skeleton className="h-[38px] w-28 rounded-md" />
747 <Skeleton className="h-[38px] w-44 rounded-md" />
748 </div>
749 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents750 <div className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5">
751 {Array.from({ length: 5 }, (_, index) => (
Merge branch 'worktree-agent-aca4790ea223aa235'752 <SkeletonStat key={index} className={index === 4 ? "col-span-2 lg:col-span-1" : undefined} />
Fast pages, required checks on the branch, self-hosted runners, honest incidents753 ))}
754 </div>
Merge branch 'worktree-agent-aca4790ea223aa235'755 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
756 <div className="self-start overflow-hidden rounded-xl border border-line bg-surface">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit757 <div className="flex items-center gap-4 border-b border-line px-4 py-3 text-sm sm:px-5">
758 <SkeletonLine className="w-24" />
759 <SkeletonLine className="w-32" />
760 <SkeletonLine className="hidden w-28 sm:flex" />
761 </div>
762 <div className="divide-y divide-line">
763 {Array.from({ length: 5 }, (_, index) => (
764 <div key={index}>
765 <div className="flex items-center gap-3 px-4 py-3 sm:px-5">
766 <Skeleton className="size-8 shrink-0 rounded-md" />
767 <span className="min-w-0 grow">
768 <SkeletonLine className="text-sm" width={`${70 - ((index * 17) % 30)}%`} />
769 <SkeletonLine className="mt-0.5 text-xs" width={`${50 - ((index * 11) % 20)}%`} />
770 </span>
771 <Skeleton className="hidden h-4 w-24 shrink-0 sm:block" />
772 </div>
773 {/* The first row stands open, as the real list's does. */}
774 {index === 0 && (
775 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
776 <Skeleton className="h-[17rem] rounded-xl" />
777 <Skeleton className="mt-3 h-8 w-44 rounded-md" />
778 </div>
779 )}
780 </div>
781 ))}
Merge branch 'worktree-agent-aca4790ea223aa235'782 </div>
783 </div>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit784 <div className="space-y-6">
785 <SkeletonCard lines={0} className="h-80 p-5" />
786 <SkeletonCard lines={6} className="p-5" />
787 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents788 </div>
789 </main>
790 );
791}
792
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step793export default function Home({ loaderData, actionData }: Route.ComponentProps) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look794 if (!loaderData.signedIn) return <Landing />;
API and MCP server, Rust identity service, registration, site redesign795 return (
Fast pages, required checks on the branch, self-hosted runners, honest incidents796 <Suspense fallback={<MissionControlSkeleton />}>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step797 <MissionControl loaderData={loaderData} delegated={actionData ?? null} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look798 </Suspense>
Initial g1t: services, event bus, intents and attempts799 );
800}

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