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 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import type { AgentRun, Price, RunnerActivity, UsageReport } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import { cloudNow, cloudPrices, machineTime, ownWaiting, perMinute, runnerCost } from "./runners.ts"; | |
| 7 | ||
| 8 | function run(id: string, status: AgentRun["status"], over: Partial<AgentRun> = {}): AgentRun { | |
| 9 | return { | |
| 10 | id, | |
| 11 | repo: { namespace: "acme", name: "web" }, | |
| 12 | number: 12, | |
| 13 | title: "Fix the flaky checkout test", | |
| 14 | kind: "implement", | |
| 15 | agent: "g1t", | |
| 16 | model: null, | |
| 17 | status, | |
| 18 | step: null, | |
| 19 | steps: [], | |
| 20 | stepCount: 0, | |
| 21 | startedBy: null, | |
| 22 | error: null, | |
| 23 | costUsd: null, | |
| 24 | turns: null, | |
| 25 | createdAt: "2026-10-10T10:00:00Z", | |
| 26 | startedAt: "2026-10-10T10:01:00Z", | |
| 27 | finishedAt: null, | |
| 28 | updatedAt: "2026-10-10T10:02:00Z", | |
| 29 | ...over, | |
| 30 | }; | |
| 31 | } | |
| 32 | ||
| 33 | const activity: RunnerActivity = { | |
| 34 | cloud_jobs: [{ id: "job_1", name: "test (ubuntu)", run_id: "wr_1", repo: "acme/web", started_at: "2026-10-10T10:05:00Z" }], | |
| 35 | cloud_jobs_queued: 2, | |
| 36 | handed_over: [ | |
| 37 | { id: "rtk_1", run_id: "run_mine", kind: "implement", title: "On my runner", repo: "acme/web", status: "in_progress", runner_name: "mac", created_at: "2026-10-10T09:00:00Z", started_at: null }, | |
| 38 | { id: "rtk_2", run_id: null, kind: "checks", title: "Waiting", repo: "acme/web", status: "queued", runner_name: null, created_at: "2026-10-10T09:00:00Z", started_at: null }, | |
| 39 | ], | |
| 40 | self_hosted_jobs_queued: 3, | |
| 41 | }; | |
| 42 | ||
| 43 | test("g1t cloud is the running agent runs not handed to the workspace's runners, and the jobs in g1t's sandboxes", () => { | |
| 44 | const now = cloudNow([run("run_a", "running"), run("run_mine", "running"), run("run_q", "queued")], activity, 200, 100)!; | |
| 45 | assert.deepEqual( | |
| 46 | now.running.map((work) => [work.kind, work.id, work.href]), | |
| 47 | [ | |
| 48 | ["workflow", "job_1", "/acme/web/actions/runs/wr_1"], | |
| 49 | ["agent", "run_a", "/acme/web/agents/runs/run_a"], | |
| 50 | ], | |
| 51 | ); | |
| 52 | // One queued agent run and two queued jobs. | |
| 53 | assert.equal(now.queued, 3); | |
| 54 | assert.equal(now.more, false); | |
| 55 | }); | |
| 56 | ||
| 57 | test("a service that does not answer leaves only its part out", () => { | |
| 58 | assert.equal(cloudNow(null, null, 200, 100), null); | |
| 59 | assert.equal(cloudNow([run("run_a", "running")], null, 200, 100)!.running.length, 1); | |
| 60 | assert.equal(ownWaiting(null), null); | |
| 61 | assert.deepEqual(ownWaiting(activity), { tasks: 1, jobs: 3 }); | |
| 62 | assert.equal(runnerCost(null), null); | |
| 63 | }); | |
| 64 | ||
| 65 | test("a full list says there may be more", () => { | |
| 66 | assert.equal(cloudNow([run("run_a", "running")], activity, 1, 100)!.more, true); | |
| 67 | }); | |
| 68 | ||
| 69 | test("this month's time adds agent sandbox and sandbox time for g1t cloud, and self-hosted time for the workspace's own", () => { | |
| 70 | const meter = (key: string, quantity: number, micros: number) => ({ key, label: key, product: "x", unit: "seconds", quantity, micros, daily: [], byProject: [] }); | |
| 71 | const report = { | |
| 72 | from: "2026-10-01", | |
| 73 | until: "2026-10-10", | |
| 74 | free: false, | |
| 75 | products: [ | |
| 76 | { key: "agent", label: "Agent", micros: 0, meters: [meter("agent_sandbox", 600, 1_000_000), meter("agent_models", 9, 5_000_000)] }, | |
| 77 | { key: "sandboxes", label: "Sandboxes", micros: 0, meters: [meter("sandbox", 120, 200_000), meter("self_hosted", 3600, 0)] }, | |
| 78 | ], | |
| 79 | } as unknown as UsageReport; | |
| 80 | assert.deepEqual(runnerCost(report), { from: "2026-10-01", until: "2026-10-10", cloudSeconds: 720, cloudMicros: 1_200_000, ownSeconds: 3600, free: false }); | |
| 81 | }); | |
| 82 | ||
| 83 | test("prices are the sandbox's parts when both are priced, else the one price, per minute", () => { | |
| 84 | const price = (meter: string, unit: string, priceMicros: number) => ({ meter, title: meter, unit, priceMicros }) as Price; | |
| 85 | assert.deepEqual( | |
| 86 | cloudPrices([price("sandbox_second", "second", 25), price("sandbox_base_second", "second", 14.5), price("sandbox_cpu_second", "vCPU-second", 24)])!.map((p) => [p.meter, p.unit, p.perMinuteMicros]), | |
| 87 | [ | |
| 88 | ["sandbox_base_second", "minute", 870], | |
| 89 | ["sandbox_cpu_second", "vCPU-minute", 1440], | |
| 90 | ], | |
| 91 | ); | |
| 92 | assert.deepEqual(cloudPrices([price("sandbox_second", "second", 25)])!.map((p) => p.perMinuteMicros), [1500]); | |
| 93 | assert.equal(cloudPrices([]), null); | |
| 94 | assert.equal(cloudPrices(null), null); | |
| 95 | }); | |
| 96 | ||
| 97 | test("machine time and prices read as people say them", () => { | |
| 98 | assert.equal(machineTime(0), "0 min"); | |
| 99 | assert.equal(machineTime(45), "45s"); | |
| 100 | assert.equal(machineTime(720), "12 min"); | |
| 101 | assert.equal(machineTime(12_240), "3.4 h"); | |
| 102 | assert.equal(perMinute(1500), "$0.0015"); | |
| 103 | assert.equal(perMinute(870), "$0.00087"); | |
| 104 | assert.equal(perMinute(0), "$0"); | |
| 105 | }); |