feat(seats): memory, the Playwright MCP config, and the seats library

:Memory: MemAvailable and the reserve policy, the sampler of a pass's peak
tree PSS (one sweep, a pause of three sweeps at least, stopped with the
driver), and the calibration file every driver on the machine shares,
re-read and written under its lock.

:Mcp: the pinned @playwright/mcp 0.0.83 arguments and the --mcp-config of
one pass, compact and in a fixed key order.

:Seats: the dotenv loader (token lines only, TOKEN7 inert), the one-word
probe (run under the supervisor, a missing binary reported with its errno
text), the window maths, the ranking, the Fable merge, the editor-bundle
lookup of the claude binary, the redaction of anything token-shaped, and
the report. This libc++ has no tzdb, so a zone by name converts through
the C library with TZ set, reading the system's zoneinfo.

tests/Seats: 92 checks against reference vectors, numbered in order, all
green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 07:32:14 +01:00
commit 8ffbd6cf5d
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
9 changed files with 1764 additions and 3 deletions

View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module Crafter.Review:Mcp_impl;
import :Mcp;
import :Json;
import :Text;
import :Tools;
import std;
namespace fs = std::filesystem;
namespace Crafter::Review::Mcp {
std::vector<std::string> DefaultArgs() {
return {"-y", std::format("@playwright/mcp@{}", PlaywrightMcpVersion), "--headless", "--isolated", "--browser", "chromium"};
}
Json PlaywrightConfig(const Json& functional, const std::string& outputDir) {
const Json& mcp = Dyn::Or(Dyn::Get(Dyn::Or(functional, Dyn::EmptyDict()), "mcp"), Dyn::EmptyDict());
Json args = Json::array();
const Json& configured = Dyn::Get(mcp, "args");
if (!configured.is_null()) {
for (const Json& arg : configured) args.push_back(arg);
} else {
for (const std::string& arg : DefaultArgs()) args.push_back(arg);
}
if (!Dyn::In(Json("--output-dir"), args)) {
args.push_back("--output-dir");
args.push_back(outputDir);
}
const Json& command = Dyn::Get(mcp, "command");
return {{"mcpServers", {{std::string(Tools::McpServer), {{"command", Dyn::Truthy(command) ? command : Json("npx")}, {"args", std::move(args)}}}}}};
}
std::string ConfigJson(const Json& config) {
return config.dump(-1, ' ', true, Json::error_handler_t::replace);
}
fs::path OutputDir(const fs::path& scratch, std::string_view label) {
const fs::path dir = scratch / "playwright" / Dyn::Replace(Dyn::Replace(label, "/", "_"), " ", "_");
fs::create_directories(dir);
return dir;
}
}

View file

@ -0,0 +1,214 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module Crafter.Review:Memory_impl;
import :Memory;
import :Json;
import :Text;
import :Platform;
import std;
namespace fs = std::filesystem;
namespace Crafter::Review::Memory {
std::optional<std::int64_t> Available(const fs::path& path) {
const std::map<std::string, std::int64_t> info = Platform::MemInfo(path);
const auto found = info.find("MemAvailable");
if (found == info.end()) return std::nullopt;
return found->second;
}
std::int64_t Reserve(const std::optional<std::string>& configured, const fs::path& path) {
if (configured && !configured->empty()) {
const std::optional<double> value = Dyn::ParseFloat(*configured);
if (!value) throw std::invalid_argument(Dyn::FloatError(*configured));
return static_cast<std::int64_t>(*value);
}
const std::map<std::string, std::int64_t> info = Platform::MemInfo(path);
const auto total = info.find("MemTotal");
return std::max<std::int64_t>(2048, (total == info.end() ? 0 : total->second) / 10);
}
std::int64_t Reserve() {
return Reserve(Platform::GetEnv("REVIEW_GATES_MEM_RESERVE_MB"));
}
struct Sampler::State {
double interval = 3.0;
Measure measure;
Scan scan;
std::mutex lock;
std::condition_variable wake;
std::map<std::string, std::int64_t> watched;
std::map<std::string, double> peaks;
bool started = false;
bool stopping = false;
std::thread thread;
double Sample() {
const auto start = std::chrono::steady_clock::now();
std::vector<std::pair<std::string, std::int64_t>> items;
{
const std::lock_guard guard(lock);
items.assign(watched.begin(), watched.end());
}
if (!items.empty()) {
const std::map<std::int64_t, std::vector<std::int64_t>> kids = scan();
for (const auto& [key, pid] : items) {
const double value = measure(pid, kids);
const std::lock_guard guard(lock);
const auto peak = peaks.find(key);
if (peak != peaks.end()) peak->second = std::max(peak->second, value);
}
}
return std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
}
void Loop() {
double sweep = 0.0;
std::unique_lock guard(lock);
while (!stopping) {
wake.wait_for(guard, std::chrono::duration<double>(Pause(interval, sweep)), [this] { return stopping; });
if (stopping) break;
guard.unlock();
try {
sweep = Sample();
} catch (const std::exception&) {
// A measurement never stops the run.
sweep = interval;
}
guard.lock();
}
}
};
Sampler::Sampler(double interval, Measure measure, Scan scan) : state(std::make_shared<State>()) {
state->interval = interval;
state->measure = measure ? std::move(measure) : Measure([](std::int64_t pid, const std::map<std::int64_t, std::vector<std::int64_t>>& kids) {
return Platform::PssMb(Platform::Tree(pid, kids));
});
state->scan = scan ? std::move(scan) : Scan([] { return Platform::PpidMap(); });
}
Sampler::~Sampler() {
{
const std::lock_guard guard(state->lock);
state->stopping = true;
}
state->wake.notify_all();
if (state->thread.joinable()) state->thread.join();
}
void Sampler::Watch(const std::string& key, std::int64_t pid) {
const std::lock_guard guard(state->lock);
state->watched[key] = pid;
state->peaks[key] = 0.0;
if (!state->started) {
state->started = true;
state->thread = std::thread([owner = state.get()] { owner->Loop(); });
}
}
double Sampler::Sample() {
return state->Sample();
}
std::optional<double> Sampler::Done(const std::string& key) {
const std::lock_guard guard(state->lock);
state->watched.erase(key);
const auto peak = state->peaks.find(key);
if (peak == state->peaks.end()) return std::nullopt;
const double value = peak->second;
state->peaks.erase(peak);
if (value == 0.0) return std::nullopt;
return Dyn::Round(value, 1);
}
double Sampler::Pause(double interval, double sweep) {
return std::max(interval, 3 * sweep);
}
namespace {
// A peak worth keeping: a number, no bool, above zero.
bool Positive(const Json& value) {
if (!value.is_number()) return false;
return value.is_number_float() ? value.get<double>() > 0.0 : (value.is_number_unsigned() ? value.get<std::uint64_t>() > 0 : value.get<std::int64_t>() > 0);
}
double AsDouble(const Json& value) {
return value.is_number_float() ? value.get<double>() : (value.is_number_unsigned() ? static_cast<double>(value.get<std::uint64_t>()) : static_cast<double>(value.get<std::int64_t>()));
}
Json Clean(const Json& value) {
Json out = Json::array();
if (!value.is_array()) return out;
for (const Json& item : value) {
if (Positive(item)) out.push_back(item);
}
return out;
}
}
Calibration::Calibration(fs::path path, const fs::path& repo, std::map<std::string, std::int64_t> defaults)
: path(std::move(path)), defaults(std::move(defaults)) {
std::error_code error;
this->repo = fs::weakly_canonical(fs::absolute(repo), error).string();
}
Json Calibration::Load() const {
try {
Json document = Dyn::LoadJsonFile(path);
return document.is_object() ? document : Json::object();
} catch (const std::exception&) {
return Json::object();
}
}
std::vector<Json> Calibration::Peaks(std::string_view environment) const {
const Json document = Load();
const Json& mine = Dyn::Get(document, repo);
const Json peaks = Clean(mine.is_object() ? Dyn::Get(mine, environment) : Json());
return {peaks.begin(), peaks.end()};
}
Calibration::Cost Calibration::CostOf(std::string_view environment) const {
const std::vector<Json> peaks = Peaks(environment);
if (!peaks.empty()) {
// max(): the first of equal maxima.
const Json* best = &peaks.front();
for (const Json& peak : peaks) {
if (AsDouble(peak) > AsDouble(*best)) best = &peak;
}
return {*best, std::format("the highest of its last {} measured peaks", peaks.size())};
}
const auto found = defaults.find(std::string(environment));
std::int64_t fallback = 1024;
if (found != defaults.end()) {
fallback = found->second;
} else if (!defaults.empty()) {
fallback = std::ranges::max(std::views::values(defaults));
}
return {Json(fallback), "the default measured 2026-10-22; none measured in this repository yet"};
}
bool Calibration::Record(std::string_view environment, double megabytes) {
if (!(megabytes > 0.0) || !std::isfinite(megabytes)) return false;
try {
const std::lock_guard guard(lock);
// Without the lock the write is still made.
const Platform::FileLock fileLock(std::format("{}.lock", path.string()));
Json document = Load();
Json mine = Dyn::Get(document, repo);
if (!mine.is_object()) mine = Json::object();
Json peaks = Clean(Dyn::Get(mine, environment));
peaks.push_back(Dyn::Round(megabytes, 1));
if (peaks.size() > Keep) peaks.erase(peaks.begin(), peaks.begin() + static_cast<std::ptrdiff_t>(peaks.size() - Keep));
mine[std::string(environment)] = std::move(peaks);
document[repo] = std::move(mine);
Platform::WriteAtomic(path, DumpFile(document), 0666);
return true;
} catch (const std::exception&) {
return false;
}
}
}

View file

