Skip to content
193 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};
42
43export type RunnerGroup = {
44 id: string;
45 name: string;
46 default: boolean;
47 /** Repository names; empty is every repository in the workspace. */
48 repositories: string[];
49 runners: number;
50 updatedAt: string;
51};
52
53/** Shown once. */
54export type RegistrationToken = {
55 token: string;
56 expiresAt: string;
57 workspace: string;
58 repo: string | null;
59 group: string | null;
60 url: string;
61};
62
63export type RunnerSettings = {
64 agentsOnSelfHosted: boolean;
65 agentLabels: string[];
66 forkPullRequests: boolean;
67 /** For a repository: these are its workspace's. */
68 inherited: boolean;
69};
70
71export type RunnersOwner = { repo: RepoPath } | { workspace: string };
72
73export type RunnerSettingsChange = {
74 agents_on_self_hosted?: boolean;
75 agent_labels?: string[];
76 fork_pull_requests?: boolean;
77 inherit?: boolean;
78};
79
80/** A workflow job running in one of g1t's sandboxes. Snake case, as `runner_activity` answers. */
81export type CloudJob = { id: string; name: string; run_id: string; repo: string; started_at: string | null };
82
83/** Agent work handed to one of the workspace's own runners, waiting for one or taken. */
84export type HandedOverTask = {
85 id: string;
86 /** The agent run it is; null for work handed over before runs were noted. */
87 run_id: string | null;
88 kind: string;
89 title: string;
90 repo: string;
91 status: "queued" | "in_progress";
92 runner_name: string | null;
93 created_at: string;
94 started_at: string | null;
95};
96
97/** What runs for a workspace now, in g1t's sandboxes and on its own runners (`runner_activity`). */
98export type RunnerActivity = {
99 /** At most `RUNNER_ACTIVITY_LIMIT`, newest first. */
100 cloud_jobs: CloudJob[];
101 cloud_jobs_queued: number;
102 handed_over: HandedOverTask[];
103 self_hosted_jobs_queued: number;
104};
105
106/** The most jobs and tasks `runner_activity` lists of each: `ACTIVITY_LIMIT` in the actions service. */
107export const RUNNER_ACTIVITY_LIMIT = 100;
108
109/** A job that has waited ten minutes or more with no matching runner online. */
110export type StuckJob = { id: string; name: string; runId: string; repo: string; labels: string; queuedAt: string };
111
112export interface RunnersApi {
113 /** The viewer's workspaces' jobs stuck waiting for a self-hosted runner, for Mission control. */
114 stuck(viewer: User): Promise<StuckJob[]>;
115 /** What runs for the workspace now, in g1t's sandboxes and handed to its own runners. Owners. */
116 activity(actor: User, workspace: string): Promise<Result<RunnerActivity>>;
117 list(actor: User, owner: RunnersOwner): Promise<Result<Runner[]>>;
118 createToken(actor: User, owner: RunnersOwner, group?: string): Promise<Result<RegistrationToken>>;
119 remove(actor: User, owner: RunnersOwner, id: string): Promise<Result<boolean>>;
120 groups(actor: User, workspace: string): Promise<Result<RunnerGroup[]>>;
121 /** Without `id`, a new group. */
122 setGroup(actor: User, workspace: string, group: { id?: string; name?: string; repositories?: string[] }): Promise<Result<RunnerGroup>>;
123 deleteGroup(actor: User, workspace: string, id: string): Promise<Result<boolean>>;
124 settings(actor: User, owner: RunnersOwner): Promise<Result<RunnerSettings>>;
125 setSettings(actor: User, owner: RunnersOwner, change: RunnerSettingsChange): Promise<Result<RunnerSettings>>;
126 /** What the person's own desktop app says of its runner, in a workspace they own. */
127 reportDesktop(actor: User, report: DesktopReport): Promise<Result<boolean>>;
128 /** The workspace's desktops, as each app last said. Owners. */
129 desktops(actor: User, workspace: string): Promise<Result<DesktopRunner[]>>;
130}
131
132/**
133 * The desktop app keeps a runner on a person's computer while it is open.
134 * What it says of that runner (its state, why, and the fix) comes from one
135 * catalogue in the app (apps/desktop/src-tauri/src/runner/diagnosis.rs),
136 * and the site shows it word for word: `closed` is what to show once g1t
137 * is closed on that computer, which the app sends too.
138 */
139export type DesktopDiagnosis = {
140 code: string;
141 tone: "ok" | "working" | "off" | "warning" | "problem";
142 title: string;
143 cause: string | null;
144 fix: string | null;
145};
146
147/** How a desktop runs jobs. */
148export type DesktopBackend = "sandboxed" | "docker" | "host";
149
150export type DesktopReport = {
151 workspace: string;
152 machine: string;
153 backend: DesktopBackend;
154 version: string | null;
155 diagnosis: DesktopDiagnosis;
156 closed: DesktopDiagnosis;
157};
158
159/** One person's computer, as its desktop app last said. */
160export type DesktopRunner = {
161 workspace: string;
162 machine: string;
163 /** Whose computer: their username. */
164 person: string;
165 backend: DesktopBackend;
166 version: string | null;
167 diagnosis: DesktopDiagnosis;
168 closed: DesktopDiagnosis;
169 /** Heard from in the last ten minutes: g1t is open there. */
170 open: boolean;
171 reportedAt: string;
172};
173
174/** The label every desktop app's runner has. */
175export const DESKTOP_LABEL = "desktop";
176
177/**
178 * The runner's container image: `g1t-runner` and the Docker CLI, for
179 * running it in Docker or Kubernetes. Published with each release.
180 */
181export const RUNNER_IMAGE = "g1t.sh/flagon-io/g1t-runner";
182
183/** Where the runner binary is published: `g1t.sh/downloads/runner/<version>/<file>`. */
184export const RUNNER_DOWNLOADS = "https://g1t.sh/downloads/runner";
185
186/** The file for each platform, as published. */
187export const RUNNER_FILES: Record<"linux-x64" | "linux-arm64" | "macos-arm64" | "macos-x64" | "windows-x64", string> = {
188 "linux-x64": "g1t-runner-linux-x64",
189 "linux-arm64": "g1t-runner-linux-arm64",
190 "macos-arm64": "g1t-runner-macos-arm64",
191 "macos-x64": "g1t-runner-macos-x64",
192 "windows-x64": "g1t-runner-windows-x64.exe",
193};