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.
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 1 | /** |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 2 | * Code's Overview data: what needs the viewer, what landed, and the week, |
| 3 | * across a workspace's projects. Code's Overview | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 4 | * (routes/workspace/code-overview.tsx) shows all of it; Home |
| 5 | * (routes/workspace/home.tsx) takes what needs you from it. | |
| 6 | */ | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 7 | import { type G1tEvent, type Pull, type Repo, type RepoPath, type User, type Viewer, REPO_ROLE_LABELS, workOwner } from "@g1t/contracts"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 8 | |
| 9 | import { WORKSPACE_COOKIE, chosenWorkspace } from "./workspace-choice"; | |
| 10 | import { | |
| 11 | type ActivityItem, | |
| 12 | type Need, | |
| 13 | TIME, | |
| 14 | actorIds, | |
| 15 | eventItem, | |
| 16 | FEED_EVENT_TYPES, | |
| 17 | groupActivity, | |
| 18 | nameActor, | |
| 19 | rankNeeds, | |
| 20 | readCookie, | |
| 21 | waitedFor, | |
| 22 | } from "./mission"; | |
| 23 | import { G1T_COMMIT_EMAILS, normalizeEmail } from "./commit-people"; | |
| 24 | import { | |
| 25 | type Fact, | |
| 26 | type Merged, | |
| 27 | type NeedRow, | |
| 28 | type QuickAction, | |
| 29 | dateLine, | |
| 30 | dayKey, | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 31 | historyCovers, |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 32 | landedToday, |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 33 | placePushes, |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 34 | pullFacts, |
| 35 | reachesBack, | |
| 36 | reasonFor, | |
| 37 | summaryLine, | |
| 38 | weekOf, | |
| 39 | who, | |
| 40 | whyFor, | |
| 41 | } from "./mission-control"; | |
| 42 | import { needsYou } from "./inbox"; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 43 | import { deployments, events as eventLog, identity, inbox, projects as projectsApi, repos as reposApi, work } from "./services.server"; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 44 | import { roleIn } from "./session.server"; |
| 45 | import { runners } from "./runners.server"; | |
| 46 | import { readableRepos } from "./access.server"; | |
| 47 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 48 | /** The viewer's time zone, which the page sets, so the date fits their day. */ |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 49 | const TZ_COOKIE = "g1t_tz"; |
| 50 | /** The most projects whose pull requests and events are read for the page. */ | |
| 51 | const MAX_PROJECTS = 10; | |
| 52 | const EVENTS_PER_PROJECT = 80; | |
| 53 | /** How many pull requests work lists at once (`LIST_PAGE` in services/work). */ | |
| 54 | const PULL_PAGE = 100; | |
| 55 | /** Unread inbox items read for the Needs you card, and how many it shows. */ | |
| 56 | const INBOX_READ = 50; | |
| 57 | const INBOX_SHOWN = 4; | |
| 58 | ||
| 59 | /** Where a need came from, so its row can say what is known about it. */ | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 60 | type Extra = Partial<Pick<NeedRow, "repo" | "ref" | "by" | "facts" | "quick" | "link" | "open">>; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 61 | |
| 62 | /** | |
| 63 | * Push events read per project, a page at a time, back two weeks. Pushes | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 64 | * to every branch are in the log, so a fixed count lost a busy week's |
| 65 | * earlier days to other branches. | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 66 | */ |
| 67 | const PUSH_PAGE = 200; | |
| 68 | const PUSH_PAGES = 5; | |
| 69 | /** The first page is smaller: most projects push far less in two weeks. */ | |
| 70 | const FIRST_PUSH_PAGE = 50; | |
| 71 | /** | |
| 72 | * Commits of the default branch read to place each push's commits: a | |
| 73 | * short read first, which covers most projects' fortnight, and the longer | |
| 74 | * one only when it does not reach the oldest push. | |
| 75 | */ | |
| 76 | const HISTORY_FIRST = 200; | |
| 77 | const HISTORY_READ = 1000; | |
| 78 | ||
| 79 | /** | |
| 80 | * The commits people pushed straight to a project's default branch in the | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 81 | * last two weeks, each with when its push landed. Merges and g1t's own |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 82 | * commits are left out: pull requests count those. |
| 83 | */ | |
| 84 | async function directCommits(repo: Repo, viewer: Viewer): Promise<{ hash: string; at: string }[]> { | |
| 85 | const since = Date.now() - 14 * TIME.DAY; | |
| 86 | const pushes: G1tEvent<"git.push">[] = []; | |
| 87 | let before: string | undefined; | |
| 88 | for (let page = 0; page < PUSH_PAGES; page++) { | |
| 89 | const limit = page === 0 ? FIRST_PUSH_PAGE : PUSH_PAGE; | |
| 90 | const batch = await eventLog.list({ repoId: repo.id, types: ["git.push"], limit, ...(before ? { before } : {}) }); | |
| 91 | for (const event of batch) { | |
| 92 | if (event.type === "git.push" && event.data.defaultBranch && Date.parse(event.time) >= since) pushes.push(event as G1tEvent<"git.push">); | |
| 93 | } | |
| 94 | const oldest = batch.at(-1); | |
| 95 | if (batch.length < limit || !oldest || Date.parse(oldest.time) < since) break; | |
| 96 | before = oldest.id; | |
| 97 | } | |
| 98 | if (pushes.length === 0) return []; | |
| 99 | // A read of the branch from the newest push back; each push brought | |
| 100 | // what lies between its `after` and its `before` in that history. | |
| 101 | const path = { namespace: repo.namespace, name: repo.name }; | |
| 102 | const read = (limit: number) => reposApi.log(path, viewer, pushes[0].data.after, limit).catch(() => null); | |
| 103 | let history = await read(HISTORY_FIRST); | |
| 104 | if (history?.ok && !historyCovers(history.value, pushes, HISTORY_FIRST)) history = await read(HISTORY_READ); | |
| 105 | if (!history?.ok) return []; | |
| 106 | // g1t's own commits are told by their author address, never by name. | |
| 107 | return placePushes(history.value, pushes, (commit) => G1T_COMMIT_EMAILS.has(normalizeEmail(commit.author.email))); | |
| 108 | } | |
| 109 | ||
| 110 | export async function loadMissionControl(viewer: User, request: Request, workspace: string | null) { | |
| 111 | const now = Date.now(); | |
| 112 | const started = Date.now(); | |
| 113 | const times: Record<string, number> = {}; | |
| 114 | // Each call is timed, and one that fails leaves its section empty rather | |
| 115 | // than taking the page down. | |
| 116 | const soft = <T,>(name: string, promise: Promise<T>): Promise<T | null> => | |
| 117 | promise.then( | |
| 118 | (value) => ((times[name] = Date.now() - started), value), | |
| 119 | (error) => { | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 120 | console.warn(`overview: ${name} failed`, error); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 121 | times[name] = Date.now() - started; |
| 122 | return null; | |
| 123 | }, | |
| 124 | ); | |
| 125 | const okOr = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null); | |
| 126 | ||
| 127 | const cookies = request.headers.get("cookie"); | |
| 128 | const tz = readCookie(cookies, TZ_COOKIE); | |
| 129 | const memberships = viewer.workspaces ?? []; | |
| 130 | // The workspace asked for, else the one you chose, as the sidebar shows it (lib/workspace-choice.ts). | |
| 131 | const slug = | |
| 132 | (workspace && roleIn(viewer, workspace) ? workspace.toLowerCase() : null) ?? | |
| 133 | chosenWorkspace(memberships, readCookie(cookies, WORKSPACE_COOKIE))?.slug ?? | |
| 134 | null; | |
| 135 | const username = viewer.username; | |
| 136 | ||
| 137 | const reposP = soft("repos", reposApi.list(viewer, { memberOnly: true })); | |
| 138 | // Workflow jobs stuck waiting for a self-hosted runner that is not there. | |
| 139 | const stuckJobsP = soft("stuck_jobs", runners.stuck(viewer)); | |
| 140 | // The chosen workspace's projects: their open and recently merged pull | |
| 141 | // requests, all in one call to work, and their recent events, read as | |
| 142 | // soon as the projects are known. | |
| 143 | const perRepoP = reposP.then(async (repos) => { | |
| 144 | const chosen = (repos ?? []) | |
| 145 | .filter((repo) => slug != null && repo.namespace.toLowerCase() === slug.toLowerCase()) | |
| 146 | .slice(0, MAX_PROJECTS); | |
| 147 | const [batch, logs, pushes] = await Promise.all([ | |
| 148 | work.pullsForRepos(chosen.map((repo) => repo.id), viewer, PULL_PAGE).catch(() => []), | |
| 149 | Promise.all(chosen.map((repo) => eventLog.list({ repoId: repo.id, types: [...FEED_EVENT_TYPES], limit: EVENTS_PER_PROJECT }).catch(() => null))), | |
| 150 | // People's pushes straight to the default branch, which no pull | |
| 151 | // request counts: the commits each brought, for the week. | |
| 152 | Promise.all(chosen.map((repo) => directCommits(repo, viewer).catch(() => []))), | |
| 153 | ]); | |
| 154 | const byId = new Map(batch.map((entry) => [entry.repoId, entry])); | |
| 155 | return Promise.all( | |
| 156 | chosen.map(async (repo, index) => { | |
| 157 | const found = byId.get(repo.id); | |
| 158 | if (found) return { repo, pulls: found.open.slice(0, 60), closed: found.closed, events: logs[index], direct: pushes[index] ?? [] }; | |
| 159 | // A fork, which the batch leaves out: asked on its own. | |
| 160 | const path = { namespace: repo.namespace, name: repo.name }; | |
| 161 | const [pulls, closed] = await Promise.all([ | |
| 162 | work.listPulls(path, viewer, "open").catch(() => null), | |
| 163 | work.listPulls(path, viewer, "closed").catch(() => null), | |
| 164 | ]); | |
| 165 | return { | |
| 166 | repo, | |
| 167 | pulls: pulls?.ok ? pulls.value.slice(0, 60) : null, | |
| 168 | closed: closed?.ok ? closed.value : null, | |
| 169 | events: logs[index], | |
| 170 | direct: pushes[index] ?? [], | |
| 171 | }; | |
| 172 | }), | |
| 173 | ); | |
| 174 | }); | |
| 175 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 176 | const [repos, perRepo, active, profile, overview, projectList, invitations, unread] = await Promise.all([ |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 177 | reposP, |
| 178 | soft("projects", perRepoP), | |
| 179 | soft("pulls", work.listActivePulls(viewer)), | |
| 180 | soft("profile", identity.profile(username)), | |
| 181 | slug ? soft("deploys", deployments.overview(slug, viewer)) : null, | |
| 182 | slug ? soft("projectList", projectsApi.list(slug, viewer)) : null, | |
| 183 | // Repositories someone has invited the viewer to. | |
| 184 | soft("invitations", identity.myRepoInvitations(viewer)), | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 185 | // What in their inbox is unread: a failure or a warning goes on the Needs you card. |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 186 | soft("inbox", inbox.list(viewer, { unread: true, limit: INBOX_READ })), |
| 187 | ]); | |
| 188 | ||
| 189 | const repoList = repos ?? []; | |
| 190 | // Each pull request is shown under its repository. Most are in the | |
| 191 | // viewer's own, already listed; the rest are looked up once each. | |
| 192 | const known = new Map<string, Repo>(repoList.map((repo) => [repo.id, repo])); | |
| 193 | const missing = [...new Set((active ?? []).map(({ pull }) => pull.repoId))].filter((id) => !known.has(id)); | |
| 194 | for (const repo of await readableRepos(missing, viewer)) known.set(repo.id, repo); | |
| 195 | const pathOf = (repo: Repo): RepoPath => ({ namespace: repo.namespace, name: repo.name }); | |
| 196 | const inWorkspace = (repo: RepoPath) => slug != null && repo.namespace.toLowerCase() === slug.toLowerCase(); | |
| 197 | ||
| 198 | const activeList = (active ?? []).flatMap((item) => { | |
| 199 | const repo = known.get(item.pull.repoId); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 200 | return repo ? [{ pull: item.pull, repo }] : []; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 201 | }); |
| 202 | const overviewList = okOr(overview ?? null); | |
| 203 | const projectsOk = okOr(projectList ?? null); | |
| 204 | ||
| 205 | const lower = username.toLowerCase(); | |
| 206 | const openPulls = (perRepo ?? []).flatMap(({ repo, pulls }) => (pulls ?? []).map((pull) => ({ pull, repo }))); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 207 | // Never your own. |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 208 | const reviewRequested = openPulls.filter( |
| 209 | ({ pull }) => | |
| 210 | pull.status === "open" && | |
| 211 | workOwner(pull).username.toLowerCase() !== lower && | |
| 212 | pull.reviewers.some((name) => name.toLowerCase() === lower), | |
| 213 | ); | |
| 214 | ||
| 215 | // --- Needs you ------------------------------------------------------------ | |
| 216 | const needs: Need[] = []; | |
| 217 | const extras = new Map<string, Extra>(); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 218 | /** What every pull request's row shares: where it is, who opened it, what is known. */ |
| 219 | const pullExtra = (pull: Pull, repo: Repo): Extra => { | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 220 | const base = `/${repo.namespace}/${repo.name}`; |
| 221 | return { | |
| 222 | repo: pathOf(repo), | |
| 223 | ref: `#${pull.number}`, | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 224 | by: who(workOwner(pull).username), |
| 225 | facts: pullFacts({ checkStatus: pull.checkStatus, files: pull.files }), | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 226 | open: pull.issue != null ? `${base}/issues/${pull.issue}` : `${base}/pull/${pull.number}?tab=changes`, |
| 227 | }; | |
| 228 | }; | |
| 229 | const pullAction = (repo: Repo, pull: Pull, quick: Omit<QuickAction, "to">): QuickAction => ({ | |
| 230 | ...quick, | |
| 231 | to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`, | |
| 232 | }); | |
| 233 | ||
| 234 | for (const invitation of invitations ?? []) { | |
| 235 | if (invitation.status !== "pending") continue; | |
| 236 | const key = `invitation:${invitation.id}`; | |
| 237 | needs.push({ | |
| 238 | key, | |
| 239 | kind: "invitation", | |
| 240 | title: `${invitation.invited_by ?? "Someone"} invited you to ${invitation.repo}`, | |
| 241 | detail: `With the ${REPO_ROLE_LABELS[invitation.role]} role. The invitation expires ${new Date(invitation.expires_at).toISOString().slice(0, 10)}.`, | |
| 242 | to: `/${invitation.repo}/invitations`, | |
| 243 | action: "Respond", | |
| 244 | at: Date.parse(invitation.created_at), | |
| 245 | where: invitation.repo, | |
| 246 | }); | |
| 247 | const [namespace, name] = invitation.repo.split("/"); | |
| 248 | extras.set(key, { | |
| 249 | repo: namespace && name ? { namespace, name } : null, | |
| 250 | by: who(invitation.invited_by), | |
| 251 | facts: [ | |
| 252 | { label: "Role", value: REPO_ROLE_LABELS[invitation.role], tone: null }, | |
| 253 | { label: "Expires", value: new Date(invitation.expires_at).toISOString().slice(0, 10), tone: null }, | |
| 254 | ], | |
| 255 | }); | |
| 256 | } | |
| 257 | for (const entry of overviewList ?? []) { | |
| 258 | const latest = entry.latest; | |
| 259 | if (latest?.kind === "production" && latest.status === "failed" && slug) { | |
| 260 | const key = `deploy:${entry.slug}`; | |
| 261 | const to = `/${slug}/${entry.slug}/deployments/${latest.id}`; | |
| 262 | needs.push({ | |
| 263 | key, | |
| 264 | kind: "deploy", | |
| 265 | title: `Production build of ${entry.slug} failed`, | |
| 266 | detail: latest.error ?? "The last build of the default branch failed. Production still serves the build before it.", | |
| 267 | to, | |
| 268 | action: "See the build", | |
| 269 | at: Date.parse(latest.finishedAt ?? latest.createdAt), | |
| 270 | where: `${slug}/${entry.slug}`, | |
| 271 | }); | |
| 272 | const facts: Fact[] = [ | |
| 273 | { label: "Commit", value: latest.commit.slice(0, 7), tone: null }, | |
| 274 | { | |
| 275 | label: "Production", | |
| 276 | value: entry.production ? "Serving the build before" : "Not live yet", | |
| 277 | tone: entry.production ? "good" : "warn", | |
| 278 | }, | |
| 279 | ]; | |
| 280 | extras.set(key, { | |
| 281 | repo: { namespace: slug, name: entry.slug }, | |
| 282 | by: who(latest.createdBy), | |
| 283 | facts, | |
| 284 | link: { label: "Deployment settings", to: `/${slug}/${entry.slug}/settings/deployments` }, | |
| 285 | }); | |
| 286 | } | |
| 287 | } | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 288 | for (const { pull, repo } of activeList) { |
| 289 | // Taken out of the merge queue: its change failed combined with what was ahead. | |
| 290 | if (pull.status !== "open" || pull.checkStatus !== "failed") continue; | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 291 | const key = `pull:${pull.id}`; |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 292 | needs.push({ |
| 293 | key, | |
| 294 | kind: "checks", | |
| 295 | title: pull.title, | |
| 296 | detail: "It failed in the merge queue. Push a fix, then merge it again.", | |
| 297 | to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`, | |
| 298 | action: "See checks", | |
| 299 | at: Date.parse(pull.updatedAt), | |
| 300 | where: `${repo.namespace}/${repo.name}#${pull.number}`, | |
| 301 | }); | |
| 302 | extras.set(key, { ...pullExtra(pull, repo), quick: null }); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 303 | } |
| 304 | for (const { pull, repo } of reviewRequested) { | |
| 305 | const key = `review:${pull.id}`; | |
| 306 | needs.push({ | |
| 307 | key, | |
| 308 | kind: "review", | |
| 309 | title: pull.title, | |
| 310 | detail: `${workOwner(pull).username} asked for your review.`, | |
| 311 | to: `/${repo.namespace}/${repo.name}/pull/${pull.number}`, | |
| 312 | action: "Review", | |
| 313 | at: Date.parse(pull.updatedAt), | |
| 314 | where: `${repo.namespace}/${repo.name}#${pull.number}`, | |
| 315 | }); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 316 | extras.set(key, { |
| 317 | ...pullExtra(pull, repo), | |
| 318 | quick: pullAction(repo, pull, { label: "Approve the change", fields: { action: "comment", verdict: "approve", body: "" }, done: "Approved" }), | |
| 319 | }); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 320 | } |
| 321 | for (const job of (await stuckJobsP) ?? []) { | |
| 322 | const key = `runner:${job.id}`; | |
| 323 | const minutes = Math.max(10, Math.round((now - Date.parse(job.queuedAt)) / 60_000)); | |
| 324 | const [namespace, name] = job.repo.split("/"); | |
| 325 | needs.push({ | |
| 326 | key, | |
| 327 | kind: "runner", | |
| 328 | title: `${job.name} is waiting for a self-hosted runner`, | |
| 329 | detail: `No runner with labels ${job.labels} is online. Start one, or change the job's runs-on.`, | |
| 330 | to: `/${job.repo}/actions/runs/${job.runId}`, | |
| 331 | action: "Look", | |
| 332 | at: Date.parse(job.queuedAt), | |
| 333 | where: job.repo, | |
| 334 | }); | |
| 335 | extras.set(key, { | |
| 336 | repo: namespace && name ? { namespace, name } : undefined, | |
| 337 | facts: [ | |
| 338 | { label: "Labels", value: job.labels, tone: null }, | |
| 339 | { label: "Waiting", value: waitedFor(minutes), tone: "warn" }, | |
| 340 | ], | |
| 341 | // Runners are the owners' to see to. | |
| 342 | ...(namespace && roleIn(viewer, namespace) === "owner" ? { link: { label: "Runners", to: `/${namespace}/-/runners` } } : {}), | |
| 343 | }); | |
| 344 | } | |
| 345 | ||
| 346 | const needRows: NeedRow[] = rankNeeds(needs).map((need) => { | |
| 347 | const extra = extras.get(need.key) ?? {}; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 348 | const reason = reasonFor(need); |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 349 | return { |
| 350 | key: need.key, | |
| 351 | reason, | |
| 352 | repo: extra.repo ?? null, | |
| 353 | ref: extra.ref ?? null, | |
| 354 | title: need.title, | |
| 355 | ask: need.detail, | |
| 356 | by: extra.by ?? null, | |
| 357 | at: need.at, | |
| 358 | to: need.to, | |
| 359 | open: extra.open ?? need.to, | |
| 360 | facts: extra.facts ?? [], | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 361 | why: whyFor(reason, need), |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 362 | quick: extra.quick ?? null, |
| 363 | link: extra.link ?? null, | |
| 364 | }; | |
| 365 | }); | |
| 366 | ||
| 367 | // --- Landed --------------------------------------------------------------- | |
| 368 | const merged: Merged[] = (perRepo ?? []).flatMap(({ repo, closed }) => | |
| 369 | (closed ?? []).flatMap((pull) => | |
| 370 | pull.status === "merged" && pull.mergedAt | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 371 | ? [{ repo: pathOf(repo), number: pull.number, title: pull.title, mergedBy: pull.mergedBy, mergedAt: pull.mergedAt, files: pull.files }] |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 372 | : [], |
| 373 | ), | |
| 374 | ); | |
| 375 | const twoWeeksAgo = now - 14 * TIME.DAY; | |
| 376 | const complete = perRepo != null && perRepo.every(({ closed }) => closed != null && reachesBack(closed, twoWeeksAgo, PULL_PAGE)); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 377 | // Commits pushed straight to the default branch are changes too, on the day they landed. |
| 378 | const direct = (perRepo ?? []).flatMap(({ direct }) => direct.map((commit) => ({ mergedAt: commit.at }))); | |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 379 | const week = weekOf([...merged, ...direct], now, tz, complete); |
| 380 | const landed = landedToday(merged, now, tz); | |
| 381 | ||
| 382 | // --- Activity ------------------------------------------------------------- | |
| 383 | const items: ActivityItem[] = []; | |
| 384 | const titles: Record<string, string> = {}; | |
| 385 | const titleKey = (repo: RepoPath, number: number) => `${repo.namespace}/${repo.name}#${number}`.toLowerCase(); | |
| 386 | for (const { repo, pulls, closed, events } of perRepo ?? []) { | |
| 387 | for (const pull of [...(pulls ?? []), ...(closed ?? [])]) titles[titleKey(repo, pull.number)] = pull.title; | |
| 388 | for (const event of events ?? []) { | |
| 389 | if (event.type === "issue.opened") titles[titleKey(repo, event.data.number)] ??= event.data.title; | |
| 390 | const item = eventItem(event, pathOf(repo)); | |
| 391 | if (item) items.push(item); | |
| 392 | } | |
| 393 | } | |
| 394 | // The log names people by account id: their usernames, in one lookup. | |
| 395 | const ids = actorIds(items.map((item) => item.actor)); | |
| 396 | const names = ids.length > 0 ? await soft("usernames", identity.usernames(ids)) : {}; | |
| 397 | const groups = groupActivity(items.map((item) => ({ ...item, actor: nameActor(item.actor, names) }))).slice(0, 40); | |
| 398 | // Only the titles the feed names travel to the page. | |
| 399 | const shownTitles: Record<string, string> = {}; | |
| 400 | for (const group of groups) { | |
| 401 | for (const part of group.parts) { | |
| 402 | for (const number of part.numbers) { | |
| 403 | const key = titleKey(group.repo, number); | |
| 404 | if (titles[key]) shownTitles[key] = titles[key]; | |
| 405 | } | |
| 406 | } | |
| 407 | } | |
| 408 | ||
| 409 | // --- The strip ------------------------------------------------------------ | |
| 410 | const month = dayKey(now, tz).slice(0, 7); | |
| 411 | const projectCount = projectsOk?.length ?? (perRepo != null ? perRepo.length : null); | |
| 412 | const projectsThisMonth = projectsOk ? projectsOk.filter((project) => dayKey(Date.parse(project.createdAt), tz).slice(0, 7) === month).length : null; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 413 | const openCount = perRepo != null ? openPulls.filter(({ pull }) => pull.status === "open" || pull.status === "draft").length : null; |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 414 | |
| 415 | times.total = Date.now() - started; | |
| 416 | const serverTiming = Object.entries(times) | |
| 417 | .map(([name, ms]) => `${name};dur=${ms}`) | |
| 418 | .join(", "); | |
| 419 | return { | |
| 420 | timing: serverTiming, | |
| 421 | value: { | |
| 422 | signedIn: true as const, | |
| 423 | viewer, | |
| 424 | name: profile?.name?.trim() || username, | |
| 425 | date: dateLine(now, tz), | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 426 | summary: summaryLine({ total: week.total, needs: needRows.length }), |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 427 | workspace: slug, |
| 428 | repos: repoList.filter((repo) => inWorkspace(repo)).map(pathOf), | |
| 429 | needs: needRows, | |
| 430 | landed, | |
| 431 | perRepoLoaded: perRepo != null, | |
| 432 | stats: { | |
| 433 | projects: projectCount, | |
| 434 | projectsThisMonth, | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 435 | openPulls: openCount, |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 436 | }, |
| 437 | week, | |
| 438 | groups, | |
| 439 | titles: shownTitles, | |
| 440 | inboxNeeds: needsYou(unread?.items ?? [], INBOX_SHOWN), | |
| 441 | }, | |
| 442 | }; | |
| 443 | } | |
| 444 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 445 | /** What Code's Overview takes. */ |
| Merge the workspace shell: navigation and phone shell, g1t as orchestrator, agents in roles with audience-checked reads, reactions and custom emoji, live notifications and browser push, the homepage tour (agents 0002, chat 0002) | 446 | export type Loaded = Awaited<ReturnType<typeof loadMissionControl>>["value"]; |
| 447 | ||
| 448 | /** The cookie that holds the viewer's time zone. */ | |
| 449 | export { TZ_COOKIE }; |
This file's history is long; its oldest lines are credited to the oldest commit read.