Report each macOS app's build commit in /api/admin/status

This commit is contained in:
Ian Gulliver
2026-10-17 15:32:35 -07:00
parent 702ce49ff1
commit 24b04966dc
9 changed files with 306 additions and 241 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ The app connects to byre's `tailscale serve` HTTPS URL, so its requests get the
- `UserNotifications` for notifications.
- Third-party code: Apple's `swift-openapi-generator` (with `swift-openapi-runtime` and `swift-openapi-urlsession`) for the API types and client, and SwiftTerm for the terminal.
The project is a Swift package under `mac/`, built with `swift build`, not an Xcode project. It builds with Xcode's toolchain: `xcode-select --print-path` must print `/Applications/Xcode.app/Contents/Developer`. With the Command Line Tools selected while Xcode is installed, Swift Build finds two toolchains for the Command Line Tools path and picks one at random on each run; the pick is part of the build request, so every change of pick rebuilds every target. `mac/bundle.sh` builds the release configuration in one directory shared by every worktree, `~/Library/Caches/byre/build`: SwiftPM keys a build on the package's absolute path, so a build in a fresh worktree, or one sharing a `--scratch-path`, recompiles every dependency. Under a `lockf` lock on `~/Library/Caches/byre/build.lock`, so builds from different worktrees take turns, it copies the worktree's `mac/` and `api/` there with `rsync --checksum`, which rewrites only files whose contents differ and leaves the rest's modification times alone, builds, and copies the executable out to `mac/.build/Byre`. It passes `--no-color-diagnostics` because `COLOR_DIAGNOSTICS` otherwise follows whether output is a terminal and is part of the build request, so agents whose shells differ would each rebuild every target in turn; `build-request.json` under `.build/out/Intermediates.noindex/XCBuildData/` shows what a build was keyed on. It then assembles `mac/.build/byre.app` (the executable, `mac/Info.plist` with the fixed bundle identifier `run.fc.byre.mac`, the `mac/fonts/` files and an `.icns` rendered from `mac/icon.svg`, cached as `mac/.build/byre.icns` until the SVG changes) and signs it; notification permission needs the bundle identifier.
The project is a Swift package under `mac/`, built with `swift build`, not an Xcode project. It builds with Xcode's toolchain: `xcode-select --print-path` must print `/Applications/Xcode.app/Contents/Developer`. With the Command Line Tools selected while Xcode is installed, Swift Build finds two toolchains for the Command Line Tools path and picks one at random on each run; the pick is part of the build request, so every change of pick rebuilds every target. `mac/bundle.sh` builds the release configuration in one directory shared by every worktree, `~/Library/Caches/byre/build`: SwiftPM keys a build on the package's absolute path, so a build in a fresh worktree, or one sharing a `--scratch-path`, recompiles every dependency. Under a `lockf` lock on `~/Library/Caches/byre/build.lock`, so builds from different worktrees take turns, it copies the worktree's `mac/` and `api/` there with `rsync --checksum`, which rewrites only files whose contents differ and leaves the rest's modification times alone, builds, and copies the executable out to `mac/.build/Byre`. It passes `--no-color-diagnostics` because `COLOR_DIAGNOSTICS` otherwise follows whether output is a terminal and is part of the build request, so agents whose shells differ would each rebuild every target in turn; `build-request.json` under `.build/out/Intermediates.noindex/XCBuildData/` shows what a build was keyed on. It then assembles `mac/.build/byre.app` (the executable, `mac/Info.plist` with the fixed bundle identifier `run.fc.byre.mac` and a `ByreCommit` key added holding the worktree's `git rev-parse HEAD`, which `API` sends as the `Byre-Commit` header on every request beside `Byre-Host`, the Mac's name, the `mac/fonts/` files and an `.icns` rendered from `mac/icon.svg`, cached as `mac/.build/byre.icns` until the SVG changes) and signs it; notification permission needs the bundle identifier.
The app is signed with the team's Apple Development certificate (`codesign --sign "Apple Development"`), with the entitlements in `mac/byre.entitlements` (the team-prefixed application identifier, the iCloud container with CloudKit, and development push for sync), and `mac/byre.provisionprofile` embedded as `Contents/embedded.provisionprofile`. The profile is a development profile Xcode manages: it allows the team's Apple Development certificates and the Macs registered to the team, so it runs only on those Macs, and its CloudKit environment is Development. `security cms -D -i mac/byre.provisionprofile` prints it, `ProvisionedDevices` and `DeveloperCertificates` included, and `security find-identity -v -p codesigning` lists the certificates on this Mac. Building on a Mac not yet in the profile, or one without an Apple Development certificate, means regenerating it with Xcode's automatic signing: an Xcode app project with bundle identifier `run.fc.byre.mac`, the team, and an entitlements file naming the iCloud container with CloudKit, built with `xcodebuild -allowProvisioningUpdates -allowProvisioningDeviceRegistration` while the team's Apple ID is signed in to Xcode, creates the certificate, registers the Mac and writes the new profile to `~/Library/Developer/Xcode/UserData/Provisioning Profiles/`; copy it over `mac/byre.provisionprofile` and commit it.
+2 -2
View File
@@ -112,7 +112,7 @@ Clients reach `tailscale serve` over HTTP/2; it proxies to byre over HTTP/1.1 an
## macOS app
**Building.** `mac/bundle.sh`, from any directory, builds the release configuration with `swift build` in `~/Library/Caches/byre/build`, one build directory every worktree shares and takes turns in (`docs/macos.md`, "Stack", says why), and assembles `mac/.build/byre.app`: the `Byre` executable, `mac/Info.plist`, the `mac/fonts/` woff2 files, an icon rendered from `mac/icon.svg` with `rsvg-convert` and `iconutil` (cached as `mac/.build/byre.icns` until the SVG changes) and `mac/byre.provisionprofile`, then signs the bundle with the Mac's Apple Development certificate and `mac/byre.entitlements`. It prints the bundle's path. It needs Xcode selected (`xcode-select --print-path` prints `/Applications/Xcode.app/Contents/Developer`), `rsvg-convert` (Homebrew's `librsvg`), an Apple Development certificate for the team (`security find-identity -v -p codesigning` lists them) and a Mac registered in the profile (`security cms -D -i mac/byre.provisionprofile` prints `ProvisionedDevices`); `docs/macos.md`, "Stack", says how to regenerate the profile for a new Mac. Debug builds are refused by an `#error` in `ByreApp.swift`. The first build on a Mac fetches the Swift packages pinned in `mac/Package.resolved` and compiles them and the generated API client; later builds, from any worktree, recompile only the targets whose sources differ from the last build's, usually just `Byre`. The API client is generated from `api/openapi.yaml` at build time. The palette is `mac/Sources/Byre/Palette.swift`, one light and one dark value per colour.
**Building.** `mac/bundle.sh`, from any directory, builds the release configuration with `swift build` in `~/Library/Caches/byre/build`, one build directory every worktree shares and takes turns in (`docs/macos.md`, "Stack", says why), and assembles `mac/.build/byre.app`: the `Byre` executable, `mac/Info.plist` with a `ByreCommit` key added holding the worktree's `git rev-parse HEAD`, the `mac/fonts/` woff2 files, an icon rendered from `mac/icon.svg` with `rsvg-convert` and `iconutil` (cached as `mac/.build/byre.icns` until the SVG changes) and `mac/byre.provisionprofile`, then signs the bundle with the Mac's Apple Development certificate and `mac/byre.entitlements`. It prints the bundle's path. It needs Xcode selected (`xcode-select --print-path` prints `/Applications/Xcode.app/Contents/Developer`), `rsvg-convert` (Homebrew's `librsvg`), an Apple Development certificate for the team (`security find-identity -v -p codesigning` lists them) and a Mac registered in the profile (`security cms -D -i mac/byre.provisionprofile` prints `ProvisionedDevices`); `docs/macos.md`, "Stack", says how to regenerate the profile for a new Mac. Debug builds are refused by an `#error` in `ByreApp.swift`. The first build on a Mac fetches the Swift packages pinned in `mac/Package.resolved` and compiles them and the generated API client; later builds, from any worktree, recompile only the targets whose sources differ from the last build's, usually just `Byre`. The API client is generated from `api/openapi.yaml` at build time. The palette is `mac/Sources/Byre/Palette.swift`, one light and one dark value per colour.
**Installing.** The app runs from where it was built: `open mac/.build/byre.app` in the worktree that built it. Nothing copies it to `/Applications`. A new build replaces the bundle, so quit the running app first (`pkill -x Byre` quits every running copy, whichever worktree built it) and open the new one; step 4 of "Deploying" in `AGENTS.md` does this after a deploy. Every build shares the bundle identifier `run.fc.byre.mac` and the same store, so any build reconnects to the server the last one used. Closing the window leaves the app running for notifications; ⌘Q quits it.
@@ -174,7 +174,7 @@ Codex runs with its own home, `codex` under the data directory (`CODEX_HOME`, se
## Status
`GET /api/admin/status` reports `running_turns` (task sessions now `running`, as session, task and vendor; lead turns aren't listed); `rate_limits`, the latest status each vendor sent since byre started (Claude's `rate_limit_info`, with `windowMins` added to each `unifiedWindows` entry by `addClaudeWindowMins` in `internal/agents/manager.go` from the window's name, since Claude doesn't send a length; Codex's `account/rateLimits/updated` reshaped by `codexRateLimit` in `internal/agents/codex.go` into the same `status`, `rateLimitType` and `unifiedWindows` fields, `windowMins` from its `windowDurationMins`). Claude only reports during its turns; Codex is also asked once at startup (`readCodexRateLimit`, a short-lived `codex app-server` that calls `account/rateLimits/read` without opening a thread), so its entry is there before any Codex turn runs, and every Codex update broadcasts `admin.health`; `agent_processes`, `zombies`, `stuck_after_seconds` and `protocol_errors` (below); and server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq).
`GET /api/admin/status` reports `running_turns` (task sessions now `running`, as session, task and vendor; lead turns aren't listed); `rate_limits`, the latest status each vendor sent since byre started (Claude's `rate_limit_info`, with `windowMins` added to each `unifiedWindows` entry by `addClaudeWindowMins` in `internal/agents/manager.go` from the window's name, since Claude doesn't send a length; Codex's `account/rateLimits/updated` reshaped by `codexRateLimit` in `internal/agents/codex.go` into the same `status`, `rateLimitType` and `unifiedWindows` fields, `windowMins` from its `windowDurationMins`). Claude only reports during its turns; Codex is also asked once at startup (`readCodexRateLimit`, a short-lived `codex app-server` that calls `account/rateLimits/read` without opening a thread), so its entry is there before any Codex turn runs, and every Codex update broadcasts `admin.health`; `agent_processes`, `zombies`, `stuck_after_seconds` and `protocol_errors` (below); `clients`, one entry per distinct login, `host` and `commit` among the open `GET /api/events` streams, with the oldest stream's `connected_at` (the app opens two streams, so it shows once). `host` and `commit` come from the `Byre-Host` and `Byre-Commit` headers, which the macOS app sends on every request: the Mac's name (`Host.current().localizedName`, else `ProcessInfo.processInfo.hostName`, the same name `ClientRun.swift` reports) and the `ByreCommit` that `mac/bundle.sh` stamped into its `Info.plist`; they are empty for a client that sends neither. To check that a rebuilt app is running, compare each client's `commit` with the commit it was built from. And server counts (start time, agent processes, projects, active tasks, SSE clients, database size including WAL, last event seq).
Agent health is `internal/agents/health.go`: