Skip to content
203 linesCodeBlameRaw
1import 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;
19 /** The workflow run the job is in; for `agent` work, the agent run it is, when known. */
20 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;
41 /** How it keeps work from its machine, as it last said; null for a runner from before runners said. */
42 isolation?: RunnerIsolation | null;
43};
44
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
53export 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
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
119/** 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
122export interface RunnersApi {
123 /** The viewer's workspaces' jobs stuck waiting for a self-hosted runner, for Mission control. */
124 stuck(viewer: User): Promise<StuckJob[]>;
125 /** What runs for the workspace now, in g1t's sandboxes and handed to its own runners. Owners. */
126 activity(actor: User, workspace: string): Promise<Result<RunnerActivity>>;
127 list(actor: User, owner: RunnersOwner): Promise<Result<Runner[]>>;
128 createToken(actor: User, owner: RunnersOwner, group?: string): Promise<Result<RegistrationToken>>;
129 remove(actor: User, owner: RunnersOwner, id: string): Promise<Result<boolean>>;
130 groups(actor: User, workspace: string): Promise<Result<RunnerGroup[]>>;
131 /** Without `id`, a new group. */
132 setGroup(actor: User, workspace: string, group: { id?: string; name?: string; repositories?: string[] }): Promise<Result<RunnerGroup>>;
133 deleteGroup(actor: User, workspace: string, id: string): Promise<Result<boolean>>;
134 settings(actor: User, owner: RunnersOwner): Promise<Result<RunnerSettings>>;
135 setSettings(actor: User, owner: RunnersOwner, change: RunnerSettingsChange): Promise<Result<RunnerSettings>>;
136 /** What the person's own desktop app says of its runner, in a workspace they own. */
137 reportDesktop(actor: User, report: DesktopReport): Promise<Result<boolean>>;
138 /** The workspace's desktops, as each app last said. Owners. */
139 desktops(actor: User, workspace: string): Promise<Result<DesktopRunner[]>>;
140}
141
142/**
143 * The desktop app keeps a runner on a person's computer while it is open.
144 * What it says of that runner (its state, why, and the fix) comes from one
145 * catalogue in the app (apps/desktop/src-tauri/src/runner/diagnosis.rs),
146 * and the site shows it word for word: `closed` is what to show once g1t
147 * is closed on that computer, which the app sends too.
148 */
149export type DesktopDiagnosis = {
150 code: string;
151 tone: "ok" | "working" | "off" | "warning" | "problem";
152 title: string;
153 cause: string | null;
154 fix: string | null;
155};
156
157/** How a desktop runs jobs. */
158export type DesktopBackend = "sandboxed" | "docker" | "host";
159
160export type DesktopReport = {
161 workspace: string;
162 machine: string;
163 backend: DesktopBackend;
164 version: string | null;
165 diagnosis: DesktopDiagnosis;
166 closed: DesktopDiagnosis;
167};
168
169/** One person's computer, as its desktop app last said. */
170export type DesktopRunner = {
171 workspace: string;
172 machine: string;
173 /** Whose computer: their username. */
174 person: string;
175 backend: DesktopBackend;
176 version: string | null;
177 diagnosis: DesktopDiagnosis;
178 closed: DesktopDiagnosis;
179 /** Heard from in the last ten minutes: g1t is open there. */
180 open: boolean;
181 reportedAt: string;
182};
183
184/** The label every desktop app's runner has. */
185export const DESKTOP_LABEL = "desktop";
186
187/**
188 * The runner's container image: `g1t-runner` and the Docker CLI, for
189 * running it in Docker or Kubernetes. Published with each release.
190 */
191export const RUNNER_IMAGE = "g1t.sh/flagon-io/g1t-runner";
192
193/** Where the runner binary is published: `g1t.sh/downloads/runner/<version>/<file>`. */
194export const RUNNER_DOWNLOADS = "https://g1t.sh/downloads/runner";
195
196/** The file for each platform, as published. */
197export const RUNNER_FILES: Record<"linux-x64" | "linux-arm64" | "macos-arm64" | "macos-x64" | "windows-x64", string> = {
198 "linux-x64": "g1t-runner-linux-x64",
199 "linux-arm64": "g1t-runner-linux-arm64",
200 "macos-arm64": "g1t-runner-macos-arm64",
201 "macos-x64": "g1t-runner-macos-x64",
202 "windows-x64": "g1t-runner-windows-x64.exe",
203};