Skip to content
398 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.

Project settings say who created the project, by name1//! The actions service: a repository's GitHub Actions workflows, run on
2//! g1t as they are. See `g1t_contracts::actions` for the methods and
3//! `g1t_actions` for how workflows, expressions and filters are read.
4//!
5//! - [`sync`] reads workflow files, at the default branch for the list and
6//! at an event's own commit for its runs.
7//! - [`trigger`] turns events, schedules and manual runs into runs.
8//! - [`plan`] moves a run's jobs along: each waits for the jobs it needs,
9//! is skipped or expanded into its matrix, queued, started in a sandbox
10//! when the workspace has room, and finished by the sandbox's report.
11//! - [`payload`] builds the webhook-shaped `github.event`.
Merge brokered secrets: a secret a job uses only to call an API can be tied to the hosts it is for, and the job gets a stand-in in its place, which g1t fills in on the way out to those hosts and nowhere else, so the value never enters the sandbox; and a request to an allowed host that fails upstream is retried when it can be, or answered with a 502 that says why, rather than reset.12//! - [`settings`] keeps secrets and variables, and [`broker`] the stand-ins
13//! jobs are given for brokered secrets.
Merge branch 'worktree-agent-a3abfcce648e87dca'14//! - [`protection`] keeps a repository's workflow settings and its
15//! environments' protection rules, holds jobs at those rules until they
16//! pass, and lets pull requests' runs from outside wait for approval.
Project settings say who created the project, by name17//! - [`cache`] lists `actions/cache` entries, which the API keeps in R2.
Merge remote-tracking branch 'origin/claude/peaceful-franklin-7qtkwx'18//! - [`reach`] finds the actions and reusable workflows of other
19//! repositories on g1t, and [`registry`] resolves those from GitHub to
20//! commits and keeps their archives, for jobs to download from g1t.
Project settings say who created the project, by name21//! - [`runners`] keeps self-hosted runners, hands them jobs (and agent
22//! work from the runner service) when they ask, and hears back.
23//!
24//! The service acts as the repository's workspace: it reads what the
Merge branch 'worktree-agent-a3abfcce648e87dca'25//! workspace can read, and a job's `GITHUB_TOKEN` is a token of the
26//! workspace's that reaches the job's repository only, with the scopes its
27//! `permissions:` give it, revoked when the job ends.
Project settings say who created the project, by name28
The artifacts service is services/artifacts, the Worker g1t-artifacts, bound as ARTIFACTS by the API, the site and the agents; its live rooms move to it with a Durable Object transfer from g1t-docs-service, and its database, bucket, indexes and queue keep their names. The git store's binding and settings are GITSTORE, its ops scripts gitstore-*, and workflow run artifacts keep their compatible API under run_artifacts modules. The deploy tool puts a Worker that has never deployed before the Workers in its stage that bind to it, and the deploy guide gives the cutover runbook.29mod run_artifacts;
Merge brokered secrets: a secret a job uses only to call an API can be tied to the hosts it is for, and the job gets a stand-in in its place, which g1t fills in on the way out to those hosts and nowhere else, so the value never enters the sandbox; and a request to an allowed host that fails upstream is retried when it can be, or answered with a 502 that says why, rather than reset.30mod broker;
Project settings say who created the project, by name31mod cache;
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.32mod limits;
A repository can run a managed workflow: one installed by g1t's own services without a file in the repository. It is listed, started, run and re-run like any workflow in `.g1t/workflows/`, under the path `managed/<name>.yml`, and a workflow of the same `name:` in the repository runs in its place, so moving one into the repository is committing its source. The Actions page says a managed workflow is managed by g1t rather than linking to a file. Nothing installs one yet; the deploy workflow deployments will install, which calls g1t's reusable deploy workflow with a project's settings as its inputs and reports as `g1t / deploy`, is written and tested but not installed.33mod managed;
Project settings say who created the project, by name34mod payload;
35mod plan;
Merge branch 'worktree-agent-a3abfcce648e87dca'36mod protection;
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts37mod reach;
Merge remote-tracking branch 'origin/claude/peaceful-franklin-7qtkwx'38mod registry;
Project settings say who created the project, by name39mod rename;
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.40mod retention;
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R241pub mod runtime;
Project settings say who created the project, by name42mod runners;
43mod settings;
44mod sync;
45mod trigger;
46mod views;
47
48use g1t_contracts::access::{self, Capability};
49use g1t_contracts::events::Event;
50use g1t_contracts::identity::{SlugArgs, Workspace};
51use g1t_contracts::repos::{GetArgs, GetByIdArgs, Repo, RepoPath};
52use g1t_contracts::{FailureCode, Membership, Outcome, PrincipalKind, User, Viewer};
53use g1t_kit::{args, reply, rpc_method};
54use g1t_secrets::Sealer;
55use serde::Deserialize;
56use serde_json::Value;
57use worker::wasm_bindgen::JsValue;
58use worker::{Context, D1Database, Env, Fetcher, MessageBatch, MessageExt, Request, Response, Result, ScheduleContext, ScheduledEvent, event};
59
60/// The most workflow files read from a repository.
61pub const MAX_WORKFLOWS: usize = 50;
62/// Jobs one workspace may have running at once; the rest wait their turn.
63pub const RUNNING_PER_WORKSPACE: u32 = 4;
What runs differently on g1t's machines is said before it surprises anyone, and four things now behave as on GitHub. A commit whose message says `[skip ci]`, `[ci skip]`, `[no ci]`, `[skip actions]` or `[actions skip]`, or ends with `skip-checks: true`, starts no `push` or `pull_request` workflows. A `pull_request_review` event's `review.state` is `approved` or `changes_requested`, not `approve` or `requestchanges`. A self-hosted job that names no `timeout-minutes` runs for 360 minutes, not 60. A JavaScript action's `::debug::` lines show on a debug run, as `run:` steps' do. The workflow's page now warns when a job asks for more than the 60 minutes g1t's machines give it, when an Arm label will run on x64, and when a matrix on a job that calls a reusable workflow will run it once; it no longer says artifacts are kept up to 60 MB (they are kept up to 5 GiB), nor that `setup-node` with `cache:` runs without its cache (the toolkit's cache works). The desktop release's two hosted jobs ask for the 60 minutes they get. The Actions and self-hosted runners guides say so.64/// The longest a job on g1t's own machines may run, whatever its
65/// `timeout-minutes`.
66pub const MAX_TIMEOUT_MINUTES: u32 = g1t_actions::workflow::HOSTED_MAX_TIMEOUT_MINUTES;
Project settings say who created the project, by name67/// The longest a job on a self-hosted runner may run: the machine is the
68/// workspace's own, and its time costs nothing.
69pub const SELF_HOSTED_MAX_TIMEOUT_MINUTES: u32 = 24 * 60;
70/// A running job that has said nothing for this long is taken as lost.
71pub const SILENT_MS: u64 = 10 * 60 * 1000;
Merge branch 'worktree-agent-ab9543c492a7ed481' into spend-guardrails72/// Schedules pause in a repository that has had no push for this long; the
73/// next push resumes them.
74pub const SCHEDULE_IDLE_MS: u64 = 60 * 24 * 60 * 60 * 1000;
75/// How long a workflow's schedule waits after billing refused to start a
76/// job of one of its scheduled runs, before it tries again.
77pub const SCHEDULE_REFUSED_MS: u64 = 60 * 60 * 1000;
Project settings say who created the project, by name78pub const SITE: &str = "https://g1t.sh";
79pub const API: &str = "https://api.g1t.sh";
80
81#[derive(Deserialize)]
82struct Count {
83 n: u32,
84}
85
86pub fn optional(value: Option<&str>) -> JsValue {
87 value.map_or(JsValue::NULL, JsValue::from)
88}
89
90pub fn fail<T>(code: FailureCode, message: impl Into<String>) -> Outcome<T> {
91 Outcome::fail(code, message)
92}
93
94/// `owner/name` as a path.
95pub fn repo_path(full_name: &str) -> RepoPath {
96 let (namespace, name) = full_name.split_once('/').unwrap_or((full_name, ""));
97 RepoPath {
98 namespace: namespace.to_owned(),
99 name: name.to_owned(),
100 }
101}
102
103pub struct Actions {
104 db: D1Database,
105 repos: Fetcher,
106 work: Fetcher,
107 identity: Fetcher,
108 runner: Fetcher,
109 events: Fetcher,
110 /// Projects: a repository's secrets and variables belong to its project.
111 projects: Fetcher,
112 /// Billing: self-hosted runners' time, recorded at $0, and the
113 /// cache's storage.
114 billing: Fetcher,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97115 /// Deployments: a job with an `environment:` deploys to it, and its
116 /// run's deployment is recorded there (plan.rs `report_deployment`).
117 deployments: Fetcher,
Merge branch 'worktree-agent-a5c94df323642630e'118 /// Notify, optional: pages open on a repository hear its runs' jobs
119 /// start and finish (plan.rs `tell_live`). Without it they catch up
120 /// on their own.
121 notify: Option<Fetcher>,
Project settings say who created the project, by name122 /// Where the API keeps cache entries, for deleting evicted ones.
123 cache: Option<worker::Bucket>,
124 /// Seals secrets; absent until `ACTIONS_KEY` is set, when secrets
125 /// cannot be saved.
126 sealer: Option<Sealer>,
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2127 /// Signs the toolkit's blob links (runtime.rs), from `ACTIONS_KEY`.
128 blob_key: Option<String>,
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.129 /// Log retention and free workspaces' caps, from wrangler vars (limits.rs).
130 limits: limits::Limits,
Project settings say who created the project, by name131}
132
133impl Actions {
134 fn new(env: &Env) -> Result<Self> {
135 Ok(Actions {
136 db: env.d1("DB")?,
137 repos: env.service("REPOS")?,
138 work: env.service("WORK")?,
139 identity: env.service("IDENTITY")?,
140 runner: env.service("RUNNER")?,
141 events: env.service("EVENTS")?,
142 projects: env.service("PROJECTS")?,
143 billing: env.service("BILLING")?,
Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97144 deployments: env.service("DEPLOYMENTS")?,
Merge branch 'worktree-agent-a5c94df323642630e'145 notify: env.service("NOTIFY").ok(),
Project settings say who created the project, by name146 cache: env.bucket("ACTIONS_CACHE").ok(),
147 sealer: env.secret("ACTIONS_KEY").ok().and_then(|key| Sealer::new(&key.to_string())),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2148 blob_key: env.secret("ACTIONS_KEY").ok().map(|key| runtime::blob_key(&key.to_string())),
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.149 limits: limits::Limits::from_env(env),
Project settings say who created the project, by name150 })
151 }
152
153 /// The workspace itself, as the service acts.
154 async fn workspace_actor(&self, slug: &str) -> Result<Option<User>> {
155 let workspace: Option<Workspace> = g1t_kit::call(&self.identity, "get_workspace", &SlugArgs { slug: slug.to_owned() }).await?;
156 Ok(workspace.map(|workspace| User {
157 id: workspace.id,
158 username: workspace.slug.clone(),
159 kind: PrincipalKind::Workspace,
160 verified: true,
161 workspaces: vec![Membership::member(workspace.slug)],
162 ..User::default()
163 }))
164 }
165
166 /// The repository, if the viewer may see it and it is not a pull
167 /// request's working copy.
168 async fn visible_repo(&self, path: &RepoPath, viewer: &Viewer) -> Result<Option<Repo>> {
169 let found: Outcome<Repo> = g1t_kit::call(
170 &self.repos,
171 "get",
172 &GetArgs {
173 path: path.clone(),
174 viewer: viewer.clone(),
175 },
176 )
177 .await?;
178 Ok(found.into_result().ok().filter(|repo| repo.fork_of.is_none()))
179 }
180
181 /// A repository by id, as its workspace sees it.
182 async fn repo_by_id(&self, id: &str) -> Result<Option<(Repo, User)>> {
183 let path: Option<RepoPath> = g1t_kit::call(&self.repos, "path_by_id", &g1t_contracts::repos::PathByIdArgs { id: id.to_owned() }).await?;
184 let Some(path) = path else { return Ok(None) };
185 let Some(actor) = self.workspace_actor(&path.namespace).await? else {
186 return Ok(None);
187 };
188 let found: Outcome<Repo> = g1t_kit::call(
189 &self.repos,
190 "get_by_id",
191 &GetByIdArgs {
192 id: id.to_owned(),
193 viewer: Some(actor.clone()),
194 },
195 )
196 .await?;
197 Ok(found.into_result().ok().filter(|repo| repo.fork_of.is_none()).map(|repo| (repo, actor)))
198 }
199
200 /// The repository at `path`, when `actor` may do `capability` in it:
201 /// not found when they cannot read it, forbidden when their role falls
The work, events, webhooks, actions, repos and packages services no longer run g1t's own agents. Work drops the 35 calls that queued issues for g1t, delegated them, messaged and reviewed through the agent, planned, captured memories automatically, rated confidence and capped agent spend, and its pull request lifecycle no longer revises or reviews: a pull request only advances to a merge by the repository's auto-merge setting. Merging a pull request that is behind now catches it up itself, through the repository's branch update, and lands it when the push arrives; when it can't be caught up cleanly the merge says to merge the base in and try again. Recording a session on a pull request, memory recall and remember, overlaps, the merge queue and required checks stay as they were.202 /// short.
Project settings say who created the project, by name203 async fn may(&self, actor: &User, path: &RepoPath, capability: Capability) -> Result<Outcome<Repo>> {
204 let Some(repo) = self.visible_repo(path, &Some(actor.clone())).await? else {
205 return Ok(fail(FailureCode::NotFound, "There is no such repository."));
206 };
207 if !access::can(Some(actor), &repo, capability) {
208 return Ok(fail(
209 FailureCode::Forbidden,
210 access::needs(capability, &format!("{}/{}", repo.namespace, repo.name)),
211 ));
212 }
213 Ok(Outcome::Ok(repo))
214 }
215}
216
217/// Unwraps an `Outcome`, or returns its failure from the enclosing method.
218#[macro_export]
219macro_rules! check {
220 ($outcome:expr) => {
221 match $outcome {
222 g1t_contracts::Outcome::Ok(value) => value,
223 g1t_contracts::Outcome::Fail(refused) => return Ok(g1t_contracts::Outcome::Fail(refused)),
224 }
225 };
226}
227
228#[event(fetch)]
229async fn fetch(mut request: Request, env: Env, _ctx: Context) -> Result<Response> {
230 let Some(method) = rpc_method(&request) else {
231 return Response::error("Not found", 404);
232 };
233 let body: Value = request.json().await?;
234 let service = Actions::new(&env)?;
235 match method.as_str() {
236 "workflows" => reply(&service.workflows(args(body)?).await?),
237 "runs" => reply(&service.runs(args(body)?).await?),
238 "run" => reply(&service.run(args(body)?).await?),
239 "logs" => reply(&service.logs(args(body)?).await?),
Merge Actions runs: summaries, attempts and re-runs, graceful cancel, log downloads, badges (actions 0009)240 "job_log_text" => reply(&service.job_log_text(args(body)?).await?),
241 "run_logs" => reply(&service.run_logs(args(body)?).await?),
242 "summaries" => reply(&service.summaries(args(body)?).await?),
Project settings say who created the project, by name243 "dispatch" => reply(&service.dispatch(args(body)?).await?),
Merge branch 'worktree-agent-a3abfcce648e87dca'244 "repository_dispatch" => reply(&service.repository_dispatch(args(body)?).await?),
245 "approve_run" => reply(&service.approve_run(args(body)?).await?),
246 "pending_deployments" => reply(&service.pending_deployments(args(body)?).await?),
247 "review_deployments" => reply(&service.review_deployments(args(body)?).await?),
248 "actions_settings" => reply(&service.actions_settings(args(body)?).await?),
249 "set_actions_settings" => reply(&service.set_actions_settings(args(body)?).await?),
250 "workspace_actions_settings" => reply(&service.workspace_actions_settings(args(body)?).await?),
251 "set_workspace_actions_settings" => reply(&service.set_workspace_actions_settings(args(body)?).await?),
252 "environments" => reply(&service.environments(args(body)?).await?),
253 "set_environment" => reply(&service.set_environment(args(body)?).await?),
254 "delete_environment" => reply(&service.delete_environment(args(body)?).await?),
Project settings say who created the project, by name255 "merge_group" => reply(&service.merge_group(args(body)?).await?),
256 "cancel" => reply(&service.cancel(args(body)?).await?),
257 "rerun" => reply(&service.rerun(args(body)?).await?),
258 "set_workflow_enabled" => reply(&service.set_workflow_enabled(args(body)?).await?),
259 "settings" => reply(&service.settings(args(body)?).await?),
260 "set_setting" => reply(&service.set_setting(args(body)?).await?),
261 "delete_setting" => reply(&service.delete_setting(args(body)?).await?),
262 "resolve_settings" => reply(&service.resolve_settings(args(body)?).await?),
A repository can run a managed workflow: one installed by g1t's own services without a file in the repository. It is listed, started, run and re-run like any workflow in `.g1t/workflows/`, under the path `managed/<name>.yml`, and a workflow of the same `name:` in the repository runs in its place, so moving one into the repository is committing its source. The Actions page says a managed workflow is managed by g1t rather than linking to a file. Nothing installs one yet; the deploy workflow deployments will install, which calls g1t's reusable deploy workflow with a project's settings as its inputs and reports as `g1t / deploy`, is written and tested but not installed.263 // Managed workflows: g1t's services' side only.
264 "set_managed_workflow" => reply(&service.set_managed_workflow(args(body)?).await?),
265 "remove_managed_workflow" => reply(&service.remove_managed_workflow(args(body)?).await?),
A project can build with a workflow. Turning on **Build with a workflow** under Settings, Deployments installs a managed workflow named `g1t` that calls g1t's reusable deploy workflow (`g1t/workflows/.g1t/workflows/deploy.yml@v1`, published from `workflows/` by the actions release) with the project's settings, and pushes and pull requests then start it rather than a build of Deployments' own: the same previews and production, at the same addresses, reported as `g1t / deploy`, now the check of the workflow's job, listed on the Actions page with its runs and logs. A pull request from a fork runs it with a read-only token, as on GitHub, and still gets its preview without the project's build secrets: a read-only job token may open a preview build only from a run of a managed workflow. Fork pull requests run the repository's managed workflows too. Its jobs are Actions time, at cost plus 20% on g1t's machines and free on a self-hosted runner. A workflow named `g1t` in `.g1t/workflows/` takes its place. The deployments and Actions guides say so.266 "set_managed_deploy" => reply(&service.set_managed_deploy(args(body)?).await?),
Merge GitHub pull request #8 (claude/cool-newton-7nm8r9): Docker deployments on g1t.page behind a switch that stays off until Cloudflare's container APIs are checked live, workflows a workspace manages in its repositories, tarball and zipball downloads, refs/pull/* kept to g1t alone, framework detection for g1t.page builds, and a workflow's event payload matching GitHub's.267 // A workspace's managed workflows: its owners'.
268 "workspace_managed_workflows" => reply(&service.workspace_managed_workflows(args(body)?).await?),
269 "set_workspace_managed_workflow" => reply(&service.set_workspace_managed_workflow(args(body)?).await?),
270 "remove_workspace_managed_workflow" => reply(&service.remove_workspace_managed_workflow(args(body)?).await?),
Project settings say who created the project, by name271 "job_spec" => reply(&service.job_spec(args(body)?).await?),
272 "job_auth" => reply(&service.job_auth(args(body)?).await?),
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts273 "job_action" => reply(&service.job_action(args(body)?).await?),
Merge remote-tracking branch 'origin/claude/peaceful-franklin-7qtkwx'274 "job_action_archive" => reply(&service.job_action_archive(args(body)?).await?),
Merge checks: statuses and check runs on every commit275 "check_runs" => reply(&service.check_runs(args(body)?).await?),
Project settings say who created the project, by name276 "job_report" => reply(&service.job_report(args(body)?).await?),
Merge brokered secrets: a secret a job uses only to call an API can be tied to the hosts it is for, and the job gets a stand-in in its place, which g1t fills in on the way out to those hosts and nowhere else, so the value never enters the sandbox; and a request to an allowed host that fails upstream is retried when it can be, or answered with a 502 that says why, rather than reset.277 // A running job's stand-ins for brokered secrets: the runner
278 // Worker's alone, never passed on by the API.
279 "brokered_secrets" => reply(&service.brokered_secrets(args(body)?).await?),
Merge branch 'main' into rm-agents280 // The same for a self-hosted job's broker requests, which the
281 // runner Worker relays (`POST /actions/jobs/{job}/broker`): with
282 // the job's current token only.
283 "relayed_secrets" => reply(&service.relayed_secrets(args(body)?).await?),
Project settings say who created the project, by name284 // actions/cache, through the API with the job's token.
285 "cache_lookup" => reply(&service.cache_lookup(args(body)?).await?),
286 "cache_reserve" => reply(&service.cache_reserve(args(body)?).await?),
287 "cache_commit" => reply(&service.cache_commit(args(body)?).await?),
288 "cache_abort" => reply(&service.cache_abort(args(body)?).await?),
Actions: OIDC tokens, the toolkit's cache and artifact services, and artifacts in R2289 "cache_upload" => reply(&service.cache_upload(args(body)?).await?),
290 // Artifacts: a job's side, with its token or its runtime token.
291 "artifact_reserve" => reply(&service.artifact_reserve(args(body)?).await?),
292 "artifact_commit" => reply(&service.artifact_commit(args(body)?).await?),
293 "artifact_abort" => reply(&service.artifact_abort(args(body)?).await?),
294 "job_artifacts" => reply(&service.job_artifacts(args(body)?).await?),
295 "job_artifact" => reply(&service.job_artifact(args(body)?).await?),
296 "job_delete_artifact" => reply(&service.job_delete_artifact(args(body)?).await?),
297 // Artifacts: people's side.
298 "artifacts" => reply(&service.artifacts(args(body)?).await?),
299 "artifact" => reply(&service.artifact(args(body)?).await?),
300 "artifact_download" => reply(&service.artifact_download(args(body)?).await?),
301 "delete_artifact" => reply(&service.delete_artifact(args(body)?).await?),
302 "artifact_retention" => reply(&service.artifact_retention(args(body)?).await?),
303 // The toolkit's protocols: the runtime token, OIDC, blob links.
304 "runtime_auth" => reply(&service.runtime_auth(args(body)?).await?),
305 "oidc_claims" => reply(&service.oidc_claims(args(body)?).await?),
306 "blob_sign" => reply(&service.blob_sign(args(body)?).await?),
307 "blob_open" => reply(&service.blob_open(args(body)?).await?),
308 "blob_part" => reply(&service.blob_part(args(body)?).await?),
309 "blob_parts" => reply(&service.blob_parts(args(body)?).await?),
310 "blob_done" => reply(&service.blob_done(args(body)?).await?),
Project settings say who created the project, by name311 // Self-hosted runners: people's side.
312 "runners" => reply(&service.runners(args(body)?).await?),
313 "create_registration_token" => reply(&service.create_registration_token(args(body)?).await?),
314 "remove_runner" => reply(&service.remove_runner(args(body)?).await?),
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.315 // Staff only, from sudo: a workspace's runners, and removing them.
316 "admin_runners" => reply(&service.admin_runners(args(body)?).await?),
317 "admin_remove_runners" => reply(&service.admin_remove_runners(args(body)?).await?),
Project settings say who created the project, by name318 "runner_groups" => reply(&service.runner_groups(args(body)?).await?),
319 "set_runner_group" => reply(&service.set_runner_group(args(body)?).await?),
320 "delete_runner_group" => reply(&service.delete_runner_group(args(body)?).await?),
321 "runner_settings" => reply(&service.runner_settings(args(body)?).await?),
322 "set_runner_settings" => reply(&service.set_runner_settings(args(body)?).await?),
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.323 // The desktop app's runners, as each app says they are.
324 "report_desktop" => reply(&service.report_desktop(args(body)?).await?),
325 "desktop_runners" => reply(&service.desktop_runners(args(body)?).await?),
Project settings say who created the project, by name326 // The runner's own side, through the API with its credential.
327 "runner_register" => reply(&service.runner_register(args(body)?).await?),
328 "runner_poll" => reply(&service.runner_poll(args(body)?).await?),
329 "runner_finished" => reply(&service.runner_finished(args(body)?).await?),
330 "runner_remove_self" => reply(&service.runner_remove_self(args(body)?).await?),
331 // Agent work, from the runner service.
332 "runner_route" => reply(&service.runner_route(args(body)?).await?),
333 "stuck_jobs" => reply(&service.stuck_jobs(args(body)?).await?),
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.334 "runner_activity" => reply(&service.runner_activity(args(body)?).await?),
Project settings say who created the project, by name335 "enqueue_task" => reply(&service.enqueue_task(args(body)?).await?),
336 "cancel_task" => reply(&service.cancel_task(args(body)?).await?),
337 _ => Response::error("Unknown method", 404),
338 }
339}
340
341/// Events from the bus, on this service's own queue.
342#[event(queue)]
343async fn queue(batch: MessageBatch<Event>, env: Env, _ctx: Context) -> Result<()> {
344 let service = Actions::new(&env)?;
345 for message in batch.messages()? {
346 // A workspace renamed: its rows move to the slug it has now.
347 if g1t_kit::rename::on_event(&env, &env.d1("DB")?, message.body(), rename::STATEMENTS).await? {
348 message.ack();
349 continue;
350 }
351 // A repository renamed or transferred: its rows follow its new path.
352 if g1t_kit::transfer::on_event(&env, &env.d1("DB")?, message.body(), rename::TRANSFERRED).await? {
353 message.ack();
354 continue;
355 }
356 // A workspace deleted: what it kept for itself goes.
357 if g1t_kit::deleted::on_event(&env.d1("DB")?, message.body(), rename::DELETED).await? {
358 message.ack();
359 continue;
360 }
361 // A repository purged: every row kept for it goes.
362 if g1t_kit::lifecycle::on_purged(&env.d1("DB")?, message.body(), rename::PURGED).await? {
363 message.ack();
364 continue;
365 }
366 // A repository deleted or archived: its runs stop.
367 if let Some(repo_id) = plan::stops_runs(message.body()) {
368 if let Err(error) = service.stop_runs(&repo_id).await {
369 worker::console_error!("actions: event {} failed: {error}", message.body().id);
370 message.retry();
371 continue;
372 }
373 message.ack();
374 continue;
375 }
376 if let Err(error) = service.on_event(message.body()).await {
377 worker::console_error!("actions: event {} failed: {error}", message.body().id);
378 message.retry();
379 continue;
380 }
381 message.ack();
382 }
383 Ok(())
384}
385
386/// Every minute: schedules that fire, jobs waiting for room, and jobs
387/// whose sandbox went quiet.
388#[event(scheduled)]
389async fn scheduled(_event: ScheduledEvent, env: Env, _ctx: ScheduleContext) {
390 match Actions::new(&env) {
391 Ok(service) => {
392 if let Err(error) = service.on_minute(g1t_kit::now_ms()).await {
393 worker::console_error!("actions: the sweep failed: {error}");
394 }
395 }
396 Err(error) => worker::console_error!("actions: could not start: {error}"),
397 }
398}

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