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
102 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 sidebar asks billing whether it is on, not for the account, so no page waits on the card processor1/**
2 * g1t's own work on a workspace's self-hosted runners. When a workspace (or
3 * one of its repositories) says so under Settings, Runners, an agent's run,
4 * checks, a review, a merge check or the merge queue is not started in a
5 * sandbox here: the sandbox's Durable Object hands the same environment to
6 * the actions service as a task, a runner of the workspace's with the
7 * right labels takes it on its next poll, and the actions service tells
8 * the Durable Object how it ended (`task_ended`), which then does exactly
9 * what it does when a container stops. The model calls still go through
10 * g1t's model proxy with the run's own credential, so spend, budgets and
11 * the audit log work as they do in a sandbox. Network guardrails cannot be
12 * enforced on someone else's machine, and the run says so.
13 */
14
15import type { RepoPath, ServiceBinding } from "@g1t/contracts";
16
17/** The actions service's JSON protocol. */
18async function call<T>(actions: ServiceBinding, method: string, args: object): Promise<T> {
19 const response = await actions.fetch(`https://actions/rpc/${method}`, {
20 method: "POST",
21 headers: { "content-type": "application/json" },
22 body: JSON.stringify(args),
23 });
24 if (!response.ok) throw new Error(`${method} failed with status ${response.status}`);
25 return (await response.json()) as T;
26}
27
28/**
29 * The labels g1t's own work in `repo` runs on, when it runs on the
30 * workspace's runners; null for g1t's sandboxes. Never throws: when the
31 * actions service cannot say, the work runs in a sandbox as before.
32 */
33export async function selfHostedRoute(actions: ServiceBinding, repo: RepoPath): Promise<string[] | null> {
34 try {
35 const labels = await call<string[] | null>(actions, "runner_route", { workspace: repo.namespace.toLowerCase(), repo });
36 return Array.isArray(labels) && labels.length > 0 ? labels : null;
37 } catch (error) {
38 console.log("self-hosted route unknown; using a sandbox", repo.namespace, String(error));
39 return null;
40 }
41}
42
43type Outcome<T> = { ok: true; value: T } | { ok: false; error: { message: string } };
44
45/** Hands a sandbox's work to the workspace's runners. Returns the task's id. */
46export async function enqueueTask(
47 actions: ServiceBinding,
48 task: {
49 sandbox: string;
50 repo: RepoPath;
51 kind: string;
52 title: string;
53 labels: string[];
54 env: Record<string, string>;
55 timeoutMinutes: 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.56 /** The agent run the work is, when the sandbox opened one: the Runners page links to it. */
57 runId?: string | null;
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor58 },
59): Promise<string> {
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.60 const { runId, ...rest } = task;
61 const queued = await call<Outcome<string>>(actions, "enqueue_task", { ...rest, run_id: runId ?? null, workspace: task.repo.namespace.toLowerCase() });
The sidebar asks billing whether it is on, not for the account, so no page waits on the card processor62 if (!queued.ok) throw new Error(queued.error.message);
63 return queued.value;
64}
65
66/** Withdraws a sandbox's task: a person stopped it, or its time ran out. Never throws. */
67export async function cancelTask(actions: ServiceBinding, sandbox: string, reason: string | null): Promise<void> {
68 await call(actions, "cancel_task", { sandbox, reason }).catch((error: unknown) =>
69 console.log("self-hosted task not cancelled", sandbox, String(error)),
70 );
71}
72
73/**
74 * The environment a self-hosted runner gets: what the sandbox would have
75 * been started with, less what only makes sense in g1t's sandbox (the
76 * egress certificate and proxy), with the mining watch off, since the
77 * machine and its electricity are the workspace's.
78 */
79export function taskEnv(vars: Record<string, string>): Record<string, string> {
80 const env: Record<string, string> = {};
81 for (const [name, value] of Object.entries(vars)) {
82 if (/^(HTTPS?_PROXY|NO_PROXY|NODE_EXTRA_CA_CERTS|SSL_CERT_FILE|G1T_EGRESS_CA)$/i.test(name)) continue;
83 env[name] = value;
84 }
85 env.G1T_ABUSE = "off";
86 env.G1T_SELF_HOSTED = "1";
87 return env;
88}
89
90/** What the run's session says when it goes to the workspace's runners. */
91export function handedOverStep(labels: string[]): string {
92 return `Handed to a self-hosted runner with labels ${labels.join(", ")}. g1t's network guardrails are not enforced on your own machines.`;
93}
94
95/** Where the work is: the tracked repository, else the meter's `owner/name`. */
96export function taskRepo(track: { repo: RepoPath } | undefined, meter: { repo: string } | undefined, owner: { repo: string | null } | undefined): RepoPath | null {
97 if (track) return track.repo;
98 const full = meter?.repo ?? owner?.repo ?? null;
99 if (!full) return null;
100 const [namespace, name] = full.split("/");
101 return namespace && name ? { namespace, name } : null;
102}

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