Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
170 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

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.1/**
2 * The Runners page's shaping: what runs on g1t's cloud now, what waits for
3 * the workspace's own runners, and what each cost this month, from the
4 * actions, work and billing services' own answers. Pure, so it can be
5 * tested; the reads are in ./runners.server.ts.
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 * - **On g1t's cloud now** is the workspace's sandbox runs (checks, the
8 * merge queue, merge checks) that are running
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.9 * (from the work service) and not handed to one of its own runners
10 * (`runner_activity`'s `handed_over`), and its workflow jobs running in
11 * g1t's sandboxes (`cloud_jobs`).
12 * - **This month** is billing's usage report from the first of the month:
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.13 * sandbox time is g1t's cloud, at price (older months may also name
14 * agent sandbox time);
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.15 * self-hosted runner time is the workspace's own, at $0.
16 */
17import type { AgentRun, MeterLine, Price, RunnerActivity, UsageReport } from "@g1t/contracts";
18
19/** One thing running in one of g1t's sandboxes. */
20export type CloudWork = {
21 kind: "agent" | "workflow";
22 id: string;
23 /** What it is: a pull request's title, a job's name. */
24 title: string;
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.25 /** The run's kind for a sandbox run (`checks`, `queue`…); the job's workflow run for a job. */
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.26 detail: string;
27 /** `owner/name`. */
28 repo: string;
29 href: string;
30 startedAt: string | null;
31};
32
33export type CloudNow = {
34 running: CloudWork[];
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.35 /** Sandbox runs and workflow jobs waiting to start in g1t's sandboxes. */
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.36 queued: number;
37 /** A list reached its limit: there may be more than shown. */
38 more: boolean;
39};
40
41/** Waiting for one of the workspace's own runners. */
42export type OwnWaiting = { tasks: number; jobs: number };
43
44/** This month's machine time, from billing's usage report. */
45export type RunnerCost = {
46 from: string;
47 until: string;
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 /** g1t's cloud: sandbox time. */
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.49 cloudSeconds: number;
50 /** At price, before what paid for it. */
51 cloudMicros: number;
52 /** The workspace's own runners: self-hosted runner time, always $0. */
53 ownSeconds: number;
54 /** g1t charges nothing for now: the figures are usage at cost. */
55 free: boolean;
56};
57
58/** A price for time on g1t's cloud, per minute, from the price book. */
59export type CloudPrice = { meter: string; title: string; unit: string; perMinuteMicros: number };
60
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.61/** Billing's meters for machine time (`METERS` in services/billing/src/report.rs); `agent_sandbox` is in older months' reports. */
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.62export const CLOUD_METERS = ["agent_sandbox", "sandbox"] as const;
63export const OWN_METER = "self_hosted";
64
65/** The price book's meters for a sandbox's time: its parts when both are priced, else the one price. */
66const PRICE_PARTS = ["sandbox_base_second", "sandbox_cpu_second"] as const;
67const PRICE_WHOLE = "sandbox_second";
68
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.69/** Where a sandbox run (checks, the merge queue) shows: its pull request, else the repository's pull requests. */
70export function sandboxRunHref(repo: string, number?: number | null): string {
71 return number != null ? `/${repo}/pull/${number}` : `/${repo}/pulls`;
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.72}
73
74/** The page of a workflow run. */
75export function workflowRunHref(repo: string, runId: string): string {
76 return `/${repo}/actions/runs/${runId}`;
77}
78
79const repoName = (run: Pick<AgentRun, "repo">) => `${run.repo.namespace}/${run.repo.name}`;
80
81/**
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.82 * What runs on g1t's cloud now. `runs` are the workspace's active sandbox
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.83 * runs (null when the work service did not answer); `activity` is the
84 * actions service's (null when it did not). Null when neither answered.
85 */
86export function cloudNow(runs: AgentRun[] | null, activity: RunnerActivity | null, runLimit: number, jobLimit: number): CloudNow | null {
87 if (!runs && !activity) return null;
88 const handed = new Set((activity?.handed_over ?? []).map((task) => task.run_id).filter((id): id is string => !!id));
89 const ours = (runs ?? []).filter((run) => !handed.has(run.id));
90 const running: CloudWork[] = [
91 ...ours
92 .filter((run) => run.status === "running")
93 .map((run) => ({
94 kind: "agent" as const,
95 id: run.id,
96 title: run.title ?? (run.number != null ? `#${run.number}` : repoName(run)),
97 detail: run.kind,
98 repo: repoName(run),
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.99 href: sandboxRunHref(repoName(run), run.number),
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.100 startedAt: run.startedAt ?? run.createdAt,
101 })),
102 ...(activity?.cloud_jobs ?? []).map((job) => ({
103 kind: "workflow" as const,
104 id: job.id,
105 title: job.name,
106 detail: job.run_id,
107 repo: job.repo,
108 href: workflowRunHref(job.repo, job.run_id),
109 startedAt: job.started_at,
110 })),
111 ].sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? ""));
112 const queued = ours.filter((run) => run.status === "queued").length + (activity?.cloud_jobs_queued ?? 0);
113 const more = (runs?.length ?? 0) >= runLimit || (activity?.cloud_jobs.length ?? 0) >= jobLimit;
114 return { running, queued, more };
115}
116
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.117/** What waits for one of the workspace's own runners: sandbox runs not yet taken, and workflow jobs. */
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.118export function ownWaiting(activity: RunnerActivity | null): OwnWaiting | null {
119 if (!activity) return null;
120 return { tasks: activity.handed_over.filter((task) => task.status === "queued").length, jobs: activity.self_hosted_jobs_queued };
121}
122
123/** This month's machine time from billing's usage report; null when billing did not answer. */
124export function runnerCost(report: UsageReport | null): RunnerCost | null {
125 if (!report) return null;
126 const meters: MeterLine[] = report.products.flatMap((product) => product.meters);
127 const sum = (keys: readonly string[], pick: (meter: MeterLine) => number) =>
128 meters.filter((meter) => keys.includes(meter.key)).reduce((total, meter) => total + pick(meter), 0);
129 return {
130 from: report.from,
131 until: report.until,
132 cloudSeconds: sum(CLOUD_METERS, (meter) => meter.quantity),
133 cloudMicros: sum(CLOUD_METERS, (meter) => meter.micros + (meter.pendingMicros ?? 0)),
134 ownSeconds: sum([OWN_METER], (meter) => meter.quantity),
135 free: report.free,
136 };
137}
138
139/** The price of a minute on g1t's cloud, as the price book has it; null when it has none. */
140export function cloudPrices(prices: Price[] | null): CloudPrice[] | null {
141 if (!prices) return null;
142 const perMinute = (price: Price): CloudPrice => ({
143 meter: price.meter,
144 title: price.title,
145 unit: price.unit.replace(/second$/, "minute"),
146 perMinuteMicros: price.priceMicros * 60,
147 });
148 const parts = PRICE_PARTS.map((meter) => prices.find((price) => price.meter === meter));
149 if (parts.every(Boolean)) return parts.map((price) => perMinute(price!));
150 const whole = prices.find((price) => price.meter === PRICE_WHOLE);
151 return whole ? [perMinute(whole)] : null;
152}
153
154/** Seconds as people read machine time: "45s", "12 min", "3.4 h". */
155export function machineTime(seconds: number): string {
156 if (seconds <= 0) return "0 min";
157 if (seconds < 60) return `${Math.round(seconds)}s`;
158 const minutes = seconds / 60;
159 if (minutes < 100) return `${Math.round(minutes)} min`;
160 const hours = minutes / 60;
161 return `${hours < 10 ? hours.toFixed(1) : Math.round(hours).toLocaleString("en-US")} h`;
162}
163
164/** A price per minute, to a tenth of a cent or finer: "$0.0012". */
165export function perMinute(micros: number): string {
166 if (micros <= 0) return "$0";
167 const dollars = micros / 1_000_000;
168 const digits = dollars >= 0.01 ? 3 : Math.min(6, Math.max(4, 2 - Math.floor(Math.log10(dollars))));
169 return `$${dollars.toFixed(digits).replace(/0+$/, "").replace(/\.$/, "")}`;
170}