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.
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 1 | import { Bot, Boxes, Cpu, Plus, ServerCog, ShieldAlert, Trash2 } from "lucide-react"; |
| Merge branch 'worktree-agent-a985c01f43538af01' | 2 | import { useEffect, useState } from "react"; |
| 3 | import { Form, Link } from "react-router"; | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 4 | |
| Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it. | 5 | import { DESKTOP_LABEL, type RegistrationToken, RUNNER_DOWNLOADS, RUNNER_FILES, RUNNER_IMAGE, type Runner, type RunnerGroup, type RunnerIsolation } from "@g1t/contracts"; |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 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 { sandboxRunHref, workflowRunHref } from "../lib/runners"; |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 8 | import type { RunnersAction, RunnersData } from "../lib/runners.server"; |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 9 | import { CopyLine, EmptyState, ErrorText, Field, Input, SubmitButton, TimeAgo } from "./ui"; |
| Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it. | 10 | import { Badge, type BadgeTone } from "./ui/badge"; |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 11 | import { Button } from "./ui/button"; |
| 12 | import { Card } from "./ui/card"; | |
| The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work. | 13 | import { Checkbox } from "./ui/checkbox"; |
| 14 | import { RadioGroup, RadioOption } from "./ui/radio-group"; | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 15 | import { SelectField } from "./ui/select"; |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 16 | import { Switch } from "./ui/switch"; |
| 17 | import { Tabs, TabsList, TabsTrigger } from "./ui/tabs"; | |
| Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it. | 18 | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 19 | |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 20 | /** Online and waiting for work reads as Idle; online and working, Busy. */ |
| 21 | export const STATUS: Record<Runner["status"], { dot: string; text: string; label: string }> = { | |
| 22 | online: { dot: "bg-success", text: "text-success", label: "Idle" }, | |
| 23 | busy: { dot: "bg-info", text: "text-info", label: "Busy" }, | |
| 24 | offline: { dot: "bg-faint", text: "text-muted", label: "Offline" }, | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 25 | }; |
| 26 | ||
| 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. | 27 | /** Where a runner's current work is: its workflow run, or the repository a sandbox run is for. */ |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 28 | function workHref(work: NonNullable<Runner["work"]>): string | null { |
| 29 | if (!work.runId || !work.repo) return 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. | 30 | return work.kind === "workflow" ? workflowRunHref(work.repo, work.runId) : sandboxRunHref(work.repo); |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 31 | } |
| 32 | ||
| 33 | export const OS_NAMES: Record<Runner["os"], string> = { linux: "Linux", macos: "macOS", windows: "Windows" }; | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 34 | |
| Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it. | 35 | /** How a runner keeps work from its machine, as a badge and what it means. */ |
| 36 | export const ISOLATION: Record<RunnerIsolation, { label: string; tone: BadgeTone; about: string }> = { | |
| 37 | "nested-container": { | |
| 38 | label: "Sandboxed", | |
| 39 | tone: "success", | |
| 40 | about: "Each job runs in a fresh container inside the runner's own sandboxed container. Neither reaches the machine.", | |
| 41 | }, | |
| 42 | vm: { | |
| 43 | label: "VM per job", | |
| 44 | tone: "success", | |
| 45 | about: "Each job runs in a throwaway virtual machine of its own, such as Windows Sandbox, with no folder of the machine's.", | |
| 46 | }, | |
| 47 | container: { | |
| 48 | label: "Shared Docker", | |
| 49 | tone: "warn", | |
| 50 | about: "Each job runs in a container, but the runner shares the machine's Docker Engine, so anything that takes it over controls the machine.", | |
| 51 | }, | |
| 52 | host: { | |
| 53 | label: "Unsandboxed", | |
| 54 | tone: "danger", | |
| 55 | about: "Jobs run directly on the machine (host mode). It is not given public repositories' work.", | |
| 56 | }, | |
| 57 | }; | |
| 58 | ||
| 59 | function IsolationBadge({ isolation }: { isolation: Runner["isolation"] }) { | |
| 60 | if (!isolation) return null; | |
| 61 | const level = ISOLATION[isolation]; | |
| 62 | if (!level) return null; | |
| 63 | return ( | |
| 64 | <Tooltip> | |
| 65 | <TooltipTrigger asChild> | |
| 66 | <Badge size="md" tone={level.tone} tabIndex={0}> | |
| 67 | {level.label} | |
| 68 | </Badge> | |
| 69 | </TooltipTrigger> | |
| 70 | <TooltipContent className="max-w-72">{level.about}</TooltipContent> | |
| 71 | </Tooltip> | |
| 72 | ); | |
| 73 | } | |
| 74 | ||
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 75 | function Section({ icon, title, about, children }: { icon: React.ReactNode; title: string; about: React.ReactNode; children: React.ReactNode }) { |
| 76 | return ( | |
| 77 | <section className="grid gap-x-10 gap-y-4 border-t border-line pt-8 first:border-t-0 first:pt-0 lg:grid-cols-[16rem_1fr]"> | |
| 78 | <div> | |
| 79 | <h2 className="flex items-center gap-2 font-medium"> | |
| 80 | {icon} | |
| 81 | {title} | |
| 82 | </h2> | |
| 83 | <div className="mt-1 text-sm text-muted">{about}</div> | |
| 84 | </div> | |
| 85 | <div className="min-w-0">{children}</div> | |
| 86 | </section> | |
| 87 | ); | |
| 88 | } | |
| 89 | ||
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 90 | export function RunnerRow({ runner, manage }: { runner: Runner; manage: boolean }) { |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 91 | const status = STATUS[runner.status]; |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 92 | const href = runner.work ? workHref(runner.work) : null; |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 93 | return ( |
| 94 | <li className="flex flex-wrap items-start gap-x-4 gap-y-2 px-4 py-3"> | |
| 95 | <span className={`mt-1.5 size-2 shrink-0 rounded-full ${status.dot}`} aria-hidden /> | |
| 96 | <div className="min-w-0 grow basis-60"> | |
| 97 | <div className="flex flex-wrap items-center gap-x-2 gap-y-1"> | |
| 98 | <span className="font-medium">{runner.name}</span> | |
| Merge the desktop app's runner: it starts with the app for the signed-in person, sandboxed by default and running nothing where no sandbox is available yet, registers through the site, keeps itself up to date, and says everywhere what it is doing, why it is not, and how to fix it. | 99 | <span className={`text-xs font-medium ${status.text}`}> |
| 100 | {/* A desktop app's runner runs while g1t is open there: offline is g1t closed. */} | |
| 101 | {runner.status === "offline" && runner.labels.includes(DESKTOP_LABEL) ? `g1t is closed on ${runner.name}` : status.label} | |
| 102 | </span> | |
| Merge the runner sandbox: g1t-runner sandbox up makes a person's own computer a self-hosted runner that nothing a job runs can get out of. Linux jobs run in a Linux VM of the runner's own (a WSL 2 distribution with no host mounts, interop or Docker socket on Windows; vfkit on macOS; Sysbox, or QEMU and KVM, on Linux), each in a fresh container under Sysbox inside it; on Windows 11 Pro and up, Windows jobs each run in a Windows Sandbox discarded after the job. Runners report their isolation, and the Runners page shows it. | 103 | <IsolationBadge isolation={runner.isolation} /> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 104 | {runner.ephemeral && <Badge size="md">ephemeral</Badge>} |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 105 | {runner.group && <span className="text-xs text-faint">in {runner.group}</span>} |
| 106 | </div> | |
| 107 | <div className="mt-1.5 flex flex-wrap gap-1"> | |
| 108 | {runner.labels.map((label) => ( | |
| 109 | <span key={label} className="rounded-md bg-raised px-1.5 py-0.5 font-mono text-[0.6875rem] text-fg-soft"> | |
| 110 | {label} | |
| 111 | </span> | |
| 112 | ))} | |
| 113 | </div> | |
| 114 | {runner.work && ( | |
| 115 | <p className="mt-1.5 truncate text-xs text-muted"> | |
| 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. | 116 | {runner.work.kind === "workflow" ? "Running " : "Sandbox run: "} |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 117 | {href ? ( |
| 118 | <Link to={href} className="text-fg hover:underline"> | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 119 | {runner.work.name} |
| 120 | </Link> | |
| 121 | ) : ( | |
| 122 | <span className="text-fg">{runner.work.name}</span> | |
| 123 | )} | |
| 124 | {runner.work.repo && <> in {runner.work.repo}</>} | |
| 125 | </p> | |
| 126 | )} | |
| 127 | </div> | |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 128 | <div className="ml-6 grow text-xs text-faint sm:ml-0 sm:shrink-0 sm:grow-0 sm:text-right"> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 129 | <div> |
| 130 | {OS_NAMES[runner.os]} {runner.arch} | |
| 131 | {runner.version && <> · {runner.version}</>} | |
| 132 | </div> | |
| 133 | <div className="mt-0.5">{runner.lastSeenAt ? <>Seen <TimeAgo at={runner.lastSeenAt} /></> : "Never seen"}</div> | |
| 134 | </div> | |
| 135 | {manage && ( | |
| 136 | <Form | |
| 137 | method="post" | |
| 138 | className="shrink-0" | |
| 139 | onSubmit={(event) => { | |
| 140 | if (!confirm(`Remove ${runner.name}? Its credential stops working at once${runner.work ? ", and the job it is running fails" : ""}.`)) event.preventDefault(); | |
| 141 | }} | |
| 142 | > | |
| 143 | <input type="hidden" name="intent" value="remove" /> | |
| 144 | <input type="hidden" name="id" value={runner.id} /> | |
| 145 | <input type="hidden" name="name" value={runner.name} /> | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 146 | <SubmitButton |
| 147 | icon | |
| 148 | match={{ intent: "remove", id: runner.id }} | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 149 | aria-label={`Remove ${runner.name}`} |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 150 | variant="ghost" |
| 151 | size="inline" | |
| 152 | className="p-1.5 text-faint hover:text-danger" | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 153 | > |
| 154 | <Trash2 size={15} /> | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 155 | </SubmitButton> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 156 | </Form> |
| 157 | )} | |
| 158 | </li> | |
| 159 | ); | |
| 160 | } | |
| 161 | ||
| 162 | type Platform = "linux" | "macos" | "windows" | "docker"; | |
| 163 | ||
| 164 | /** The copy-paste steps for one platform. */ | |
| 165 | export function installSteps(platform: Platform, token: RegistrationToken | null, arch: "x64" | "arm64" = "x64"): { title: string; lines: string[] }[] { | |
| 166 | const url = token?.url ?? "https://g1t.sh"; | |
| 167 | const value = token?.token ?? "<registration token>"; | |
| 168 | const register = (exe: string) => `${exe} register --url ${url} --token ${value}`; | |
| 169 | switch (platform) { | |
| 170 | case "linux": { | |
| 171 | const file = arch === "arm64" ? RUNNER_FILES["linux-arm64"] : RUNNER_FILES["linux-x64"]; | |
| 172 | return [ | |
| 173 | { title: "Download", lines: [`curl -fsSLo g1t-runner ${RUNNER_DOWNLOADS}/latest/${file} && chmod +x g1t-runner`] }, | |
| 174 | { title: "Register", lines: [register("./g1t-runner")] }, | |
| 175 | { title: "Run it, or install it as a service", lines: ["./g1t-runner run", "sudo ./g1t-runner service install"] }, | |
| 176 | ]; | |
| 177 | } | |
| 178 | case "macos": { | |
| 179 | const file = arch === "x64" ? RUNNER_FILES["macos-x64"] : RUNNER_FILES["macos-arm64"]; | |
| 180 | return [ | |
| 181 | { title: "Download", lines: [`curl -fsSLo g1t-runner ${RUNNER_DOWNLOADS}/latest/${file} && chmod +x g1t-runner`] }, | |
| 182 | { title: "Register", lines: [register("./g1t-runner")] }, | |
| 183 | { title: "Run it, or install it as a launch agent", lines: ["./g1t-runner run", "./g1t-runner service install"] }, | |
| 184 | ]; | |
| 185 | } | |
| 186 | case "windows": | |
| 187 | return [ | |
| 188 | { | |
| 189 | title: "Download (PowerShell)", | |
| 190 | lines: [`Invoke-WebRequest ${RUNNER_DOWNLOADS}/latest/${RUNNER_FILES["windows-x64"]} -OutFile g1t-runner.exe`], | |
| 191 | }, | |
| 192 | { title: "Register", lines: [register(".\\g1t-runner.exe")] }, | |
| 193 | { title: "Run it, or install it as a service (as Administrator)", lines: [".\\g1t-runner.exe run", ".\\g1t-runner.exe service install"] }, | |
| 194 | ]; | |
| 195 | case "docker": | |
| 196 | return [ | |
| 197 | { | |
| Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9 | 198 | title: "Register and run under Sysbox, each job in a container inside the runner's", |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 199 | lines: [ |
| Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9 | 200 | `docker run -d --name g1t-runner --restart unless-stopped --runtime=sysbox-runc -v g1t-runner:/data ${RUNNER_IMAGE} register-and-run --url ${url} --token ${value}`, |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 201 | ], |
| 202 | }, | |
| 203 | ]; | |
| 204 | } | |
| 205 | } | |
| 206 | ||
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 207 | export function NewRunner({ token, groups, repoScoped }: { token: RegistrationToken | null; groups: RunnerGroup[]; repoScoped: boolean }) { |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 208 | const [platform, setPlatform] = useState<Platform>("linux"); |
| 209 | const [arch, setArch] = useState<"x64" | "arm64">("x64"); | |
| 210 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 211 | <Card className="space-y-4 p-4"> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 212 | {!token ? ( |
| 213 | <Form method="post" className="flex flex-wrap items-end gap-3"> | |
| 214 | <input type="hidden" name="intent" value="token" /> | |
| 215 | {!repoScoped && groups.length > 1 && ( | |
| 216 | <Field label="Group"> | |
| Chat controls, public profiles, shadcn selects, and no Docs tab in a project | 217 | <SelectField |
| 218 | name="group" | |
| 219 | defaultValue={groups.find((g) => g.default)?.name ?? groups[0]?.name} | |
| 220 | className="w-auto min-w-40" | |
| 221 | options={groups.map((group) => ({ value: group.name, label: group.name }))} | |
| 222 | /> | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 223 | </Field> |
| 224 | )} | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 225 | <SubmitButton match={{ intent: "token" }} pending="Making a token…"> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 226 | <Plus size={15} /> |
| 227 | New runner | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 228 | </SubmitButton> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 229 | <p className="basis-full text-xs text-faint">Makes a registration token that lasts an hour and registers any number of runners. It is shown once.</p> |
| 230 | </Form> | |
| 231 | ) : ( | |
| 232 | <p className="text-sm"> | |
| 233 | Registration token made. It expires <TimeAgoFuture at={token.expiresAt} /> | |
| 234 | {token.group ? <>; its runners join {token.group}</> : null}. Copy it now: it is not shown again. | |
| 235 | </p> | |
| 236 | )} | |
| 237 | <Tabs value={platform} onValueChange={(value) => setPlatform(value as Platform)}> | |
| 238 | <div className="flex flex-wrap items-center gap-3"> | |
| 239 | <TabsList aria-label="Where the runner runs"> | |
| 240 | <TabsTrigger value="linux">Linux</TabsTrigger> | |
| 241 | <TabsTrigger value="macos">macOS</TabsTrigger> | |
| 242 | <TabsTrigger value="windows">Windows</TabsTrigger> | |
| 243 | <TabsTrigger value="docker">Docker</TabsTrigger> | |
| 244 | </TabsList> | |
| 245 | {(platform === "linux" || platform === "macos") && ( | |
| 246 | <TabsList aria-label="Architecture"> | |
| 247 | <button | |
| 248 | type="button" | |
| 249 | onClick={() => setArch("x64")} | |
| 250 | className={`rounded-md px-2.5 py-1 text-xs font-medium ${arch === "x64" ? "bg-raised text-fg" : "text-muted hover:text-fg"}`} | |
| 251 | > | |
| 252 | {platform === "macos" ? "Intel" : "x64"} | |
| 253 | </button> | |
| 254 | <button | |
| 255 | type="button" | |
| 256 | onClick={() => setArch("arm64")} | |
| 257 | className={`rounded-md px-2.5 py-1 text-xs font-medium ${arch === "arm64" ? "bg-raised text-fg" : "text-muted hover:text-fg"}`} | |
| 258 | > | |
| 259 | {platform === "macos" ? "Apple silicon" : "arm64"} | |
| 260 | </button> | |
| 261 | </TabsList> | |
| 262 | )} | |
| 263 | </div> | |
| 264 | </Tabs> | |
| 265 | <ol className="space-y-3"> | |
| 266 | {installSteps(platform, token, platform === "macos" && arch === "x64" ? "x64" : arch).map((step, at) => ( | |
| 267 | <li key={step.title}> | |
| 268 | <p className="mb-1.5 text-xs text-muted"> | |
| 269 | {at + 1}. {step.title} | |
| 270 | </p> | |
| 271 | <div className="space-y-1.5"> | |
| 272 | {step.lines.map((line) => ( | |
| 273 | <CopyLine key={line} text={line} prompt /> | |
| 274 | ))} | |
| 275 | </div> | |
| 276 | </li> | |
| 277 | ))} | |
| 278 | </ol> | |
| 279 | <p className="text-xs text-faint"> | |
| Merge branch 'worktree-agent-ac79a505eca749d4d' into claude/cool-newton-7nm8r9 | 280 | Each job runs in a container of its own; on Linux, run the Docker command under Sysbox so neither the runner nor its jobs reach the machine.{" "} |
| 281 | <code className="font-mono">--no-docker</code>, which runs jobs on the machine itself, is deprecated. Add{" "} | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 282 | <code className="font-mono">--labels gpu,cuda</code> for jobs to ask for, or <code className="font-mono">--ephemeral</code> for a runner that takes |
| 283 | one job and removes itself.{" "} | |
| 284 | <a href="https://docs.g1t.sh/guides/self-hosted-runners/" className="underline underline-offset-2"> | |
| 285 | Self-hosted runners | |
| 286 | </a> | |
| 287 | </p> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 288 | </Card> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 289 | ); |
| 290 | } | |
| 291 | ||
| 292 | /** "in 59m", for a time still to come. */ | |
| 293 | function TimeAgoFuture({ at }: { at: string }) { | |
| 294 | const minutes = Math.max(0, Math.round((new Date(at).getTime() - Date.now()) / 60_000)); | |
| 295 | return ( | |
| 296 | <time dateTime={at} suppressHydrationWarning> | |
| 297 | {minutes >= 60 ? "in an hour" : `in ${minutes}m`} | |
| 298 | </time> | |
| 299 | ); | |
| 300 | } | |
| 301 | ||
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 302 | export function Groups({ |
| Merge branch 'worktree-agent-a985c01f43538af01' | 303 | groups, |
| 304 | repositories, | |
| 305 | manage, | |
| 306 | action, | |
| 307 | }: { | |
| 308 | groups: RunnerGroup[]; | |
| 309 | repositories: string[]; | |
| 310 | manage: boolean; | |
| 311 | action: RunnersAction | undefined; | |
| 312 | }) { | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 313 | const [editing, setEditing] = useState<string | null>(null); |
| Merge branch 'worktree-agent-a985c01f43538af01' | 314 | // A group saved closes its form: the list above now shows it as saved. |
| 315 | useEffect(() => { | |
| 316 | if (action?.notice) setEditing(null); | |
| 317 | }, [action]); | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 318 | return ( |
| 319 | <div className="space-y-3"> | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 320 | <Card asChild divided> |
| 321 | <ul> | |
| 322 | {groups.map((group) => ( | |
| 323 | <li key={group.id} className="px-4 py-3"> | |
| 324 | <div className="flex flex-wrap items-center gap-x-3 gap-y-1"> | |
| 325 | <span className="font-medium">{group.name}</span> | |
| 326 | {group.default && <Badge size="md">default</Badge>} | |
| 327 | <span className="text-xs text-muted"> | |
| 328 | {group.runners} {group.runners === 1 ? "runner" : "runners"} ·{" "} | |
| 329 | {group.repositories.length === 0 ? "every repository" : group.repositories.join(", ")} | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 330 | </span> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 331 | {manage && ( |
| 332 | <span className="ml-auto flex items-center gap-2"> | |
| 333 | <Button type="button" variant="outline" onClick={() => setEditing(editing === group.id ? null : group.id)}> | |
| 334 | {editing === group.id ? "Close" : "Edit"} | |
| 335 | </Button> | |
| 336 | {!group.default && ( | |
| 337 | <Form method="post" onSubmit={(event) => { if (!confirm(`Delete ${group.name}? Its runners join the default group.`)) event.preventDefault(); }}> | |
| 338 | <input type="hidden" name="intent" value="delete-group" /> | |
| 339 | <input type="hidden" name="id" value={group.id} /> | |
| 340 | <SubmitButton variant="outline" match={{ intent: "delete-group", id: group.id }} pending="Deleting…"> | |
| 341 | Delete | |
| 342 | </SubmitButton> | |
| 343 | </Form> | |
| 344 | )} | |
| 345 | </span> | |
| 346 | )} | |
| 347 | </div> | |
| 348 | {editing === group.id && <GroupForm group={group} repositories={repositories} />} | |
| 349 | </li> | |
| 350 | ))} | |
| 351 | </ul> | |
| 352 | </Card> | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 353 | {manage && (editing === "new" ? <GroupForm group={null} repositories={repositories} /> : ( |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 354 | <Button type="button" variant="outline" onClick={() => setEditing("new")}> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 355 | <Plus size={15} /> |
| 356 | New group | |
| 357 | </Button> | |
| 358 | ))} | |
| 359 | </div> | |
| 360 | ); | |
| 361 | } | |
| 362 | ||
| 363 | function GroupForm({ group, repositories }: { group: RunnerGroup | null; repositories: string[] }) { | |
| 364 | const [reach, setReach] = useState(group && group.repositories.length > 0 ? "some" : "all"); | |
| 365 | return ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 366 | <Card asChild tone="bg" radius="lg" className="mt-3 space-y-3 p-3"> |
| 367 | <Form method="post"> | |
| 368 | <input type="hidden" name="intent" value="group" /> | |
| 369 | {group && <input type="hidden" name="id" value={group.id} />} | |
| 370 | <Field label="Name"> | |
| 371 | <Input name="name" defaultValue={group?.name ?? ""} placeholder="GPU machines" maxLength={64} required={!group} /> | |
| 372 | </Field> | |
| 373 | <fieldset className="space-y-2 text-sm"> | |
| 374 | <legend className="mb-1 text-xs text-muted">Repositories that may use its runners</legend> | |
| 375 | <RadioGroup name="reach" value={reach} onValueChange={setReach}> | |
| 376 | <RadioOption value="all" label="Every repository in the workspace" /> | |
| 377 | <RadioOption value="some" label="Only these" /> | |
| 378 | </RadioGroup> | |
| 379 | {reach === "some" && ( | |
| 380 | <div className="grid gap-1 pl-6 sm:grid-cols-2"> | |
| 381 | {repositories.map((name) => ( | |
| 382 | <label key={name} className="flex min-h-8 cursor-pointer items-center gap-2 font-mono text-xs"> | |
| 383 | <Checkbox name="repository" value={name} defaultChecked={group?.repositories.includes(name)} /> | |
| 384 | {name} | |
| 385 | </label> | |
| 386 | ))} | |
| 387 | </div> | |
| 388 | )} | |
| 389 | </fieldset> | |
| 390 | <SubmitButton match={{ intent: "group", id: group?.id }} pending="Saving…"> | |
| 391 | Save group | |
| 392 | </SubmitButton> | |
| 393 | </Form> | |
| 394 | </Card> | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 395 | ); |
| 396 | } | |
| 397 | ||
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 398 | export function RunnerSettingsForm({ data, manage, scope }: { data: RunnersData; manage: boolean; scope: "workspace" | "project" }) { |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 399 | const settings = data.settings; |
| 400 | const [agents, setAgents] = useState(settings?.agentsOnSelfHosted ?? false); | |
| 401 | const [forks, setForks] = useState(settings?.forkPullRequests ?? false); | |
| 402 | if (!settings) return null; | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 403 | // The intent comes from the button pressed: a hidden one before it would |
| 404 | // win the form's first "intent", and Follow the workspace would only save. | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 405 | return ( |
| 406 | <Form method="post" className="space-y-4"> | |
| 407 | {scope === "project" && settings.inherited && ( | |
| 408 | <p className="text-sm text-muted">These are the workspace's settings. Saving here gives this project its own.</p> | |
| 409 | )} | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 410 | <Card asChild className="flex items-start justify-between gap-4 p-4"> |
| 411 | <label> | |
| 412 | <span className="min-w-0"> | |
| 413 | <span className="flex items-center gap-2 text-sm font-medium"> | |
| 414 | <Bot size={15} className="text-muted" /> | |
| 415 | Run g1t's work on self-hosted runners | |
| 416 | </span> | |
| 417 | <span className="mt-1 block text-sm text-muted"> | |
| 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. | 418 | Checks, merge checks and the merge queue run on your runners with these labels instead of g1t's sandboxes. The machine time is |
| 419 | free. Sandbox runs need Docker, or a | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 420 | Linux runner. |
| 421 | </span> | |
| 422 | <span className="mt-3 block max-w-sm"> | |
| 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. | 423 | <Input name="agentLabels" defaultValue={settings.agentLabels.join(", ")} disabled={!manage} aria-label="Labels for sandbox runs" /> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 424 | </span> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 425 | </span> |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 426 | <Switch name="agents" checked={agents} onCheckedChange={setAgents} disabled={!manage} aria-label="Run g1t's work on self-hosted runners" /> |
| 427 | </label> | |
| 428 | </Card> | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 429 | <label className={`flex items-start justify-between gap-4 rounded-xl border p-4 ${forks ? "border-danger/50 bg-danger/5" : "border-line bg-surface"}`}> |
| 430 | <span className="min-w-0"> | |
| 431 | <span className="flex items-center gap-2 text-sm font-medium"> | |
| 432 | <ShieldAlert size={15} className={forks ? "text-danger" : "text-muted"} /> | |
| 433 | Let pull requests from forks use self-hosted runners | |
| 434 | </span> | |
| 435 | <span className="mt-1 block text-sm text-muted"> | |
| 436 | Off by default, and best left off. Anyone who can open a pull request could then run any code on your machines, read what they can | |
| 437 | reach, and leave something behind for the next job. Their jobs never get secrets either way. | |
| 438 | </span> | |
| 439 | </span> | |
| 440 | <Switch name="forks" checked={forks} onCheckedChange={setForks} disabled={!manage} /> | |
| 441 | </label> | |
| 442 | {manage && ( | |
| 443 | <div className="flex flex-wrap gap-3"> | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 444 | <SubmitButton name="intent" value="settings" pending="Saving…"> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 445 | Save |
| Merge branch 'worktree-agent-a985c01f43538af01' | 446 | </SubmitButton> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 447 | {scope === "project" && !settings.inherited && ( |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 448 | <SubmitButton name="intent" value="inherit" variant="outline" pending="Following…"> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 449 | Follow the workspace |
| Merge branch 'worktree-agent-a985c01f43538af01' | 450 | </SubmitButton> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 451 | )} |
| 452 | </div> | |
| 453 | )} | |
| 454 | </Form> | |
| 455 | ); | |
| 456 | } | |
| 457 | ||
| 458 | /** Settings, Runners: a workspace's, or a project's own. */ | |
| 459 | export function RunnersPanel({ | |
| 460 | data, | |
| 461 | action, | |
| 462 | scope, | |
| 463 | manage, | |
| 464 | }: { | |
| 465 | data: RunnersData; | |
| 466 | action: RunnersAction | undefined; | |
| 467 | scope: "workspace" | "project"; | |
| 468 | manage: boolean; | |
| 469 | }) { | |
| 470 | const online = data.runners.filter((runner) => runner.status !== "offline").length; | |
| 471 | return ( | |
| 472 | <div className="max-w-5xl space-y-8"> | |
| 473 | <div className="min-h-6"> | |
| Merge branch 'worktree-agent-ad7c6d88d93adc817' | 474 | {action?.notice && <p className="text-sm text-success">{action.notice}</p>} |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 475 | <ErrorText>{action?.error ?? data.error ?? null}</ErrorText> |
| 476 | </div> | |
| 477 | <Section | |
| 478 | icon={<ServerCog size={15} className="text-muted" />} | |
| 479 | title="Runners" | |
| 480 | about={ | |
| 481 | <> | |
| 482 | <p> | |
| 483 | Your own machines, for jobs with <code className="font-mono text-fg">runs-on: self-hosted</code>. Their time is free on every plan. | |
| 484 | </p> | |
| 485 | <p className="mt-2"> | |
| 486 | {data.runners.length === 0 ? "None yet." : `${online} of ${data.runners.length} online.`} | |
| 487 | </p> | |
| 488 | </> | |
| 489 | } | |
| 490 | > | |
| 491 | {data.runners.length === 0 ? ( | |
| 492 | <EmptyState title="No self-hosted runners"> | |
| 493 | Add one below. It connects out to g1t; nothing needs to reach the machine. | |
| 494 | </EmptyState> | |
| 495 | ) : ( | |
| The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after. | 496 | <Card asChild divided> |
| 497 | <ul> | |
| 498 | {data.runners.map((runner) => ( | |
| 499 | <RunnerRow key={runner.id} runner={runner} manage={manage && (scope === "workspace" || runner.repo !== null)} /> | |
| 500 | ))} | |
| 501 | </ul> | |
| 502 | </Card> | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 503 | )} |
| 504 | </Section> | |
| 505 | {manage && ( | |
| 506 | <Section | |
| 507 | icon={<Plus size={15} className="text-muted" />} | |
| 508 | title="New runner" | |
| 509 | about="Download g1t-runner, register it with a token made here, and start it. Linux, macOS and Windows, x64 and arm64." | |
| 510 | > | |
| 511 | <NewRunner token={action?.token ?? null} groups={data.groups} repoScoped={scope === "project"} /> | |
| 512 | </Section> | |
| 513 | )} | |
| 514 | {scope === "workspace" && ( | |
| 515 | <Section | |
| 516 | icon={<Boxes size={15} className="text-muted" />} | |
| 517 | title="Groups" | |
| 518 | about="Which repositories may use which runners. A runner joins the default group, every repository, unless its token names another." | |
| 519 | > | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 520 | <Groups groups={data.groups} repositories={data.repositories} manage={manage} action={action} /> |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 521 | </Section> |
| 522 | )} | |
| 523 | <Section | |
| 524 | icon={<Cpu size={15} className="text-muted" />} | |
| 525 | title="Where work runs" | |
| 526 | about={scope === "workspace" ? "For every project in the workspace, unless a project says otherwise." : "For this project."} | |
| 527 | > | |
| Merge branch 'worktree-agent-a985c01f43538af01' | 528 | {/* Keyed to what is saved, so following the workspace again shows its settings, not the switches as they were. */} |
| The Runners page shows the machines a workspace's agents and workflow jobs run on, in Workspace under Compute: g1t's cloud beside your own runners, what each is running now with a link to it, what's waiting, this month's machine time and its cost (your own runners' free), where agent work and workflow jobs run, adding a runner, and groups; runner_activity in Actions says what runs where, work handed to your runners keeps its agent run, and the self-hosted runners guide says how. | 529 | <RunnerSettingsForm |
| Merge branch 'worktree-agent-a985c01f43538af01' | 530 | key={data.settings ? `${data.settings.inherited}:${data.settings.agentsOnSelfHosted}:${data.settings.forkPullRequests}:${data.settings.agentLabels.join(",")}` : "none"} |
| 531 | data={data} | |
| 532 | manage={manage} | |
| 533 | scope={scope} | |
| 534 | /> | |
| Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less charged | 535 | </Section> |
| 536 | </div> | |
| 537 | ); | |
| 538 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.