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
289 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.

Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides1import type { User } from "./identity";
2import type { RepoPath } from "./repos";
3import type { Result } from "./result";
4
5/**
6 * Self-hosted runners: a workspace's or a repository's own machines, which
7 * run its workflow jobs (and, when it says so, its agents' work) instead of
8 * g1t's sandboxes, at $0. Mirrors `crates/contracts/src/runners.rs`.
9 */
10
11export type RunnerStatus = "online" | "busy" | "offline";
12
13/** What a runner is doing now. */
14export type RunnerWork = {
15 kind: "workflow" | "agent";
16 id: string;
17 name: string;
18 repo: string | null;
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.19 /** The workflow run the job is in; for `agent` work, the agent run it is, when known. */
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides20 runId: string | null;
21 startedAt: string | null;
22};
23
24export type Runner = {
25 id: string;
26 name: string;
27 workspace: string;
28 /** `owner/name` for a repository's own runner; null for the workspace's. */
29 repo: string | null;
30 group: string | null;
31 labels: string[];
32 os: "linux" | "macos" | "windows";
33 arch: "x64" | "arm64";
34 version: string;
35 ephemeral: boolean;
36 status: RunnerStatus;
37 work: RunnerWork | null;
38 lastSeenAt: string | null;
39 createdAt: string;
40 createdBy: string | null;
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.41 /** How it keeps work from its machine, as it last said; null for a runner from before runners said. */
42 isolation?: RunnerIsolation | null;
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides43};
44
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.45/**
46 * `nested-container`: a container per job inside the runner's own sandboxed container (or VM).
47 * `vm`: a throwaway VM per job, native to the machine's OS (Windows Sandbox).
48 * `container`: a container per job on a Docker Engine shared with the machine.
49 * `host`: directly on the machine, unsandboxed.
50 */
51export type RunnerIsolation = "nested-container" | "vm" | "container" | "host";
52
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides53export type RunnerGroup = {
54 id: string;
55 name: string;
56 default: boolean;
57 /** Repository names; empty is every repository in the workspace. */
58 repositories: string[];
59 runners: number;
60 updatedAt: string;
61};
62
63/** Shown once. */
64export type RegistrationToken = {
65 token: string;
66 expiresAt: string;
67 workspace: string;
68 repo: string | null;
69 group: string | null;
70 url: string;
71};
72
73export type RunnerSettings = {
74 agentsOnSelfHosted: boolean;
75 agentLabels: string[];
76 forkPullRequests: boolean;
77 /** For a repository: these are its workspace's. */
78 inherited: boolean;
79};
80
81export type RunnersOwner = { repo: RepoPath } | { workspace: string };
82
83export type RunnerSettingsChange = {
84 agents_on_self_hosted?: boolean;
85 agent_labels?: string[];
86 fork_pull_requests?: boolean;
87 inherit?: boolean;
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/** A workflow job running in one of g1t's sandboxes. Snake case, as `runner_activity` answers. */
91export type CloudJob = { id: string; name: string; run_id: string; repo: string; started_at: string | null };
92
93/** Agent work handed to one of the workspace's own runners, waiting for one or taken. */
94export type HandedOverTask = {
95 id: string;
96 /** The agent run it is; null for work handed over before runs were noted. */
97 run_id: string | null;
98 kind: string;
99 title: string;
100 repo: string;
101 status: "queued" | "in_progress";
102 runner_name: string | null;
103 created_at: string;
104 started_at: string | null;
105};
106
107/** What runs for a workspace now, in g1t's sandboxes and on its own runners (`runner_activity`). */
108export type RunnerActivity = {
109 /** At most `RUNNER_ACTIVITY_LIMIT`, newest first. */
110 cloud_jobs: CloudJob[];
111 cloud_jobs_queued: number;
112 handed_over: HandedOverTask[];
113 self_hosted_jobs_queued: number;
114};
115
116/** The most jobs and tasks `runner_activity` lists of each: `ACTIVITY_LIMIT` in the actions service. */
117export const RUNNER_ACTIVITY_LIMIT = 100;
118
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides119/** A job that has waited ten minutes or more with no matching runner online. */
120export type StuckJob = { id: string; name: string; runId: string; repo: string; labels: string; queuedAt: string };
121
Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.122/** A self-hosted runner as staff see it. Mirrors `AdminRunner` in crates/contracts. */
123export type AdminRunner = {
124 id: string;
125 name: string;
126 /** `workspace/repo` for a repository's own runner. */
127 repo: string | null;
128 os: string;
129 version: string;
130 /** Running something now. */
131 busy: boolean;
132 lastSeenAt: string | null;
133 createdAt: string;
134 createdBy: string | null;
135};
136
137/** Staff-only: a workspace's runners, and removing them (sudo). */
138export interface RunnersAdminApi {
139 list(workspace: string): Promise<AdminRunner[]>;
140 /** Removes one runner, or with `id` null every one, and the workspace's registration tokens. How many went. */
141 remove(workspace: string, id: string | null, staff: string): Promise<Result<number>>;
142}
143
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides144export interface RunnersApi {
145 /** The viewer's workspaces' jobs stuck waiting for a self-hosted runner, for Mission control. */
146 stuck(viewer: User): Promise<StuckJob[]>;
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.147 /** What runs for the workspace now, in g1t's sandboxes and handed to its own runners. Owners. */
148 activity(actor: User, workspace: string): Promise<Result<RunnerActivity>>;
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides149 list(actor: User, owner: RunnersOwner): Promise<Result<Runner[]>>;
150 createToken(actor: User, owner: RunnersOwner, group?: string): Promise<Result<RegistrationToken>>;
151 remove(actor: User, owner: RunnersOwner, id: string): Promise<Result<boolean>>;
152 groups(actor: User, workspace: string): Promise<Result<RunnerGroup[]>>;
153 /** Without `id`, a new group. */
154 setGroup(actor: User, workspace: string, group: { id?: string; name?: string; repositories?: string[] }): Promise<Result<RunnerGroup>>;
155 deleteGroup(actor: User, workspace: string, id: string): Promise<Result<boolean>>;
156 settings(actor: User, owner: RunnersOwner): Promise<Result<RunnerSettings>>;
157 setSettings(actor: User, owner: RunnersOwner, change: RunnerSettingsChange): Promise<Result<RunnerSettings>>;
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.158 /** What the person's own desktop app says of its runner, in a workspace they own. */
159 reportDesktop(actor: User, report: DesktopReport): Promise<Result<boolean>>;
160 /** The workspace's desktops, as each app last said. Owners. */
161 desktops(actor: User, workspace: string): Promise<Result<DesktopRunner[]>>;
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides162}
163
164/**
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.165 * The desktop app keeps a runner on a person's computer while it is open.
166 * What it says of that runner (its state, why, and the fix) comes from one
167 * catalogue in the app (apps/desktop/src-tauri/src/runner/diagnosis.rs),
168 * and the site shows it word for word: `closed` is what to show once g1t
169 * is closed on that computer, which the app sends too.
170 */
171export type DesktopDiagnosis = {
172 code: string;
173 tone: "ok" | "working" | "off" | "warning" | "problem";
174 title: string;
175 cause: string | null;
176 fix: string | null;
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.177 /** A sandboxed desktop's kinds of job, each with how it is: Linux jobs can run while Windows jobs cannot. */
178 capabilities?: DesktopCapability[];
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.179 /** The kinds of job say all of this, each for itself: a page showing them shows no line of the whole runner's as well. */
180 in_capabilities?: boolean;
The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report.181 /** How its runner's updates stand. From newer apps; older ones leave it out. */
182 update?: DesktopUpdate | null;
183};
184
185/**
186 * How a desktop's runner updates stand, as its app says it. The app looks
187 * every hour, and installs a newer runner kind of job by kind of job, as
188 * each is idle.
189 */
190export type DesktopUpdate = {
191 /** When the app last heard from the runner's releases, RFC 3339. */
192 checked_at: string | null;
193 /** A newer version, downloaded and checked, waiting to be installed. */
194 ready: string | null;
195 /** What becomes of it, for a desktop with no kinds of job of its own: `0.3.2 ready, installs after this job`. */
196 note: string | null;
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.197};
198
199/** How one kind of job is, as its card's label says it. */
200export type DesktopCapabilityState = "busy" | "idle" | "starting" | "downloading" | "updating" | "waiting" | "stopped" | "unavailable" | "problem";
201
202/** What a kind of job is running. */
203export type DesktopJob = {
204 /** `workflow` or `agent`. */
205 kind: string;
206 name: string;
207 /** `acme/web`. */
208 repo: string;
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.209};
210
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.211/**
212 * One kind of job a sandboxed desktop runs (`linux`, `windows`, `macos`),
213 * as its app says it. Each starts and stops on its own: `enabled` is false
214 * for one the person stopped, which is not failing. The fields after
215 * `fix` come from newer apps; older ones leave them out.
216 */
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.217export type DesktopCapability = {
218 id: string;
219 /** `Linux jobs`. */
220 label: string;
221 tone: DesktopDiagnosis["tone"];
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.222 /** `Waiting for jobs`, `Not available`: its state in a few words. */
Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it.223 title: string;
224 cause: string | null;
225 fix: string | null;
Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page.226 state?: DesktopCapabilityState;
227 /** What is wrong, in a few words: `WSL isn't installed`. */
228 headline?: string | null;
229 /** What it is, in a line: `A Linux VM of its own, each job in a fresh container`. */
230 about?: string | null;
231 /** On in the person's settings; false when they stopped it. */
232 enabled?: boolean;
233 job?: DesktopJob | null;
234 /** Its runner's name on g1t: `build-box-linux`. */
235 runner?: string | null;
236 /** `nested-container` or `vm`. */
237 isolation?: string | null;
238 version?: string | null;
The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report.239 /** A newer version waiting for it: `0.3.2 ready, installs after this job`. */
240 update?: string | null;
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.241};
242
243/** How a desktop runs jobs. */
244export type DesktopBackend = "sandboxed" | "docker" | "host";
245
246export type DesktopReport = {
247 workspace: string;
248 machine: string;
249 backend: DesktopBackend;
250 version: string | null;
251 diagnosis: DesktopDiagnosis;
252 closed: DesktopDiagnosis;
253};
254
255/** One person's computer, as its desktop app last said. */
256export type DesktopRunner = {
257 workspace: string;
258 machine: string;
259 /** Whose computer: their username. */
260 person: string;
261 backend: DesktopBackend;
262 version: string | null;
263 diagnosis: DesktopDiagnosis;
264 closed: DesktopDiagnosis;
265 /** Heard from in the last ten minutes: g1t is open there. */
266 open: boolean;
267 reportedAt: string;
268};
269
270/** The label every desktop app's runner has. */
271export const DESKTOP_LABEL = "desktop";
272
273/**
Security: postcss-selector-parser 7.1.6 and basic-ftp 6.2.2, pinned by overrides274 * The runner's container image: `g1t-runner` and the Docker CLI, for
275 * running it in Docker or Kubernetes. Published with each release.
276 */
277export const RUNNER_IMAGE = "g1t.sh/flagon-io/g1t-runner";
278
279/** Where the runner binary is published: `g1t.sh/downloads/runner/<version>/<file>`. */
280export const RUNNER_DOWNLOADS = "https://g1t.sh/downloads/runner";
281
282/** The file for each platform, as published. */
283export const RUNNER_FILES: Record<"linux-x64" | "linux-arm64" | "macos-arm64" | "macos-x64" | "windows-x64", string> = {
284 "linux-x64": "g1t-runner-linux-x64",
285 "linux-arm64": "g1t-runner-linux-arm64",
286 "macos-arm64": "g1t-runner-macos-arm64",
287 "macos-x64": "g1t-runner-macos-x64",
288 "windows-x64": "g1t-runner-windows-x64.exe",
289};

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