@ -0,0 +1,997 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module;
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
module Crafter.Review:Seats_impl;
import :Seats;
import :Json;
import :Text;
import :Stream;
import :Platform;
import :Process;
import :Crypto;
import std;
namespace fs = std::filesystem;
namespace Crafter::Review::Seats {
namespace {
constexpr std::array<std::string_view, 7> Weekdays = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
// en-GB, as Intl.DateTimeFormat abbreviates them (September is "Sept").
constexpr std::array<std::string_view, 12> Months = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"};
constexpr std::array<std::string_view, 4> ModelFamilies = {"fable", "opus", "sonnet", "haiku"};
const std::string& EnvOf(const std::map<std::string, std::string>& env, const std::string& key) {
static const std::string none;
const auto found = env.find(key);
return found == env.end() ? none : found->second;
}
double AsDouble(const Json& value) {
if (value.is_number_float()) return value.get<double>();
if (value.is_boolean()) return value.get<bool>() ? 1.0 : 0.0;
if (value.is_number_unsigned()) return static_cast<double>(value.get<std::uint64_t>());
if (value.is_number_integer()) return static_cast<double>(value.get<std::int64_t>());
return 0.0;
}
// The value as a float, or nullopt for one that is none.
std::optional<double> FloatOf(const Json& value) {
if (value.is_number() || value.is_boolean()) return AsDouble(value);
if (value.is_string()) return Dyn::ParseFloat(value.get_ref<const std::string&>());
return std::nullopt;
}
// An integer when n is integral, else n.
Json IntIfWhole(double n) {
if (std::isfinite(n) && n == std::floor(n) && std::fabs(n) < 9.0e18) return Json(static_cast<std::int64_t>(n));
return Json(n);
}
// A number field as a float, 0 when it is falsy.
double NumberOr0(const Json& value) {
return Dyn::Truthy(value) ? AsDouble(value) : 0.0;
}
bool Less(const Json& a, const Json& b) {
return AsDouble(a) < AsDouble(b);
}
// An ASCII literal, case-insensitive as re.I reads it, at `at`.
bool MatchCi(std::u32string_view text, std::size_t at, std::string_view word) {
if (at + word.size() > text.size()) return false;
for (std::size_t i = 0; i < word.size(); ++i) {
const char p = word[i];
if (p >= 'a' && p <= 'z') {
if (Dyn::IgnoreCaseLetter(text[at + i]) != p) return false;
} else if (text[at + i] != static_cast<char32_t>(static_cast<std::uint8_t>(p))) {
return false;
}
}
return true;
}
bool ContainsCi(std::string_view text, std::string_view word) {
const std::u32string decoded = Dyn::Decode(text);
for (std::size_t at = 0; at + word.size() <= decoded.size(); ++at) {
if (MatchCi(decoded, at, word)) return true;
}
return false;
}
// seats.CREDIT_RE: the organization's credit balance is gone, or a
// spend limit was reached.
bool CreditText(std::string_view text) {
constexpr std::array<std::string_view, 9> Phrases = {
"credit balance too low", "credit balance is too low", "add funds", "reached your api usage limits", "reached your specified api usage limits",
"reached your workspace api usage limits", "reached your specified workspace api usage limits", "crossed its monthly api usage threshold",
"enforced_spend_limit_reached",
};
return std::ranges::any_of(Phrases, [&](std::string_view phrase) { return ContainsCi(text, phrase); });
}
std::string ResultText(const Json& result) {
return Dyn::StrOr(Dyn::Get(Dyn::Or(result, Dyn::EmptyDict()), "result"), "");
}
bool TokenChar(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-';
}
std::size_t RunOf(std::string_view s, std::size_t at, const std::function<bool(char)>& in) {
std::size_t end = at;
while (end < s.size() && in(s[end])) ++end;
return end - at;
}
// re.sub(pattern, replacement, text) for a pattern given as a matcher:
// the length of the match at a position, or 0.
std::string SubAll(std::string_view text, const std::function<std::size_t(std::string_view, std::size_t)>& match, std::string_view replacement) {
std::string out;
std::size_t at = 0;
while (at < text.size()) {
const std::size_t length = match(text, at);
if (length > 0) {
out += replacement;
at += length;
} else {
out.push_back(text[at]);
++at;
}
}
return out;
}
// eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}
std::size_t Jwt(std::string_view s, std::size_t at) {
if (!s.substr(at).starts_with("eyJ")) return 0;
std::size_t i = at + 3;
for (std::int32_t part = 0; part < 3; ++part) {
if (part > 0) {
if (i >= s.size() || s[i] != '.') return 0;
++i;
}
const std::size_t run = RunOf(s, i, TokenChar);
if (run < 10) return 0;
i += run;
}
return i - at;
}
// sk-ant-[A-Za-z0-9_-]{20,}
std::size_t AnthropicKey(std::string_view s, std::size_t at) {
if (!s.substr(at).starts_with("sk-ant-")) return 0;
const std::size_t run = RunOf(s, at + 7, TokenChar);
return run >= 20 ? 7 + run : 0;
}
// Bearer\s+[A-Za-z0-9._~+/=-]{16,}
std::size_t Bearer(std::string_view s, std::size_t at) {
if (!s.substr(at).starts_with("Bearer")) return 0;
const std::size_t gap = Dyn::SpaceRun(s, at + 6);
if (gap == 0) return 0;
const std::size_t run = RunOf(s, at + 6 + gap, [](char c) { return TokenChar(c) || std::string_view("._~+/=").contains(c); });
return run >= 16 ? 6 + gap + run : 0;
}
// [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}, as the regex engine
// backs off: the local part runs up to the @; the domain is the
// longest prefix of its run that ends in a dot and two letters or more.
std::size_t Email(std::string_view s, std::size_t at) {
auto local = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || std::string_view("._%+-").contains(c); };
auto domain = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-'; };
auto letter = [](char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); };
const std::size_t name = RunOf(s, at, local);
if (name == 0 || at + name >= s.size() || s[at + name] != '@') return 0;
const std::size_t start = at + name + 1;
const std::size_t run = RunOf(s, start, domain);
for (std::size_t k = run; k-- > 1;) {
if (s[start + k] != '.') continue;
const std::size_t letters = RunOf(s, start + k + 1, letter);
if (letters >= 2) return name + 1 + k + 1 + letters;
}
return 0;
}
// shutil.which(name, path=...): an executable file on the path.
std::optional<std::string> Which(std::string_view name, std::string_view path) {
for (const std::string& dir : Dyn::Split(path, ":")) {
// An empty PATH entry gives the bare name.
const fs::path candidate = dir.empty() ? fs::path(name) : fs::path(dir) / name;
std::error_code error;
if (fs::is_regular_file(candidate, error) && access(candidate.c_str(), X_OK) == 0) return candidate.string();
}
return std::nullopt;
}
// _version_key: claude-code-<major>.<minor>.<patch>.
std::array<std::int64_t, 3> VersionKey(std::string_view name) {
const std::size_t at = name.find("claude-code-");
if (at == std::string_view::npos) return {0, 0, 0};
for (std::size_t from = at; from != std::string_view::npos; from = name.find("claude-code-", from + 1)) {
std::array<std::int64_t, 3> parts {};
std::size_t i = from + 12;
bool ok = true;
for (std::int32_t p = 0; p < 3 && ok; ++p) {
if (p > 0) {
if (i >= name.size() || name[i] != '.') {
ok = false;
break;
}
++i;
}
const std::size_t digits = Dyn::DecimalRun(name, i);
if (digits == 0) {
ok = false;
break;
}
parts[static_cast<std::size_t>(p)] = Dyn::ParseInt(name.substr(i, digits)).value_or(0);
i += digits;
}
if (ok) return parts;
}
return {0, 0, 0};
}
const Json& Window(const Json& seat, std::string_view key) {
return Dyn::Get(Dyn::Or(Dyn::Get(seat, "windows"), Dyn::EmptyDict()), key);
}
std::string SeatName(const Json& seat) {
const Json& id = Dyn::At(seat, "seat");
return Dyn::Equal(id, Json(0)) ? std::string("local") : std::format("seat {}", Dyn::Str(id));
}
std::string SeatCell(const Json& seat) {
const Json& id = Dyn::At(seat, "seat");
return Dyn::Equal(id, Json(0)) ? std::string("local") : Dyn::Str(id);
}
std::int64_t Inflight(const Json& board, const std::map<std::int32_t, std::string>& keys, const Json& seat) {
const auto key = keys.find(Dyn::At(seat, "seat").get<std::int32_t>());
if (key == keys.end() || !board.is_object()) return 0;
const Json& record = Dyn::Get(Dyn::Or(Dyn::Get(board, "seats"), Dyn::EmptyDict()), key->second);
return static_cast<std::int64_t>(AsDouble(Dyn::Get(Dyn::Or(record, Dyn::EmptyDict()), "inflight")));
}
std::vector<Json> SortedBySeat(const std::vector<Json>& seats) {
std::vector<Json> out = seats;
std::ranges::stable_sort(out, [](const Json& a, const Json& b) { return Less(Dyn::At(a, "seat"), Dyn::At(b, "seat")); });
return out;
}
}
double JsRound(double x) {
return std::floor(x + 0.5);
}
std::string Fmt(const Json& value) {
if ((value.is_number() || value.is_boolean())) {
const double n = AsDouble(value);
if (std::isfinite(n) && n == std::floor(n)) return std::to_string(static_cast<std::int64_t>(n));
}
return Dyn::Str(value);
}
Json DefaultConfig() {
return {{"maxUtil", 85}, {"lensCostPct", 10}, {"waitMaxMin", 15}, {"policy", "spread"}, {"handoverPct", 90}};
}
Json ConfigFromEnv(const std::map<std::string, std::string>& env) {
Json config = DefaultConfig();
const Json defaults = DefaultConfig();
for (const auto& [key, variable] : std::array<std::pair<std::string_view, std::string_view>, 4>{{
{"maxUtil", "REVIEW_GATES_SEAT_MAX_UTIL"}, {"lensCostPct", "REVIEW_GATES_LENS_COST_PCT"},
{"waitMaxMin", "REVIEW_GATES_SEAT_WAIT_MIN"}, {"handoverPct", "REVIEW_GATES_SEAT_HANDOVER_PCT"}}}) {
const auto found = env.find(std::string(variable));
Json value = defaults.at(std::string(key));
if (found != env.end()) {
const std::optional<double> n = Dyn::ParseFloat(found->second);
if (n && !Dyn::Strip(found->second).empty() && std::isfinite(*n) && *n >= 0) value = IntIfWhole(*n);
}
config[std::string(key)] = value;
}
config["policy"] = EnvOf(env, "REVIEW_GATES_SEAT_POLICY") == "pack" ? "pack" : "spread";
return config;
}
std::vector<std::pair<std::int32_t, std::string>> SeatTokensFromEnv(const std::map<std::string, std::string>& env) {
std::vector<std::pair<std::int32_t, std::string>> out;
for (std::int32_t i = 1; i <= MaxSeats; ++i) {
std::string token = Dyn::Strip(EnvOf(env, std::format("CLAUDE_CODE_OAUTH_TOKEN{}", i)));
if (token.empty() && i == 1) token = Dyn::Strip(EnvOf(env, "CLAUDE_CODE_OAUTH_TOKEN"));
if (!token.empty()) out.emplace_back(i, token);
}
return out;
}
std::vector<std::pair<std::int32_t, std::string>> ApiKeysFromEnv(const std::map<std::string, std::string>& env) {
std::vector<std::pair<std::int32_t, std::string>> out;
for (std::int32_t i = 1; i <= MaxSeats; ++i) {
const std::string key = Dyn::Strip(EnvOf(env, std::format("ANTHROPIC_API_KEY{}", i)));
if (!key.empty()) out.emplace_back(i, key);
}
return out;
}
std::vector<std::pair<std::int32_t, std::string>> OpenRouterKeysFromEnv(const std::map<std::string, std::string>& env) {
std::vector<std::pair<std::int32_t, std::string>> out;
for (std::int32_t i = 1; i <= MaxSeats; ++i) {
std::string key = Dyn::Strip(EnvOf(env, std::format("OPENROUTER_KEY{}", i)));
if (key.empty() && i == 1) key = Dyn::Strip(EnvOf(env, "OPENROUTER_KEY"));
if (!key.empty()) out.emplace_back(i, key);
}
return out;
}
std::map<std::string, std::string> SeatTokensFromEnvFile(std::string_view text) {
std::map<std::string, std::string> env;
for (const std::string& raw : Dyn::Split(text, "\n")) {
// ^\s*(?:export\s+)?(NAME)\s*=\s*(.*?)\s*$
std::string_view line(raw);
std::size_t at = Dyn::SpaceRun(line, 0);
if (line.substr(at).starts_with("export")) {
const std::size_t gap = Dyn::SpaceRun(line, at + 6);
if (gap > 0) at += 6 + gap;
}
std::string name;
for (const std::string_view stem : {std::string_view("CLAUDE_CODE_OAUTH_TOKEN"), std::string_view("ANTHROPIC_API_KEY"), std::string_view("OPENROUTER_KEY")}) {
if (!line.substr(at).starts_with(stem)) continue;
std::size_t end = at + stem.size();
const bool digit = end < line.size() && line[end] >= '1' && line[end] <= '6';
if (digit) ++end;
if (stem == "ANTHROPIC_API_KEY" && !digit) break;
name = std::string(line.substr(at, end - at));
at = end;
break;
}
if (name.empty()) continue;
at += Dyn::SpaceRun(line, at);
if (at >= line.size() || line[at] != '=') continue;
++at;
at += Dyn::SpaceRun(line, at);
std::string value = Dyn::RStrip(line.substr(at));
if (value.size() >= 2 && (value.front() == '"' || value.front() == '\'') && value.back() == value.front()) value = value.substr(1, value.size() - 2);
env[name] = value;
}
return env;
}
Json Pct(const Json& utilization) {
const std::optional<double> n = FloatOf(utilization);
if (!n || !std::isfinite(*n) || *n < 0) return 0;
return IntIfWhole(JsRound(*n * 1000) / 10);
}
namespace {
std::int64_t IntOf(const Json& value) {
const std::optional<double> n = FloatOf(value);
if (!n || !std::isfinite(*n)) return 0;
return static_cast<std::int64_t>(*n);
}
}
Json ParseRateLimitInfo(const Json& raw) {
if (!raw.is_object()) return nullptr;
Json windows = Json::object();
const Json& unified = Dyn::Get(raw, "unifiedWindows");
if (unified.is_object()) {
for (auto it = unified.begin(); it != unified.end(); ++it) {
if (!it.value().is_object()) continue;
windows[it.key()] = {{"utilization", Pct(Dyn::Get(it.value(), "utilization"))}, {"resetsAt", IntOf(Dyn::Get(it.value(), "resetsAt"))}};
}
}
const std::int64_t resets = IntOf(Dyn::Get(raw, "resetsAt"));
return {
{"status", Dyn::StrOr(Dyn::Get(raw, "status"), "")},
{"rateLimitType", Dyn::Truthy(Dyn::Get(raw, "rateLimitType")) ? Json(Dyn::Str(Dyn::At(raw, "rateLimitType"))) : Json()},
{"resetsAt", resets ? Json(resets) : Json()},
{"windows", std::move(windows)},
// overageDisabledReason, e.g. out_of_credits: a limit is a hard 429,
// not a charge.
{"overageReason", Dyn::Truthy(Dyn::Get(raw, "overageDisabledReason")) ? Json(Dyn::Str(Dyn::At(raw, "overageDisabledReason"))) : Json()},
};
}
Json ParseStream(std::string_view text) {
Json result;
Json rate;
Json session;
if (Dyn::Strip(text).empty()) return {{"result", nullptr}, {"rateLimit", nullptr}, {"sessionId", nullptr}};
for (const std::string& raw : Dyn::Split(text, "\n")) {
const std::string line = Dyn::Strip(raw);
if (line.empty()) continue;
Json o;
try {
o = ParseLoose(line);
} catch (const std::exception&) {
continue;
}
if (!o.is_object()) continue;
const Json& id = Dyn::Get(o, "session_id");
if (!Dyn::Truthy(session) && id.is_string()) {
const std::string& value = id.get_ref<const std::string&>();
const bool shaped = value.size() == 36 && std::ranges::all_of(value, [](char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '-'; });
if (shaped) session = id;
}
if (Dyn::Get(o, "type") == "result") {
result = o;
} else if (Dyn::Get(o, "type") == "rate_limit_event") {
const Json parsed = ParseRateLimitInfo(Dyn::Get(o, "rate_limit_info"));
if (Dyn::Truthy(parsed)) rate = parsed;
}
}
return {{"result", result}, {"rateLimit", rate}, {"sessionId", session}};
}
std::vector<std::string> ProbeArgs(std::string_view model) {
return {"--print", "ok", "--system-prompt", "Reply ok.", "--model", std::string(model), "--max-turns", "1", "--tools", "",
"--strict-mcp-config", "--disable-slash-commands", "--output-format", "stream-json", "--verbose"};
}
std::map<std::string, std::string> ProbeEnv(std::string_view token, const std::string& configDir, const std::map<std::string, std::string>& base) {
const std::string& home = EnvOf(base, "HOME");
return {
{"PATH", EnvOf(base, "PATH")},
{"HOME", home.empty() ? fs::temp_directory_path().string() : home},
{"CLAUDE_CONFIG_DIR", configDir},
{"CLAUDE_CODE_OAUTH_TOKEN", std::string(token)},
{"MAX_THINKING_TOKENS", "0"},
{"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1"},
{"DISABLE_AUTOUPDATER", "1"},
};
}
std::map<std::string, std::string> LocalEnv(const std::map<std::string, std::string>& base) {
std::map<std::string, std::string> env = base;
for (const std::string_view key : {"CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"}) env.erase(std::string(key));
env["MAX_THINKING_TOKENS"] = "0";
env["CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"] = "1";
env["DISABLE_AUTOUPDATER"] = "1";
return env;
}
fs::path WriteApiKeyApproval(const fs::path& configDir, std::string_view key) {
const fs::path path = configDir / ".claude.json";
if (fs::exists(path)) return path;
const std::size_t length = Dyn::Length(key);
const std::string tail = Dyn::Slice(key, static_cast<std::int64_t>(length) - std::min<std::int64_t>(20, static_cast<std::int64_t>(length)));
const Json approval = {{"hasCompletedOnboarding", true}, {"customApiKeyResponses", {{"approved", {tail}}, {"rejected", Json::array()}}}};
// O_EXCL: two passes of a fresh seat write it in the same instant; the
// contents are the same, so the one that loses leaves the other's.
const fs::path temp = std::format("{}.{}.{}.tmp", path.string(), Platform::PublishedPid(), TokenHex(4));
{
std::ofstream out(temp, std::ios::binary);
out << DumpSpaced(approval);
}
fs::permissions(temp, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace);
std::error_code error;
if (!fs::exists(path)) fs::rename(temp, path, error);
fs::remove(temp, error);
return path;
}
std::map<std::string, std::string> ApiEnv(std::string_view key, const std::string& configDir, const std::map<std::string, std::string>& base) {
// Minimal by construction: no CLAUDE_CODE_ENTRYPOINT of a launcher (it
// turns the subscriber credits' answer into "Credit balance is too
// low").
std::map<std::string, std::string> env = ProbeEnv("", configDir, base);
env.erase("CLAUDE_CODE_OAUTH_TOKEN");
env["ANTHROPIC_API_KEY"] = std::string(key);
WriteApiKeyApproval(configDir, key);
return env;
}
bool LooksLikeCreditExhaustion(const Json& result, std::string_view text) {
return CreditText(ResultText(result)) || CreditText(text);
}
bool LooksLikeAuthFailure(const Json& result, std::string_view text) {
const Json& status = Dyn::Get(Dyn::Or(result, Dyn::EmptyDict()), "api_error_status");
if (Dyn::Equal(status, Json(401)) || Dyn::Equal(status, Json(403))) return true;
return Stream::AuthText(ResultText(result)) || Stream::AuthText(text);
}
std::string ClipReason(std::string_view input, std::span<const std::string> secrets) {
std::string s(input);
for (const std::string& secret : secrets) {
if (!secret.empty()) s = Dyn::Replace(s, secret, "[redacted:token]");
}
s = SubAll(s, Jwt, "[redacted:jwt]");
s = SubAll(s, AnthropicKey, "[redacted:token]");
s = SubAll(s, Bearer, "Bearer [redacted]");
s = SubAll(s, Email, "[redacted:email]");
// re.sub(r'\s+', ' ', s).strip()[:160]
std::string collapsed;
for (std::size_t at = 0; at < s.size();) {
const std::size_t gap = Dyn::SpaceRun(s, at);
if (gap > 0) {
collapsed.push_back(' ');
at += gap;
continue;
}
const std::size_t used = Dyn::CodePointAt(s, at).second;
collapsed.append(s, at, used);
at += used;
}
return Dyn::Slice(Dyn::Strip(collapsed), 0, 160);
}
Json ProbeSeat(std::int32_t seat, const std::string& token, const ProbeOptions& options) {
const std::string claude = options.claudeBin.empty() ? FindClaudeBin() : options.claudeBin;
Json base = {{"seat", seat}, {"probedWith", options.model}, {"windows", Json::object()}};
auto with = [&](Json extra) {
Json out = base;
for (auto it = extra.begin(); it != extra.end(); ++it) out[it.key()] = it.value();
return out;
};
const std::vector<std::string> secrets = {token};
std::optional<fs::path> configDir;
if (!options.local) {
for (;;) {
configDir = fs::temp_directory_path() / std::format("rg-seat-{}-{}", seat, TokenHex(4));
if (fs::create_directory(*configDir)) break;
}
fs::permissions(*configDir, fs::perms::owner_all, fs::perm_options::replace);
}
const std::map<std::string, std::string> parent = Platform::Environment();
const std::map<std::string, std::string> env = options.local ? LocalEnv(parent) : (options.api ? ApiEnv(token, configDir->string(), parent) : ProbeEnv(token, configDir->string(), parent));
std::vector<std::string> argv = {claude};
std::ranges::move(ProbeArgs(options.model), std::back_inserter(argv));
if (options.local) {
argv.emplace_back("--setting-sources");
argv.emplace_back("");
argv.emplace_back("--no-session-persistence");
}
auto cleanup = [&] {
std::error_code error;
if (configDir) fs::remove_all(*configDir, error);
};
// A binary that cannot be started is said here, as an OSError, before
// the supervisor would only report it as an exit code.
const std::string path = claude.contains('/') ? claude : Which(claude, EnvOf(env, "PATH")).value_or("");
if (path.empty() || access(path.c_str(), X_OK) != 0) {
cleanup();
return with({{"state", "unknown"}, {"error", ClipReason(std::format("probe spawn failed: {}: {}", Dyn::ErrnoText(2), Dyn::Repr(claude)), secrets)}});
}
Process::Captured run;
try {
run = Process::Run(Process::Supervised(argv), std::nullopt, std::chrono::milliseconds(static_cast<std::int64_t>(options.timeoutS * 1000)), {}, env);
} catch (const std::exception& error) {
cleanup();
return with({{"state", "unknown"}, {"error", ClipReason(std::format("probe spawn failed: {}", error.what()), secrets)}});
}
cleanup();
if (run.timedOut) return with({{"state", "unknown"}, {"error", std::format("probe timeout after {} ms", static_cast<std::int64_t>(options.timeoutS * 1000))}});
const std::string out = Dyn::UniversalNewlines(Dyn::DecodeReplace(run.out));
const std::string err = Dyn::UniversalNewlines(Dyn::DecodeReplace(run.err));
const std::int32_t code = Process::SupervisedReturnCode(run.returnCode);
const Json parsed = ParseStream(out);
const Json& rate = parsed.at("rateLimit");
const Json& result = parsed.at("result");
const Json cost = Dyn::Get(Dyn::Or(result, Dyn::EmptyDict()), "total_cost_usd");
auto said = [&](std::string_view fallback) {
const std::string text = ResultText(result);
return !text.empty() ? text : (!err.empty() ? err : std::string(fallback));
};
if (options.api) {
// No rate windows on a Console key: the probe's result line is the
// whole reading.
if (Dyn::Truthy(result) && !Dyn::Truthy(Dyn::Get(result, "is_error"))) return with({{"state", "ok"}, {"costUsd", cost}});
if (LooksLikeCreditExhaustion(result, err)) return with({{"state", "out"}, {"error", ClipReason(said("credit balance too low"), secrets)}});
if (LooksLikeAuthFailure(result, err)) return with({{"state", "invalid"}, {"error", ClipReason(said("authentication failed"), secrets)}});
return with({{"state", "unknown"}, {"error", ClipReason(std::format("probe failed (exit {}) {}", code, said("")), secrets)}});
}
if (Dyn::Truthy(rate) && Dyn::Truthy(rate.at("windows"))) {
const auto [util, binding] = SeatUtil(rate.at("windows"));
Json resets = Dyn::Get(Dyn::Or(Dyn::Get(rate.at("windows"), "five_hour"), Dyn::EmptyDict()), "resetsAt");
if (!Dyn::Truthy(resets)) resets = Dyn::Get(rate, "resetsAt");
if (rate.at("status") == "rejected") {
// The one-word probe was refused: the seat takes nothing until
// its reset, whatever its windows read.
std::string why = std::format("{} rejected", Dyn::StrOr(Dyn::Get(rate, "rateLimitType"), "window"));
if (Dyn::Truthy(Dyn::Get(rate, "overageReason"))) why += std::format(", overage {}", Dyn::Str(rate.at("overageReason")));
const Json limitedUtil = AsDouble(util) > 100 ? util : Json(100);
return with({{"state", "limited"}, {"windows", rate.at("windows")}, {"util", limitedUtil},
{"binding", Dyn::Truthy(Dyn::Get(rate, "rateLimitType")) ? rate.at("rateLimitType") : Json(binding)}, {"resetsAt", resets}, {"error", why}});
}
return with({{"state", "ok"}, {"windows", rate.at("windows")}, {"util", util}, {"binding", binding}, {"resetsAt", resets}});
}
if (LooksLikeAuthFailure(result, err)) return with({{"state", "invalid"}, {"error", ClipReason(said("authentication failed"), secrets)}});
return with({{"state", "unknown"}, {"error", ClipReason(std::format("no rate_limit_event (exit {}) {}", code, said("")), secrets)}});
}
double Quantize(double util) {
return JsRound(util / RankQuantum) * RankQuantum;
}
std::pair<Json, std::string> BindingWindow(const Json& windows) {
Json util = 0;
std::string binding;
for (auto it = windows.begin(); it != windows.end(); ++it) {
const Json& value = Dyn::At(it.value(), "utilization");
if (Less(util, value) || binding.empty()) {
util = value;
binding = it.key();
}
}
return {util, binding};
}
std::pair<Json, std::string> SeatUtil(const Json& windows) {
const auto [util, binding] = BindingWindow(windows);
return {Json(static_cast<std::int64_t>(Quantize(AsDouble(util)))), binding};
}
std::string ModelFamily(std::string_view model) {
const std::u32string text = Dyn::Decode(model);
for (std::size_t at = 0; at < text.size(); ++at) {
for (const std::string_view family : ModelFamilies) {
if (MatchCi(text, at, family)) return std::string(family);
}
}
return "";
}
bool IsFableWindow(std::string_view key) {
return key == FableWindow || ModelFamily(key) == "fable";
}
const Json* FableWindowOf(const Json& seat) {
const Json& windows = Dyn::Or(Dyn::Get(seat, "windows"), Dyn::EmptyDict());
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (IsFableWindow(it.key())) return &it.value();
}
return nullptr;
}
bool LongWindowsOk(const Json& seat) {
const Json& windows = Dyn::Or(Dyn::Get(seat, "windows"), Dyn::EmptyDict());
for (auto it = windows.begin(); it != windows.end(); ++it) {
if (it.key() == "five_hour" || IsFableWindow(it.key())) continue;
if (AsDouble(Dyn::At(it.value(), "utilization")) >= LongWindowCutoff) return false;
}
return true;
}
bool IsEligible(const Json& seat, const Json& config) {
return Dyn::At(seat, "state") == "ok" && NumberOr0(Dyn::Get(seat, "util")) <= AsDouble(config.at("maxUtil")) && LongWindowsOk(seat);
}
bool FableOpen(const Json& seat, const Json& config) {
const Json& fable = Dyn::Or(Dyn::Get(seat, "fable"), Dyn::EmptyDict());
if (Dyn::Get(fable, "state") == "limited" || (Dyn::At(seat, "state") == "limited" && IsFableWindow(Dyn::StrOr(Dyn::Get(seat, "binding"), "")))) return false;
const Json* w = FableWindowOf(seat);
if (!w) return true;
const double u = AsDouble(Dyn::At(*w, "utilization"));
return u <= AsDouble(config.at("maxUtil")) && u < LongWindowCutoff;
}
Json PickNext(const std::vector<Json>& seats, const Json& config) {
if (seats.empty()) return {{"kind", "stop"}, {"status", "error"}, {"reason", "no seat token configured"}};
std::vector<Json> usable;
for (const Json& s : SortedBySeat(seats)) {
if (Dyn::At(s, "state") != "invalid") usable.push_back(s);
}
if (usable.empty()) {
std::vector<std::string> ids;
for (const Json& s : seats) ids.push_back(Dyn::Str(Dyn::At(s, "seat")));
return {{"kind", "stop"}, {"status", "error"}, {"reason", std::format("no valid seat token (seat{} {} rejected)", seats.size() == 1 ? "" : "s", Dyn::Join(ids, ", "))}};
}
if (std::ranges::all_of(usable, [](const Json& s) { return Dyn::At(s, "state") == "unknown"; })) {
return {{"kind", "seat"}, {"seat", usable[0].at("seat")}, {"source", "round-robin"}};
}
const double maxUtil = AsDouble(config.at("maxUtil"));
const double cost = AsDouble(config.at("lensCostPct"));
auto projected = [&](const Json& s) {
return Dyn::At(s, "state") == "unknown" || Dyn::Get(s, "util").is_null() ? maxUtil : AsDouble(Dyn::At(s, "util"));
};
auto longOk = [&](const Json& s) { return Dyn::At(s, "state") == "unknown" || LongWindowsOk(s); };
std::vector<const Json*> pool;
for (const Json& s : usable) {
if (projected(s) + cost <= maxUtil && longOk(s)) pool.push_back(&s);
}
const Json* chosen = nullptr;
auto least = [&](const std::vector<const Json*>& candidates) {
const Json* best = candidates.front();
for (const Json* s : candidates) {
if (projected(*s) < projected(*best)) best = s;
}
return best;
};
if (!pool.empty()) {
chosen = config.at("policy") == "pack" ? pool.front() : least(pool);
} else {
// Nothing fits under the ceiling: the most headroom among the seats
// whose long windows are open, else any.
std::vector<const Json*> candidates;
for (const Json& s : usable) {
if (longOk(s)) candidates.push_back(&s);
}
if (candidates.empty()) {
for (const Json& s : usable) candidates.push_back(&s);
}
chosen = least(candidates);
}
return {{"kind", "seat"}, {"seat", chosen->at("seat")}, {"source", "probe"}};
}
bool NeedsHaiku(const Json& seat) {
const Json& state = Dyn::At(seat, "state");
return state == "unknown" || state == "invalid" || (state == "limited" && IsFableWindow(Dyn::StrOr(Dyn::Get(seat, "binding"), "")));
}
std::string FableState(const Json& fable) {
if (Dyn::At(fable, "state") == "limited") return IsFableWindow(Dyn::StrOr(Dyn::Get(fable, "binding"), "")) ? "limited" : "ok";
return Dyn::Str(Dyn::At(fable, "state"));
}
Json MergeFableProbe(Json base, const Json& fable) {
if (const Json* w = FableWindowOf(fable)) {
Json windows = Json::object();
const Json& own = Dyn::Or(Dyn::Get(base, "windows"), Dyn::EmptyDict());
for (auto it = own.begin(); it != own.end(); ++it) {
if (!IsFableWindow(it.key())) windows[it.key()] = it.value();
}
windows[std::string(FableWindow)] = *w;
base["windows"] = std::move(windows);
}
if (Dyn::At(base, "state") == "ok") {
// The seat's utilisation is its own windows' — the Fable window
// gates Fable work only (FableOpen): a drained Fable window makes an
// Opus seat, never an idle one.
Json own = Json::object();
for (auto it = base.at("windows").begin(); it != base.at("windows").end(); ++it) {
if (!IsFableWindow(it.key())) own[it.key()] = it.value();
}
const auto [util, binding] = SeatUtil(own);
base["util"] = util;
base["binding"] = binding;
}
base["fable"] = {{"state", FableState(fable)}};
const Json& state = Dyn::At(fable, "state");
if (state != "ok" && state != "limited" && Dyn::Truthy(Dyn::Get(fable, "error"))) base["fable"]["error"] = fable.at("error");
return base;
}
std::string FindClaudeBin(const std::map<std::string, std::string>& env, const std::string& home) {
for (const std::string_view variable : {"CLAUDE_BIN", "REVIEW_GATES_CLAUDE_BIN"}) {
if (const std::string& value = EnvOf(env, std::string(variable)); !value.empty()) return value;
}
if (const std::string& path = EnvOf(env, "PATH"); !path.empty()) {
if (const std::optional<std::string> found = Which("claude", path)) return *found;
}
std::vector<std::string> found;
for (const std::string_view extensions : {".vscode-oss/extensions", ".vscode/extensions", ".vscode-insiders/extensions", ".cursor/extensions"}) {
const fs::path dir = fs::path(home) / extensions;
std::error_code error;
if (!fs::is_directory(dir, error)) continue;
std::vector<std::string> names;
for (const fs::directory_entry& entry : fs::directory_iterator(dir, error)) {
const std::string name = entry.path().filename().string();
if (name.starts_with("anthropic.claude-code-")) names.push_back(name);
}
std::ranges::stable_sort(names, [](const std::string& a, const std::string& b) { return VersionKey(a) > VersionKey(b); });
for (const std::string& name : names) {
const fs::path binary = dir / name / "resources" / "native-binary" / "claude";
if (fs::exists(binary, error)) found.push_back(binary.string());
}
}
for (const fs::path local : {fs::path(home) / ".claude" / "local" / "claude", fs::path(home) / ".local" / "bin" / "claude"}) {
std::error_code error;
if (fs::exists(local, error)) found.push_back(local.string());
}
return found.empty() ? std::string("claude") : found.front();
}
std::string FindClaudeBin() {
return FindClaudeBin(Platform::Environment(), Platform::Home());
}
Zone ZoneNamed(std::string_view name) {
// ZoneInfo(name): a zone the system's zoneinfo holds, else none.
std::error_code error;
const bool known = name == "UTC" || fs::is_regular_file(fs::path("/usr/share/zoneinfo") / name, error);
return {known ? std::string(name) : std::string(), std::string(name)};
}
Zone LocalZone() {
std::string name = Dyn::LStrip(Platform::GetEnv("TZ").value_or(""), ":");
if (name.empty()) {
std::error_code error;
const std::string target = fs::weakly_canonical("/etc/localtime", error).string();
const std::size_t at = target.find("/zoneinfo/");
if (at != std::string::npos) name = target.substr(at + 10);
}
if (!name.empty()) {
Zone zone = ZoneNamed(name);
if (!zone.name.empty()) return zone;
}
return {"", name.empty() ? Platform::LocalTime("%Z") : name};
}
namespace {
// The broken-down time of `t` in the zone: the C library's, with TZ
// set to the zone's name for the call.
std::tm InZone(std::time_t t, const Zone& zone) {
static std::mutex lock;
const std::lock_guard guard(lock);
std::tm out {};
if (zone.name.empty()) {
localtime_r(&t, &out);
return out;
}
const char* before = getenv("TZ");
const std::optional<std::string> saved = before ? std::optional<std::string>(before) : std::nullopt;
setenv("TZ", zone.name.c_str(), 1);
tzset();
localtime_r(&t, &out);
if (saved) {
setenv("TZ", saved->c_str(), 1);
} else {
unsetenv("TZ");
}
tzset();
return out;
}
}
std::string FormatWhen(double epoch, double now, const Zone& zone) {
const auto diffMin = static_cast<std::int64_t>(JsRound((epoch - now) / 60));
std::string relative;
if (diffMin <= 0) {
relative = "now";
} else if (diffMin < 60) {
relative = std::format("in {} min", diffMin);
} else if (diffMin < 48 * 60) {
relative = std::format("in {}h {:02d}m", diffMin / 60, diffMin % 60);
} else {
relative = std::format("in {}d {}h", diffMin / 1440, (diffMin % 1440) / 60);
}
const std::tm d = InZone(static_cast<std::time_t>(std::floor(epoch)), zone);
std::string clock = std::format("{:02d}:{:02d}", d.tm_hour, d.tm_min);
if (epoch - now > 24 * 3600) {
// Monday is 0 here; tm_wday counts from Sunday.
const auto index = static_cast<std::size_t>((d.tm_wday + 6) % 7);
clock = std::format("{} {:02d} {}, {}", Weekdays[index], d.tm_mday, Months[static_cast<std::size_t>(d.tm_mon)], clock);
}
return std::format("{} ({})", clock, relative);
}
std::string RenderTable(const std::vector<std::vector<std::string>>& rows) {
std::vector<std::size_t> widths(rows.at(0).size(), 0);
for (const std::vector<std::string>& row : rows) {
for (std::size_t i = 0; i < widths.size(); ++i) widths[i] = std::max(widths[i], Dyn::Length(row.at(i)));
}
auto line = [&](const std::vector<std::string>& row) {
std::vector<std::string> cells;
for (std::size_t i = 0; i < widths.size(); ++i) cells.push_back(Dyn::LJust(row[i], widths[i]));
return std::format("| {} |", Dyn::Join(cells, " | "));
};
std::vector<std::string> out = {line(rows[0])};
std::vector<std::string> dashes;
for (const std::size_t width : widths) dashes.emplace_back(width + 2, '-');
out.push_back(std::format("|{}|", Dyn::Join(dashes, "|")));
for (std::size_t r = 1; r < rows.size(); ++r) out.push_back(line(rows[r]));
return Dyn::Join(out, "\n");
}
std::string RenderStatusReport(const std::vector<Json>& seats, const Json& config, double now, const Zone& zone, const Json& board,
const std::map<std::int32_t, std::string>& keys) {
const std::vector<Json> ordered = SortedBySeat(seats);
const bool anyInflight = std::ranges::any_of(ordered, [&](const Json& s) { return Inflight(board, keys, s) != 0; });
std::vector<std::vector<std::string>> rows;
std::vector<std::string> header = {"seat", "5h", "resets", "7d", "resets", "fable 7d"};
if (anyInflight) header.emplace_back("in flight");
rows.push_back(header);
for (const Json& s : ordered) {
auto w = [&](std::string_view k) {
if (!Dyn::Has(Dyn::Or(Dyn::Get(s, "windows"), Dyn::EmptyDict()), k)) return std::string("—");
return std::format("{}%", Fmt(Dyn::At(Window(s, k), "utilization")));
};
auto when = [&](std::string_view k) {
const Json& at = Dyn::Get(Dyn::Or(Window(s, k), Dyn::EmptyDict()), "resetsAt");
return Dyn::Truthy(at) ? FormatWhen(AsDouble(at), now, zone) : std::string("—");
};
const Json* fw = FableWindowOf(s);
const Json& f = Dyn::Get(s, "fable");
std::string fable;
if (Dyn::Truthy(f) && Dyn::Get(f, "state") == "limited") {
fable = std::format("limited{}", fw ? std::format(" ({}%)", Fmt(Dyn::At(*fw, "utilization"))) : std::string());
} else if (fw) {
fable = std::format("{}%", Fmt(Dyn::At(*fw, "utilization")));
} else {
fable = Dyn::Truthy(f) ? Dyn::Str(Dyn::At(f, "state")) : std::string("—");
}
std::vector<std::string> row = {SeatCell(s), w("five_hour"), when("five_hour"), w("seven_day"), when("seven_day"), fable};
if (anyInflight) {
const std::int64_t n = Inflight(board, keys, s);
row.push_back(n ? std::to_string(n) : "—");
}
rows.push_back(std::move(row));
}
std::vector<Json> eligible;
for (const Json& s : ordered) {
if (IsEligible(s, config)) eligible.push_back(s);
}
std::ranges::stable_sort(eligible, [](const Json& a, const Json& b) { return NumberOr0(Dyn::Get(a, "util")) < NumberOr0(Dyn::Get(b, "util")); });
const Json pick = PickNext(seats, config);
const std::string next = pick.at("kind") == "seat" ? SeatName({{"seat", pick.at("seat")}}) : std::format("{}: {}", Dyn::Str(pick.at("status")), Dyn::Str(pick.at("reason")));
std::vector<std::string> limited;
std::vector<std::string> unusable;
std::vector<std::string> fableDead;
for (const Json& s : ordered) {
const Json& state = Dyn::At(s, "state");
if (state == "limited") {
const Json& resets = Dyn::Get(s, "resetsAt");
limited.push_back(std::format("{} until {}", SeatName(s), Dyn::Truthy(resets) ? FormatWhen(AsDouble(resets), now, zone) : std::string("?")));
}
if (state != "ok" && state != "limited") {
unusable.push_back(std::format("{} {}{}", SeatName(s), Dyn::Str(state), Dyn::Truthy(Dyn::Get(s, "error")) ? std::format(" ({})", Dyn::Str(s.at("error"))) : std::string()));
}
const Json& fableOfSeat = Dyn::Or(Dyn::Get(s, "fable"), Dyn::EmptyDict());
const Json& fableStateOfSeat = Dyn::Get(fableOfSeat, "state");
if ((state == "ok" || state == "limited") && !fableStateOfSeat.is_null() && fableStateOfSeat != "ok" && fableStateOfSeat != "limited") {
fableDead.push_back(std::format("{} {}{}", SeatName(s), Dyn::Str(fableStateOfSeat),
Dyn::Truthy(Dyn::Get(fableOfSeat, "error")) ? std::format(" ({})", Dyn::Str(fableOfSeat.at("error"))) : std::string()));
}
}
const bool fableSeen = std::ranges::any_of(seats, [](const Json& s) { return FableWindowOf(s) != nullptr; });
std::vector<Json> fableRoom;
for (const Json& s : ordered) {
if (Dyn::At(s, "state") == "ok" && FableWindowOf(s) && FableOpen(s, config)) fableRoom.push_back(s);
}
std::ranges::stable_sort(fableRoom, [](const Json& a, const Json& b) { return Less(Dyn::At(*FableWindowOf(a), "utilization"), Dyn::At(*FableWindowOf(b), "utilization")); });
const double maxUtil = AsDouble(config.at("maxUtil"));
std::vector<std::string> lines = {RenderTable(rows), ""};
lines.push_back(std::format("policy {} · ceiling {}% · handover at {}% · a lens is placed at +{}% · 7d cutoff {}%", Dyn::Str(config.at("policy")), Fmt(config.at("maxUtil")),
Fmt(config.at("handoverPct")), Fmt(config.at("lensCostPct")), LongWindowCutoff));
std::vector<std::string> eligibleText;
for (const Json& s : eligible) eligibleText.push_back(std::format("{} ({}% headroom)", SeatName(s), Fmt(Json(maxUtil - NumberOr0(Dyn::Get(s, "util"))))));
lines.push_back(std::format("eligible now: {} → next lens: {}", eligible.empty() ? std::string("none") : Dyn::Join(eligibleText, ", "), next));
if (fableSeen) {
std::vector<std::string> room;
for (const Json& s : fableRoom) room.push_back(std::format("{} ({}% fable headroom)", SeatName(s), Fmt(Json(maxUtil - AsDouble(Dyn::At(*FableWindowOf(s), "utilization"))))));
lines.push_back(std::format("fable lenses: {}", fableRoom.empty() ? std::string("no seat with room on its Fable window — Fable lenses run on their fallback model") : Dyn::Join(room, ", ")));
}
if (!limited.empty()) lines.push_back(std::format("limited: {}", Dyn::Join(limited, "; ")));
if (!unusable.empty()) lines.push_back(std::format("not usable: {}", Dyn::Join(unusable, "; ")));
if (!fableDead.empty()) lines.push_back(std::format("fable probe: {}", Dyn::Join(fableDead, "; ")));
return Dyn::Join(lines, "\n");
}
std::string RenderApiReport(const std::vector<Json>& apiSeats, const Json& apiConfig, const Json& board, const std::map<std::int32_t, std::string>& keys) {
std::vector<std::vector<std::string>> rows = {{"seat", "state", "driver spend this cycle", "procs", "cycle from", "ledger: out", "in flight"}};
for (const Json& s : SortedBySeat(apiSeats)) {
const Json& ledger = Dyn::Or(Dyn::Get(s, "ledger"), Dyn::EmptyDict());
const double spent = NumberOr0(Dyn::Get(ledger, "spentUsd"));
const std::string state = Dyn::Str(Dyn::At(s, "state")) + (Dyn::Truthy(Dyn::Get(s, "error")) && Dyn::At(s, "state") != "ok" ? std::format(" ({})", Dyn::Str(s.at("error"))) : std::string());
const Json& outSince = Dyn::Get(ledger, "outSince");
const std::int64_t n = Inflight(board, keys, s);
rows.push_back({
std::format("api{}", Dyn::Str(Dyn::At(s, "seat"))), state, std::format("${:.2f} of ${}", spent, Fmt(apiConfig.at("budgetUsd"))), Dyn::StrOr(Dyn::Get(ledger, "procs"), "0"),
Dyn::StrOr(Dyn::Get(ledger, "cycle"), "—"), Dyn::Truthy(outSince) ? std::format("since {}", Dyn::Replace(Dyn::Slice(Dyn::Str(outSince), 0, 16), "T", " ")) : std::string("—"),
n ? std::format("{} of {}", n, Dyn::Str(apiConfig.at("concurrency"))) : std::string("—"),
});
}
return Dyn::Join(std::vector<std::string>{
RenderTable(rows),
std::format("api seats run first (REVIEW_GATES_SEAT_ORDER={}) · budget ${} per seat per cycle · cycle day {} · {} processes per seat · placed at +${} each",
Dyn::Str(apiConfig.at("order")), Fmt(apiConfig.at("budgetUsd")), Dyn::Str(apiConfig.at("cycleDay")), Dyn::Str(apiConfig.at("concurrency")), Fmt(apiConfig.at("lensCostUsd"))),
"the balance itself is in the Console (Settings > Billing > Promotional credits); the ledger counts only what the review drivers spent",
}, "\n");
}
std::string RenderDrivers(const Json& board, double now, const Zone& zone) {
const Json& drivers = Dyn::Or(Dyn::Get(Dyn::Or(board, Dyn::EmptyDict()), "drivers"), Dyn::EmptyDict());
if (!Dyn::Truthy(drivers)) return std::format("drivers: none running (the board at {})", Dyn::StrOr(Dyn::Get(Dyn::Or(board, Dyn::EmptyDict()), "path"), "?"));
std::vector<std::pair<std::string, const Json*>> sorted;
for (auto it = drivers.begin(); it != drivers.end(); ++it) sorted.emplace_back(it.key(), &it.value());
std::ranges::stable_sort(sorted, [](const auto& a, const auto& b) { return NumberOr0(Dyn::Get(*a.second, "since")) < NumberOr0(Dyn::Get(*b.second, "since")); });
std::vector<std::string> parts;
std::int64_t total = 0;
for (const auto& [key, driver] : sorted) {
const Json& d = *driver;
const Json& settings = Dyn::Or(Dyn::Get(d, "settings"), Dyn::EmptyDict());
std::string held;
if (Dyn::Truthy(Dyn::Get(d, "mb"))) held += std::format(", {:.0f} MB", AsDouble(d.at("mb")));
if (Dyn::Truthy(Dyn::Get(d, "stacks"))) {
std::vector<std::string> stacks;
for (const Json& stack : d.at("stacks")) stacks.push_back(Dyn::Str(stack));
std::ranges::sort(stacks);
held += std::format(", stack(s) {}", Dyn::Join(stacks, ", "));
}
const Json unknown = "?";
const Json zero = 0;
parts.push_back(std::format("pid {} ({}, {}, width {}) since {} - {} in flight{}", Dyn::Split(key, ":")[0], Dyn::Str(Dyn::Has(d, "run") ? d.at("run") : unknown),
Dyn::Str(Dyn::Has(d, "effort") ? d.at("effort") : unknown), Dyn::Str(Dyn::Has(settings, "concurrency") ? settings.at("concurrency") : unknown),
FormatWhen(NumberOr0(Dyn::Get(d, "since")), now, zone), Dyn::Str(Dyn::Has(d, "inflight") ? d.at("inflight") : zero), held));
total += static_cast<std::int64_t>(NumberOr0(Dyn::Get(d, "inflight")));
}
return std::format("drivers ({} in flight on this machine; the width is the machine's cap): {}", total, Dyn::Join(parts, "; "));
}
}

