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 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. | 1 | /** |
| 2 | * The Runners page's shaping: what runs on g1t's cloud now, what waits for | |
| 3 | * the workspace's own runners, and what each cost this month, from the | |
| 4 | * actions, work and billing services' own answers. Pure, so it can be | |
| 5 | * tested; the reads are in ./runners.server.ts. | |
| 6 | * | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 7 | * - **On g1t's cloud now** is the workspace's sandbox runs (checks, the |
| 8 | * merge queue, merge checks) that are running | |
| 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. | 9 | * (from the work service) and not handed to one of its own runners |
| 10 | * (`runner_activity`'s `handed_over`), and its workflow jobs running in | |
| 11 | * g1t's sandboxes (`cloud_jobs`). | |
| 12 | * - **This month** is billing's usage report from the first of the month: | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 13 | * sandbox time is g1t's cloud, at price (older months may also name |
| 14 | * agent sandbox time); | |
| 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. | 15 | * self-hosted runner time is the workspace's own, at $0. |
| 16 | */ | |
| 17 | import type { AgentRun, MeterLine, Price, RunnerActivity, UsageReport } from "@g1t/contracts"; | |
| 18 | ||
| 19 | /** One thing running in one of g1t's sandboxes. */ | |
| 20 | export type CloudWork = { | |
| 21 | kind: "agent" | "workflow"; | |
| 22 | id: string; | |
| 23 | /** What it is: a pull request's title, a job's name. */ | |
| 24 | title: string; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 25 | /** The run's kind for a sandbox run (`checks`, `queue`…); the job's workflow run for a job. */ |
| 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. | 26 | detail: string; |
| 27 | /** `owner/name`. */ | |
| 28 | repo: string; | |
| 29 | href: string; | |
| 30 | startedAt: string | null; | |
| 31 | }; | |
| 32 | ||
| 33 | export type CloudNow = { | |
| 34 | running: CloudWork[]; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 35 | /** Sandbox runs and workflow jobs waiting to start in g1t's sandboxes. */ |
| 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. | 36 | queued: number; |
| 37 | /** A list reached its limit: there may be more than shown. */ | |
| 38 | more: boolean; | |
| 39 | }; | |
| 40 | ||
| 41 | /** Waiting for one of the workspace's own runners. */ | |
| 42 | export type OwnWaiting = { tasks: number; jobs: number }; | |
| 43 | ||
| 44 | /** This month's machine time, from billing's usage report. */ | |
| 45 | export type RunnerCost = { | |
| 46 | from: string; | |
| 47 | until: string; | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 48 | /** g1t's cloud: sandbox time. */ |
| 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. | 49 | cloudSeconds: number; |
| 50 | /** At price, before what paid for it. */ | |
| 51 | cloudMicros: number; | |
| 52 | /** The workspace's own runners: self-hosted runner time, always $0. */ | |
| 53 | ownSeconds: number; | |
| 54 | /** g1t charges nothing for now: the figures are usage at cost. */ | |
| 55 | free: boolean; | |
| 56 | }; | |
| 57 | ||
| 58 | /** A price for time on g1t's cloud, per minute, from the price book. */ | |
| 59 | export type CloudPrice = { meter: string; title: string; unit: string; perMinuteMicros: number }; | |
| 60 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 61 | /** Billing's meters for machine time (`METERS` in services/billing/src/report.rs); `agent_sandbox` is in older months' reports. */ |
| 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. | 62 | export const CLOUD_METERS = ["agent_sandbox", "sandbox"] as const; |
| 63 | export const OWN_METER = "self_hosted"; | |
| 64 | ||
| 65 | /** The price book's meters for a sandbox's time: its parts when both are priced, else the one price. */ | |
| 66 | const PRICE_PARTS = ["sandbox_base_second", "sandbox_cpu_second"] as const; | |
| 67 | const PRICE_WHOLE = "sandbox_second"; | |
| 68 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 69 | /** Where a sandbox run (checks, the merge queue) shows: its pull request, else the repository's pull requests. */ |
| 70 | export function sandboxRunHref(repo: string, number?: number | null): string { | |
| 71 | return number != null ? `/${repo}/pull/${number}` : `/${repo}/pulls`; | |
| 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. | 72 | } |
| 73 | ||
| 74 | /** The page of a workflow run. */ | |
| 75 | export function workflowRunHref(repo: string, runId: string): string { | |
| 76 | return `/${repo}/actions/runs/${runId}`; | |
| 77 | } | |
| 78 | ||
| 79 | const repoName = (run: Pick<AgentRun, "repo">) => `${run.repo.namespace}/${run.repo.name}`; | |
| 80 | ||
| 81 | /** | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 82 | * What runs on g1t's cloud now. `runs` are the workspace's active sandbox |
| 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. | 83 | * runs (null when the work service did not answer); `activity` is the |
| 84 | * actions service's (null when it did not). Null when neither answered. | |
| 85 | */ | |
| 86 | export function cloudNow(runs: AgentRun[] | null, activity: RunnerActivity | null, runLimit: number, jobLimit: number): CloudNow | null { | |
| 87 | if (!runs && !activity) return null; | |
| 88 | const handed = new Set((activity?.handed_over ?? []).map((task) => task.run_id).filter((id): id is string => !!id)); | |
| 89 | const ours = (runs ?? []).filter((run) => !handed.has(run.id)); | |
| 90 | const running: CloudWork[] = [ | |
| 91 | ...ours | |
| 92 | .filter((run) => run.status === "running") | |
| 93 | .map((run) => ({ | |
| 94 | kind: "agent" as const, | |
| 95 | id: run.id, | |
| 96 | title: run.title ?? (run.number != null ? `#${run.number}` : repoName(run)), | |
| 97 | detail: run.kind, | |
| 98 | repo: repoName(run), | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 99 | href: sandboxRunHref(repoName(run), run.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. | 100 | startedAt: run.startedAt ?? run.createdAt, |
| 101 | })), | |
| 102 | ...(activity?.cloud_jobs ?? []).map((job) => ({ | |
| 103 | kind: "workflow" as const, | |
| 104 | id: job.id, | |
| 105 | title: job.name, | |
| 106 | detail: job.run_id, | |
| 107 | repo: job.repo, | |
| 108 | href: workflowRunHref(job.repo, job.run_id), | |
| 109 | startedAt: job.started_at, | |
| 110 | })), | |
| 111 | ].sort((a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? "")); | |
| 112 | const queued = ours.filter((run) => run.status === "queued").length + (activity?.cloud_jobs_queued ?? 0); | |
| 113 | const more = (runs?.length ?? 0) >= runLimit || (activity?.cloud_jobs.length ?? 0) >= jobLimit; | |
| 114 | return { running, queued, more }; | |
| 115 | } | |
| 116 | ||
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 117 | /** What waits for one of the workspace's own runners: sandbox runs not yet taken, and workflow jobs. */ |
| 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. | 118 | export function ownWaiting(activity: RunnerActivity | null): OwnWaiting | null { |
| 119 | if (!activity) return null; | |
| 120 | return { tasks: activity.handed_over.filter((task) => task.status === "queued").length, jobs: activity.self_hosted_jobs_queued }; | |
| 121 | } | |
| 122 | ||
| 123 | /** This month's machine time from billing's usage report; null when billing did not answer. */ | |
| 124 | export function runnerCost(report: UsageReport | null): RunnerCost | null { | |
| 125 | if (!report) return null; | |
| 126 | const meters: MeterLine[] = report.products.flatMap((product) => product.meters); | |
| 127 | const sum = (keys: readonly string[], pick: (meter: MeterLine) => number) => | |
| 128 | meters.filter((meter) => keys.includes(meter.key)).reduce((total, meter) => total + pick(meter), 0); | |
| 129 | return { | |
| 130 | from: report.from, | |
| 131 | until: report.until, | |
| 132 | cloudSeconds: sum(CLOUD_METERS, (meter) => meter.quantity), | |
| 133 | cloudMicros: sum(CLOUD_METERS, (meter) => meter.micros + (meter.pendingMicros ?? 0)), | |
| 134 | ownSeconds: sum([OWN_METER], (meter) => meter.quantity), | |
| 135 | free: report.free, | |
| 136 | }; | |
| 137 | } | |
| 138 | ||
| 139 | /** The price of a minute on g1t's cloud, as the price book has it; null when it has none. */ | |
| 140 | export function cloudPrices(prices: Price[] | null): CloudPrice[] | null { | |
| 141 | if (!prices) return null; | |
| 142 | const perMinute = (price: Price): CloudPrice => ({ | |
| 143 | meter: price.meter, | |
| 144 | title: price.title, | |
| 145 | unit: price.unit.replace(/second$/, "minute"), | |
| 146 | perMinuteMicros: price.priceMicros * 60, | |
| 147 | }); | |
| 148 | const parts = PRICE_PARTS.map((meter) => prices.find((price) => price.meter === meter)); | |
| 149 | if (parts.every(Boolean)) return parts.map((price) => perMinute(price!)); | |
| 150 | const whole = prices.find((price) => price.meter === PRICE_WHOLE); | |
| 151 | return whole ? [perMinute(whole)] : null; | |
| 152 | } | |
| 153 | ||
| 154 | /** Seconds as people read machine time: "45s", "12 min", "3.4 h". */ | |
| 155 | export function machineTime(seconds: number): string { | |
| 156 | if (seconds <= 0) return "0 min"; | |
| 157 | if (seconds < 60) return `${Math.round(seconds)}s`; | |
| 158 | const minutes = seconds / 60; | |
| 159 | if (minutes < 100) return `${Math.round(minutes)} min`; | |
| 160 | const hours = minutes / 60; | |
| 161 | return `${hours < 10 ? hours.toFixed(1) : Math.round(hours).toLocaleString("en-US")} h`; | |
| 162 | } | |
| 163 | ||
| 164 | /** A price per minute, to a tenth of a cent or finer: "$0.0012". */ | |
| 165 | export function perMinute(micros: number): string { | |
| 166 | if (micros <= 0) return "$0"; | |
| 167 | const dollars = micros / 1_000_000; | |
| 168 | const digits = dollars >= 0.01 ? 3 : Math.min(6, Math.max(4, 2 - Math.floor(Math.log10(dollars)))); | |
| 169 | return `$${dollars.toFixed(digits).replace(/0+$/, "").replace(/\.$/, "")}`; | |
| 170 | } |