Skip to content
816 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";
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)21import { NO_WORKSPACE_PATH, hasNoWorkspace } from "../lib/workspace-gate";
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API22import {
23 type ActivityItem,
24 type Need,
25 TIME,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit26 actorIds,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API27 agentHours,
28 dailyBuckets,
29 eventItem,
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md30 FEED_EVENT_TYPES,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API31 greetingFor,
32 groupActivity,
33 hourIn,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms34 isAgent,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit35 nameActor,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API36 rankNeeds,
37 readCookie,
38 stuckMinutes,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit39 waitedFor,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API40} 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)41import { 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 200ms42import {
43 type Fact,
44 type Merged,
Merge branch 'main' into actions-toolkit-oidc-artifacts45 placePushes,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms46 type NeedRow,
47 type QuickAction,
48 RUN_LABEL,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step49 confidenceAsk,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms50 dateLine,
51 dayKey,
52 landedToday,
53 needPathKey,
54 pullFacts,
55 reachesBack,
56 reasonFor,
57 summaryLine,
58 usd,
59 waitingRows,
60 weekOf,
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails61 historyCovers,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms62 who,
63 whyFor,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step64 withConfidence,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms65} from "../lib/mission-control";
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step66import { 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 all67import { needsYou } from "../lib/inbox";
API and MCP server, Rust identity service, registration, site redesign68import { Landing } from "../components/landing";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit69import { Skeleton, SkeletonCard, SkeletonLine, SkeletonStat } from "../components/ui/skeleton";
API and MCP server, Rust identity service, registration, site redesign70import {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API71 agents,
72 billing,
73 deployments,
74 events as eventLog,
75 identity,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all76 inbox,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API77 projects as projectsApi,
78 repos as reposApi,
79 work,
80} from "../lib/services.server";
Runners are owner-only: the page, its actions, the Settings entry and the links to it81import { assertSameOrigin, getViewer, requireUser, roleIn } from "../lib/session.server";
Fast pages, required checks on the branch, self-hosted runners, honest incidents82import { runners } from "../lib/runners.server";
83import { readableRepos } from "../lib/access.server";
Initial g1t: services, event bus, intents and attempts84
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look85/** 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 API86const TZ_COOKIE = "g1t_tz";
87/** The most projects whose pull requests and events are read for the page. */
88const MAX_PROJECTS = 10;
89const EVENTS_PER_PROJECT = 80;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms90/** How many pull requests work lists at once (`LIST_PAGE` in services/work). */
91const PULL_PAGE = 100;
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all92/** Unread inbox items read for the Needs you card, and how many it shows. */
93const INBOX_READ = 50;
94const INBOX_SHOWN = 4;
A free allowance on g1t's models, so anyone can try its agents95
Social cards for every page: og.g1t.sh96export function meta(args: Route.MetaArgs) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit97 // Someone signed in is on mission control; visitors get the landing page's title.
98 if (args.loaderData?.signedIn) return page(args, { title: "Mission control · g1t" });
Social cards for every page: og.g1t.sh99 return page(args, {
Chat and workspace agents: channels, DMs and named agents you talk to100 title: "g1t · Your team and its agents, working in one place",
Social cards for every page: og.g1t.sh101 description:
Chat and workspace agents: channels, DMs and named agents you talk to102 "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.sh103 });
Initial g1t: services, event bus, intents and attempts104}
105
Agents as a team: lifecycle, merge queue, billing and a new shell106export function headers({ loaderHeaders }: Route.HeadersArgs) {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API107 const out = new Headers();
108 const timing = loaderHeaders.get("Server-Timing");
109 if (timing) out.set("Server-Timing", timing);
110 const cookie = loaderHeaders.get("Set-Cookie");
111 if (cookie) out.set("Set-Cookie", cookie);
112 return out;
Agents as a team: lifecycle, merge queue, billing and a new shell113}
114
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms115/**
116 * 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 all117 * everything is already loaded. Marking an inbox item read or done changes
118 * nothing else here: the card steps out by itself, and the bell's count
119 * comes with the shell (root.tsx). A refresh, a form, or anything else
120 * loads again as usual.
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms121 */
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all122export 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 200ms123 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 all124 if (formAction === "/inbox") return false;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms125 return defaultShouldRevalidate;
126}
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API127
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms128/** 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 step129type Extra = Partial<Pick<NeedRow, "repo" | "ref" | "by" | "for" | "facts" | "quick" | "link" | "open">> & {
130 /** How sure g1t is of the agent's change, for a pull request. */
131 confidence?: Confidence | null;
132};
133
134/** What the composer said back, when the issue opened but the agent did not start, or nothing opened. */
135export 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 API136
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step137/**
138 * "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-agent139 * 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 step140 * start, the issue is still open, and the composer says why and where to
141 * fix it.
142 */
143export async function action({ request, context }: Route.ActionArgs) {
144 assertSameOrigin(request);
145 const user = requireUser(context, request);
146 const form = await request.formData();
147 if (form.get("intent") !== "delegate") return data<DelegateResult>({ error: "Nothing to do.", notStarted: null }, { status: 400 });
148 const projects = await reposApi.list(user, { memberOnly: true }).catch(() => []);
149 const repo = chosenRepo(form.get("repo"), projects.map((repo) => ({ namespace: repo.namespace, name: repo.name })));
150 if (!repo) return data<DelegateResult>({ error: "Choose one of your projects.", notStarted: null }, { status: 400 });
151 const result = await env.RUNNER.delegate(user, repo, delegateForm(form));
152 if (!result.ok) return data<DelegateResult>({ error: result.error.message, notStarted: null }, { status: 400 });
153 const { issue, agent } = result.value;
154 const refused = notStarted(agent, repo, issue.number);
155 if (!refused) throw redirect(issuePath(repo, issue.number));
156 return data<DelegateResult>({ error: null, notStarted: refused });
157}
158
Merge branch 'main' into actions-toolkit-oidc-artifacts159/**
160 * Push events read per project, a page at a time, back two weeks. Pushes
161 * to every branch are in the log, so a fixed count (it was 40) lost a busy
162 * week's earlier days to agents' branches.
163 */
164const PUSH_PAGE = 200;
165const PUSH_PAGES = 5;
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails166/** The first page is smaller: most projects push far less in two weeks. */
167const FIRST_PUSH_PAGE = 50;
168/**
169 * Commits of the default branch read to place each push's commits: a
170 * short read first, which covers most projects' fortnight, and the longer
171 * one only when it does not reach the oldest push.
172 */
173const HISTORY_FIRST = 200;
Merge branch 'main' into actions-toolkit-oidc-artifacts174const HISTORY_READ = 1000;
Mission control counts people's direct pushes to the default branch175
176/**
177 * The commits people pushed straight to a project's default branch in the
178 * last two weeks, each with when its push landed. Merges and agents'
179 * commits are left out: pull requests count those.
180 */
181async function directCommits(repo: Repo, viewer: Viewer): Promise<{ hash: string; at: string }[]> {
182 const since = Date.now() - 14 * TIME.DAY;
Merge branch 'main' into actions-toolkit-oidc-artifacts183 const pushes: G1tEvent<"git.push">[] = [];
184 let before: string | undefined;
185 for (let page = 0; page < PUSH_PAGES; page++) {
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails186 const limit = page === 0 ? FIRST_PUSH_PAGE : PUSH_PAGE;
187 const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit, ...(before ? { before } : {}) });
Merge branch 'main' into actions-toolkit-oidc-artifacts188 for (const event of batch) {
189 if (event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since) pushes.push(event as G1tEvent<"git.push">);
190 }
191 const oldest = batch.at(-1);
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails192 if (batch.length < limit || !oldest || Date.parse(oldest.time) < since) break;
Merge branch 'main' into actions-toolkit-oidc-artifacts193 before = oldest.id;
194 }
195 if (pushes.length === 0) return [];
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails196 // A read of the branch from the newest push back; each push brought
Merge branch 'main' into actions-toolkit-oidc-artifacts197 // what lies between its `after` and its `before` in that history.
Mission control counts people's direct pushes to the default branch198 const path = { namespace: repo.namespace, name: repo.name };
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails199 const read = (limit: number) => reposApi.log(path, viewer, pushes[0].data.after, limit).catch(() => null);
200 let history = await read(HISTORY_FIRST);
201 if (history?.ok && !historyCovers(history.value, pushes, HISTORY_FIRST)) history = await read(HISTORY_READ);
Merge branch 'main' into actions-toolkit-oidc-artifacts202 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)203 // g1t's own commits are told by their author address, never by name.
204 return placePushes(history.value, pushes, (commit) => G1T_COMMIT_EMAILS.has(normalizeEmail(commit.author.email)));
Mission control counts people's direct pushes to the default branch205}
206
Merge branch 'main' into actions-toolkit-oidc-artifacts207
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API208export async function loader({ context, request }: Route.LoaderArgs) {
Initial g1t: services, event bus, intents and attempts209 const viewer = getViewer(context);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API210 if (!viewer) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look211 // The signed-out home page is about signing up; it lists no repositories.
212 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 API213 }
Merge workspace invitations: nobody joins a workspace without saying yes, people are found by username, your own invites can bring someone in, and nobody is left without a workspace (identity 0040)214 // Mission control is about a workspace: never shown without one (lib/workspace-gate.ts).
215 if (hasNoWorkspace(viewer)) throw redirect(NO_WORKSPACE_PATH);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API216
217 const now = Date.now();
218 const weekAgo = now - 7 * TIME.DAY;
Agents as a team: lifecycle, merge queue, billing and a new shell219 const started = Date.now();
220 const times: Record<string, number> = {};
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API221 // Each call is timed, and one that fails leaves its section empty rather
222 // than taking the page down.
223 const soft = <T,>(name: string, promise: Promise<T>): Promise<T | null> =>
224 promise.then(
225 (value) => ((times[name] = Date.now() - started), value),
226 (error) => {
227 console.warn(`home: ${name} failed`, error);
228 times[name] = Date.now() - started;
229 return null;
230 },
231 );
232 const okOr = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null);
233
234 const cookies = request.headers.get("cookie");
235 const tz = readCookie(cookies, TZ_COOKIE);
236 const memberships = viewer.workspaces ?? [];
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put237 // The workspace you chose, as the sidebar shows it (lib/workspace-choice.ts).
238 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 API239 const username = viewer.username;
240
241 const reposP = soft("repos", reposApi.list(viewer, { memberOnly: true }));
Fast pages, required checks on the branch, self-hosted runners, honest incidents242 // Workflow jobs stuck waiting for a self-hosted runner that is not there.
243 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 200ms244 // The chosen workspace's projects: their open and recently merged pull
Fast pages, required checks on the branch, self-hosted runners, honest incidents245 // requests, all in one call to work, and their recent events, read as
246 // soon as the projects are known.
247 const perRepoP = reposP.then(async (repos) => {
248 const chosen = (repos ?? [])
249 .filter((repo) => slug != null && repo.namespace.toLowerCase() === slug.toLowerCase())
250 .slice(0, MAX_PROJECTS);
Mission control counts people's direct pushes to the default branch251 const [batch, logs, pushes] = await Promise.all([
Fast pages, required checks on the branch, self-hosted runners, honest incidents252 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.md253 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 branch254 // People's pushes straight to the default branch, which no pull
255 // request counts: the commits each brought, for the week.
256 Promise.all(chosen.map((repo) => directCommits(repo, viewer).catch(() => []))),
Fast pages, required checks on the branch, self-hosted runners, honest incidents257 ]);
258 const byId = new Map(batch.map((entry) => [entry.repoId, entry]));
259 return Promise.all(
260 chosen.map(async (repo, index) => {
261 const found = byId.get(repo.id);
Mission control counts people's direct pushes to the default branch262 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 incidents263 // A fork, which the batch leaves out: asked on its own.
264 const path = { namespace: repo.namespace, name: repo.name };
265 const [pulls, closed] = await Promise.all([
266 work.listPulls(path, viewer, "open").catch(() => null),
267 work.listPulls(path, viewer, "closed").catch(() => null),
268 ]);
269 return {
270 repo,
271 pulls: pulls?.ok ? pulls.value.slice(0, 60) : null,
272 closed: closed?.ok ? closed.value : null,
273 events: logs[index],
Mission control counts people's direct pushes to the default branch274 direct: pushes[index] ?? [],
Fast pages, required checks on the branch, self-hosted runners, honest incidents275 };
276 }),
277 );
278 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API279
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all280 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 200ms281 reposP,
282 soft("projects", perRepoP),
283 soft("pulls", work.listActivePulls(viewer)),
284 slug ? soft("models", env.RUNNER.modelAccess(slug)) : null,
285 soft("profile", identity.profile(username)),
286 slug ? soft("runs", agents.listRuns(viewer, { workspace: slug, limit: 150 })) : null,
287 slug ? soft("deploys", deployments.overview(slug, viewer)) : null,
288 slug ? soft("usage", billing.usage(slug, viewer, new Date(weekAgo - TIME.DAY).toISOString())) : null,
289 slug ? soft("projectList", projectsApi.list(slug, viewer)) : null,
290 slug ? soft("memories", agents.listMemories(viewer, slug, null)) : null,
291 // Repositories someone has invited the viewer to.
292 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 mix293 // Model tokens over the last six weeks: the workspace's, and yours.
294 slug ? soft("tokens", billing.tokenUsage(slug, viewer)) : null,
295 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 all296 // What in their inbox is unread: an agent waiting on them, or a failure, goes on the Needs you card.
297 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 200ms298 ]);
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API299
300 const repoList = repos ?? [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms301 // Each pull request is shown under its repository. Most are in the
302 // 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 API303 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 200ms304 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 incidents305 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 API306 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 200ms307 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 API308
309 const activeList = (active ?? []).flatMap((item) => {
310 const repo = known.get(item.pull.repoId);
311 return repo ? [{ ...item, repo }] : [];
312 });
313 const runList = okOr(runs ?? null) ?? [];
314 const liveRuns = runList.filter((run) => isActiveRun(run.status));
315 const overviewList = okOr(overview ?? null);
316 const projectsOk = okOr(projectList ?? null);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms317 const runsOn = (repo: RepoPath, number: number) =>
318 runList.filter(
319 (run) => run.number === number && `${run.repo.namespace}/${run.repo.name}`.toLowerCase() === `${repo.namespace}/${repo.name}`.toLowerCase(),
320 );
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API321
322 const lower = username.toLowerCase();
323 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 rights324 // 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 API325 const reviewRequested = openPulls.filter(
326 ({ pull }) =>
327 pull.status === "open" &&
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights328 workOwner(pull).username.toLowerCase() !== lower &&
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API329 pull.reviewers.some((name) => name.toLowerCase() === lower),
330 );
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms331
332 // --- Needs you ------------------------------------------------------------
333 const needs: Need[] = [];
334 const extras = new Map<string, Extra>();
335 /** What every pull request's row shares: where it is, who is on it, what is known. */
336 const pullExtra = (pull: Pull, repo: Repo, lifecycle: Lifecycle | null): Extra => {
337 const base = `/${repo.namespace}/${repo.name}`;
338 const agentWork = isAgent(pull.agent);
339 return {
340 repo: pathOf(repo),
341 ref: `#${pull.number}`,
342 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 rights343 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 step344 facts: pullFacts({
345 checkStatus: pull.checkStatus,
346 files: pull.files,
347 lifecycle,
348 runs: runsOn(repo, pull.number),
349 confidence: pull.confidence,
350 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms351 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 step352 confidence: pull.confidence ?? null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms353 };
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API354 };
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms355 const pullAction = (repo: Repo, pull: Pull, quick: Omit<QuickAction, "to">): QuickAction => ({
356 ...quick,
357 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 API358 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms359 const approve = (repo: Repo, pull: Pull) =>
360 pullAction(repo, pull, {
361 label: isAgent(pull.agent) ? "Approve the agent's change" : "Approve the change",
362 fields: { action: "comment", verdict: "approve", body: "" },
363 done: "Approved",
364 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API365
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look366 for (const invitation of invitations ?? []) {
367 if (invitation.status !== "pending") continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms368 const key = `invitation:${invitation.id}`;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look369 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms370 key,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look371 kind: "invitation",
372 title: `${invitation.invited_by ?? "Someone"} invited you to ${invitation.repo}`,
373 detail: `With the ${REPO_ROLE_LABELS[invitation.role]} role. The invitation expires ${new Date(invitation.expires_at).toISOString().slice(0, 10)}.`,
374 to: `/${invitation.repo}/invitations`,
375 action: "Respond",
376 at: Date.parse(invitation.created_at),
377 where: invitation.repo,
378 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms379 const [namespace, name] = invitation.repo.split("/");
380 extras.set(key, {
381 repo: namespace && name ? { namespace, name } : null,
382 by: who(invitation.invited_by),
383 facts: [
384 { label: "Role", value: REPO_ROLE_LABELS[invitation.role], tone: null },
385 { label: "Expires", value: new Date(invitation.expires_at).toISOString().slice(0, 10), tone: null },
386 ],
387 });
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look388 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API389 for (const entry of overviewList ?? []) {
390 const latest = entry.latest;
391 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 200ms392 const key = `deploy:${entry.slug}`;
393 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 API394 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms395 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API396 kind: "deploy",
397 title: `Production build of ${entry.slug} failed`,
398 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 200ms399 to,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API400 action: "See the build",
401 at: Date.parse(latest.finishedAt ?? latest.createdAt),
402 where: `${slug}/${entry.slug}`,
403 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms404 const facts: Fact[] = [
405 { label: "Commit", value: latest.commit.slice(0, 7), tone: null },
406 {
407 label: "Production",
408 value: entry.production ? "Serving the build before" : "Not live yet",
409 tone: entry.production ? "good" : "warn",
410 },
411 ];
412 extras.set(key, {
413 repo: { namespace: slug, name: entry.slug },
414 by: who(latest.createdBy),
415 facts,
416 link: { label: "Deployment settings", to: `/${slug}/${entry.slug}/settings/deployments` },
417 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API418 }
419 }
420 for (const { pull, lifecycle, repo } of activeList) {
421 const where = `${repo.namespace}/${repo.name}#${pull.number}`;
422 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 200ms423 const key = `pull:${pull.id}`;
424 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 step425 // Held for its low confidence: the ask says so in a sentence of its own.
426 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 API427 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 step428 const held = lowConfidence != null && /confidence in this change is low/i.test(lifecycle.detail);
429 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 200ms430 const need: Need = {
431 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API432 kind: conflict ? "conflict" : "stalled",
433 title: pull.title,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step434 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 API435 to,
436 action: conflict ? "Resolve" : "Decide",
437 at: Date.parse(pull.updatedAt),
438 where,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms439 };
440 needs.push(need);
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step441 const reason = held ? "low_confidence" : reasonFor(need);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms442 extras.set(key, {
443 ...extra,
444 quick:
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step445 reason === "needs_review" || reason === "low_confidence"
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms446 ? approve(repo, pull)
Fast pages, required checks on the branch, self-hosted runners, honest incidents447 : /required checks? .*still fails?\b/i.test(lifecycle.detail)
448 ? 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 200ms449 : null,
450 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 API451 });
452 } 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 step453 needs.push({
454 key,
455 kind: "ready",
456 title: pull.title,
Fast pages, required checks on the branch, self-hosted runners, honest incidents457 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 step458 to,
459 action: "Merge",
460 at: Date.parse(pull.updatedAt),
461 where,
462 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms463 extras.set(key, {
464 ...extra,
465 quick: pullAction(repo, pull, {
466 label: isAgent(pull.agent) ? "Merge the agent's change" : "Merge it",
467 fields: { action: "merge" },
468 done: "Merging",
469 }),
470 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API471 } else if (!lifecycle && pull.status === "open" && pull.checkStatus === "failed") {
Fast pages, required checks on the branch, self-hosted runners, honest incidents472 // Taken out of the merge queue: its change failed combined with what was ahead.
473 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 });
474 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 API475 }
476 }
477 for (const { pull, repo } of reviewRequested) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms478 const key = `review:${pull.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API479 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms480 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API481 kind: "review",
482 title: pull.title,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights483 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 API484 to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`,
485 action: "Review",
486 at: Date.parse(pull.updatedAt),
487 where: `${repo.namespace}/${repo.name}#${pull.number}`,
488 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms489 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 API490 }
Fast pages, required checks on the branch, self-hosted runners, honest incidents491 for (const job of (await stuckJobsP) ?? []) {
492 const key = `runner:${job.id}`;
493 const minutes = Math.max(10, Math.round((now - Date.parse(job.queuedAt)) / 60_000));
494 const [namespace, name] = job.repo.split("/");
495 needs.push({
496 key,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit497 kind: "runner",
Fast pages, required checks on the branch, self-hosted runners, honest incidents498 title: `${job.name} is waiting for a self-hosted runner`,
499 detail: `No runner with labels ${job.labels} is online. Start one, or change the job's runs-on.`,
500 to: `/${job.repo}/actions/runs/${job.runId}`,
501 action: "Look",
502 at: Date.parse(job.queuedAt),
503 where: job.repo,
504 });
505 extras.set(key, {
506 repo: namespace && name ? { namespace, name } : undefined,
507 facts: [
508 { label: "Labels", value: job.labels, tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit509 { label: "Waiting", value: waitedFor(minutes), tone: "warn" },
Fast pages, required checks on the branch, self-hosted runners, honest incidents510 ],
Runners are owner-only: the page, its actions, the Settings entry and the links to it511 // Runners are the owners' to see to.
512 ...(namespace && roleIn(viewer, namespace) === "owner" ? { link: { label: "Runners", to: `/${namespace}/-/runners` } } : {}),
Fast pages, required checks on the branch, self-hosted runners, honest incidents513 });
514 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API515 for (const run of liveRuns) {
516 const minutes = stuckMinutes(run, now);
517 if (minutes == null) continue;
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms518 const key = `run:${run.id}`;
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API519 needs.push({
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms520 key,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API521 kind: "stuck",
522 title: run.title ?? `${run.agent}'s run`,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit523 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 API524 to: `/${run.repo.namespace}/${run.repo.name}/agents/runs/${run.id}`,
525 action: "Look",
526 at: Date.parse(run.updatedAt),
527 where: `${run.repo.namespace}/${run.repo.name}${run.number != null ? `#${run.number}` : ""}`,
528 });
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms529 extras.set(key, {
530 repo: run.repo,
531 ref: run.number != null ? `#${run.number}` : null,
532 by: who(run.agent),
533 facts: [
534 { label: "Run", value: RUN_LABEL[run.kind], tone: null },
Usage, Billing settings and prepaid AI credit; fixes from the UX audit535 { 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 200ms536 { label: "Steps so far", value: String(run.stepCount), tone: null },
537 ...(run.costUsd != null ? [{ label: "Cost so far", value: usd(run.costUsd), tone: null }] : []),
538 ],
539 open: run.number != null ? `/${run.repo.namespace}/${run.repo.name}/pull/${run.number}` : undefined,
540 });
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API541 }
542
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms543 const needRows: NeedRow[] = rankNeeds(needs).map((need) => {
544 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 step545 const reason = withConfidence(reasonFor(need), extra.confidence);
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms546 return {
547 key: need.key,
548 reason,
549 repo: extra.repo ?? null,
550 ref: extra.ref ?? null,
551 title: need.title,
552 ask: need.detail,
553 by: extra.by ?? null,
554 for: extra.for ?? null,
555 at: need.at,
556 to: need.to,
557 open: extra.open ?? need.to,
558 facts: extra.facts ?? [],
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step559 why: whyFor(reason, need, extra.confidence),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms560 quick: extra.quick ?? null,
561 link: extra.link ?? null,
562 };
563 });
564
565 // --- Waiting on agents ----------------------------------------------------
566 const needKeys = new Set(
567 needRows.flatMap((row) => (row.repo && row.ref ? [needPathKey(row.repo, Number(row.ref.slice(1)))] : [])),
568 );
569 const waiting = waitingRows({
570 active: activeList.filter(({ repo }) => inWorkspace(repo)).map(({ pull, lifecycle, repo }) => ({ pull, lifecycle, repo: pathOf(repo) })),
571 live: liveRuns,
572 drafts: openPulls.filter(({ pull }) => pull.status === "draft").map(({ pull, repo }) => ({ ...pull, repo: pathOf(repo) })),
573 needKeys,
574 });
575
576 // --- Landed ---------------------------------------------------------------
577 const merged: Merged[] = (perRepo ?? []).flatMap(({ repo, closed }) =>
578 (closed ?? []).flatMap((pull) =>
579 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 bar580 ? [
581 {
582 repo: pathOf(repo),
583 number: pull.number,
584 title: pull.title,
585 agent: pull.agent,
586 // Who wrote it, not who merged it: a person's change that g1t
587 // auto-merged is still theirs.
588 authoredByAgent: pull.author.kind === "agent" || isAgent(pull.author.username),
589 mergedBy: pull.mergedBy,
590 mergedAt: pull.mergedAt,
591 files: pull.files,
592 },
593 ]
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms594 : [],
595 ),
596 );
597 const twoWeeksAgo = now - 14 * TIME.DAY;
598 const complete = perRepo != null && perRepo.every(({ closed }) => closed != null && reachesBack(closed, twoWeeksAgo, PULL_PAGE));
Mission control counts people's direct pushes to the default branch599 // A person's commits pushed straight to the default branch are their own
600 // changes too, on the day they landed.
601 const direct = (perRepo ?? []).flatMap(({ direct }) =>
602 direct.map((commit) => ({ mergedAt: commit.at, mergedBy: null, authoredByAgent: false })),
603 );
604 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 200ms605 const landed = landedToday(merged, now, tz);
606
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API607 // --- Activity -------------------------------------------------------------
608 const items: ActivityItem[] = [];
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms609 const titles: Record<string, string> = {};
610 const titleKey = (repo: RepoPath, number: number) => `${repo.namespace}/${repo.name}#${number}`.toLowerCase();
611 for (const { repo, pulls, closed, events } of perRepo ?? []) {
612 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 API613 for (const event of events ?? []) {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms614 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 API615 const item = eventItem(event, pathOf(repo));
616 if (item) items.push(item);
617 }
618 }
Merge demo dry run fixes: settled checks, other attempts, queue links, clone box, activity feed, DEMO.md619 // Production deploys come from the log too (deployment_status.created),
620 // 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 API621 for (const memory of okOr(memories ?? null)?.workspace ?? []) {
622 const repo = memory.repo ?? memory.source.repo;
623 if (!repo || !slug) continue;
624 items.push({
625 id: `memory:${memory.id}`,
626 at: Date.parse(memory.createdAt),
627 repo,
628 actor: memory.createdBy,
629 verb: "learned",
630 number: null,
631 text: memory.text.length > 90 ? `${memory.text.slice(0, 88)}…` : memory.text,
632 to: `/${slug}/-/memory`,
633 });
634 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit635 // The log names people by account id: their usernames, in one lookup.
636 const ids = actorIds(items.map((item) => item.actor));
637 const names = ids.length > 0 ? await soft("usernames", identity.usernames(ids)) : {};
638 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 200ms639 // Only the titles the feed names travel to the page.
640 const shownTitles: Record<string, string> = {};
641 for (const group of groups) {
642 for (const part of group.parts) {
643 for (const number of part.numbers) {
644 const key = titleKey(group.repo, number);
645 if (titles[key]) shownTitles[key] = titles[key];
646 }
647 }
648 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API649
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms650 // --- The strip ------------------------------------------------------------
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API651 const usageOk = okOr(usage ?? null);
652 const costPoints = usageOk
653 ? usageOk.byDay.map((slice) => ({ at: Date.parse(`${slice.key.split("/")[0]}T12:00:00Z`), value: slice.micros / 1_000_000 }))
654 : 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 200ms655 const weekCost = dailyBuckets(costPoints, 7, now).reduce((sum, v) => sum + v, 0);
656 const month = dayKey(now, tz).slice(0, 7);
657 const projectCount = projectsOk?.length ?? (perRepo != null ? perRepo.length : null);
658 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 API659
660 const models_ = models ?? null;
661 times.total = Date.now() - started;
Agents as a team: lifecycle, merge queue, billing and a new shell662 const serverTiming = Object.entries(times)
663 .map(([name, ms]) => `${name};dur=${ms}`)
664 .join(", ");
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API665 return data(
666 {
667 signedIn: true as const,
668 viewer,
669 name: profile?.name?.trim() || username,
670 greeting: greetingFor(hourIn(now, tz)),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms671 date: dateLine(now, tz),
Mission control counts people's own changes as theirs; a shorter sidebar with setup under Settings, and a 56px top bar672 summary: summaryLine({
673 total: week.total,
674 byAgents: week.byAgents,
675 agentChanges: week.agentChanges,
676 people: week.people,
677 live: liveRuns.length,
678 needs: needRows.length,
679 }),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms680 workspace: slug,
681 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 API682 canRunAgents: models_ == null || models_.hosted || models_.own != null,
683 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 200ms684 handedOff: activeList.length > 0 || runList.length > 0 || merged.length > 0,
685 needs: needRows,
686 waiting,
687 landed,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API688 liveTotal: liveRuns.length,
689 runsLoaded: runs != null && runs.ok,
690 perRepoLoaded: perRepo != null,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms691 stats: {
692 projects: projectCount,
693 projectsThisMonth,
694 agentHours: agentHours(runList, weekAgo, now),
695 weekCost,
696 },
697 week,
Mission control shows model usage, yours and the workspace's: tokens, cost, active days, cache share, each day, and the mix698 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 API699 groups,
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms700 titles: shownTitles,
Inbox: a bell in the top bar opens it beside the page, and /inbox holds it all701 inboxNeeds: needsYou(unread?.items ?? [], INBOX_SHOWN),
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms702 // A run that is going makes the page worth refreshing on its own.
703 changing:
704 liveRuns.length > 0 ||
705 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 API706 },
707 {
Mission control shows where you are needed and what agents landed without you; git answers in about 200ms708 headers: { "Server-Timing": serverTiming },
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API709 },
710 );
Agents as a team: lifecycle, merge queue, billing and a new shell711}
Get started on mission control712
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look713export type Loaded = Extract<Route.ComponentProps["loaderData"], { signedIn: true }>;
Get started on mission control714
Fast pages, required checks on the branch, self-hosted runners, honest incidents715/**
716 * Mission control, loaded only for someone signed in (components/mission-control.tsx).
717 * The app shell starts loading it once the browser is idle, so the
718 * skeleton below is rarely seen.
719 */
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look720const MissionControl = lazy(() => import("../components/mission-control"));
Agents as a team: lifecycle, merge queue, billing and a new shell721
Usage, Billing settings and prepaid AI credit; fixes from the UX audit722/**
723 * Mission control's outline while its code arrives, piece for piece as
724 * components/mission-control.tsx lays it out: the greeting, the Agent box,
725 * the actions under it, the stats, then the work list beside the week. Each
726 * piece has the padding and line heights of the real one, so nothing moves
727 * when it arrives.
728 */
Fast pages, required checks on the branch, self-hosted runners, honest incidents729function MissionControlSkeleton() {
730 return (
731 <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'732 <span role="status" className="sr-only">
733 Loading…
734 </span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit735 <header>
736 <SkeletonLine className="w-72 max-w-full text-[1.75rem] leading-tight sm:text-[2rem]" barClassName="h-8" />
737 {/* The date and the week's summary: one line wide, three on a phone. */}
738 <div className="mt-1.5 text-sm">
739 <SkeletonLine className="w-[36rem] max-w-full" barClassName="h-3.5" />
740 <SkeletonLine className="w-full sm:hidden" barClassName="h-3.5" />
741 <SkeletonLine className="w-2/3 sm:hidden" barClassName="h-3.5" />
Merge branch 'worktree-agent-aca4790ea223aa235'742 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents743 </header>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit744 {/* The Agent box (components/ask-composer.tsx), then the actions under it. */}
745 <div className="space-y-3">
746 <div className="rounded-xl border border-line bg-surface">
747 <SkeletonLine className="mx-4 mt-3 w-36 text-sm" />
748 <div className="px-4 pt-3.5 pb-2 text-sm">
749 <SkeletonLine className="w-[30rem] max-w-full" />
750 <span className="block h-[1lh]" />
751 </div>
752 <div className="flex flex-wrap items-center gap-2 px-3 pb-3">
753 <Skeleton className="h-8 w-[4.5rem] rounded-md" />
754 <Skeleton className="h-8 w-[9.5rem] rounded-md" />
755 <Skeleton className="size-8 rounded-md" />
756 <Skeleton className="size-8 rounded-md" />
757 <Skeleton className="ml-auto size-8 rounded-md" />
758 </div>
759 </div>
760 <div className="flex flex-wrap items-center gap-2">
761 <Skeleton className="h-[38px] w-60 rounded-md" />
762 <Skeleton className="h-[38px] w-28 rounded-md" />
763 <Skeleton className="h-[38px] w-44 rounded-md" />
764 </div>
765 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents766 <div className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-line bg-line lg:grid-cols-5">
767 {Array.from({ length: 5 }, (_, index) => (
Merge branch 'worktree-agent-aca4790ea223aa235'768 <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 incidents769 ))}
770 </div>
Merge branch 'worktree-agent-aca4790ea223aa235'771 <div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_21rem] xl:grid-cols-[minmax(0,1fr)_23rem]">
772 <div className="self-start overflow-hidden rounded-xl border border-line bg-surface">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit773 <div className="flex items-center gap-4 border-b border-line px-4 py-3 text-sm sm:px-5">
774 <SkeletonLine className="w-24" />
775 <SkeletonLine className="w-32" />
776 <SkeletonLine className="hidden w-28 sm:flex" />
777 </div>
778 <div className="divide-y divide-line">
779 {Array.from({ length: 5 }, (_, index) => (
780 <div key={index}>
781 <div className="flex items-center gap-3 px-4 py-3 sm:px-5">
782 <Skeleton className="size-8 shrink-0 rounded-md" />
783 <span className="min-w-0 grow">
784 <SkeletonLine className="text-sm" width={`${70 - ((index * 17) % 30)}%`} />
785 <SkeletonLine className="mt-0.5 text-xs" width={`${50 - ((index * 11) % 20)}%`} />
786 </span>
787 <Skeleton className="hidden h-4 w-24 shrink-0 sm:block" />
788 </div>
789 {/* The first row stands open, as the real list's does. */}
790 {index === 0 && (
791 <div className="px-4 pb-4 sm:px-5 sm:pb-5">
792 <Skeleton className="h-[17rem] rounded-xl" />
793 <Skeleton className="mt-3 h-8 w-44 rounded-md" />
794 </div>
795 )}
796 </div>
797 ))}
Merge branch 'worktree-agent-aca4790ea223aa235'798 </div>
799 </div>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit800 <div className="space-y-6">
801 <SkeletonCard lines={0} className="h-80 p-5" />
802 <SkeletonCard lines={6} className="p-5" />
803 </div>
Fast pages, required checks on the branch, self-hosted runners, honest incidents804 </div>
805 </main>
806 );
807}
808
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step809export default function Home({ loaderData, actionData }: Route.ComponentProps) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look810 if (!loaderData.signedIn) return <Landing />;
API and MCP server, Rust identity service, registration, site redesign811 return (
Fast pages, required checks on the branch, self-hosted runners, honest incidents812 <Suspense fallback={<MissionControlSkeleton />}>
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step813 <MissionControl loaderData={loaderData} delegated={actionData ?? null} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look814 </Suspense>
Initial g1t: services, event bus, intents and attempts815 );
816}

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