View file

@ -0,0 +1,34 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Review:Mcp;
import std;
import :Json;
// The Playwright MCP server a lens with the browser environment drives the
// application with (2026-10-15): one server per pass, launched by the CLI
// from --mcp-config (never from the lens's Bash), headless, an in-memory
// profile, its screenshots under the pass's own output dir in the scratch
// tree. The tools are allowlisted by name (:Tools); browser_run_code_unsafe
// runs arbitrary Node code in the server's process and is denied.
export namespace Crafter::Review::Mcp {
// @playwright/mcp, verified 2026-10-15 with CLI 2.1.300.
constexpr std::string_view PlaywrightMcpVersion = "0.0.83";
// The Chromium build that version's Playwright drives, as `npx playwright
// install chromium` puts it under ~/.cache/ms-playwright: what
// `crafter-review browser install` provides.
constexpr std::string_view ChromiumRevision = "1247";
// --browser chromium: Playwright's own Chromium; without it the server
// wants the `chrome` channel, Google Chrome at /opt/google/chrome
// (2026-10-15: every navigate of the second firing run failed on it).
std::vector<std::string> DefaultArgs();
// The --mcp-config value for one pass: the consumer's command and args (the
// pipeline's pinned default when the config names none) plus this pass's
// output dir.
Json PlaywrightConfig(const Json& functional, const std::string& outputDir);
// The config as compact JSON (DumpCompact), keys in their order.
std::string ConfigJson(const Json& config);
// <scratch>/playwright/<label with / and spaces as _>, made.
std::filesystem::path OutputDir(const std::filesystem::path& scratch, std::string_view label);
}

