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", | |
| 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. | 14 | kind: "checks", |
| 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 | 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 | ||
| 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. | 43 | test("g1t cloud is the running sandbox runs not handed to the workspace's runners, and the jobs 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. | 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"], | |
| 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. | 49 | ["agent", "run_a", "/acme/web/pull/12"], |
| 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. | 50 | ], |
| 51 | ); | |
| 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. | 52 | // One queued sandbox run and two queued 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. | 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 | }); |