Skip to content
813 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,
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails60 historyCovers,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms61 who,
62 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step63 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms64} from "../lib/mission-control";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step65import { 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 all66import { needsYou } from "../lib/inbox";
API and MCP server, Rust identity service, registration, site redesign67import { Landing } from "../components/landing";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit68import { Skeleton, SkeletonCard, SkeletonLine, SkeletonStat } from "../components/ui/skeleton";
API and MCP server, Rust identity service, registration, site redesign69import {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API70 agents,
71 billing,
72 deployments,
73 events as eventLog,
74 identity,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all75 inbox,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API76 projects as projectsApi,
77 repos as reposApi,
78 work,
79} from "../lib/services.server";
Runners are owner-only: the page, its actions, the Settings entry and the links to it80import { assertSameOrigin, getViewer, requireUser, roleIn } from "../lib/session.server";
Fast pages, required checks on the branch, self-hosted runners, honest incidents81import { runners } from "../lib/runners.server";
82import { readableRepos } from "../lib/access.server";
Initial g1t: services, event bus, intents and attempts83
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look84/** 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 API85const TZ_COOKIE = "g1t_tz";
86/** The most projects whose pull requests and events are read for the page. */
87const MAX_PROJECTS = 10;
88const EVENTS_PER_PROJECT = 80;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms89/** How many pull requests work lists at once (`LIST_PAGE` in services/work). */
90const PULL_PAGE = 100;
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all91/** Unread inbox items read for the Needs you card, and how many it shows. */
92const INBOX_READ = 50;
93const INBOX_SHOWN = 4;
A free allowance on g1t's models, so anyone can try its agents94
Social cards for every page: og.g1t.sh95export function meta(args: Route.MetaArgs) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit96 // Someone signed in is on mission control; visitors get the landing page's title.
97 if (args.loaderData?.signedIn) return page(args, { title: "Mission control · g1t" });
Social cards for every page: og.g1t.sh98 return page(args, {
Chat and workspace agents: channels, DMs and named agents you talk to99 title: "g1t · Your team and its agents, working in one place",
Social cards for every page: og.g1t.sh100 description:
Chat and workspace agents: channels, DMs and named agents you talk to101 "Chat with your team and your agents in channels and DMs. Agents are teammates with a role, a personality and a budget, and their code lands through pull requests, checks and a merge queue. Chat is free on every plan, never per seat.",
Social cards for every page: og.g1t.sh102 });
Initial g1t: services, event bus, intents and attempts103}
104
Agents as a team: lifecycle, merge queue, billing and a new shell105export function headers({ loaderHeaders }: Route.HeadersArgs) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API106 const out = new Headers();
107 const timing = loaderHeaders.get("Server-Timing");
108 if (timing) out.set("Server-Timing", timing);
109 const cookie = loaderHeaders.get("Set-Cookie");
110 if (cookie) out.set("Set-Cookie", cookie);
111 return out;
Agents as a team: lifecycle, merge queue, billing and a new shell112}
113
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms114/**
115 * 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 all116 * everything is already loaded. Marking an inbox item read or done changes
117 * nothing else here: the card steps out by itself, and the bell's count
118 * comes with the shell (root.tsx). A refresh, a form, or anything else
119 * loads again as usual.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms120 */
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all121export 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 200ms122 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 all123 if (formAction === "/inbox") return false;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms124 return defaultShouldRevalidate;
125}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API126
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms127/** 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 step128type Extra = Partial<Pick<NeedRow, "repo" | "ref" | "by" | "for" | "facts" | "quick" | "link" | "open">> & {
129 /** How sure g1t is of the agent's change, for a pull request. */
130 confidence?: Confidence | null;
131};
132
133/** What the composer said back, when the issue opened but the agent did not start, or nothing opened. */
134export 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 API135
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step136/**
137 * "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-agent138 * 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 step139 * start, the issue is still open, and the composer says why and where to
140 * fix it.
141 */
142export async function action({ request, context }: Route.ActionArgs) {
143 assertSameOrigin(request);
144 const user = requireUser(context, request);
145 const form = await request.formData();
146 if (form.get("intent") !== "delegate") return data<DelegateResult>({ error: "Nothing to do.", notStarted: null }, { status: 400 });
147 const projects = await reposApi.list(user, { memberOnly: true }).catch(() => []);
148 const repo = chosenRepo(form.get("repo"), projects.map((repo) => ({ namespace: repo.namespace, name: repo.name })));
149 if (!repo) return data<DelegateResult>({ error: "Choose one of your projects.", notStarted: null }, { status: 400 });
150 const result = await env.RUNNER.delegate(user, repo, delegateForm(form));
151 if (!result.ok) return data<DelegateResult>({ error: result.error.message, notStarted: null }, { status: 400 });
152 const { issue, agent } = result.value;
153 const refused = notStarted(agent, repo, issue.number);
154 if (!refused) throw redirect(issuePath(repo, issue.number));
155 return data<DelegateResult>({ error: null, notStarted: refused });
156}
157
Merge branch 'main' into actions-toolkit-oidc-artifacts158/**
159 * Push events read per project, a page at a time, back two weeks. Pushes
160 * to every branch are in the log, so a fixed count (it was 40) lost a busy
161 * week's earlier days to agents' branches.
162 */
163const PUSH_PAGE = 200;
164const PUSH_PAGES = 5;
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails165/** The first page is smaller: most projects push far less in two weeks. */
166const FIRST_PUSH_PAGE = 50;
167/**
168 * Commits of the default branch read to place each push's commits: a
169 * short read first, which covers most projects' fortnight, and the longer
170 * one only when it does not reach the oldest push.
171 */
172const HISTORY_FIRST = 200;
Merge branch 'main' into actions-toolkit-oidc-artifacts173const HISTORY_READ = 1000;
Mission control counts people's direct pushes to the default branch174
175/**
176 * The commits people pushed straight to a project's default branch in the
177 * last two weeks, each with when its push landed. Merges and agents'
178 * commits are left out: pull requests count those.
179 */
180async function directCommits(repo: Repo, viewer: Viewer): Promise<{ hash: string; at: string }[]> {
181 const since = Date.now() - 14 * TIME.DAY;
Merge branch 'main' into actions-toolkit-oidc-artifacts182 const pushes: G1tEvent<"git.push">[] = [];
183 let before: string | undefined;
184 for (let page = 0; page < PUSH_PAGES; page++) {
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails185 const limit = page === 0 ? FIRST_PUSH_PAGE : PUSH_PAGE;
186 const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit, ...(before ? { before } : {}) });
Merge branch 'main' into actions-toolkit-oidc-artifacts187 for (const event of batch) {
188 if (event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since) pushes.push(event as G1tEvent<"git.push">);
189 }
190 const oldest = batch.at(-1);
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails191 if (batch.length < limit || !oldest || Date.parse(oldest.time) < since) break;
Merge branch 'main' into actions-toolkit-oidc-artifacts192 before = oldest.id;
193 }
194 if (pushes.length === 0) return [];
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails195 // A read of the branch from the newest push back; each push brought
Merge branch 'main' into actions-toolkit-oidc-artifacts196 // what lies between its `after` and its `before` in that history.
Mission control counts people's direct pushes to the default branch197 const path = { namespace: repo.namespace, name: repo.name };
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails198 const read = (limit: number) => reposApi.log(path, viewer, pushes[0].data.after, limit).catch(() => null);
199 let history = await read(HISTORY_FIRST);
200 if (history?.ok && !historyCovers(history.value, pushes, HISTORY_FIRST)) history = await read(HISTORY_READ);
Merge branch 'main' into actions-toolkit-oidc-artifacts201 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)202 // g1t's own commits are told by their author address, never by name.
203 return placePushes(history.value, pushes, (commit) => G1T_COMMIT_EMAILS.has(normalizeEmail(commit.author.email)));
Mission control counts people's direct pushes to the default branch204}
205
Merge branch 'main' into actions-toolkit-oidc-artifacts206
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API207export async function loader({ context, request }: Route.LoaderArgs) {
Initial g1t: services, event bus, intents and attempts208 const viewer = getViewer(context);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API209 if (!viewer) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look210 // The signed-out home page is about signing up; it lists no repositories.
211 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 API212 }
213
214 const now = Date.now();
215 const weekAgo = now - 7 * TIME.DAY;
Agents as a team: lifecycle, merge queue, billing and a new shell216 const started = Date.now();
217 const times: Record<string, number> = {};
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API218 // Each call is timed, and one that fails leaves its section empty rather
219 // than taking the page down.
220 const soft = <T,>(name: string, promise: Promise<T>): Promise<T | null> =>
221 promise.then(
222 (value) => ((times[name] = Date.now() - started), value),
223 (error) => {
224 console.warn(`home: ${name} failed`, error);
225 times[name] = Date.now() - started;
226 return null;
227 },
228 );
229 const okOr = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null);
230
231 const cookies = request.headers.get("cookie");
232 const tz = readCookie(cookies, TZ_COOKIE);
233 const memberships = viewer.workspaces ?? [];
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put234 // The workspace you chose, as the sidebar shows it (lib/workspace-choice.ts).
235 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 API236 const username = viewer.username;
237
238 const reposP = soft("repos", reposApi.list(viewer, { memberOnly: true }));
Fast pages, required checks on the branch, self-hosted runners, honest incidents239 // Workflow jobs stuck waiting for a self-hosted runner that is not there.
240 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 200ms241 // The chosen workspace's projects: their open and recently merged pull
Fast pages, required checks on the branch, self-hosted runners, honest incidents242 // requests, all in one call to work, and their recent events, read as
243 // soon as the projects are known.
244 const perRepoP = reposP.then(async (repos) => {
245 const chosen = (repos ?? [])
246 .filter((repo) => slug != null && repo.namespace.toLowerCase() === slug.toLowerCase())
247 .slice(0, MAX_PROJECTS);
Mission control counts people's direct pushes to the default branch248 const [batch, logs, pushes] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents249 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.md250 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 branch251 // People's pushes straight to the default branch, which no pull
252 // request counts: the commits each brought, for the week.
253 Promise.all(chosen.map((repo) => directCommits(repo, viewer).catch(() => []))),
Fast pages, required checks on the branch, self-hosted runners, honest incidents254 ]);
255 const byId = new Map(batch.map((entry) => [entry.repoId, entry]));
256 return Promise.all(
257 chosen.map(async (repo, index) => {
258 const found = byId.get(repo.id);
Mission control counts people's direct pushes to the default branch259 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 incidents260 // A fork, which the batch leaves out: asked on its own.
261 const path = { namespace: repo.namespace, name: repo.name };
262 const [pulls, closed] = await Promise.all([
263 work.listPulls(path, viewer, "open").catch(() => null),
264 work.listPulls(path, viewer, "closed").catch(() => null),
265 ]);
266 return {
267 repo,
268 pulls: pulls?.ok ? pulls.value.slice(0, 60) : null,
269 closed: closed?.ok ? closed.value : null,
270 events: logs[index],
Mission control counts people's direct pushes to the default branch271 direct: pushes[index] ?? [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents272 };
273 }),
274 );
275 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API276
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all277 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 200ms278 reposP,
279 soft("projects", perRepoP),
280 soft("pulls", work.listActivePulls(viewer)),
281 slug ? soft("models", env.RUNNER.modelAccess(slug)) : null,
282 soft("profile", identity.profile(username)),
283 slug ? soft("runs", agents.listRuns(viewer, { workspace: slug, limit: 150 })) : null,
284 slug ? soft("deploys", deployments.overview(slug, viewer)) : null,
285 slug ? soft("usage", billing.usage(slug, viewer, new Date(weekAgo - TIME.DAY).toISOString())) : null,
286 slug ? soft("projectList", projectsApi.list(slug, viewer)) : null,
287 slug ? soft("memories", agents.listMemories(viewer, slug, null)) : null,
288 // Repositories someone has invited the viewer to.
289 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 mix290 // Model tokens over the last six weeks: the workspace's, and yours.
291 slug ? soft("tokens", billing.tokenUsage(slug, viewer)) : null,
292 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 all293 // What in their inbox is unread: an agent waiting on them, or a failure, goes on the Needs you card.
294 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 200ms295 ]);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API296
297 const repoList = repos ?? [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms298 // Each pull request is shown under its repository. Most are in the
299 // 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 API300 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 200ms301 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 incidents302 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 API303 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 200ms304 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 API305
306 const activeList = (active ?? []).flatMap((item) => {
307 const repo = known.get(item.pull.repoId);
308 return repo ? [{ ...item, repo }] : [];
309 });
310 const runList = okOr(runs ?? null) ?? [];
311 const liveRuns = runList.filter((run) => isActiveRun(run.status));
312 const overviewList = okOr(overview ?? null);
313 const projectsOk = okOr(projectList ?? null);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms314 const runsOn = (repo: RepoPath, number: number) =>
315 runList.filter(
316 (run) => run.number === number && `${run.repo.namespace}/${run.repo.name}`.toLowerCase() === `${repo.namespace}/${repo.name}`.toLowerCase(),
317 );
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API318
319 const lower = username.toLowerCase();
320 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 rights321 // 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 API322 const reviewRequested = openPulls.filter(
323 ({ pull }) =>
324 pull.status === "open" &&
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights325 workOwner(pull).username.toLowerCase() !== lower &&
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API326 pull.reviewers.some((name) => name.toLowerCase() === lower),
327 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms328
329 // --- Needs you ------------------------------------------------------------
330 const needs: Need[] = [];
331 const extras = new Map<string, Extra>();
332 /** What every pull request's row shares: where it is, who is on it, what is known. */
333 const pullExtra = (pull: Pull, repo: Repo, lifecycle: Lifecycle | null): Extra => {
334 const base = `/${repo.namespace}/${repo.name}`;
335 const agentWork = isAgent(pull.agent);
336 return {
337 repo: pathOf(repo),
338 ref: `#${pull.number}`,
339 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 rights340 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 step341 facts: pullFacts({
342 checkStatus: pull.checkStatus,
343 files: pull.files,
344 lifecycle,
345 runs: runsOn(repo, pull.number),
346 confidence: pull.confidence,
347 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms348 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 step349 confidence: pull.confidence ?? null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms350 };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API351 };
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms352 const pullAction = (repo: Repo, pull: Pull, quick: Omit<QuickAction, "to">): QuickAction => ({
353 ...quick,
354 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 API355 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms356 const approve = (repo: Repo, pull: Pull) =>
357 pullAction(repo, pull, {
358 label: isAgent(pull.agent) ? "Approve the agent's change" : "Approve the change",
359 fields: { action: "comment", verdict: "approve", body: "" },
360 done: "Approved",
361 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API362
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look363 for (const invitation of invitations ?? []) {
364 if (invitation.status !== "pending") continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms365 const key = `invitation:${invitation.id}`;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look366 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms367 key,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look368 kind: "invitation",
369 title: `${invitation.invited_by ?? "Someone"} invited you to ${invitation.repo}`,
370 detail: `With the ${REPO_ROLE_LABELS[invitation.role]} role. The invitation expires ${new Date(invitation.expires_at).toISOString().slice(0, 10)}.`,
371 to: `/${invitation.repo}/invitations`,
372 action: "Respond",
373 at: Date.parse(invitation.created_at),
374 where: invitation.repo,
375 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms376 const [namespace, name] = invitation.repo.split("/");
377 extras.set(key, {
378 repo: namespace && name ? { namespace, name } : null,
379 by: who(invitation.invited_by),
380 facts: [
381 { label: "Role", value: REPO_ROLE_LABELS[invitation.role], tone: null },
382 { label: "Expires", value: new Date(invitation.expires_at).toISOString().slice(0, 10), tone: null },
383 ],
384 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look385 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API386 for (const entry of overviewList ?? []) {
387 const latest = entry.latest;
388 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 200ms389 const key = `deploy:${entry.slug}`;
390 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 API391 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms392 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API393 kind: "deploy",
394 title: `Production build of ${entry.slug} failed`,
395 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 200ms396 to,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API397 action: "See the build",
398 at: Date.parse(latest.finishedAt ?? latest.createdAt),
399 where: `${slug}/${entry.slug}`,
400 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms401 const facts: Fact[] = [
402 { label: "Commit", value: latest.commit.slice(0, 7), tone: null },
403 {
404 label: "Production",
405 value: entry.production ? "Serving the build before" : "Not live yet",
406 tone: entry.production ? "good" : "warn",
407 },
408 ];
409 extras.set(key, {
410 repo: { namespace: slug, name: entry.slug },
411 by: who(latest.createdBy),
412 facts,
413 link: { label: "Deployment settings", to: `/${slug}/${entry.slug}/settings/deployments` },
414 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API415 }
416 }
417 for (const { pull, lifecycle, repo } of activeList) {
418 const where = `${repo.namespace}/${repo.name}#${pull.number}`;
419 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 200ms420 const key = `pull:${pull.id}`;
421 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 step422 // Held for its low confidence: the ask says so in a sentence of its own.
423 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 API424 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 step425 const held = lowConfidence != null && /confidence in this change is low/i.test(lifecycle.detail);
426 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 200ms427 const need: Need = {
428 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API429 kind: conflict ? "conflict" : "stalled",
430 title: pull.title,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step431 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 API432 to,
433 action: conflict ? "Resolve" : "Decide",
434 at: Date.parse(pull.updatedAt),
435 where,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms436 };
437 needs.push(need);
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step438 const reason = held ? "low_confidence" : reasonFor(need);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms439 extras.set(key, {
440 ...extra,
441 quick:
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step442 reason === "needs_review" || reason === "low_confidence"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms443 ? approve(repo, pull)
Fast pages, required checks on the branch, self-hosted runners, honest incidents444 : /required checks? .*still fails?\b/i.test(lifecycle.detail)
445 ? 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 200ms446 : null,
447 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 API448 });
449 } 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 step450 needs.push({
451 key,
452 kind: "ready",
453 title: pull.title,
Fast pages, required checks on the branch, self-hosted runners, honest incidents454 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 step455 to,
456 action: "Merge",
457 at: Date.parse(pull.updatedAt),
458 where,
459 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms460 extras.set(key, {
461 ...extra,
462 quick: pullAction(repo, pull, {
463 label: isAgent(pull.agent) ? "Merge the agent's change" : "Merge it",
464 fields: { action: "merge" },
465 done: "Merging",
466 }),
467 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API468 } else if (!lifecycle && pull.status === "open" && pull.checkStatus === "failed") {
Fast pages, required checks on the branch, self-hosted runners, honest incidents469 // Taken out of the merge queue: its change failed combined with what was ahead.
470 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 });
471 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 API472 }
473 }
474 for (const { pull, repo } of reviewRequested) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms475 const key = `review:${pull.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API476 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms477 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API478 kind: "review",
479 title: pull.title,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights480 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 API481 to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`,
482 action: "Review",
483 at: Date.parse(pull.updatedAt),
484 where: `${repo.namespace}/${repo.name}#${pull.number}`,
485 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms486 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 API487 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents488 for (const job of (await stuckJobsP) ?? []) {
489 const key = `runner:${job.id}`;
490 const minutes = Math.max(10, Math.round((now - Date.parse(job.queuedAt)) / 60_000));
491 const [namespace, name] = job.repo.split("/");
492 needs.push({
493 key,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit494 kind: "runner",
Fast pages, required checks on the branch, self-hosted runners, honest incidents495 title: `${job.name} is waiting for a self-hosted runner`,
496 detail: `No runner with labels ${job.labels} is online. Start one, or change the job's runs-on.`,
497 to: `/${job.repo}/actions/runs/${job.runId}`,
498 action: "Look",
499 at: Date.parse(job.queuedAt),
500 where: job.repo,
501 });
502 extras.set(key, {
503 repo: namespace && name ? { namespace, name } : undefined,
504 facts: [
505 { label: "Labels", value: job.labels, tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit506 { label: "Waiting", value: waitedFor(minutes), tone: "warn" },
Fast pages, required checks on the branch, self-hosted runners, honest incidents507 ],
Runners are owner-only: the page, its actions, the Settings entry and the links to it508 // Runners are the owners' to see to.
509 ...(namespace && roleIn(viewer, namespace) === "owner" ? { link: { label: "Runners", to: `/${namespace}/-/runners` } } : {}),
Fast pages, required checks on the branch, self-hosted runners, honest incidents510 });
511 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API512 for (const run of liveRuns) {
513 const minutes = stuckMinutes(run, now);
514 if (minutes == null) continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms515 const key = `run:${run.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API516 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms517 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API518 kind: "stuck",
519 title: run.title ?? `${run.agent}'s run`,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit520 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 API521 to: `/${run.repo.namespace}/${run.repo.name}/agents/runs/${run.id}`,
522 action: "Look",
523 at: Date.parse(run.updatedAt),
524 where: `${run.repo.namespace}/${run.repo.name}${run.number != null ? `#${run.number}` : ""}`,
525 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms526 extras.set(key, {
527 repo: run.repo,
528 ref: run.number != null ? `#${run.number}` : null,
529 by: who(run.agent),
530 facts: [
531 { label: "Run", value: RUN_LABEL[run.kind], tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit532 { 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 200ms533 { label: "Steps so far", value: String(run.stepCount), tone: null },
534 ...(run.costUsd != null ? [{ label: "Cost so far", value: usd(run.costUsd), tone: null }] : []),
535 ],
536 open: run.number != null ? `/${run.repo.namespace}/${run.repo.name}/pull/${run.number}` : undefined,
537 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API538 }
539
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms540 const needRows: NeedRow[] = rankNeeds(needs).map((need) => {
541 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 step542 const reason = withConfidence(reasonFor(need), extra.confidence);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms543 return {
544 key: need.key,
545 reason,
546 repo: extra.repo ?? null,
547 ref: extra.ref ?? null,
548 title: need.title,
549 ask: need.detail,
550 by: extra.by ?? null,
551 for: extra.for ?? null,
552 at: need.at,
553 to: need.to,
554 open: extra.open ?? need.to,
555 facts: extra.facts ?? [],
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step556 why: whyFor(reason, need, extra.confidence),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms557 quick: extra.quick ?? null,
558 link: extra.link ?? null,
559 };
560 });
561
562 // --- Waiting on agents ----------------------------------------------------
563 const needKeys = new Set(
564 needRows.flatMap((row) => (row.repo && row.ref ? [needPathKey(row.repo, Number(row.ref.slice(1)))] : [])),
565 );
566 const waiting = waitingRows({
567 active: activeList.filter(({ repo }) => inWorkspace(repo)).map(({ pull, lifecycle, repo }) => ({ pull, lifecycle, repo: pathOf(repo) })),
568 live: liveRuns,
569 drafts: openPulls.filter(({ pull }) => pull.status === "draft").map(({ pull, repo }) => ({ ...pull, repo: pathOf(repo) })),
570 needKeys,
571 });
572
573 // --- Landed ---------------------------------------------------------------
574 const merged: Merged[] = (perRepo ?? []).flatMap(({ repo, closed }) =>
575 (closed ?? []).flatMap((pull) =>
576 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 bar577 ? [
578 {
579 repo: pathOf(repo),
580 number: pull.number,
581 title: pull.title,
582 agent: pull.agent,
583 // Who wrote it, not who merged it: a person's change that g1t
584 // auto-merged is still theirs.
585 authoredByAgent: pull.author.kind === "agent" || isAgent(pull.author.username),
586 mergedBy: pull.mergedBy,
587 mergedAt: pull.mergedAt,
588 files: pull.files,
589 },
590 ]
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms591 : [],
592 ),
593 );
594 const twoWeeksAgo = now - 14 * TIME.DAY;
595 const complete = perRepo != null && perRepo.every(({ closed }) => closed != null && reachesBack(closed, twoWeeksAgo, PULL_PAGE));
Mission control counts people's direct pushes to the default branch596 // A person's commits pushed straight to the default branch are their own
597 // changes too, on the day they landed.
598 const direct = (perRepo ?? []).flatMap(({ direct }) =>
599 direct.map((commit) => ({ mergedAt: commit.at, mergedBy: null, authoredByAgent: false })),
600 );
601 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 200ms602 const landed = landedToday(merged, now, tz);
603
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API604 // --- Activity -------------------------------------------------------------
605 const items: ActivityItem[] = [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms606 const titles: Record<string, string> = {};
607 const titleKey = (repo: RepoPath, number: number) => `${repo.namespace}/${repo.name}#${number}`.toLowerCase();
608 for (const { repo, pulls, closed, events } of perRepo ?? []) {
609 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 API610 for (const event of events ?? []) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms611 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 API612 const item = eventItem(event, pathOf(repo));
613 if (item) items.push(item);
614 }
615 }
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md616 // Production deploys come from the log too (deployment_status.created),
617 // 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 API618 for (const memory of okOr(memories ?? null)?.workspace ?? []) {
619 const repo = memory.repo ?? memory.source.repo;
620 if (!repo || !slug) continue;
621 items.push({
622 id: `memory:${memory.id}`,
623 at: Date.parse(memory.createdAt),
624 repo,
625 actor: memory.createdBy,
626 verb: "learned",
627 number: null,
628 text: memory.text.length > 90 ? `${memory.text.slice(0, 88)}…` : memory.text,
629 to: `/${slug}/-/memory`,
630 });
631 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit632 // The log names people by account id: their usernames, in one lookup.
633 const ids = actorIds(items.map((item) => item.actor));
634 const names = ids.length > 0 ? await soft("usernames", identity.usernames(ids)) : {};
635 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 200ms636 // Only the titles the feed names travel to the page.
637 const shownTitles: Record<string, string> = {};
638 for (const group of groups) {
639 for (const part of group.parts) {
640 for (const number of part.numbers) {
641 const key = titleKey(group.repo, number);
642 if (titles[key]) shownTitles[key] = titles[key];
643 }
644 }
645 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API646
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms647 // --- The strip ------------------------------------------------------------
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API648 const usageOk = okOr(usage ?? null);
649 const costPoints = usageOk
650 ? usageOk.byDay.map((slice) => ({ at: Date.parse(`${slice.key.split("/")[0]}T12:00:00Z`), value: slice.micros / 1_000_000 }))
651 : 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 200ms652 const weekCost = dailyBuckets(costPoints, 7, now).reduce((sum, v) => sum + v, 0);
653 const month = dayKey(now, tz).slice(0, 7);
654 const projectCount = projectsOk?.length ?? (perRepo != null ? perRepo.length : null);
655 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 API656
657 const models_ = models ?? null;
658 times.total = Date.now() - started;
Agents as a team: lifecycle, merge queue, billing and a new shell659 const serverTiming = Object.entries(times)
660 .map(([name, ms]) => `${name};dur=${ms}`)
661 .join(", ");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API662 return data(
663 {
664 signedIn: true as const,
665 viewer,
666 name: profile?.name?.trim() || username,
667 greeting: greetingFor(hourIn(now, tz)),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms668 date: dateLine(now, tz),
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar669 summary: summaryLine({
670 total: week.total,
671 byAgents: week.byAgents,
672 agentChanges: week.agentChanges,
673 people: week.people,
674 live: liveRuns.length,
675 needs: needRows.length,
676 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms677 workspace: slug,
678 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 API679 canRunAgents: models_ == null || models_.hosted || models_.own != null,
680 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 200ms681 handedOff: activeList.length > 0 || runList.length > 0 || merged.length > 0,
682 needs: needRows,
683 waiting,
684 landed,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API685 liveTotal: liveRuns.length,
686 runsLoaded: runs != null && runs.ok,
687 perRepoLoaded: perRepo != null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms688 stats: {
689 projects: projectCount,
690 projectsThisMonth,
691 agentHours: agentHours(runList, weekAgo, now),
692 weekCost,
693 },
694 week,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix695 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 API696 groups,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms697 titles: shownTitles,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all698 inboxNeeds: needsYou(unread?.items ?? [], INBOX_SHOWN),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms699 // A run that is going makes the page worth refreshing on its own.
700 changing:
701 liveRuns.length > 0 ||
702 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 API703 },
704 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms705 headers: { "Server-Timing": serverTiming },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API706 },
707 );
Agents as a team: lifecycle, merge queue, billing and a new shell708}
Get started on mission control709
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look710export type Loaded = Extract<Route.ComponentProps["loaderData"], { signedIn: true }>;
Get started on mission control711
Fast pages, required checks on the branch, self-hosted runners, honest incidents712/**
713 * Mission control, loaded only for someone signed in (components/mission-control.tsx).
714 * The app shell starts loading it once the browser is idle, so the
715 * skeleton below is rarely seen.
716 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look717const MissionControl = lazy(() => import("../components/mission-control"));
Agents as a team: lifecycle, merge queue, billing and a new shell718
Usage, Billing settings and prepaid AI credit; fixes from the UX audit719/**
720 * Mission control's outline while its code arrives, piece for piece as
721 * components/mission-control.tsx lays it out: the greeting, the Agent box,
722 * the actions under it, the stats, then the work list beside the week. Each
723 * piece has the padding and line heights of the real one, so nothing moves
724 * when it arrives.
725 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents726function MissionControlSkeleton() {
727 return (
728 <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'729 <span role="status" className="sr-only">
730 Loading…
731 </span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit732 <header>
733 <SkeletonLine className="w-72 max-w-full text-[1.75rem] leading-tight sm:text-[2rem]" barClassName="h-8" />
734 {/* The date and the week's summary: one line wide, three on a phone. */}
735 <div className="mt-1.5 text-sm">
736 <SkeletonLine className="w-[36rem] max-w-full" barClassName="h-3.5" />
737 <SkeletonLine className="w-full sm:hidden" barClassName="h-3.5" />
738 <SkeletonLine className="w-2/3 sm:hidden" barClassName="h-3.5" />
Merge branch 'worktree-agent-aca4790ea223aa235'739 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents740 </header>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit741 {/* The Agent box (components/ask-composer.tsx), then the actions under it. */}
742 <div className="space-y-3">
743 <div className="rounded-xl border border-line bg-surface">
744 <SkeletonLine className="mx-4 mt-3 w-36 text-sm" />
745 <div className="px-4 pt-3.5 pb-2 text-sm">
746 <SkeletonLine className="w-[30rem] max-w-full" />
747 <span className="block h-[1lh]" />
748 </div>
749 <div className="flex flex-wrap items-center gap-2 px-3 pb-3">
750 <Skeleton className="h-8 w-[4.5rem] rounded-md" />
751 <Skeleton className="h-8 w-[9.5rem] rounded-md" />
752 <Skeleton className="size-8 rounded-md" />
753 <Skeleton className="size-8 rounded-md" />
754 <Skeleton className="ml-auto size-8 rounded-md" />
755 </div>
756 </div>
757 <div className="flex flex-wrap items-center gap-2">
758 <Skeleton className="h-[38px] w-60 rounded-md" />
759 <Skeleton className="h-[38px] w-28 rounded-md" />
760 <Skeleton className="h-[38px] w-44 rounded-md" />
761 </div>
762 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents763 <div className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5">
764 {Array.from({ length: 5 }, (_, index) => (
Merge branch 'worktree-agent-aca4790ea223aa235'765 <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 incidents766 ))}
767 </div>
Merge branch 'worktree-agent-aca4790ea223aa235'768 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
769 <div className="self-start overflow-hidden rounded-xl border border-line bg-surface">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit770 <div className="flex items-center gap-4 border-b border-line px-4 py-3 text-sm sm:px-5">
771 <SkeletonLine className="w-24" />
772 <SkeletonLine className="w-32" />
773 <SkeletonLine className="hidden w-28 sm:flex" />
774 </div>
775 <div className="divide-y divide-line">
776 {Array.from({ length: 5 }, (_, index) => (
777 <div key={index}>
778 <div className="flex items-center gap-3 px-4 py-3 sm:px-5">
779 <Skeleton className="size-8 shrink-0 rounded-md" />
780 <span className="min-w-0 grow">
781 <SkeletonLine className="text-sm" width={`${70 - ((index * 17) % 30)}%`} />
782 <SkeletonLine className="mt-0.5 text-xs" width={`${50 - ((index * 11) % 20)}%`} />
783 </span>
784 <Skeleton className="hidden h-4 w-24 shrink-0 sm:block" />
785 </div>
786 {/* The first row stands open, as the real list's does. */}
787 {index === 0 && (
788 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
789 <Skeleton className="h-[17rem] rounded-xl" />
790 <Skeleton className="mt-3 h-8 w-44 rounded-md" />
791 </div>
792 )}
793 </div>
794 ))}
Merge branch 'worktree-agent-aca4790ea223aa235'795 </div>
796 </div>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit797 <div className="space-y-6">
798 <SkeletonCard lines={0} className="h-80 p-5" />
799 <SkeletonCard lines={6} className="p-5" />
800 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents801 </div>
802 </main>
803 );
804}
805
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step806export default function Home({ loaderData, actionData }: Route.ComponentProps) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look807 if (!loaderData.signedIn) return <Landing />;
API and MCP server, Rust identity service, registration, site redesign808 return (
Fast pages, required checks on the branch, self-hosted runners, honest incidents809 <Suspense fallback={<MissionControlSkeleton />}>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step810 <MissionControl loaderData={loaderData} delegated={actionData ?? null} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look811 </Suspense>
Initial g1t: services, event bus, intents and attempts812 );
813}

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