View file

@ -0,0 +1,88 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Review:Memory;
import std;
import :Json;
// Memory (2026-10-22, Jorijn: "make the concurrency limit from free memory,
// instead of just guessing"): what the machine has free, what a pass costs,
// and the budget the pool admits passes against.
//
// - Free: the kernel's MemAvailable, read at the start of every gate and
// before every launch: the whole machine's, which is right for a budget and
// wrong for a cost.
// - Cost: the peak PSS of a pass's own process tree (claude, and for the
// browser environments the Playwright server and Chromium under it), sampled
// by the driver on its own passes and kept per repository and environment
// (Calibration); a pass is admitted at the highest of its environment's
// recent peaks, or at the measured default before any is recorded.
// - Reserve: what is left for the desktop and the page cache —
// REVIEW_GATES_MEM_RESERVE_MB, by default 10 % of MemTotal, at least 2 GB.
// A policy, not a measurement.
export namespace Crafter::Review::Memory {
// Recent peaks kept per repository and environment.
constexpr std::size_t Keep = 30;
// MemAvailable in MB; nullopt without /proc/meminfo.
std::optional<std::int64_t> Available(const std::filesystem::path& path = "/proc/meminfo");
// REVIEW_GATES_MEM_RESERVE_MB, else 10 % of MemTotal, at least 2048.
// std::invalid_argument for a value that is no number.
std::int64_t Reserve(const std::optional<std::string>& configured, const std::filesystem::path& path = "/proc/meminfo");
std::int64_t Reserve();
// The peak tree PSS of every watched pass, on one thread. A sweep makes one
// ppid map, then reads smaps_rollup for every process of every watched tree
// — the costly read, it walks the page tables. The pause after a sweep is
// `interval` or three times the sweep's own length, whichever is longer, so
// sampling holds at most a quarter of one core whatever the number of
// passes (2026-10-22). A pass shorter than one pause may go unmeasured.
class Sampler {
public:
using Measure = std::function<double(std::int64_t pid, const std::map<std::int64_t, std::vector<std::int64_t>>& kids)>;
using Scan = std::function<std::map<std::int64_t, std::vector<std::int64_t>>()>;
explicit Sampler(double interval = 3.0, Measure measure = {}, Scan scan = {});
~Sampler();
Sampler(const Sampler&) = delete;
Sampler& operator=(const Sampler&) = delete;
void Watch(const std::string& key, std::int64_t pid);
// One sweep over every watched pass; its length in seconds.
double Sample();
// Stop watching; the peak in MB, rounded to 0.1, or nullopt when
// nothing was measured.
std::optional<double> Done(const std::string& key);
static double Pause(double interval, double sweep);
private:
struct State;
std::shared_ptr<State> state;
};
// Recent peaks per repository and environment, in one JSON file next to the
// seat ledger: {repo: {env: [MB]}}. A record, never a dependency
// (2026-10-22): a damaged file reads as empty, a part of the wrong shape as
// absent, and a peak that cannot be written is dropped.
class Calibration {
public:
Calibration(std::filesystem::path path, const std::filesystem::path& repo, std::map<std::string, std::int64_t> defaults);
std::vector<Json> Peaks(std::string_view environment) const;
struct Cost {
// MB, an int or a float as the file holds it.
Json megabytes;
std::string source;
};
Cost CostOf(std::string_view environment) const;
// Keep a pass's peak; whether it was stored. The file is every
// driver's on this machine: the re-read and the write are one section
// under its lock (2026-10-31), so a peak another driver wrote between
// them is kept.
bool Record(std::string_view environment, double megabytes);
private:
std::filesystem::path path;
std::string repo;
std::map<std::string, std::int64_t> defaults;
mutable std::mutex lock;
Json Load() const;
};
}

