Skip to content
802 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";
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)40import { G1T_COMMIT_EMAILS, normalizeEmail } from "../lib/commit-people";
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms41import {
42 type Fact,
43 type Merged,
Merge branch 'main' into actions-toolkit-oidc-artifacts44 placePushes,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms45 type NeedRow,
46 type QuickAction,
47 RUN_LABEL,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step48 confidenceAsk,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms49 dateLine,
50 dayKey,
51 landedToday,
52 needPathKey,
53 pullFacts,
54 reachesBack,
55 reasonFor,
56 summaryLine,
57 usd,
58 waitingRows,
59 weekOf,
60 who,
61 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step62 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms63} from "../lib/mission-control";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step64import { 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 all65import { needsYou } from "../lib/inbox";
API and MCP server, Rust identity service, registration, site redesign66import { Landing } from "../components/landing";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit67import { Skeleton, SkeletonCard, SkeletonLine, SkeletonStat } from "../components/ui/skeleton";
API and MCP server, Rust identity service, registration, site redesign68import {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API69 agents,
70 billing,
71 deployments,
72 events as eventLog,
73 identity,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all74 inbox,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API75 projects as projectsApi,
76 repos as reposApi,
77 work,
78} from "../lib/services.server";
Runners are owner-only: the page, its actions, the Settings entry and the links to it79import { assertSameOrigin, getViewer, requireUser, roleIn } from "../lib/session.server";
Fast pages, required checks on the branch, self-hosted runners, honest incidents80import { runners } from "../lib/runners.server";
81import { readableRepos } from "../lib/access.server";
Initial g1t: services, event bus, intents and attempts82
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look83/** 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 API84const TZ_COOKIE = "g1t_tz";
85/** The most projects whose pull requests and events are read for the page. */
86const MAX_PROJECTS = 10;
87const EVENTS_PER_PROJECT = 80;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms88/** How many pull requests work lists at once (`LIST_PAGE` in services/work). */
89const PULL_PAGE = 100;
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all90/** Unread inbox items read for the Needs you card, and how many it shows. */
91const INBOX_READ = 50;
92const INBOX_SHOWN = 4;
A free allowance on g1t's models, so anyone can try its agents93
Social cards for every page: og.g1t.sh94export function meta(args: Route.MetaArgs) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit95 // Someone signed in is on mission control; visitors get the landing page's title.
96 if (args.loaderData?.signedIn) return page(args, { title: "Mission control · g1t" });
Social cards for every page: og.g1t.sh97 return page(args, {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look98 title: "g1t — where people and agents ship software together",
Social cards for every page: og.g1t.sh99 description:
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look100 "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.sh101 });
Initial g1t: services, event bus, intents and attempts102}
103
Agents as a team: lifecycle, merge queue, billing and a new shell104export function headers({ loaderHeaders }: Route.HeadersArgs) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API105 const out = new Headers();
106 const timing = loaderHeaders.get("Server-Timing");
107 if (timing) out.set("Server-Timing", timing);
108 const cookie = loaderHeaders.get("Set-Cookie");
109 if (cookie) out.set("Set-Cookie", cookie);
110 return out;
Agents as a team: lifecycle, merge queue, billing and a new shell111}
112
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms113/**
114 * 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 all115 * everything is already loaded. Marking an inbox item read or done changes
116 * nothing else here: the card steps out by itself, and the bell's count
117 * comes with the shell (root.tsx). A refresh, a form, or anything else
118 * loads again as usual.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms119 */
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all120export 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 200ms121 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 all122 if (formAction === "/inbox") return false;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms123 return defaultShouldRevalidate;
124}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API125
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms126/** 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 step127type Extra = Partial<Pick<NeedRow, "repo" | "ref" | "by" | "for" | "facts" | "quick" | "link" | "open">> & {
128 /** How sure g1t is of the agent's change, for a pull request. */
129 confidence?: Confidence | null;
130};
131
132/** What the composer said back, when the issue opened but the agent did not start, or nothing opened. */
133export 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 API134
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step135/**
136 * "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-agent137 * 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 step138 * start, the issue is still open, and the composer says why and where to
139 * fix it.
140 */
141export async function action({ request, context }: Route.ActionArgs) {
142 assertSameOrigin(request);
143 const user = requireUser(context, request);
144 const form = await request.formData();
145 if (form.get("intent") !== "delegate") return data<DelegateResult>({ error: "Nothing to do.", notStarted: null }, { status: 400 });
146 const projects = await reposApi.list(user, { memberOnly: true }).catch(() => []);
147 const repo = chosenRepo(form.get("repo"), projects.map((repo) => ({ namespace: repo.namespace, name: repo.name })));
148 if (!repo) return data<DelegateResult>({ error: "Choose one of your projects.", notStarted: null }, { status: 400 });
149 const result = await env.RUNNER.delegate(user, repo, delegateForm(form));
150 if (!result.ok) return data<DelegateResult>({ error: result.error.message, notStarted: null }, { status: 400 });
151 const { issue, agent } = result.value;
152 const refused = notStarted(agent, repo, issue.number);
153 if (!refused) throw redirect(issuePath(repo, issue.number));
154 return data<DelegateResult>({ error: null, notStarted: refused });
155}
156
Merge branch 'main' into actions-toolkit-oidc-artifacts157/**
158 * Push events read per project, a page at a time, back two weeks. Pushes
159 * to every branch are in the log, so a fixed count (it was 40) lost a busy
160 * week's earlier days to agents' branches.
161 */
162const PUSH_PAGE = 200;
163const PUSH_PAGES = 5;
164/** Commits of the default branch read once, to place each push's commits. */
165const HISTORY_READ = 1000;
Mission control counts people's direct pushes to the default branch166
167/**
168 * The commits people pushed straight to a project's default branch in the
169 * last two weeks, each with when its push landed. Merges and agents'
170 * commits are left out: pull requests count those.
171 */
172async function directCommits(repo: Repo, viewer: Viewer): Promise<{ hash: string; at: string }[]> {
173 const since = Date.now() - 14 * TIME.DAY;
Merge branch 'main' into actions-toolkit-oidc-artifacts174 const pushes: G1tEvent<"git.push">[] = [];
175 let before: string | undefined;
176 for (let page = 0; page < PUSH_PAGES; page++) {
177 const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit: PUSH_PAGE, ...(before ? { before } : {}) });
178 for (const event of batch) {
179 if (event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since) pushes.push(event as G1tEvent<"git.push">);
180 }
181 const oldest = batch.at(-1);
182 if (batch.length < PUSH_PAGE || !oldest || Date.parse(oldest.time) < since) break;
183 before = oldest.id;
184 }
185 if (pushes.length === 0) return [];
186 // One read of the branch from the newest push back; each push brought
187 // what lies between its `after` and its `before` in that history.
Mission control counts people's direct pushes to the default branch188 const path = { namespace: repo.namespace, name: repo.name };
Merge branch 'main' into actions-toolkit-oidc-artifacts189 const history = await reposApi.log(path, viewer, pushes[0].data.after, HISTORY_READ).catch(() => null);
190 if (!history?.ok) return [];
Merge leftovers: plan activity filtered in the query, pushes counted by account, ghost during the deletion window, profile time zones (identity 0039)191 // g1t's own commits are told by their author address, never by name.
192 return placePushes(history.value, pushes, (commit) => G1T_COMMIT_EMAILS.has(normalizeEmail(commit.author.email)));
Mission control counts people's direct pushes to the default branch193}
194
Merge branch 'main' into actions-toolkit-oidc-artifacts195
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API196export async function loader({ context, request }: Route.LoaderArgs) {
Initial g1t: services, event bus, intents and attempts197 const viewer = getViewer(context);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API198 if (!viewer) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look199 // The signed-out home page is about signing up; it lists no repositories.
200 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 API201 }
202
203 const now = Date.now();
204 const weekAgo = now - 7 * TIME.DAY;
Agents as a team: lifecycle, merge queue, billing and a new shell205 const started = Date.now();
206 const times: Record<string, number> = {};
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API207 // Each call is timed, and one that fails leaves its section empty rather
208 // than taking the page down.
209 const soft = <T,>(name: string, promise: Promise<T>): Promise<T | null> =>
210 promise.then(
211 (value) => ((times[name] = Date.now() - started), value),
212 (error) => {
213 console.warn(`home: ${name} failed`, error);
214 times[name] = Date.now() - started;
215 return null;
216 },
217 );
218 const okOr = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null);
219
220 const cookies = request.headers.get("cookie");
221 const tz = readCookie(cookies, TZ_COOKIE);
222 const memberships = viewer.workspaces ?? [];
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put223 // The workspace you chose, as the sidebar shows it (lib/workspace-choice.ts).
224 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 API225 const username = viewer.username;
226
227 const reposP = soft("repos", reposApi.list(viewer, { memberOnly: true }));
Fast pages, required checks on the branch, self-hosted runners, honest incidents228 // Workflow jobs stuck waiting for a self-hosted runner that is not there.
229 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 200ms230 // The chosen workspace's projects: their open and recently merged pull
Fast pages, required checks on the branch, self-hosted runners, honest incidents231 // requests, all in one call to work, and their recent events, read as
232 // soon as the projects are known.
233 const perRepoP = reposP.then(async (repos) => {
234 const chosen = (repos ?? [])
235 .filter((repo) => slug != null && repo.namespace.toLowerCase() === slug.toLowerCase())
236 .slice(0, MAX_PROJECTS);
Mission control counts people's direct pushes to the default branch237 const [batch, logs, pushes] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents238 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.md239 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 branch240 // People's pushes straight to the default branch, which no pull
241 // request counts: the commits each brought, for the week.
242 Promise.all(chosen.map((repo) => directCommits(repo, viewer).catch(() => []))),
Fast pages, required checks on the branch, self-hosted runners, honest incidents243 ]);
244 const byId = new Map(batch.map((entry) => [entry.repoId, entry]));
245 return Promise.all(
246 chosen.map(async (repo, index) => {
247 const found = byId.get(repo.id);
Mission control counts people's direct pushes to the default branch248 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 incidents249 // A fork, which the batch leaves out: asked on its own.
250 const path = { namespace: repo.namespace, name: repo.name };
251 const [pulls, closed] = await Promise.all([
252 work.listPulls(path, viewer, "open").catch(() => null),
253 work.listPulls(path, viewer, "closed").catch(() => null),
254 ]);
255 return {
256 repo,
257 pulls: pulls?.ok ? pulls.value.slice(0, 60) : null,
258 closed: closed?.ok ? closed.value : null,
259 events: logs[index],
Mission control counts people's direct pushes to the default branch260 direct: pushes[index] ?? [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents261 };
262 }),
263 );
264 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API265
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all266 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 200ms267 reposP,
268 soft("projects", perRepoP),
269 soft("pulls", work.listActivePulls(viewer)),
270 slug ? soft("models", env.RUNNER.modelAccess(slug)) : null,
271 soft("profile", identity.profile(username)),
272 slug ? soft("runs", agents.listRuns(viewer, { workspace: slug, limit: 150 })) : null,
273 slug ? soft("deploys", deployments.overview(slug, viewer)) : null,
274 slug ? soft("usage", billing.usage(slug, viewer, new Date(weekAgo - TIME.DAY).toISOString())) : null,
275 slug ? soft("projectList", projectsApi.list(slug, viewer)) : null,
276 slug ? soft("memories", agents.listMemories(viewer, slug, null)) : null,
277 // Repositories someone has invited the viewer to.
278 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 mix279 // Model tokens over the last six weeks: the workspace's, and yours.
280 slug ? soft("tokens", billing.tokenUsage(slug, viewer)) : null,
281 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 all282 // What in their inbox is unread: an agent waiting on them, or a failure, goes on the Needs you card.
283 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 200ms284 ]);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API285
286 const repoList = repos ?? [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms287 // Each pull request is shown under its repository. Most are in the
288 // 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 API289 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 200ms290 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 incidents291 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 API292 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 200ms293 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 API294
295 const activeList = (active ?? []).flatMap((item) => {
296 const repo = known.get(item.pull.repoId);
297 return repo ? [{ ...item, repo }] : [];
298 });
299 const runList = okOr(runs ?? null) ?? [];
300 const liveRuns = runList.filter((run) => isActiveRun(run.status));
301 const overviewList = okOr(overview ?? null);
302 const projectsOk = okOr(projectList ?? null);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms303 const runsOn = (repo: RepoPath, number: number) =>
304 runList.filter(
305 (run) => run.number === number && `${run.repo.namespace}/${run.repo.name}`.toLowerCase() === `${repo.namespace}/${repo.name}`.toLowerCase(),
306 );
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API307
308 const lower = username.toLowerCase();
309 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 rights310 // 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 API311 const reviewRequested = openPulls.filter(
312 ({ pull }) =>
313 pull.status === "open" &&
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights314 workOwner(pull).username.toLowerCase() !== lower &&
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API315 pull.reviewers.some((name) => name.toLowerCase() === lower),
316 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms317
318 // --- Needs you ------------------------------------------------------------
319 const needs: Need[] = [];
320 const extras = new Map<string, Extra>();
321 /** What every pull request's row shares: where it is, who is on it, what is known. */
322 const pullExtra = (pull: Pull, repo: Repo, lifecycle: Lifecycle | null): Extra => {
323 const base = `/${repo.namespace}/${repo.name}`;
324 const agentWork = isAgent(pull.agent);
325 return {
326 repo: pathOf(repo),
327 ref: `#${pull.number}`,
328 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 rights329 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 step330 facts: pullFacts({
331 checkStatus: pull.checkStatus,
332 files: pull.files,
333 lifecycle,
334 runs: runsOn(repo, pull.number),
335 confidence: pull.confidence,
336 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms337 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 step338 confidence: pull.confidence ?? null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms339 };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API340 };
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms341 const pullAction = (repo: Repo, pull: Pull, quick: Omit<QuickAction, "to">): QuickAction => ({
342 ...quick,
343 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 API344 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms345 const approve = (repo: Repo, pull: Pull) =>
346 pullAction(repo, pull, {
347 label: isAgent(pull.agent) ? "Approve the agent's change" : "Approve the change",
348 fields: { action: "comment", verdict: "approve", body: "" },
349 done: "Approved",
350 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API351
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look352 for (const invitation of invitations ?? []) {
353 if (invitation.status !== "pending") continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms354 const key = `invitation:${invitation.id}`;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look355 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms356 key,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look357 kind: "invitation",
358 title: `${invitation.invited_by ?? "Someone"} invited you to ${invitation.repo}`,
359 detail: `With the ${REPO_ROLE_LABELS[invitation.role]} role. The invitation expires ${new Date(invitation.expires_at).toISOString().slice(0, 10)}.`,
360 to: `/${invitation.repo}/invitations`,
361 action: "Respond",
362 at: Date.parse(invitation.created_at),
363 where: invitation.repo,
364 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms365 const [namespace, name] = invitation.repo.split("/");
366 extras.set(key, {
367 repo: namespace && name ? { namespace, name } : null,
368 by: who(invitation.invited_by),
369 facts: [
370 { label: "Role", value: REPO_ROLE_LABELS[invitation.role], tone: null },
371 { label: "Expires", value: new Date(invitation.expires_at).toISOString().slice(0, 10), tone: null },
372 ],
373 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look374 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API375 for (const entry of overviewList ?? []) {
376 const latest = entry.latest;
377 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 200ms378 const key = `deploy:${entry.slug}`;
379 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 API380 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms381 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API382 kind: "deploy",
383 title: `Production build of ${entry.slug} failed`,
384 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 200ms385 to,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API386 action: "See the build",
387 at: Date.parse(latest.finishedAt ?? latest.createdAt),
388 where: `${slug}/${entry.slug}`,
389 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms390 const facts: Fact[] = [
391 { label: "Commit", value: latest.commit.slice(0, 7), tone: null },
392 {
393 label: "Production",
394 value: entry.production ? "Serving the build before" : "Not live yet",
395 tone: entry.production ? "good" : "warn",
396 },
397 ];
398 extras.set(key, {
399 repo: { namespace: slug, name: entry.slug },
400 by: who(latest.createdBy),
401 facts,
402 link: { label: "Deployment settings", to: `/${slug}/${entry.slug}/settings/deployments` },
403 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API404 }
405 }
406 for (const { pull, lifecycle, repo } of activeList) {
407 const where = `${repo.namespace}/${repo.name}#${pull.number}`;
408 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 200ms409 const key = `pull:${pull.id}`;
410 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 step411 // Held for its low confidence: the ask says so in a sentence of its own.
412 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 API413 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 step414 const held = lowConfidence != null && /confidence in this change is low/i.test(lifecycle.detail);
415 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 200ms416 const need: Need = {
417 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API418 kind: conflict ? "conflict" : "stalled",
419 title: pull.title,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step420 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 API421 to,
422 action: conflict ? "Resolve" : "Decide",
423 at: Date.parse(pull.updatedAt),
424 where,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms425 };
426 needs.push(need);
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step427 const reason = held ? "low_confidence" : reasonFor(need);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms428 extras.set(key, {
429 ...extra,
430 quick:
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step431 reason === "needs_review" || reason === "low_confidence"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms432 ? approve(repo, pull)
Fast pages, required checks on the branch, self-hosted runners, honest incidents433 : /required checks? .*still fails?\b/i.test(lifecycle.detail)
434 ? 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 200ms435 : null,
436 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 API437 });
438 } 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 step439 needs.push({
440 key,
441 kind: "ready",
442 title: pull.title,
Fast pages, required checks on the branch, self-hosted runners, honest incidents443 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 step444 to,
445 action: "Merge",
446 at: Date.parse(pull.updatedAt),
447 where,
448 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms449 extras.set(key, {
450 ...extra,
451 quick: pullAction(repo, pull, {
452 label: isAgent(pull.agent) ? "Merge the agent's change" : "Merge it",
453 fields: { action: "merge" },
454 done: "Merging",
455 }),
456 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API457 } else if (!lifecycle && pull.status === "open" && pull.checkStatus === "failed") {
Fast pages, required checks on the branch, self-hosted runners, honest incidents458 // Taken out of the merge queue: its change failed combined with what was ahead.
459 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 });
460 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 API461 }
462 }
463 for (const { pull, repo } of reviewRequested) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms464 const key = `review:${pull.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API465 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms466 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API467 kind: "review",
468 title: pull.title,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights469 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 API470 to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`,
471 action: "Review",
472 at: Date.parse(pull.updatedAt),
473 where: `${repo.namespace}/${repo.name}#${pull.number}`,
474 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms475 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 API476 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents477 for (const job of (await stuckJobsP) ?? []) {
478 const key = `runner:${job.id}`;
479 const minutes = Math.max(10, Math.round((now - Date.parse(job.queuedAt)) / 60_000));
480 const [namespace, name] = job.repo.split("/");
481 needs.push({
482 key,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit483 kind: "runner",
Fast pages, required checks on the branch, self-hosted runners, honest incidents484 title: `${job.name} is waiting for a self-hosted runner`,
485 detail: `No runner with labels ${job.labels} is online. Start one, or change the job's runs-on.`,
486 to: `/${job.repo}/actions/runs/${job.runId}`,
487 action: "Look",
488 at: Date.parse(job.queuedAt),
489 where: job.repo,
490 });
491 extras.set(key, {
492 repo: namespace && name ? { namespace, name } : undefined,
493 facts: [
494 { label: "Labels", value: job.labels, tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit495 { label: "Waiting", value: waitedFor(minutes), tone: "warn" },
Fast pages, required checks on the branch, self-hosted runners, honest incidents496 ],
Runners are owner-only: the page, its actions, the Settings entry and the links to it497 // Runners are the owners' to see to.
498 ...(namespace && roleIn(viewer, namespace) === "owner" ? { link: { label: "Runners", to: `/${namespace}/-/runners` } } : {}),
Fast pages, required checks on the branch, self-hosted runners, honest incidents499 });
500 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API501 for (const run of liveRuns) {
502 const minutes = stuckMinutes(run, now);
503 if (minutes == null) continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms504 const key = `run:${run.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API505 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms506 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API507 kind: "stuck",
508 title: run.title ?? `${run.agent}'s run`,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit509 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 API510 to: `/${run.repo.namespace}/${run.repo.name}/agents/runs/${run.id}`,
511 action: "Look",
512 at: Date.parse(run.updatedAt),
513 where: `${run.repo.namespace}/${run.repo.name}${run.number != null ? `#${run.number}` : ""}`,
514 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms515 extras.set(key, {
516 repo: run.repo,
517 ref: run.number != null ? `#${run.number}` : null,
518 by: who(run.agent),
519 facts: [
520 { label: "Run", value: RUN_LABEL[run.kind], tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit521 { 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 200ms522 { label: "Steps so far", value: String(run.stepCount), tone: null },
523 ...(run.costUsd != null ? [{ label: "Cost so far", value: usd(run.costUsd), tone: null }] : []),
524 ],
525 open: run.number != null ? `/${run.repo.namespace}/${run.repo.name}/pull/${run.number}` : undefined,
526 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API527 }
528
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms529 const needRows: NeedRow[] = rankNeeds(needs).map((need) => {
530 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 step531 const reason = withConfidence(reasonFor(need), extra.confidence);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms532 return {
533 key: need.key,
534 reason,
535 repo: extra.repo ?? null,
536 ref: extra.ref ?? null,
537 title: need.title,
538 ask: need.detail,
539 by: extra.by ?? null,
540 for: extra.for ?? null,
541 at: need.at,
542 to: need.to,
543 open: extra.open ?? need.to,
544 facts: extra.facts ?? [],
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step545 why: whyFor(reason, need, extra.confidence),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms546 quick: extra.quick ?? null,
547 link: extra.link ?? null,
548 };
549 });
550
551 // --- Waiting on agents ----------------------------------------------------
552 const needKeys = new Set(
553 needRows.flatMap((row) => (row.repo && row.ref ? [needPathKey(row.repo, Number(row.ref.slice(1)))] : [])),
554 );
555 const waiting = waitingRows({
556 active: activeList.filter(({ repo }) => inWorkspace(repo)).map(({ pull, lifecycle, repo }) => ({ pull, lifecycle, repo: pathOf(repo) })),
557 live: liveRuns,
558 drafts: openPulls.filter(({ pull }) => pull.status === "draft").map(({ pull, repo }) => ({ ...pull, repo: pathOf(repo) })),
559 needKeys,
560 });
561
562 // --- Landed ---------------------------------------------------------------
563 const merged: Merged[] = (perRepo ?? []).flatMap(({ repo, closed }) =>
564 (closed ?? []).flatMap((pull) =>
565 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 bar566 ? [
567 {
568 repo: pathOf(repo),
569 number: pull.number,
570 title: pull.title,
571 agent: pull.agent,
572 // Who wrote it, not who merged it: a person's change that g1t
573 // auto-merged is still theirs.
574 authoredByAgent: pull.author.kind === "agent" || isAgent(pull.author.username),
575 mergedBy: pull.mergedBy,
576 mergedAt: pull.mergedAt,
577 files: pull.files,
578 },
579 ]
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms580 : [],
581 ),
582 );
583 const twoWeeksAgo = now - 14 * TIME.DAY;
584 const complete = perRepo != null && perRepo.every(({ closed }) => closed != null && reachesBack(closed, twoWeeksAgo, PULL_PAGE));
Mission control counts people's direct pushes to the default branch585 // A person's commits pushed straight to the default branch are their own
586 // changes too, on the day they landed.
587 const direct = (perRepo ?? []).flatMap(({ direct }) =>
588 direct.map((commit) => ({ mergedAt: commit.at, mergedBy: null, authoredByAgent: false })),
589 );
590 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 200ms591 const landed = landedToday(merged, now, tz);
592
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API593 // --- Activity -------------------------------------------------------------
594 const items: ActivityItem[] = [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms595 const titles: Record<string, string> = {};
596 const titleKey = (repo: RepoPath, number: number) => `${repo.namespace}/${repo.name}#${number}`.toLowerCase();
597 for (const { repo, pulls, closed, events } of perRepo ?? []) {
598 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 API599 for (const event of events ?? []) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms600 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 API601 const item = eventItem(event, pathOf(repo));
602 if (item) items.push(item);
603 }
604 }
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md605 // Production deploys come from the log too (deployment_status.created),
606 // 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 API607 for (const memory of okOr(memories ?? null)?.workspace ?? []) {
608 const repo = memory.repo ?? memory.source.repo;
609 if (!repo || !slug) continue;
610 items.push({
611 id: `memory:${memory.id}`,
612 at: Date.parse(memory.createdAt),
613 repo,
614 actor: memory.createdBy,
615 verb: "learned",
616 number: null,
617 text: memory.text.length > 90 ? `${memory.text.slice(0, 88)}…` : memory.text,
618 to: `/${slug}/-/memory`,
619 });
620 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit621 // The log names people by account id: their usernames, in one lookup.
622 const ids = actorIds(items.map((item) => item.actor));
623 const names = ids.length > 0 ? await soft("usernames", identity.usernames(ids)) : {};
624 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 200ms625 // Only the titles the feed names travel to the page.
626 const shownTitles: Record<string, string> = {};
627 for (const group of groups) {
628 for (const part of group.parts) {
629 for (const number of part.numbers) {
630 const key = titleKey(group.repo, number);
631 if (titles[key]) shownTitles[key] = titles[key];
632 }
633 }
634 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API635
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms636 // --- The strip ------------------------------------------------------------
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API637 const usageOk = okOr(usage ?? null);
638 const costPoints = usageOk
639 ? usageOk.byDay.map((slice) => ({ at: Date.parse(`${slice.key.split("/")[0]}T12:00:00Z`), value: slice.micros / 1_000_000 }))
640 : 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 200ms641 const weekCost = dailyBuckets(costPoints, 7, now).reduce((sum, v) => sum + v, 0);
642 const month = dayKey(now, tz).slice(0, 7);
643 const projectCount = projectsOk?.length ?? (perRepo != null ? perRepo.length : null);
644 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 API645
646 const models_ = models ?? null;
647 times.total = Date.now() - started;
Agents as a team: lifecycle, merge queue, billing and a new shell648 const serverTiming = Object.entries(times)
649 .map(([name, ms]) => `${name};dur=${ms}`)
650 .join(", ");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API651 return data(
652 {
653 signedIn: true as const,
654 viewer,
655 name: profile?.name?.trim() || username,
656 greeting: greetingFor(hourIn(now, tz)),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms657 date: dateLine(now, tz),
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar658 summary: summaryLine({
659 total: week.total,
660 byAgents: week.byAgents,
661 agentChanges: week.agentChanges,
662 people: week.people,
663 live: liveRuns.length,
664 needs: needRows.length,
665 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms666 workspace: slug,
667 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 API668 canRunAgents: models_ == null || models_.hosted || models_.own != null,
669 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 200ms670 handedOff: activeList.length > 0 || runList.length > 0 || merged.length > 0,
671 needs: needRows,
672 waiting,
673 landed,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API674 liveTotal: liveRuns.length,
675 runsLoaded: runs != null && runs.ok,
676 perRepoLoaded: perRepo != null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms677 stats: {
678 projects: projectCount,
679 projectsThisMonth,
680 agentHours: agentHours(runList, weekAgo, now),
681 weekCost,
682 },
683 week,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix684 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 API685 groups,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms686 titles: shownTitles,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all687 inboxNeeds: needsYou(unread?.items ?? [], INBOX_SHOWN),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms688 // A run that is going makes the page worth refreshing on its own.
689 changing:
690 liveRuns.length > 0 ||
691 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 API692 },
693 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms694 headers: { "Server-Timing": serverTiming },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API695 },
696 );
Agents as a team: lifecycle, merge queue, billing and a new shell697}
Get started on mission control698
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look699export type Loaded = Extract<Route.ComponentProps["loaderData"], { signedIn: true }>;
Get started on mission control700
Fast pages, required checks on the branch, self-hosted runners, honest incidents701/**
702 * Mission control, loaded only for someone signed in (components/mission-control.tsx).
703 * The app shell starts loading it once the browser is idle, so the
704 * skeleton below is rarely seen.
705 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look706const MissionControl = lazy(() => import("../components/mission-control"));
Agents as a team: lifecycle, merge queue, billing and a new shell707
Usage, Billing settings and prepaid AI credit; fixes from the UX audit708/**
709 * Mission control's outline while its code arrives, piece for piece as
710 * components/mission-control.tsx lays it out: the greeting, the Agent box,
711 * the actions under it, the stats, then the work list beside the week. Each
712 * piece has the padding and line heights of the real one, so nothing moves
713 * when it arrives.
714 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents715function MissionControlSkeleton() {
716 return (
717 <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'718 <span role="status" className="sr-only">
719 Loading…
720 </span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit721 <header>
722 <SkeletonLine className="w-72 max-w-full text-[1.75rem] leading-tight sm:text-[2rem]" barClassName="h-8" />
723 {/* The date and the week's summary: one line wide, three on a phone. */}
724 <div className="mt-1.5 text-sm">
725 <SkeletonLine className="w-[36rem] max-w-full" barClassName="h-3.5" />
726 <SkeletonLine className="w-full sm:hidden" barClassName="h-3.5" />
727 <SkeletonLine className="w-2/3 sm:hidden" barClassName="h-3.5" />
Merge branch 'worktree-agent-aca4790ea223aa235'728 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents729 </header>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit730 {/* The Agent box (components/ask-composer.tsx), then the actions under it. */}
731 <div className="space-y-3">
732 <div className="rounded-xl border border-line bg-surface">
733 <SkeletonLine className="mx-4 mt-3 w-36 text-sm" />
734 <div className="px-4 pt-3.5 pb-2 text-sm">
735 <SkeletonLine className="w-[30rem] max-w-full" />
736 <span className="block h-[1lh]" />
737 </div>
738 <div className="flex flex-wrap items-center gap-2 px-3 pb-3">
739 <Skeleton className="h-8 w-[4.5rem] rounded-md" />
740 <Skeleton className="h-8 w-[9.5rem] rounded-md" />
741 <Skeleton className="size-8 rounded-md" />
742 <Skeleton className="size-8 rounded-md" />
743 <Skeleton className="ml-auto size-8 rounded-md" />
744 </div>
745 </div>
746 <div className="flex flex-wrap items-center gap-2">
747 <Skeleton className="h-[38px] w-60 rounded-md" />
748 <Skeleton className="h-[38px] w-28 rounded-md" />
749 <Skeleton className="h-[38px] w-44 rounded-md" />
750 </div>
751 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents752 <div className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5">
753 {Array.from({ length: 5 }, (_, index) => (
Merge branch 'worktree-agent-aca4790ea223aa235'754 <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 incidents755 ))}
756 </div>
Merge branch 'worktree-agent-aca4790ea223aa235'757 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
758 <div className="self-start overflow-hidden rounded-xl border border-line bg-surface">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit759 <div className="flex items-center gap-4 border-b border-line px-4 py-3 text-sm sm:px-5">
760 <SkeletonLine className="w-24" />
761 <SkeletonLine className="w-32" />
762 <SkeletonLine className="hidden w-28 sm:flex" />
763 </div>
764 <div className="divide-y divide-line">
765 {Array.from({ length: 5 }, (_, index) => (
766 <div key={index}>
767 <div className="flex items-center gap-3 px-4 py-3 sm:px-5">
768 <Skeleton className="size-8 shrink-0 rounded-md" />
769 <span className="min-w-0 grow">
770 <SkeletonLine className="text-sm" width={`${70 - ((index * 17) % 30)}%`} />
771 <SkeletonLine className="mt-0.5 text-xs" width={`${50 - ((index * 11) % 20)}%`} />
772 </span>
773 <Skeleton className="hidden h-4 w-24 shrink-0 sm:block" />
774 </div>
775 {/* The first row stands open, as the real list's does. */}
776 {index === 0 && (
777 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
778 <Skeleton className="h-[17rem] rounded-xl" />
779 <Skeleton className="mt-3 h-8 w-44 rounded-md" />
780 </div>
781 )}
782 </div>
783 ))}
Merge branch 'worktree-agent-aca4790ea223aa235'784 </div>
785 </div>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit786 <div className="space-y-6">
787 <SkeletonCard lines={0} className="h-80 p-5" />
788 <SkeletonCard lines={6} className="p-5" />
789 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents790 </div>
791 </main>
792 );
793}
794
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step795export default function Home({ loaderData, actionData }: Route.ComponentProps) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look796 if (!loaderData.signedIn) return <Landing />;
API and MCP server, Rust identity service, registration, site redesign797 return (
Fast pages, required checks on the branch, self-hosted runners, honest incidents798 <Suspense fallback={<MissionControlSkeleton />}>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step799 <MissionControl loaderData={loaderData} delegated={actionData ?? null} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look800 </Suspense>
Initial g1t: services, event bus, intents and attempts801 );
802}

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