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.
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 1 | import { AppWindow, Laptop, SquareTerminal } from "lucide-react"; |
| 2 | import { type ReactNode, useState } from "react"; | |
| 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. | 3 | import { Link } from "react-router"; |
| 4 | ||
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 5 | import type { DesktopBackend, DesktopDiagnosis, DesktopRunner, DesktopUpdate, Runner } from "@g1t/contracts"; |
| 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. | 6 | |
| Merge branch 'main' into rm-agents | 7 | import { useBusy } from "../lib/busy"; |
| 8 | import { BACKEND_LABEL, type DesktopRunnerAction, type DesktopRunnerCapability, type DesktopRunnerView, desktopRunner, jobHref, kindStatusKey, runnerStatusKey, useDesktopRunner } from "../lib/desktop-runner"; | |
| 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. | 9 | import { EmptyState, TimeAgo } from "./ui"; |
| Merge branch 'main' into rm-agents | 10 | import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "./ui/alert-dialog"; |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 11 | import { Badge, type BadgeTone } from "./ui/badge"; |
| 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. | 12 | import { Button } from "./ui/button"; |
| 13 | import { Card } from "./ui/card"; | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 14 | import { Progress } from "./ui/progress"; |
| 15 | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; | |
| 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. | 16 | |
| 17 | /** A diagnosis's tone, as a dot. */ | |
| Merge branch 'main' into rm-agents | 18 | export const DOT: Record<DesktopDiagnosis["tone"], string> = { |
| 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. | 19 | ok: "bg-success", |
| 20 | working: "bg-info", | |
| 21 | off: "bg-faint", | |
| 22 | warning: "bg-warn", | |
| 23 | problem: "bg-danger", | |
| 24 | }; | |
| 25 | ||
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 26 | /** A kind of job's tone, as its label's. */ |
| Merge branch 'main' into rm-agents | 27 | export const BADGE: Record<DesktopDiagnosis["tone"], BadgeTone> = { |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 28 | ok: "success", |
| 29 | working: "info", | |
| 30 | off: "neutral", | |
| 31 | warning: "warn", | |
| 32 | problem: "danger", | |
| 33 | }; | |
| 34 | ||
| 35 | /** Each kind of job's icon: a terminal for Linux jobs, a window for Windows jobs, a laptop for macOS jobs. */ | |
| Merge branch 'main' into rm-agents | 36 | export const ICON: Record<string, ReactNode> = { |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 37 | linux: <SquareTerminal size={15} aria-hidden />, |
| 38 | windows: <AppWindow size={15} aria-hidden />, | |
| 39 | macos: <Laptop size={15} aria-hidden />, | |
| 40 | }; | |
| 41 | ||
| 42 | /** How a kind of job keeps each job from the computer, and what that means. */ | |
| 43 | const ISOLATION: Record<string, { label: string; hint: string }> = { | |
| 44 | "nested-container": { label: "Fresh container in its VM", hint: "Each job runs in a fresh container inside the runner's own Linux VM, and is removed after." }, | |
| 45 | vm: { label: "A VM of its own", hint: "Each job runs in a VM of its own, such as a Windows Sandbox, discarded after." }, | |
| 46 | }; | |
| 47 | ||
| 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. | 48 | type Row = { |
| 49 | machine: string; | |
| 50 | person: string | null; | |
| 51 | backend: DesktopBackend; | |
| 52 | version: string | null; | |
| 53 | diagnosis: DesktopDiagnosis; | |
| 54 | /** This computer's fixes, which only work here. */ | |
| 55 | actions: DesktopRunnerAction[]; | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 56 | /** Sandboxed: each kind of job, Linux and Windows, with how it is (and here, its fixes and Start or Stop). */ |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 57 | capabilities: DesktopRunnerCapability[]; |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 58 | /** The kinds of job say it all: the whole runner's line isn't shown as well. */ |
| 59 | inCapabilities: boolean; | |
| 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. | 60 | here: boolean; |
| 61 | reportedAt: string | null; | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 62 | /** How its runner's updates stand, while g1t is open there. */ |
| 63 | update: DesktopUpdate | null; | |
| 64 | /** Here only: the app's button that looks for a newer runner now. */ | |
| 65 | check: DesktopRunnerAction | null; | |
| Merge branch 'main' into rm-agents | 66 | /** Here only: the app is looking for a newer runner now. */ |
| 67 | checking: boolean; | |
| 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. | 68 | }; |
| 69 | ||
| 70 | /** The desktops to list: this computer, live, first; then the others, as their apps last said, or closed. */ | |
| 71 | export function desktopRows(desktops: DesktopRunner[], here: ReturnType<typeof useDesktopRunner>, slug: string): Row[] { | |
| 72 | const rows: Row[] = []; | |
| 73 | if (here && here.workspace === slug) { | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 74 | const capabilities = here.diagnosis.capabilities ?? []; |
| 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. | 75 | rows.push({ |
| 76 | machine: here.machine, | |
| 77 | person: null, | |
| 78 | backend: here.backend, | |
| 79 | version: here.version, | |
| 80 | diagnosis: here.diagnosis, | |
| 81 | actions: here.diagnosis.actions, | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 82 | capabilities, |
| 83 | inCapabilities: !!here.diagnosis.in_capabilities && capabilities.length > 0, | |
| 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. | 84 | here: true, |
| 85 | reportedAt: null, | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 86 | update: here.diagnosis.update ?? null, |
| Merge branch 'main' into rm-agents | 87 | check: here.diagnosis.update?.check ?? null, |
| 88 | checking: !!here.diagnosis.update?.checking, | |
| 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. | 89 | }); |
| 90 | } | |
| 91 | for (const desktop of desktops) { | |
| 92 | if (rows.some((row) => row.here && row.machine.toLowerCase() === desktop.machine.toLowerCase())) continue; | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 93 | const capabilities = desktop.open ? (desktop.diagnosis.capabilities ?? []).map((c) => ({ ...c, actions: [] })) : []; |
| 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. | 94 | rows.push({ |
| 95 | machine: desktop.machine, | |
| 96 | person: desktop.person, | |
| 97 | backend: desktop.backend, | |
| 98 | version: desktop.version, | |
| 99 | diagnosis: desktop.open ? desktop.diagnosis : desktop.closed, | |
| 100 | actions: [], | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 101 | capabilities, |
| 102 | inCapabilities: desktop.open && !!desktop.diagnosis.in_capabilities && capabilities.length > 0, | |
| 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. | 103 | here: false, |
| 104 | reportedAt: desktop.reportedAt, | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 105 | update: desktop.open ? (desktop.diagnosis.update ?? null) : null, |
| 106 | check: null, | |
| Merge branch 'main' into rm-agents | 107 | checking: false, |
| 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. | 108 | }); |
| 109 | } | |
| 110 | return rows; | |
| 111 | } | |
| 112 | ||
| Merge branch 'main' into rm-agents | 113 | /** |
| 114 | * A diagnosis's fixes, as buttons: they work only on this computer, in the | |
| 115 | * app. Each is busy from its click until the status it waits on changes | |
| 116 | * (`status`, lib/busy.ts); one the app asks about first asks. | |
| 117 | */ | |
| 118 | export function Actions({ | |
| 119 | actions, | |
| 120 | primary = true, | |
| 121 | status, | |
| 122 | working = false, | |
| 123 | onError, | |
| 124 | }: { | |
| 125 | actions: DesktopRunnerAction[]; | |
| 126 | primary?: boolean; | |
| 127 | /** What a press waits on: the runner's status (`runnerStatusKey`, `kindStatusKey`). */ | |
| 128 | status: string; | |
| 129 | /** Busy for a reason of the app's own, such as a check already under way. */ | |
| 130 | working?: boolean; | |
| 131 | onError?: (error: unknown) => void; | |
| 132 | }) { | |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 133 | if (actions.length === 0) return null; |
| 134 | return ( | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 135 | <> |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 136 | {actions.map((action, at) => ( |
| Merge branch 'main' into rm-agents | 137 | <ActionButton key={action.id} action={action} variant={primary && at === 0 ? "secondary" : "outline"} status={status} working={working} onError={onError} /> |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 138 | ))} |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 139 | </> |
| 140 | ); | |
| 141 | } | |
| 142 | ||
| Merge branch 'main' into rm-agents | 143 | function ActionButton({ |
| 144 | action, | |
| 145 | variant, | |
| 146 | status, | |
| 147 | working, | |
| 148 | onError, | |
| 149 | }: { | |
| 150 | action: DesktopRunnerAction; | |
| 151 | variant: "secondary" | "outline"; | |
| 152 | status: string; | |
| 153 | working: boolean; | |
| 154 | onError?: (error: unknown) => void; | |
| 155 | }) { | |
| 156 | const { busy, run } = useBusy(status, onError); | |
| 157 | const [asking, setAsking] = useState(false); | |
| 158 | const go = () => run(() => desktopRunner()?.act(action.id), !!action.waits); | |
| 159 | // The app's question, as a title and what it means: "Remove this computer's sandbox?" and the rest. | |
| 160 | const [title, ...rest] = (action.confirm ?? "").split(/(?<=\?)\s+/); | |
| 161 | return ( | |
| 162 | <> | |
| 163 | <Button size="xs" variant={variant} busy={busy || working} onClick={() => (action.confirm ? setAsking(true) : go())}> | |
| 164 | {action.label} | |
| 165 | </Button> | |
| 166 | {action.confirm && ( | |
| 167 | <AlertDialog open={asking} onOpenChange={setAsking}> | |
| 168 | <AlertDialogContent> | |
| 169 | <AlertDialogHeader> | |
| 170 | <AlertDialogTitle>{title}</AlertDialogTitle> | |
| 171 | {rest.length > 0 && <AlertDialogDescription>{rest.join(" ")}</AlertDialogDescription>} | |
| 172 | </AlertDialogHeader> | |
| 173 | <AlertDialogFooter> | |
| 174 | <AlertDialogCancel>Cancel</AlertDialogCancel> | |
| 175 | <AlertDialogAction onClick={go}>{action.label}</AlertDialogAction> | |
| 176 | </AlertDialogFooter> | |
| 177 | </AlertDialogContent> | |
| 178 | </AlertDialog> | |
| 179 | )} | |
| 180 | </> | |
| 181 | ); | |
| 182 | } | |
| 183 | ||
| 184 | /** | |
| 185 | * Start or Stop for one kind of job on this computer, which leaves the | |
| 186 | * others as they are: busy from the click until its status says it | |
| 187 | * started or stopped. | |
| 188 | */ | |
| 189 | export function Toggle({ capability, status, onError }: { capability: DesktopRunnerCapability; status: string; onError?: (error: unknown) => void }) { | |
| 190 | const { busy, run } = useBusy(status, onError); | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 191 | const on = capability.enabled !== false; |
| 192 | return ( | |
| 193 | <Button | |
| 194 | size="xs" | |
| 195 | variant={on ? "outline" : "soft"} | |
| 196 | className="ml-auto" | |
| Merge branch 'main' into rm-agents | 197 | busy={busy} |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 198 | aria-label={`${on ? "Stop" : "Start"} ${capability.label}`} |
| Merge branch 'main' into rm-agents | 199 | onClick={() => run(() => desktopRunner()?.act(on ? "stop" : "start", capability.id))} |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 200 | > |
| Merge branch 'main' into rm-agents | 201 | {on ? "Stop" : "Start"} |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 202 | </Button> |
| 203 | ); | |
| 204 | } | |
| 205 | ||
| Merge branch 'main' into rm-agents | 206 | /** |
| 207 | * A button that asks the runner for something by its id: busy until the | |
| 208 | * app has it (a page, a window, the log), or with `waits` until the | |
| 209 | * status that follows changes. | |
| 210 | */ | |
| 211 | export function ActButton({ | |
| 212 | action, | |
| 213 | label, | |
| 214 | status, | |
| 215 | fail, | |
| 216 | waits = false, | |
| 217 | }: { | |
| 218 | action: string; | |
| 219 | label: string; | |
| 220 | status: string; | |
| 221 | fail?: (error: unknown) => void; | |
| 222 | waits?: boolean; | |
| 223 | }) { | |
| 224 | const { busy, run } = useBusy(status, fail); | |
| 225 | return ( | |
| 226 | <Button size="xs" variant="outline" busy={busy} onClick={() => run(() => desktopRunner()?.act(action), waits)}> | |
| 227 | {label} | |
| 228 | </Button> | |
| 229 | ); | |
| 230 | } | |
| 231 | ||
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 232 | function Detail({ term, children }: { term: ReactNode; children: ReactNode }) { |
| 233 | return ( | |
| 234 | <div className="flex min-w-0 items-baseline gap-3"> | |
| 235 | <dt className="w-16 shrink-0 text-faint">{term}</dt> | |
| 236 | <dd className="min-w-0 break-words text-fg-soft">{children}</dd> | |
| Merge the desktop app's sandbox: its default runner now starts g1t-runner's sandbox on Windows, macOS and Linux, and says per kind of job (Linux, Windows) what it is doing or why it cannot and how to fix it. | 237 | </div> |
| 238 | ); | |
| 239 | } | |
| 240 | ||
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 241 | /** When the app last looked for a newer runner: the app sends when, and this says how long ago, as "Heard from" does. */ |
| 242 | function Checked({ update }: { update: DesktopUpdate | null }) { | |
| 243 | if (!update?.checked_at) return null; | |
| 244 | return ( | |
| 245 | <span className="block"> | |
| 246 | Checked <TimeAgo at={update.checked_at} /> | |
| 247 | </span> | |
| 248 | ); | |
| 249 | } | |
| 250 | ||
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 251 | /** One kind of job, as a card of its own: its state, what it runs, why it doesn't and the fix, and here its own Start or Stop. */ |
| Merge branch 'main' into rm-agents | 252 | export function KindCard({ |
| 253 | capability, | |
| 254 | here, | |
| 255 | runners, | |
| 256 | update, | |
| 257 | view = null, | |
| 258 | onError, | |
| 259 | }: { | |
| 260 | capability: DesktopRunnerCapability; | |
| 261 | here: boolean; | |
| 262 | runners: Runner[]; | |
| 263 | update: DesktopUpdate | null; | |
| 264 | /** Here: the runner as the app says it now, which its buttons wait on. */ | |
| 265 | view?: DesktopRunnerView | null; | |
| 266 | onError?: (error: unknown) => void; | |
| 267 | }) { | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 268 | const href = jobHref(capability, runners); |
| 269 | const isolation = capability.isolation ? (ISOLATION[capability.isolation] ?? { label: capability.isolation, hint: null }) : null; | |
| 270 | const edge = capability.tone === "problem" ? "border-danger/40" : capability.tone === "warning" ? "border-warn/35" : ""; | |
| 271 | const live = capability.state === "busy" || capability.state === "starting" || capability.state === "downloading" || capability.state === "updating"; | |
| 272 | const fixes = here ? capability.actions : []; | |
| 273 | const toggle = here && capability.can_toggle; | |
| 274 | return ( | |
| 275 | <Card tone="plain" radius="lg" className={`flex min-w-0 flex-col gap-3 p-4 ${edge}`} aria-label={capability.label} role="group"> | |
| 276 | <div className="flex items-center gap-3"> | |
| 277 | <span className={`grid size-9 shrink-0 place-items-center rounded-lg bg-raised ${capability.enabled === false ? "text-faint" : "text-fg-soft"}`}> | |
| 278 | {ICON[capability.id] ?? ICON.linux} | |
| 279 | </span> | |
| 280 | <div className="min-w-0"> | |
| 281 | <p className="text-sm font-medium">{capability.label}</p> | |
| 282 | <Badge tone={BADGE[capability.tone]} className="mt-1" role={here ? "status" : undefined}> | |
| 283 | <span className={`size-1.5 rounded-full bg-current ${live ? "motion-safe:animate-pulse" : ""}`} aria-hidden /> | |
| 284 | {capability.title} | |
| 285 | </Badge> | |
| 286 | </div> | |
| 287 | </div> | |
| 288 | {capability.about && <p className="text-sm text-muted">{capability.about}</p>} | |
| 289 | {capability.job && ( | |
| 290 | <div className="rounded-lg bg-raised px-3 py-2 text-sm"> | |
| 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. | 291 | <span className="block text-[0.6875rem] font-medium tracking-wide text-muted uppercase">{capability.job.kind === "agent" ? "Sandbox run" : "Running"}</span> |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 292 | {href ? ( |
| 293 | <Link to={href} className="font-medium text-fg underline-offset-4 hover:underline"> | |
| 294 | {capability.job.name} | |
| 295 | </Link> | |
| 296 | ) : ( | |
| 297 | <span className="font-medium text-fg">{capability.job.name}</span> | |
| 298 | )} | |
| 299 | <span className="text-muted"> in {capability.job.repo}</span> | |
| 300 | </div> | |
| 301 | )} | |
| 302 | {capability.state === "downloading" && typeof capability.progress === "number" && ( | |
| 303 | <div className="flex items-center gap-3"> | |
| 304 | <Progress value={capability.progress} aria-label={`${capability.label}: the sandbox's image`} className="h-1.5" /> | |
| 305 | <span className="w-9 shrink-0 text-right text-xs text-muted tabular-nums">{capability.progress}%</span> | |
| 306 | </div> | |
| 307 | )} | |
| 308 | {(capability.headline || capability.cause || capability.fix) && ( | |
| 309 | <div className="space-y-1 text-sm"> | |
| 310 | {capability.headline && <p className="font-medium text-fg">{capability.headline}</p>} | |
| 311 | {capability.cause && <p className="text-muted">{capability.cause}</p>} | |
| 312 | {capability.fix && <p className="text-fg-soft">{capability.fix}</p>} | |
| 313 | </div> | |
| 314 | )} | |
| 315 | {(capability.runner || capability.version || isolation) && ( | |
| 316 | <dl className="space-y-0.5 border-t border-line pt-2 text-xs"> | |
| 317 | {capability.runner && <Detail term="On g1t">{capability.runner}</Detail>} | |
| 318 | {capability.version && <Detail term="Version">g1t-runner {capability.version}</Detail>} | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 319 | {(capability.update || (capability.version && update?.checked_at)) && ( |
| 320 | <Detail term="Updates"> | |
| 321 | {capability.update && <span className="block text-accent">{capability.update}</span>} | |
| 322 | <Checked update={update} /> | |
| 323 | </Detail> | |
| 324 | )} | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 325 | {isolation && ( |
| 326 | <Detail term="Isolation"> | |
| 327 | {isolation.hint ? ( | |
| 328 | <Tooltip> | |
| 329 | <TooltipTrigger asChild> | |
| 330 | <span tabIndex={0} className="cursor-help underline decoration-line-strong decoration-dotted underline-offset-4"> | |
| 331 | {isolation.label} | |
| 332 | </span> | |
| 333 | </TooltipTrigger> | |
| 334 | <TooltipContent>{isolation.hint}</TooltipContent> | |
| 335 | </Tooltip> | |
| 336 | ) : ( | |
| 337 | isolation.label | |
| 338 | )} | |
| 339 | </Detail> | |
| 340 | )} | |
| 341 | </dl> | |
| 342 | )} | |
| 343 | {(fixes.length > 0 || toggle) && ( | |
| 344 | <div className="mt-auto flex flex-wrap items-center gap-2"> | |
| Merge branch 'main' into rm-agents | 345 | <Actions |
| 346 | actions={fixes} | |
| 347 | primary={capability.tone === "problem" || (capability.tone === "warning" && capability.state !== "waiting")} | |
| 348 | status={kindStatusKey(capability, view)} | |
| 349 | onError={onError} | |
| 350 | /> | |
| 351 | {toggle && <Toggle capability={capability} status={kindStatusKey(capability, view)} onError={onError} />} | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 352 | </div> |
| 353 | )} | |
| 354 | </Card> | |
| 355 | ); | |
| 356 | } | |
| 357 | ||
| 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. | 358 | /** |
| 359 | * The workspace's desktops: each person's computer with the g1t desktop | |
| 360 | * app, whose runner runs while g1t is open there. What each says, why it | |
| 361 | * isn't running and the fix, is the app's own words (lib/desktop-runner.ts). | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 362 | * A sandboxed one's kinds of job, Linux and Windows, are cards of their own: |
| 363 | * one can run while the other can't, or is stopped; on this computer, each | |
| 364 | * has its own Start or Stop. | |
| 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. | 365 | */ |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 366 | export function DesktopRunners({ desktops, slug, runners = [] }: { desktops: DesktopRunner[] | null; slug: string; runners?: Runner[] }) { |
| 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. | 367 | const here = useDesktopRunner(); |
| 368 | if (!desktops) return <p className="text-sm text-muted">The desktops can't be read right now.</p>; | |
| 369 | const rows = desktopRows(desktops, here, slug); | |
| 370 | if (rows.length === 0) { | |
| 371 | return ( | |
| 372 | <EmptyState title="No desktops yet"> | |
| 373 | The <Link to="/download" className="underline underline-offset-2">g1t desktop app</Link> runs this workspace's self-hosted jobs while it's open, sandboxed, with nothing to set up. | |
| 374 | Open it signed in as an owner of {slug}. | |
| 375 | </EmptyState> | |
| 376 | ); | |
| 377 | } | |
| 378 | return ( | |
| 379 | <Card asChild divided> | |
| 380 | <ul> | |
| 381 | {rows.map((row) => ( | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 382 | <li key={row.machine} className="space-y-3 px-4 py-4"> |
| 383 | <div className="flex items-start gap-3"> | |
| 384 | <div className="min-w-0 grow"> | |
| 385 | <div className="flex flex-wrap items-center gap-x-2 gap-y-1"> | |
| 386 | <Laptop size={13} className="text-muted" aria-hidden /> | |
| 387 | <span className="font-medium">{row.machine}</span> | |
| 388 | {row.here && <Badge tone="accent">This computer</Badge>} | |
| 389 | <Badge tone={row.backend === "sandboxed" ? "neutral" : "warn"}>{BACKEND_LABEL[row.backend]}</Badge> | |
| 390 | {row.person && <span className="text-xs text-faint">@{row.person}</span>} | |
| 391 | </div> | |
| 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. | 392 | </div> |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 393 | <div className="shrink-0 text-right text-xs text-faint"> |
| 394 | {row.version && <div>g1t-runner {row.version}</div>} | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 395 | {/* With no cards to say it, what becomes of a newer runner, and when the app last looked. */} |
| 396 | {row.capabilities.length === 0 && row.update?.note && <div className="mt-0.5 text-accent">{row.update.note}</div>} | |
| 397 | {row.capabilities.length === 0 && row.update?.checked_at && ( | |
| 398 | <div className="mt-0.5"> | |
| 399 | <Checked update={row.update} /> | |
| 400 | </div> | |
| 401 | )} | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 402 | {row.reportedAt && ( |
| 403 | <div className="mt-0.5"> | |
| 404 | Heard from <TimeAgo at={row.reportedAt} /> | |
| 405 | </div> | |
| 406 | )} | |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 407 | {row.check && ( |
| 408 | <div className="mt-2"> | |
| Merge branch 'main' into rm-agents | 409 | <Actions actions={[row.check]} primary={false} status={here ? runnerStatusKey(here) : ""} working={row.checking} /> |
| The desktop app is 0.3.2, and its runner updates are fast, seen and only ever g1t's. The app now trusts a g1t-runner release only when its manifest's Ed25519 signature, latest.json.sig, checks out against g1t's runner release key, which is built into the app (the public half of RUNNER_RELEASE_PUBLIC_KEY, kept in runner/native.rs, where development builds have it too; G1T_RUNNER_RELEASE_KEY overrides it for a g1t with its own key), as the runner already did with its own updates. A manifest with no signature, or one that doesn't check out, is refused and nothing in it is used: the runner keeps the version it has and says "Update refused: not signed by g1t", or, with no runner installed yet, "Not running: the runner's release isn't signed by g1t", each with why and what to do. It looks every hour rather than every six, on a thread of its own so the runner is looked after meanwhile, and what it finds waits, downloaded and checked, until each kind of job is idle, then goes in kind by kind, never cutting a job short: Linux jobs take the release's image (sandbox update) as soon as they have no job, while Windows jobs, whose runner is the program itself, are stopped after their job (sandbox down --only windows --drain), the program is replaced and they start again, the other kind running on throughout; a runner in Docker or on the computer stops between jobs and starts on the new one. The program there is moved aside rather than overwritten, so a status the app is asking at that moment can't stop it. Each kind of job's card in Settings shows its runner version, when the app last looked ("Checked 12 min ago") and a newer one waiting ("0.3.2 ready, installs after this job", "Installing 0.3.2"), and "Check for runner updates" beside the computer's name looks at once and installs what it finds, even with "Download updates by themselves" off, which otherwise stops the hourly look. The desktop report carries the same (snake_case: the diagnosis's update, with checked_at, ready and note, and each kind's update), so the Runners page's Desktops cards show it, "Checked" with how long ago, and this computer's row has the button too. All the words are diagnosis.rs's. The desktop guide says how updates work, what each card shows, the new troubleshooting rows and where the release key lives. Tests cover the signature with a key pair of their own and with release 0.3.0's manifest and signature as g1t.sh published them, kept byte for byte (.gitattributes), the wording, the cards and the report. | 410 | </div> |
| 411 | )} | |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 412 | </div> |
| 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. | 413 | </div> |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 414 | {!row.inCapabilities && ( |
| 415 | <div className="flex items-start gap-3" role={row.here ? "status" : undefined}> | |
| 416 | <span className={`mt-1.5 size-2 shrink-0 rounded-full ${DOT[row.diagnosis.tone]}`} aria-hidden /> | |
| 417 | <div className="min-w-0"> | |
| 418 | <p className="text-sm text-fg">{row.diagnosis.title}</p> | |
| 419 | {row.diagnosis.cause && <p className="mt-0.5 text-sm text-muted">{row.diagnosis.cause}</p>} | |
| 420 | {row.diagnosis.fix && <p className="mt-0.5 text-sm text-fg-soft">{row.diagnosis.fix}</p>} | |
| 421 | {row.actions.length > 0 && ( | |
| 422 | <div className="mt-2 flex flex-wrap gap-2"> | |
| Merge branch 'main' into rm-agents | 423 | <Actions actions={row.actions} status={here ? runnerStatusKey(here) : ""} /> |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 424 | </div> |
| 425 | )} | |
| 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. | 426 | </div> |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 427 | </div> |
| 428 | )} | |
| 429 | {row.capabilities.length > 0 && ( | |
| 430 | <div className={`grid gap-3 ${row.capabilities.length > 1 ? "sm:grid-cols-2" : ""}`}> | |
| 431 | {row.capabilities.map((capability) => ( | |
| Merge branch 'main' into rm-agents | 432 | <KindCard key={capability.id} capability={capability} here={row.here} runners={runners} update={row.update} view={row.here ? here : null} /> |
| Merge the desktop runner's settings: a card for each kind of job, Linux and Windows, each with its state, job, cause and fix, and its own Start and Stop; an honest wait for a release's sandbox images, checked again by itself; and the same on the Runners page. | 433 | ))} |
| 434 | </div> | |
| 435 | )} | |
| 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. | 436 | </li> |
| 437 | ))} | |
| 438 | </ul> | |
| 439 | </Card> | |
| 440 | ); | |
| 441 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.