View file

@ -0,0 +1,124 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Review:Seats;
import std;
import :Json;
// How much usage each Claude seat has left: the dotenv loader, the one-word
// probe, the window maths, the ranking and the report. The seat pool
// (:SeatPool) places on these; `crafter-review seats` prints the report (its
// glue, which needs the ledger and the board, is in :Driver).
//
// Seats are CLAUDE_CODE_OAUTH_TOKEN1..6 by fixed name (the legacy
// CLAUDE_CODE_OAUTH_TOKEN is seat 1 when TOKEN1 is empty), API-credit seats
// ANTHROPIC_API_KEY1..6, OpenRouter seats OPENROUTER_KEY[1..6]; never printed:
// the table and the JSON carry indices and percentages only. A probe result
// and a seat are JSON: the dump and --json show them.
export namespace Crafter::Review::Seats {
constexpr std::int32_t MaxSeats = 6;
constexpr std::string_view ProbeModel = "haiku";
// The Fable weekly limit is a window of its own, reported ONLY in the
// rate_limit_event of a Fable request (verified 2026-09-08). The CLI has
// named it seven_day_overage_included and seven_day_fable; either lands
// under the canonical key.
constexpr std::string_view FableProbeModel = "claude-fable-5-1";
constexpr std::string_view FableWindow = "seven_day_overage_included";
constexpr double ProbeTimeoutS = 30;
// Fixed rules, not knobs: utilisation is rounded to RankQuantum before
// ranking, and a seat whose 7-day window is at or above LongWindowCutoff is
// out regardless — its reset is days away. The Fable window is exempt here.
constexpr std::int32_t RankQuantum = 5;
constexpr std::int32_t LongWindowCutoff = 95;
// JavaScript's Math.round: halves go up, never to even.
double JsRound(double x);
// A number the way a JS template prints it: 5 not 5.0, 62.5 as is.
std::string Fmt(const Json& value);
// {maxUtil, lensCostPct, waitMaxMin, policy, handoverPct}: the placement
// knobs; garbage leaves the default.
Json DefaultConfig();
Json ConfigFromEnv(const std::map<std::string, std::string>& env);
std::vector<std::pair<std::int32_t, std::string>> SeatTokensFromEnv(const std::map<std::string, std::string>& env);
std::vector<std::pair<std::int32_t, std::string>> ApiKeysFromEnv(const std::map<std::string, std::string>& env);
std::vector<std::pair<std::int32_t, std::string>> OpenRouterKeysFromEnv(const std::map<std::string, std::string>& env);
// Only the seat lines of a dotenv file; `export` and quotes stripped,
// everything else ignored.
std::map<std::string, std::string> SeatTokensFromEnvFile(std::string_view text);
// The CLI's fraction as a percent, past 100 for a seat over its limit.
Json Pct(const Json& utilization);
// {status, rateLimitType, resetsAt, windows, overageReason}, or null.
Json ParseRateLimitInfo(const Json& raw);
// The last result line, the last rate_limit_event and the session id.
Json ParseStream(std::string_view text);
std::vector<std::string> ProbeArgs(std::string_view model = ProbeModel);
// The probe child's whole environment: one token under the name the CLI
// reads, an isolated config dir, thinking off, no nonessential traffic, no
// updater, and none of the other pool tokens.
std::map<std::string, std::string> ProbeEnv(std::string_view token, const std::string& configDir, const std::map<std::string, std::string>& base);
// The CLI's own login: no token or API key from the environment.
std::map<std::string, std::string> LocalEnv(const std::map<std::string, std::string>& base);
// The key's approval in the config dir's .claude.json, once.
std::filesystem::path WriteApiKeyApproval(const std::filesystem::path& configDir, std::string_view key);
std::map<std::string, std::string> ApiEnv(std::string_view key, const std::string& configDir, const std::map<std::string, std::string>& base);
bool LooksLikeCreditExhaustion(const Json& result, std::string_view text);
bool LooksLikeAuthFailure(const Json& result, std::string_view text);
// One line, at most 160 characters, the seat's own secrets and anything
// token-shaped redacted.
std::string ClipReason(std::string_view text, std::span<const std::string> secrets = {});
struct ProbeOptions {
std::string model = std::string(ProbeModel);
double timeoutS = ProbeTimeoutS;
std::string claudeBin;
bool local = false;
bool api = false;
};
// One probe of one seat: {seat, probedWith, windows, state: ok | limited |
// unknown | invalid | out, util, binding, resetsAt, error, costUsd}.
Json ProbeSeat(std::int32_t seat, const std::string& token, const ProbeOptions& options);
double Quantize(double util);
// The most-used window and its utilisation, raw.
std::pair<Json, std::string> BindingWindow(const Json& windows);
// Quantised for ranking.
std::pair<Json, std::string> SeatUtil(const Json& windows);
std::string ModelFamily(std::string_view model);
bool IsFableWindow(std::string_view key);
const Json* FableWindowOf(const Json& seat);
bool LongWindowsOk(const Json& seat);
bool IsEligible(const Json& seat, const Json& config);
bool FableOpen(const Json& seat, const Json& config);
Json PickNext(const std::vector<Json>& seats, const Json& config);
bool NeedsHaiku(const Json& seat);
std::string FableState(const Json& fable);
Json MergeFableProbe(Json base, const Json& fable);
// Where the CLI is: CLAUDE_BIN / REVIEW_GATES_CLAUDE_BIN, then PATH, then
// the editor extensions' bundled binary (newest first), then the
// standalone installs. The bare name when nothing is found.
std::string FindClaudeBin(const std::map<std::string, std::string>& env, const std::string& home);
std::string FindClaudeBin();
// The report's zone: $TZ, else /etc/localtime. A zone by name, as
// zoneinfo reads it (this libc++ has no tzdb, so the C library converts,
// with TZ set to the name); an empty name is the process's own zone.
struct Zone {
std::string name;
std::string label;
};
Zone LocalZone();
Zone ZoneNamed(std::string_view name);
// "02:40 (in 1h 50m)"; more than a day out with its date: "Wed 16 Sept,
// 11:00 (in 6d 10h)".
std::string FormatWhen(double epoch, double now, const Zone& zone);
std::string RenderTable(const std::vector<std::vector<std::string>>& rows);
std::string RenderStatusReport(const std::vector<Json>& seats, const Json& config, double now, const Zone& zone, const Json& board = nullptr,
const std::map<std::int32_t, std::string>& keys = {});
std::string RenderApiReport(const std::vector<Json>& apiSeats, const Json& apiConfig, const Json& board = nullptr,
const std::map<std::int32_t, std::string>& keys = {});
std::string RenderDrivers(const Json& board, double now, const Zone& zone);
}

View file

@ -23,5 +23,8 @@ export import :Totp;
export import :Stream; export import :Stream;
export import :Prompt; export import :Prompt;
export import :Config; export import :Config;
export import :Memory;
export import :Mcp;
export import :Seats;
export import :Freeze; export import :Freeze;
export import :Driver; export import :Driver;

View file

@ -13,7 +13,7 @@ using namespace Crafter;
namespace { namespace {
// The umbrella, then the partitions in import order: each after the ones // The umbrella, then the partitions in import order: each after the ones
// it imports, which is also the order the module map in AGENTS.md uses. // it imports, which is also the order the module map in AGENTS.md uses.
constexpr std::array<std::string_view, 23> Interfaces = { constexpr std::array<std::string_view, 26> Interfaces = {
"interfaces/Crafter.Review", "interfaces/Crafter.Review",
"interfaces/Crafter.Review-Unicode", "interfaces/Crafter.Review-Unicode",
"interfaces/Crafter.Review-Json", "interfaces/Crafter.Review-Json",
@ -35,10 +35,13 @@ namespace {
"interfaces/Crafter.Review-Stream", "interfaces/Crafter.Review-Stream",
"interfaces/Crafter.Review-Prompt", "interfaces/Crafter.Review-Prompt",
"interfaces/Crafter.Review-Config", "interfaces/Crafter.Review-Config",
"interfaces/Crafter.Review-Memory",
"interfaces/Crafter.Review-Mcp",
"interfaces/Crafter.Review-Seats",
"interfaces/Crafter.Review-Freeze", "interfaces/Crafter.Review-Freeze",
"interfaces/Crafter.Review-Driver", "interfaces/Crafter.Review-Driver",
}; };
constexpr std::array<std::string_view, 20> Implementations = { constexpr std::array<std::string_view, 23> Implementations = {
"implementations/Crafter.Review-Text", "implementations/Crafter.Review-Text",
"implementations/Crafter.Review-Crypto", "implementations/Crafter.Review-Crypto",
"implementations/Crafter.Review-Platform", "implementations/Crafter.Review-Platform",
@ -57,6 +60,9 @@ namespace {
"implementations/Crafter.Review-Stream", "implementations/Crafter.Review-Stream",
"implementations/Crafter.Review-Prompt", "implementations/Crafter.Review-Prompt",
"implementations/Crafter.Review-Config", "implementations/Crafter.Review-Config",
"implementations/Crafter.Review-Memory",
"implementations/Crafter.Review-Mcp",
"implementations/Crafter.Review-Seats",
"implementations/Crafter.Review-Freeze", "implementations/Crafter.Review-Freeze",
"implementations/Crafter.Review-Driver", "implementations/Crafter.Review-Driver",
}; };
@ -68,7 +74,7 @@ namespace {
// One per directory under tests/, each an ordinary main() that links the // One per directory under tests/, each an ordinary main() that links the
// library: exit 0 passes, nonzero fails, 77 skips. // library: exit 0 passes, nonzero fails, 77 skips.
constexpr std::array<std::string_view, 14> Tests = { constexpr std::array<std::string_view, 15> Tests = {
"Crypto", "Crypto",
"Text", "Text",
"Platform", "Platform",
@ -83,6 +89,7 @@ namespace {
"CodeKeys", "CodeKeys",
"Freeze", "Freeze",
"PgWire", "PgWire",
"Seats",
}; };
// The version the binary reports: CI sets it from the tag, a checkout // The version the binary reports: CI sets it from the tag, a checkout

250
tests/Seats/main.cpp Normal file
View file

@ -0,0 +1,250 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// Seat usage: the pure functions against reference vectors, numbered in
// order, so "check 17" always names the same vector.
import std;
import Crafter.Review;
namespace fs = std::filesystem;
using namespace Crafter::Review;
using namespace Crafter::Review::Seats;
namespace {
std::int32_t Failures = 0;
std::int32_t N = 0;
void Eq(const std::string& got, const std::string& want) {
++N;
if (got != want) {
std::println(std::cerr, "check {}:\n got: {}\nwant: {}", N, Dyn::Repr(got), Dyn::Repr(want));
++Failures;
}
}
void Eq(bool got, bool want) {
Eq(std::string(got ? "True" : "False"), std::string(want ? "True" : "False"));
}
void EqJson(const Json& got, const Json& want) {
++N;
if (!Dyn::Equal(got, want)) {
std::println(std::cerr, "check {}:\n got: {}\nwant: {}", N, DumpCompactSorted(got), DumpCompactSorted(want));
++Failures;
}
}
std::vector<std::string> Lines(const std::string& text) {
return Dyn::Split(text, "\n");
}
Json With(Json value, const Json& changes) {
for (auto it = changes.begin(); it != changes.end(); ++it) value[it.key()] = it.value();
return value;
}
}
int main() {
const Zone utc = ZoneNamed("UTC");
const Zone ams = ZoneNamed("Europe/Amsterdam");
// 2026-09-07 10:00 UTC
constexpr double now = 1788775200.0;
const auto nowS = static_cast<std::int64_t>(now);
const Json cfg = DefaultConfig();
auto seat = [&](std::int32_t k, double five, double seven = 0, const Json& over = Json::object()) {
Json windows = {{"five_hour", {{"utilization", five}, {"resetsAt", nowS + 300 * k}}}, {"seven_day", {{"utilization", seven}, {"resetsAt", nowS + 3 * 86400}}}};
// An integer stays an integer.
for (auto& w : windows) {
const double u = w.at("utilization").get<double>();
if (u == std::floor(u)) w["utilization"] = static_cast<std::int64_t>(u);
}
const auto [util, binding] = SeatUtil(windows);
Json s = {{"seat", k}, {"state", "ok"}, {"windows", windows}, {"util", util}, {"binding", binding}, {"resetsAt", windows.at("five_hour").at("resetsAt")}};
return With(s, over);
};
const std::string fable(FableWindow);
// format_when: the clock in the report zone and a countdown; a reset over
// a day out carries its date.
Eq(FormatWhen(nowS + 45 * 60, now, utc), "10:45 (in 45 min)");
Eq(FormatWhen(nowS + 3 * 3600 + 5 * 60, now, ams), "15:05 (in 3h 05m)");
Eq(FormatWhen(nowS + 3 * 86400, now, utc), "Thu 10 Sept, 10:00 (in 3d 0h)");
Eq(FormatWhen(nowS - 60, now, utc), "09:59 (now)");
// The report: a row per seat, columns padded to line up; who is eligible
// and where the next lens lands.
const Json lim = seat(1, 116, 21, {{"state", "limited"}, {"util", 115}, {"error", "five_hour rejected, overage out_of_credits"}});
Json three = seat(3, 62, 16);
three["windows"][fable] = {{"utilization", 22}, {"resetsAt", 0}};
std::vector<std::string> lines = Lines(RenderStatusReport({lim, seat(2, 61, 18), three}, cfg, now, utc));
Eq(lines.at(0), "| seat | 5h | resets | 7d | resets | fable 7d |");
Eq(lines.at(1), "|------|------|-------------------|-----|-------------------------------|----------|");
Eq(lines.at(2), "| 1 | 116% | 10:05 (in 5 min) | 21% | Thu 10 Sept, 10:00 (in 3d 0h) | — |");
Eq(lines.at(3), "| 2 | 61% | 10:10 (in 10 min) | 18% | Thu 10 Sept, 10:00 (in 3d 0h) | — |");
Eq(lines.at(4), "| 3 | 62% | 10:15 (in 15 min) | 16% | Thu 10 Sept, 10:00 (in 3d 0h) | 22% |");
Eq(lines.at(5), "");
Eq(lines.at(6), "policy spread · ceiling 85% · handover at 90% · a lens is placed at +10% · 7d cutoff 95%");
Eq(lines.at(7), "eligible now: seat 2 (25% headroom), seat 3 (25% headroom) → next lens: seat 2");
Eq(lines.at(8), "fable lenses: seat 3 (63% fable headroom)");
Eq(lines.at(9), "limited: seat 1 until 10:05 (in 5 min)");
// A seat the probe could not read: an empty row, the reason under the table.
const std::string dead = RenderStatusReport({seat(2, 10), {{"seat", 4}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout after 20000 ms"}}}, cfg, now, utc);
Eq(dead.contains("| 4 | — | — | — | — | — |"), true);
// The board: the passes every driver on this machine has in flight on an
// account, a column only when there are any.
const std::vector<std::string> held = Lines(RenderStatusReport({seat(2, 61, 18), three}, cfg, now, utc, {{"seats", {{"k3", {{"inflight", 4}}}}}}, {{3, "k3"}}));
Eq(held.at(0), "| seat | 5h | resets | 7d | resets | fable 7d | in flight |");
Eq(held.at(3), "| 3 | 62% | 10:15 (in 15 min) | 16% | Thu 10 Sept, 10:00 (in 3d 0h) | 22% | 4 |");
Eq(dead.contains("not usable: seat 4 unknown (probe timeout after 20000 ms)"), true);
// A Fable probe refused for the Fable window needs the Haiku stand-in,
// which it then lands on: its window, and 'limited' for that window alone.
Json fableWindows = seat(2, 10).at("windows");
fableWindows[fable] = {{"utilization", 100}, {"resetsAt", nowS + 86400}};
const Json fableLimited = With(seat(2, 10), {{"state", "limited"}, {"util", 100}, {"binding", fable}, {"error", std::format("{} rejected", fable)}, {"windows", fableWindows}});
Eq(NeedsHaiku(fableLimited), true);
const Json merged = MergeFableProbe(seat(2, 10), fableLimited);
EqJson(merged.at("fable"), {{"state", "limited"}});
EqJson(merged.at("windows").at(fable), {{"utilization", 100}, {"resetsAt", nowS + 86400}});
std::string r = RenderStatusReport({merged}, cfg, now, utc);
Eq(Lines(r).at(2).contains("| limited (100%) |"), true);
Eq(r.contains("fable lenses: no seat with room on its Fable window — Fable lenses run on their fallback model"), true);
Eq(r.contains("eligible now: seat 2 (75% headroom)"), true);
// A readable Fable probe is the seat itself.
Json fullWindows = seat(3, 20, 30).at("windows");
fullWindows[fable] = {{"utilization", 44}, {"resetsAt", nowS + 86400}};
const Json full = With(seat(3, 20, 30), {{"windows", fullWindows}});
Eq(NeedsHaiku(full), false);
EqJson(MergeFableProbe(full, full).at("fable"), {{"state", "ok"}});
// A drained Fable window makes an Opus seat, never an idle one.
Json drainedWindows = seat(4, 10, 20).at("windows");
drainedWindows[fable] = {{"utilization", 98}, {"resetsAt", nowS + 86400}};
Json drained = With(seat(4, 10, 20), {{"util", 100}, {"binding", fable}, {"windows", drainedWindows}});
drained = MergeFableProbe(drained, drained);
EqJson(Json::array({drained.at("util"), drained.at("binding"), drained.at("fable")}), Json::array({20, "seven_day", {{"state", "ok"}}}));
r = RenderStatusReport({drained, full}, cfg, now, utc);
Eq(r.contains("eligible now: seat 4 (65% headroom), seat 3 (55% headroom) → next lens: seat 4"), true);
Eq(r.contains("fable lenses: seat 3 (41% fable headroom)"), true);
// Reported under the CLI's other name, it lands under the canonical key.
Json altWindows = seat(5, 10).at("windows");
altWindows["seven_day_fable"] = {{"utilization", 44}, {"resetsAt", nowS + 86400}};
Json alt = With(seat(5, 10), {{"windows", altWindows}});
alt = MergeFableProbe(alt, alt);
EqJson(alt.at("windows").at(fable), {{"utilization", 44}, {"resetsAt", nowS + 86400}});
Eq(alt.at("windows").contains("seven_day_fable"), false);
Eq(RenderStatusReport({alt}, cfg, now, utc).contains("fable lenses: seat 5 (41% fable headroom)"), true);
// A Fable probe refused for the 5-hour window: the seat is limited for
// everything, no stand-in, the Fable window still read.
Json fiveWindows = seat(2, 110).at("windows");
fiveWindows[fable] = {{"utilization", 40}, {"resetsAt", nowS + 86400}};
const Json fiveLimited = With(seat(2, 110), {{"state", "limited"}, {"util", 110}, {"binding", "five_hour"}, {"error", "five_hour rejected"}, {"windows", fiveWindows}});
Eq(NeedsHaiku(fiveLimited), false);
r = RenderStatusReport({MergeFableProbe(fiveLimited, fiveLimited)}, cfg, now, utc);
Eq(Lines(r).at(2).contains("| 110% | 10:10 (in 10 min) | 0%") && Lines(r).at(2).ends_with("| 40% |"), true);
Eq(r.contains("limited: seat 2 until 10:10 (in 10 min)"), true);
Eq(r.contains("fable lenses: seat 2"), false);
Eq(NeedsHaiku({{"seat", 1}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout"}}), true);
Eq(NeedsHaiku(seat(1, 10)), false);
// A Fable probe that could not be read: its state in the cell, the reason
// under the table.
const Json deadFable = MergeFableProbe(seat(2, 10), {{"seat", 2}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout after 20000 ms"}});
r = RenderStatusReport({deadFable}, cfg, now, utc);
Eq(Lines(r).at(2).contains("| unknown |"), true);
Eq(r.contains("fable probe: seat 2 unknown (probe timeout after 20000 ms)"), true);
const Json revoked = {{"seat", 3}, {"state", "invalid"}, {"windows", Json::object()}, {"error", "token revoked"}};
r = RenderStatusReport({MergeFableProbe(revoked, revoked)}, cfg, now, utc);
Eq(r.contains("not usable: seat 3 invalid (token revoked)"), true);
Eq(r.contains("fable probe:"), false);
// The local login is seat 0, named `local`.
r = RenderStatusReport({seat(0, 30), seat(2, 50)}, cfg, now, utc);
Eq(Lines(r).at(2).starts_with("| local | 30%"), true);
Eq(r.contains("eligible now: local (55% headroom), seat 2 (35% headroom) → next lens: local"), true);
// The pick: the pipeline's one-lens wave.
auto unknown = [](std::int32_t k) { return Json{{"seat", k}, {"state", "unknown"}, {"windows", Json::object()}, {"error", "probe timeout"}}; };
auto invalid = [](std::int32_t k) { return Json{{"seat", k}, {"state", "invalid"}, {"windows", Json::object()}, {"error", "authentication failed"}}; };
EqJson(PickNext({}, cfg), {{"kind", "stop"}, {"status", "error"}, {"reason", "no seat token configured"}});
EqJson(PickNext({invalid(1), invalid(2)}, cfg).at("reason"), "no valid seat token (seats 1, 2 rejected)");
EqJson(PickNext({unknown(3), unknown(1)}, cfg), {{"kind", "seat"}, {"seat", 1}, {"source", "round-robin"}});
EqJson(PickNext({seat(1, 5), seat(2, 1), seat(3, 0)}, cfg), {{"kind", "seat"}, {"seat", 2}, {"source", "probe"}});
EqJson(PickNext({seat(1, 5), seat(2, 1), seat(3, 0)}, With(cfg, {{"policy", "pack"}})).at("seat"), 1);
EqJson(PickNext({seat(1, 90, 10), seat(2, 80, 96), seat(3, 86)}, cfg).at("seat"), 3);
EqJson(PickNext({seat(1, 50), unknown(2)}, cfg).at("seat"), 1);
// Tokens: TOKEN1..6 by fixed name, the legacy name as seat 1, a dotenv
// file's token lines only.
const std::string text = Dyn::Join(std::vector<std::string>{"MAILOSAUR_API_KEY=abc def", "export CLAUDE_CODE_OAUTH_TOKEN1=\"one\"", "CLAUDE_CODE_OAUTH_TOKEN2='two'",
"CLAUDE_CODE_OAUTH_TOKEN3 = three ", "CLAUDE_CODE_OAUTH_TOKEN7=seven", "CLAUDE_CODE_OAUTH_TOKEN=legacy",
"# CLAUDE_CODE_OAUTH_TOKEN4=commented"}, "\n");
const std::map<std::string, std::string> parsed = SeatTokensFromEnvFile(text);
const std::map<std::string, std::string> wantParsed = {{"CLAUDE_CODE_OAUTH_TOKEN1", "one"}, {"CLAUDE_CODE_OAUTH_TOKEN2", "two"}, {"CLAUDE_CODE_OAUTH_TOKEN3", "three"}, {"CLAUDE_CODE_OAUTH_TOKEN", "legacy"}};
Eq(parsed == wantParsed, true);
std::vector<std::int32_t> ids;
for (const auto& [n, token] : SeatTokensFromEnv(parsed)) ids.push_back(n);
Eq(ids == std::vector<std::int32_t>{1, 2, 3}, true);
using Tokens = std::vector<std::pair<std::int32_t, std::string>>;
Eq(SeatTokensFromEnv({{"CLAUDE_CODE_OAUTH_TOKEN1", "a"}, {"CLAUDE_CODE_OAUTH_TOKEN2", " "}, {"CLAUDE_CODE_OAUTH_TOKEN3", "c"}, {"CLAUDE_CODE_OAUTH_TOKEN7", "x"},
{"CLAUDE_CODE_OAUTH_TOKENX", "y"}, {"CLAUDE_CODE_OAUTH_TOKEN", "legacy"}}) == Tokens{{1, "a"}, {3, "c"}}, true);
Eq(SeatTokensFromEnv({{"CLAUDE_CODE_OAUTH_TOKEN", "legacy"}, {"CLAUDE_CODE_OAUTH_TOKEN2", "b"}}) == Tokens{{1, "legacy"}, {2, "b"}}, true);
Eq(SeatTokensFromEnv({}).empty(), true);
// Config: repo variables override the defaults, garbage does not.
EqJson(ConfigFromEnv({}), {{"maxUtil", 85}, {"lensCostPct", 10}, {"waitMaxMin", 15}, {"policy", "spread"}, {"handoverPct", 90}});
EqJson(ConfigFromEnv({{"REVIEW_GATES_SEAT_MAX_UTIL", "70"}, {"REVIEW_GATES_LENS_COST_PCT", "abc"}, {"REVIEW_GATES_SEAT_WAIT_MIN", "0"}, {"REVIEW_GATES_SEAT_HANDOVER_PCT", "80"},
{"REVIEW_GATES_SEAT_POLICY", "pack"}}),
{{"maxUtil", 70}, {"lensCostPct", 10}, {"waitMaxMin", 0}, {"policy", "pack"}, {"handoverPct", 80}});
EqJson(ConfigFromEnv({{"REVIEW_GATES_SEAT_POLICY", "weird"}}).at("policy"), "spread");
// The stream: captured from claude 2.1.263, values shortened.
const std::string init = R"({"type":"system","subtype":"init","model":"claude-haiku-4-5-20251001","claude_code_version":"2.1.263"})";
const std::string event = R"({"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1788789600,"rateLimitType":"five_hour",)"
R"("overageStatus":"rejected","overageDisabledReason":"out_of_credits","isUsingOverage":false,"unifiedWindows":{)"
R"("five_hour":{"utilization":0.05,"resetsAt":1788789600},"seven_day":{"utilization":0.01,"resetsAt":1789038000}}},)"
R"("uuid":"u","session_id":"400f3eb9-4732-42f1-a257-b84d1da95164"})";
const std::string assistant = R"({"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"ok"}]}})";
const std::string result = R"({"type":"result","subtype":"success","is_error":false,"structured_output":{"ok":true},"result":"ok","modelUsage":{"claude-haiku-4-5-20251001":{"inputTokens":134}}})";
for (const std::vector<std::string>& order : {std::vector<std::string>{init, event, assistant, result}, std::vector<std::string>{init, assistant, event, result},
std::vector<std::string>{init, assistant, result, event}}) {
const Json p = ParseStream(std::format("{}\n", Dyn::Join(order, "\n")));
EqJson(p.at("result").at("structured_output"), {{"ok", true}});
EqJson(p.at("sessionId"), "400f3eb9-4732-42f1-a257-b84d1da95164");
const Json& rl = p.at("rateLimit");
EqJson(Json::array({rl.at("status"), rl.at("rateLimitType"), rl.at("resetsAt"), rl.at("overageReason")}), Json::array({"allowed", "five_hour", 1788789600, "out_of_credits"}));
EqJson(rl.at("windows").at("five_hour"), {{"utilization", 5}, {"resetsAt", 1788789600}});
EqJson(rl.at("windows").at("seven_day"), {{"utilization", 1}, {"resetsAt", 1789038000}});
}
EqJson(ParseStream(Dyn::Join(std::vector<std::string>{"garbage", event, "more garbage", result}, "\n")).at("result").at("structured_output"), {{"ok", true}});
EqJson(ParseStream("{\n \"type\": \"result\",\n \"subtype\": \"success\"\n}").at("result"), nullptr);
EqJson(ParseStream("").at("rateLimit"), nullptr);
EqJson(Json::array({Pct(1.16), Pct(0.05), Pct(0.625), Pct("x"), Pct(-1)}), Json::array({116, 5, 62.5, 0, 0}));
auto pair = [](const std::pair<Json, std::string>& p) { return Json::array({p.first, p.second}); };
EqJson(pair(SeatUtil({{"five_hour", {{"utilization", 61}, {"resetsAt", 1}}}, {"seven_day", {{"utilization", 18}, {"resetsAt", 2}}}})), Json::array({60, "five_hour"}));
EqJson(pair(SeatUtil({{"five_hour", {{"utilization", 12}, {"resetsAt", 1}}}, {"seven_day_fable", {{"utilization", 97}, {"resetsAt", 2}}}})), Json::array({95, "seven_day_fable"}));
Eq(IsEligible(seat(1, 10, 96), With(cfg, {{"maxUtil", 100}})), false);
Eq(IsEligible(seat(1, 10, 94), With(cfg, {{"maxUtil", 100}})), true);
Eq(IsEligible(seat(1, 10, 94), cfg), false);
// Redaction: the seat's own token, token shapes and addresses; auth
// failures by status or by text.
const std::vector<std::string> secret = {"lit-secret"};
Eq(ClipReason("token sk-ant-oat01-abcdefghijklmnopqrstuvwxyz0123 and jorijn@example.com x\n y", secret), "token [redacted:token] and [redacted:email] x y");
Eq(ClipReason("bad lit-secret here", secret), "bad [redacted:token] here");
Eq(LooksLikeAuthFailure({{"api_error_status", 401}}, ""), true);
Eq(LooksLikeAuthFailure(nullptr, "Invalid API key · Please run /login"), true);
Eq(LooksLikeAuthFailure({{"result", "ok"}}, ""), false);
// find_claude_bin: CLAUDE_BIN, then PATH, then the newest editor-bundled
// binary (numerically), else the bare name.
const fs::path home = fs::temp_directory_path() / std::format("seats-home-{}", Platform::PublishedPid());
fs::remove_all(home);
fs::create_directories(home);
Eq(FindClaudeBin({{"CLAUDE_BIN", "/x/claude"}, {"PATH", ""}}, home.string()), "/x/claude");
const fs::path binDir = home / "bin";
fs::create_directories(binDir);
const fs::path fake = binDir / "claude";
std::ofstream(fake).close();
fs::permissions(fake, fs::perms::owner_all | fs::perms::group_read | fs::perms::group_exec | fs::perms::others_read | fs::perms::others_exec);
Eq(FindClaudeBin({{"PATH", std::format("/nowhere:{}", binDir.string())}}, home.string()), fake.string());
for (const std::string_view v : {"2.1.100-linux-x64", "2.1.99-linux-x64", "2.1.263-linux-x64"}) {
const fs::path d = home / ".vscode-oss/extensions" / std::format("anthropic.claude-code-{}", v) / "resources/native-binary";
fs::create_directories(d);
std::ofstream(d / "claude").close();
}
Eq(FindClaudeBin({{"PATH", "/nowhere"}}, home.string()), (home / ".vscode-oss/extensions/anthropic.claude-code-2.1.263-linux-x64/resources/native-binary/claude").string());
Eq(FindClaudeBin({{"PATH", "/nowhere"}}, (home / "empty").string()), "claude");
fs::remove_all(home);
std::println("seats: {} checks{}", N, Failures ? std::format(", {} failed", Failures) : std::string(" ok"));
return Failures ? 1 : 0;
}