diff --git a/README.md b/README.md index 6f6b70b..485e365 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,12 @@ Press `enter` and `code` launches oh-my-pi with that setup, as a one-shot overlay — your omp config is never modified. It's made for people who run oh-my-pi with **both Anthropic and OpenAI**: -the whole point is deciding, per task, how to blend the two pools and which -quota to spend. With a single provider you can still launch, but the dials -lose most of their meaning. +the whole point is deciding, per task, how to blend the pools and which +quota to spend. A DeepSeek API key adds a third, pay-as-you-go pool — its +own `ds` lanes, a live balance readout in Usage, and a relief tail at the +end of the heavyweight fallback chains for when the metered windows are +drained. With a single provider you can still launch, but the dials lose +most of their meaning. ## Usage @@ -62,9 +65,12 @@ are not orphaned onto init while still holding their memory. ## Features -- **Dials, not config files** — provider lane, model tier, thinking depth, - advisor level, plus the spark/fable toggles; every combination maps to a - pre-computed routing. +- **Dials, not config files** — a provider **lead** dial with a led/only + blend child (scales past two pools without overflowing), notched sliders + for model tier and thinking depth, advisor level, plus the spark/fable + toggles; every combination maps to a pre-computed routing. Optional pools + plug in as their own lanes, and a **relief** dial decides whether drained + metered chains may spill into the pay-as-you-go pool. - **Hosted or local** — an optional runtime broker can advertise only the local targets this machine supports; selecting one delegates first-use setup and launch without mixing cloud credentials into the session. @@ -75,10 +81,14 @@ are not orphaned onto init while still holding their memory. - **Prompt → profile** — `ctrl+o`, describe the task, a small local model rates its difficulty and sets the dials (optional, needs [ollama](https://ollama.com); the prompt is forwarded into the session). + Suggestions are quota-aware: a lane whose lead pool is maxed falls to a + sibling with headroom, and a low DeepSeek balance stops proposals from + spending it. - **Usage at a glance** — quota bars and reset countdowns per provider, before you spend the scarce bucket. - **Account presets** — choose broker accounts and save reusable selections (`v`). -- **Cost & speed meters** — every dial change reprices the session. +- **Cost & speed meters** — every dial change reprices the session; DeepSeek + rungs are priced by the clock during its off-peak discount window. - **Guided first run** — no catalog? `code` builds one from your omp, interactively; `code generate` scripts the same thing. - **Argument passthrough** — `code ` just works. diff --git a/colorize.go b/colorize.go new file mode 100644 index 0000000..d00b6d5 --- /dev/null +++ b/colorize.go @@ -0,0 +1,134 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/lipgloss" +) + +// ── colourisers ────────────────────────────────────────────────────────────── +func lvl(s string) int { + switch s { + case "minimal": + return 0 + case "low": + return 1 + case "medium": + return 2 + case "high": + return 3 + case "xhigh": + return 4 + } + return 5 +} + +func shortModel(name string) string { + if name == "gpt-5.4" { + return name + } + // Slash-scoped ids display without their provider path, and keep their + // full model part — the vendor's own naming is the recognizable bit. + if i := strings.LastIndexByte(name, '/'); i >= 0 { + name = name[i+1:] + if !strings.HasPrefix(name, "claude") { + return name + } + } + p := strings.Split(name, "-") + if strings.HasPrefix(name, "claude") && len(p) > 1 { + return p[1] + } + return p[len(p)-1] +} + +func clampByte(x float64) int { + v := int(x) + if v > 255 { + return 255 + } + if v < 0 { + return 0 + } + return v +} + +func paintModel(tok string) string { + i := strings.LastIndex(tok, ":") + name, level := tok[:i], tok[i+1:] + p := providerByModel(name) + var br, bg, bb float64 + switch { + case p != nil: + br, bg, bb = p.PaintRGB[0], p.PaintRGB[1], p.PaintRGB[2] + case strings.Contains(name, "local-"): + // Free/local runtimes read green — the same family as the ox accents. + br, bg, bb = 96, 211, 150 + default: + return shortModel(name) + ":" + level // unknown provider: uncoloured + } + f := 0.60 + float64(lvl(level))*0.088 + col := lipgloss.Color(fmt.Sprintf("#%02x%02x%02x", clampByte(br*f), clampByte(bg*f), clampByte(bb*f))) + return lipgloss.NewStyle().Foreground(col).Render(shortModel(name) + ":" + level) +} + +func colorizeRoute(line string) string { return modelRe.ReplaceAllStringFunc(line, paintModel) } + +// bucketOf guesses a quota bucket from a model name. It is the fallback for +// catalogs that declare no bucket column, and the only resolver for the bare +// facet names ("fable", "spark") the suggest box asks about — prefer +// model.bucketFor wherever a receiver is in reach. An unknown name maps to no +// bucket at all rather than someone else's quota window. +func bucketOf(model string) string { + m := model + if i := strings.IndexByte(m, ':'); i >= 0 { + m = m[:i] + } + // Provider-scoped ids outside the subscription pools (OpenRouter, + // local runtimes) have no quota window code knows about. An empty bucket + // never reads as down, which is exactly right for a free or local model. + if strings.Contains(m, "/") { + return "" + } + for _, p := range providerRegistry { + for _, s := range p.Special { + if strings.Contains(m, s.Bucket) { + return p.BucketBase + "-" + s.Bucket + } + } + } + if p := providerByModel(m); p != nil { + return p.mainBucket() + } + for _, p := range providerRegistry { + for _, pre := range p.ModelPrefixes { + if strings.Contains(m, pre) { + return p.mainBucket() + } + } + } + return "" +} + +// bucketFor resolves a routing token's quota bucket from the catalog, falling +// back to the name guess only when the catalog declares none. The catalog wins +// because names are not a taxonomy: claude-mythos-5 sits in omp's catalog at +// claude-fable-5's price yet 404s on this account, and every model omp adds +// would otherwise need one more substring arm here before it could be struck +// through correctly. +func (m model) bucketFor(name string) string { + id := name + if i := strings.IndexByte(id, ':'); i >= 0 { + id = id[:i] + } + if f, ok := m.facts[id]; ok { + if f.bucket != "" { + return f.bucket + } + if p := providerByPool(f.pool); p != nil { + return p.mainBucket() + } + } + return bucketOf(id) +} diff --git a/docs/configuration.md b/docs/configuration.md index 14ba4b2..1685ad6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,163 +1 @@ -# Configuration - -`code` needs no config file. Everything is a key inside the TUI or an -environment variable with a sane fallback. - -## Keys - -| Key | Action | -|---|---| -| `↑` `↓` | move between dials | -| `←` `→` | change the selected dial | -| `d` | reset all dials to defaults | -| `ctrl+o` | describe the task, let a local model set the dials | -| `enter` | launch oh-my-pi with the selected hosted profile or local runtime | -| `m` | launch plain managed omp (no overlay) | -| `u` | launch through a sandboxed omp, if you have one | -| `v` | manage broker account selections and presets | -| `p` / `f` / `s` | toggle routing panel / fallback chains / usage panel | -| `r` | refresh the usage panel now | -| `?` | expanded help | -| `pgup` / `pgdn` | scroll the routing preview | -| `q` | quit | - -`↑↓←→` also answer to their vim aliases (`j`/`k`/`h`/`l`). - -## Environment variables - -| Variable | Purpose | Without it | -|---|---|---| -| `CODE_GENERATED` | path to the generated facet catalog (the routing blocks behind the dials) | `$XDG_DATA_HOME/code/generated.plain`, where `code generate` writes; if that's missing too, the TUI opens the guided first-run that builds it | -| `CODE_SELECTION_STATE` | file persisting your dial choices | choices reset each run | -| `CODE_SESSION_STATE` | directory recording live sessions for `code ls` / `code session reap`; `off` disables recording | `$XDG_STATE_HOME/code/sessions` — note this one defaults to a path rather than to disabled, so the registry works without wrapper changes | -| `CODE_OMP` | omp binary for trusted launches (`m` and `enter`) | `omp-managed`, then `omp` on PATH | -| `CODE_OMP_UNTRUSTED` | sandboxed omp for the `u` key | `ompu` on PATH, else the key is hidden and inert | -| `CODE_RUNTIME_BROKER` | executable implementing `runtime list --json` and `runtime run TARGET -- ...`; applicable targets become a runtime dial | no runtime dial; hosted behavior is unchanged | -| `OMP_AUTH_BROKER_URL` | central auth broker behind the usage panel and the account picker (`v`); inherited from your omp environment | no fetch — the usage panel has nothing to show | -| `OMP_AUTH_BROKER_TOKEN` | bearer token for that broker | same: `code` only fetches when both the URL and the token are set | -| `OMP_AUTH_BROKER_SNAPSHOT_CACHE` | broker snapshot cache path; `code` never reads it, it only forwards it to the omp it launches | forwarded empty | -| `CODE_AUTH_VAULTS` | legacy vault manifest (inline JSON), consulted only when no `OMP_AUTH_BROKER_*` variable is set | the broker variables are the only source | -| `CODE_AUTH_VAULTS_FILE` | the same legacy manifest read from a file, when `CODE_AUTH_VAULTS` is empty | ditto | -| `CODE_AUTH_ACCOUNT_STATE` | file persisting your broker account selections and presets (`v`) | selections reset each run | -| `CODE_USAGE_CACHE` | file caching the last usage snapshot, so the panel opens on last-known numbers (marked stale) instead of blank | the panel starts empty and fills on the first fetch | -| `CODE_EVAL_MODEL` | ollama model tag for `ctrl+o` | `qwen2.5:3b` | -| `CODE_OLLAMA_ENDPOINT` | non-default ollama endpoint | `http://127.0.0.1:11434` | -| `CODE_FACET_GLYPHS` | override the Nerd Font dial glyphs | built-in glyphs | - -`CODE_USAGE` and `CODE_OMP_RAW` are no longer read; the dotfiles wrapper still -exports them for older pinned builds. The usage panel now comes from the auth -broker (`OMP_AUTH_BROKER_URL` / `OMP_AUTH_BROKER_TOKEN`). - -Provider authentication is owned by OMP, not `code`. Authenticate with -`omp auth-broker login` before launching `code`. - -The runtime-broker boundary is deliberately narrow. `code` reads only -schema-version-1 targets marked `applicable`, then delegates the selected -target's complete lifecycle to the broker. It does not download weights, -create credentials, or assume a container engine. Local launches receive the -thinking dial and forwarded OMP arguments, but not cloud auth-broker variables; -the runtime broker owns its OMP profile, routing config, and fallback policy. - -## The `code generate` subcommand - -The dials are backed by a pre-rendered catalog. Building it is two steps, both -scriptable: - -``` -code generate init [--models-file OUT] [--refresh] [--from-json FILE] -code generate [--models-file FILE] [--out FILE|-] -``` - -`init` scaffolds a models file from your own omp (`omp models --json`), keeping -the newest model per family and ranking it by thinking ceiling, context and -price — review what it derived. It also reads `omp usage --json`: a quota bucket -scoped to a model tier is how the spark and elite rungs are identified, and -without that report they are simply left empty. `generate` renders that file -into the catalog the TUI reads. Paths default to -`$XDG_CONFIG_HOME/code/models.yml` and `$XDG_DATA_HOME/code/generated.plain` -(`~/.config` and `~/.local/share` when those are unset); `--out -` prints the -catalog to stdout. - -Every candidate is probed with `omp bench` before it can become a rung. This is -not optional and not a benchmark: omp lists models your account cannot actually -call, and no field distinguishes them — `claude-mythos-5` reports -`claude-fable-5`'s exact price, context window and thinking range, and 404s. -A model that does not return a passing probe is dropped, and a model missing -from the probe report entirely is dropped too, because unverified is not the -same as fine. One request per model, so expect `init` to take a minute. The -probe also supplies the real `speed`/`ttft` the meter reads. - -A file whose models were all verified is marked `probed: true`, and `generate` -refuses to render one that is not — that marker is the only thing standing -between an unverified scaffold and live routing. Treat it as your attestation -rather than a permanent certificate: it describes the ids as they were written, -so if you edit an `id` by hand, re-run `init --refresh` (or satisfy yourself the -new one is callable) instead of leaving the old `true` in place. - -| Flag | Effect | -|---|---| -| `--refresh` | re-derive the tiers over an existing models file instead of refusing to touch it. Without it `init` stops when the file already exists, so a scaffold from months ago keeps naming retired models. This is the line to run when a provider ships new models | -| `--from-json` | read the model list from a file instead of omp, and skip the probe. Offline inspection only: the output is marked `probed: false`, which `generate` rejects | - -### The models file - -Two top-level keys: `probed`, and `models:` mapping a short key to one model. - -| Top-level field | Meaning | -|---|---| -| `probed` | must be `true` or `generate` refuses the file. `init` sets it after every model passed a live probe; an offline `--from-json` scaffold writes `false` | - -Each entry under `models:`: - -| Field | Meaning | -|---|---| -| `id` | the model id omp routes to | -| `pool` | `O` (OpenAI/Codex), `A` (Anthropic), or `R` (OpenRouter — optional, see below) | -| `tier` | `1` cheap · `2` regular · `3` smart — the per-pool fallback ladder. `0` (a fast idle-bucket model the `spark` toggle drains) and `4` (a scarce elite the `fable` toggle leads with) are optional | -| `bucket` | the quota window this model draws from (`claude-main`, `claude-fable`, `codex-main`, `codex-spark`). The TUI prefers it over guessing from the model family | -| `cost_in` / `cost_out` | dollars per 1M tokens; drives the cost meter | -| `speed` / `ttft` | output tok/s and seconds to first token; drives the speed meter. Measured by `init`'s probe — a single timed request each, so treat them as one sample rather than a stable benchmark | -| `context` | context window, in tokens | -| `thinking` | the levels the model really offers (see below) | -| `image` | omitted for image-capable models, which is most of them. `init` writes `image: false` only for a model omp reports as text-only, and the `vision` role then avoids it | - -The `vision` lead follows the model dial: `fast`, `normal`, and `smart` select -tiers 1, 2, and 3 respectively. Mixed routing keeps GPT for fast and normal, -then prefers Claude's tier-3 model for smart, with the GPT tier-3 model in its -fallback chain. Any text-only rung is skipped. - -### Pool R and the ox lanes - -Pool `R` is optional, and its presence is its own switch: with no `R` models -the generator serves only the five base lanes; with a full ladder it also -serves three ox lanes — `ox-only` (every role on the free pool), -`ox-led` (the free pool leads everything high-volume while plan/slow/ -designer/reviewer cross to Anthropic, and `fable` may still lead those), and -`ox-lean` (the mirror: paid providers answer for default/task/librarian while -the free pool absorbs scout/sonic/smol/tiny/commit and vision; `fable` and -fable-as-main stay available, so an elite can take the default seat). A -half-declared R ladder is refused. A one-model family — Ox Alpha is exactly -that — declares the same id once per tier with ascending thinking ceilings; -the tier dial then means thinking depth. `code generate init` never scaffolds -pool R: curate those entries by hand and re-confirm `probed: true` yourself. - -The thinking scale is `minimal · low · medium · high · xhigh · max`. Write -`low→max` ONLY for a genuinely contiguous run — a range claims every level in -between, and requesting one the model doesn't offer sends a level the API may -reject. A model that skips levels must be written as a comma list: -claude-opus-4-6 offers `low,medium,high,max` but not `xhigh`; Ox Alpha offers -only `low,high,max`, so its rungs declare `low→low`, `low,high`, and -`low,high,max`. A single-level model writes `low→low`. - -## The `ctrl+o` classifier - -Any ollama daemon on loopback works: - -``` -ollama pull qwen2.5:3b -``` - -The model is loaded into memory only when you choose (`ctrl+l` inside the -box toggles residency); a one-off suggestion never leaves weights resident. -Small instruct models around 3B parameters work best — smaller ones rate -every task the same. +| `pool` | `O` (OpenAI/Codex), `A` (Anthropic), `D` (DeepSeek), or `R` (OpenRouter — optional, see below). `O` and `A` must fill tiers 1..3; `D` is optional — one verified model is enough, missing tiers borrow the nearest rung; `R` is optional but all-or-nothing — when present it must fill tiers 1..3 | \ No newline at end of file diff --git a/facets.go b/facets.go new file mode 100644 index 0000000..bb135d2 --- /dev/null +++ b/facets.go @@ -0,0 +1,140 @@ +package main + +import ( + "strconv" + "strings" +) + +// ── generator facets ───────────────────────────────────────────────────────── +type facet struct { + key string + values []string + glyph string +} + +// facetDefs seeds the facet dials. The lane facet starts empty: its values are +// catalog-driven (applyCatalog collects the lanes the catalog actually +// generated), so a two-pool catalog shows exactly the classic five lanes and a +// richer one adds its own. +func facetDefs(glyphs map[string]string) []facet { + return []facet{ + // Seeded with the required pools' lanes so a catalog-less run (the + // onboarding shell, a broken CODE_GENERATED) keeps a working dial; + // applyCatalog replaces the list with the lanes the catalog generated. + {"lane", requiredPoolLanes(), glyphs["lane"]}, + {"model", []string{"fast", "normal", "smart"}, glyphs["model"]}, + {"thinking", []string{"minimal", "low", "medium", "high", "xhigh", "max"}, glyphs["thinking"]}, + // advisor as a power/cost dial: a quick glance, a proper review, or a + // deep (expensive) audit — off spends nothing. + {"advisor", []string{"off", "glance", "review", "audit"}, glyphs["advisor"]}, + {"fast", []string{"on", "off"}, glyphs["fast"]}, + {"spark", []string{"on", "off"}, glyphs["spark"]}, + {"fable", []string{"on", "off"}, glyphs["fable"]}, + // fable-as-main: hand the scarce elite the default (main-agent) role too. + // A sub-setting of fable — only visible while fable is on (see + // visibleFacets) and never set by a suggestion (see validFacetActions). + {"main", []string{"on", "off"}, glyphs["main"]}, + // relief: whether metered-led chains may spill into a pay-as-you-go + // pool's tail rung. Only rendered when the catalog carries an + // optional pool and the lane is a metered-led blend. + {"relief", []string{"on", "off"}, glyphs["relief"]}, + } +} + +// parseAdvisors reads the __advisors__ block (rows: " ") +// into a map keyed "level/ctx" — the advisor model table, sourced from +// generate-profiles.py so the catalog stays a single source of truth. +func parseAdvisors(rows []string) map[string][]string { + out := map[string][]string{} + for _, r := range rows { + f := strings.Fields(strings.ReplaceAll(r, "→", " ")) + if len(f) < 3 { + continue + } + var chain []string + for _, t := range f[2:] { + if modelRe.MatchString(t) { + chain = append(chain, t) + } + } + if len(chain) > 0 { + out[f[0]+"/"+f[1]] = chain + } + } + return out +} + +// modelFact is a model's measured facts from omp (via the catalog): pricing +// ($/1M tokens), output throughput (tok/s), time-to-first-token (seconds), the +// quota bucket it draws from ("" when the catalog declares none), and the pool +// it belongs to ("" in catalogs that predate the column — the provider-prefix +// heuristic covers those). +type modelFact struct { + in, out, speed, ttft float64 + bucket string + pool string // catalog pool letter ("" in legacy catalogs — the family guess covers those) +} + +// effTPS folds ttft into throughput — the effective tok/s for a representative +// reply of effTokens: total time = ttft (startup) + tokens/speed (streaming), so +// a blazing-but-slow-to-start model (spark: 287 t/s, 5.6s ttft) reads honestly. +const effTokens = 300.0 + +func (f modelFact) effTPS() float64 { + if f.speed <= 0 { + return 0 + } + return effTokens / (f.ttft + effTokens/f.speed) +} + +// parseFacts reads the __models__ block (rows: " +// [ []]") into a per-model table, sourced from the catalog +// so meters and routing agree. The trailing columns are optionals so legacy +// five- and six-token catalogs keep working: column six is the quota bucket, +// column seven the provider — accepted as the registry id (current renderer) +// or the bare pool letter (older renderers), resolved to a pool either way; +// unknown values fall back to the model-family guess. + +// catalogPool resolves the __models__ trailing column to a pool letter. The +// current renderer writes the registry provider id there; an older one wrote +// the bare pool letter. Either parses; anything unknown falls back to the +// model-family guess so mixed-age catalogs keep working. +func catalogPool(col, id string) string { + if len(col) == 1 { + if providerByPool(col) != nil { + return col + } + } + if p := providerByID(col); p != nil { + return p.Pool + } + if p := providerByModel(id); p != nil { + return p.Pool + } + return "" +} + +func parseFacts(rows []string) map[string]modelFact { + out := map[string]modelFact{} + for _, r := range rows { + f := strings.Fields(r) + if len(f) < 5 { + continue + } + in, e1 := strconv.ParseFloat(f[1], 64) + outc, e2 := strconv.ParseFloat(f[2], 64) + sp, e3 := strconv.ParseFloat(f[3], 64) + tt, e4 := strconv.ParseFloat(f[4], 64) + bucket, pool := "", "" + if len(f) >= 6 { + bucket = f[5] + } + if len(f) >= 7 { + pool = catalogPool(f[6], f[0]) + } + if e1 == nil && e2 == nil && e3 == nil && e4 == nil { + out[f[0]] = modelFact{in, outc, sp, tt, bucket, pool} + } + } + return out +} diff --git a/facts.go b/facts.go new file mode 100644 index 0000000..d702480 --- /dev/null +++ b/facts.go @@ -0,0 +1,39 @@ +package main + +import ( + "bufio" + "os" + "strings" +) + +// ── data ───────────────────────────────────────────────────────────────────── +// loadBlocks parses a generated page into name -> role rows. +func loadBlocks(path string) map[string][]string { + blocks := map[string][]string{} + f, err := os.Open(path) + if err != nil { + return blocks + } + defer f.Close() + sc := bufio.NewScanner(f) + sc.Buffer(make([]byte, 1024*128), 1024*128) + var cur string + for sc.Scan() { + line := sc.Text() + if line == "" { + cur = "" + continue + } + if line[0] != ' ' { + if fs := strings.Fields(line); len(fs) > 0 { + cur = fs[0] + blocks[cur] = nil + } + continue + } + if cur != "" { + blocks[cur] = append(blocks[cur], line) + } + } + return blocks +} diff --git a/generate.go b/generate.go index 837e4b8..42c8e51 100644 --- a/generate.go +++ b/generate.go @@ -8,14 +8,16 @@ package main // pkgs/omp-configured), generalised from that setup's hard-coded model keys to // pure pool/tier logic so it works against anyone's catalog: // -// - pools O (OpenAI/Codex) and A (Anthropic) must each fill tiers 1..3 — -// the per-pool fallback ladder (cheap, regular, smart). code assumes both -// providers are present; generation fails loudly otherwise. -// - pool R (OpenRouter) is optional: a free/aggregator lane. When any R model -// is declared, its tiers 1..3 must all be filled (a one-model family -// declares the same id three times with ascending thinking ceilings); when -// none is, the ox lanes are simply not generated and the TUI never offers -// them. That presence is the whole on/off switch — no dial of its own. +// - required pools (see providerRegistry: O = OpenAI/Codex, A = Anthropic) +// must each fill tiers 1..3 — the per-pool fallback ladder (cheap, +// regular, smart); generation fails loudly otherwise. Optional pools +// participate when present, each with its own strictness: D (DeepSeek, +// pay-as-you-go) needs one verified rung — missing tiers borrow the +// nearest existing one — while R (OpenRouter) is all-or-nothing: when any +// R model is declared, its tiers 1..3 must all be filled (a one-model +// family declares the same id three times with ascending thinking +// ceilings). An absent optional pool's lanes are simply not generated and +// the TUI never offers them; catalog presence is the whole on/off switch. // - tier 0 (an idle-bucket speed model, "spark") and tier 4 (a scarce elite, // "fable") are optional; without them the corresponding facet combos are // simply not generated and the TUI hides the dial. @@ -115,15 +117,19 @@ func loadCatalogBytes(raw []byte, path string) (*catalog, error) { if probedNode == nil || probedNode.Value != "true" { return nil, fmt.Errorf("%s: missing `probed: true` — these models were never verified as callable by your account. Re-run `code generate init --refresh`, which probes every model, or set `probed: true` yourself once you have confirmed each one", path) } - c := &catalog{models: map[string]catModel{}, levels: map[string][]int{}, ladder: map[string][5]string{"O": {}, "A": {}, "R": {}}} + ladder := map[string][5]string{} + for _, p := range providerRegistry { + ladder[p.Pool] = [5]string{} + } + c := &catalog{models: map[string]catModel{}, levels: map[string][]int{}, ladder: ladder} for i := 0; i+1 < len(modelsNode.Content); i += 2 { key := modelsNode.Content[i].Value var m catModel if err := modelsNode.Content[i+1].Decode(&m); err != nil { return nil, fmt.Errorf("%s: model %q: %w", path, key, err) } - if m.Pool != "O" && m.Pool != "A" && m.Pool != "R" { - return nil, fmt.Errorf("%s: model %q: pool must be O, A, or R (optional OpenRouter), got %q", path, key, m.Pool) + if providerByPool(m.Pool) == nil { + return nil, fmt.Errorf("%s: model %q: pool must be one of %s, got %q", path, key, strings.Join(fallbackPoolOrder, ", "), m.Pool) } if m.Tier < 0 || m.Tier > 4 { return nil, fmt.Errorf("%s: model %q: tier must be 0..4, got %d", path, key, m.Tier) @@ -142,26 +148,100 @@ func loadCatalogBytes(raw []byte, path string) (*catalog, error) { l[m.Tier] = key c.ladder[m.Pool] = l } - for _, pool := range []string{"O", "A"} { + for _, pool := range fallbackPoolOrder { + prov := providerByPool(pool) + if !prov.Required { + continue + } for t := 1; t <= 3; t++ { if c.ladder[pool][t] == "" { - return nil, fmt.Errorf("%s: pool %s has no tier-%d model — code assumes both an OpenAI and an Anthropic pool with tiers 1..3 filled (cheap, regular, smart)", path, pool, t) + return nil, fmt.Errorf("%s: pool %s has no tier-%d model — code assumes %s with tiers 1..3 filled (cheap, regular, smart)", path, pool, t, requiredProviderNames()) } } } // Pool R is all-or-nothing: a half-declared ox ladder would generate lanes // whose fallback rungs silently vanish. One-model families declare the same // id at every tier with ascending thinking ceilings — that repetition is - // the encoding, not a mistake. - if c.hasOxLadderPart() && !c.hasOxLadder() { - return nil, fmt.Errorf("%s: pool R must fill tiers 1..3 when present — declare the model once per tier with ascending thinking ceilings, or remove the pool entirely", path) + // the encoding, not a mistake. Strictness is a registry property: other + // optional pools (DeepSeek) stay lenient via fillOptionalLadders below. + for _, p := range providerRegistry { + if p.Required || !p.StrictLadder { + continue + } + l := c.ladder[p.Pool] + part, full := false, true + for t := 1; t <= 3; t++ { + if l[t] != "" { + part = true + } else { + full = false + } + } + if part && !full { + return nil, fmt.Errorf("%s: pool %s must fill tiers 1..3 when present — declare the model once per tier with ascending thinking ceilings, or remove the pool entirely", path, p.Pool) + } } + c.fillOptionalLadders() if err := c.checkLadder(path); err != nil { return nil, err } return c, nil } +// requiredProviderNames names the pools generation cannot proceed without — +// the registry's Required providers, joined for error text. +func requiredProviderNames() string { + var names []string + for _, pool := range fallbackPoolOrder { + if p := providerByPool(pool); p != nil && p.Required { + names = append(names, p.Label) + } + } + return "both " + strings.Join(names, " and ") + " pools" +} + +// pools lists the catalog's usable pools in fallbackPoolOrder: a pool is +// present once tiers 1..3 are filled (fillOptionalLadders completes partial +// optional pools first, so one verified rung is enough to participate). +func (c *catalog) pools() []string { + var out []string + for _, pool := range fallbackPoolOrder { + l := c.ladder[pool] + if l[1] != "" && l[2] != "" && l[3] != "" { + out = append(out, pool) + } + } + return out +} + +// fillOptionalLadders completes a non-Required pool that brought at least one +// ladder rung but not all three: each missing tier borrows the nearest +// existing rung, preferring the lower tier index. Chain dedupe absorbs the +// duplicates, so a one-model pool still yields working lanes. +func (c *catalog) fillOptionalLadders() { + for _, p := range providerRegistry { + if p.Required { + continue + } + l := c.ladder[p.Pool] + if l[1] == "" && l[2] == "" && l[3] == "" { + continue + } + for t := 1; t <= 3; t++ { + if l[t] != "" { + continue + } + for _, s := range []int{t - 1, t + 1, t - 2, t + 2} { + if s >= 1 && s <= 3 && l[s] != "" { + l[t] = l[s] + break + } + } + } + c.ladder[p.Pool] = l + } +} + // checkLadder rejects a ladder whose rungs are out of order. Input price used // to be a fair proxy for capability, so the scaffolder ranked by it — then // providers started repricing new models below predecessors they never @@ -171,9 +251,9 @@ func loadCatalogBytes(raw []byte, path string) (*catalog, error) { // less. Only tiers 1..3 are the capability ladder — tier 0 is a bucket-drain // lead and tier 4 an elite lead, both deliberately off it. func (c *catalog) checkLadder(path string) error { - // R joins only when declared; an absent pool has no rungs to compare and - // the empty-ladder guard below skips it. - for _, pool := range []string{"O", "A", "R"} { + // Optional pools join only when declared; an absent pool has no rungs to + // compare and the empty-rung guard below skips it. + for _, pool := range fallbackPoolOrder { for lo := 1; lo <= 3; lo++ { for hi := lo + 1; hi <= 3; hi++ { a, b := c.ladder[pool][lo], c.ladder[pool][hi] @@ -190,29 +270,6 @@ func (c *catalog) checkLadder(path string) error { return nil } -// hasOxLadder reports whether the optional OpenRouter pool is fully declared. -// The ox lanes exist exactly when this is true — catalog presence is their -// on/off switch. -func (c *catalog) hasOxLadder() bool { - for t := 1; t <= 3; t++ { - if c.ladder["R"][t] == "" { - return false - } - } - return true -} - -// hasOxLadderPart reports whether any R model is declared at all. The loader -// pairs it with hasOxLadder to reject half-declared pools. -func (c *catalog) hasOxLadderPart() bool { - for t := 0; t <= 4; t++ { - if c.ladder["R"][t] != "" { - return true - } - } - return false -} - // regression reports why rung hi is worse than the cheaper rung lo, or "" when // it isn't. A pricier model with more context and more thinking headroom is the // ladder working; a pricier model with less of either is a stale pick. @@ -282,14 +339,65 @@ func (c *catalog) clampTh(key, level string) string { } // otherPool is the crossing target for roles that must leave their lead pool: -// the reviewer's independent second eye, the advisor's minimum diversity. O -// and A cross to each other; R crosses to A — the strongest judgment pool, -// which is what every crossing on an ox lane is for. +// the reviewer's independent second eye, the advisor's minimum diversity. +// Declared per provider in the registry (providerDesc.CrossTo): O and A cross +// to each other, D crosses to O, and R crosses to A — the strongest judgment +// pool, which is what every crossing on an ox lane is for. func otherPool(p string) string { - if p == "O" || p == "R" { - return "A" + if d := providerByPool(p); d != nil { + return d.CrossTo } - return "O" + return "" +} + +// specialKey resolves a special-tier facet ("spark", "fable") to its ladder +// rung — "" when the owning pool never filled that tier. +func (c *catalog) specialKey(facet string) string { + p := providerBySpecial(facet) + if p == nil { + return "" + } + return c.ladder[p.Pool][p.special(facet).Tier] +} + +// advisorPool picks the advisor's context pool: the first advisorPoolOrder +// entry present in the catalog that is not the lead pool. +func (c *catalog) advisorPool(lead string) string { + present := map[string]bool{} + for _, p := range c.pools() { + present[p] = true + } + for _, p := range advisorPoolOrder { + if p != lead && present[p] { + return p + } + } + return lead +} + +// reliefRungs lists each optional pool's regular (tier-2) rung, in +// fallbackPoolOrder, skipping the lead's own pool. +func (c *catalog) reliefRungs(lead string) []string { + leadPool := c.models[lead].Pool + var out []string + for _, pool := range c.pools() { + if pool == leadPool || providerByPool(pool).Required { + continue + } + out = append(out, c.ladder[pool][2]) + } + return out +} + +// hasOptionalPool reports whether the catalog carries any non-required pool — +// the gate for the relief dial and its id segment. +func (c *catalog) hasOptionalPool() bool { + for _, pool := range c.pools() { + if p := providerByPool(pool); p != nil && !p.Required { + return true + } + } + return false } // sibDown is the same-pool fallback rung below a lead: the elite (tier 4) @@ -306,14 +414,19 @@ func (c *catalog) sibDown(key string) string { return "" } -// cross is the equivalent rung on the opposite pool (elites cross to smart). +// cross is the equivalent rung on the crossing target pool (elites cross to +// smart). func (c *catalog) cross(key string) string { m := c.models[key] t := m.Tier if t > 3 { t = 3 } - return c.ladder[otherPool(m.Pool)][t] + cp := otherPool(m.Pool) + if cp == "" { + return "" + } + return c.ladder[cp][t] } func dedup(seq []string, lead string) []string { @@ -366,25 +479,39 @@ func (c *catalog) visionLead(pool string, tier int) string { return "" } +// visionCross finds an image-capable rung on any other catalog pool, in +// fallbackPoolOrder — vision correctness beats lane purity, so even a pure +// lane crosses when its own pool is text-only at every rung. +func (c *catalog) visionCross(pool string, tier int) string { + for _, p := range c.pools() { + if p == pool { + continue + } + if k := c.visionLead(p, tier); k != "" { + return k + } + } + return "" +} + // ── the facet grid ──────────────────────────────────────────────────────────── var ( genRoleOrder = []string{"default", "task", "plan", "slow", "designer", "reviewer", - "librarian", "scout", "sonic", "advisor", "vision", "smol", "tiny", "commit"} + "security-reviewer", "librarian", "scout", "sonic", "advisor", "vision", "smol", "tiny", "commit"} // The bundled agents this grid routes: every ●-marked role is mirrored - // into task.agentModelOverrides. omp bundles a seventh since 17.2.1, - // security-reviewer, deliberately unrouted: security scans inject the - // scan's own model into task.agentModelOverrides per session, so a - // generated route would only skew ad-hoc spawns. scout was the one this - // grid never routed, so it silently inherited @smol and never appeared - // in the preview. + // into task.agentModelOverrides. security-reviewer gets reviewer's exact + // routing membership; note `omp security` still injects the scan's own + // model into task.agentModelOverrides at runtime, superseding this value + // inside that workflow — the catalog route covers ad-hoc spawns. genAgentRoles = map[string]bool{"designer": true, "librarian": true, "reviewer": true, - "scout": true, "sonic": true, "task": true} - genDelib = map[string]bool{"plan": true, "slow": true, "designer": true, "reviewer": true} - // Anti-tunnel-vision: on a *-led lane the reviewer crosses to the opposite + "security-reviewer": true, "scout": true, "sonic": true, "task": true} + genDelib = map[string]bool{"plan": true, "slow": true, "designer": true, + "reviewer": true, "security-reviewer": true} + // Anti-tunnel-vision: on a *-led lane the reviewers cross to the opposite // provider so the output always gets an independent second eye (the advisor // crosses too, in its own branch). - genCrossLed = map[string]bool{"reviewer": true} + genCrossLed = map[string]bool{"reviewer": true, "security-reviewer": true} genUtil = map[string]bool{"scout": true, "sonic": true, "smol": true, "tiny": true, "commit": true} // Utility roles respond to the dials but are tier-capped so none can ever // become expensive. @@ -410,6 +537,9 @@ var ( genMTiers = []string{"fast", "normal", "smart"} genThinking = []string{"minimal", "low", "medium", "high", "xhigh", "max"} genExtremes = map[string]bool{"minimal": true, "max": true} + // genRelief marks the heavyweight roles whose led/mixed chains gain a + // relief tail on each optional (unmetered, pay-as-you-go) pool. + genRelief = map[string]bool{"default": true, "task": true, "plan": true, "slow": true} ) // lanePolicy is a lane's whole role-mapping, as data. primary answers for @@ -439,8 +569,13 @@ var genLanePolicies = map[string]lanePolicy{ "mixed": {primary: "O", delib: "A", visionSmart: "A"}, "claude-led": {primary: "A"}, "claude-only": {primary: "A", pure: true}, - "ox-only": {primary: "R", pure: true}, - "ox-led": {primary: "R", delib: "A"}, + // DeepSeek lanes: pay-as-you-go capacity. The blend lane leads the work + // and lets reviewers cross to GPT; the pure lane is text-only end to end, + // so vision alone borrows an image-capable pool. + "ds-led": {primary: "D"}, + "ds-only": {primary: "D", pure: true}, + "ox-only": {primary: "R", pure: true}, + "ox-led": {primary: "R", delib: "A"}, // The mirror of ox-led: paid providers keep everything that answers for // the work (default, task, librarian), while the free pool absorbs the // high-volume background and image description. @@ -464,14 +599,24 @@ func (p lanePolicy) pool(role string) string { } // lanes lists the lanes this catalog serves: the five base lanes always, plus -// the ox trio only when the optional OpenRouter ladder is fully declared. -// This is the generator side of the ox on/off switch. Order follows the dial: -// base lanes first, ox lanes appended. +// each optional pool's lanes once its ladder qualifies — the ds pair when a +// DeepSeek ladder participates (one verified rung suffices), the ox trio only +// when the OpenRouter ladder is fully declared. This is the generator side of +// each optional pool's on/off switch. Order follows the dial: base lanes +// first, optional-pool lanes appended. func (c *catalog) lanes() []string { - if !c.hasOxLadder() { - return genBaseLanes + out := append([]string{}, genBaseLanes...) + present := map[string]bool{} + for _, p := range c.pools() { + present[p] = true } - return append(append([]string{}, genBaseLanes...), "ox-only", "ox-led", "ox-lean") + if present["D"] { + out = append(out, "ds-led", "ds-only") + } + if present["R"] { + out = append(out, "ox-only", "ox-led", "ox-lean") + } + return out } type roleRoute struct { @@ -484,14 +629,14 @@ type roleRoute struct { // genCombo computes {role -> route} for one facet combination. A direct port // of generate-profiles.py's gen(), with the hard-coded model keys generalised // to pool/tier lookups. -func (c *catalog) genCombo(lane, mtier, thinking string, spark, fable, fableMain bool) map[string]roleRoute { +func (c *catalog) genCombo(lane, mtier, thinking string, spark, fable, fableMain, relief bool) map[string]roleRoute { pol := genLanePolicies[lane] p := pol.primary base := genTierMap[mtier] isPure := pol.pure extreme := genExtremes[thinking] - sparkKey := c.ladder["O"][0] - eliteKey := c.ladder["A"][4] + sparkKey := c.specialKey("spark") + eliteKey := c.specialKey("fable") rprov := func(r string) string { return pol.pool(r) @@ -542,8 +687,10 @@ func (c *catalog) genCombo(lane, mtier, thinking string, spark, fable, fableMain vp = pol.visionSmart } lead := c.visionLead(vp, base) - if lead == "" && !isPure { - lead = c.visionLead(otherPool(vp), base) + if lead == "" { + // No image-capable rung on the lead pool at all: cross pools + // even on a pure lane — vision correctness beats lane purity. + lead = c.visionCross(vp, base) } if lead == "" { out[r] = roleRoute{} @@ -573,7 +720,7 @@ func (c *catalog) genCombo(lane, mtier, thinking string, spark, fable, fableMain // allows crossing — the minimum diversity guarantee. ap := p if !isPure { - ap = otherPool(p) + ap = c.advisorPool(p) } amod := c.ladder[ap][1] if mtier == "smart" { @@ -615,6 +762,13 @@ func (c *catalog) genCombo(lane, mtier, thinking string, spark, fable, fableMain lead = eliteKey } chain := c.buildChain(lead, isPure) + if !isPure && relief && genRelief[r] { + // Relief tails: led/mixed lanes end their heavyweight chains on + // each optional pool's regular rung — pay-as-you-go capacity that + // keeps a session alive when every metered window is drained. + // Pure lanes stay pure. dedup absorbs rungs already in the chain. + chain = dedup(append(chain, c.reliefRungs(lead)...), lead) + } out[r] = roleRoute{lead, th, chain, repeatLvl(th, len(chain))} } return out @@ -628,7 +782,7 @@ func repeatLvl(lvl string, n int) []string { return out } -func genComboID(lane, mtier, thinking string, spark, fable, fableMain bool) string { +func genComboID(lane, mtier, thinking string, spark, fable, fableMain, relief, hasRelief bool) string { sp, fa := "nosp", "nofa" if spark { sp = "sp" @@ -639,15 +793,29 @@ func genComboID(lane, mtier, thinking string, spark, fable, fableMain bool) stri fa = "famain" } } - return fmt.Sprintf("%s_%s_%s_%s_%s", lane, mtier, thinking, sp, fa) + id := fmt.Sprintf("%s_%s_%s_%s_%s", lane, mtier, thinking, sp, fa) + if hasRelief { + // The relief segment exists only in catalogs that carry an optional + // pool — two-pool ids stay byte-identical. + if relief { + id += "_rel" + } else { + id += "_norel" + } + } + return id } -func genValid(lane string, spark, fable, fableMain bool) bool { - if lane == "gpt-only" && fable { - return false // no elite on pure GPT +// genValid rejects facet combos the lane cannot host: a special-tier facet +// (spark, fable) is valid only when its provider's pool is in the lane's +// pool-set — a pure lane hosts only its own pool. relief=off exists only +// where relief tails could appear at all: a metered-led blend. +func genValid(lane string, spark, fable, fableMain, relief bool) bool { + if fable && !laneHostsSpecial(lane, "fable") { + return false // no elite outside its pool's lanes } - if lane == "claude-only" && spark { - return false // no spark on pure Claude + if spark && !laneHostsSpecial(lane, "spark") { + return false // no spark outside its pool's lanes } if lane == "ox-only" && (spark || fable) { return false // a pure ox lane has no O drain bucket or A elite to lead with @@ -661,14 +829,17 @@ func genValid(lane string, spark, fable, fableMain bool) bool { if fableMain && !fable { return false // fable-as-main only exists on top of fable } + if !relief && !laneReliefApplies(lane) { + return false // relief is not a choice where no tail is generated + } return true } // ── rendering (byte-compatible with generate-profiles.py) ──────────────────── -func (c *catalog) renderCombo(lane, mtier, thinking string, spark, fable, fableMain bool) string { - roles := c.genCombo(lane, mtier, thinking, spark, fable, fableMain) - cid := genComboID(lane, mtier, thinking, spark, fable, fableMain) +func (c *catalog) renderCombo(lane, mtier, thinking string, spark, fable, fableMain, relief, hasRelief bool) string { + roles := c.genCombo(lane, mtier, thinking, spark, fable, fableMain, relief) + cid := genComboID(lane, mtier, thinking, spark, fable, fableMain, relief, hasRelief) desc := []string{lane, mtier, thinking} if spark { desc = append(desc, "spark") @@ -679,6 +850,9 @@ func (c *catalog) renderCombo(lane, mtier, thinking string, spark, fable, fableM if fable && fableMain { desc = append(desc, "main") } + if !relief { + desc = append(desc, "no-relief") + } lines := []string{fmt.Sprintf("%s %s", cid, strings.Join(desc, " · "))} advOn := roles["advisor"].lead != "" adv := "off" @@ -712,23 +886,24 @@ func (c *catalog) renderCombo(lane, mtier, thinking string, spark, fable, fableM return strings.Join(lines, "\n") } -// renderModelFacts emits the per-model table the TUI's meters read. The bucket -// is a trailing optional column: the consumer falls back to guessing from the -// model family when a catalog omits it. The pool column after it is the -// authoritative provider prefix for launched configs, replacing that same -// name heuristic wherever present — this renderer always writes it, but both -// columns are optional on the parsing side, so catalogs and binaries of mixed -// age keep working together. +// renderModelFacts emits the per-model table the TUI's meters read: seven +// fixed columns — id, pricing, speed, ttft, quota bucket, provider id. The +// provider column is the authoritative provider for launched configs, +// replacing the name heuristic wherever present. Old catalogs with five- or +// six-column rows still parse; the consumer falls back to guessing bucket and +// provider from the model family for those. func (c *catalog) renderModelFacts() string { - lines := []string{"__models__ model facts (id in out speed ttft [bucket] [pool] — $/1M in·out, tok/s, s)"} + lines := []string{"__models__ model facts (id in out speed ttft bucket provider — $/1M in·out, tok/s, s)"} for _, k := range c.keys { m := c.models[k] - row := fmt.Sprintf(" %s %s %s %s %s", - m.ID, trimFloat(m.CostIn), trimFloat(m.CostOut), trimFloat(m.Speed), trimFloat(m.TTFT)) - if m.Bucket != "" { - row += " " + m.Bucket + prov := providerByPool(m.Pool) + bucket := m.Bucket + if bucket == "" { + bucket = prov.mainBucket() } - row += " " + m.Pool + row := fmt.Sprintf(" %s %s %s %s %s %s %s", + m.ID, trimFloat(m.CostIn), trimFloat(m.CostOut), trimFloat(m.Speed), trimFloat(m.TTFT), + bucket, prov.ID) lines = append(lines, row) } lines = append(lines, "") @@ -761,11 +936,8 @@ func (c *catalog) renderAdvisors() string { {"audit", []rung{{3, "high"}, {2, "high"}, {1, "low"}}}, } lines := []string{"__advisors__ advisor dial (level context → chain)"} - advisorContexts := []struct{ name, pool string }{{"gpt", "O"}, {"claude", "A"}} - if c.hasOxLadder() { - advisorContexts = append(advisorContexts, struct{ name, pool string }{"ox", "R"}) - } - for _, ctx := range advisorContexts { + for _, pool := range c.pools() { + ctx := struct{ name, pool string }{providerByPool(pool).Lane, pool} for _, d := range dial { var parts []string for _, rg := range d.chain { @@ -795,8 +967,9 @@ func (c *catalog) renderCatalog() string { " — ● marks a role mirrored into task.agentModelOverrides\n\n") b.WriteString(c.renderAdvisors() + "\n") b.WriteString(c.renderModelFacts() + "\n") - hasSpark := c.ladder["O"][0] != "" - hasElite := c.ladder["A"][4] != "" + hasSpark := c.specialKey("spark") != "" + hasElite := c.specialKey("fable") != "" + hasRelief := c.hasOptionalPool() for _, lane := range c.lanes() { for _, mtier := range genMTiers { for _, thinking := range genThinking { @@ -809,8 +982,13 @@ func (c *catalog) renderCatalog() string { continue } for _, fableMain := range []bool{false, true} { - if genValid(lane, spark, fable, fableMain) { - b.WriteString(c.renderCombo(lane, mtier, thinking, spark, fable, fableMain) + "\n") + for _, relief := range []bool{true, false} { + if !relief && !hasRelief { + continue + } + if genValid(lane, spark, fable, fableMain, relief) { + b.WriteString(c.renderCombo(lane, mtier, thinking, spark, fable, fableMain, relief, hasRelief) + "\n") + } } } } diff --git a/generate_init.go b/generate_init.go index ab4d414..de53ae6 100644 --- a/generate_init.go +++ b/generate_init.go @@ -44,12 +44,12 @@ var ompModelsJSON = func() ([]byte, error) { var datedID = regexp.MustCompile(`-\d{6,8}$`) +// poolOf maps an omp provider id to its catalog pool letter via the registry; +// providers outside the registry (google, groq, ollama, …) map to "" and are +// dropped from the scaffold, as ever. func poolOf(provider string) string { - switch provider { - case "anthropic": - return "A" - case "openai-codex", "openai": - return "O" + if p := providerByID(provider); p != nil { + return p.Pool } return "" } @@ -323,10 +323,7 @@ func matchSpecial(st specialTier, cands []ompModel) string { // bucketName follows the catalog's existing convention: the pool's own quota // window, or a tier-scoped one when the model draws a separate bucket. func bucketName(pool, tier string) string { - base := "codex" - if pool == "A" { - base = "claude" - } + base := providerByPool(pool).BucketBase if tier == "" { return base + "-main" } @@ -535,8 +532,14 @@ func scaffoldModels(raw []byte, probe map[string]benchFact) (string, error) { bucket string } rungs := map[string][]rung{} - for _, pool := range []string{"O", "A"} { + for _, pool := range fallbackPoolOrder { + required := providerByPool(pool).Required cands := supersede(byPool[pool]) + if !required && len(cands) == 0 { + // A missing optional provider is the normal state: the catalog + // simply grows no lanes for its pool. + continue + } // Lift a tier-scoped model out before ranking: it is a lead, not a rung // on the capability ladder. At most one per pool — two would both claim // the same tier and loadCatalog would refuse the file — preferring the @@ -577,9 +580,9 @@ func scaffoldModels(raw []byte, probe map[string]benchFact) (string, error) { c = m.Cost.Input } } - if pool == "A" && c >= top { + if poolDeclaresSpecialTier(pool, 4) && c >= top { specialTierNo = 4 - } else if pool == "O" && c < top { + } else if poolDeclaresSpecialTier(pool, 0) && c < top { specialTierNo = 0 } } @@ -591,7 +594,7 @@ func scaffoldModels(raw []byte, probe map[string]benchFact) (string, error) { // Price is legitimate evidence here. It says nothing about entitlement, // which is why the reachability probe exists, but a model in its own // price class is by definition not the everyday workhorse. - if specialTierNo < 0 && pool == "A" && len(cands) > 1 { + if specialTierNo < 0 && poolDeclaresSpecialTier(pool, 4) && len(cands) > 1 { lead, next := cands[0], 0.0 for _, m := range cands { if m.Cost.Input > lead.Cost.Input { @@ -645,12 +648,9 @@ func scaffoldModels(raw []byte, probe map[string]benchFact) (string, error) { ladderCands = kept } ladder := pickLadder(ladderCands) - if len(ladder) < 3 { - name := "OpenAI/Codex" - if pool == "A" { - name = "Anthropic" - } - hint := "code assumes both Anthropic and OpenAI are set up in omp" + if len(ladder) < 3 && required { + name := providerByPool(pool).Label + hint := "code assumes " + requiredProviderNames() + " are set up in omp" if probe != nil { hint += "; models the provider reported as non-existent were dropped by the probe" } @@ -669,7 +669,7 @@ func scaffoldModels(raw []byte, probe map[string]benchFact) (string, error) { # omp; the tier assignments are derived (newest model per family, then ranked by # thinking ceiling, context and price) and worth a sanity check. # -# pool: O = OpenAI/Codex · A = Anthropic +# pool: O = OpenAI/Codex · A = Anthropic · D = DeepSeek # tier: 1 cheap · 2 regular · 3 smart (the per-pool fallback ladder) # tier 0 = a fast idle-bucket model the 'spark' toggle drains; # tier 4 = a scarce elite the 'fable' toggle leads with. Both are @@ -698,7 +698,7 @@ probed: false } b.WriteString("models:\n") used := map[string]bool{} - for _, pool := range []string{"O", "A"} { + for _, pool := range fallbackPoolOrder { for _, r := range rungs[pool] { key := shortKey(r.m.ID) if used[key] { diff --git a/generate_test.go b/generate_test.go index 1d925a0..d973d17 100644 --- a/generate_test.go +++ b/generate_test.go @@ -133,17 +133,16 @@ const goldenAdvisors = `__advisors__ advisor dial (level context → chain) audit claude claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:low ` -// goldenFacts pins the trailing bucket and pool columns the TUI's quota meter -// and provider prefixing read. -const goldenFacts = `__models__ model facts (id in out speed ttft [bucket] [pool] — $/1M in·out, tok/s, s) - gpt-5.6-luna 1 6 52.3 1.18 codex-main O - gpt-5.6-terra 2.5 15 51.8 1.74 codex-main O - gpt-5.6-sol 5 30 31.5 4.59 codex-main O - gpt-5.3-codex-spark 1.75 14 286.7 5.56 codex-spark O - claude-haiku-4-5 1 5 48.9 1.7 claude-main A - claude-sonnet-5 2 10 35.2 3.84 claude-main A - claude-opus-5 5 25 46.6 1.77 claude-main A - claude-fable-5 10 50 54 6.9 claude-fable A +// goldenFacts pins the trailing bucket column the TUI's quota meter reads. +const goldenFacts = `__models__ model facts (id in out speed ttft bucket provider — $/1M in·out, tok/s, s) + gpt-5.6-luna 1 6 52.3 1.18 codex-main openai-codex + gpt-5.6-terra 2.5 15 51.8 1.74 codex-main openai-codex + gpt-5.6-sol 5 30 31.5 4.59 codex-main openai-codex + gpt-5.3-codex-spark 1.75 14 286.7 5.56 codex-spark openai-codex + claude-haiku-4-5 1 5 48.9 1.7 claude-main anthropic + claude-sonnet-5 2 10 35.2 3.84 claude-main anthropic + claude-opus-5 5 25 46.6 1.77 claude-main anthropic + claude-fable-5 10 50 54 6.9 claude-fable anthropic ` const goldenMixedSmart = `mixed_smart_medium_sp_fa mixed · smart · medium · spark · fable @@ -154,6 +153,7 @@ const goldenMixedSmart = `mixed_smart_medium_sp_fa mixed · smart · medium · slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium @@ -172,6 +172,7 @@ const goldenClaudeMax = `claude-only_normal_max_nosp_famain claude-only · norm slow claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max ● designer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max ● reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh @@ -194,6 +195,7 @@ const goldenClaudeSmart = `claude-only_smart_medium_nosp_nofa claude-only · sm slow claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high ● designer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high ● reviewer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high ● librarian claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium @@ -218,15 +220,16 @@ func TestGoldenModelFacts(t *testing.T) { } } -// A catalog that declares no buckets keeps the old five-column rows plus the -// pool column, so the consumer's fallback paths stay exercised. +// A catalog that declares no bucket for a model still renders all seven +// columns: the pool's main window is the derived bucket, so the TUI never has +// to guess from the model family for a freshly generated catalog. func TestModelFactsWithoutBuckets(t *testing.T) { c, err := catalogFrom(t, strings.ReplaceAll(fixtureYML, " bucket: codex-main\n", "")) if err != nil { t.Fatalf("loadCatalog: %v", err) } - if !strings.Contains(c.renderModelFacts(), " gpt-5.6-luna 1 6 52.3 1.18 O\n") { - t.Errorf("bucketless model row should stop after ttft + pool:\n%s", c.renderModelFacts()) + if !strings.Contains(c.renderModelFacts(), " gpt-5.6-luna 1 6 52.3 1.18 codex-main openai-codex\n") { + t.Errorf("bucketless model row should derive the pool's main bucket:\n%s", c.renderModelFacts()) } } @@ -237,13 +240,13 @@ func TestGoldenCombos(t *testing.T) { render func() string }{ {"mixed_smart_medium_sp_fa", goldenMixedSmart, func() string { - return c.renderCombo("mixed", "smart", "medium", true, true, false) + return c.renderCombo("mixed", "smart", "medium", true, true, false, true, false) }}, {"claude-only_normal_max_nosp_famain", goldenClaudeMax, func() string { - return c.renderCombo("claude-only", "normal", "max", false, true, true) + return c.renderCombo("claude-only", "normal", "max", false, true, true, true, false) }}, {"claude-only_smart_medium_nosp_nofa", goldenClaudeSmart, func() string { - return c.renderCombo("claude-only", "smart", "medium", false, false, false) + return c.renderCombo("claude-only", "smart", "medium", false, false, false, true, false) }}, } { if got := tc.render(); got != tc.want { @@ -288,7 +291,7 @@ func TestRenderCatalogStructure(t *testing.T) { misses := 0 walk = func(i int) { if i == len(facets) { - id := comboID(sel) + id := comboID(sel, false) if !strings.Contains(out, "\n"+id+" ") { misses++ if misses < 5 { @@ -322,7 +325,7 @@ func TestEveryEmittedRoleIsWeighted(t *testing.T) { // genConfigYAML mirrors it into task.agentModelOverrides. func TestScoutIsAgentBacked(t *testing.T) { c := fixtureCatalog(t) - block := c.renderCombo("mixed", "normal", "medium", false, false, false) + block := c.renderCombo("mixed", "normal", "medium", false, false, false, true, false) if !strings.Contains(block, "● scout ") { t.Errorf("scout must render as an agent-backed role:\n%s", block) } @@ -386,7 +389,7 @@ func TestVisionSkipsTextOnlyModels(t *testing.T) { if lead := c.visionLead("O", 1); lead == "" || c.models[lead].ID != "gpt-5.6-terra" { t.Errorf("visionLead(O, 1) = %q, want the next image-capable rung (terra)", lead) } - block := c.renderCombo("gpt-only", "fast", "medium", false, false, false) + block := c.renderCombo("gpt-only", "fast", "medium", false, false, false, true, false) for _, l := range strings.Split(block, "\n") { if strings.Contains(l, " vision ") && strings.Contains(l, "codex-spark") { t.Errorf("vision must not route to a text-only model: %s", l) @@ -410,7 +413,7 @@ func TestVisionFollowsModelTier(t *testing.T) { {"mixed", "smart", "claude-opus-5"}, } { t.Run(tc.lane+"/"+tc.tier, func(t *testing.T) { - route := c.genCombo(tc.lane, tc.tier, "medium", false, false, false)["vision"] + route := c.genCombo(tc.lane, tc.tier, "medium", false, false, false, true)["vision"] if got := c.models[route.lead].ID; got != tc.want { t.Errorf("vision lead = %q, want %q", got, tc.want) } @@ -1123,6 +1126,249 @@ func TestGenerateInitRefusesUnresolvedProbe(t *testing.T) { } } +// fixtureYMLDeepSeek extends the two-pool fixture with a full DeepSeek pool +// (three text-only rungs) — the three-pool grid the ds lanes are generated +// from. +const fixtureYMLDeepSeek = fixtureYML + ` lite: + id: deepseek-v4-lite + pool: D + tier: 1 + bucket: deepseek-main + cost_in: 0.1 + cost_out: 0.4 + speed: 60 + ttft: 1.5 + context: 128000 + thinking: low→high + image: false + v4: + id: deepseek-v4 + pool: D + tier: 2 + bucket: deepseek-main + cost_in: 0.3 + cost_out: 1.2 + speed: 45 + ttft: 2.1 + context: 128000 + thinking: low→high + image: false + pro: + id: deepseek-v4-pro + pool: D + tier: 3 + bucket: deepseek-main + cost_in: 0.6 + cost_out: 2.4 + speed: 38 + ttft: 2.8 + context: 128000 + thinking: low→high + image: false +` + +// fixtureYMLDeepSeekOneRung brings a single DeepSeek model: the optional-pool +// fill must complete tiers 1..3 from it, and chain dedupe must collapse the +// duplicates. +const fixtureYMLDeepSeekOneRung = fixtureYML + ` v4: + id: deepseek-v4 + pool: D + tier: 2 + bucket: deepseek-main + cost_in: 0.3 + cost_out: 1.2 + speed: 45 + ttft: 2.1 + context: 128000 + thinking: low→high + image: false +` + +// TestGoldenCatalogTwoPool is the byte-compat contract of the N-pool +// generalisation: a two-pool models file renders the exact catalog the old +// binary-pool renderer produced (modulo the reviewed additions the golden +// carries: the __models__ bucket+provider columns and the security-reviewer +// role). +func TestGoldenCatalogTwoPool(t *testing.T) { + want, err := os.ReadFile(filepath.Join("testdata", "two-pool-golden.plain")) + if err != nil { + t.Fatal(err) + } + c := fixtureCatalog(t) + if got := c.renderCatalog(); got != string(want) { + t.Errorf("two-pool catalog is no longer byte-identical to the golden (diff it against testdata/two-pool-golden.plain to review)\ngot %d bytes, want %d", len(got), len(want)) + } +} + +// TestThreePoolCatalog locks the DeepSeek pool's grid semantics: the two new +// lanes, special-tier validity, relief tails on led/mixed heavyweight chains +// (and only there), the vision purity exception, and the ds advisor contexts. +func TestThreePoolCatalog(t *testing.T) { + c, err := catalogFrom(t, fixtureYMLDeepSeek) + if err != nil { + t.Fatalf("loadCatalog: %v", err) + } + out := c.renderCatalog() + + for _, lane := range []string{"gpt-only", "gpt-led", "mixed", "claude-led", "claude-only", "ds-led", "ds-only"} { + if !strings.Contains(out, "\n"+lane+"_normal_medium_nosp_nofa_rel ") { + t.Errorf("lane %s missing from the three-pool grid", lane) + } + } + // Special tiers follow their pool: none on ds-only, both on ds-led. + if strings.Contains(out, "\nds-only_normal_medium_sp_") || strings.Contains(out, "_medium_nosp_fa\nds-only") || + strings.Contains(out, "\nds-only_normal_medium_nosp_fa") { + t.Error("ds-only generated spark/fable combos it cannot host") + } + for _, id := range []string{"ds-led_normal_medium_sp_nofa_rel", "ds-led_normal_medium_nosp_fa_rel", "ds-led_normal_medium_nosp_famain_rel"} { + if !strings.Contains(out, "\n"+id+" ") { + t.Errorf("ds-led combo %s missing", id) + } + } + + block := func(id string) string { + i := strings.Index(out, "\n"+id+" ") + if i < 0 { + t.Fatalf("combo %s missing", id) + } + rest := out[i+1:] + if j := strings.Index(rest, "\n\n"); j >= 0 { + rest = rest[:j] + } + return rest + } + row := func(blk, role string) string { + for _, ln := range strings.Split(blk, "\n") { + f := strings.Fields(ln) + if len(f) > 1 && f[0] == "●" { + f = f[1:] + } + if len(f) > 0 && f[0] == role { + return ln + } + } + t.Fatalf("role %s missing from block:\n%s", role, blk) + return "" + } + + // Relief tails: led/mixed heavyweight chains end on the DeepSeek regular + // rung; pure lanes stay pure. + for _, lane := range []string{"gpt-led", "mixed", "claude-led"} { + blk := block(lane + "_normal_medium_nosp_nofa_rel") + for _, role := range []string{"default", "task", "plan", "slow"} { + if r := row(blk, role); !strings.HasSuffix(r, "→ deepseek-v4:medium") && !strings.HasSuffix(r, "→ deepseek-v4:high") { + t.Errorf("%s %s chain lacks the DeepSeek relief tail: %q", lane, role, r) + } + } + if r := row(blk, "reviewer"); strings.Contains(r, "deepseek") { + t.Errorf("%s reviewer must not gain a relief tail: %q", lane, r) + } + } + for _, lane := range []string{"gpt-only", "claude-only"} { + if blk := block(lane + "_normal_medium_nosp_nofa_rel"); strings.Contains(blk, "deepseek") { + t.Errorf("pure lane %s crossed into the DeepSeek pool:\n%s", lane, blk) + } + } + + // Vision purity exception: every DeepSeek rung is text-only, so ds-only's + // vision role must cross pools rather than route images to a text model. + dsOnly := block("ds-only_smart_medium_nosp_nofa_rel") + if r := row(dsOnly, "vision"); strings.Contains(r, "deepseek") || !strings.Contains(r, "gpt-5.6-sol:low") { + t.Errorf("ds-only vision must cross to an image-capable pool: %q", r) + } + // …and every other ds-only role stays on DeepSeek. + for _, role := range []string{"default", "task", "plan", "reviewer", "advisor", "commit"} { + if r := row(dsOnly, role); strings.Contains(r, "gpt") || strings.Contains(r, "claude") { + t.Errorf("ds-only %s left the pool: %q", role, r) + } + } + + // The advisor dial gains one context per pool. + for _, want := range []string{ + " glance ds deepseek-v4-lite:low", + " review ds deepseek-v4:medium → deepseek-v4-lite:low", + " audit ds deepseek-v4-pro:high → deepseek-v4:high → deepseek-v4-lite:low", + } { + if !strings.Contains(out, want+"\n") { + t.Errorf("advisors block lacks %q", want) + } + } + + // The facts table carries the provider column for every pool. + if !strings.Contains(out, " deepseek-v4 0.3 1.2 45 2.1 deepseek-main deepseek\n") { + t.Error("facts table lacks the deepseek provider row") + } +} + +// TestOneRungOptionalPool: a single verified DeepSeek model is enough to grow +// the ds lanes — the fill borrows it for every ladder tier and dedupe keeps +// the chains single-entry. +func TestOneRungOptionalPool(t *testing.T) { + c, err := catalogFrom(t, fixtureYMLDeepSeekOneRung) + if err != nil { + t.Fatalf("loadCatalog: %v", err) + } + for tier := 1; tier <= 3; tier++ { + if got := c.ladder["D"][tier]; got != "v4" { + t.Fatalf("optional-pool fill: ladder[D][%d] = %q, want v4", tier, got) + } + } + out := c.renderCatalog() + blkStart := strings.Index(out, "\nds-only_smart_medium_nosp_nofa_rel ") + if blkStart < 0 { + t.Fatal("one-rung D pool generated no ds-only lane") + } + blk := out[blkStart:] + if i := strings.Index(blk[1:], "\n\n"); i >= 0 { + blk = blk[:i+1] + } + if strings.Contains(blk, "deepseek-v4:medium → deepseek-v4:medium") { + t.Errorf("borrowed rungs must dedupe out of the chains:\n%s", blk) + } + if !strings.Contains(blk, " default deepseek-v4:medium\n") { + t.Errorf("one-rung ds-only default should be the single model, got:\n%s", blk) + } +} + +// TestReliefToggle: _norel combos exist only on metered-led blends, and the +// off variant strips exactly the DeepSeek tail while everything else in the +// block stays identical. +func TestReliefToggle(t *testing.T) { + c, err := catalogFrom(t, fixtureYMLDeepSeek) + if err != nil { + t.Fatalf("loadCatalog: %v", err) + } + out := c.renderCatalog() + for _, lane := range []string{"gpt-led", "mixed", "claude-led"} { + if !strings.Contains(out, "\n"+lane+"_normal_medium_nosp_nofa_norel ") { + t.Errorf("%s lacks a relief-off combo", lane) + } + } + for _, lane := range []string{"gpt-only", "claude-only", "ds-led", "ds-only"} { + if strings.Contains(out, "\n"+lane+"_normal_medium_nosp_nofa_norel ") { + t.Errorf("%s generated a relief-off combo it cannot use", lane) + } + } + on := c.renderCombo("gpt-led", "normal", "medium", false, false, false, true, true) + off := c.renderCombo("gpt-led", "normal", "medium", false, false, false, false, true) + if !strings.Contains(on, "deepseek") { + t.Fatalf("relief-on block lost its tail:\n%s", on) + } + if strings.Contains(off, "deepseek") { + t.Errorf("relief-off block still spills into DeepSeek:\n%s", off) + } + strip := func(s string) string { + s = strings.ReplaceAll(s, " → deepseek-v4:medium", "") + s = strings.ReplaceAll(s, " → deepseek-v4:high", "") + s = strings.ReplaceAll(s, "_rel ", " ") + s = strings.ReplaceAll(s, "_norel ", " ") + return strings.ReplaceAll(s, " · no-relief", "") + } + if strip(on) != strip(off) { + t.Errorf("relief must only add/remove tails; blocks diverge:\n--- on ---\n%s\n--- off ---\n%s", on, off) + } +} + // ── pool R (OpenRouter) ─────────────────────────────────────────────────────── // oxEntries declares a one-model family the only way the loader accepts: once @@ -1209,7 +1455,7 @@ func TestGenValidOxLanes(t *testing.T) { {"ox-lean", false, true, false, true}, // deliberative roles stay Claude; fable may lead them {"ox-lean", false, true, true, true}, // fable-as-default is exactly what lean is for } { - if got := genValid(tc.lane, tc.spark, tc.fable, tc.main_); got != tc.want { + if got := genValid(tc.lane, tc.spark, tc.fable, tc.main_, true); got != tc.want { t.Errorf("genValid(%s, sp=%v, fa=%v, famain=%v) = %v, want %v", tc.lane, tc.spark, tc.fable, tc.main_, got, tc.want) } @@ -1221,7 +1467,7 @@ func TestGenValidOxLanes(t *testing.T) { // its lead's provider. func TestOxLaneRoutingPolicy(t *testing.T) { c := catalogWithOx(t) - combo := c.genCombo("ox-led", "smart", "high", false, true, false) + combo := c.genCombo("ox-led", "smart", "high", false, true, false, false) for _, r := range []string{"default", "task", "scout", "sonic", "smol", "tiny", "commit", "vision"} { if id := c.models[combo[r].lead].ID; id != "stealth/ox-alpha" { t.Errorf("ox-led %s lead = %s, want stealth/ox-alpha", r, id) @@ -1240,7 +1486,7 @@ func TestOxLaneRoutingPolicy(t *testing.T) { } } // Pure ox: every role including advisor and reviewer stays on R. - pure := c.genCombo("ox-only", "normal", "medium", false, false, false) + pure := c.genCombo("ox-only", "normal", "medium", false, false, false, false) for _, r := range genRoleOrder { rt := pure[r] if rt.lead == "" { @@ -1257,7 +1503,7 @@ func TestOxLaneRoutingPolicy(t *testing.T) { // seat to the elite is exactly what an operator on this lane may want. func TestOxLeanRoutingPolicy(t *testing.T) { c := catalogWithOx(t) - combo := c.genCombo("ox-lean", "smart", "high", false, true, true) + combo := c.genCombo("ox-lean", "smart", "high", false, true, true, false) // fable-as-main hands only the default seat to the elite; task and // librarian follow the lane's OpenAI primary. for _, r := range []string{"task", "librarian"} { @@ -1279,7 +1525,7 @@ func TestOxLeanRoutingPolicy(t *testing.T) { } } // Without fable, workers stay on the OpenAI primary. - base := c.genCombo("ox-lean", "normal", "medium", false, false, false) + base := c.genCombo("ox-lean", "normal", "medium", false, false, false, false) for _, r := range []string{"default", "task"} { if pool := c.models[base[r].lead].Pool; pool != "O" { t.Errorf("ox-lean %s pool = %s, want O", r, pool) diff --git a/keys.go b/keys.go new file mode 100644 index 0000000..d888dfc --- /dev/null +++ b/keys.go @@ -0,0 +1,45 @@ +package main + +import "github.com/charmbracelet/bubbles/key" + +// ── keybindings (drive both input handling and the bubbles/help footer) ─────── +type keyMap struct { + Move, Change, Reset, Depth, Refresh, Manager, Collapse, Usage, Launch, Managed, Untrusted, Help, Quit key.Binding +} + +// ShortHelp is a static single-line stand-in used only when measuring the +// footer height for mode selection (which would otherwise recurse through the +// state-derived compact help). The rendered compact line comes from +// contextHelp, which derives its bindings from the live model state (atyrode/dotfiles#198). +func (k keyMap) ShortHelp() []key.Binding { + return []key.Binding{k.Move, k.Change, k.Reset, k.Help, k.Quit} +} +func (k keyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.Move, k.Change, k.Reset}, + {k.Depth, k.Refresh, k.Manager, k.Collapse, k.Usage}, + {k.Launch, k.Managed, k.Untrusted, k.Help, k.Quit}, + } +} + +var keys = keyMap{ + Move: key.NewBinding(key.WithKeys("up", "down", "j", "k"), key.WithHelp("↑↓", "move")), + Change: key.NewBinding(key.WithKeys("left", "right", "h", "l"), key.WithHelp("←→", "change")), + Reset: key.NewBinding(key.WithKeys("d"), key.WithHelp("d", gReset+" defaults")), + Depth: key.NewBinding(key.WithKeys("f"), key.WithHelp("f", "primary ⇄ full chains")), + Refresh: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "refresh usage")), + Manager: key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "manage accounts")), + Collapse: key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "show/hide routing")), + Usage: key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "show/hide usage")), + Launch: key.NewBinding(key.WithKeys("enter"), key.WithHelp("⏎", "launch")), + Managed: key.NewBinding(key.WithKeys("m"), key.WithHelp("m", "managed omp")), + Untrusted: key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "sandbox")), + Help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "more")), + Quit: key.NewBinding(key.WithKeys("q", "esc", "ctrl+c"), key.WithHelp("q", "quit")), +} + +// defaultSel returns a fresh copy of the generator's default facet selection — +// used both to seed the model and to restore it via the reset key. +func defaultSel() map[string]string { + return map[string]string{"lane": "mixed", "model": "smart", "thinking": "medium", "advisor": "glance", "spark": "on", "fable": "off", "main": "off", "fast": "off", "relief": "on"} +} diff --git a/launch.go b/launch.go new file mode 100644 index 0000000..ae1f3c2 --- /dev/null +++ b/launch.go @@ -0,0 +1,135 @@ +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" +) + +func forwardArgv(path string, forwarded []string, prompt string) []string { + out := append([]string{path}, stripProfileArgs(forwarded)...) + if prompt != "" { + out = append(out, prompt) + } + return out +} + +func managedLaunchArgv(path string, forwarded []string, prompt string) []string { + return forwardArgv(path, forwarded, prompt) +} + +func sandboxLaunchArgv(path string, forwarded []string, prompt string) []string { + return forwardArgv(path, forwarded, prompt) +} + +func generatedLaunchArgv(path, cfgPath string, forwarded []string, prompt string) []string { + args := append([]string{"--config", cfgPath}, stripProfileArgs(forwarded)...) + out := append([]string{path}, args...) + if prompt != "" { + out = append(out, prompt) + } + return out +} + +func resolveLaunchPath(envName string, fallbacks []string) (string, error) { + if configured := os.Getenv(envName); configured != "" { + return exec.LookPath(configured) + } + var err error + for _, fallback := range fallbacks { + var path string + if path, err = exec.LookPath(fallback); err == nil { + return path, nil + } + } + if err == nil { + err = errors.New("no launcher configured") + } + return "", err +} + +func runChild(path string, argv, env []string) error { + cmd := exec.Command(path, argv[1:]...) + cmd.Args = argv + cmd.Env = env + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +func childStatus(err error) int { + if err == nil { + return 0 + } + var exitErr *exec.ExitError + if errors.As(err, &exitErr) && exitErr.ExitCode() >= 0 { + return exitErr.ExitCode() + } + return 1 +} + +func runSandbox(envName string, fallbacks []string, prompt string) int { + path, err := resolveLaunchPath(envName, fallbacks) + if err != nil { + fmt.Fprintln(os.Stderr, "code: sandbox not found:", err) + return 1 + } + err = runChild(path, sandboxLaunchArgv(path, os.Args[1:], prompt), withoutAuthEnv(os.Environ())) + if err != nil { + fmt.Fprintln(os.Stderr, "code: sandbox:", err) + } + return childStatus(err) +} + +func runTrusted(envName string, fallbacks []string, + argv func(string, []string, string) []string, prompt string, + broker brokerConfig, selections accountSelectionState) int { + disabled := selections.CurrentDisabled() + path, err := resolveLaunchPath(envName, fallbacks) + if err != nil { + fmt.Fprintln(os.Stderr, "code: trusted launcher not found:", err) + return 1 + } + accounts, err := loadAccounts(broker) + if err != nil { + fmt.Fprintln(os.Stderr, "code: account snapshot unavailable; refusing unrestricted launch:", err) + return 1 + } + accountPoolPath, cleanup, err := writeAccountPool(accounts, disabled) + if err != nil { + fmt.Fprintln(os.Stderr, "code: account pool unavailable; refusing unrestricted launch:", err) + return 1 + } + defer cleanup() + childEnv := withAuthEnv(os.Environ(), broker, accountPoolPath) + err = runChild(path, argv(path, os.Args[1:], prompt), childEnv) + if err != nil { + fmt.Fprintln(os.Stderr, "code: trusted child:", err) + } + return childStatus(err) +} + +// launchGenerated keeps both immutable launch inputs alive only for the child. +func launchGenerated(cfg, prompt string, broker brokerConfig, selections accountSelectionState) int { + tmp, err := os.CreateTemp("", "code-gen-*.yml") + if err != nil { + fmt.Fprintln(os.Stderr, "code:", err) + return 1 + } + cfgPath := tmp.Name() + defer os.Remove(cfgPath) + if _, err = tmp.WriteString(cfg); err == nil { + err = tmp.Close() + } else { + _ = tmp.Close() + } + if err != nil { + fmt.Fprintln(os.Stderr, "code: generated config:", err) + return 1 + } + return runTrusted("CODE_OMP", []string{"omp"}, func(path string, forwarded []string, prompt string) []string { + return generatedLaunchArgv(path, cfgPath, forwarded, prompt) + }, prompt, broker, selections) +} diff --git a/layout.go b/layout.go new file mode 100644 index 0000000..1e681d8 --- /dev/null +++ b/layout.go @@ -0,0 +1,276 @@ +package main + +import ( + "github.com/charmbracelet/lipgloss" +) + +// ── model ──────────────────────────────────────────────────────────────────── +// layout modes, chosen from the terminal size (unless the user collapses): +// +// split — wide: the focused list on the left, routing preview on the +// right, and Usage spanning the full bottom width +// medium — generator-dominant: the list full width on top (primary), then +// Usage and Routing side by side in a secondary row — Usage's +// provider groups stacked vertically inside its measured left +// column, Routing on the right (and taking the whole row while +// ‹s› hides Usage) +// collapsed — narrow/short or ‹p›: one full-width panel at a time (list, or +// routing w/ showResult) — the Generator stays usable instead of +// compressing every section into an unreadable split +const ( + modeSplit = iota + modeMedium + modeCollapsed +) + +// size classes behind mode(): derived from terminal cells and the measured +// rendered minima of each section (atyrode/dotfiles#197) — never from pixels or a hard-coded +// screenshot width. +const ( + sizeWide = iota + sizeMedium + sizeNarrow +) + +// gut is the left gutter every panel shares, so the whole UI hangs off one +// consistent margin instead of a ragged mix of flush-left and indented rows. +// topGap is the matching vertical breathing room above the section tabs. +// headRows counts the section head (tabs + blank separator) above a list body. +const ( + gut = 2 + topGap = 1 + headRows = 2 + // the launch footer pinned under the list: blank + cost + speed + blank + + // the ⏎ launch action on its own visually separated row. + launchFooterRows = 5 + // routingMinW is the narrowest useful routing column: pane chrome plus room + // for a lead chain — below this a side-by-side routing panel stops earning + // its keep. + routingMinW = 33 + // secSepW is the one-cell border column between medium's adjacent + // secondary panes (Usage left, Routing right) — visible separation, same + // stroke as the wide layout's routing pane border. + secSepW = 1 + // genMinRows is the fewest facet rows the generator list may be windowed to + // before the layout must shed secondary sections instead of compressing it. + genMinRows = 4 + // minRouteRows is the fewest routing viewport rows worth pinning chrome around. + minRouteRows = 4 +) + +// genColMinH is the generator column's minimum useful height: the pinned head, +// a windowed-but-usable slice of the facet list, and the pinned launch footer. +const genColMinH = headRows + genMinRows + launchFooterRows + +// genRowWidth is the width needed to render the widest generator facet row (all +// options) on a single line — the minimum for the left panel. +func (m model) genRowWidth() int { + max := 30 + for _, f := range m.facets { // widest over ALL facets, so width is lane-stable + w := 14 // ▸ + glyph + spaces + padded label + for _, v := range f.values { + w += len(v) + 4 + } + if w > max { + max = w + } + } + return max + 2 +} + +// sizeMode classifies the terminal into the wide / medium / narrow-short +// responsive classes. Widths compare against the measured generator row, +// routing, and usage-column minima; heights against the chrome each composition +// pins on screen — breakpoints track content needs, not screenshot numbers. +func (m model) sizeMode() int { + if m.w >= m.genRowWidth()+routingMinW && m.h >= m.wideMinH() { + return sizeWide + } + if m.w >= m.mediumMinW() && m.h >= m.mediumMinH() { + return sizeMedium + } + return sizeNarrow +} + +func (m model) mode() int { + if m.collapse { + return modeCollapsed + } + switch m.sizeMode() { + case sizeWide: + return modeSplit + case sizeMedium: + return modeMedium + default: + return modeCollapsed + } +} + +// wideMinH is the least height at which the wide composition stays readable: +// a usable generator column above the full-width Usage footer. Shorter than +// this, keeping every section visible would compress them all — shed instead. +func (m model) wideMinH() int { + return topGap + genColMinH + m.footerH(!m.hideUsage) +} + +// mediumMinH stacks the generator over the secondary Routing+Usage row (at its +// measured minimum) with Usage out of the footer. +func (m model) mediumMinH() int { + return topGap + genColMinH + 1 + m.secondaryMinH() + m.footerH(false) +} + +// mediumMinW: the secondary row must seat a useful routing viewport beside the +// measured usage column — plus the one-cell separator between them — without +// clipping either. +func (m model) mediumMinW() int { + if m.hideUsage { + return routingMinW + } + return routingMinW + secSepW + m.usageColW() +} + +// footerH measures the pinned footer for a composition directly from its parts +// — mode selection depends on it, so it must not consult the mode itself. +func (m model) footerH(withUsage bool) int { + h := 1 + lipgloss.Height(padLeft(m.help.View(keys), gut)) + if withUsage { + if p := m.usagePanel(); p != "" { + h += 1 + lipgloss.Height(p) + } + } + return h +} + +// bodyH is the height available above the pinned footer. +func (m model) bodyH() int { + h := m.h - lipgloss.Height(m.footer()) + if h < 1 { + h = 1 + } + return h +} + +// contentH is the height the panels actually fill — bodyH minus the top gap. +func (m model) contentH() int { + h := m.bodyH() - topGap + if h < 1 { + h = 1 + } + return h +} + +// bodyLines renders the generator's scrolling facet list and reports the cursor's +// line index within it. +func (m model) bodyLines() ([]string, int) { + return m.genLines() +} + +// launchFooter is the cost/speed meters for the current facet combo plus the +// enter-to-launch call to action — so the generator always shows what the choice +// costs, how fast it is, and how Enter will launch it. Enter always launches +// the generated profile for the current facets (the untouched default combo is +// a profile like any other); m runs omp-managed on the managed defaults with +// no overlay, and the sandbox (u) key is always offered. A selected runtime +// target gets the same footer shape with an honest summary instead of meters: +// its tokens are free and code has no measurement to quote. +func (m model) launchFooter() []string { + acc := lipgloss.NewStyle().Foreground(lipgloss.Color(m.accent())).Bold(true).Render(" ⏎ launch") + if _, local := m.selectedRuntime(); local { + return []string{ + "", + stDim.Render(" cost free · local inference"), + "", + "", + acc, + } + } + cs, ss := m.costScore(), m.speedScore() + return []string{ + "", + m.meter("cost", "$", meterRamp[cs], cs), // dear → red, cheap → green + m.meter("speed", "»", meterRamp[6-ss], ss), // fast → green, slow → red + "", // breathing room between the meters and the action row + acc + stDim.Render(" m managed omp · u sandbox"), + } +} + +// mediumSplit gives the Routing+Usage row only its measured minimum, then lets +// the primary Generator absorb every remaining row. The medium height threshold +// guarantees both sections fit; taller terminals therefore expand Generator +// instead of leaving slack below the compact secondary content. +func (m model) mediumSplit(bodyH int) (genH, secH int) { + secH = m.secondaryMinH() + genH = bodyH - 1 - secH + if genH < genColMinH { + genH = genColMinH + secH = bodyH - 1 - genH + } + return +} + +// previewDims returns the preview viewport's inner (width, height) for the mode. +// The full-width modes reserve the shared gutter; split leaves the preview's own +// border + padding to do the breathing. Every mode reserves prevChromeRows for +// the pinned pill head above and fallback-display hint below the viewport. +func (m model) previewDims() (int, int) { + bodyH := m.contentH() + switch m.mode() { + case modeCollapsed: + return m.w - gut, bodyH - prevChromeRows + case modeMedium: + _, secH := m.mediumSplit(bodyH) + return m.routingColW() - gut, secH - prevChromeRows + default: // split — the pane draws a border + prevPadL inside its width, so the + // viewport (what renderRoute wraps to) gets the inner text area, not the box. + return m.w - m.listW() - 3 - prevPadL, bodyH - prevChromeRows + } +} + +// usageColShare is the medium secondary row's Usage width: Usage is the +// favored pane — it takes the larger 3/5 proportional share of the row so its +// bars and notes keep breathing room, never dropping below its measured +// stacked minimum. Routing is the pane that shrinks as Usage grows, floored +// at routingMinW (the medium width threshold guarantees both floors seat). +func (m model) usageColShare() int { + avail := m.w - secSepW + uw := avail * 3 / 5 + if min := m.usageColW(); uw < min { + uw = min + } + if avail-uw < routingMinW { + uw = avail - routingMinW + } + return uw +} + +// routingColW is the medium secondary row's routing share: whatever Usage's +// favored share (and the separator between the panes) leaves free. +func (m model) routingColW() int { + w := m.w + if !m.hideUsage { + w -= m.usageColShare() + secSepW + } + if w < routingMinW { + w = routingMinW + } + return w +} + +func (m model) listW() int { + // wide enough for the generator options on one line; capped so a very wide + // terminal doesn't stretch the list needlessly. + w := m.genRowWidth() + if w > m.w-33 { + w = m.w - 33 + } + // The ox lanes widen the lane row past this function's old 80-cell + // aesthetic cap; a wider list beats clipping dial options mid-value. + if w > 116 { + w = 116 + } + return w +} + +// Usage bars naturally measure ten cells, then grow or shrink to the width +// assigned by their row group. Styling is deliberately applied after the +// display-cell geometry is settled, so ANSI sequences never enter the math. diff --git a/main.go b/main.go index 9fa8182..d4f6b26 100644 --- a/main.go +++ b/main.go @@ -11,3588 +11,16 @@ package main import ( - "bufio" - "encoding/json" - "errors" "fmt" - "io" - "math" - "net/http" "os" "os/exec" - "regexp" - "sort" - "strconv" "strings" - "time" - "unicode" clikit "github.com/atyrode/cli-kit" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/spinner" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" ) -// ── keybindings (drive both input handling and the bubbles/help footer) ─────── -type keyMap struct { - Move, Change, Reset, Depth, Refresh, Manager, Collapse, Usage, Launch, Managed, Untrusted, Help, Quit key.Binding -} - -// ShortHelp is a static single-line stand-in used only when measuring the -// footer height for mode selection (which would otherwise recurse through the -// state-derived compact help). The rendered compact line comes from -// contextHelp, which derives its bindings from the live model state (atyrode/dotfiles#198). -func (k keyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Move, k.Change, k.Reset, k.Help, k.Quit} -} -func (k keyMap) FullHelp() [][]key.Binding { - return [][]key.Binding{ - {k.Move, k.Change, k.Reset}, - {k.Depth, k.Refresh, k.Manager, k.Collapse, k.Usage}, - {k.Launch, k.Managed, k.Untrusted, k.Help, k.Quit}, - } -} - -var keys = keyMap{ - Move: key.NewBinding(key.WithKeys("up", "down", "j", "k"), key.WithHelp("↑↓", "move")), - Change: key.NewBinding(key.WithKeys("left", "right", "h", "l"), key.WithHelp("←→", "change")), - Reset: key.NewBinding(key.WithKeys("d"), key.WithHelp("d", gReset+" defaults")), - Depth: key.NewBinding(key.WithKeys("f"), key.WithHelp("f", "primary ⇄ full chains")), - Refresh: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "refresh usage")), - Manager: key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "manage accounts")), - Collapse: key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "show/hide routing")), - Usage: key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "show/hide usage")), - Launch: key.NewBinding(key.WithKeys("enter"), key.WithHelp("⏎", "launch")), - Managed: key.NewBinding(key.WithKeys("m"), key.WithHelp("m", "managed omp")), - Untrusted: key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "sandbox")), - Help: key.NewBinding(key.WithKeys("?"), key.WithHelp("?", "more")), - Quit: key.NewBinding(key.WithKeys("q", "esc", "ctrl+c"), key.WithHelp("q", "quit")), -} - -// defaultSel returns a fresh copy of the generator's default facet selection — -// used both to seed the model and to restore it via the reset key. -func defaultSel() map[string]string { - return map[string]string{"lane": "mixed", "model": "smart", "thinking": "medium", "advisor": "glance", "spark": "on", "fable": "off", "main": "off", "fast": "off"} -} - -// ── palette ────────────────────────────────────────────────────────────────── -// The palette, glyphs, and styles now live in the shared cli-kit; these are -// ergonomic local aliases so the rest of the file reads unchanged. cli-kit is -// the single source both `code` and `atyrode` build on. -const ( - cAcc = clikit.CAcc - cBord = clikit.CBord - cHead = clikit.CHead - cSelBg = clikit.CSelBg - cGreen = clikit.CGreen -) - -const ( - gWarn = clikit.GWarn - gReset = clikit.GReset -) - -var ( - meterRamp = clikit.MeterRamp - - stDim = clikit.StDim - stHead = clikit.StHead - stWarn = clikit.StWarn - stBrk = clikit.StBrk - stStruck = clikit.StStruck - - // stKey renders an inline key cue (r, a, s, p) — visually secondary but - // readable against the background, per the section-chrome convention. - stKey = lipgloss.NewStyle().Foreground(lipgloss.Color(cHead)) - - // Title-local hotkey cues (d · defaults, p · hide, s · hide) are quieter - // than the footer help: terminals have no portable alpha, so these are - // dedicated pre-blended tokens — CHead/CDim mixed ~40% toward the app's - // dark backdrop — applied to the whole cue, key included. Pre-blending is - // used instead of ANSI faint because faint's dimming factor varies wildly - // across terminals (and would double-dim already-muted text). Footer - // recovery cues keep the brighter help styles for readability. - stCueKey = lipgloss.NewStyle().Foreground(lipgloss.Color("#646b76")) // CHead → backdrop - stCue = lipgloss.NewStyle().Foreground(lipgloss.Color("#4f5768")) // CDim → backdrop - - // layout + meter primitives now live in cli-kit - padLeft = clikit.PadLeft - pad = clikit.Pad - windowList = clikit.WindowList - - // Provider-qualified ids: bare catalog ids today (gpt-…, claude-…) plus - // slash-scoped ones (stealth/ox-alpha, local-qwen/qwen3.8-27b). The level - // suffix with its colon is what keeps prose out; the word boundary keeps - // "maxed" from reading as a model. - modelRe = regexp.MustCompile(`([a-z][a-z0-9._/-]*):(minimal|low|medium|high|xhigh|max)\b`) -) - -// ── colourisers ────────────────────────────────────────────────────────────── -func lvl(s string) int { - switch s { - case "minimal": - return 0 - case "low": - return 1 - case "medium": - return 2 - case "high": - return 3 - case "xhigh": - return 4 - } - return 5 -} - -func shortModel(name string) string { - if name == "gpt-5.4" { - return name - } - // Slash-scoped ids display without their provider path, and keep their - // full model part — the vendor's own naming is the recognizable bit. - if i := strings.LastIndexByte(name, '/'); i >= 0 { - name = name[i+1:] - if !strings.HasPrefix(name, "claude") { - return name - } - } - p := strings.Split(name, "-") - if strings.HasPrefix(name, "claude") && len(p) > 1 { - return p[1] - } - return p[len(p)-1] -} - -func clampByte(x float64) int { - v := int(x) - if v > 255 { - return 255 - } - if v < 0 { - return 0 - } - return v -} - -func paintModel(tok string) string { - i := strings.LastIndex(tok, ":") - name, level := tok[:i], tok[i+1:] - var br, bg, bb float64 - switch { - case strings.HasPrefix(tok, "gpt"): - br, bg, bb = 110, 170, 240 - case strings.Contains(tok, "ox-alpha"), strings.Contains(tok, "local-qwen"): - // Free/local pools read green — the same family as their lane accents. - br, bg, bb = 96, 211, 150 - default: - br, bg, bb = 240, 160, 105 - } - f := 0.60 + float64(lvl(level))*0.088 - col := lipgloss.Color(fmt.Sprintf("#%02x%02x%02x", clampByte(br*f), clampByte(bg*f), clampByte(bb*f))) - return lipgloss.NewStyle().Foreground(col).Render(shortModel(name) + ":" + level) -} - -func colorizeRoute(line string) string { return modelRe.ReplaceAllStringFunc(line, paintModel) } - -// bucketOf guesses a quota bucket from a model name. It is the fallback for -// catalogs that declare no bucket column, and the only resolver for the bare -// facet names ("fable", "spark") the suggest box asks about — prefer -// model.bucketFor wherever a receiver is in reach. -func bucketOf(model string) string { - m := model - if i := strings.IndexByte(m, ':'); i >= 0 { - m = m[:i] - } - // Provider-scoped ids outside the two subscription pools (OpenRouter, - // local runtimes) have no quota window code knows about. An empty bucket - // never reads as down, which is exactly right for a free or local model. - if strings.Contains(m, "/") { - return "" - } - switch { - case strings.Contains(m, "fable"): - return "claude-fable" - case strings.Contains(m, "spark"): - return "codex-spark" - case strings.Contains(m, "claude"), strings.Contains(m, "sonnet"), - strings.Contains(m, "haiku"), strings.Contains(m, "opus"): - return "claude-main" - } - return "codex-main" -} - -// bucketFor resolves a routing token's quota bucket from the catalog, falling -// back to the name guess only when the catalog declares none. The catalog wins -// because names are not a taxonomy: claude-mythos-5 sits in omp's catalog at -// claude-fable-5's price yet 404s on this account, and every model omp adds -// would otherwise need one more substring arm here before it could be struck -// through correctly. -func (m model) bucketFor(name string) string { - id := name - if i := strings.IndexByte(id, ':'); i >= 0 { - id = id[:i] - } - if f, ok := m.facts[id]; ok && f.bucket != "" { - return f.bucket - } - return bucketOf(id) -} - -// ── data ───────────────────────────────────────────────────────────────────── -// loadBlocks parses a generated page into name -> role rows. -func loadBlocks(path string) map[string][]string { - blocks := map[string][]string{} - f, err := os.Open(path) - if err != nil { - return blocks - } - defer f.Close() - sc := bufio.NewScanner(f) - sc.Buffer(make([]byte, 1024*128), 1024*128) - var cur string - for sc.Scan() { - line := sc.Text() - if line == "" { - cur = "" - continue - } - if line[0] != ' ' { - if fs := strings.Fields(line); len(fs) > 0 { - cur = fs[0] - blocks[cur] = nil - } - continue - } - if cur != "" { - blocks[cur] = append(blocks[cur], line) - } - } - return blocks -} - -// ── usage + availability ───────────────────────────────────────────────────── -type usageWin struct { - label string - pct int - tier string - secs int64 // seconds until reset (relative) - dur int64 // window length in seconds - prov string - stale bool // retained from the last successful fetch after a refresh omitted this window - missing bool // never observed: rendered as a deterministic placeholder row - observed int64 // Unix timestamp of the last real value; retained across cache fallback -} - -// resetCredits tracks OpenAI reset credits: how many are currently available -// and the seconds until each available credit expires (relative, unsorted). -type resetCredits struct { - avail int - exp []int64 -} - -type availability struct { - bucket map[string]string // bucket -> "ok" | "maxed" | "unauthed" - reset map[string]int64 - wins []usageWin - credits resetCredits - accountCredits map[accountKey]resetCredits - ok bool - accounts map[string][]account - accountUsage map[accountKey][]usageWin - accountsOK bool - selectionApplied bool - accountsStale bool -} - -func fetchBrokerUsage(broker brokerConfig) ([]byte, error) { - if broker.URL == "" || broker.Token == "" { - return nil, errors.New("central auth broker is not configured") - } - req, err := http.NewRequest(http.MethodGet, strings.TrimRight(broker.URL, "/")+"/v1/usage", nil) - if err != nil { - return nil, err - } - req.Header.Set("Authorization", "Bearer "+broker.Token) - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - _, _ = io.Copy(io.Discard, resp.Body) - return nil, fmt.Errorf("usage endpoint returned %s", resp.Status) - } - return io.ReadAll(resp.Body) -} - -type usageCacheWin struct { - Label string `json:"label"` - Pct int `json:"pct"` - Tier string `json:"tier,omitempty"` - ResetsAt int64 `json:"resetsAt"` - Dur int64 `json:"dur"` - Provider string `json:"provider"` - Observed int64 `json:"observed"` -} - -type usageCacheAccount struct { - Provider string `json:"provider"` - IdentityKey string `json:"identityKey"` - Wins []usageCacheWin `json:"wins"` -} - -type usageCacheFile struct { - SavedAt int64 `json:"savedAt"` - Accounts map[string][]account `json:"accounts"` - Usage []usageCacheAccount `json:"usage"` -} - -func emptyAvailability() availability { - return availability{ - bucket: map[string]string{}, reset: map[string]int64{}, - accounts: map[string][]account{}, accountUsage: map[accountKey][]usageWin{}, - accountCredits: map[accountKey]resetCredits{}, - } -} - -// parseAvailability associates broker usage with stable identities from the -// same snapshot. observedAt is the cache observation time; reset countdowns -// remain relative to now because the broker payload stores absolute deadlines. -func parseAvailability(accounts map[string][]account, accountsOK bool, out []byte, observedAt int64) availability { - a := emptyAvailability() - a.accounts, a.accountsOK = accounts, accountsOK - type limit struct { - Label string `json:"label"` - Scope struct { - Tier string `json:"tier"` - } `json:"scope"` - Amount struct { - UsedFraction float64 `json:"usedFraction"` - } `json:"amount"` - Window struct { - ResetsAt int64 `json:"resetsAt"` - DurationMs int64 `json:"durationMs"` - } `json:"window"` - } - var doc struct { - Reports []struct { - Provider string `json:"provider"` - Email string `json:"email"` - AccountID string `json:"accountId"` - Metadata struct { - Email string `json:"email"` - AccountID string `json:"accountId"` - } `json:"metadata"` - Limits []limit `json:"limits"` - ResetCredits struct { - AvailableCount int `json:"availableCount"` - Credits []struct { - ExpiresAt string `json:"expiresAt"` - Status string `json:"status"` - } `json:"credits"` - } `json:"resetCredits"` - } `json:"reports"` - } - if len(out) == 0 || json.Unmarshal(out, &doc) != nil { - return a - } - a.ok = true - provSeen := map[string]bool{} - now := time.Now().Unix() - if observedAt <= 0 { - observedAt = now - } - for _, r := range doc.Reports { - provSeen[r.Provider] = true - reportWins := make([]usageWin, 0, len(r.Limits)) - for _, l := range r.Limits { - pct := int(l.Amount.UsedFraction*100 + 0.5) - win := usageWin{label: l.Label, pct: pct, tier: l.Scope.Tier, - secs: l.Window.ResetsAt/1000 - now, dur: l.Window.DurationMs / 1000, - prov: r.Provider, observed: observedAt} - reportWins = append(reportWins, win) - a.wins = append(a.wins, win) - bkt := bucketForProviderTier(r.Provider, l.Scope.Tier) - if bkt == "" { - continue - } - if pct >= 100 { - a.bucket[bkt] = "maxed" - a.reset[bkt] = l.Window.ResetsAt/1000 - now - } else if a.bucket[bkt] != "maxed" { - a.bucket[bkt] = "ok" - } - } - email, accountID := r.Metadata.Email, r.Metadata.AccountID - if email == "" { - email = r.Email - } - if accountID == "" { - accountID = r.AccountID - } - var matchedKey accountKey - matched := false - for _, acct := range a.accounts[r.Provider] { - if (email != "" && acct.Email != "" && strings.EqualFold(email, acct.Email)) || - (accountID != "" && accountID == acct.IdentityKey) { - matchedKey = accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey} - a.accountUsage[matchedKey] = append(a.accountUsage[matchedKey], reportWins...) - matched = true - break - } - } - if r.Provider == "openai-codex" { - credits := resetCredits{avail: r.ResetCredits.AvailableCount} - for _, c := range r.ResetCredits.Credits { - if c.Status != "available" { - continue - } - if t, err := time.Parse(time.RFC3339, c.ExpiresAt); err == nil { - credits.exp = append(credits.exp, t.Unix()-now) - } - } - a.credits.avail += credits.avail - a.credits.exp = append(a.credits.exp, credits.exp...) - if matched { - attributed := a.accountCredits[matchedKey] - attributed.avail += credits.avail - attributed.exp = append(attributed.exp, credits.exp...) - a.accountCredits[matchedKey] = attributed - } - } - } - for _, b := range []string{"codex-main", "codex-spark", "claude-main", "claude-fable"} { - p := "openai-codex" - if strings.HasPrefix(b, "claude") { - p = "anthropic" - } - if !provSeen[p] { - a.bucket[b] = "unauthed" - } else if _, ok := a.bucket[b]; !ok { - a.bucket[b] = "ok" - } - } - return a -} - -// loadAvailability reads one central snapshot and one aggregate usage report. -func loadAvailability(broker brokerConfig) availability { - accounts, err := loadAccounts(broker) - accountsOK := err == nil - if !accountsOK { - accounts = map[string][]account{} - } - out, err := fetchBrokerUsage(broker) - if err != nil { - out = nil - } - return parseAvailability(accounts, accountsOK, out, 0) -} - -func loadUsageCache(path string) availability { - a := emptyAvailability() - if path == "" { - return a - } - body, err := os.ReadFile(path) - if err != nil { - return a - } - var cached usageCacheFile - if json.Unmarshal(body, &cached) != nil || cached.SavedAt <= 0 || len(cached.Usage) == 0 { - return a - } - a.accounts, a.accountsOK, a.ok = cached.Accounts, true, true - provSeen := map[string]bool{} - for _, entry := range cached.Usage { - key := accountKey{Provider: entry.Provider, IdentityKey: entry.IdentityKey} - for _, cachedWin := range entry.Wins { - win := usageWin{ - label: cachedWin.Label, pct: cachedWin.Pct, tier: cachedWin.Tier, - secs: cachedWin.ResetsAt - time.Now().Unix(), dur: cachedWin.Dur, prov: cachedWin.Provider, - observed: cachedWin.Observed, stale: true, - } - a.accountUsage[key] = append(a.accountUsage[key], win) - a.wins = append(a.wins, win) - provSeen[win.prov] = true - bucket := bucketForProviderTier(win.prov, win.tier) - if bucket == "" { - continue - } - if win.pct >= 100 { - a.bucket[bucket], a.reset[bucket] = "maxed", win.secs - } else if a.bucket[bucket] != "maxed" { - a.bucket[bucket] = "ok" - } - } - } - for _, bucket := range []string{"codex-main", "codex-spark", "claude-main", "claude-fable"} { - provider := openAIProvider - if strings.HasPrefix(bucket, "claude") { - provider = anthropicProvider - } - if !provSeen[provider] { - a.bucket[bucket] = "unauthed" - } else if _, ok := a.bucket[bucket]; !ok { - a.bucket[bucket] = "ok" - } - } - return a -} - -func saveUsageCache(path string, a availability) { - if path == "" || !a.ok { - return - } - now := time.Now().Unix() - cached := usageCacheFile{SavedAt: now, Accounts: a.accounts} - for key, wins := range a.accountUsage { - entry := usageCacheAccount{Provider: key.Provider, IdentityKey: key.IdentityKey} - for _, win := range wins { - if win.missing { - continue - } - observed := win.observed - if observed <= 0 { - observed = now - } - entry.Wins = append(entry.Wins, usageCacheWin{ - Label: win.label, Pct: win.pct, Tier: win.tier, - ResetsAt: observed + win.secs, Dur: win.dur, - Provider: win.prov, Observed: observed, - }) - } - if len(entry.Wins) > 0 { - cached.Usage = append(cached.Usage, entry) - } - } - if len(cached.Usage) == 0 { - return - } - sort.Slice(cached.Usage, func(i, j int) bool { - if cached.Usage[i].Provider != cached.Usage[j].Provider { - return cached.Usage[i].Provider < cached.Usage[j].Provider - } - return cached.Usage[i].IdentityKey < cached.Usage[j].IdentityKey - }) - body, err := json.Marshal(cached) - if err != nil { - return - } - body = append(body, '\n') - _ = atomicPrivateWrite(path, body) -} - -func bucketForProviderTier(prov, tier string) string { - if prov == "openai-codex" { - if tier == "spark" { - return "codex-spark" - } - if tier == "" || tier == "-" { - return "codex-main" - } - } - if prov == "anthropic" { - if tier == "fable" { - return "claude-fable" - } - if tier == "" || tier == "-" { - return "claude-main" - } - } - return "" -} - -func (a availability) down(bucket string) bool { - return a.bucket[bucket] == "maxed" || a.bucket[bucket] == "unauthed" -} - -// reconcileUsage folds a freshly fetched availability over the one currently -// shown, so a flaky upstream never wipes known-good data. It returns the -// availability to display plus whether the whole panel is stale: -// -// - a total fetch failure after any prior success keeps the previous -// availability wholesale and reports it stale — the control row shows a -// refresh-failed warning instead of dropping to the unauthenticated error; -// - a successful payload that omits an account's usage retains that -// account's last observed rows, visibly marked stale with their age; -// - a successful Anthropic payload that only omits the flaky Fable window -// retains the last Fable row, along with its bucket/reset routing state; -// - a successful Anthropic payload with no Fable window ever observed -// appends a deterministic unavailable placeholder, so the datum appearing -// on a later refresh never pops the panel geometry. -// -// Fresh values always win; nothing is fabricated — retained rows are visibly -// marked stale and placeholders carry no numbers. -func reconcileUsage(prev, next availability) (availability, bool) { - if !next.ok { - if prev.ok { - return prev, true - } - return next, false - } - if !next.accountsOK && prev.accountsOK { - next.accounts, next.accountsOK, next.accountsStale = prev.accounts, true, true - } - next.accountUsage = reconcileAccountUsage(prev.accountUsage, next.accountUsage, next.accounts) - hasClaude, hasFable := false, false - for _, w := range next.wins { - if w.prov != "anthropic" { - continue - } - hasClaude = true - if w.tier == "fable" { - hasFable = true - } - } - if !hasClaude || hasFable { - return next, false - } - for _, w := range prev.wins { - if w.prov == "anthropic" && w.tier == "fable" && !w.missing { - w.stale = true - next.wins = append(next.wins, w) - // Carry the bucket/reset state observed with the retained window: - // loadAvailability defaults an unseen bucket to "ok", which would - // route onto a fable the last real datum said was maxed. - if st, ok := prev.bucket["claude-fable"]; ok { - next.bucket["claude-fable"] = st - } - if r, ok := prev.reset["claude-fable"]; ok { - next.reset["claude-fable"] = r - } - return next, false - } - } - next.wins = append(next.wins, fablePlaceholder) - return next, false -} - -func reconcileAccountUsage(prev, next map[accountKey][]usageWin, accounts map[string][]account) map[accountKey][]usageWin { - if next == nil { - next = map[accountKey][]usageWin{} - } - active := map[accountKey]bool{} - for _, providerAccounts := range accounts { - for _, acct := range providerAccounts { - active[accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey}] = true - } - } - if len(active) == 0 { - for key := range next { - active[key] = true - } - } - for key := range active { - wins := next[key] - hasFresh := false - for _, w := range wins { - if !w.missing { - hasFresh = true - break - } - } - if !hasFresh { - retained := make([]usageWin, 0, len(prev[key])) - for _, w := range prev[key] { - if w.missing { - continue - } - w.stale = true - retained = append(retained, w) - } - if len(retained) > 0 { - next[key] = retained - } - continue - } - if key.Provider != "anthropic" { - continue - } - hasClaude, hasFable := false, false - for _, w := range wins { - if w.prov != "anthropic" { - continue - } - if !w.missing { - hasClaude = true - } - if w.tier == "fable" { - hasFable = true - } - } - if !hasClaude || hasFable { - continue - } - retained := false - for _, w := range prev[key] { - if w.prov == "anthropic" && w.tier == "fable" && !w.missing { - w.stale = true - next[key] = append(next[key], w) - retained = true - break - } - } - if !retained { - next[key] = append(next[key], fablePlaceholder) - } - } - return next -} - -// fablePlaceholder is the never-observed fable window's deterministic -// stand-in: the real payload label (so shortWin renders the same "7d fable" -// tag) and window length, with no usage numbers to fabricate. -var fablePlaceholder = usageWin{label: "Claude 7 Day (Fable)", tier: "fable", dur: 7 * 24 * 3600, prov: "anthropic", missing: true} - -type usageGroupKey struct { - prov string - tier string - dur int64 - label string -} - -type usageGroup struct { - win usageWin - count int64 - pctSum int64 - secsSum int64 - observed int64 -} - -func knownUsageWindow(w usageWin) bool { - label := shortWin(w.label) - switch bucketForProviderTier(w.prov, w.tier) { - case "codex-main", "claude-main": - return label == "5h" || label == "7d" - case "codex-spark": - return label == "5h spark" || label == "7d spark" - case "claude-fable": - return label == "7d fable" - default: - return false - } -} - -// selectedAvailability derives account-sensitive usage and routing availability -// solely from enabled broker identities. Unmatched reports never enter this seam. -func selectedAvailability(a availability, disabled map[accountKey]bool) availability { - selected := a - selected.selectionApplied = true - selected.accounts = map[string][]account{} - selected.accountUsage = map[accountKey][]usageWin{} - selected.accountCredits = map[accountKey]resetCredits{} - selected.bucket = map[string]string{} - selected.reset = map[string]int64{} - selected.wins = nil - selected.credits = resetCredits{} - - enabledProviders := map[string]bool{} - groups := map[usageGroupKey]*usageGroup{} - missing := map[usageGroupKey]usageWin{} - var groupOrder []usageGroupKey - for prov, accounts := range a.accounts { - for _, acct := range accounts { - key := accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey} - if disabled[key] { - continue - } - selected.accounts[prov] = append(selected.accounts[prov], acct) - enabledProviders[acct.Provider] = true - wins := a.accountUsage[key] - if credits, ok := a.accountCredits[key]; ok { - selected.accountCredits[key] = credits - selected.credits.avail += credits.avail - selected.credits.exp = append(selected.credits.exp, credits.exp...) - } - for _, win := range wins { - if win.prov != acct.Provider || !knownUsageWindow(win) { - continue - } - selected.accountUsage[key] = append(selected.accountUsage[key], win) - groupKey := usageGroupKey{prov: win.prov, tier: win.tier, dur: win.dur, label: shortWin(win.label)} - if win.missing { - if _, ok := missing[groupKey]; !ok { - placeholder := win - placeholder.label = groupKey.label - missing[groupKey] = placeholder - groupOrder = append(groupOrder, groupKey) - } - continue - } - group := groups[groupKey] - if group == nil { - aggregate := win - aggregate.label = groupKey.label - group = &usageGroup{win: aggregate} - groups[groupKey] = group - groupOrder = append(groupOrder, groupKey) - } - pct, secs := int64(win.pct), win.secs - if pct < 0 { - pct = 0 - } - if secs < 0 { - secs = 0 - } - group.count++ - group.pctSum += pct - group.secsSum += secs - group.win.stale = group.win.stale || win.stale - if win.observed > 0 && (group.observed == 0 || win.observed < group.observed) { - group.observed = win.observed - } - } - } - } - seen := map[usageGroupKey]bool{} - for _, key := range groupOrder { - if seen[key] { - continue - } - seen[key] = true - if group := groups[key]; group != nil { - group.win.pct = int((group.pctSum + group.count/2) / group.count) - group.win.secs = (group.secsSum + group.count/2) / group.count - group.win.observed = group.observed - selected.wins = append(selected.wins, group.win) - } else { - selected.wins = append(selected.wins, missing[key]) - } - } - for _, bucket := range []string{"codex-main", "codex-spark", "claude-main", "claude-fable"} { - provider := "openai-codex" - if strings.HasPrefix(bucket, "claude") { - provider = "anthropic" - } - if enabledProviders[provider] { - selected.bucket[bucket] = "ok" - } else { - selected.bucket[bucket] = "unauthed" - } - } - for _, win := range selected.wins { - bucket := bucketForProviderTier(win.prov, win.tier) - if bucket == "" || win.missing || win.pct < 100 { - continue - } - selected.bucket[bucket] = "maxed" - // Multiple quota windows can constrain one route. The route becomes - // usable only after the last maxed aggregate resets, so retain the - // longest selected reset rather than whichever account/map came last. - if win.secs > selected.reset[bucket] { - selected.reset[bucket] = win.secs - } - } - return selected -} - -// ── routing render (depth: 0 lead · 1 full) ────────────────────────────────── -// renderRoute lays out each role's chain, wrapping cleanly at `width`: when a -// chain doesn't fit, it breaks after an arrow and the continuation is indented -// to align under the first model, so it reads as one hanging block rather than a -// ragged wrap. Down (maxed/unauthed) models are struck through — which bucket a -// model draws from is a catalog fact, hence the receiver. -func (m model) renderRoute(rows []string, depth int, a availability, width int) string { - if width < 24 { - width = 24 - } - arrow := stDim.Render(" → ") - var out []string - for _, r := range rows { - locs := modelRe.FindAllStringIndex(r, -1) - if len(locs) == 0 { // a note/meta line with no models — pass through - out = append(out, colorizeRoute(r)) - continue - } - label := r[:locs[0][0]] // role + its alignment padding, kept verbatim - labelW := lipgloss.Width(label) - indent := strings.Repeat(" ", labelW) - - type tok struct { - text string - down bool - } - var toks []tok - for _, loc := range locs { - mt := r[loc[0]:loc[1]] - toks = append(toks, tok{mt, a.ok && a.down(m.bucketFor(mt))}) - } - // how many to show: full → all; lead → primary, or up to the first live - // model when the lead is down (the one that actually runs). - last := len(toks) - 1 - if depth == 0 { - last = 0 - for i, t := range toks { - last = i - if !t.down { - break - } - } - } - // render a token as it displays (short name), returning the styled - // string and its display width — struck if the model is down. - render := func(t tok) (string, int) { - c := strings.LastIndexByte(t.text, ':') - short := shortModel(t.text[:c]) + t.text[c:] - if t.down { - s := stStruck.Render(short) - return s, lipgloss.Width(s) - } - s := colorizeRoute(t.text) // paintModel shortens + colours - return s, lipgloss.Width(s) - } - s0, w0 := render(toks[0]) - line, lineW := label+s0, labelW+w0 - for i := 1; i <= last; i++ { - si, wi := render(toks[i]) - // Reserve 2 cols for a trailing " →" whenever more models follow, so a - // break line's continuation arrow always fits within width instead of - // being clipped at the edge; the final model needs no such reserve. - budget := width - if i < last { - budget -= 2 - } - if lineW+3+wi > budget { // won't fit — break after a trailing arrow - out = append(out, line+stDim.Render(" →")) - line, lineW = indent+si, labelW+wi - } else { - line += arrow + si - lineW += 3 + wi - } - } - out = append(out, line) - } - return strings.Join(out, "\n") + "\n" -} - -// splitMeta peels the "thinking … · fallback … · advisor …" summary line off a -// routing block (if present), returning it trimmed plus the remaining role rows. -func splitMeta(rows []string) (string, []string) { - if len(rows) > 0 && strings.Contains(rows[0], "·") && !modelRe.MatchString(rows[0]) { - return strings.TrimSpace(rows[0]), rows[1:] - } - return "", rows -} - -// ── generator facets ───────────────────────────────────────────────────────── -type facet struct { - key string - values []string - glyph string -} - -func facetDefs(glyphs map[string]string) []facet { - return []facet{ - // The ox values are trimmed away by applyCatalog unless the catalog - // serves them — presence in models.yml is what makes them appear. - {"lane", []string{"gpt-only", "gpt-led", "mixed", "claude-led", "claude-only", "ox-only", "ox-led", "ox-lean"}, glyphs["lane"]}, - {"model", []string{"fast", "normal", "smart"}, glyphs["model"]}, - {"thinking", []string{"minimal", "low", "medium", "high", "xhigh", "max"}, glyphs["thinking"]}, - // advisor as a power/cost dial: a quick glance, a proper review, or a - // deep (expensive) audit — off spends nothing. - {"advisor", []string{"off", "glance", "review", "audit"}, glyphs["advisor"]}, - {"fast", []string{"on", "off"}, glyphs["fast"]}, - {"spark", []string{"on", "off"}, glyphs["spark"]}, - {"fable", []string{"on", "off"}, glyphs["fable"]}, - // fable-as-main: hand the scarce elite the default (main-agent) role too. - // A sub-setting of fable — only visible while fable is on (see - // visibleFacets) and never set by a suggestion (see validFacetActions). - {"main", []string{"on", "off"}, glyphs["main"]}, - } -} - -// parseAdvisors reads the __advisors__ block (rows: " ") -// into a map keyed "level/ctx" — the advisor model table, sourced from -// generate-profiles.py so the catalog stays a single source of truth. -func parseAdvisors(rows []string) map[string][]string { - out := map[string][]string{} - for _, r := range rows { - f := strings.Fields(strings.ReplaceAll(r, "→", " ")) - if len(f) < 3 { - continue - } - var chain []string - for _, t := range f[2:] { - if modelRe.MatchString(t) { - chain = append(chain, t) - } - } - if len(chain) > 0 { - out[f[0]+"/"+f[1]] = chain - } - } - return out -} - -// modelFact is a model's measured facts from omp (via the catalog): pricing -// ($/1M tokens), output throughput (tok/s), time-to-first-token (seconds), the -// quota bucket it draws from ("" when the catalog declares none), and the pool -// it belongs to ("" in catalogs that predate the column — the provider-prefix -// heuristic covers those). -type modelFact struct { - in, out, speed, ttft float64 - bucket string - pool string -} - -// effTPS folds ttft into throughput — the effective tok/s for a representative -// reply of effTokens: total time = ttft (startup) + tokens/speed (streaming), so -// a blazing-but-slow-to-start model (spark: 287 t/s, 5.6s ttft) reads honestly. -const effTokens = 300.0 - -func (f modelFact) effTPS() float64 { - if f.speed <= 0 { - return 0 - } - return effTokens / (f.ttft + effTokens/f.speed) -} - -// parseFacts reads the __models__ block (rows: " -// [] []") into a per-model table, sourced from the catalog so -// meters and routing agree. Bucket and pool are trailing optional columns — -// older catalogs carry neither, and the name heuristics cover those rows. -func parseFacts(rows []string) map[string]modelFact { - out := map[string]modelFact{} - for _, r := range rows { - f := strings.Fields(r) - if len(f) < 5 { - continue - } - in, e1 := strconv.ParseFloat(f[1], 64) - outc, e2 := strconv.ParseFloat(f[2], 64) - sp, e3 := strconv.ParseFloat(f[3], 64) - tt, e4 := strconv.ParseFloat(f[4], 64) - bucket, pool := "", "" - if len(f) >= 6 { - bucket = f[5] - } - if len(f) >= 7 { - pool = f[6] - } - if e1 == nil && e2 == nil && e3 == nil && e4 == nil { - out[f[0]] = modelFact{in, outc, sp, tt, bucket, pool} - } - } - return out -} - -// ── cost + speed meters ────────────────────────────────────────────────────── -// A profile's price and pace are dominated by the models on its heaviest roles, -// so each role is weighted by the token volume it drives over a session: the -// default agent and its task sub-agents move the needle; commit/tiny barely -// register — so Fable-as-commit stays cheap while Fable-as-default is dear (and -// slow). Per role, cost blends input+output pricing while speed reads the model's -// effective throughput (tok/s folded with time-to-first-token — see effTPS); both -// scale with thinking effort (more reasoning = pricier + slower) and take OpenAI's -// priority tier under fast mode (pricier but quicker). The weighted averages map -// onto 1..5 log scales (both perceived multiplicatively), calibrated across every -// valid facet × advisor × fast combination. Every role the generator emits must -// appear here: weightedModels silently skips a role it cannot weigh, so an -// omission drops that model out of both meters with no trace. -var roleWeight = map[string]float64{ - "default": 10, "task": 6, "reviewer": 3, "sonic": 3, "plan": 3, "advisor": 4, "slow": 2, - "designer": 2, "librarian": 2, "scout": 2, "smol": 1, "tiny": 0.5, "commit": 0.5, "vision": 0.5, -} -var thinkMult = map[string]float64{ // reasoning tokens grow with effort → pricier - "minimal": 0.6, "low": 0.8, "medium": 1.0, "high": 1.3, "xhigh": 1.6, "max": 2.0, -} -var thinkSpeed = map[string]float64{ // more reasoning before the answer → slower - "minimal": 1.4, "low": 1.2, "medium": 1.0, "high": 0.8, "xhigh": 0.65, "max": 0.5, -} - -const ( - priorityMult = 1.9 // OpenAI priority tier costs more under fast mode … - fastSpeed = 1.3 // … but responds quicker -) - -// Ln endpoints of the grid-wide min/max weighted indices, calibrated over every -// valid facet × advisor × fast combination (cost dear→ high, speed fast→ high). -const ( - costLnLo, costLnHi = 1.27, 4.42 - speedLnLo, speedLnHi = 2.49, 4.20 -) - -// weightedModels walks the current config's rows and calls fn(weight, id, level) -// for each role's lead model — the shared basis for both meters. -// currentRows is the routing block the cost/speed meters score: the generator's -// facet combo with the advisor dial applied. -func (m model) currentRows() []string { - return m.applyAdvisor(m.generated[comboID(m.sel)], m.sel["advisor"]) -} - -func (m model) weightedModels(rows []string, fn func(w float64, id, lvl string)) { - for _, r := range rows { - f := strings.Fields(strings.ReplaceAll(r, "→", " ")) - i := 0 - if len(f) > 0 && f[0] == "●" { - i = 1 - } - if i >= len(f) { - continue - } - w, ok := roleWeight[f[i]] - if !ok { - continue - } - var lead string - for _, t := range f[i+1:] { - if modelRe.MatchString(t) { - lead = t - break - } - } - id, lvl, _ := strings.Cut(lead, ":") - if id != "" { - fn(w, id, lvl) - } - } -} - -func logScore(idx, lnLo, lnHi float64) int { - s := 1 + 4*(math.Log(idx)-lnLo)/(lnHi-lnLo) - return int(math.Round(math.Max(1, math.Min(5, s)))) -} - -// costScore rates the current config from 1 (cheap) to 5 (dear). -func (m model) costScore() int { - if _, ok := m.selectedRuntime(); ok { - return 1 - } - fast := m.sel["fast"] == "on" && m.sel["lane"] != "claude-only" - var num, den float64 - m.weightedModels(m.currentRows(), func(w float64, id, lvl string) { - c, ok := m.facts[id] - if !ok { - return - } - mult, ok := thinkMult[lvl] - if !ok { - mult = 1 - } - cost := (0.25*c.in + 0.75*c.out) * mult - if fast && strings.HasPrefix(id, "gpt-") { - cost *= priorityMult - } - num += w * cost - den += w - }) - if den == 0 { - return 1 - } - return logScore(num/den, costLnLo, costLnHi) -} - -// speedScore rates the current config from 1 (slow) to 5 (fast). -func (m model) speedScore() int { - if _, ok := m.selectedRuntime(); ok { - return 3 - } - fast := m.sel["fast"] == "on" && m.sel["lane"] != "claude-only" - var num, den float64 - m.weightedModels(m.currentRows(), func(w float64, id, lvl string) { - c, ok := m.facts[id] - if !ok || c.speed == 0 { - return - } - mult, ok := thinkSpeed[lvl] - if !ok { - mult = 1 - } - sp := c.effTPS() * mult - if fast && strings.HasPrefix(id, "gpt-") { - sp *= fastSpeed - } - num += w * sp - den += w - }) - if den == 0 { - return 3 - } - return logScore(num/den, speedLnLo, speedLnHi) -} - -// meter renders a labelled 1..5 scale — n glyphs in the fill colour, the rest in -// the dim "empty" colour — always five glyphs so the fill (and the headroom) read -// at a glance. -func (m model) meter(label, glyph, fill string, n int) string { - return clikit.Meter(label, glyph, fill, n) -} - -// advisorChain returns the advisor role's model chain for an intensity, sourced -// from the baked __advisors__ table. The advisor is the independent second -// opinion, so it uses the opposite provider to whoever leads the session: GPT -// when the lead is Claude — a Claude-led (or pure-GPT) lane, or fable-as-main -// handing the default role to Fable — and Claude otherwise. Pure lanes keep -// their own provider, including the ox lane: ox-only's second opinion is Ox. -// -// On the mixed ox lanes the chain carries a cross-pool net in spend order — -// the other paid pool's cheapest rung, then the free pool itself — so a dead -// quota never leaves the second eye blind. -func (m model) advisorChain(level string) []string { - lane := m.sel["lane"] - ctx := "claude" - if lane == "ox-only" { - ctx = "ox" - } else if lane == "gpt-only" || lane == "claude-led" { - ctx = "gpt" - } - // fable-as-main puts Claude Fable in the default seat, so the second - // opinion flips to GPT — except on claude-only, where the pure-lane rule - // keeps the whole pool (advisor included) on Claude. - if lane != "claude-only" && m.sel["fable"] == "on" && m.sel["main"] == "on" { - ctx = "gpt" - } - chain := m.advisors[level+"/"+ctx] - if lane == "ox-led" || lane == "ox-lean" { - tail := m.advisors["glance/gpt"] - if ctx == "gpt" { - tail = m.advisors["glance/claude"] - } - for _, t := range append(append([]string{}, tail...), m.advisors["glance/ox"]...) { - dup := false - for _, c := range chain { - if c == t { - dup = true - break - } - } - if !dup && t != "" { - chain = append(chain, t) - } - } - } - return chain -} - -// roleOf returns the role name of a routing row ("● task" → "task"). -func roleOf(row string) string { - f := strings.Fields(row) - if len(f) > 0 && f[0] == "●" { - f = f[1:] - } - if len(f) > 0 { - return f[0] - } - return "" -} - -// applyAdvisor replaces the baked advisor row with one synthesised from the -// chosen intensity (dropping it entirely when off), so the generated preview and -// the launched config both reflect the advisor facet. -func (m model) applyAdvisor(rows []string, level string) []string { - chain := m.advisorChain(level) - newRow := "" - if len(chain) > 0 { - newRow = " advisor " + strings.Join(chain, " → ") - } - var out []string - replaced := false - for _, r := range rows { - if roleOf(r) == "advisor" { - replaced = true - if newRow != "" { - out = append(out, newRow) - } - continue - } - out = append(out, r) - } - if !replaced && newRow != "" { - out = append(out, newRow) - } - return out -} - -// visibleFacets drops facets that don't apply to the current lane, so the -// generator only ever shows actionable options: no spark/fast on a Claude-only -// pool, no fable on a GPT-only pool, and on the ox lanes only what an ox-led -// session can actually use (fable stays: it leads the deliberative roles). -// main is fable's sub-setting, so it only shows while fable is on (and the -// lane can host it at all). A dial this catalog generated no combo for is -// dropped the same way — it is not a choice. -func (m model) visibleFacets() []facet { - if _, local := m.selectedRuntime(); local { - var out []facet - for _, f := range m.facets { - if f.key == "runtime" || f.key == "thinking" { - out = append(out, f) - } - } - return out - } - lane := m.sel["lane"] - var out []facet - for _, f := range m.facets { - if lane == "claude-only" && (f.key == "spark" || f.key == "fast") { - continue - } - if lane == "gpt-only" && (f.key == "fable" || f.key == "main") { - continue - } - if lane == "ox-only" && (f.key == "spark" || f.key == "fable" || f.key == "main" || f.key == "fast") { - continue - } - if lane == "ox-led" && (f.key == "spark" || f.key == "main" || f.key == "fast") { - continue - } - if lane == "ox-lean" && (f.key == "spark") { - continue - } - if f.key == "spark" && m.noSpark { - continue - } - if (f.key == "fable" || f.key == "main") && m.noFable { - continue - } - if f.key == "main" && m.sel["fable"] != "on" { - continue - } - out = append(out, f) - } - return out -} - -func comboID(sel map[string]string) string { - lane := sel["lane"] - sp, fb := sel["spark"], sel["fable"] - if lane == "gpt-only" || lane == "ox-only" { - fb = "off" - } - if lane == "claude-only" || lane == "ox-only" || lane == "ox-led" || lane == "ox-lean" { - sp = "off" - } - spid, faid := "nosp", "nofa" - if sp == "on" { - spid = "sp" - } - if fb == "on" { - faid = "fa" - // ox-led hosts the elite on deliberative roles only; promoting it to - // the default role would defeat the lane, and genValid refuses that - // combo outright. - if sel["main"] == "on" && lane != "ox-led" { - faid = "famain" - } - } - return fmt.Sprintf("%s_%s_%s_%s_%s", lane, sel["model"], sel["thinking"], spid, faid) -} - -// applyCatalog records which dials this catalog can actually serve, then forces -// the rest off. A models file with no tier-0 model yields no _sp_ combos at all, -// so the shipped default (spark on) would open the TUI on a combo that was never -// written — and a selection persisted against a richer catalog does the same. -// Ids are ____, so match whole -// segments: "nosp" and "nofa" contain the very substrings being looked for. -func (m *model) applyCatalog() { - if len(m.generated) == 0 { - return // no catalog read yet: onboarding, or a broken CODE_GENERATED - } - spark, fable := false, false - served := map[string]bool{} - for id := range m.generated { - lane := id - if i := strings.IndexByte(id, '_'); i >= 0 { - lane = id[:i] - } - served[lane] = true - for _, seg := range strings.Split(id, "_") { - switch seg { - case "sp": - spark = true - case "fa", "famain": - fable = true - } - } - } - m.noSpark, m.noFable = !spark, !fable - m.trimLanes(served) - m.clampSel() -} - -// trimLanes narrows the lane dial to the lanes this catalog actually serves, -// and lands the selection on a served lane when a persisted or default choice -// points at one that vanished (an older catalog without ox, say). This is the -// consumer side of the ox on/off switch: no ox entries in models.yml means no -// ox values on the dial at all. -func (m *model) trimLanes(served map[string]bool) { - for i, f := range m.facets { - if f.key != "lane" { - continue - } - var values []string - for _, v := range f.values { - if served[v] { - values = append(values, v) - } - } - if len(values) == len(f.values) { - continue // nothing to trim - } - if len(values) > 0 { - m.facets[i].values = values - } - break - } - if !served[m.sel["lane"]] { - for _, fallback := range []string{"mixed", "gpt-only", "claude-only"} { - if served[fallback] { - m.sel["lane"] = fallback - break - } - } - } -} - -// clampSel turns off every dial the catalog cannot serve. main is fable's -// sub-setting and never outlives it. -func (m *model) clampSel() { - if m.noSpark { - m.sel["spark"] = "off" - } - if m.noFable { - m.sel["fable"] = "off" - m.sel["main"] = "off" - } -} - -func laneColor(lane string) string { - switch lane { - case "ox-only": - return "#1f9d5b" // deeper green — pure free pool - case "ox-led": - return "#5fce96" // lighter green — leans Ox Alpha - case "ox-lean": - return "#2dd4bf" // teal — paid work riding the free pool - case "gpt-only": - return "#3f8ef0" // deeper blue — pure pool - case "gpt-led": - return "#7ab6ff" // lighter blue — leans GPT - case "mixed": - return "#aa96e1" - case "claude-led": - return "#ffb277" // lighter orange — leans Claude - case "claude-only": - return "#ff8534" // deeper orange — pure pool - } - return "#ff9f52" -} - -// prefixed qualifies a bare catalog id with the omp provider omp routes -// through. The catalog's pool column is authoritative; the name heuristic is -// only for catalogs that predate it. -func (m model) prefixed(model string) string { - // Routing tokens carry a thinking level ("id:level"); the catalog is - // keyed on the bare id. Qualify the full token either way. - id := model - if i := strings.IndexByte(id, ':'); i >= 0 { - id = id[:i] - } - if f, ok := m.facts[id]; ok && f.pool != "" { - switch f.pool { - case "O": - return "openai-codex/" + model - case "A": - return "anthropic/" + model - case "R": - return "openrouter/" + model - } - } - if strings.HasPrefix(model, "claude") { - return "anthropic/" + model - } - return "openai-codex/" + model -} - -// genConfigYAML reconstructs an omp config (modelRoles, task-agent model -// overrides for the ●-marked agent-backed roles, fallback chains, thinking, -// advisor, and the priority tier when fast is on) from the generated routing -// block for the current facets — what Enter launches omp with. The agent -// overrides mirror the preview: without them the static managed defaults -// would keep the five agent-backed types pinned regardless of the generated -// profile (issue atyrode/dotfiles#173). -func (m model) genConfigYAML() string { - rows := m.applyAdvisor(m.generated[comboID(m.sel)], m.sel["advisor"]) - var mr, fc, ao strings.Builder - advisorOn := false - for _, r := range rows { - f := strings.Fields(strings.ReplaceAll(r, "→", " ")) - i := 0 - if len(f) > 0 && f[0] == "●" { - i = 1 - } - if i >= len(f) { - continue - } - role := f[i] - var models []string - for _, t := range f[i+1:] { - if modelRe.MatchString(t) { - models = append(models, t) - } - } - if len(models) == 0 { - continue - } - if role == "advisor" { - advisorOn = true - } - if i == 1 && role != "advisor" { - // ●-marked agent-backed role: mirror its lead route as the task-agent - // model override so spawned agents follow the generated profile. - ao.WriteString(" " + role + ": " + m.prefixed(models[0]) + "\n") - } - mr.WriteString(" " + role + ": " + m.prefixed(models[0]) + "\n") - if len(models) > 1 { - var fbs []string - for _, x := range models[1:] { - fbs = append(fbs, m.prefixed(x)) - } - fc.WriteString(" " + role + ": [" + strings.Join(fbs, ", ") + "]\n") - } - } - var b strings.Builder - b.WriteString("modelRoles:\n" + mr.String()) - b.WriteString("retry:\n enabled: true\n modelFallback: true\n fallbackRevertPolicy: cooldown-expiry\n fallbackChains:\n" + fc.String()) - if ao.Len() > 0 { - b.WriteString("task:\n agentModelOverrides:\n" + ao.String()) - } - b.WriteString("defaultThinkingLevel: " + m.sel["thinking"] + "\n") - if advisorOn { - b.WriteString("advisor:\n enabled: true\n") - } else { - b.WriteString("advisor:\n enabled: false\n") - } - if m.sel["fast"] == "on" && m.sel["lane"] != "claude-only" { - b.WriteString("tier:\n openai: priority\n") - } - return b.String() -} - -// ── model ──────────────────────────────────────────────────────────────────── -// layout modes, chosen from the terminal size (unless the user collapses): -// -// split — wide: the focused list on the left, routing preview on the -// right, and Usage spanning the full bottom width -// medium — generator-dominant: the list full width on top (primary), then -// Usage and Routing side by side in a secondary row — Usage's -// provider groups stacked vertically inside its measured left -// column, Routing on the right (and taking the whole row while -// ‹s› hides Usage) -// collapsed — narrow/short or ‹p›: one full-width panel at a time (list, or -// routing w/ showResult) — the Generator stays usable instead of -// compressing every section into an unreadable split -const ( - modeSplit = iota - modeMedium - modeCollapsed -) - -// size classes behind mode(): derived from terminal cells and the measured -// rendered minima of each section (atyrode/dotfiles#197) — never from pixels or a hard-coded -// screenshot width. -const ( - sizeWide = iota - sizeMedium - sizeNarrow -) - -// gut is the left gutter every panel shares, so the whole UI hangs off one -// consistent margin instead of a ragged mix of flush-left and indented rows. -// topGap is the matching vertical breathing room above the section tabs. -// headRows counts the section head (tabs + blank separator) above a list body. -const ( - gut = 2 - topGap = 1 - headRows = 2 - // the launch footer pinned under the list: blank + cost + speed + blank + - // the ⏎ launch action on its own visually separated row. - launchFooterRows = 5 - // routingMinW is the narrowest useful routing column: pane chrome plus room - // for a lead chain — below this a side-by-side routing panel stops earning - // its keep. - routingMinW = 33 - // secSepW is the one-cell border column between medium's adjacent - // secondary panes (Usage left, Routing right) — visible separation, same - // stroke as the wide layout's routing pane border. - secSepW = 1 - // genMinRows is the fewest facet rows the generator list may be windowed to - // before the layout must shed secondary sections instead of compressing it. - genMinRows = 4 - // minRouteRows is the fewest routing viewport rows worth pinning chrome around. - minRouteRows = 4 -) - -// genColMinH is the generator column's minimum useful height: the pinned head, -// a windowed-but-usable slice of the facet list, and the pinned launch footer. -const genColMinH = headRows + genMinRows + launchFooterRows - -// genRowWidth is the width needed to render the widest generator facet row (all -// options) on a single line — the minimum for the left panel. -func (m model) genRowWidth() int { - max := 30 - for _, f := range m.facets { // widest over ALL facets, so width is lane-stable - w := 14 // ▸ + glyph + spaces + padded label - for _, v := range f.values { - w += len(v) + 4 - } - if w > max { - max = w - } - } - return max + 2 -} - -// sizeMode classifies the terminal into the wide / medium / narrow-short -// responsive classes. Widths compare against the measured generator row, -// routing, and usage-column minima; heights against the chrome each composition -// pins on screen — breakpoints track content needs, not screenshot numbers. -func (m model) sizeMode() int { - if m.w >= m.genRowWidth()+routingMinW && m.h >= m.wideMinH() { - return sizeWide - } - if m.w >= m.mediumMinW() && m.h >= m.mediumMinH() { - return sizeMedium - } - return sizeNarrow -} - -func (m model) mode() int { - if m.collapse { - return modeCollapsed - } - switch m.sizeMode() { - case sizeWide: - return modeSplit - case sizeMedium: - return modeMedium - default: - return modeCollapsed - } -} - -// wideMinH is the least height at which the wide composition stays readable: -// a usable generator column above the full-width Usage footer. Shorter than -// this, keeping every section visible would compress them all — shed instead. -func (m model) wideMinH() int { - return topGap + genColMinH + m.footerH(!m.hideUsage) -} - -// mediumMinH stacks the generator over the secondary Routing+Usage row (at its -// measured minimum) with Usage out of the footer. -func (m model) mediumMinH() int { - return topGap + genColMinH + 1 + m.secondaryMinH() + m.footerH(false) -} - -// mediumMinW: the secondary row must seat a useful routing viewport beside the -// measured usage column — plus the one-cell separator between them — without -// clipping either. -func (m model) mediumMinW() int { - if m.hideUsage { - return routingMinW - } - return routingMinW + secSepW + m.usageColW() -} - -// footerH measures the pinned footer for a composition directly from its parts -// — mode selection depends on it, so it must not consult the mode itself. -func (m model) footerH(withUsage bool) int { - h := 1 + lipgloss.Height(padLeft(m.help.View(keys), gut)) - if withUsage { - if p := m.usagePanel(); p != "" { - h += 1 + lipgloss.Height(p) - } - } - return h -} - -// bodyH is the height available above the pinned footer. -func (m model) bodyH() int { - h := m.h - lipgloss.Height(m.footer()) - if h < 1 { - h = 1 - } - return h -} - -// contentH is the height the panels actually fill — bodyH minus the top gap. -func (m model) contentH() int { - h := m.bodyH() - topGap - if h < 1 { - h = 1 - } - return h -} - -// bodyLines renders the generator's scrolling facet list and reports the cursor's -// line index within it. -func (m model) bodyLines() ([]string, int) { - return m.genLines() -} - -// launchFooter is the cost/speed meters for the current facet combo plus the -// enter-to-launch call to action — so the generator always shows what the choice -// costs, how fast it is, and how Enter will launch it. Enter always launches -// the generated profile for the current facets (the untouched default combo is -// a profile like any other); m runs omp-managed on the managed defaults with -// no overlay, and the sandbox (u) key is always offered. A selected runtime -// target gets the same footer shape with an honest summary instead of meters: -// its tokens are free and code has no measurement to quote. -func (m model) launchFooter() []string { - acc := lipgloss.NewStyle().Foreground(lipgloss.Color(m.accent())).Bold(true).Render(" ⏎ launch") - if _, local := m.selectedRuntime(); local { - return []string{ - "", - stDim.Render(" cost free · local inference"), - "", - "", - acc, - } - } - cs, ss := m.costScore(), m.speedScore() - return []string{ - "", - m.meter("cost", "$", meterRamp[cs], cs), // dear → red, cheap → green - m.meter("speed", "»", meterRamp[6-ss], ss), // fast → green, slow → red - "", // breathing room between the meters and the action row - acc + stDim.Render(" m managed omp · u sandbox"), - } -} - -// mediumSplit gives the Routing+Usage row only its measured minimum, then lets -// the primary Generator absorb every remaining row. The medium height threshold -// guarantees both sections fit; taller terminals therefore expand Generator -// instead of leaving slack below the compact secondary content. -func (m model) mediumSplit(bodyH int) (genH, secH int) { - secH = m.secondaryMinH() - genH = bodyH - 1 - secH - if genH < genColMinH { - genH = genColMinH - secH = bodyH - 1 - genH - } - return -} - -// previewDims returns the preview viewport's inner (width, height) for the mode. -// The full-width modes reserve the shared gutter; split leaves the preview's own -// border + padding to do the breathing. Every mode reserves prevChromeRows for -// the pinned pill head above and fallback-display hint below the viewport. -func (m model) previewDims() (int, int) { - bodyH := m.contentH() - switch m.mode() { - case modeCollapsed: - return m.w - gut, bodyH - prevChromeRows - case modeMedium: - _, secH := m.mediumSplit(bodyH) - return m.routingColW() - gut, secH - prevChromeRows - default: // split — the pane draws a border + prevPadL inside its width, so the - // viewport (what renderRoute wraps to) gets the inner text area, not the box. - return m.w - m.listW() - 3 - prevPadL, bodyH - prevChromeRows - } -} - -// usageColShare is the medium secondary row's Usage width: Usage is the -// favored pane — it takes the larger 3/5 proportional share of the row so its -// bars and notes keep breathing room, never dropping below its measured -// stacked minimum. Routing is the pane that shrinks as Usage grows, floored -// at routingMinW (the medium width threshold guarantees both floors seat). -func (m model) usageColShare() int { - avail := m.w - secSepW - uw := avail * 3 / 5 - if min := m.usageColW(); uw < min { - uw = min - } - if avail-uw < routingMinW { - uw = avail - routingMinW - } - return uw -} - -// routingColW is the medium secondary row's routing share: whatever Usage's -// favored share (and the separator between the panes) leaves free. -func (m model) routingColW() int { - w := m.w - if !m.hideUsage { - w -= m.usageColShare() + secSepW - } - if w < routingMinW { - w = routingMinW - } - return w -} - -// ── trackpad / mouse wheel ─────────────────────────────────────────────────── -// The wheel drives the generator directly: vertical scroll moves the facet -// selection, horizontal scroll changes the selected facet's value. Terminal -// mouse protocols expose direction-only press events rather than trackpad -// distance, so require a small burst before committing one generator step. -// This gives fine Mac trackpad motion room to settle without making every raw -// event select a new row or option. Routing remains continuous and ungated. -const ( - wheelStepEvents = 5 - wheelGestureGap = 200 * time.Millisecond -) - -const ( - wheelAxisNone = iota - wheelAxisV - wheelAxisH -) - -// admittedWheelMsg marks a generator wheel event whose same-direction burst -// crossed the step threshold before Bubble Tea's Update/render cycle. -type admittedWheelMsg struct{ tea.MouseMsg } - -// wheelTarget is the layout state the pre-dispatch filter needs. Keeping this -// interface narrow also lets the raw-input regression test wrap model while -// preserving the exact production filter path. -type wheelTarget interface { - wheelInRouting(int, int) bool - routingWheelCanMove(tea.MouseButton) bool -} - -// wheelInputFilter removes mouse traffic before Bubble Tea's unconditional -// redraw-after-Update. Generator motion accumulates by axis and direction; -// only each complete threshold reaches Update. Motion, non-wheel presses, -// routing horizontal wheel, and clamped routing scroll are dropped because -// none can change the view. -type wheelInputFilter struct { - axis int - button tea.MouseButton - count int - last time.Time -} - -func (f *wheelInputFilter) Filter(app tea.Model, msg tea.Msg) tea.Msg { - - mouse, ok := msg.(tea.MouseMsg) - if !ok { - return msg - } - if mouse.Action != tea.MouseActionPress { - return nil - } - target, ok := app.(wheelTarget) - if !ok { - return msg - } - if target.wheelInRouting(mouse.X, mouse.Y) { - switch mouse.Button { - case tea.MouseButtonWheelUp, tea.MouseButtonWheelDown: - if target.routingWheelCanMove(mouse.Button) { - return mouse - } - } - return nil - } - - axis := wheelAxisNone - switch mouse.Button { - case tea.MouseButtonWheelUp, tea.MouseButtonWheelDown: - axis = wheelAxisV - case tea.MouseButtonWheelLeft, tea.MouseButtonWheelRight: - axis = wheelAxisH - default: - return nil - } - now := time.Now() - if f.count > 0 && now.Sub(f.last) <= wheelGestureGap && axis != f.axis { - // Ignore brief orthogonal trackpad jitter without discarding progress - // along the operator's dominant gesture axis. - return nil - } - if axis != f.axis || mouse.Button != f.button || now.Sub(f.last) > wheelGestureGap { - f.axis = axis - f.button = mouse.Button - f.count = 0 - } - f.last = now - f.count++ - if f.count < wheelStepEvents { - return nil - } - f.count = 0 - return admittedWheelMsg{MouseMsg: mouse} -} - -type model struct { - generated map[string][]string - advisors map[string][]string // "level/ctx" → advisor model chain - facts map[string]modelFact // model id → cost ($/1M) + curated speed (tok/s) - avail availability - glyphs map[string]string - runtimeTargets []runtimeTarget - - // Catalog capability, phrased as absence so the zero value keeps every dial: - // a model with no catalog yet (the onboarding shell, tests) must behave as it - // always did. applyCatalog sets these only from a catalog it actually read. - noSpark bool // no _sp_ combos exist — hide the spark dial and force it off - noFable bool // no _fa_/_famain_ combos — same for fable and its main child - - depth int // 0 lead · 1 full - collapse bool // p: hide the Routing section - showResult bool // in collapsed mode: show the preview full-width - hideUsage bool // s: hide the Usage section (atyrode/dotfiles#198); fetch state keeps running unseen - showUsage bool // narrow mode: show Usage full-screen instead of silently shedding it - fullUsageIDs bool // i: expand compact Usage identities to full account addresses - - facets []facet - fcur int - sel map[string]string - selectionState string // CODE_SELECTION_STATE; empty keeps standalone runs stateless - - vp viewport.Model - spin spinner.Model - help help.Model - w, h int - rdy bool - - broker brokerConfig - usageCache string - accountState string - accountSelections accountSelectionState - accountErr string - manager bool - mgrCursor int - managerPreset managerPresetState - - fetching bool // a usage fetch is in flight (manual or auto) - nextRefresh time.Time // when the next auto-refresh fires - hadUsage bool // a successful central fetch has landed - usageStale bool // the last central refresh failed; prior data is retained - barAnim int // first-load fill frame (1..barAnimSteps-1 = partial); 0 = inactive, bars at full value - - launchManaged bool // m: run CODE_OMP with no overlay (the managed defaults) - launchUntrusted bool // u: run the CODE_OMP_UNTRUSTED sandbox - launchRuntime string // delegated local runtime target selected via CODE_RUNTIME_BROKER - hasSandbox bool // a sandbox binary exists; gates the u key - genConfig string // generated config YAML to launch omp with (generator Enter) - firstPrompt string // prompt from the suggest box, forwarded as omp's first message - savedSel map[string]string // selection snapshot before a live suggest preview (for revert) -} - -// usage auto-refreshes on this cadence; a 1s tick drives the countdown. -const refreshEvery = 5 * time.Minute - -type refreshTickMsg struct{} - -func tickCmd() tea.Cmd { - return tea.Tick(time.Second, func(time.Time) tea.Msg { return refreshTickMsg{} }) -} - -// usageMsg carries the single central account/usage snapshot fetched off the -// main thread so startup and refresh never block the TUI. -type usageMsg struct { - avail availability -} - -func fetchUsageCmd(broker brokerConfig) tea.Cmd { - if broker.URL == "" || broker.Token == "" { - return nil - } - return func() tea.Msg { return usageMsg{avail: loadAvailability(broker)} } -} - -func (m *model) startUsageFetch() tea.Cmd { - cmd := fetchUsageCmd(m.broker) - if cmd != nil { - m.fetching = true - } - return cmd -} - -// First-load bar fill grows each central usage bar from empty when the first -// successful fetch replaces the loading skeleton. A dedicated bounded tick -// sequence renders labels and numbers immediately and animates only the fill. -// Manual and automatic refreshes never re-run it. -const ( - barAnimSteps = 8 - barAnimInterval = 25 * time.Millisecond -) - -// barAnimMsg advances the first-load fill to the given frame (2..barAnimSteps). -type barAnimMsg struct{ step int } - -func barAnimCmd(step int) tea.Cmd { - return tea.Tick(barAnimInterval, func(time.Time) tea.Msg { return barAnimMsg{step} }) -} - -func (m model) Init() tea.Cmd { - if m.broker.URL == "" || m.broker.Token == "" { - return nil - } - return tea.Batch(m.startUsageFetch(), m.spin.Tick, tickCmd()) -} - -func (m model) listW() int { - // wide enough for the generator options on one line; capped so a very wide - // terminal doesn't stretch the list needlessly. - w := m.genRowWidth() - if w > m.w-33 { - w = m.w - 33 - } - // The ox lanes widen the lane row past this function's old 80-cell - // aesthetic cap; a wider list beats clipping dial options mid-value. - if w > 116 { - w = 116 - } - return w -} - -// Usage bars naturally measure ten cells, then grow or shrink to the width -// assigned by their row group. Styling is deliberately applied after the -// display-cell geometry is settled, so ANSI sequences never enter the math. -const usageBarNaturalW = 10 - -func barStr(p, width int) string { - if width < 0 { - width = 0 - } - var r, g float64 - if p <= 50 { - r, g = 90+float64(p)*3, 200 - } else { - r, g = 235, 200-float64(p-50)*3 - } - if r > 235 { - r = 235 - } - if g < 60 { - g = 60 - } - fill := (p*width + 50) / 100 - if fill > width { - fill = width - } - if fill < 0 { - fill = 0 - } - filled := lipgloss.NewStyle().Foreground(lipgloss.Color(fmt.Sprintf("#%02x%02x46", clampByte(r), clampByte(g)))).Render(strings.Repeat("█", fill)) - return filled + stDim.Render(strings.Repeat("░", width-fill)) -} - -func fmtReset(s int64) string { - if s < 0 { - s = 0 - } - switch { - case s >= 86400: - return fmt.Sprintf("%dd%dh", s/86400, (s%86400)/3600) - case s >= 3600: - return fmt.Sprintf("%dh%dm", s/3600, (s%3600)/60) - } - return fmt.Sprintf("%dm", s/60) -} - -func shortWin(l string) string { - switch l { - case "5 hours", "Claude 5 Hour", "Codex 5 Hour", "OpenAI 5 Hour": - return "5h" - case "7 days", "Claude 7 Day", "Codex 7 Day", "OpenAI 7 Day": - return "7d" - case "5 hours (Spark)", "Codex 5 Hour (Spark)", "OpenAI 5 Hour (Spark)": - return "5h spark" - case "7 days (Spark)", "Codex 7 Day (Spark)", "OpenAI 7 Day (Spark)": - return "7d spark" - case "Claude 7 Day (Fable)": - return "7d fable" - } - return l -} - -// usageCtrlLine is the Usage chrome's bottom action row: central refresh state, -// account-manager access, and any account persistence error. -func (m *model) usageCtrlLine() string { - var parts []string - if m.broker.URL != "" { - switch { - case !m.avail.ok && (m.fetching || m.nextRefresh.IsZero()): - parts = append(parts, stDim.Render(m.spin.View()+" fetching usage…")) - case m.fetching: - parts = append(parts, stWarn.Render(gReset+" refreshing…")) - case m.usageStale: - // A failed refresh kept the previous data on screen: the warning - // takes the countdown's slot (same row, similar width) so the - // measured panel geometry — and with it the medium/collapsed - // breakpoint — barely moves on a flaky refresh. - parts = append(parts, stWarn.Render("refresh failed · stale")+ - stDim.Render(" · ")+stKey.Render("r")+stDim.Render(" retry")) - default: - rem := time.Until(m.nextRefresh) - if rem < 0 { - rem = 0 - } - s := int(rem.Seconds()) - parts = append(parts, - stDim.Render(fmt.Sprintf("next refresh %d:%02d · ", s/60, s%60))+ - stKey.Render("r")+stDim.Render(" now")) - } - } - identityAction := "full ids" - if m.fullUsageIDs { - identityAction = "short ids" - } - parts = append(parts, stKey.Render("i")+stDim.Render(" "+identityAction)) - parts = append(parts, stKey.Render("v")+stDim.Render(" accounts")) - if len(parts) == 0 { - return "" - } - line := " " + strings.Join(parts, stDim.Render(" · ")) - if m.accountErr != "" { - line += "\n" + stBrk.Render(" account update failed: "+m.accountErr) - } - return line -} - -// compactDisplayIdentity produces a deliberately lossy display label. Email -// matching continues to use the untouched broker identity; this helper is only -// for the compact Usage heading. -func compactDisplayIdentity(identity string) string { - normalized := strings.ToLower(strings.TrimSpace(identity)) - at := strings.IndexByte(normalized, '@') - if at > 0 && at == strings.LastIndexByte(normalized, '@') { - local, domain := normalized[:at], normalized[at+1:] - dot := strings.LastIndexByte(domain, '.') - valid := dot > 0 && dot < len(domain)-1 && - !strings.HasPrefix(local, ".") && !strings.HasSuffix(local, ".") && - !strings.Contains(local, "..") && !strings.Contains(domain, "..") && - strings.IndexFunc(normalized, func(r rune) bool { - return unicode.IsSpace(r) || unicode.IsControl(r) - }) < 0 - if valid { - localRunes := []rune(local) - if len(localRunes) > 2 { - localRunes = localRunes[:2] - } - return string(localRunes) + "*" - } - } - if normalized == "" { - return "id unavailable" - } - runes := []rune(normalized) - if len(runes) > 2 { - runes = runes[:2] - } - return string(runes) + "*" -} - -func usageDisplayIdentity(identity string, full bool) string { - if full { - if identity = strings.TrimSpace(identity); identity != "" { - return identity - } - return "id unavailable" - } - return compactDisplayIdentity(identity) -} - -type compactProviderIdentity struct { - label string - reporting bool -} - -// providerIdentities preserves broker snapshot order and collapses repeated -// copies of the same stable account. Compact ambiguity is intentional; pressing -// i reveals full addresses when disambiguation matters. -func providerIdentities(a availability, prov string, full bool) []compactProviderIdentity { - accounts := a.accounts[prov] - identities := make([]compactProviderIdentity, 0, len(accounts)) - seenAccounts := map[string]bool{} - for _, acct := range accounts { - stableID := acct.IdentityKey - if stableID == "" { - stableID = acct.Email - } - if stableID != "" { - stableID = acct.Provider + "\x00" + stableID - if seenAccounts[stableID] { - continue - } - seenAccounts[stableID] = true - } - - identity := acct.Email - if identity == "" { - identity = acct.IdentityKey - } - label := usageDisplayIdentity(identity, full) - - reporting := false - for _, win := range a.accountUsage[accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey}] { - if !win.missing { - reporting = true - break - } - } - identities = append(identities, compactProviderIdentity{ - label: label, reporting: reporting, - }) - } - return identities -} - -// providerHeading keeps the provider's established color and puts compact, -// enabled snapshot identities in a dim parenthetical suffix. -func providerHeading(prov string, identities []compactProviderIdentity) string { - col, name := "#62a7ff", "Codex" - if prov == "anthropic" { - col, name = "#ff9f52", "Claude" - } - heading := lipgloss.NewStyle().Foreground(lipgloss.Color(col)).Bold(true).Render(name) - if len(identities) == 0 { - return heading - } - labels := make([]string, 0, len(identities)) - for _, identity := range identities { - labels = append(labels, identity.label) - } - return heading + " " + stDim.Render("("+strings.Join(labels, " + ")+")") -} - -// providerIdentityBlockFor keeps missing usage explicit without spending a -// separate row on accounts already represented by aggregate usage bars. -func providerIdentityBlockFor(a availability, prov string, checking, full bool) []string { - identities := providerIdentities(a, prov, full) - if checking && len(identities) == 0 { - identities = []compactProviderIdentity{{label: "checking account…"}} - } - rows := []string{padLeft(providerHeading(prov, identities), gut)} - if checking { - return rows - } - if !a.accountsOK { - return append(rows, stWarn.Render(" account status unavailable")) - } - if len(identities) == 0 { - if a.selectionApplied { - return append(rows, stDim.Render(" no enabled accounts")) - } - return append(rows, stBrk.Render(" not authenticated")) - } - unavailable := 0 - for _, identity := range identities { - if !identity.reporting { - unavailable++ - } - } - if unavailable > 0 { - status := " usage unavailable" - if len(identities) > 1 { - noun := "account" - if unavailable > 1 { - noun = "accounts" - } - status += fmt.Sprintf(" for %d %s", unavailable, noun) - } - rows = append(rows, stWarn.Render(status)) - } - if a.accountsStale { - rows = append(rows, stWarn.Render(" identity cached")) - } - return rows -} - -func providerIdentityBlock(a availability, prov string, checking bool) []string { - return providerIdentityBlockFor(a, prov, checking, false) -} - -// identityLines keeps provider and broker-reported account state visible even -// when no usage rows exist. -func identityLinesFor(a availability) string { - var lines []string - for i, prov := range []string{"anthropic", "openai-codex"} { - if i > 0 { - lines = append(lines, "") - } - lines = append(lines, providerIdentityBlock(a, prov, false)...) - } - return strings.Join(lines, "\n") -} - -func (m *model) selectedLaunchAvailability() availability { - return selectedAvailability(m.avail, m.accountSelections.CurrentDisabled()) -} - -func (m *model) selectedUsageAvailability() availability { - disabled := m.accountSelections.CurrentDisabled() - if m.manager { - disabled = m.managerDisplayedDisabled() - } - return selectedAvailability(m.avail, disabled) -} - -func (m *model) identityLines() string { - return identityLinesFor(m.selectedUsageAvailability()) -} - -// usagePanel is the composition-agnostic Usage band sized for the current -// terminal width — the wide layout's full-width footer form. -func (m *model) usagePanel() string { return m.usagePanelFor(m.w) } - -// usagePanelFor renders central account usage with local visibility and account -// manager cues. There is no selectable vault or profile identity. -func (m *model) usagePanelFor(w int) string { - return m.usagePanelLayout(w, false) -} - -func (m *model) usagePanelStackedFor(w int) string { - return m.usagePanelLayout(w, true) -} - -func (m *model) usagePanelLayout(w int, stacked bool) string { - title := m.pill("usage") - title += " " + stCueKey.Render("s") + stCue.Render(" · hide") - innerWidth := max(0, w-gut) - out := padLeft(title, gut) + "\n" + - "\n" + m.usageBodyLayout(innerWidth, stacked) - if ctrl := m.usageCtrlLine(); ctrl != "" && !m.manager { - out += "\n\n" + ctrl // blank row: air between provider content and the control row - } - return out -} - -// usageRenderGroup keeps provider chrome and usage rows separate until layout -// has assigned the provider its real display width. That is the composition seam -// which lets both stacked and side-by-side layouts grow bars without changing -// headings, notes, or the canonical row grammar. -type usageRenderGroup struct { - prefix []string - rows []usageRowSpec - suffix []string -} - -func (g usageRenderGroup) linesWithUsageLayout(barWidth, noteWidth, prefixHeight int) []string { - lines := make([]string, 0, max(len(g.prefix), prefixHeight)+len(g.rows)+len(g.suffix)) - lines = append(lines, g.prefix...) - for len(lines) < prefixHeight { - lines = append(lines, "") - } - for _, row := range g.rows { - lines = append(lines, row.render(barWidth, noteWidth)) - } - lines = append(lines, g.suffix...) - return lines -} - -func usageRenderWidth(lines []string) int { - width := 0 - for _, line := range lines { - if lineWidth := lipgloss.Width(line); lineWidth > width { - width = lineWidth - } - } - return width -} - -// usageBodyFor renders the provider/account content between the pinned title -// and the bottom control row: the identity-headed usage groups, or the -// loading/unavailable identity block. -func (m *model) usageBodyFor(w int) string { - return m.usageBodyLayout(w, false) -} - -func (m *model) usageBodyLayout(w int, stacked bool) string { - a := m.selectedUsageAvailability() - if !a.ok { - if m.usageLoading() { - return m.skeletonBodyLayout(w, stacked) - } - out := identityLinesFor(a) - if m.broker.URL != "" && !m.fetching && !m.nextRefresh.IsZero() { - out += "\n" + stWarn.Render(" usage unavailable · press v to manage accounts") - } - return out - } - if len(a.wins) == 0 { - return identityLinesFor(a) + "\n" + - stWarn.Render(" no enabled provider usage · press v to manage accounts") - } - wins := append([]usageWin(nil), a.wins...) - provOrder := func(p string) int { - switch p { - case "anthropic": - return 0 - case "openai-codex": - return 1 - } - return 2 - } - tierOrder := func(t string) int { - if t == "" || t == "-" { - return 0 - } - return 1 - } - sort.SliceStable(wins, func(i, j int) bool { - if o := provOrder(wins[i].prov) - provOrder(wins[j].prov); o != 0 { - return o < 0 - } - if o := tierOrder(wins[i].tier) - tierOrder(wins[j].tier); o != 0 { - return o < 0 - } - return wins[i].dur < wins[j].dur - }) - blocks := map[string]usageRenderGroup{} - var order []string - for _, prov := range []string{"anthropic", "openai-codex"} { - if len(a.accounts[prov]) > 0 { - order = append(order, prov) - blocks[prov] = usageRenderGroup{prefix: providerIdentityBlockFor(a, prov, false, m.fullUsageIDs)} - } - } - for _, win := range wins { - block, ok := blocks[win.prov] - if !ok { - order = append(order, win.prov) - block.prefix = providerIdentityBlockFor(a, win.prov, false, m.fullUsageIDs) - } - block.rows = append(block.rows, m.usageRowSpec(win, " ")) - blocks[win.prov] = block - } - if block, ok := blocks["openai-codex"]; ok { - for _, acct := range a.accounts["openai-codex"] { - key := accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey} - identity := usageDisplayIdentity(managerAccountLabel(acct), m.fullUsageIDs) - if cl := creditLineForAccount(identity, a.accountCredits[key]); cl != "" { - block.suffix = append(block.suffix, cl) - } - } - blocks["openai-codex"] = block - } - return layoutGroups(w, order, blocks, stacked) -} - -// layoutGroups assigns provider columns before rendering any usage row. -// Side-by-side groups share the whole panel width; stacked groups each receive -// the whole section width. A zero width is the non-recursive measurement path -// and therefore retains natural ten-cell bars. -func layoutGroups(w int, order []string, blocks map[string]usageRenderGroup, stacked bool) string { - allRows := make([]usageRowSpec, 0) - for _, prov := range order { - allRows = append(allRows, blocks[prov].rows...) - } - naturalBarWidth, noteWidth := usageRowsLayout(0, allRows) - naturalColW := lipgloss.Width(skeletonRow("7d fable")) + 2 - for _, prov := range order { - blockW := usageRenderWidth(blocks[prov].linesWithUsageLayout(naturalBarWidth, noteWidth, 0)) + 2 - if blockW > naturalColW { - naturalColW = blockW - } - } - sideBySide := !stacked && w > 0 && len(order) > 1 && w >= naturalColW*len(order) - layoutWidth := w - if sideBySide { - for i := range order { - colW := w*(i+1)/len(order) - w*i/len(order) - if i == 0 || colW < layoutWidth { - layoutWidth = colW - } - } - } - barWidth, noteWidth := usageRowsLayout(layoutWidth, allRows) - if sideBySide { - prefixHeight := 0 - for _, prov := range order { - if len(blocks[prov].prefix) > prefixHeight { - prefixHeight = len(blocks[prov].prefix) - } - } - cols := make([]string, 0, len(order)) - for i, prov := range order { - colW := w*(i+1)/len(order) - w*i/len(order) - content := strings.Join(blocks[prov].linesWithUsageLayout(barWidth, noteWidth, prefixHeight), "\n") - cols = append(cols, lipgloss.NewStyle().Width(colW).Render(content)) - } - return lipgloss.JoinHorizontal(lipgloss.Top, cols...) - } - var lines []string - for i, prov := range order { - if i > 0 { - lines = append(lines, "") - } - lines = append(lines, blocks[prov].linesWithUsageLayout(barWidth, noteWidth, 0)...) - } - return strings.Join(lines, "\n") -} - -// usageLoading reports the initial central fetch window where the layout-stable -// skeleton replaces the not-yet-known account and usage rows. -func (m *model) usageLoading() bool { - return m.broker.URL != "" && !m.avail.ok && (m.fetching || m.nextRefresh.IsZero()) -} - -// skeletonWinsByProvider mirrors each provider's stable usage shape. Anthropic -// always reserves the Fable row, even before a first successful fetch. -var skeletonWinsByProvider = map[string][]string{ - "openai-codex": {"5h", "7d"}, - "anthropic": {"5h", "7d", "7d fable"}, -} - -// usageRowSpec is the unrendered canonical Usage-row grammar. Every caller -// supplies its indentation and group width, while this one seam reserves the -// label, percentage, reset, and actual note before assigning all safe cells to -// the bar. -type usageRowSpec struct { - indent string - label string - barPct int - percentage string - reset string - note string - reserveNote string -} - -func usageRowsNoteWidth(rows []usageRowSpec) int { - width := 0 - for _, row := range rows { - for _, note := range []string{row.note, row.reserveNote} { - if noteWidth := lipgloss.Width(note); noteWidth > width { - width = noteWidth - } - } - } - return width -} - -const usageResetValueWidth = 6 - -func paddedUsageReset(reset string) string { - width := lipgloss.Width(gReset) + 1 + usageResetValueWidth - return reset + strings.Repeat(" ", max(0, width-lipgloss.Width(reset))) -} - -func (r usageRowSpec) render(barWidth, noteWidth int) string { - note := "" - if r.note != "" { - note = " " + r.note + strings.Repeat(" ", max(0, noteWidth-lipgloss.Width(r.note))) - } else if noteWidth > 0 { - note = strings.Repeat(" ", 2+noteWidth) - } - return fmt.Sprintf("%s%-9s %s %s used %s%s", - r.indent, r.label, barStr(r.barPct, barWidth), r.percentage, paddedUsageReset(r.reset), note) -} - -func (r usageRowSpec) reservedWidth(noteWidth int) int { - return lipgloss.Width(r.render(0, noteWidth)) -} - -func usageRowsLayout(width int, rows []usageRowSpec) (barWidth, noteWidth int) { - noteWidth = usageRowsNoteWidth(rows) - if width == 0 { - return usageBarNaturalW, noteWidth - } - reserved := 0 - for _, row := range rows { - if rowW := row.reservedWidth(noteWidth); rowW > reserved { - reserved = rowW - } - } - return max(0, width-reserved), noteWidth -} - -func usageRowsBarWidth(width int, rows []usageRowSpec) int { - barWidth, _ := usageRowsLayout(width, rows) - return barWidth -} - -func renderUsageRows(width int, rows []usageRowSpec) []string { - barWidth, noteWidth := usageRowsLayout(width, rows) - rendered := make([]string, len(rows)) - for i, row := range rows { - rendered[i] = row.render(barWidth, noteWidth) - } - return rendered -} - -func skeletonUsageRowSpec(label, indent string) usageRowSpec { - return usageRowSpec{ - indent: indent, label: label, - percentage: stDim.Render(" ··%"), - reset: stDim.Render(gReset + " ····"), - reserveNote: "unavailable", - } -} - -// skeletonRow is the natural-width placeholder row used by measurement and -// focused tests. Real layout goes through renderUsageRows with its group width. -func skeletonRow(label string) string { - return renderUsageRows(0, []usageRowSpec{skeletonUsageRowSpec(label, " ")})[0] -} - -// skeletonBody is the pre-first-fetch Usage content: provider headings, -// explicit checking state, and generic placeholder window rows, laid out by -// the same group logic as real data so the first result lands predictably. -func (m *model) skeletonBody(w int) string { - return m.skeletonBodyLayout(w, false) -} - -func (m *model) skeletonBodyLayout(w int, stacked bool) string { - a := m.selectedUsageAvailability() - order := []string{"openai-codex", "anthropic"} - blocks := map[string]usageRenderGroup{} - for _, prov := range order { - block := usageRenderGroup{prefix: providerIdentityBlockFor(a, prov, true, m.fullUsageIDs)} - for _, label := range skeletonWinsByProvider[prov] { - block.rows = append(block.rows, skeletonUsageRowSpec(label, " ")) - } - blocks[prov] = block - } - return layoutGroups(w, order, blocks, stacked) -} - -// usageColumn is the medium layout's left-hand Usage section: the panel with -// provider groups forced into a vertical stack — the column is deliberately -// too narrow for side-by-side groups. The panel carries its own title chrome. -func (m model) usageColumn() string { - return m.usagePanelStackedFor(0) -} - -// usageColW is the medium usage column's measured width: the widest rendered -// line of the stacked panel (title, controls, bars, notes) — measured, not guessed. -func (m model) usageColW() int { - return lipgloss.Width(m.usageColumn()) -} - -// secondaryMinH is the medium secondary row's minimum height: routing's pinned -// chrome plus a few useful route rows, or the full stacked usage column when -// that is taller — medium only engages when neither column needs clipping. -func (m model) secondaryMinH() int { - h := prevChromeRows + minRouteRows - if m.hideUsage { - return h - } - if u := lipgloss.Height(m.usageColumn()); u > h { - h = u - } - return h -} - -func formatCachedAge(observed int64, now time.Time) string { - seconds := now.Unix() - observed - if seconds < 0 { - seconds = 0 - } - switch { - case seconds < 60: - return "<1m ago" - case seconds < 60*60: - return fmt.Sprintf("%dm ago", seconds/60) - case seconds < 24*60*60: - return fmt.Sprintf("%dh ago", seconds/(60*60)) - case seconds < 7*24*60*60: - return fmt.Sprintf("%dd ago", seconds/(24*60*60)) - case seconds < 365*24*60*60: - return fmt.Sprintf("%dw ago", seconds/(7*24*60*60)) - default: - return fmt.Sprintf("%dy ago", seconds/(365*24*60*60)) - } -} - -func (m *model) usageRowSpec(w usageWin, indent string) usageRowSpec { - if w.missing { - // Never-observed windows keep the exact row grammar with dotted values; - // only the status text differs from the loading skeleton. - row := skeletonUsageRowSpec(shortWin(w.label), indent) - row.note = stDim.Render("unavailable") - return row - } - note := "" - if w.pct >= 80 { - note = stWarn.Render("tight") - } - if w.pct >= 100 { - note = stBrk.Render("maxed") - } - if w.tier == "spark" && w.pct == 0 { - note = lipgloss.NewStyle().Foreground(lipgloss.Color(cGreen)).Render("idle") - } - if w.stale { - // Retained after a refresh omitted this window; show its age explicitly. - cached := "cached" - if w.observed > 0 { - cached += " " + formatCachedAge(w.observed, time.Now()) - } - if note != "" { - note += " " - } - note += stWarn.Render(cached) - } - resetText := gReset + " " + pad(fmtReset(w.secs), 4) - reset := stDim.Render(resetText) - if w.dur > 0 && w.secs*10 < w.dur { - reset = lipgloss.NewStyle().Foreground(lipgloss.Color("#c8d0dc")).Bold(true).Render(resetText) - } else if w.dur > 0 && w.secs*4 < w.dur { - reset = lipgloss.NewStyle().Foreground(lipgloss.Color("#c8d0dc")).Render(resetText) - } - // During the one-time first-load fill only the bar is scaled toward its - // target; the label, percentage, reset, and note are real from frame one. - barPct := w.pct - if m.barAnim > 0 { - barPct = barPct * m.barAnim / barAnimSteps - } - return usageRowSpec{ - indent: indent, label: shortWin(w.label), barPct: barPct, - percentage: fmt.Sprintf("%3d%%", w.pct), reset: reset, note: note, - } -} - -// usageRow is the natural-width measurement/test path. Composed panels and -// manager account groups render the same spec through renderUsageRows using -// their actual assigned width. -func (m *model) usageRow(w usageWin) string { - return renderUsageRows(0, []usageRowSpec{m.usageRowSpec(w, " ")})[0] -} - -// Reset-credit expiry urgency tints: each individual expiry (`3d`, `12d`, …) -// in the credit line is colored on a muted red→amber→green ramp so soon -// expiries read as warnings and distant ones as headroom, while the icon, -// count, and connecting prose stay dim. The palette is precomputed and -// deliberately desaturated (no per-frame color math, no saturated alarm -// colors inside a dim summary row); the day text itself stays sufficient -// without color. Thresholds are whole days remaining, exactly as fmtDays -// rounds them (up, so later-today = 1): ≤ creditUrgentDays is muted red, -// ≤ creditSoonDays muted amber, anything later muted green. -const ( - creditUrgentDays = 3 // expiring within three days — spend it or lose it - creditSoonDays = 10 // within ten days — plan around it -) - -var ( - stCreditUrgent = lipgloss.NewStyle().Foreground(lipgloss.Color("#b0716f")) // muted red - stCreditSoon = lipgloss.NewStyle().Foreground(lipgloss.Color("#b39c6b")) // muted amber - stCreditSafe = lipgloss.NewStyle().Foreground(lipgloss.Color("#85a883")) // muted green -) - -// creditDayStyle picks the urgency tint for a credit expiring in s seconds, -// bucketing on the same rounded-up whole days fmtDays renders — the color and -// the text can never disagree about which side of a threshold an expiry is on. -func creditDayStyle(s int64) lipgloss.Style { - d := int64(0) - if s > 0 { - d = (s + 86399) / 86400 - } - switch { - case d <= creditUrgentDays: - return stCreditUrgent - case d <= creditSoonDays: - return stCreditSoon - default: - return stCreditSafe - } -} - -// creditSummary renders the OpenAI reset-credit summary: the available count -// and the days remaining until the three soonest credit expirations, ascending. -// Callers own indentation and any account identity prefix. -func creditSummary(c resetCredits) string { - if c.avail == 0 && len(c.exp) == 0 { - return "" - } - exp := append([]int64(nil), c.exp...) - sort.Slice(exp, func(i, j int) bool { return exp[i] < exp[j] }) - if len(exp) > 3 { - exp = exp[:3] - } - noun := "resets" - if c.avail == 1 { - noun = "reset" - } - line := stDim.Render(gReset + " " + fmt.Sprintf("%d %s", c.avail, noun)) - if len(exp) > 0 { - days := make([]string, len(exp)) - for i, s := range exp { - days[i] = creditDayStyle(s).Render(fmtDays(s)) - } - line += stDim.Render(" · expiring in ") + strings.Join(days, stDim.Render(", ")) - } - return line -} - -func creditLineFor(c resetCredits) string { - if summary := creditSummary(c); summary != "" { - return " " + summary - } - return "" -} - -func creditLineForAccount(identity string, c resetCredits) string { - if summary := creditSummary(c); summary != "" { - return " " + stDim.Render(identity+" · ") + summary - } - return "" -} - -func (m *model) creditLine() string { - return creditLineFor(m.selectedUsageAvailability().credits) -} - -// fmtDays renders a relative duration as whole days remaining, rounding up so -// a credit expiring later today still reads 1d. -func fmtDays(s int64) string { - if s <= 0 { - return "0d" - } - return fmt.Sprintf("%dd", (s+86399)/86400) -} - -// syncPreview re-renders the routing content and jumps back to the top — for -// content changes (facet cycling, depth, reset), where the old scroll offset -// points at rows that no longer exist. -func (m *model) syncPreview() { - m.syncPreviewAt(0) -} - -// syncPreviewKeepScroll re-renders while preserving the scroll position where -// still valid — resizes and background usage refreshes must never yank the view. -func (m *model) syncPreviewKeepScroll() { - m.syncPreviewAt(m.vp.YOffset) -} - -func (m *model) syncPreviewAt(yoff int) { - if !m.rdy || m.collapse { - return - } - rw := m.vp.Width - // No settings summary here: every dial is already visible (selected) in the - // generator list on the left, so the preview shows only what that selection - // produces — the role → model routing itself. - var b strings.Builder - if target, local := m.selectedRuntime(); local { - b.WriteString(lipgloss.NewStyle().Bold(true).Render(target.Label) + "\n") - b.WriteString(stDim.Render(target.statusLine()) + "\n\n") - if target.ContextWindow > 0 { - b.WriteString(fmt.Sprintf("context %dk tokens\n\n", target.ContextWindow/1000)) - } - // Same grammar as a hosted profile: every role the broker's generated - // profile routes (all of them but the advisor, which stays off), led by - // the one local model at the dialed thinking — the flag is forwarded - // verbatim and omp clamps it to what the model offers. - rows := []string{fmt.Sprintf(" thinking %s · fallback off · advisor off", m.sel["thinking"])} - for _, r := range genRoleOrder { - if r == "advisor" { - continue - } - marker := " " - if genAgentRoles[r] { - marker = "●" - } - rows = append(rows, fmt.Sprintf(" %s %-10s %s:%s", marker, r, target.Model, m.sel["thinking"])) - } - b.WriteString(m.renderRoute(rows, m.depth, m.selectedLaunchAvailability(), rw)) - b.WriteString("\n" + stDim.Render("broker-owned profile · cloud auth excluded · weights provisioned by the runtime") + "\n") - content := lipgloss.NewStyle().MaxWidth(m.vp.Width).Render(b.String()) - m.vp.SetContent(content) - m.vp.SetYOffset(yoff) - return - } - id := comboID(m.sel) - if base, ok := m.generated[id]; ok { - _, roles := splitMeta(base) - roles = m.applyAdvisor(roles, m.sel["advisor"]) - b.WriteString(m.renderRoute(roles, m.depth, m.selectedLaunchAvailability(), rw)) - } else { - b.WriteString(stDim.Render("no profile for this combination") + "\n") - } - // clip (don't wrap) to pane width — renderRoute already wrapped the chains. - content := lipgloss.NewStyle().MaxWidth(m.vp.Width).Render(b.String()) - m.vp.SetContent(content) - m.vp.SetYOffset(yoff) // clamps into the new content and viewport height -} - -// footer is the pinned bottom block: the usage panel (when this composition -// keeps Usage in the footer) then the controls help, each under a rule. Built -// once here so relayout and View stay in sync. -func (m *model) footer() string { - usage := "" - if m.usageInFooter() { - usage = m.usagePanel() - } - return clikit.SeparatedSections( - m.w, - usage, - padLeft(m.help.View(m.contextHelp()), gut), - ) -} - -// usageInFooter says where Usage lives: the wide layout keeps it as the -// full-width bottom band; medium moves it into the secondary column (back to -// the footer while ‹p› hides that row); narrow/short hides it entirely so the -// Generator stays usable first — fetch state and the refresh cadence keep -// running unseen, and nothing is refetched when it reappears. -func (m *model) usageInFooter() bool { - if m.hideUsage { - return false - } - switch m.sizeMode() { - case sizeWide: - return true - case sizeMedium: - return m.collapse - default: - return false - } -} - -// routingShown reports whether the Routing section (and so its title-local -// p · hide cue) is on screen in the current composition. -func (m model) routingShown() bool { - if m.showUsage && m.sizeMode() == sizeNarrow { - return false - } - if m.collapse { - return false - } - if m.mode() == modeCollapsed { - return m.showResult - } - return true -} - -// usageShown reports whether the Usage panel is rendered anywhere — the -// wide/collapsed footer band, medium's secondary column, or narrow's dedicated -// full-screen view. -func (m model) usageShown() bool { - if m.hideUsage { - return false - } - if m.sizeMode() == sizeNarrow { - return m.showUsage - } - return m.usageInFooter() || m.mode() == modeMedium -} - -// usageCanShow reports whether restoring Usage would actually render it. -// Narrow terminals use a dedicated full-width view when they can seat the -// stacked Usage column; extremely small terminals still suppress a dead cue. -func (m model) usageCanShow() bool { - if m.sizeMode() == sizeNarrow { - return m.w >= m.usageColW() - } - t := m - t.hideUsage = false - return t.usageShown() -} - -// generatorShown: the Generator (and its launch footer advertising ⏎/m/u) is -// visible in every composition except a narrow full-screen secondary view. -func (m model) generatorShown() bool { - if m.sizeMode() == sizeNarrow && m.showUsage { - return false - } - return !(m.mode() == modeCollapsed && !m.collapse && m.showResult) -} - -// helpKeys is the state-derived key map handed to the bubbles help view: the -// compact line lists only what contextHelp selected, while ? always exposes -// the complete static reference. -type helpKeys struct { - short []key.Binding - full [][]key.Binding -} - -func (h helpKeys) ShortHelp() []key.Binding { return h.short } -func (h helpKeys) FullHelp() [][]key.Binding { return h.full } - -// contextHelp derives the compact footer from the model state (atyrode/dotfiles#198): -// - navigation, reset, full-help discovery, and quit are always offered; -// - a hidden section contributes its recovery action (show routing/usage) — -// usage only when the current terminal could actually seat it again; -// - account management and manual refresh surface while Usage is off screen, -// with refresh hidden while its one central request is in flight; -// - the launch trio surfaces only while the Generator launch footer is -// hidden (the narrow routing-full-screen swap). -// -// Everything else lives in visible section chrome or behind ?. -func (m model) contextHelp() helpKeys { - short := []key.Binding{keys.Move, keys.Change} - // The generator title advertises d · defaults itself; the compact line - // repeats it only while that chrome is off screen (routing full-screen). - if !m.generatorShown() { - short = append(short, keys.Reset) - } - if !m.routingShown() { - short = append(short, key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "show routing"))) - } - if !m.usageShown() && m.usageCanShow() { - short = append(short, key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "show usage"))) - } - // Keep full-help discovery and quit ahead of optional contextual actions: - // bubbles truncates a compact line from the right on narrow terminals. - short = append(short, keys.Help, keys.Quit) - if !m.usageShown() { - short = append(short, keys.Manager) - if m.broker.URL != "" && !m.fetching { - short = append(short, keys.Refresh) - } - } - if !m.generatorShown() { - short = append(short, keys.Launch, keys.Managed, keys.Untrusted) - } - return helpKeys{short: short, full: keys.FullHelp()} -} - -func (m *model) relayout() { - m.help.Width = m.w - gut // the footer help is gutter-inset on every line; wrap inside the padded width - pw, ph := m.previewDims() - if pw < 10 { - pw = 10 - } - if ph < 3 { - ph = 3 - } - if !m.rdy { - m.vp = viewport.New(pw, ph) - m.rdy = true - } else { - m.vp.Width, m.vp.Height = pw, ph - } - m.syncPreviewKeepScroll() -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.w, m.h = msg.Width, msg.Height - m.relayout() - case usageMsg: - scoped, scopedStale := reconcileUsage(m.avail, msg.avail) - refreshAt := time.Now().Add(refreshEvery) - first := !m.hadUsage && msg.avail.ok - m.avail, m.usageStale = scoped, scopedStale - m.hadUsage = m.hadUsage || msg.avail.ok - m.fetching = false - if msg.avail.ok { - saveUsageCache(m.usageCache, scoped) - } - m.nextRefresh = refreshAt - m.relayout() - if first { - // The first real data replaces the skeleton: run the one-time - // bounded bar fill. Refreshes never reach this branch again. - m.barAnim = 1 - return m, barAnimCmd(2) - } - case barAnimMsg: - // Bounded and self-terminating: apply the frame, arm the next tick, - // and stop at the final step (barAnim 0 = inactive, bars at value). - if m.barAnim == 0 { - return m, nil - } - if msg.step >= barAnimSteps { - m.barAnim = 0 - return m, nil - } - m.barAnim = msg.step - return m, barAnimCmd(msg.step + 1) - case refreshTickMsg: - // re-arm the 1s tick; auto-refresh once the interval elapses. - cmds := []tea.Cmd{tickCmd()} - if !m.fetching && !m.nextRefresh.IsZero() && !time.Now().Before(m.nextRefresh) { - cmds = append(cmds, m.startUsageFetch()) - } - return m, tea.Batch(cmds...) - case spinner.TickMsg: - if m.fetching || !m.avail.ok { - var cmd tea.Cmd - m.spin, cmd = m.spin.Update(msg) - return m, cmd - } - case admittedWheelMsg: - m.applyWheelStep(msg.Button) - return m, nil - case tea.MouseMsg: - // Wheel dispatch by pointer position: inside the visible Routing pane - // the viewport owns vertical scrolling — continuous, ungated, clamped - // by the viewport itself, with horizontal wheel deliberately inert. - // Everywhere else direct Update calls apply one generator step; live - // input is coalesced before dispatch by wheelInputFilter. - if msg.Action == tea.MouseActionPress { - if m.wheelInRouting(msg.X, msg.Y) { - switch msg.Button { - case tea.MouseButtonWheelUp: - m.vp.LineDown(1) // inverted: operator-confirmed trackpad direction - case tea.MouseButtonWheelDown: - m.vp.LineUp(1) - } - return m, nil - } - m.applyWheelStep(msg.Button) - } - case tea.KeyMsg: - if m.manager { - return m.updateManager(msg) - } - switch msg.String() { - case "q", "esc", "ctrl+c": - return m, tea.Quit - case "p": - switch { - case m.showUsage && m.sizeMode() == sizeNarrow: - m.showUsage = false - m.showResult = true - m.collapse = false - case m.collapse: - m.collapse = false // restore the hidden preview - case m.mode() == modeCollapsed: - m.showResult = !m.showResult // narrow+short: swap list ↔ result full-screen - default: - m.collapse = true // split/stacked: hide the preview, list full-screen - } - m.relayout() - case "s": - // Toggle the rendered section, not the pre-toggle size class. Usage - // changes the responsive minima, so a layout that fits while it is - // hidden may become narrow when it returns. In that case open the - // dedicated view atomically. It overlays the existing generator / - // Routing state so closing it restores that exact composition. - switch { - case m.showUsage: - m.showUsage = false - m.hideUsage = true - case !m.usageShown(): - m.hideUsage = false - m.showUsage = m.sizeMode() == sizeNarrow - default: - m.showUsage = false - m.hideUsage = true - } - m.relayout() - case "i": - m.fullUsageIDs = !m.fullUsageIDs - m.relayout() - case "?": - m.help.ShowAll = !m.help.ShowAll - m.relayout() // the taller/shorter footer changes the body height - case "d": - m.sel = defaultSel() - if len(m.runtimeTargets) > 0 { - m.sel["runtime"] = "hosted" - } - m.clampSel() // the defaults assume a full catalog; this one may not be - m.persistSelection() - m.syncPreview() - case "f": - m.depth = (m.depth + 1) % 2 - m.syncPreview() - case "r": - if m.broker.URL != "" && !m.fetching { - return m, m.startUsageFetch() - } - case "v": - m.manager = true - m.clampManagerCursor() - m.relayout() - case "up", "k": - m.moveUp() - case "down", "j": - m.moveDown() - case "left", "h": - m.cycleFacet(-1) - case "right", "l": - m.cycleFacet(1) - case "u": - // Untrusted sandbox: hand off to CODE_OMP_UNTRUSTED (ompu), which owns its - // own routing/policy — no generated --config is passed to it. Inert when - // no sandbox binary exists (the help hides the key too), so a stranger - // can't kill the TUI with a stray keypress. - if !m.hasSandbox { - return m, nil - } - m.launchUntrusted = true - return m, tea.Quit - case "pgup", "ctrl+u": - m.vp.HalfViewUp() // scroll the preview (mouse capture is off, see main) - case "pgdown", "ctrl+d": - m.vp.HalfViewDown() - case "m": - // Managed-defaults omp: omp-managed with no generated overlay. The - // explicit keybind keeps every Enter launch a generated profile. - m.launchManaged = true - return m, tea.Quit - case "enter": - if target, local := m.selectedRuntime(); local { - m.launchRuntime = target.Name - return m, tea.Quit - } - // Enter always launches the generated profile for the current facets — - // the untouched default combo is a generated profile like any other. - // Never for a combo the catalog doesn't carry, though: genConfigYAML - // would walk a nil block and emit an overlay whose modelRoles map is - // empty, handing omp a session with no routing at all. The preview - // already says "no profile for this combination", so the key does - // nothing rather than launching something broken. - if _, ok := m.generated[comboID(m.sel)]; !ok { - return m, nil - } - m.genConfig = m.genConfigYAML() - return m, tea.Quit - } - - case clikit.ActionsProposedMsg: - // Live preview: snapshot the current selection, then apply the proposal to - // the generator so the user sees the change while deciding. Report the FULL - // applied diff back to the box (the model's picks plus the derived toggles), - // so its "applied" list reflects everything that changed, not just the three - // facets the model named directly. - m.savedSel = map[string]string{} - for k, v := range m.sel { - m.savedSel[k] = v - } - m.applyActions(msg.Actions) - // applyActions' repair rules know lanes and quota, not catalog contents: - // a "critical" proposal switches fable on even where no fable combo was - // generated. Clamp and re-render before reporting what was applied. - m.clampSel() - m.syncPreview() - return m, func() tea.Msg { return clikit.AppliedActionsMsg{Actions: m.appliedDiff()} } - - case clikit.ActionsConfirmedMsg: - // Kept: the preview stays; remember the prompt for the launched session. - m.savedSel = nil - m.firstPrompt = msg.Prompt - m.persistSelection() - - case clikit.ActionsRevertedMsg: - // Rejected: restore the pre-preview selection. - if m.savedSel != nil { - m.sel = m.savedSel - m.savedSel = nil - m.syncPreview() - } - } - return m, nil -} - -// wheelInRouting reports whether a pointer position (terminal cells, 0-based) -// falls inside the visible Routing pane, whose viewport then owns vertical -// wheel scrolling: the wide split's right pane, medium's lower-right pane, or -// the narrow routing-only swap's full body. Hidden/collapsed routing claims -// nothing, so the generator keeps the wheel everywhere else. -func (m model) wheelInRouting(x, y int) bool { - if !m.routingShown() { - return false - } - ch := m.contentH() - switch m.mode() { - case modeCollapsed: // routing-only swap: the whole body is Routing - return y >= topGap && y < topGap+ch - case modeMedium: // the secondary row's right column, under the divider - genH, secH := m.mediumSplit(ch) - secTop := topGap + genH + 1 - return y >= secTop && y < secTop+secH && x >= m.w-m.routingColW() - default: // split: the right pane, from the list's right edge on - return y >= topGap && y < topGap+ch && x >= m.listW() - } -} - -// routingWheelCanMove reports whether a vertical routing scroll would change -// the viewport. No-op events at either clamp are filtered before redraw. -func (m model) routingWheelCanMove(b tea.MouseButton) bool { - switch b { - case tea.MouseButtonWheelUp: - return m.vp.YOffset < m.vp.TotalLineCount()-m.vp.Height - case tea.MouseButtonWheelDown: - return m.vp.YOffset > 0 - default: - return false - } -} - -// applyWheelStep translates one admitted wheel event into the matching facet -// action: vertical scroll moves the selection, horizontal scroll changes the -// value. The raw mapping is INVERTED on both axes — operator-confirmed trackpad -// direction: WheelUp moves the selection down, WheelDown up; WheelLeft cycles -// to the next (right) option, WheelRight to the previous. Arrow keys keep their -// literal semantics. -func (m *model) applyWheelStep(b tea.MouseButton) { - switch b { - case tea.MouseButtonWheelUp: - m.moveDown() - case tea.MouseButtonWheelDown: - m.moveUp() - case tea.MouseButtonWheelLeft: - m.cycleFacet(1) - case tea.MouseButtonWheelRight: - m.cycleFacet(-1) - } -} - -func (m *model) moveUp() { - if m.fcur > 0 { - m.fcur-- - } -} -func (m *model) moveDown() { - if m.fcur < len(m.visibleFacets())-1 { - m.fcur++ - } -} -func (m *model) cycleFacet(dir int) { - vf := m.visibleFacets() - if m.fcur >= len(vf) { - m.fcur = len(vf) - 1 - } - f := vf[m.fcur] - cur := m.sel[f.key] - idx := 0 - for i, v := range f.values { - if v == cur { - idx = i - } - } - next := idx + dir - if next < 0 { - next = 0 - } else if next >= len(f.values) { - next = len(f.values) - 1 - } - if next == idx { - return - } - m.sel[f.key] = f.values[next] - // main is fable's sub-setting: whenever fable leaves "on" it must clear too, - // so a later fable re-enable never silently resurrects the (expensive) - // fable-as-main escalation — it is re-chosen deliberately every time. - if m.sel["fable"] != "on" { - m.sel["main"] = "off" - } - // changing the lane can hide/show facets; keep the cursor in range. - if nv := len(m.visibleFacets()); m.fcur >= nv { - m.fcur = nv - 1 - } - m.syncPreview() - m.persistSelection() -} - -// prevPadL is the split preview pane's left padding. Width() counts it, so the -// viewport's usable text width is the box width minus this — the viewport must -// be sized to that inner area (see previewDims), else lines wrap 1:1 with the -// box and their tail overflows to the pane's left edge. -const prevPadL = 2 - -func (m model) previewPane(w, h int) string { - return lipgloss.NewStyle(). - Border(lipgloss.NormalBorder(), false, false, false, true). - BorderForeground(lipgloss.Color(cBord)).PaddingLeft(prevPadL). - Width(w).Height(h). - Render(m.previewColumn()) -} - -// accent is the context colour — the selected lane in the generator. -// Blue / purple / orange. -func (m model) accent() string { - if _, local := m.selectedRuntime(); local { - return cGreen - } - return laneColor(m.sel["lane"]) -} - -// pill renders an accent-backed section label, coloured by the active lane — -// the shared shape of the generator (left) and routing (right) column heads. -func (m model) pill(label string) string { - return lipgloss.NewStyle().Padding(0, 1). - Background(lipgloss.Color(m.accent())).Foreground(lipgloss.Color("#12161d")).Bold(true). - Render(label) -} - -// sectionTitle is the accent-pilled "generator" label at the top of the left -// column. -func (m model) sectionTitle() string { - return m.pill("generator") -} - -// sectionHead is the gutter-inset title row — the pill plus its local -// reset-to-defaults cue (d · defaults) — and a blank separator (headRows -// tall); it stays pinned above the scrolling facet list. -func (m model) sectionHead() string { - return padLeft(m.sectionTitle()+" "+stCueKey.Render("d")+stCue.Render(" · defaults"), gut) + "\n\n" -} - -// prevChromeRows is the Routing column's pinned chrome around the scrolling -// viewport: the title row with its local collapse cue and a blank separator -// above, plus the fallback-display cue pinned beneath the viewport. -const prevChromeRows = headRows + 1 - -// previewColumn assembles the Routing section: the pinned title row carrying -// the section-local collapse cue (p · hide), the scrolling routing viewport, -// then the fallback-display cue pinned at the section's bottom edge — bottom -// chrome, where the chains it toggles end. The f wording makes clear it only -// changes what is DISPLAYED: the launched profile always keeps its fallback -// chains. -func (m model) previewColumn() string { - verb := "show" - if m.depth == 1 { - verb = "hide" - } - return m.pill("routing") + " " + stCueKey.Render("p") + stCue.Render(" · hide") + "\n\n" + - m.vp.View() + "\n" + - stKey.Render("f") + stDim.Render(" · "+verb+" fallback chains") -} - -// leftColumn renders the pinned section head plus the scrolling list body, the -// whole column inset by the shared gutter, sized to totalH rows and w columns. -func (m model) leftColumn(w, totalH int) string { - iw := w - gut - body, bcur := m.bodyLines() - listH := totalH - headRows - launchFooterRows - if listH < 1 { - listH = 1 - } - list := padLeft(windowList(body, bcur, listH, iw), gut) - footer := padLeft(strings.Join(m.launchFooter(), "\n"), gut) - return m.sectionHead() + list + "\n" + footer -} - -// mediumContent is the generator-dominant layout: the full-width facet list on -// top (primary), a divider, then Usage (left) and Routing (right) side by side -// in a secondary row, separated by a one-cell border column. Usage stacks its -// provider groups vertically inside the measured-width left column; Routing -// keeps its own scrolling viewport in whatever the row leaves free. -func (m model) mediumContent(bodyH int) string { - genH, secH := m.mediumSplit(bodyH) - top := m.leftColumn(m.w, genH) - div := stDim.Render(strings.Repeat("─", m.w)) - rw := m.routingColW() - // clip each column before fixing its width — Width() alone would wrap any - // over-wide line onto an extra physical row and break the row's height. - routing := lipgloss.NewStyle().Width(rw).MaxHeight(secH).Render( - lipgloss.NewStyle().MaxWidth(rw).Render(padLeft(m.previewColumn(), gut))) - sec := routing - if !m.hideUsage { - uw := m.w - rw - secSepW // the measured usage column's share, left of the border - sep := lipgloss.NewStyle().Foreground(lipgloss.Color(cBord)).Render( - strings.TrimSuffix(strings.Repeat("│\n", secH), "\n")) - usage := lipgloss.NewStyle().Width(uw).MaxHeight(secH).Render( - lipgloss.NewStyle().MaxWidth(uw).Render(m.usagePanelStackedFor(uw))) - sec = lipgloss.JoinHorizontal(lipgloss.Top, usage, sep, routing) - } - return lipgloss.JoinVertical(lipgloss.Left, top, div, sec) -} - -func (m model) View() string { - if !m.rdy { - return "loading…" - } - if m.manager { - return m.managerView() - } - foot := m.footer() - bodyH := m.bodyH() - ch := m.contentH() - var content string - switch m.mode() { - case modeCollapsed: - switch { - case m.showUsage && m.sizeMode() == sizeNarrow: - content = padLeft(m.usagePanelFor(m.w-gut), gut) - case m.showResult && !m.collapse: - content = padLeft(m.previewColumn(), gut) - default: - content = m.leftColumn(m.w, ch) - } - case modeMedium: - content = m.mediumContent(ch) - default: // split - content = lipgloss.JoinHorizontal(lipgloss.Top, - m.leftColumn(m.listW(), ch), - m.previewPane(m.w-m.listW()-3, ch)) - } - // Pin the body into a fixed box (top-left) with lipgloss, then clip: any - // stray overflow (e.g. a glyph a terminal renders wider than measured) is - // absorbed here, never pushing the pinned footer off-screen. A top gap above - // the content gives the section tabs vertical breathing room. - body := lipgloss.NewStyle().MaxHeight(bodyH).Render( - strings.Repeat("\n", topGap) + - lipgloss.Place(m.w, ch, lipgloss.Left, lipgloss.Top, content)) - return lipgloss.NewStyle().MaxWidth(m.w).MaxHeight(m.h).Render( - lipgloss.JoinVertical(lipgloss.Left, body, foot)) -} - -func (m model) genLines() ([]string, int) { - acc := m.accent() - var lines []string - cursor := 0 - selected := m.selectedLaunchAvailability() - for i, f := range m.visibleFacets() { - onRow := i == m.fcur - glyCol := laneColor(m.sel["lane"]) - gly := lipgloss.NewStyle().Foreground(lipgloss.Color(glyCol)).Width(2).Render(f.glyph) - ptr := " " - if onRow { - ptr = lipgloss.NewStyle().Foreground(lipgloss.Color(acc)).Render("▸ ") - cursor = len(lines) - } - // main renders as fable's tabulated child "default": the indent + the - // default-role row lighting up Fable in the preview explain themselves, - // so it carries no flavor text (which would wrap on narrow panes anyway). - label, childPad, childW := f.key, "", 0 - if f.key == "main" { - // tree-style L connector: reads as fable's child, like `tree`. - label, childPad, childW = "default", stDim.Render("└ "), 2 - } - row := fmt.Sprintf("%s%s%s%s", ptr, childPad, gly, stDim.Render(pad(label, 9-childW))) - for _, v := range f.values { - display := v - if f.key == "runtime" { - display = m.runtimeValueLabel(v) - } - switch { - case v == m.sel[f.key]: - col := acc - if f.key == "lane" { - col = laneColor(v) - } else if (f.key == "spark" || f.key == "fable" || f.key == "main") && v == "on" { - col = cGreen - } - st := lipgloss.NewStyle().Foreground(lipgloss.Color(col)).Bold(true) - if onRow { // the cursor sits on the selected value of the focused row - st = st.Background(lipgloss.Color(cSelBg)) - } - row += " " + st.Render(" "+display+" ") - default: - row += " " + stDim.Render(display) - } - } - switch { - case (f.key == "fable" || f.key == "spark") && m.sel[f.key] == "on": - bkt, lbl := "claude-fable", "Fable" - if f.key == "spark" { - bkt, lbl = "codex-spark", "Spark" - } - if selected.down(bkt) { - w := lbl + " maxed · " + gReset + " " + fmtReset(selected.reset[bkt]) - if selected.bucket[bkt] == "unauthed" { - w = lbl + " unavailable" - } - row += " " + stWarn.Render(gWarn+" "+w+" — no usage left") - } - case f.key == "fast" && m.sel["fast"] == "on": - row += " " + stDim.Render("GPT only") - } - lines = append(lines, row) - } - return lines, cursor -} - -// defaultGlyphs is the built-in facet-glyph set (Nerd Font, Font Awesome PUA -// range), written as explicit \u escapes so the codepoints stay visible and -// verifiable in source — a literal PUA glyph is invisible in most editors and -// was once wiped by an edit exactly because of that. CODE_FACET_GLYPHS may -// override any entry (see main). -// -// runtime 🖥 (f108) lane ⇄ (f127) model ⚙ (f085) thinking 💡 (f0eb) advisor 🧭 (f14e) -// spark 🚀 (f135) fable 📖 (f02d) default 🎯 (f140) fast ⚡ (f0e7) -func defaultGlyphs() map[string]string { - return map[string]string{ - "runtime": "\uf108", "lane": "\uf127", "model": "\uf085", "thinking": "\uf0eb", "advisor": "\uf14e", - "spark": "\uf135", "fable": "\uf02d", "main": "\uf140", "fast": "\uf0e7", - } -} - func main() { // Subcommands are not TUI sessions; any other argv is still forwarded to // the launched omp session as before. @@ -3706,7 +134,7 @@ func main() { fm.firstPrompt, fm.broker, fm.accountSelections) }) case fm.genConfig != "": - status = withSession(comboID(fm.sel), "CODE_OMP", []string{"omp"}, func() int { + status = withSession(comboID(fm.sel, fm.hasRelief), "CODE_OMP", []string{"omp"}, func() int { return launchGenerated(fm.genConfig, fm.firstPrompt, fm.broker, fm.accountSelections) }) } @@ -3717,129 +145,3 @@ func main() { // forwardArgv strips every forwarded profile flag. Trusted launches use OMP's // ordinary implicit default state; sandbox routing remains owned by ompu. -func forwardArgv(path string, forwarded []string, prompt string) []string { - out := append([]string{path}, stripProfileArgs(forwarded)...) - if prompt != "" { - out = append(out, prompt) - } - return out -} - -func managedLaunchArgv(path string, forwarded []string, prompt string) []string { - return forwardArgv(path, forwarded, prompt) -} - -func sandboxLaunchArgv(path string, forwarded []string, prompt string) []string { - return forwardArgv(path, forwarded, prompt) -} - -func generatedLaunchArgv(path, cfgPath string, forwarded []string, prompt string) []string { - args := append([]string{"--config", cfgPath}, stripProfileArgs(forwarded)...) - out := append([]string{path}, args...) - if prompt != "" { - out = append(out, prompt) - } - return out -} - -func resolveLaunchPath(envName string, fallbacks []string) (string, error) { - if configured := os.Getenv(envName); configured != "" { - return exec.LookPath(configured) - } - var err error - for _, fallback := range fallbacks { - var path string - if path, err = exec.LookPath(fallback); err == nil { - return path, nil - } - } - if err == nil { - err = errors.New("no launcher configured") - } - return "", err -} - -func runChild(path string, argv, env []string) error { - cmd := exec.Command(path, argv[1:]...) - cmd.Args = argv - cmd.Env = env - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} - -func childStatus(err error) int { - if err == nil { - return 0 - } - var exitErr *exec.ExitError - if errors.As(err, &exitErr) && exitErr.ExitCode() >= 0 { - return exitErr.ExitCode() - } - return 1 -} - -func runSandbox(envName string, fallbacks []string, prompt string) int { - path, err := resolveLaunchPath(envName, fallbacks) - if err != nil { - fmt.Fprintln(os.Stderr, "code: sandbox not found:", err) - return 1 - } - err = runChild(path, sandboxLaunchArgv(path, os.Args[1:], prompt), withoutAuthEnv(os.Environ())) - if err != nil { - fmt.Fprintln(os.Stderr, "code: sandbox:", err) - } - return childStatus(err) -} - -func runTrusted(envName string, fallbacks []string, - argv func(string, []string, string) []string, prompt string, - broker brokerConfig, selections accountSelectionState) int { - disabled := selections.CurrentDisabled() - path, err := resolveLaunchPath(envName, fallbacks) - if err != nil { - fmt.Fprintln(os.Stderr, "code: trusted launcher not found:", err) - return 1 - } - accounts, err := loadAccounts(broker) - if err != nil { - fmt.Fprintln(os.Stderr, "code: account snapshot unavailable; refusing unrestricted launch:", err) - return 1 - } - accountPoolPath, cleanup, err := writeAccountPool(accounts, disabled) - if err != nil { - fmt.Fprintln(os.Stderr, "code: account pool unavailable; refusing unrestricted launch:", err) - return 1 - } - defer cleanup() - childEnv := withAuthEnv(os.Environ(), broker, accountPoolPath) - err = runChild(path, argv(path, os.Args[1:], prompt), childEnv) - if err != nil { - fmt.Fprintln(os.Stderr, "code: trusted child:", err) - } - return childStatus(err) -} - -// launchGenerated keeps both immutable launch inputs alive only for the child. -func launchGenerated(cfg, prompt string, broker brokerConfig, selections accountSelectionState) int { - tmp, err := os.CreateTemp("", "code-gen-*.yml") - if err != nil { - fmt.Fprintln(os.Stderr, "code:", err) - return 1 - } - cfgPath := tmp.Name() - defer os.Remove(cfgPath) - if _, err = tmp.WriteString(cfg); err == nil { - err = tmp.Close() - } else { - _ = tmp.Close() - } - if err != nil { - fmt.Fprintln(os.Stderr, "code: generated config:", err) - return 1 - } - return runTrusted("CODE_OMP", []string{"omp"}, func(path string, forwarded []string, prompt string) []string { - return generatedLaunchArgv(path, cfgPath, forwarded, prompt) - }, prompt, broker, selections) -} diff --git a/main_test.go b/main_test.go index d42fe81..0d3a477 100644 --- a/main_test.go +++ b/main_test.go @@ -3,11 +3,6 @@ package main import ( "encoding/json" "fmt" - clikit "github.com/atyrode/cli-kit" - "github.com/charmbracelet/bubbles/spinner" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" "io" "net/http" "net/http/httptest" @@ -19,6 +14,12 @@ import ( "sync/atomic" "testing" "time" + + clikit "github.com/atyrode/cli-kit" + "github.com/charmbracelet/bubbles/spinner" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) // ansiRe strips SGR sequences so tests assert on visible text regardless of the @@ -213,7 +214,7 @@ func TestComboID(t *testing.T) { {map[string]string{"lane": "gpt-only", "model": "smart", "thinking": "high", "spark": "on", "fable": "on", "main": "on"}, "gpt-only_smart_high_sp_nofa"}, } for _, c := range cases { - if got := comboID(c.sel); got != c.want { + if got := comboID(c.sel, false); got != c.want { t.Errorf("comboID(%v) = %q, want %q", c.sel, got, c.want) } } @@ -359,20 +360,21 @@ func TestCycleFacetClearsMain(t *testing.T) { } } +// TestCycleFacetClampsAtEndpoints: the lead dial (row 0) clamps rather than +// wraps at either end — mixed is first, the last pool's lead is last. func TestCycleFacetClampsAtEndpoints(t *testing.T) { m := &model{facets: facetDefs(map[string]string{}), sel: defaultSel()} - m.fcur = 0 // lane + m.fcur = 0 // lead - m.sel["lane"] = m.facets[0].values[0] + m.sel["lane"] = "mixed" // lead = mixed, the dial's first value m.cycleFacet(-1) - if got := m.sel["lane"]; got != m.facets[0].values[0] { + if got := m.sel["lane"]; got != "mixed" { t.Fatalf("left at first option wrapped to %q", got) } - last := m.facets[0].values[len(m.facets[0].values)-1] - m.sel["lane"] = last + m.sel["lane"] = "claude-led" // lead = claude, the dial's last value m.cycleFacet(1) - if got := m.sel["lane"]; got != last { + if got := m.sel["lane"]; got != "claude-led" { t.Fatalf("right at last option wrapped to %q", got) } } @@ -387,7 +389,7 @@ func TestLaunchKeys(t *testing.T) { } base := model{ sel: defaultSel(), - generated: map[string][]string{comboID(defaultSel()): rows}, + generated: map[string][]string{comboID(defaultSel(), false): rows}, } next, _ := base.Update(tea.KeyMsg{Type: tea.KeyEnter}) @@ -436,7 +438,7 @@ func TestGenConfigYAMLAgentOverrides(t *testing.T) { } m := model{ sel: defaultSel(), - generated: map[string][]string{comboID(defaultSel()): rows}, + generated: map[string][]string{comboID(defaultSel(), false): rows}, } m.sel["advisor"] = "off" got := m.genConfigYAML() @@ -468,6 +470,7 @@ func TestDefaultGlyphs(t *testing.T) { want := map[string]rune{ "runtime": 0xf108, "lane": 0xf127, "model": 0xf085, "thinking": 0xf0eb, "advisor": 0xf14e, "spark": 0xf135, "fable": 0xf02d, "main": 0xf140, "fast": 0xf0e7, + "relief": 0xf132, } g := defaultGlyphs() if len(g) != len(want) { @@ -581,7 +584,7 @@ func TestApplyAdvisorFableMain(t *testing.T) { // settings-summary line reaches the preview (the dials are visible on the // left). func TestPreviewColumn(t *testing.T) { - id := comboID(defaultSel()) + id := comboID(defaultSel(), false) m := model{ generated: map[string][]string{id: { " thinking medium · fallback on · advisor on", @@ -620,7 +623,7 @@ func TestPreviewColumn(t *testing.T) { // tests exercise the actual compositions rather than skeleton fixtures. func layoutModel() model { glyphs := defaultGlyphs() - id := comboID(defaultSel()) + id := comboID(defaultSel(), false) rows := []string{ " thinking medium · fallback on · advisor on", " default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium", @@ -967,7 +970,7 @@ func TestRepeatedResizeCrossingsPreserveState(t *testing.T) { // shrinks the panel; a facet change still resets to the top. func TestResizeScrollClamp(t *testing.T) { m := layoutModel() - id := comboID(defaultSel()) + id := comboID(defaultSel(), false) rows := []string{" thinking medium · fallback on · advisor on"} for i := range 40 { rows = append(rows, fmt.Sprintf(" role%02d gpt-5.6-terra:medium", i)) @@ -1027,7 +1030,7 @@ func TestWheelStepsFacets(t *testing.T) { t.Fatalf("fixture: lane = %q, want mixed", m.sel["lane"]) } m.applyWheelStep(tea.MouseButtonWheelLeft) - if m.sel["lane"] != "claude-led" { + if m.sel["lane"] != "gpt-led" { t.Fatalf("wheel LEFT must cycle to next option: lane = %q", m.sel["lane"]) } m.applyWheelStep(tea.MouseButtonWheelRight) @@ -1107,7 +1110,7 @@ func TestWheelInputFilterRequiresDeliberateBurst(t *testing.T) { left := admit(tea.MouseButtonWheelLeft) nm, _ = m.Update(left) m = nm.(model) - if m.sel["lane"] != "claude-led" { + if m.sel["lane"] != "gpt-led" { t.Fatalf("horizontal burst did not move lane: %q", m.sel["lane"]) } } @@ -1131,8 +1134,8 @@ func TestFilteredWheelPreservesSelectionPersistence(t *testing.T) { } nm, _ := m.Update(msg) m = nm.(model) - if got := loadSelectionState(m.selectionState, m.facets)["lane"]; got != "claude-led" { - t.Fatalf("persisted lane after wheel-left = %q, want claude-led", got) + if got := loadSelectionState(m.selectionState, m.facets)["lane"]; got != "gpt-led" { + t.Fatalf("persisted lane after wheel-left = %q, want gpt-led", got) } filter.last = time.Now().Add(-wheelGestureGap - time.Millisecond) @@ -1811,7 +1814,7 @@ func TestSectionStatePreservation(t *testing.T) { // Restoring routing recovers the prior scroll position. sc := layoutModel() - id := comboID(defaultSel()) + id := comboID(defaultSel(), false) rows := []string{" thinking medium · fallback on · advisor on"} for i := range 40 { rows = append(rows, fmt.Sprintf(" role%02d gpt-5.6-terra:medium", i)) @@ -2522,7 +2525,7 @@ func TestFirstLoadBarFill(t *testing.T) { // facet selection. func TestRoutingWheelScroll(t *testing.T) { long := layoutModel() - id := comboID(defaultSel()) + id := comboID(defaultSel(), false) rows := []string{" thinking medium · fallback on · advisor on"} for i := range 60 { rows = append(rows, fmt.Sprintf(" role%02d gpt-5.6-terra:medium", i)) @@ -3637,6 +3640,387 @@ func TestSandboxLaunchStripsInheritedBrokerEnvironment(t *testing.T) { } } +// threePoolModel builds a TUI model over the rendered three-pool catalog — +// the integration seam the launch overlay is generated from. +func threePoolModel(t *testing.T) model { + t.Helper() + c, err := catalogFrom(t, fixtureYMLDeepSeek) + if err != nil { + t.Fatalf("loadCatalog: %v", err) + } + path := filepath.Join(t.TempDir(), "generated.plain") + if err := os.WriteFile(path, []byte(c.renderCatalog()), 0o644); err != nil { + t.Fatal(err) + } + generated := loadBlocks(path) + m := model{ + generated: generated, + advisors: parseAdvisors(generated["__advisors__"]), + facts: parseFacts(generated["__models__"]), + facets: facetDefs(defaultGlyphs()), + sel: defaultSel(), + } + m.applyCatalog() + return m +} + +// TestGenConfigYAMLDeepSeekLane: a ds-led selection launches deepseek-prefixed +// roles, mirrors security-reviewer into the agent overrides, and version-gates +// task.agentAdvisor on the probed omp (17.2 hard-errors on the unknown key). +func TestGenConfigYAMLDeepSeekLane(t *testing.T) { + m := threePoolModel(t) + m.sel["lane"] = "ds-led" + m.sel["spark"], m.sel["fable"], m.sel["fast"] = "off", "off", "off" + m.sel["advisor"] = "audit" + + if _, ok := m.generated[comboID(m.sel, m.hasRelief)]; !ok { + t.Fatalf("no generated block for %s", comboID(m.sel, m.hasRelief)) + } + + m.ompMajor, m.ompMinor = 17, 3 + got := m.genConfigYAML() + for _, want := range []string{ + " default: deepseek/deepseek-v4-pro:medium\n", + " security-reviewer: ", + " agentAdvisor:\n task: \"on\"\n", + } { + if !strings.Contains(got, want) { + t.Errorf("ds-led overlay lacks %q:\n%s", want, got) + } + } + // The relief-tail fallback and cross-pool chains must carry their own + // provider prefixes, never a mis-prefixed openai-codex/deepseek-…. + if strings.Contains(got, "openai-codex/deepseek") || strings.Contains(got, "anthropic/deepseek") || + strings.Contains(got, "deepseek/gpt") || strings.Contains(got, "deepseek/claude") { + t.Errorf("mis-prefixed model in overlay:\n%s", got) + } + // ds lanes have no OpenAI priority tier even with fast on. + m.sel["lane"] = "ds-only" + m.sel["fast"] = "on" + if only := m.genConfigYAML(); strings.Contains(only, "tier:") { + t.Errorf("ds-only must not emit the OpenAI priority tier:\n%s", only) + } + + // Version gate: an unknown or 17.2 omp omits the 17.3-only key entirely. + m.sel["lane"] = "ds-led" + for _, v := range []struct{ major, minor int }{{0, 0}, {17, 2}} { + m.ompMajor, m.ompMinor = v.major, v.minor + if got := m.genConfigYAML(); strings.Contains(got, "agentAdvisor") { + t.Errorf("agentAdvisor emitted on omp %d.%d:\n%s", v.major, v.minor, got) + } + } +} + +// TestApplyCatalogGrowsLaneDial: the lane facet's values are the catalog's; +// a three-pool catalog grows ds-led/ds-only, an old two-pool one keeps the +// classic five, and a persisted lane the catalog lacks resets to the first. +func TestApplyCatalogGrowsLaneDial(t *testing.T) { + m := threePoolModel(t) + var lanes []string + for _, f := range m.facets { + if f.key == "lane" { + lanes = f.values + } + } + want := []string{"gpt-only", "gpt-led", "mixed", "claude-led", "claude-only", "ds-led", "ds-only"} + if !reflect.DeepEqual(lanes, want) { + t.Fatalf("three-pool lane dial = %v, want %v", lanes, want) + } + + m.sel["lane"] = "ds-led" + if id := comboID(m.sel, m.hasRelief); m.generated[id] == nil { + t.Fatalf("ds-led selection resolves to no block: %s", id) + } + + two := model{ + generated: map[string][]string{"gpt-only_fast_low_nosp_nofa": {" default gpt-5.6-luna:low"}}, + facets: facetDefs(defaultGlyphs()), + sel: defaultSel(), + } + two.sel["lane"] = "ds-led" // persisted against a richer catalog + two.applyCatalog() + if two.sel["lane"] != "gpt-only" { + t.Fatalf("vanished lane must reset to the dial's first value, got %q", two.sel["lane"]) + } +} + +// TestUsageBodyDeepSeekBalanceGroup: the DeepSeek usage group renders only +// when a credential exists (an absent API key is the normal state, not "not +// authenticated"), shows the prepaid balance with no bar or reset, and +// degrades to an explicit unavailable row on a failed fetch. +func TestUsageBodyDeepSeekBalanceGroup(t *testing.T) { + m := &model{broker: brokerConfig{URL: "http://broker"}, hadUsage: true} + m.accountSelections = defaultAccountSelectionState() + a := emptyAvailability() + a.ok, a.accountsOK = true, true + a.accounts[anthropicProvider] = []account{{Provider: anthropicProvider, IdentityKey: "k", Email: "a@x.test"}} + a.wins = []usageWin{{label: "Claude 5 Hour", pct: 10, secs: 60, dur: 5 * 3600, prov: anthropicProvider}} + key := accountKey{Provider: anthropicProvider, IdentityKey: "k"} + a.accountUsage[key] = a.wins + m.avail = a + + if body := stripAnsi(m.usageBodyFor(0)); strings.Contains(body, "DeepSeek") { + t.Fatalf("no credential: the DeepSeek group must be hidden entirely:\n%s", body) + } + + m.avail.deepseek = &deepseekBalance{ok: true, currency: "USD", total: "12.34"} + body := stripAnsi(m.usageBodyFor(0)) + if !strings.Contains(body, "DeepSeek") || !strings.Contains(body, "balance $12.34 USD · pay-as-you-go") { + t.Fatalf("balance group missing or malformed:\n%s", body) + } + if strings.Contains(body, "$12.34 USD") && (strings.Contains(body, "% used") && strings.Count(body, "% used") > 1) { + t.Fatalf("the balance row must not grow bar/reset chrome:\n%s", body) + } + + m.avail.deepseek = &deepseekBalance{} + if body := stripAnsi(m.usageBodyFor(0)); !strings.Contains(body, "balance unavailable") { + t.Fatalf("failed fetch must degrade to an unavailable row:\n%s", body) + } + + // A deepseek bucket can never gate a launch: unmetered providers own no + // buckets, so nothing in availability can mark one down. + if b := bucketForProviderTier(deepseekProvider, ""); b != "" { + t.Fatalf("unmetered provider grew a quota bucket: %q", b) + } + sel := selectedAvailability(m.avail, nil) + for bucket := range sel.bucket { + if strings.HasPrefix(bucket, "deepseek") { + t.Fatalf("selected availability seeded a deepseek bucket: %q", bucket) + } + } + if sel.deepseek == nil { + t.Fatal("selected availability dropped the deepseek balance") + } +} + +// TestDeepSeekOffPeakWindow pins the discount window edges (UTC 16:30-00:30) +// and that the cost meter actually prices D rungs down inside it. +func TestDeepSeekOffPeakWindow(t *testing.T) { + for _, tc := range []struct { + hhmm string + want bool + }{ + {"16:29", false}, {"16:30", true}, {"23:59", true}, + {"00:00", true}, {"00:29", true}, {"00:30", false}, {"12:00", false}, + } { + ts, _ := time.Parse("15:04", tc.hhmm) + if got := deepseekOffPeak(ts); got != tc.want { + t.Errorf("deepseekOffPeak(%s) = %v, want %v", tc.hhmm, got, tc.want) + } + } + + m := threePoolModel(t) + m.sel["lane"] = "ds-only" + prev := offPeakNow + defer func() { offPeakNow = prev }() + offPeakNow = func() time.Time { return time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC) } + peak := m.costScore() + offPeakNow = func() time.Time { return time.Date(2026, 8, 14, 20, 0, 0, 0, time.UTC) } + if off := m.costScore(); off > peak { + t.Errorf("off-peak cost score %d must not exceed peak %d", off, peak) + } + // The discount must show up in the raw weighted index, even when logScore + // clamps both readings into the same 1..5 bucket for a cheap pool. + idx := func() float64 { + var num, den float64 + m.weightedModels(m.currentRows(), func(w float64, id, lvl string) { + c, ok := m.facts[id] + if !ok { + return + } + cost := 0.25*c.in + 0.75*c.out + if m.poolOfModel(id) == "D" && deepseekOffPeak(offPeakNow().UTC()) { + cost *= deepseekOffPeakMult + } + num += w * cost + den += w + }) + return num / den + } + offIdx := idx() + offPeakNow = func() time.Time { return time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC) } + peakIdx := idx() + if offIdx >= peakIdx { + t.Errorf("off-peak weighted cost %v must be under peak %v", offIdx, peakIdx) + } +} + +// TestDeepSeekBalanceRowNotes: the low-balance cue appears exactly under the +// suggestion floor, and the off-peak tag only inside the discount window. +func TestDeepSeekBalanceRowNotes(t *testing.T) { + m := &model{} + prev := offPeakNow + defer func() { offPeakNow = prev }() + + offPeakNow = func() time.Time { return time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC) } + row := stripAnsi(m.deepseekBalanceRow(deepseekBalance{ok: true, currency: "USD", total: "1.99"})) + if !strings.Contains(row, "low") { + t.Errorf("balance under the floor must carry the low cue: %q", row) + } + if strings.Contains(row, "off-peak") { + t.Errorf("peak hours must not show the off-peak tag: %q", row) + } + row = stripAnsi(m.deepseekBalanceRow(deepseekBalance{ok: true, currency: "USD", total: "2.00"})) + if strings.Contains(row, "low") { + t.Errorf("balance at the floor is not low: %q", row) + } + offPeakNow = func() time.Time { return time.Date(2026, 8, 14, 20, 0, 0, 0, time.UTC) } + row = stripAnsi(m.deepseekBalanceRow(deepseekBalance{ok: true, currency: "USD", total: "18.03"})) + if !strings.Contains(row, "off-peak −50%") { + t.Errorf("off-peak window must surface the discount: %q", row) + } +} + +// TestSegmentGaugeRendersAsMeter: the thinking and model dials draw a notched +// ▰▱ meter with the selected word beside it, never the word list; one cell per +// step, filled to the selected depth, so ←/→ reads as a slider. +func TestSegmentGaugeRendersAsMeter(t *testing.T) { + m := model{facets: facetDefs(defaultGlyphs()), sel: defaultSel()} + rowFor := func(key string) string { + lines, _ := m.genLines() + for _, ln := range lines { + if strings.Contains(stripAnsi(ln), key) { + return stripAnsi(ln) + } + } + t.Fatalf("no %s row rendered", key) + return "" + } + + m.sel["thinking"] = "medium" + think := rowFor("thinking") + if got := strings.Count(think, "▰") + strings.Count(think, "▱"); got != 6 { + t.Fatalf("thinking gauge must keep one cell per level, got %d: %q", got, think) + } + if !strings.Contains(think, " medium ") { + t.Errorf("gauge must carry the selected word: %q", think) + } + for _, word := range []string{"minimal", "xhigh", "max"} { + if strings.Contains(think, word) { + t.Errorf("gauge must replace the word list, still shows %q: %q", word, think) + } + } + + m.sel["model"] = "normal" + mdl := rowFor("model") + if got := strings.Count(mdl, "▰") + strings.Count(mdl, "▱"); got != 3 { + t.Fatalf("model gauge must keep one cell per option, got %d cells: %q", got, mdl) + } + if got := strings.Count(mdl, "▰"); got != 2 { + t.Fatalf("model step 2/3 must light two cells, got %d lit: %q", got, mdl) + } + if !strings.Contains(mdl, " normal ") || strings.Contains(mdl, "smart") { + t.Errorf("model gauge must show only the selected word: %q", mdl) + } + + // advisor's leading "off" is the zero mark: no cell of its own, empty + // track when selected, and the levels light one cell each. + m.sel["advisor"] = "off" + adv := rowFor("advisor") + if strings.Count(adv, "▱") != 3 || strings.Count(adv, "▰") != 0 { + t.Fatalf("advisor off must render an empty three-cell track: %q", adv) + } + if !strings.Contains(adv, " off ") { + t.Errorf("advisor gauge must carry the selected word: %q", adv) + } + m.sel["advisor"] = "review" + adv = rowFor("advisor") + if strings.Count(adv, "▰") != 2 || strings.Count(adv, "▱") != 1 { + t.Fatalf("advisor review must light 2 of 3 cells: %q", adv) + } +} + +// TestLaneSplitDials: the lane facet renders as a lead row plus a blend child +// (hidden for mixed); cycling either recomposes the canonical lane, and the +// persisted state still stores only "lane". +func TestLaneSplitDials(t *testing.T) { + m := model{facets: facetDefs(defaultGlyphs()), sel: defaultSel()} + + // mixed: a single lead row, no blend child, no lane word list. + vf := m.visibleFacets() + if vf[0].key != "lead" { + t.Fatalf("first dial = %q, want lead", vf[0].key) + } + if vf[1].key == "blend" { + t.Fatal("mixed must not render a blend child") + } + wantLeads := []string{"mixed", "gpt", "claude"} + if !reflect.DeepEqual(vf[0].values, wantLeads) { + t.Fatalf("lead values = %v, want %v", vf[0].values, wantLeads) + } + + // Cycling lead off mixed lands on the -led lane and grows the blend child. + m.cycleFacet(1) // mixed → gpt (cursor starts on the lead row) + if m.sel["lane"] != "gpt-led" { + t.Fatalf("lead change composed lane %q, want gpt-led", m.sel["lane"]) + } + vf = m.visibleFacets() + if vf[1].key != "blend" { + t.Fatalf("non-mixed lead must render the blend child, got %q", vf[1].key) + } + + // Cycling blend led → only composes the pure lane. + m.fcur = 1 + m.cycleFacet(1) + if m.sel["lane"] != "gpt-only" { + t.Fatalf("blend change composed lane %q, want gpt-only", m.sel["lane"]) + } + + // A three-pool dial grows a ds lead; lead/blend never persist. + for i := range m.facets { + if m.facets[i].key == "lane" { + m.facets[i].values = []string{"gpt-only", "gpt-led", "mixed", "claude-led", "claude-only", "ds-led", "ds-only"} + } + } + vf = m.visibleFacets() + if !reflect.DeepEqual(vf[0].values, []string{"mixed", "gpt", "claude", "ds"}) { + t.Fatalf("three-pool lead values = %v", vf[0].values) + } + choices := selectionChoices(m.sel, m.facets) + if _, ok := choices["lead"]; ok { + t.Error("lead is a derived dial and must not persist") + } + if _, ok := choices["blend"]; ok { + t.Error("blend is a derived dial and must not persist") + } + if choices["lane"] != "gpt-only" { + t.Errorf("persisted lane = %q, want gpt-only", choices["lane"]) + } +} + +// TestAdvisorAuditReliefTail: audit is the advisor's heavyweight setting, so +// it follows the same metered logic as the heavyweight roles — with relief on, +// a metered-led blend's audit chain ends on the optional pool's audit rung; +// relief off, lighter levels, and optional-led lanes stay untouched. +func TestAdvisorAuditReliefTail(t *testing.T) { + m := threePoolModel(t) + m.sel["lane"] = "mixed" + m.sel["relief"] = "on" + + audit := m.advisorChain("audit") + if len(audit) == 0 { + t.Fatal("no audit chain on mixed") + } + if !strings.HasPrefix(audit[len(audit)-1], "deepseek-") { + t.Fatalf("relief-on audit chain must tail into the optional pool: %v", audit) + } + + m.sel["relief"] = "off" + if got := m.advisorChain("audit"); strings.HasPrefix(got[len(got)-1], "deepseek-") { + t.Fatalf("relief-off audit chain must stay metered: %v", got) + } + + m.sel["relief"] = "on" + if got := m.advisorChain("review"); strings.HasPrefix(got[len(got)-1], "deepseek-") { + t.Fatalf("lighter advisor levels take no tail: %v", got) + } + + m.sel["lane"] = "ds-led" // optional-led lane already spends DeepSeek deliberately + if got := m.advisorChain("audit"); strings.HasPrefix(got[len(got)-1], "deepseek-") { + t.Fatalf("relief does not apply on an optional-led lane: %v", got) + } +} + // ── pool R surface ──────────────────────────────────────────────────────────── func TestModelReMatchesProviderScopedIds(t *testing.T) { @@ -3721,7 +4105,6 @@ func TestPrefixedLeveledTokens(t *testing.T) { // End to end: the emitted config must qualify every reference with the // catalog's pool, and the ox-led advisor must carry its cross-pool net -// (Claude lead, GPT glance rung, then the free pool) instead of a bare lead. func TestGenConfigYAMLOxLed(t *testing.T) { blocks := loadBlocks("/tmp/grid-ox.plain") if len(blocks) == 0 { @@ -3734,7 +4117,11 @@ func TestGenConfigYAMLOxLed(t *testing.T) { glyphs: defaultGlyphs(), facets: facetDefs(defaultGlyphs()), sel: map[string]string{"lane": "ox-led", "model": "smart", "thinking": "high", - "advisor": "glance", "spark": "off", "fable": "off", "main": "off", "fast": "off"}, + "advisor": "glance", "spark": "off", "fable": "off", "main": "off", "fast": "off", + "relief": "on"}, + // An ox catalog carries an optional pool, so its rendered combos are + // relief-segmented — mirror what applyCatalog would derive. + hasRelief: true, } cfg := m.genConfigYAML() if strings.Contains(cfg, "openai-codex/stealth") || strings.Contains(cfg, "anthropic/stealth") { @@ -3743,7 +4130,7 @@ func TestGenConfigYAMLOxLed(t *testing.T) { if !strings.Contains(cfg, "openrouter/stealth/ox-alpha:") { t.Errorf("no pool-qualified ox reference:\n%s", cfg) } - adv := m.applyAdvisor(m.generated[comboID(m.sel)], "glance") + adv := m.applyAdvisor(m.generated[comboID(m.sel, m.hasRelief)], "glance") advisorRow := "" for _, r := range adv { if roleOf(r) == "advisor" { diff --git a/manager.go b/manager.go index d537386..5d9f7ec 100644 --- a/manager.go +++ b/manager.go @@ -10,7 +10,15 @@ import ( "github.com/charmbracelet/lipgloss" ) -var managerProviders = []string{"anthropic", "openai-codex"} +// managerProviders is the account manager's provider order — registry order, +// so it can never disagree with the Usage panel again. +var managerProviders = func() []string { + ids := make([]string, len(providerRegistry)) + for i := range providerRegistry { + ids[i] = providerRegistry[i].ID + } + return ids +}() // managerPresetState holds only transient account-manager UI state. Persisted // selections live in model.accountSelections; a draft is never launch-visible. @@ -382,11 +390,17 @@ type managerProviderRange struct { const ( managerProviderBoxBorderWidth = 2 managerProviderBoxFrameWidth = 4 - managerAnthropicColor = "#ff9f52" - managerOpenAIColor = "#62a7ff" ) func managerAccountLabel(a account) string { + if a.apiKey != "" { + // API-key credentials carry no broker identity; account-pool routing + // is OAuth-only, so this row is display-only. + if a.credentialID != "" { + return "API key · credential #" + a.credentialID + } + return "API key" + } if a.Email != "" { return a.Email } @@ -397,16 +411,16 @@ func managerAccountLabel(a account) string { } func managerProviderColor(provider string) string { - if provider == "openai-codex" { - return managerOpenAIColor + if p := providerByID(provider); p != nil { + return p.Color } - return managerAnthropicColor + return "#8a93a6" } func managerProviderHeading(provider string) string { - label := "Anthropic" - if provider == "openai-codex" { - label = "OpenAI" + label := provider + if p := providerByID(provider); p != nil { + label = p.AccountLabel } return lipgloss.NewStyle(). Foreground(lipgloss.Color(managerProviderColor(provider))). @@ -566,10 +580,15 @@ func (m model) managerLines(width int) []managerLine { selectable := 0 group := 0 for _, provider := range managerProviders { + accounts := m.avail.accounts[provider] + if p := providerByID(provider); p != nil && !p.Metered && len(accounts) == 0 { + // An absent API key is the normal state for an unmetered provider + // — no heading, no "not authenticated" noise. + continue + } lines = append(lines, managerLine{ text: managerProviderHeading(provider), selectable: -1, group: -1, provider: provider, }) - accounts := m.avail.accounts[provider] switch { case !m.avail.accountsOK: lines = append(lines, managerLine{ @@ -591,6 +610,8 @@ func (m model) managerLines(width int) []managerLine { status := "enabled" if disabled { status = "off" + } else if a.apiKey != "" { + status = "api key" } else if a.IdentityKey == "" { status = "unavailable" } @@ -648,7 +669,7 @@ func (m model) managerLines(width int) []managerLine { provider: provider, }) } - if len(usageSpecs) == 0 { + if len(usageSpecs) == 0 && a.apiKey == "" { unavailable := " " + stWarn.Render("usage unavailable") if m.avail.accountsStale { unavailable += " " + stWarn.Render("cached") @@ -670,6 +691,16 @@ func (m model) managerLines(width int) []managerLine { }) } } + // The DeepSeek prepaid balance is the api-key row's only usage + // datum — mirror the Usage panel's row under the credential. + if a.apiKey != "" && provider == deepseekProvider && m.avail.deepseek != nil { + lines = append(lines, managerLine{ + text: managerClipCell(" "+m.deepseekBalanceRow(*m.avail.deepseek), lineWidth), + selectable: -1, + group: group, + provider: provider, + }) + } group++ if accountIndex+1 < len(accounts) { lines = append(lines, managerLine{ diff --git a/manager_test.go b/manager_test.go index acb9289..1f4bfcc 100644 --- a/manager_test.go +++ b/manager_test.go @@ -1486,7 +1486,14 @@ func TestManagerUsageInlineRequiresExactMeasuredFit(t *testing.T) { footer := clikit.SeparatedSections(m.w, usage, controls) unspacedExact := lipgloss.Height(strings.Repeat("\n", topGap)+unspacedAccounts) + lipgloss.Height(footer) - if got, want := exact-unspacedExact, len(m.managerAccounts())-len(managerProviders); got != want { + rendered := 0 + for _, provider := range managerProviders { + if p := providerByID(provider); p != nil && !p.Metered && len(m.avail.accounts[provider]) == 0 { + continue // unmetered provider with no accounts renders no group + } + rendered++ + } + if got, want := exact-unspacedExact, len(m.managerAccounts())-rendered; got != want { t.Fatalf("exact-fit geometry counted %d inter-account breathing rows, want %d", got, want) } @@ -1781,3 +1788,37 @@ func TestManagerUsageControlsFollowContextWithoutPresetSaveCollision(t *testing. t.Fatalf("naming modal leaked Usage controls: %s", naming) } } + +// TestManagerDeepSeekBalanceRow: the DeepSeek box mirrors the Usage panel's +// prepaid balance under the display-only credential row - and degrades to the +// explicit unavailable text rather than dropping the row, so a flaky upstream +// stays visible. +func TestManagerDeepSeekBalanceRow(t *testing.T) { + m := managerTestModel(t) + m.avail.accounts[deepseekProvider] = []account{ + {Provider: deepseekProvider, apiKey: "sk-test", credentialID: "19"}, + } + m.avail.deepseek = &deepseekBalance{ok: true, currency: "USD", total: "18.03"} + + flat := func() string { + var b strings.Builder + for _, ln := range m.managerLines(100) { + b.WriteString(stripAnsi(ln.text)) + b.WriteString("\n") + } + return b.String() + } + + view := flat() + if !strings.Contains(view, "API key · credential #19") { + t.Fatalf("manager lost the DeepSeek credential row:\n%s", view) + } + if !strings.Contains(view, "balance $18.03 USD · pay-as-you-go") { + t.Errorf("manager DeepSeek box lacks the balance row:\n%s", view) + } + + m.avail.deepseek = &deepseekBalance{ok: false} + if view := flat(); !strings.Contains(view, "balance unavailable") { + t.Errorf("failed balance must render as unavailable, not vanish:\n%s", view) + } +} diff --git a/model.go b/model.go new file mode 100644 index 0000000..f75056d --- /dev/null +++ b/model.go @@ -0,0 +1,172 @@ +package main + +import ( + "os/exec" + "regexp" + "strconv" + "time" + + tea "github.com/charmbracelet/bubbletea" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/spinner" + "github.com/charmbracelet/bubbles/viewport" +) + +type model struct { + generated map[string][]string + advisors map[string][]string // "level/ctx" → advisor model chain + facts map[string]modelFact // model id → cost ($/1M) + curated speed (tok/s) + avail availability + glyphs map[string]string + runtimeTargets []runtimeTarget + + // Catalog capability, phrased as absence so the zero value keeps every dial: + // a model with no catalog yet (the onboarding shell, tests) must behave as it + // always did. applyCatalog sets these only from a catalog it actually read. + noSpark bool // no _sp_ combos exist — hide the spark dial and force it off + noFable bool // no _fa_/_famain_ combos — same for fable and its main child + // hasRelief is positive-polarity: the relief segment only exists in + // catalogs with an optional pool, so the zero value keeps old ids intact. + hasRelief bool // _rel_/_norel combos exist — show the relief dial + + depth int // 0 lead · 1 full + collapse bool // p: hide the Routing section + showResult bool // in collapsed mode: show the preview full-width + hideUsage bool // s: hide the Usage section (atyrode/dotfiles#198); fetch state keeps running unseen + showUsage bool // narrow mode: show Usage full-screen instead of silently shedding it + fullUsageIDs bool // i: expand compact Usage identities to full account addresses + + facets []facet + fcur int + sel map[string]string + selectionState string // CODE_SELECTION_STATE; empty keeps standalone runs stateless + + vp viewport.Model + spin spinner.Model + help help.Model + w, h int + rdy bool + + broker brokerConfig + usageCache string + accountState string + accountSelections accountSelectionState + accountErr string + manager bool + mgrCursor int + managerPreset managerPresetState + + fetching bool // a usage fetch is in flight (manual or auto) + nextRefresh time.Time // when the next auto-refresh fires + hadUsage bool // a successful central fetch has landed + usageStale bool // the last central refresh failed; prior data is retained + barAnim int // first-load fill frame (1..barAnimSteps-1 = partial); 0 = inactive, bars at full value + + launchManaged bool // m: run CODE_OMP with no overlay (the managed defaults) + launchUntrusted bool // u: run the CODE_OMP_UNTRUSTED sandbox + launchRuntime string // delegated local runtime target selected via CODE_RUNTIME_BROKER + hasSandbox bool // a sandbox binary exists; gates the u key + genConfig string // generated config YAML to launch omp with (generator Enter) + firstPrompt string // prompt from the suggest box, forwarded as omp's first message + savedSel map[string]string // selection snapshot before a live suggest preview (for revert) + // The probed omp version (omp/ from `omp --version`), fetched + // async at startup. Zero = unknown: 17.3-only overlay keys are omitted so + // a lagging CODE_OMP wrapper never hard-errors at launch. + ompMajor, ompMinor int +} + +// usage auto-refreshes on this cadence; a 1s tick drives the countdown. +const refreshEvery = 5 * time.Minute + +type refreshTickMsg struct{} + +func tickCmd() tea.Cmd { + return tea.Tick(time.Second, func(time.Time) tea.Msg { return refreshTickMsg{} }) +} + +// usageMsg carries the single central account/usage snapshot fetched off the +// main thread so startup and refresh never block the TUI. +type usageMsg struct { + avail availability +} + +func fetchUsageCmd(broker brokerConfig) tea.Cmd { + if broker.URL == "" || broker.Token == "" { + return nil + } + return func() tea.Msg { return usageMsg{avail: loadAvailability(broker)} } +} + +func (m *model) startUsageFetch() tea.Cmd { + cmd := fetchUsageCmd(m.broker) + if cmd != nil { + m.fetching = true + } + return cmd +} + +// First-load bar fill grows each central usage bar from empty when the first +// successful fetch replaces the loading skeleton. A dedicated bounded tick +// sequence renders labels and numbers immediately and animates only the fill. +// Manual and automatic refreshes never re-run it. +const ( + barAnimSteps = 8 + barAnimInterval = 25 * time.Millisecond +) + +// barAnimMsg advances the first-load fill to the given frame (2..barAnimSteps). +type barAnimMsg struct{ step int } + +func barAnimCmd(step int) tea.Cmd { + return tea.Tick(barAnimInterval, func(time.Time) tea.Msg { return barAnimMsg{step} }) +} + +// ompVersionMsg carries the probed omp version; ok=false leaves it unknown. +type ompVersionMsg struct { + major, minor int + ok bool +} + +// ompVersionRe parses `omp/....` anywhere in --version output. +var ompVersionRe = regexp.MustCompile(`omp/(\d+)\.(\d+)`) + +// probeOmpVersionCmd resolves the same binary Enter launches and asks it for +// its version, off the main thread. A drift guard, not a feature flag: any +// failure just reads as "unknown" and version-gated keys stay off. +func probeOmpVersionCmd() tea.Cmd { + return func() tea.Msg { + path, err := resolveLaunchPath("CODE_OMP", []string{"omp"}) + if err != nil { + return ompVersionMsg{} + } + out, err := exec.Command(path, "--version").Output() + if err != nil { + return ompVersionMsg{} + } + match := ompVersionRe.FindSubmatch(out) + if match == nil { + return ompVersionMsg{} + } + major, _ := strconv.Atoi(string(match[1])) + minor, _ := strconv.Atoi(string(match[2])) + return ompVersionMsg{major: major, minor: minor, ok: true} + } +} + +// ompVersionAtLeast reports a probed version ≥ major.minor; unknown is never +// "at least" anything. +func (m model) ompVersionAtLeast(major, minor int) bool { + if m.ompMajor == 0 && m.ompMinor == 0 { + return false + } + return m.ompMajor > major || (m.ompMajor == major && m.ompMinor >= minor) +} + +func (m model) Init() tea.Cmd { + cmds := []tea.Cmd{probeOmpVersionCmd()} + if m.broker.URL != "" && m.broker.Token != "" { + cmds = append(cmds, m.startUsageFetch(), m.spin.Tick, tickCmd()) + } + return tea.Batch(cmds...) +} diff --git a/onboarding.go b/onboarding.go index f123e22..d32a625 100644 --- a/onboarding.go +++ b/onboarding.go @@ -112,7 +112,7 @@ func (o onboarding) fail(err error) onboarding { case strings.Contains(o.errMsg, "omp models"): o.remedy = "Install oh-my-pi (omp) and make sure it is on PATH, then press enter to retry." case strings.Contains(o.errMsg, "need 3"): - o.remedy = "code assumes omp has BOTH Anthropic and OpenAI providers set up. Add the missing provider (or hand-write " + o.modelsPath + "), then press enter to retry." + o.remedy = "code assumes omp has " + requiredProviderNames() + " set up. Add the missing provider (or hand-write " + o.modelsPath + "), then press enter to retry." default: o.remedy = "Fix the above (the models file lives at " + o.modelsPath + "), then press enter to retry." } diff --git a/providers.go b/providers.go new file mode 100644 index 0000000..deda353 --- /dev/null +++ b/providers.go @@ -0,0 +1,303 @@ +package main + +// The provider registry: the single table every provider-aware code path +// consults. Adding a provider is a new entry here (plus a lane pool letter); +// nothing else in the tree may hard-code a provider id, pool letter, model +// prefix, bucket name, or brand color. + +import "strings" + +const ( + anthropicProvider = "anthropic" + openAIProvider = "openai-codex" + deepseekProvider = "deepseek" + openRouterProvider = "openrouter" +) + +// specialFacet is a provider's tier-scoped lead: a facet dial ("spark", +// "fable") that swaps a dedicated ladder tier in as a role lead, drawing a +// separate quota bucket. +type specialFacet struct { + Facet string // facet key: "spark" | "fable" + Tier int // ladder index: 0 | 4 + Bucket string // bucket suffix; full name = BucketBase + "-" + Bucket +} + +type providerDesc struct { + ID string // broker/omp provider id + Aliases []string // extra omp ids mapping to the same pool + Pool string // catalog pool letter + Lane string // lane-name segment used in lane facet values + Label string // UI label (product name: Codex, Claude, DeepSeek) + AccountLabel string // account-manager heading (company name: OpenAI, Anthropic) + Color string // UI heading hex + LaneOnly string // deeper shade for the pure "-only" lane + LaneLed string // lighter shade for the "-led" lane + PaintRGB [3]float64 // routing-token tint base (paintModel) + ModelPrefixes []string // model-id prefix matchers (legacy-catalog fallback) + BucketBase string // bucket name base; main bucket = BucketBase + "-main" + Metered bool // false => no quota windows; never gates launches + Required bool // generate-init: must fill tiers 1..3, else hard error + StrictLadder bool // optional-pool ladder must be complete when declared (no nearest-rung borrow) + CrossTo string // pool this pool's crossing roles divert to (reviewer, advisor) + SkeletonWins []string // usage-panel loading skeleton windows; nil when unmetered + Special []specialFacet + ServiceTier [2]string // omp overlay `tier:` key/value when the `fast` facet is on +} + +// providerRegistry order is the account/usage display order (Claude first, +// matching the established Usage panel). Generator-side pool iteration uses +// fallbackPoolOrder instead, so the two orders are independent knobs. +var providerRegistry = []providerDesc{ + {ID: anthropicProvider, Pool: "A", Lane: "claude", Label: "Claude", AccountLabel: "Anthropic", + Color: "#ff9f52", LaneOnly: "#ff8534", LaneLed: "#ffb277", PaintRGB: [3]float64{240, 160, 105}, + ModelPrefixes: []string{"claude", "sonnet", "haiku", "opus"}, BucketBase: "claude", + Metered: true, Required: true, CrossTo: "O", + SkeletonWins: []string{"5h", "7d", "7d fable"}, + Special: []specialFacet{{Facet: "fable", Tier: 4, Bucket: "fable"}}}, + {ID: openAIProvider, Aliases: []string{"openai"}, Pool: "O", Lane: "gpt", Label: "Codex", AccountLabel: "OpenAI", + Color: "#62a7ff", LaneOnly: "#3f8ef0", LaneLed: "#7ab6ff", PaintRGB: [3]float64{110, 170, 240}, + ModelPrefixes: []string{"gpt", "codex"}, BucketBase: "codex", + Metered: true, Required: true, CrossTo: "A", + SkeletonWins: []string{"5h", "7d"}, + Special: []specialFacet{{Facet: "spark", Tier: 0, Bucket: "spark"}}, + ServiceTier: [2]string{"openai", "priority"}}, + {ID: deepseekProvider, Pool: "D", Lane: "ds", Label: "DeepSeek", AccountLabel: "DeepSeek", + Color: "#4d6bfe", LaneOnly: "#3a55f0", LaneLed: "#7d92ff", PaintRGB: [3]float64{77, 107, 254}, + ModelPrefixes: []string{"deepseek"}, BucketBase: "deepseek", CrossTo: "O"}, + {ID: openRouterProvider, Pool: "R", Lane: "ox", Label: "Ox Alpha", AccountLabel: "OpenRouter", + Color: "#5fce96", LaneOnly: "#1f9d5b", LaneLed: "#5fce96", PaintRGB: [3]float64{95, 206, 150}, + ModelPrefixes: []string{"stealth/ox-alpha"}, BucketBase: "openrouter", + StrictLadder: true, CrossTo: "A"}, +} + +// fallbackPoolOrder is the generator-side pool order: non-lead pools appear in +// fallback chains (and cross-provider role dispatch) in this order. +var fallbackPoolOrder = []string{"O", "A", "D", "R"} + +// advisorPoolOrder decides the advisor's context: the first entry that is not +// the lead pool (pure lanes keep their own pool). +var advisorPoolOrder = []string{"A", "O", "D", "R"} + +// providerByID matches a provider id or alias; nil when unknown. +func providerByID(id string) *providerDesc { + for i := range providerRegistry { + p := &providerRegistry[i] + if p.ID == id { + return p + } + for _, a := range p.Aliases { + if a == id { + return p + } + } + } + return nil +} + +func providerByPool(pool string) *providerDesc { + for i := range providerRegistry { + if providerRegistry[i].Pool == pool { + return &providerRegistry[i] + } + } + return nil +} + +// providerByModel resolves a model id by its longest matching prefix; nil when +// no provider claims it. It is the legacy-catalog fallback — catalogs now +// carry an explicit provider column that wins over this guess. +func providerByModel(modelID string) *providerDesc { + var best *providerDesc + bestLen := 0 + for i := range providerRegistry { + for _, pre := range providerRegistry[i].ModelPrefixes { + if len(pre) > bestLen && strings.HasPrefix(modelID, pre) { + best, bestLen = &providerRegistry[i], len(pre) + } + } + } + return best +} + +// providerByLane maps a lane facet value ("gpt-only", "ds-led") to its +// provider; nil for "mixed" or an unknown lane. +func providerByLane(lane string) *providerDesc { + seg, _, ok := strings.Cut(lane, "-") + if !ok { + return nil + } + for i := range providerRegistry { + if providerRegistry[i].Lane == seg { + return &providerRegistry[i] + } + } + return nil +} + +// providerBySpecial finds the provider owning a special-tier facet ("spark", +// "fable"); nil when no provider declares it. +func providerBySpecial(facet string) *providerDesc { + for i := range providerRegistry { + for _, s := range providerRegistry[i].Special { + if s.Facet == facet { + return &providerRegistry[i] + } + } + } + return nil +} + +// special returns the provider's special-tier declaration for a facet. +func (p *providerDesc) special(facet string) *specialFacet { + for i := range p.Special { + if p.Special[i].Facet == facet { + return &p.Special[i] + } + } + return nil +} + +// mainBucket is the provider's ordinary quota window name. +func (p *providerDesc) mainBucket() string { return p.BucketBase + "-main" } + +// buckets lists every quota bucket the provider draws: main first, then each +// special tier's window. +func (p *providerDesc) buckets() []string { + out := []string{p.mainBucket()} + for _, s := range p.Special { + out = append(out, p.BucketBase+"-"+s.Bucket) + } + return out +} + +// meteredProviderIDs lists the quota-windowed providers in registry (display) +// order — the providers the usage panel, skeleton, and launch gating track. +func meteredProviderIDs() []string { + var out []string + for i := range providerRegistry { + if providerRegistry[i].Metered { + out = append(out, providerRegistry[i].ID) + } + } + return out +} + +// optionalPoolLabels names the pay-as-you-go pools ("DeepSeek"), joined for +// display — the pools relief can spill into. +func optionalPoolLabels() string { + var out []string + for i := range providerRegistry { + if !providerRegistry[i].Required { + out = append(out, providerRegistry[i].Label) + } + } + return strings.Join(out, "/") +} + +// poolDeclaresSpecialTier reports whether the pool's provider declares a +// special facet at the given ladder tier (O's spark at 0, A's fable at 4). +func poolDeclaresSpecialTier(pool string, tier int) bool { + p := providerByPool(pool) + if p == nil { + return false + } + for _, s := range p.Special { + if s.Tier == tier { + return true + } + } + return false +} + +// requiredPoolLanes is the lane list over just the Required pools — the +// classic five-lane dial, and the pre-catalog default. +func requiredPoolLanes() []string { + var pools []string + for _, pool := range fallbackPoolOrder { + if p := providerByPool(pool); p != nil && p.Required { + pools = append(pools, pool) + } + } + return laneOrderForPools(pools) +} + +// lanePure reports whether a lane is a single-pool lane. +func lanePure(lane string) bool { return strings.HasSuffix(lane, "-only") } + +// laneHasPool reports whether a lane's pool-set contains the pool: a pure lane +// hosts only its own pool; every other lane (led, mixed) hosts all pools. +func laneHasPool(lane, pool string) bool { + if !lanePure(lane) { + return true + } + p := providerByLane(lane) + return p != nil && p.Pool == pool +} + +// laneReliefApplies reports whether relief tails are a real choice on this +// lane: a metered-led blend can spill into a pay-as-you-go pool, so the +// relief dial exists there. Pure lanes never take tails, and a lane led by +// the optional pool already spends it deliberately. +func laneReliefApplies(lane string) bool { + if lanePure(lane) { + return false + } + if p := providerByLane(lane); p != nil && !p.Required { + return false + } + return true +} + +// laneSplit decomposes a lane into the two dials the TUI renders: the lead +// (a provider's lane segment, or "mixed") and the blend ("led" | "only"). +// mixed has no blend of its own; it reports "led" so a later lead change +// lands on the -led lane. +func laneSplit(lane string) (lead, blend string) { + if lane == "mixed" { + return "mixed", "led" + } + blend = "led" + if lanePure(lane) { + blend = "only" + } + if p := providerByLane(lane); p != nil { + return p.Lane, blend + } + return lane, blend +} + +// laneJoin is laneSplit's inverse: the canonical lane a lead+blend pair names. +func laneJoin(lead, blend string) string { + if lead == "mixed" { + return "mixed" + } + return lead + "-" + blend +} + +// laneHostsSpecial reports whether a special-tier facet ("spark", "fable") can +// be on for the lane: its provider's pool must be in the lane's pool-set. +func laneHostsSpecial(lane, facet string) bool { + p := providerBySpecial(facet) + return p != nil && laneHasPool(lane, p.Pool) +} + +// laneOrderForPools is the canonical lane-dial order over the given pools +// (subset of fallbackPoolOrder, in that order): the first pool's pure lane +// leads, mixed sits in the middle, the second pool mirrors, and every further +// pool appends its led/only pair. +func laneOrderForPools(pools []string) []string { + var lanes []string + name := func(pool string) string { return providerByPool(pool).Lane } + if len(pools) == 0 { + return nil + } + lanes = append(lanes, name(pools[0])+"-only", name(pools[0])+"-led") + if len(pools) > 1 { + lanes = append(lanes, "mixed", name(pools[1])+"-led", name(pools[1])+"-only") + } + for _, pool := range pools[2:] { + lanes = append(lanes, name(pool)+"-led", name(pool)+"-only") + } + return lanes +} diff --git a/render.go b/render.go new file mode 100644 index 0000000..5f5007c --- /dev/null +++ b/render.go @@ -0,0 +1,161 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/lipgloss" +) + +// ── routing render (depth: 0 lead · 1 full) ────────────────────────────────── +// renderRoute lays out each role's chain, wrapping cleanly at `width`: when a +// chain doesn't fit, it breaks after an arrow and the continuation is indented +// to align under the first model, so it reads as one hanging block rather than a +// ragged wrap. Down (maxed/unauthed) models are struck through — which bucket a +// model draws from is a catalog fact, hence the receiver. +func (m model) renderRoute(rows []string, depth int, a availability, width int) string { + if width < 24 { + width = 24 + } + arrow := stDim.Render(" → ") + var out []string + for _, r := range rows { + locs := modelRe.FindAllStringIndex(r, -1) + if len(locs) == 0 { // a note/meta line with no models — pass through + out = append(out, colorizeRoute(r)) + continue + } + label := r[:locs[0][0]] // role + its alignment padding, kept verbatim + labelW := lipgloss.Width(label) + indent := strings.Repeat(" ", labelW) + + type tok struct { + text string + down bool + } + var toks []tok + for _, loc := range locs { + mt := r[loc[0]:loc[1]] + toks = append(toks, tok{mt, a.ok && a.down(m.bucketFor(mt))}) + } + // how many to show: full → all; lead → primary, or up to the first live + // model when the lead is down (the one that actually runs). + last := len(toks) - 1 + if depth == 0 { + last = 0 + for i, t := range toks { + last = i + if !t.down { + break + } + } + } + // render a token as it displays (short name), returning the styled + // string and its display width — struck if the model is down. + render := func(t tok) (string, int) { + c := strings.LastIndexByte(t.text, ':') + short := shortModel(t.text[:c]) + t.text[c:] + if t.down { + s := stStruck.Render(short) + return s, lipgloss.Width(s) + } + s := colorizeRoute(t.text) // paintModel shortens + colours + return s, lipgloss.Width(s) + } + s0, w0 := render(toks[0]) + line, lineW := label+s0, labelW+w0 + for i := 1; i <= last; i++ { + si, wi := render(toks[i]) + // Reserve 2 cols for a trailing " →" whenever more models follow, so a + // break line's continuation arrow always fits within width instead of + // being clipped at the edge; the final model needs no such reserve. + budget := width + if i < last { + budget -= 2 + } + if lineW+3+wi > budget { // won't fit — break after a trailing arrow + out = append(out, line+stDim.Render(" →")) + line, lineW = indent+si, labelW+wi + } else { + line += arrow + si + lineW += 3 + wi + } + } + out = append(out, line) + } + return strings.Join(out, "\n") + "\n" +} + +// splitMeta peels the "thinking … · fallback … · advisor …" summary line off a +// routing block (if present), returning it trimmed plus the remaining role rows. +func splitMeta(rows []string) (string, []string) { + if len(rows) > 0 && strings.Contains(rows[0], "·") && !modelRe.MatchString(rows[0]) { + return strings.TrimSpace(rows[0]), rows[1:] + } + return "", rows +} + +func (m *model) syncPreview() { + m.syncPreviewAt(0) +} + +// syncPreviewKeepScroll re-renders while preserving the scroll position where +// still valid — resizes and background usage refreshes must never yank the view. +func (m *model) syncPreviewKeepScroll() { + m.syncPreviewAt(m.vp.YOffset) +} + +func (m *model) syncPreviewAt(yoff int) { + if !m.rdy || m.collapse { + return + } + rw := m.vp.Width + // No settings summary here: every dial is already visible (selected) in the + // generator list on the left, so the preview shows only what that selection + // produces — the role → model routing itself. + var b strings.Builder + if target, local := m.selectedRuntime(); local { + b.WriteString(lipgloss.NewStyle().Bold(true).Render(target.Label) + "\n") + b.WriteString(stDim.Render(target.statusLine()) + "\n\n") + if target.ContextWindow > 0 { + b.WriteString(fmt.Sprintf("context %dk tokens\n\n", target.ContextWindow/1000)) + } + // Same grammar as a hosted profile: every role the broker's generated + // profile routes (all of them but the advisor, which stays off), led by + // the one local model at the dialed thinking — the flag is forwarded + // verbatim and omp clamps it to what the model offers. + rows := []string{fmt.Sprintf(" thinking %s · fallback off · advisor off", m.sel["thinking"])} + for _, r := range genRoleOrder { + if r == "advisor" { + continue + } + marker := " " + if genAgentRoles[r] { + marker = "●" + } + rows = append(rows, fmt.Sprintf(" %s %-10s %s:%s", marker, r, target.Model, m.sel["thinking"])) + } + b.WriteString(m.renderRoute(rows, m.depth, m.selectedLaunchAvailability(), rw)) + b.WriteString("\n" + stDim.Render("broker-owned profile · cloud auth excluded · weights provisioned by the runtime") + "\n") + content := lipgloss.NewStyle().MaxWidth(m.vp.Width).Render(b.String()) + m.vp.SetContent(content) + m.vp.SetYOffset(yoff) + return + } + id := comboID(m.sel, m.hasRelief) + if base, ok := m.generated[id]; ok { + _, roles := splitMeta(base) + roles = m.applyAdvisor(roles, m.sel["advisor"]) + b.WriteString(m.renderRoute(roles, m.depth, m.selectedLaunchAvailability(), rw)) + } else { + b.WriteString(stDim.Render("no profile for this combination") + "\n") + } + // clip (don't wrap) to pane width — renderRoute already wrapped the chains. + content := lipgloss.NewStyle().MaxWidth(m.vp.Width).Render(b.String()) + m.vp.SetContent(content) + m.vp.SetYOffset(yoff) // clamps into the new content and viewport height +} + +// footer is the pinned bottom block: the usage panel (when this composition +// keeps Usage in the footer) then the controls help, each under a rule. Built +// once here so relayout and View stay in sync. diff --git a/routing.go b/routing.go new file mode 100644 index 0000000..2448ca5 --- /dev/null +++ b/routing.go @@ -0,0 +1,693 @@ +package main + +import ( + "fmt" + "math" + "slices" + "sort" + "strings" + "time" + + clikit "github.com/atyrode/cli-kit" +) + +// ── cost + speed meters ────────────────────────────────────────────────────── +// A profile's price and pace are dominated by the models on its heaviest roles, +// so each role is weighted by the token volume it drives over a session: the +// default agent and its task sub-agents move the needle; commit/tiny barely +// register — so Fable-as-commit stays cheap while Fable-as-default is dear (and +// slow). Per role, cost blends input+output pricing while speed reads the model's +// effective throughput (tok/s folded with time-to-first-token — see effTPS); both +// scale with thinking effort (more reasoning = pricier + slower) and take OpenAI's +// priority tier under fast mode (pricier but quicker). The weighted averages map +// onto 1..5 log scales (both perceived multiplicatively), calibrated across every +// valid facet × advisor × fast combination. Every role the generator emits must +// appear here: weightedModels silently skips a role it cannot weigh, so an +// omission drops that model out of both meters with no trace. +var roleWeight = map[string]float64{ + "default": 10, "task": 6, "reviewer": 3, "sonic": 3, "plan": 3, "advisor": 4, "slow": 2, + "designer": 2, "librarian": 2, "scout": 2, "smol": 1, "tiny": 0.5, "commit": 0.5, "vision": 0.5, + // security-reviewer routes like reviewer but is spawned far more rarely + // (ad-hoc scans), so it barely moves the needle. + "security-reviewer": 1, +} +var thinkMult = map[string]float64{ // reasoning tokens grow with effort → pricier + "minimal": 0.6, "low": 0.8, "medium": 1.0, "high": 1.3, "xhigh": 1.6, "max": 2.0, +} +var thinkSpeed = map[string]float64{ // more reasoning before the answer → slower + "minimal": 1.4, "low": 1.2, "medium": 1.0, "high": 0.8, "xhigh": 0.65, "max": 0.5, +} + +const ( + priorityMult = 1.9 // OpenAI priority tier costs more under fast mode … + fastSpeed = 1.3 // … but responds quicker +) + +// Ln endpoints of the grid-wide min/max weighted indices, calibrated over every +// valid facet × advisor × fast combination (cost dear→ high, speed fast→ high). +const ( + costLnLo, costLnHi = 1.27, 4.42 + speedLnLo, speedLnHi = 2.49, 4.20 +) + +// weightedModels walks the current config's rows and calls fn(weight, id, level) +// for each role's lead model — the shared basis for both meters. +// currentRows is the routing block the cost/speed meters score: the generator's +// facet combo with the advisor dial applied. +func (m model) currentRows() []string { + return m.applyAdvisor(m.generated[comboID(m.sel, m.hasRelief)], m.sel["advisor"]) +} + +func (m model) weightedModels(rows []string, fn func(w float64, id, lvl string)) { + for _, r := range rows { + f := strings.Fields(strings.ReplaceAll(r, "→", " ")) + i := 0 + if len(f) > 0 && f[0] == "●" { + i = 1 + } + if i >= len(f) { + continue + } + w, ok := roleWeight[f[i]] + if !ok { + continue + } + var lead string + for _, t := range f[i+1:] { + if modelRe.MatchString(t) { + lead = t + break + } + } + id, lvl, _ := strings.Cut(lead, ":") + if id != "" { + fn(w, id, lvl) + } + } +} + +func logScore(idx, lnLo, lnHi float64) int { + s := 1 + 4*(math.Log(idx)-lnLo)/(lnHi-lnLo) + return int(math.Round(math.Max(1, math.Min(5, s)))) +} + +// DeepSeek discounts the pay-as-you-go API during its off-peak window, +// UTC 16:30–00:30 (deepseek.com/pricing). The meter prices D rungs by the +// clock so a cheap window reads as the discount it is. offPeakNow is a var +// for tests only. +const deepseekOffPeakMult = 0.5 + +var offPeakNow = time.Now + +func deepseekOffPeak(utc time.Time) bool { + m := utc.Hour()*60 + utc.Minute() + return m >= 16*60+30 || m < 30 +} + +// costScore rates the current config from 1 (cheap) to 5 (dear). +func (m model) costScore() int { + if _, ok := m.selectedRuntime(); ok { + return 1 + } + fast := m.sel["fast"] == "on" && laneHasPool(m.sel["lane"], "O") + var num, den float64 + m.weightedModels(m.currentRows(), func(w float64, id, lvl string) { + c, ok := m.facts[id] + if !ok { + return + } + mult, ok := thinkMult[lvl] + if !ok { + mult = 1 + } + cost := (0.25*c.in + 0.75*c.out) * mult + if fast && m.poolOfModel(id) == "O" { + cost *= priorityMult + } + if m.poolOfModel(id) == "D" && deepseekOffPeak(offPeakNow().UTC()) { + cost *= deepseekOffPeakMult + } + num += w * cost + den += w + }) + if den == 0 { + return 1 + } + return logScore(num/den, costLnLo, costLnHi) +} + +// speedScore rates the current config from 1 (slow) to 5 (fast). +func (m model) speedScore() int { + if _, ok := m.selectedRuntime(); ok { + return 3 + } + fast := m.sel["fast"] == "on" && laneHasPool(m.sel["lane"], "O") + var num, den float64 + m.weightedModels(m.currentRows(), func(w float64, id, lvl string) { + c, ok := m.facts[id] + if !ok || c.speed == 0 { + return + } + mult, ok := thinkSpeed[lvl] + if !ok { + mult = 1 + } + sp := c.effTPS() * mult + if fast && m.poolOfModel(id) == "O" { + sp *= fastSpeed + } + num += w * sp + den += w + }) + if den == 0 { + return 3 + } + return logScore(num/den, speedLnLo, speedLnHi) +} + +// meter renders a labelled 1..5 scale — n glyphs in the fill colour, the rest in +// the dim "empty" colour — always five glyphs so the fill (and the headroom) read +// at a glance. +func (m model) meter(label, glyph, fill string, n int) string { + return clikit.Meter(label, glyph, fill, n) +} + +// advisorChain returns the advisor role's model chain for an intensity, sourced +// from the baked __advisors__ table. The advisor is the independent second +// opinion, so it crosses to another provider whenever the lane allows it: the +// first advisorPoolOrder pool that is not the lead's (fable-as-main hands the +// default role to the elite, so the lead becomes the elite's pool). Only the +// pure lanes stay on their own provider — ox-only's second opinion is Ox. +// +// On the mixed ox lanes the chain carries a cross-pool net in spend order — +// the other paid pool's cheapest rung, then the free pool itself — so a dead +// quota never leaves the second eye blind. +func (m model) advisorChain(level string) []string { + lane := m.sel["lane"] + if p := providerByLane(lane); p != nil && lanePure(lane) { + return m.advisors[level+"/"+p.Lane] + } + lead := genLanePolicies[m.sel["lane"]].primary + if fb := providerBySpecial("fable"); fb != nil && m.sel["fable"] == "on" && m.sel["main"] == "on" { + // fable-as-main puts the elite in the default seat, so the second + // opinion flips away from the elite's own pool. + lead = fb.Pool + } + for _, pool := range advisorPoolOrder { + if pool == lead { + continue + } + p := providerByPool(pool) + if p == nil { + continue + } + chain := m.advisors[level+"/"+p.Lane] + if len(chain) == 0 { + continue + } + if lane == "ox-led" || lane == "ox-lean" { + chain = m.advisorSpendNet(chain, p.Lane) + } + return m.advisorRelief(level, chain) + } + return nil +} + +// advisorSpendNet appends the mixed ox lanes' cross-pool net in spend order — +// the other paid pool's cheapest rung, then the free pool itself — so a dead +// quota never leaves the second eye blind. +func (m model) advisorSpendNet(chain []string, ctx string) []string { + tail := m.advisors["glance/gpt"] + if ctx == "gpt" { + tail = m.advisors["glance/claude"] + } + out := append([]string(nil), chain...) + for _, t := range append(append([]string{}, tail...), m.advisors["glance/ox"]...) { + if t != "" && !slices.Contains(out, t) { + out = append(out, t) + } + } + return out +} + +// advisorRelief appends the relief tail to the audit chain: audit is the +// advisor's heavyweight setting, so — exactly like the heavyweight roles — +// its metered chain ends on each optional pool's own audit rung when relief +// is on, and a drained day still gets its second opinion. Lighter levels +// stay short: they are cheap enough that quota rarely blocks them. +func (m model) advisorRelief(level string, chain []string) []string { + if level != "audit" || m.sel["relief"] != "on" || !laneReliefApplies(m.sel["lane"]) { + return chain + } + out := append([]string(nil), chain...) + for i := range providerRegistry { + p := &providerRegistry[i] + if p.Required { + continue + } + relief := m.advisors[level+"/"+p.Lane] + if len(relief) == 0 || slices.Contains(out, relief[0]) { + continue + } + out = append(out, relief[0]) + } + return out +} + +// roleOf returns the role name of a routing row ("● task" → "task"). +func roleOf(row string) string { + f := strings.Fields(row) + if len(f) > 0 && f[0] == "●" { + f = f[1:] + } + if len(f) > 0 { + return f[0] + } + return "" +} + +// applyAdvisor replaces the baked advisor row with one synthesised from the +// chosen intensity (dropping it entirely when off), so the generated preview and +// the launched config both reflect the advisor facet. +func (m model) applyAdvisor(rows []string, level string) []string { + chain := m.advisorChain(level) + newRow := "" + if len(chain) > 0 { + newRow = " advisor " + strings.Join(chain, " → ") + } + var out []string + replaced := false + for _, r := range rows { + if roleOf(r) == "advisor" { + replaced = true + if newRow != "" { + out = append(out, newRow) + } + continue + } + out = append(out, r) + } + if !replaced && newRow != "" { + out = append(out, newRow) + } + return out +} + +// visibleFacets drops facets that don't apply to the current lane, so the +// generator only ever shows actionable options: no spark/fast on a pure lane +// of another pool, no fable outside its pool's lanes. main is fable's +// sub-setting, so it only shows while fable is on (and the lane can host it at +// all). A dial this catalog generated no combo for is dropped the same way — +// it is not a choice. +// +// The lane facet renders as two rows: lead (one segment per pool, plus mixed) +// and blend (led | only, hidden for mixed). sel["lane"] stays the canonical +// value — lead/blend are derived here and recomposed by cycleFacet, and are +// never persisted (saveSelectionState filters on m.facets, which carries only +// "lane"). +func (m model) visibleFacets() []facet { + if _, local := m.selectedRuntime(); local { + var out []facet + for _, f := range m.facets { + if f.key == "runtime" || f.key == "thinking" { + out = append(out, f) + } + } + return out + } + lane := m.sel["lane"] + var out []facet + for _, f := range m.facets { + if f.key == "lane" { + lead, blend := laneSplit(lane) + m.sel["lead"], m.sel["blend"] = lead, blend + // mixed leads the dial: it is the default and the only lead + // without a blend child, so it anchors the left edge. + leads := []string{"mixed"} + seen := map[string]bool{"mixed": true} + for _, v := range f.values { + l, _ := laneSplit(v) + if !seen[l] { + seen[l] = true + leads = append(leads, l) + } + } + out = append(out, facet{"lead", leads, f.glyph}) + if lead != "mixed" { + out = append(out, facet{"blend", []string{"led", "only"}, f.glyph}) + } + continue + } + switch f.key { + case "spark": + // genValid refuses every ox-lane spark combo (the drain bucket's + // leads are utility roles those lanes give to their own pools). + if !laneHostsSpecial(lane, "spark") || m.noSpark || + lane == "ox-only" || lane == "ox-led" || lane == "ox-lean" { + continue + } + case "fast": + // The fast dial is the priority service tier — an OpenAI pool + // feature, meaningless on a pure lane of any other pool or where + // no OpenAI token leads the work. + if p := providerByLane(lane); lanePure(lane) && p != nil && p.ServiceTier[0] == "" { + continue + } + if lane == "ox-only" || lane == "ox-led" { + continue + } + case "fable", "main": + if !laneHostsSpecial(lane, "fable") || m.noFable { + continue + } + if f.key == "main" && (m.sel["fable"] != "on" || lane == "ox-led") { + // fable-as-main would defeat ox-led's free worker, and + // genValid refuses that combo outright. + continue + } + case "relief": + if !m.hasRelief || !laneReliefApplies(lane) { + continue + } + } + out = append(out, f) + } + return out +} + +func comboID(sel map[string]string, hasRelief bool) string { + lane := sel["lane"] + sp, fb := sel["spark"], sel["fable"] + if !laneHostsSpecial(lane, "fable") { + fb = "off" + } + if !laneHostsSpecial(lane, "spark") || lane == "ox-led" || lane == "ox-lean" { + sp = "off" + } + spid, faid := "nosp", "nofa" + if sp == "on" { + spid = "sp" + } + if fb == "on" { + faid = "fa" + // ox-led hosts the elite on deliberative roles only; promoting it to + // the default role would defeat the lane, and genValid refuses that + // combo outright. + if sel["main"] == "on" && lane != "ox-led" { + faid = "famain" + } + } + id := fmt.Sprintf("%s_%s_%s_%s_%s", lane, sel["model"], sel["thinking"], spid, faid) + if hasRelief { + rel := sel["relief"] + if !laneReliefApplies(lane) { + rel = "on" // the only variant generated there + } + if rel == "off" { + id += "_norel" + } else { + id += "_rel" + } + } + return id +} + +// applyCatalog records which dials this catalog can actually serve, then forces +// the rest off. A models file with no tier-0 model yields no _sp_ combos at all, +// so the shipped default (spark on) would open the TUI on a combo that was never +// written — and a selection persisted against a richer catalog does the same. +// Ids are ____, so match whole +// segments: "nosp" and "nofa" contain the very substrings being looked for. +// The lane facet's value list is the catalog's too: the distinct lane segments +// of the combo ids, ordered canonically (laneOrderForPools), so a catalog with +// a DeepSeek pool grows ds-led/ds-only dials and an old one shows exactly the +// classic five. +func (m *model) applyCatalog() { + if len(m.generated) == 0 { + return // no catalog read yet: onboarding, or a broken CODE_GENERATED + } + spark, fable, relief := false, false, false + served := map[string]bool{} + for id := range m.generated { + lane := id + if i := strings.IndexByte(id, '_'); i >= 0 { + lane = id[:i] + } + served[lane] = true + for _, seg := range strings.Split(id, "_") { + switch seg { + case "sp": + spark = true + case "fa", "famain": + fable = true + case "norel": + relief = true + } + } + } + m.noSpark, m.noFable, m.hasRelief = !spark, !fable, relief + if lanes := catalogLanes(m.generated); len(lanes) > 0 { + for i := range m.facets { + if m.facets[i].key == "lane" { + m.facets[i].values = lanes + } + } + } + m.trimLanes(served) + + m.clampSel() +} + +// catalogLanes collects the distinct lane values the catalog generated, in +// canonical dial order; unknown lane names (a newer catalog) trail sorted so +// they are still reachable. +func catalogLanes(generated map[string][]string) []string { + seen := map[string]bool{} + for id := range generated { + if strings.HasPrefix(id, "__") { + continue + } + lane, rest, ok := strings.Cut(id, "_") + if !ok || lane == "" || rest == "" { + continue + } + seen[lane] = true + } + if len(seen) == 0 { + return nil + } + var lanes []string + for _, lane := range laneOrderForPools(fallbackPoolOrder) { + if seen[lane] { + lanes = append(lanes, lane) + delete(seen, lane) + } + } + var extra []string + for lane := range seen { + extra = append(extra, lane) + } + sort.Strings(extra) + return append(lanes, extra...) +} + +// trimLanes narrows the lane dial to the lanes this catalog actually serves, +// and lands the selection on a served lane when a persisted or default choice +// points at one that vanished (an older catalog without ox, say). This is the +// consumer side of the optional-pool switches: no ox entries in models.yml +// means no ox values on the dial at all. +func (m *model) trimLanes(served map[string]bool) { + for i, f := range m.facets { + if f.key != "lane" { + continue + } + var values []string + for _, v := range f.values { + if served[v] { + values = append(values, v) + } + } + if len(values) == len(f.values) { + continue // nothing to trim + } + if len(values) > 0 { + m.facets[i].values = values + } + break + } + if !served[m.sel["lane"]] { + for _, fallback := range []string{"mixed", "gpt-only", "claude-only"} { + if served[fallback] { + m.sel["lane"] = fallback + break + } + } + } +} + +// clampSel turns off every dial the catalog cannot serve. main is fable's +// sub-setting and never outlives it. +func (m *model) clampSel() { + if m.noSpark { + m.sel["spark"] = "off" + } + if m.noFable { + m.sel["fable"] = "off" + m.sel["main"] = "off" + } + if !m.hasRelief { + m.sel["relief"] = "on" + } + // A persisted lane the applied catalog no longer generates (or one from a + // richer catalog) resets to the dial's first lane. + for _, f := range m.facets { + if f.key != "lane" || len(f.values) == 0 { + continue + } + known := false + for _, v := range f.values { + if v == m.sel["lane"] { + known = true + break + } + } + if !known { + m.sel["lane"] = f.values[0] + } + } +} + +// laneColor tints the accent by lane: each provider carries a deeper shade for +// its pure lane and a lighter one for its led lane; mixed keeps its purple. +func laneColor(lane string) string { + if lane == "mixed" { + return "#aa96e1" + } + if lane == "ox-lean" { + return "#2dd4bf" // teal — paid work riding the free pool + } + if p := providerByLane(lane); p != nil { + if lanePure(lane) { + return p.LaneOnly + } + return p.LaneLed + } + return "#ff9f52" +} + +// prefixed qualifies a model id with its omp provider: the catalog column when +// present, else the registry's family guess. The unknown-model fallback stays +// openai-codex so a legacy catalog launches exactly as before. +func (m model) prefixed(model string) string { + // Routing tokens carry a thinking level ("id:level"); the catalog is + // keyed on the bare id. Qualify the full token either way. + id := model + if i := strings.IndexByte(id, ':'); i >= 0 { + id = id[:i] + } + if f, ok := m.facts[id]; ok { + if p := providerByPool(f.pool); p != nil { + return p.ID + "/" + model + } + } + if p := providerByModel(model); p != nil { + return p.ID + "/" + model + } + return openAIProvider + "/" + model +} + +// poolOfModel is the model's registry pool letter: the catalog column first +// (leveled tokens are stripped before the lookup), the family guess second; +// "" when nobody claims it. +func (m model) poolOfModel(id string) string { + bare := id + if i := strings.IndexByte(bare, ':'); i >= 0 { + bare = bare[:i] + } + if f, ok := m.facts[bare]; ok && f.pool != "" { + return f.pool + } + if p := providerByModel(bare); p != nil { + return p.Pool + } + return "" +} + +// genConfigYAML reconstructs an omp config (modelRoles, task-agent model +// overrides for the ●-marked agent-backed roles, fallback chains, thinking, +// advisor, and the priority tier when fast is on) from the generated routing +// block for the current facets — what Enter launches omp with. The agent +// overrides mirror the preview: without them the static managed defaults +// would keep the five agent-backed types pinned regardless of the generated +// profile (issue atyrode/dotfiles#173). +func (m model) genConfigYAML() string { + rows := m.applyAdvisor(m.generated[comboID(m.sel, m.hasRelief)], m.sel["advisor"]) + var mr, fc, ao strings.Builder + advisorOn := false + for _, r := range rows { + f := strings.Fields(strings.ReplaceAll(r, "→", " ")) + i := 0 + if len(f) > 0 && f[0] == "●" { + i = 1 + } + if i >= len(f) { + continue + } + role := f[i] + var models []string + for _, t := range f[i+1:] { + if modelRe.MatchString(t) { + models = append(models, t) + } + } + if len(models) == 0 { + continue + } + if role == "advisor" { + advisorOn = true + } + if i == 1 && role != "advisor" { + // ●-marked agent-backed role: mirror its lead route as the task-agent + // model override so spawned agents follow the generated profile. + ao.WriteString(" " + role + ": " + m.prefixed(models[0]) + "\n") + } + mr.WriteString(" " + role + ": " + m.prefixed(models[0]) + "\n") + if len(models) > 1 { + var fbs []string + for _, x := range models[1:] { + fbs = append(fbs, m.prefixed(x)) + } + fc.WriteString(" " + role + ": [" + strings.Join(fbs, ", ") + "]\n") + } + } + var b strings.Builder + b.WriteString("modelRoles:\n" + mr.String()) + b.WriteString("retry:\n enabled: true\n modelFallback: true\n fallbackRevertPolicy: cooldown-expiry\n fallbackChains:\n" + fc.String()) + // task.agentAdvisor (omp ≥ 17.3; earlier omps hard-error on the unknown + // key, and CODE_OMP wrappers can lag the store during a dotfiles rollout, + // so the probed version gates the emission): at the audit dial, spawned + // task agents get their own advisor. Merged into the one task: block — + // overlays are strict YAML and two task: keys would be invalid. + agentAdvisor := m.sel["advisor"] == "audit" && m.ompVersionAtLeast(17, 3) + if ao.Len() > 0 || agentAdvisor { + b.WriteString("task:\n") + if ao.Len() > 0 { + b.WriteString(" agentModelOverrides:\n" + ao.String()) + } + if agentAdvisor { + b.WriteString(" agentAdvisor:\n task: \"on\"\n") + } + } + b.WriteString("defaultThinkingLevel: " + m.sel["thinking"] + "\n") + if advisorOn { + b.WriteString("advisor:\n enabled: true\n") + } else { + b.WriteString("advisor:\n enabled: false\n") + } + if m.sel["fast"] == "on" && laneHasPool(m.sel["lane"], "O") { + if p := providerByPool("O"); p != nil && p.ServiceTier[0] != "" { + b.WriteString("tier:\n " + p.ServiceTier[0] + ": " + p.ServiceTier[1] + "\n") + } + } + return b.String() +} diff --git a/selection_state.go b/selection_state.go index 731d3ef..38a1ad6 100644 --- a/selection_state.go +++ b/selection_state.go @@ -52,16 +52,31 @@ func facetValues(facets []facet) map[string]map[string]bool { return valid } -// repairPersistedSelection prevents a hidden fable/main choice from being -// resurrected after loading. Other hidden facets retain their ordinary model -// semantics; only main is a subordinate choice that must be explicitly remade. +// repairPersistedSelection prevents a hidden special-tier choice from being +// resurrected after loading: spark/fable are forced off when the persisted +// lane's pool-set cannot host them, and main (a subordinate choice that must +// be explicitly remade) never outlives fable. func repairPersistedSelection(sel map[string]string) { - if sel["lane"] == "gpt-only" { - sel["fable"] = "off" + repairSelectionSpecials(sel) +} + +// repairSelectionSpecials is the one lane-validity rule shared by persisted +// loads and live suggestions: a special-tier facet is forced off iff its +// provider's pool is outside the selected lane's pool-set. +func repairSelectionSpecials(sel map[string]string) { + for _, facet := range []string{"spark", "fable"} { + if !laneHostsSpecial(sel["lane"], facet) { + sel[facet] = "off" + } } if sel["fable"] != "on" { sel["main"] = "off" } + // relief is only a choice on a metered-led blend; everywhere else the + // generator writes a single (on) variant, so the selection must match. + if !laneReliefApplies(sel["lane"]) { + sel["relief"] = "on" + } } func selectionChoices(sel map[string]string, facets []facet) map[string]string { diff --git a/selection_state_test.go b/selection_state_test.go index 16d5b7c..8cf5c83 100644 --- a/selection_state_test.go +++ b/selection_state_test.go @@ -45,7 +45,7 @@ func TestSelectionStateRoundTripStoresOnlyFacets(t *testing.T) { } if got := loadSelectionState(path, testFacets()); !reflect.DeepEqual(got, map[string]string{ "lane": "claude-led", "model": "smart", "thinking": "xhigh", "advisor": "glance", - "spark": "on", "fable": "off", "main": "off", "fast": "off", + "spark": "on", "fable": "off", "main": "off", "fast": "off", "relief": "on", }) { t.Fatalf("round-trip selection = %v", got) } @@ -224,8 +224,10 @@ func TestFacetChangeAndResetPersistSelection(t *testing.T) { changedModel, _ := m.Update(tea.KeyMsg{Type: tea.KeyRight}) changed := changedModel.(model) - if got := loadSelectionState(path, testFacets()); !reflect.DeepEqual(got, changed.sel) { - t.Fatalf("facet change persisted %v, want %v", got, changed.sel) + // changed.sel also carries the transient lead/blend derivations of lane; + // what persists (and reloads) is the facet-only projection. + if got := loadSelectionState(path, testFacets()); !reflect.DeepEqual(got, selectionChoices(changed.sel, testFacets())) { + t.Fatalf("facet change persisted %v, want %v", got, selectionChoices(changed.sel, testFacets())) } resetModel, _ := changed.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}}) diff --git a/suggest.go b/suggest.go index 881838f..57407f8 100644 --- a/suggest.go +++ b/suggest.go @@ -2,6 +2,7 @@ package main import ( "os" + "strconv" clikit "github.com/atyrode/cli-kit" "github.com/atyrode/cli-kit/ollama" @@ -96,17 +97,18 @@ func (m model) Commander() clikit.Commander { } // repairConstraints enforces the deterministic rules a suggestion (or selection) -// must never violate — mirroring genValid plus live quota: spark is an OpenAI -// model, so it can't run on a pure-Claude or an ox lane; fable is an Anthropic -// elite, so it can't run on a pure-GPT or a pure-ox lane; fable-as-main would -// defeat ox-led's free worker; and neither lead may be left on when its quota -// bucket is maxed or unauthed. Runs after an applied proposal, so the generator -// can't land on an impossible or unavailable combo. +// must never violate — mirroring the generator's `genValid` plus live quota: +// a special-tier facet (spark, fable) can only run on a lane whose pool-set +// contains its provider's pool (and never on an ox lane); fable-as-main would +// defeat ox-led's free worker; and neither may be left on when its quota +// bucket is maxed or unauthed. Runs after an applied proposal, so the +// generator can't land on an impossible or unavailable combo. func (m *model) repairConstraints() { - if lane := m.sel["lane"]; lane == "claude-only" || lane == "ox-only" || lane == "ox-led" || lane == "ox-lean" { + repairSelectionSpecials(m.sel) + if lane := m.sel["lane"]; lane == "ox-only" || lane == "ox-led" || lane == "ox-lean" { m.sel["spark"] = "off" } - if lane := m.sel["lane"]; lane == "gpt-only" || lane == "ox-only" { + if m.sel["lane"] == "ox-only" { m.sel["fable"] = "off" } if m.sel["lane"] == "ox-led" { @@ -195,10 +197,21 @@ func (m model) appliedDiff() []clikit.Action { // task-specific, so it keeps its current value; repairConstraints still turns // it off if its bucket is down or the lane is Claude-only. func (m *model) deriveToggles() { + // Quota-aware lane fallback: a suggestion must not land on a lane whose + // lead pool is drained when a sibling lane has live headroom. + if alt := m.quotaLane(); alt != "" { + m.sel["lane"] = alt + } + // Balance guard: when the pay-as-you-go pool is dry (or its balance is + // unknown), a suggestion stops routing relief tails into it. The manual + // dial stays free — this only shapes proposals. + if m.hasRelief && laneReliefApplies(m.sel["lane"]) && !m.optionalPoolUsable() { + m.sel["relief"] = "off" + } tier := m.sel["thinking"] critical := m.sel["model"] == "smart" && (tier == "xhigh" || tier == "max") - claudeLane := m.sel["lane"] != "gpt-only" - if critical && claudeLane && !m.avail.down(bucketOf("fable")) { + fableLane := laneHostsSpecial(m.sel["lane"], "fable") + if critical && fableLane && !m.avail.down(bucketOf("fable")) { m.sel["fable"] = "on" } else { m.sel["fable"] = "off" @@ -209,3 +222,59 @@ func (m *model) deriveToggles() { m.sel["fast"] = "off" } } + +// deepseekLowBalanceUSD is the prepaid floor under which suggestions stop +// spending the pay-as-you-go pool: below it, ds lanes are not proposed and +// relief tails are suggested off. +const deepseekLowBalanceUSD = 2.0 + +// optionalPoolUsable reports whether the pay-as-you-go pool can absorb routed +// traffic: a credential exists, the last balance fetch succeeded, and the +// prepaid balance clears the floor. An unknown balance is not usable — the +// guard's whole point is never to discover $0 mid-session. +func (m *model) optionalPoolUsable() bool { + b := m.avail.deepseek + if b == nil || !b.ok { + return false + } + v, err := strconv.ParseFloat(b.total, 64) + return err == nil && v >= deepseekLowBalanceUSD +} + +// quotaLane returns the led lane of the first pool with live headroom when the +// current lane's lead pool is maxed or unauthenticated — the dial move a human +// would make after glancing at the usage panel. "" means stay put: the lead +// pool is fine, no alternative lane exists in this catalog, or none has quota. +func (m *model) quotaLane() string { + lead := providerByPool(genLanePolicies[m.sel["lane"]].primary) + if lead == nil || !m.avail.down(lead.mainBucket()) { + return "" + } + lanes := map[string]bool{} + for _, f := range m.facets { + if f.key == "lane" { + for _, v := range f.values { + lanes[v] = true + } + } + } + for _, pool := range fallbackPoolOrder { + p := providerByPool(pool) + if p == nil || p.Pool == lead.Pool { + continue + } + alt := p.Lane + "-led" + if !lanes[alt] { + continue + } + if p.Metered { + if m.avail.down(p.mainBucket()) { + continue + } + } else if !m.optionalPoolUsable() { + continue + } + return alt + } + return "" +} diff --git a/suggest_test.go b/suggest_test.go index 37c34fb..f7ec9c0 100644 --- a/suggest_test.go +++ b/suggest_test.go @@ -194,3 +194,93 @@ func TestEvalSystemPromptIsSizerRole(t *testing.T) { t.Errorf("evalSystemPrompt should pin the difficulty-rating, sizer-only role, got: %q", s) } } + +// suggestModel builds a three-pool-shaped model for suggestion-path tests: +// all seven lanes on the dial, a relief facet, and a controllable quota map. +func suggestModel() model { + m := model{facets: facetDefs(defaultGlyphs()), sel: defaultSel(), hasRelief: true} + for i := range m.facets { + if m.facets[i].key == "lane" { + m.facets[i].values = []string{"gpt-only", "gpt-led", "mixed", "claude-led", "claude-only", "ds-led", "ds-only"} + } + } + m.avail = availability{ok: true, bucket: map[string]string{}, reset: map[string]int64{}} + return m +} + +// TestQuotaLaneSuggestion: a suggestion never lands on a lane whose lead pool +// is drained while a sibling lane has headroom - in fallbackPoolOrder, gated +// by the prepaid balance for the pay-as-you-go pool. +func TestQuotaLaneSuggestion(t *testing.T) { + m := suggestModel() + m.avail.deepseek = &deepseekBalance{ok: true, currency: "USD", total: "18.03"} + + // Lead pool fine: stay put. + m.sel["lane"] = "gpt-led" + m.deriveToggles() + if m.sel["lane"] != "gpt-led" { + t.Fatalf("healthy lead pool must not move the lane, got %q", m.sel["lane"]) + } + + // Codex maxed: the first pool in fallbackPoolOrder with headroom leads. + m.avail.bucket["codex-main"] = "maxed" + m.deriveToggles() + if m.sel["lane"] != "claude-led" { + t.Fatalf("maxed lead pool should fall to claude-led, got %q", m.sel["lane"]) + } + + // Claude maxed too: DeepSeek is the last lane standing (balance is fine). + m.sel["lane"] = "gpt-led" + m.avail.bucket["claude-main"] = "maxed" + m.deriveToggles() + if m.sel["lane"] != "ds-led" { + t.Fatalf("both metered pools maxed should fall to ds-led, got %q", m.sel["lane"]) + } + + // ...but not when the balance is under the floor. + m.sel["lane"] = "gpt-led" + m.avail.deepseek = &deepseekBalance{ok: true, currency: "USD", total: "1.17"} + m.deriveToggles() + if m.sel["lane"] != "gpt-led" { + t.Fatalf("a dry prepaid pool must not be suggested, got %q", m.sel["lane"]) + } + + // A two-pool catalog (no ds lanes on the dial) never proposes one. + m2 := suggestModel() + for i := range m2.facets { + if m2.facets[i].key == "lane" { + m2.facets[i].values = []string{"gpt-only", "gpt-led", "mixed", "claude-led", "claude-only"} + } + } + m2.avail.deepseek = &deepseekBalance{ok: true, currency: "USD", total: "50"} + m2.avail.bucket["codex-main"] = "maxed" + m2.avail.bucket["claude-main"] = "maxed" + m2.sel["lane"] = "gpt-led" + m2.deriveToggles() + if m2.sel["lane"] != "gpt-led" { + t.Fatalf("no alternative lane on the dial: stay put, got %q", m2.sel["lane"]) + } +} + +// TestBalanceGuardRelief: suggestions turn relief off when the prepaid pool is +// dry or its balance unknown, and leave it alone when it is healthy. +func TestBalanceGuardRelief(t *testing.T) { + for _, tc := range []struct { + name string + bal *deepseekBalance + want string + }{ + {"healthy", &deepseekBalance{ok: true, currency: "USD", total: "18.03"}, "on"}, + {"low", &deepseekBalance{ok: true, currency: "USD", total: "0.42"}, "off"}, + {"fetch failed", &deepseekBalance{ok: false}, "off"}, + {"no credential", nil, "off"}, + } { + m := suggestModel() + m.sel["lane"] = "gpt-led" + m.avail.deepseek = tc.bal + m.deriveToggles() + if got := m.sel["relief"]; got != tc.want { + t.Errorf("%s: relief = %q, want %q", tc.name, got, tc.want) + } + } +} diff --git a/testdata/two-pool-golden.plain b/testdata/two-pool-golden.plain new file mode 100644 index 0000000..0a1820e --- /dev/null +++ b/testdata/two-pool-golden.plain @@ -0,0 +1,7335 @@ +OMP generated routing — first-principles facet grid +agent-backed roles: task designer reviewer security-reviewer librarian scout sonic — ● marks a role mirrored into task.agentModelOverrides + +__advisors__ advisor dial (level context → chain) + glance gpt gpt-5.6-luna:low + review gpt gpt-5.6-terra:medium → gpt-5.6-luna:low + audit gpt gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:low + glance claude claude-haiku-4-5:low + review claude claude-sonnet-5:medium → claude-haiku-4-5:low + audit claude claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:low + +__models__ model facts (id in out speed ttft bucket provider — $/1M in·out, tok/s, s) + gpt-5.6-luna 1 6 52.3 1.18 codex-main openai-codex + gpt-5.6-terra 2.5 15 51.8 1.74 codex-main openai-codex + gpt-5.6-sol 5 30 31.5 4.59 codex-main openai-codex + gpt-5.3-codex-spark 1.75 14 286.7 5.56 codex-spark openai-codex + claude-haiku-4-5 1 5 48.9 1.7 claude-main anthropic + claude-sonnet-5 2 10 35.2 3.84 claude-main anthropic + claude-opus-5 5 25 46.6 1.77 claude-main anthropic + claude-fable-5 10 50 54 6.9 claude-fable anthropic + +gpt-only_fast_minimal_sp_nofa gpt-only · fast · minimal · spark + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low + ● task gpt-5.6-luna:low + plan gpt-5.6-terra:low → gpt-5.6-luna:low + slow gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_fast_minimal_nosp_nofa gpt-only · fast · minimal + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low + ● task gpt-5.6-luna:low + plan gpt-5.6-terra:low → gpt-5.6-luna:low + slow gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_fast_low_sp_nofa gpt-only · fast · low · spark + thinking low · fallback on · advisor off + default gpt-5.6-luna:low + ● task gpt-5.6-luna:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_fast_low_nosp_nofa gpt-only · fast · low + thinking low · fallback on · advisor off + default gpt-5.6-luna:low + ● task gpt-5.6-luna:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_fast_medium_sp_nofa gpt-only · fast · medium · spark + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium + ● task gpt-5.6-luna:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_fast_medium_nosp_nofa gpt-only · fast · medium + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium + ● task gpt-5.6-luna:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_fast_high_sp_nofa gpt-only · fast · high · spark + thinking high · fallback on · advisor off + default gpt-5.6-luna:high + ● task gpt-5.6-luna:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_fast_high_nosp_nofa gpt-only · fast · high + thinking high · fallback on · advisor off + default gpt-5.6-luna:high + ● task gpt-5.6-luna:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_fast_xhigh_sp_nofa gpt-only · fast · xhigh · spark + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh + ● task gpt-5.6-luna:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_fast_xhigh_nosp_nofa gpt-only · fast · xhigh + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh + ● task gpt-5.6-luna:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_fast_max_sp_nofa gpt-only · fast · max · spark + thinking max · fallback on · advisor off + default gpt-5.6-luna:max + ● task gpt-5.6-luna:max + plan gpt-5.6-terra:max → gpt-5.6-luna:max + slow gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max + ● scout gpt-5.6-luna:max + ● sonic gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + vision gpt-5.6-luna:max + smol gpt-5.6-luna:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-only_fast_max_nosp_nofa gpt-only · fast · max + thinking max · fallback on · advisor off + default gpt-5.6-luna:max + ● task gpt-5.6-luna:max + plan gpt-5.6-terra:max → gpt-5.6-luna:max + slow gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max + ● scout gpt-5.6-luna:max + ● sonic gpt-5.6-luna:max + vision gpt-5.6-luna:max + smol gpt-5.6-luna:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-only_normal_minimal_sp_nofa gpt-only · normal · minimal · spark + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_normal_minimal_nosp_nofa gpt-only · normal · minimal + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_normal_low_sp_nofa gpt-only · normal · low · spark + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_normal_low_nosp_nofa gpt-only · normal · low + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_normal_medium_sp_nofa gpt-only · normal · medium · spark + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_normal_medium_nosp_nofa gpt-only · normal · medium + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_normal_high_sp_nofa gpt-only · normal · high · spark + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_normal_high_nosp_nofa gpt-only · normal · high + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_normal_xhigh_sp_nofa gpt-only · normal · xhigh · spark + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_normal_xhigh_nosp_nofa gpt-only · normal · xhigh + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-only_normal_max_sp_nofa gpt-only · normal · max · spark + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max + ● task gpt-5.6-terra:max → gpt-5.6-luna:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-only_normal_max_nosp_nofa gpt-only · normal · max + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max + ● task gpt-5.6-terra:max → gpt-5.6-luna:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max + smol gpt-5.6-terra:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-only_smart_minimal_sp_nofa gpt-only · smart · minimal · spark + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_smart_minimal_nosp_nofa gpt-only · smart · minimal + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-only_smart_low_sp_nofa gpt-only · smart · low · spark + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_smart_low_nosp_nofa gpt-only · smart · low + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-only_smart_medium_sp_nofa gpt-only · smart · medium · spark + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_smart_medium_nosp_nofa gpt-only · smart · medium + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-only_smart_high_sp_nofa gpt-only · smart · high · spark + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_smart_high_nosp_nofa gpt-only · smart · high + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-only_smart_xhigh_sp_nofa gpt-only · smart · xhigh · spark + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-only_smart_xhigh_nosp_nofa gpt-only · smart · xhigh + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-only_smart_max_sp_nofa gpt-only · smart · max · spark + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-terra:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-only_smart_max_nosp_nofa gpt-only · smart · max + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → gpt-5.6-luna:max + smol gpt-5.6-terra:max + tiny gpt-5.6-terra:max + commit gpt-5.6-luna:max + +gpt-led_fast_minimal_sp_fa gpt-led · fast · minimal · spark · fable + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_minimal_sp_famain gpt-led · fast · minimal · spark · fable · main + thinking minimal · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_minimal_sp_nofa gpt-led · fast · minimal · spark + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_minimal_nosp_fa gpt-led · fast · minimal · fable + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_minimal_nosp_famain gpt-led · fast · minimal · fable · main + thinking minimal · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_minimal_nosp_nofa gpt-led · fast · minimal + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_low_sp_fa gpt-led · fast · low · spark · fable + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_low_sp_famain gpt-led · fast · low · spark · fable · main + thinking low · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_low_sp_nofa gpt-led · fast · low · spark + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_low_nosp_fa gpt-led · fast · low · fable + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_low_nosp_famain gpt-led · fast · low · fable · main + thinking low · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_low_nosp_nofa gpt-led · fast · low + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_medium_sp_fa gpt-led · fast · medium · spark · fable + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_medium_sp_famain gpt-led · fast · medium · spark · fable · main + thinking medium · fallback on · advisor off + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_medium_sp_nofa gpt-led · fast · medium · spark + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_medium_nosp_fa gpt-led · fast · medium · fable + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_medium_nosp_famain gpt-led · fast · medium · fable · main + thinking medium · fallback on · advisor off + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_medium_nosp_nofa gpt-led · fast · medium + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_high_sp_fa gpt-led · fast · high · spark · fable + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_high_sp_famain gpt-led · fast · high · spark · fable · main + thinking high · fallback on · advisor off + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_high_sp_nofa gpt-led · fast · high · spark + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_high_nosp_fa gpt-led · fast · high · fable + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_high_nosp_famain gpt-led · fast · high · fable · main + thinking high · fallback on · advisor off + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_high_nosp_nofa gpt-led · fast · high + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_xhigh_sp_fa gpt-led · fast · xhigh · spark · fable + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_xhigh_sp_famain gpt-led · fast · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor off + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_xhigh_sp_nofa gpt-led · fast · xhigh · spark + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_fast_xhigh_nosp_fa gpt-led · fast · xhigh · fable + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_xhigh_nosp_famain gpt-led · fast · xhigh · fable · main + thinking xhigh · fallback on · advisor off + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_xhigh_nosp_nofa gpt-led · fast · xhigh + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_fast_max_sp_fa gpt-led · fast · max · spark · fable + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_fast_max_sp_famain gpt-led · fast · max · spark · fable · main + thinking max · fallback on · advisor off + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_fast_max_sp_nofa gpt-led · fast · max · spark + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_fast_max_nosp_fa gpt-led · fast · max · fable + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-led_fast_max_nosp_famain gpt-led · fast · max · fable · main + thinking max · fallback on · advisor off + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-led_fast_max_nosp_nofa gpt-led · fast · max + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-led_normal_minimal_sp_fa gpt-led · normal · minimal · spark · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_minimal_sp_famain gpt-led · normal · minimal · spark · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_minimal_sp_nofa gpt-led · normal · minimal · spark + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_minimal_nosp_fa gpt-led · normal · minimal · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_minimal_nosp_famain gpt-led · normal · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_minimal_nosp_nofa gpt-led · normal · minimal + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_low_sp_fa gpt-led · normal · low · spark · fable + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_low_sp_famain gpt-led · normal · low · spark · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_low_sp_nofa gpt-led · normal · low · spark + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_low_nosp_fa gpt-led · normal · low · fable + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_low_nosp_famain gpt-led · normal · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_low_nosp_nofa gpt-led · normal · low + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_medium_sp_fa gpt-led · normal · medium · spark · fable + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_medium_sp_famain gpt-led · normal · medium · spark · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_medium_sp_nofa gpt-led · normal · medium · spark + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_medium_nosp_fa gpt-led · normal · medium · fable + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_medium_nosp_famain gpt-led · normal · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_medium_nosp_nofa gpt-led · normal · medium + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_high_sp_fa gpt-led · normal · high · spark · fable + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_high_sp_famain gpt-led · normal · high · spark · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_high_sp_nofa gpt-led · normal · high · spark + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_high_nosp_fa gpt-led · normal · high · fable + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_high_nosp_famain gpt-led · normal · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_high_nosp_nofa gpt-led · normal · high + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_xhigh_sp_fa gpt-led · normal · xhigh · spark · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_xhigh_sp_famain gpt-led · normal · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_xhigh_sp_nofa gpt-led · normal · xhigh · spark + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_normal_xhigh_nosp_fa gpt-led · normal · xhigh · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_xhigh_nosp_famain gpt-led · normal · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_xhigh_nosp_nofa gpt-led · normal · xhigh + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +gpt-led_normal_max_sp_fa gpt-led · normal · max · spark · fable + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_normal_max_sp_famain gpt-led · normal · max · spark · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_normal_max_sp_nofa gpt-led · normal · max · spark + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_normal_max_nosp_fa gpt-led · normal · max · fable + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-led_normal_max_nosp_famain gpt-led · normal · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-led_normal_max_nosp_nofa gpt-led · normal · max + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +gpt-led_smart_minimal_sp_fa gpt-led · smart · minimal · spark · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_minimal_sp_famain gpt-led · smart · minimal · spark · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_minimal_sp_nofa gpt-led · smart · minimal · spark + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_minimal_nosp_fa gpt-led · smart · minimal · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_minimal_nosp_famain gpt-led · smart · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_minimal_nosp_nofa gpt-led · smart · minimal + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + slow gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● designer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_low_sp_fa gpt-led · smart · low · spark · fable + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_low_sp_famain gpt-led · smart · low · spark · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_low_sp_nofa gpt-led · smart · low · spark + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_low_nosp_fa gpt-led · smart · low · fable + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_low_nosp_famain gpt-led · smart · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_low_nosp_nofa gpt-led · smart · low + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_medium_sp_fa gpt-led · smart · medium · spark · fable + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_medium_sp_famain gpt-led · smart · medium · spark · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_medium_sp_nofa gpt-led · smart · medium · spark + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_medium_nosp_fa gpt-led · smart · medium · fable + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_medium_nosp_famain gpt-led · smart · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_medium_nosp_nofa gpt-led · smart · medium + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + slow gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● designer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_high_sp_fa gpt-led · smart · high · spark · fable + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_high_sp_famain gpt-led · smart · high · spark · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_high_sp_nofa gpt-led · smart · high · spark + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_high_nosp_fa gpt-led · smart · high · fable + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_high_nosp_famain gpt-led · smart · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_high_nosp_nofa gpt-led · smart · high + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_xhigh_sp_fa gpt-led · smart · xhigh · spark · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_xhigh_sp_famain gpt-led · smart · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_xhigh_sp_nofa gpt-led · smart · xhigh · spark + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +gpt-led_smart_xhigh_nosp_fa gpt-led · smart · xhigh · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_xhigh_nosp_famain gpt-led · smart · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_xhigh_nosp_nofa gpt-led · smart · xhigh + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +gpt-led_smart_max_sp_fa gpt-led · smart · max · spark · fable + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-terra:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_smart_max_sp_famain gpt-led · smart · max · spark · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-terra:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_smart_max_sp_nofa gpt-led · smart · max · spark + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-terra:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +gpt-led_smart_max_nosp_fa gpt-led · smart · max · fable + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + smol gpt-5.6-terra:max + tiny gpt-5.6-terra:max + commit gpt-5.6-luna:max + +gpt-led_smart_max_nosp_famain gpt-led · smart · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + smol gpt-5.6-terra:max + tiny gpt-5.6-terra:max + commit gpt-5.6-luna:max + +gpt-led_smart_max_nosp_nofa gpt-led · smart · max + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + slow gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● designer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + smol gpt-5.6-terra:max + tiny gpt-5.6-terra:max + commit gpt-5.6-luna:max + +mixed_fast_minimal_sp_fa mixed · fast · minimal · spark · fable + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_minimal_sp_famain mixed · fast · minimal · spark · fable · main + thinking minimal · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_minimal_sp_nofa mixed · fast · minimal · spark + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_minimal_nosp_fa mixed · fast · minimal · fable + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_minimal_nosp_famain mixed · fast · minimal · fable · main + thinking minimal · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_minimal_nosp_nofa mixed · fast · minimal + thinking minimal · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● task gpt-5.6-luna:low → claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:minimal + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_low_sp_fa mixed · fast · low · spark · fable + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_low_sp_famain mixed · fast · low · spark · fable · main + thinking low · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_low_sp_nofa mixed · fast · low · spark + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_low_nosp_fa mixed · fast · low · fable + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_low_nosp_famain mixed · fast · low · fable · main + thinking low · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_low_nosp_nofa mixed · fast · low + thinking low · fallback on · advisor off + default gpt-5.6-luna:low → claude-haiku-4-5:low + ● task gpt-5.6-luna:low → claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● librarian gpt-5.6-luna:low → claude-haiku-4-5:low + ● scout gpt-5.6-luna:low + ● sonic gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_medium_sp_fa mixed · fast · medium · spark · fable + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_medium_sp_famain mixed · fast · medium · spark · fable · main + thinking medium · fallback on · advisor off + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_medium_sp_nofa mixed · fast · medium · spark + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_medium_nosp_fa mixed · fast · medium · fable + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_medium_nosp_famain mixed · fast · medium · fable · main + thinking medium · fallback on · advisor off + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_medium_nosp_nofa mixed · fast · medium + thinking medium · fallback on · advisor off + default gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● task gpt-5.6-luna:medium → claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● librarian gpt-5.6-luna:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_high_sp_fa mixed · fast · high · spark · fable + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_high_sp_famain mixed · fast · high · spark · fable · main + thinking high · fallback on · advisor off + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_high_sp_nofa mixed · fast · high · spark + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_high_nosp_fa mixed · fast · high · fable + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_high_nosp_famain mixed · fast · high · fable · main + thinking high · fallback on · advisor off + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_high_nosp_nofa mixed · fast · high + thinking high · fallback on · advisor off + default gpt-5.6-luna:high → claude-haiku-4-5:high + ● task gpt-5.6-luna:high → claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:high → claude-haiku-4-5:high + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_xhigh_sp_fa mixed · fast · xhigh · spark · fable + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_xhigh_sp_famain mixed · fast · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor off + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_xhigh_sp_nofa mixed · fast · xhigh · spark + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.3-codex-spark:low → gpt-5.6-luna:low + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_fast_xhigh_nosp_fa mixed · fast · xhigh · fable + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_xhigh_nosp_famain mixed · fast · xhigh · fable · main + thinking xhigh · fallback on · advisor off + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_xhigh_nosp_nofa mixed · fast · xhigh + thinking xhigh · fallback on · advisor off + default gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● librarian gpt-5.6-luna:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:medium + ● sonic gpt-5.6-luna:medium + vision gpt-5.6-luna:low → claude-haiku-4-5:low + smol gpt-5.6-luna:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_fast_max_sp_fa mixed · fast · max · spark · fable + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_fast_max_sp_famain mixed · fast · max · spark · fable · main + thinking max · fallback on · advisor off + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_fast_max_sp_nofa mixed · fast · max · spark + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_fast_max_nosp_fa mixed · fast · max · fable + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +mixed_fast_max_nosp_famain mixed · fast · max · fable · main + thinking max · fallback on · advisor off + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +mixed_fast_max_nosp_nofa mixed · fast · max + thinking max · fallback on · advisor off + default gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-luna:max → claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● librarian gpt-5.6-luna:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-luna:max + ● sonic gpt-5.6-luna:max + vision gpt-5.6-luna:max → claude-haiku-4-5:xhigh + smol gpt-5.6-luna:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +mixed_normal_minimal_sp_fa mixed · normal · minimal · spark · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_minimal_sp_famain mixed · normal · minimal · spark · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_minimal_sp_nofa mixed · normal · minimal · spark + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_minimal_nosp_fa mixed · normal · minimal · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_minimal_nosp_famain mixed · normal · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_minimal_nosp_nofa mixed · normal · minimal + thinking minimal · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:minimal → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_low_sp_fa mixed · normal · low · spark · fable + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_low_sp_famain mixed · normal · low · spark · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_low_sp_nofa mixed · normal · low · spark + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_low_nosp_fa mixed · normal · low · fable + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_low_nosp_famain mixed · normal · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_low_nosp_nofa mixed · normal · low + thinking low · fallback on · advisor on + default gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_medium_sp_fa mixed · normal · medium · spark · fable + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_medium_sp_famain mixed · normal · medium · spark · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_medium_sp_nofa mixed · normal · medium · spark + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_medium_nosp_fa mixed · normal · medium · fable + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_medium_nosp_famain mixed · normal · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_medium_nosp_nofa mixed · normal · medium + thinking medium · fallback on · advisor on + default gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:low + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_high_sp_fa mixed · normal · high · spark · fable + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_high_sp_famain mixed · normal · high · spark · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_high_sp_nofa mixed · normal · high · spark + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_high_nosp_fa mixed · normal · high · fable + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_high_nosp_famain mixed · normal · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_high_nosp_nofa mixed · normal · high + thinking high · fallback on · advisor on + default gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_xhigh_sp_fa mixed · normal · xhigh · spark · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_xhigh_sp_famain mixed · normal · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_xhigh_sp_nofa mixed · normal · xhigh · spark + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-luna:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_normal_xhigh_nosp_fa mixed · normal · xhigh · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_xhigh_nosp_famain mixed · normal · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_xhigh_nosp_nofa mixed · normal · xhigh + thinking xhigh · fallback on · advisor on + default gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-haiku-4-5:low → gpt-5.6-luna:low + vision gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-luna:low + commit gpt-5.6-luna:low + +mixed_normal_max_sp_fa mixed · normal · max · spark · fable + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_normal_max_sp_famain mixed · normal · max · spark · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_normal_max_sp_nofa mixed · normal · max · spark + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_normal_max_nosp_fa mixed · normal · max · fable + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +mixed_normal_max_nosp_famain mixed · normal · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +mixed_normal_max_nosp_nofa mixed · normal · max + thinking max · fallback on · advisor on + default gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-haiku-4-5:xhigh → gpt-5.6-luna:max + vision gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol gpt-5.6-terra:max + tiny gpt-5.6-luna:max + commit gpt-5.6-luna:max + +mixed_smart_minimal_sp_fa mixed · smart · minimal · spark · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_minimal_sp_famain mixed · smart · minimal · spark · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_minimal_sp_nofa mixed · smart · minimal · spark + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_minimal_nosp_fa mixed · smart · minimal · fable + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_minimal_nosp_famain mixed · smart · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_minimal_nosp_nofa mixed · smart · minimal + thinking minimal · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_low_sp_fa mixed · smart · low · spark · fable + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_low_sp_famain mixed · smart · low · spark · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_low_sp_nofa mixed · smart · low · spark + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_low_nosp_fa mixed · smart · low · fable + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_low_nosp_famain mixed · smart · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_low_nosp_nofa mixed · smart · low + thinking low · fallback on · advisor on + default gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● task gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● librarian gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● scout gpt-5.6-terra:low → gpt-5.6-luna:low + ● sonic gpt-5.6-terra:low → gpt-5.6-luna:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_medium_sp_fa mixed · smart · medium · spark · fable + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_medium_sp_famain mixed · smart · medium · spark · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_medium_sp_nofa mixed · smart · medium · spark + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_medium_nosp_fa mixed · smart · medium · fable + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_medium_nosp_famain mixed · smart · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_medium_nosp_nofa mixed · smart · medium + thinking medium · fallback on · advisor on + default gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● librarian gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:low + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_high_sp_fa mixed · smart · high · spark · fable + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_high_sp_famain mixed · smart · high · spark · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_high_sp_nofa mixed · smart · high · spark + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_high_nosp_fa mixed · smart · high · fable + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_high_nosp_famain mixed · smart · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_high_nosp_nofa mixed · smart · high + thinking high · fallback on · advisor on + default gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● task gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_xhigh_sp_fa mixed · smart · xhigh · spark · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_xhigh_sp_famain mixed · smart · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_xhigh_sp_nofa mixed · smart · xhigh · spark + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.3-codex-spark:low → gpt-5.6-terra:low + commit gpt-5.3-codex-spark:low → gpt-5.6-luna:low + +mixed_smart_xhigh_nosp_fa mixed · smart · xhigh · fable + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_xhigh_nosp_famain mixed · smart · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_xhigh_nosp_nofa mixed · smart · xhigh + thinking xhigh · fallback on · advisor on + default gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● librarian gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● scout gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● sonic gpt-5.6-terra:medium → gpt-5.6-luna:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol gpt-5.6-terra:medium + tiny gpt-5.6-terra:low + commit gpt-5.6-luna:low + +mixed_smart_max_sp_fa mixed · smart · max · spark · fable + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-terra:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_smart_max_sp_famain mixed · smart · max · spark · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-terra:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_smart_max_sp_nofa mixed · smart · max · spark + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol gpt-5.6-terra:max + tiny gpt-5.3-codex-spark:xhigh → gpt-5.6-terra:max + commit gpt-5.3-codex-spark:xhigh → gpt-5.6-luna:max + +mixed_smart_max_nosp_fa mixed · smart · max · fable + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol gpt-5.6-terra:max + tiny gpt-5.6-terra:max + commit gpt-5.6-luna:max + +mixed_smart_max_nosp_famain mixed · smart · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol gpt-5.6-terra:max + tiny gpt-5.6-terra:max + commit gpt-5.6-luna:max + +mixed_smart_max_nosp_nofa mixed · smart · max + thinking max · fallback on · advisor on + default gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● task gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● librarian gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● scout gpt-5.6-terra:max → gpt-5.6-luna:max + ● sonic gpt-5.6-terra:max → gpt-5.6-luna:max + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol gpt-5.6-terra:max + tiny gpt-5.6-terra:max + commit gpt-5.6-luna:max + +claude-led_fast_minimal_sp_fa claude-led · fast · minimal · spark · fable + thinking minimal · fallback on · advisor off + default claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● task claude-haiku-4-5:minimal → gpt-5.6-luna:low + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● scout claude-haiku-4-5:minimal + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal → gpt-5.6-luna:low + smol claude-haiku-4-5:minimal + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_fast_minimal_sp_famain claude-led · fast · minimal · spark · fable · main + thinking minimal · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-haiku-4-5:minimal → gpt-5.6-luna:low + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● scout claude-haiku-4-5:minimal + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal → gpt-5.6-luna:low + smol claude-haiku-4-5:minimal + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_fast_minimal_sp_nofa claude-led · fast · minimal · spark + thinking minimal · fallback on · advisor off + default claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● task claude-haiku-4-5:minimal → gpt-5.6-luna:low + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● scout claude-haiku-4-5:minimal + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal → gpt-5.6-luna:low + smol claude-haiku-4-5:minimal + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_fast_minimal_nosp_fa claude-led · fast · minimal · fable + thinking minimal · fallback on · advisor off + default claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● task claude-haiku-4-5:minimal → gpt-5.6-luna:low + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● scout claude-haiku-4-5:minimal + ● sonic claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal → gpt-5.6-luna:low + smol claude-haiku-4-5:minimal + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_fast_minimal_nosp_famain claude-led · fast · minimal · fable · main + thinking minimal · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-haiku-4-5:minimal → gpt-5.6-luna:low + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● scout claude-haiku-4-5:minimal + ● sonic claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal → gpt-5.6-luna:low + smol claude-haiku-4-5:minimal + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_fast_minimal_nosp_nofa claude-led · fast · minimal + thinking minimal · fallback on · advisor off + default claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● task claude-haiku-4-5:minimal → gpt-5.6-luna:low + plan claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + slow claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal → gpt-5.6-luna:low + ● scout claude-haiku-4-5:minimal + ● sonic claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal → gpt-5.6-luna:low + smol claude-haiku-4-5:minimal + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_fast_low_sp_fa claude-led · fast · low · spark · fable + thinking low · fallback on · advisor off + default claude-haiku-4-5:low → gpt-5.6-luna:low + ● task claude-haiku-4-5:low → gpt-5.6-luna:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low → gpt-5.6-luna:low + ● scout claude-haiku-4-5:low + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_low_sp_famain claude-led · fast · low · spark · fable · main + thinking low · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-haiku-4-5:low → gpt-5.6-luna:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low → gpt-5.6-luna:low + ● scout claude-haiku-4-5:low + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_low_sp_nofa claude-led · fast · low · spark + thinking low · fallback on · advisor off + default claude-haiku-4-5:low → gpt-5.6-luna:low + ● task claude-haiku-4-5:low → gpt-5.6-luna:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low → gpt-5.6-luna:low + ● scout claude-haiku-4-5:low + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_low_nosp_fa claude-led · fast · low · fable + thinking low · fallback on · advisor off + default claude-haiku-4-5:low → gpt-5.6-luna:low + ● task claude-haiku-4-5:low → gpt-5.6-luna:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low → gpt-5.6-luna:low + ● scout claude-haiku-4-5:low + ● sonic claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_fast_low_nosp_famain claude-led · fast · low · fable · main + thinking low · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-haiku-4-5:low → gpt-5.6-luna:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low → gpt-5.6-luna:low + ● scout claude-haiku-4-5:low + ● sonic claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_fast_low_nosp_nofa claude-led · fast · low + thinking low · fallback on · advisor off + default claude-haiku-4-5:low → gpt-5.6-luna:low + ● task claude-haiku-4-5:low → gpt-5.6-luna:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer gpt-5.6-terra:medium → gpt-5.6-luna:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low → gpt-5.6-luna:low + ● scout claude-haiku-4-5:low + ● sonic claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_fast_medium_sp_fa claude-led · fast · medium · spark · fable + thinking medium · fallback on · advisor off + default claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● task claude-haiku-4-5:medium → gpt-5.6-luna:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_medium_sp_famain claude-led · fast · medium · spark · fable · main + thinking medium · fallback on · advisor off + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-haiku-4-5:medium → gpt-5.6-luna:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_medium_sp_nofa claude-led · fast · medium · spark + thinking medium · fallback on · advisor off + default claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● task claude-haiku-4-5:medium → gpt-5.6-luna:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_medium_nosp_fa claude-led · fast · medium · fable + thinking medium · fallback on · advisor off + default claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● task claude-haiku-4-5:medium → gpt-5.6-luna:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_fast_medium_nosp_famain claude-led · fast · medium · fable · main + thinking medium · fallback on · advisor off + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-haiku-4-5:medium → gpt-5.6-luna:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_fast_medium_nosp_nofa claude-led · fast · medium + thinking medium · fallback on · advisor off + default claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● task claude-haiku-4-5:medium → gpt-5.6-luna:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + slow claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer gpt-5.6-terra:high → gpt-5.6-luna:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium → gpt-5.6-luna:medium + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_fast_high_sp_fa claude-led · fast · high · spark · fable + thinking high · fallback on · advisor off + default claude-haiku-4-5:high → gpt-5.6-luna:high + ● task claude-haiku-4-5:high → gpt-5.6-luna:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high → gpt-5.6-luna:high + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_high_sp_famain claude-led · fast · high · spark · fable · main + thinking high · fallback on · advisor off + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-haiku-4-5:high → gpt-5.6-luna:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high → gpt-5.6-luna:high + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_high_sp_nofa claude-led · fast · high · spark + thinking high · fallback on · advisor off + default claude-haiku-4-5:high → gpt-5.6-luna:high + ● task claude-haiku-4-5:high → gpt-5.6-luna:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high → gpt-5.6-luna:high + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_high_nosp_fa claude-led · fast · high · fable + thinking high · fallback on · advisor off + default claude-haiku-4-5:high → gpt-5.6-luna:high + ● task claude-haiku-4-5:high → gpt-5.6-luna:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high → gpt-5.6-luna:high + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_fast_high_nosp_famain claude-led · fast · high · fable · main + thinking high · fallback on · advisor off + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-haiku-4-5:high → gpt-5.6-luna:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high → gpt-5.6-luna:high + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_fast_high_nosp_nofa claude-led · fast · high + thinking high · fallback on · advisor off + default claude-haiku-4-5:high → gpt-5.6-luna:high + ● task claude-haiku-4-5:high → gpt-5.6-luna:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high → gpt-5.6-luna:high + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_fast_xhigh_sp_fa claude-led · fast · xhigh · spark · fable + thinking xhigh · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_xhigh_sp_famain claude-led · fast · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor off + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_xhigh_sp_nofa claude-led · fast · xhigh · spark + thinking xhigh · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● scout claude-haiku-4-5:medium + ● sonic gpt-5.3-codex-spark:low → claude-haiku-4-5:low + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_fast_xhigh_nosp_fa claude-led · fast · xhigh · fable + thinking xhigh · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-led_fast_xhigh_nosp_famain claude-led · fast · xhigh · fable · main + thinking xhigh · fallback on · advisor off + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-led_fast_xhigh_nosp_nofa claude-led · fast · xhigh + thinking xhigh · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:xhigh + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low → gpt-5.6-luna:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-led_fast_max_sp_fa claude-led · fast · max · spark · fable + thinking max · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:max + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● scout claude-haiku-4-5:xhigh + ● sonic gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh → gpt-5.6-luna:max + smol claude-haiku-4-5:xhigh + tiny gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_fast_max_sp_famain claude-led · fast · max · spark · fable · main + thinking max · fallback on · advisor off + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:max + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● scout claude-haiku-4-5:xhigh + ● sonic gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh → gpt-5.6-luna:max + smol claude-haiku-4-5:xhigh + tiny gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_fast_max_sp_nofa claude-led · fast · max · spark + thinking max · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:max + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● scout claude-haiku-4-5:xhigh + ● sonic gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh → gpt-5.6-luna:max + smol claude-haiku-4-5:xhigh + tiny gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_fast_max_nosp_fa claude-led · fast · max · fable + thinking max · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:max + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● scout claude-haiku-4-5:xhigh + ● sonic claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh → gpt-5.6-luna:max + smol claude-haiku-4-5:xhigh + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-led_fast_max_nosp_famain claude-led · fast · max · fable · main + thinking max · fallback on · advisor off + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:max + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● scout claude-haiku-4-5:xhigh + ● sonic claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh → gpt-5.6-luna:max + smol claude-haiku-4-5:xhigh + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-led_fast_max_nosp_nofa claude-led · fast · max + thinking max · fallback on · advisor off + default claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● task claude-haiku-4-5:xhigh → gpt-5.6-luna:max + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh → gpt-5.6-luna:max + ● scout claude-haiku-4-5:xhigh + ● sonic claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh → gpt-5.6-luna:max + smol claude-haiku-4-5:xhigh + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-led_normal_minimal_sp_fa claude-led · normal · minimal · spark · fable + thinking minimal · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-luna:low → claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_normal_minimal_sp_famain claude-led · normal · minimal · spark · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-luna:low → claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_normal_minimal_sp_nofa claude-led · normal · minimal · spark + thinking minimal · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-luna:low → claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_normal_minimal_nosp_fa claude-led · normal · minimal · fable + thinking minimal · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-luna:low → claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_normal_minimal_nosp_famain claude-led · normal · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-luna:low → claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_normal_minimal_nosp_nofa claude-led · normal · minimal + thinking minimal · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-luna:low → claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_normal_low_sp_fa claude-led · normal · low · spark · fable + thinking low · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_low_sp_famain claude-led · normal · low · spark · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_low_sp_nofa claude-led · normal · low · spark + thinking low · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_low_nosp_fa claude-led · normal · low · fable + thinking low · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_normal_low_nosp_famain claude-led · normal · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_normal_low_nosp_nofa claude-led · normal · low + thinking low · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-led_normal_medium_sp_fa claude-led · normal · medium · spark · fable + thinking medium · fallback on · advisor on + default claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_medium_sp_famain claude-led · normal · medium · spark · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_medium_sp_nofa claude-led · normal · medium · spark + thinking medium · fallback on · advisor on + default claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_medium_nosp_fa claude-led · normal · medium · fable + thinking medium · fallback on · advisor on + default claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_normal_medium_nosp_famain claude-led · normal · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_normal_medium_nosp_nofa claude-led · normal · medium + thinking medium · fallback on · advisor on + default claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium → gpt-5.6-terra:medium → gpt-5.6-luna:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_normal_high_sp_fa claude-led · normal · high · spark · fable + thinking high · fallback on · advisor on + default claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_high_sp_famain claude-led · normal · high · spark · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_high_sp_nofa claude-led · normal · high · spark + thinking high · fallback on · advisor on + default claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_high_nosp_fa claude-led · normal · high · fable + thinking high · fallback on · advisor on + default claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_normal_high_nosp_famain claude-led · normal · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_normal_high_nosp_nofa claude-led · normal · high + thinking high · fallback on · advisor on + default claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high → gpt-5.6-terra:high → gpt-5.6-luna:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-led_normal_xhigh_sp_fa claude-led · normal · xhigh · spark · fable + thinking xhigh · fallback on · advisor on + default claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_xhigh_sp_famain claude-led · normal · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_xhigh_sp_nofa claude-led · normal · xhigh · spark + thinking xhigh · fallback on · advisor on + default claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-haiku-4-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_normal_xhigh_nosp_fa claude-led · normal · xhigh · fable + thinking xhigh · fallback on · advisor on + default claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-led_normal_xhigh_nosp_famain claude-led · normal · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-led_normal_xhigh_nosp_nofa claude-led · normal · xhigh + thinking xhigh · fallback on · advisor on + default claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh → gpt-5.6-terra:xhigh → gpt-5.6-luna:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-luna:low → claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low → gpt-5.6-terra:low → gpt-5.6-luna:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-led_normal_max_sp_fa claude-led · normal · max · spark · fable + thinking max · fallback on · advisor on + default claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-luna:max → claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + smol claude-sonnet-5:max + tiny gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_normal_max_sp_famain claude-led · normal · max · spark · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-luna:max → claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + smol claude-sonnet-5:max + tiny gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_normal_max_sp_nofa claude-led · normal · max · spark + thinking max · fallback on · advisor on + default claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-luna:max → claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + smol claude-sonnet-5:max + tiny gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_normal_max_nosp_fa claude-led · normal · max · fable + thinking max · fallback on · advisor on + default claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-luna:max → claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + smol claude-sonnet-5:max + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-led_normal_max_nosp_famain claude-led · normal · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-luna:max → claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + smol claude-sonnet-5:max + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-led_normal_max_nosp_nofa claude-led · normal · max + thinking max · fallback on · advisor on + default claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-luna:max → claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh → gpt-5.6-terra:max → gpt-5.6-luna:max + smol claude-sonnet-5:max + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-led_smart_minimal_sp_fa claude-led · smart · minimal · spark · fable + thinking minimal · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_smart_minimal_sp_famain claude-led · smart · minimal · spark · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_smart_minimal_sp_nofa claude-led · smart · minimal · spark + thinking minimal · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:minimal + +claude-led_smart_minimal_nosp_fa claude-led · smart · minimal · fable + thinking minimal · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_minimal_nosp_famain claude-led · smart · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_minimal_nosp_nofa claude-led · smart · minimal + thinking minimal · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + slow claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● designer claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer gpt-5.6-sol:low → gpt-5.6-terra:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor gpt-5.6-terra:low → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_low_sp_fa claude-led · smart · low · spark · fable + thinking low · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_low_sp_famain claude-led · smart · low · spark · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_low_sp_nofa claude-led · smart · low · spark + thinking low · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_low_nosp_fa claude-led · smart · low · fable + thinking low · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_low_nosp_famain claude-led · smart · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_low_nosp_nofa claude-led · smart · low + thinking low · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● task claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + plan claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer gpt-5.6-sol:medium → gpt-5.6-terra:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_medium_sp_fa claude-led · smart · medium · spark · fable + thinking medium · fallback on · advisor on + default claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_medium_sp_famain claude-led · smart · medium · spark · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_medium_sp_nofa claude-led · smart · medium · spark + thinking medium · fallback on · advisor on + default claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_medium_nosp_fa claude-led · smart · medium · fable + thinking medium · fallback on · advisor on + default claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_medium_nosp_famain claude-led · smart · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + plan claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_medium_nosp_nofa claude-led · smart · medium + thinking medium · fallback on · advisor on + default claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + plan claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + slow claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● designer claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer gpt-5.6-sol:high → gpt-5.6-terra:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → gpt-5.6-sol:medium → gpt-5.6-terra:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_high_sp_fa claude-led · smart · high · spark · fable + thinking high · fallback on · advisor on + default claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_high_sp_famain claude-led · smart · high · spark · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_high_sp_nofa claude-led · smart · high · spark + thinking high · fallback on · advisor on + default claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_high_nosp_fa claude-led · smart · high · fable + thinking high · fallback on · advisor on + default claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_high_nosp_famain claude-led · smart · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_high_nosp_nofa claude-led · smart · high + thinking high · fallback on · advisor on + default claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● task claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → gpt-5.6-sol:high → gpt-5.6-terra:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-led_smart_xhigh_sp_fa claude-led · smart · xhigh · spark · fable + thinking xhigh · fallback on · advisor on + default claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_xhigh_sp_famain claude-led · smart · xhigh · spark · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_xhigh_sp_nofa claude-led · smart · xhigh · spark + thinking xhigh · fallback on · advisor on + default claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny gpt-5.3-codex-spark:low → claude-sonnet-5:low + commit gpt-5.3-codex-spark:low → claude-haiku-4-5:low + +claude-led_smart_xhigh_nosp_fa claude-led · smart · xhigh · fable + thinking xhigh · fallback on · advisor on + default claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:low + +claude-led_smart_xhigh_nosp_famain claude-led · smart · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:low + +claude-led_smart_xhigh_nosp_nofa claude-led · smart · xhigh + thinking xhigh · fallback on · advisor on + default claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → gpt-5.6-sol:xhigh → gpt-5.6-terra:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor gpt-5.6-terra:high → gpt-5.6-luna:low → claude-sonnet-5:low → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → gpt-5.6-sol:low → gpt-5.6-terra:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:low + +claude-led_smart_max_sp_fa claude-led · smart · max · spark · fable + thinking max · fallback on · advisor on + default claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol claude-sonnet-5:max + tiny gpt-5.3-codex-spark:xhigh → claude-sonnet-5:max + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_smart_max_sp_famain claude-led · smart · max · spark · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol claude-sonnet-5:max + tiny gpt-5.3-codex-spark:xhigh → claude-sonnet-5:max + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_smart_max_sp_nofa claude-led · smart · max · spark + thinking max · fallback on · advisor on + default claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol claude-sonnet-5:max + tiny gpt-5.3-codex-spark:xhigh → claude-sonnet-5:max + commit gpt-5.3-codex-spark:xhigh → claude-haiku-4-5:xhigh + +claude-led_smart_max_nosp_fa claude-led · smart · max · fable + thinking max · fallback on · advisor on + default claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol claude-sonnet-5:max + tiny claude-sonnet-5:max + commit claude-haiku-4-5:xhigh + +claude-led_smart_max_nosp_famain claude-led · smart · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + plan claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-fable-5:max → claude-opus-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol claude-sonnet-5:max + tiny claude-sonnet-5:max + commit claude-haiku-4-5:xhigh + +claude-led_smart_max_nosp_nofa claude-led · smart · max + thinking max · fallback on · advisor on + default claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● task claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + plan claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + slow claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● designer claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer gpt-5.6-sol:max → gpt-5.6-terra:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor gpt-5.6-terra:max → gpt-5.6-luna:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → gpt-5.6-sol:max → gpt-5.6-terra:max + smol claude-sonnet-5:max + tiny claude-sonnet-5:max + commit claude-haiku-4-5:xhigh + +claude-only_fast_minimal_nosp_fa claude-only · fast · minimal · fable + thinking minimal · fallback on · advisor off + default claude-haiku-4-5:minimal + ● task claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal + slow claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal + ● scout claude-haiku-4-5:minimal + ● sonic claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal + smol claude-haiku-4-5:minimal + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_fast_minimal_nosp_famain claude-only · fast · minimal · fable · main + thinking minimal · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● task claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal + slow claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal + ● scout claude-haiku-4-5:minimal + ● sonic claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal + smol claude-haiku-4-5:minimal + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_fast_minimal_nosp_nofa claude-only · fast · minimal + thinking minimal · fallback on · advisor off + default claude-haiku-4-5:minimal + ● task claude-haiku-4-5:minimal + plan claude-sonnet-5:low → claude-haiku-4-5:minimal + slow claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-haiku-4-5:minimal + ● scout claude-haiku-4-5:minimal + ● sonic claude-haiku-4-5:minimal + vision claude-haiku-4-5:minimal + smol claude-haiku-4-5:minimal + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_fast_low_nosp_fa claude-only · fast · low · fable + thinking low · fallback on · advisor off + default claude-haiku-4-5:low + ● task claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low + ● scout claude-haiku-4-5:low + ● sonic claude-haiku-4-5:low + vision claude-haiku-4-5:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_fast_low_nosp_famain claude-only · fast · low · fable · main + thinking low · fallback on · advisor off + default claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● task claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low + ● scout claude-haiku-4-5:low + ● sonic claude-haiku-4-5:low + vision claude-haiku-4-5:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_fast_low_nosp_nofa claude-only · fast · low + thinking low · fallback on · advisor off + default claude-haiku-4-5:low + ● task claude-haiku-4-5:low + plan claude-sonnet-5:medium → claude-haiku-4-5:medium + slow claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-haiku-4-5:low + ● scout claude-haiku-4-5:low + ● sonic claude-haiku-4-5:low + vision claude-haiku-4-5:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_fast_medium_nosp_fa claude-only · fast · medium · fable + thinking medium · fallback on · advisor off + default claude-haiku-4-5:medium + ● task claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high + slow claude-sonnet-5:high → claude-haiku-4-5:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_fast_medium_nosp_famain claude-only · fast · medium · fable · main + thinking medium · fallback on · advisor off + default claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high + slow claude-sonnet-5:high → claude-haiku-4-5:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_fast_medium_nosp_nofa claude-only · fast · medium + thinking medium · fallback on · advisor off + default claude-haiku-4-5:medium + ● task claude-haiku-4-5:medium + plan claude-sonnet-5:high → claude-haiku-4-5:high + slow claude-sonnet-5:high → claude-haiku-4-5:high + ● designer claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-haiku-4-5:medium + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_fast_high_nosp_fa claude-only · fast · high · fable + thinking high · fallback on · advisor off + default claude-haiku-4-5:high + ● task claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_fast_high_nosp_famain claude-only · fast · high · fable · main + thinking high · fallback on · advisor off + default claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● task claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_fast_high_nosp_nofa claude-only · fast · high + thinking high · fallback on · advisor off + default claude-haiku-4-5:high + ● task claude-haiku-4-5:high + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:high + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_fast_xhigh_nosp_fa claude-only · fast · xhigh · fable + thinking xhigh · fallback on · advisor off + default claude-haiku-4-5:xhigh + ● task claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-only_fast_xhigh_nosp_famain claude-only · fast · xhigh · fable · main + thinking xhigh · fallback on · advisor off + default claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-only_fast_xhigh_nosp_nofa claude-only · fast · xhigh + thinking xhigh · fallback on · advisor off + default claude-haiku-4-5:xhigh + ● task claude-haiku-4-5:xhigh + plan claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh + ● scout claude-haiku-4-5:medium + ● sonic claude-haiku-4-5:medium + vision claude-haiku-4-5:low + smol claude-haiku-4-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-only_fast_max_nosp_fa claude-only · fast · max · fable + thinking max · fallback on · advisor off + default claude-haiku-4-5:xhigh + ● task claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh + ● scout claude-haiku-4-5:xhigh + ● sonic claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh + smol claude-haiku-4-5:xhigh + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-only_fast_max_nosp_famain claude-only · fast · max · fable · main + thinking max · fallback on · advisor off + default claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● task claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh + ● scout claude-haiku-4-5:xhigh + ● sonic claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh + smol claude-haiku-4-5:xhigh + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-only_fast_max_nosp_nofa claude-only · fast · max + thinking max · fallback on · advisor off + default claude-haiku-4-5:xhigh + ● task claude-haiku-4-5:xhigh + plan claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-haiku-4-5:xhigh + ● scout claude-haiku-4-5:xhigh + ● sonic claude-haiku-4-5:xhigh + vision claude-haiku-4-5:xhigh + smol claude-haiku-4-5:xhigh + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-only_normal_minimal_nosp_fa claude-only · normal · minimal · fable + thinking minimal · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + slow claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● designer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_normal_minimal_nosp_famain claude-only · normal · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + slow claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● designer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_normal_minimal_nosp_nofa claude-only · normal · minimal + thinking minimal · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor claude-haiku-4-5:minimal + vision claude-sonnet-5:low → claude-haiku-4-5:minimal + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_normal_low_nosp_fa claude-only · normal · low · fable + thinking low · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_normal_low_nosp_famain claude-only · normal · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_normal_low_nosp_nofa claude-only · normal · low + thinking low · fallback on · advisor on + default claude-sonnet-5:low → claude-haiku-4-5:low + ● task claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-sonnet-5:low → claude-haiku-4-5:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:minimal + commit claude-haiku-4-5:minimal + +claude-only_normal_medium_nosp_fa claude-only · normal · medium · fable + thinking medium · fallback on · advisor on + default claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + slow claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● designer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_normal_medium_nosp_famain claude-only · normal · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + slow claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● designer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_normal_medium_nosp_nofa claude-only · normal · medium + thinking medium · fallback on · advisor on + default claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_normal_high_nosp_fa claude-only · normal · high · fable + thinking high · fallback on · advisor on + default claude-sonnet-5:high → claude-haiku-4-5:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_normal_high_nosp_famain claude-only · normal · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_normal_high_nosp_nofa claude-only · normal · high + thinking high · fallback on · advisor on + default claude-sonnet-5:high → claude-haiku-4-5:high + ● task claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-sonnet-5:high → claude-haiku-4-5:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:minimal + +claude-only_normal_xhigh_nosp_fa claude-only · normal · xhigh · fable + thinking xhigh · fallback on · advisor on + default claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-only_normal_xhigh_nosp_famain claude-only · normal · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-only_normal_xhigh_nosp_nofa claude-only · normal · xhigh + thinking xhigh · fallback on · advisor on + default claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-haiku-4-5:low + vision claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-haiku-4-5:low + commit claude-haiku-4-5:low + +claude-only_normal_max_nosp_fa claude-only · normal · max · fable + thinking max · fallback on · advisor on + default claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + slow claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● designer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol claude-sonnet-5:max + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-only_normal_max_nosp_famain claude-only · normal · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + slow claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● designer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol claude-sonnet-5:max + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-only_normal_max_nosp_nofa claude-only · normal · max + thinking max · fallback on · advisor on + default claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor claude-haiku-4-5:xhigh + vision claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol claude-sonnet-5:max + tiny claude-haiku-4-5:xhigh + commit claude-haiku-4-5:xhigh + +claude-only_smart_minimal_nosp_fa claude-only · smart · minimal · fable + thinking minimal · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + slow claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● designer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_minimal_nosp_famain claude-only · smart · minimal · fable · main + thinking minimal · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● task claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + slow claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● designer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● security-reviewer claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● librarian claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_minimal_nosp_nofa claude-only · smart · minimal + thinking minimal · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● task claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + plan claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + slow claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● designer claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● reviewer claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● security-reviewer claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● librarian claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + ● scout claude-sonnet-5:low → claude-haiku-4-5:minimal + ● sonic claude-sonnet-5:low → claude-haiku-4-5:minimal + advisor claude-sonnet-5:low → claude-haiku-4-5:minimal + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:minimal + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_low_nosp_fa claude-only · smart · low · fable + thinking low · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_low_nosp_famain claude-only · smart · low · fable · main + thinking low · fallback on · advisor on + default claude-fable-5:low → claude-opus-5:low → claude-sonnet-5:low + ● task claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + slow claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● designer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● security-reviewer claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_low_nosp_nofa claude-only · smart · low + thinking low · fallback on · advisor on + default claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● task claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + plan claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + slow claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● designer claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● reviewer claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● security-reviewer claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● librarian claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + ● scout claude-sonnet-5:low → claude-haiku-4-5:low + ● sonic claude-sonnet-5:low → claude-haiku-4-5:low + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_medium_nosp_fa claude-only · smart · medium · fable + thinking medium · fallback on · advisor on + default claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + slow claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● designer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_medium_nosp_famain claude-only · smart · medium · fable · main + thinking medium · fallback on · advisor on + default claude-fable-5:medium → claude-opus-5:medium → claude-sonnet-5:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + slow claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● designer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● security-reviewer claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_medium_nosp_nofa claude-only · smart · medium + thinking medium · fallback on · advisor on + default claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● task claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + plan claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + slow claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● designer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● reviewer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● security-reviewer claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● librarian claude-opus-5:medium → claude-sonnet-5:medium → claude-haiku-4-5:medium + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:low + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_high_nosp_fa claude-only · smart · high · fable + thinking high · fallback on · advisor on + default claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_high_nosp_famain claude-only · smart · high · fable · main + thinking high · fallback on · advisor on + default claude-fable-5:high → claude-opus-5:high → claude-sonnet-5:high + ● task claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_high_nosp_nofa claude-only · smart · high + thinking high · fallback on · advisor on + default claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● task claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-opus-5:high → claude-sonnet-5:high → claude-haiku-4-5:high + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:minimal + +claude-only_smart_xhigh_nosp_fa claude-only · smart · xhigh · fable + thinking xhigh · fallback on · advisor on + default claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:low + +claude-only_smart_xhigh_nosp_famain claude-only · smart · xhigh · fable · main + thinking xhigh · fallback on · advisor on + default claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + slow claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● designer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● security-reviewer claude-fable-5:xhigh → claude-opus-5:xhigh → claude-sonnet-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:low + +claude-only_smart_xhigh_nosp_nofa claude-only · smart · xhigh + thinking xhigh · fallback on · advisor on + default claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● task claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + plan claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + slow claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● designer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● security-reviewer claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● librarian claude-opus-5:xhigh → claude-sonnet-5:xhigh → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:medium → claude-haiku-4-5:medium + ● sonic claude-sonnet-5:medium → claude-haiku-4-5:medium + advisor claude-sonnet-5:high → claude-haiku-4-5:low + vision claude-opus-5:low → claude-sonnet-5:low → claude-haiku-4-5:low + smol claude-sonnet-5:medium + tiny claude-sonnet-5:low + commit claude-haiku-4-5:low + +claude-only_smart_max_nosp_fa claude-only · smart · max · fable + thinking max · fallback on · advisor on + default claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + slow claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● designer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol claude-sonnet-5:max + tiny claude-sonnet-5:max + commit claude-haiku-4-5:xhigh + +claude-only_smart_max_nosp_famain claude-only · smart · max · fable · main + thinking max · fallback on · advisor on + default claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● task claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + slow claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● designer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● security-reviewer claude-fable-5:max → claude-opus-5:max → claude-sonnet-5:max + ● librarian claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol claude-sonnet-5:max + tiny claude-sonnet-5:max + commit claude-haiku-4-5:xhigh + +claude-only_smart_max_nosp_nofa claude-only · smart · max + thinking max · fallback on · advisor on + default claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● task claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + plan claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + slow claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● designer claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● reviewer claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● security-reviewer claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● librarian claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● scout claude-sonnet-5:max → claude-haiku-4-5:xhigh + ● sonic claude-sonnet-5:max → claude-haiku-4-5:xhigh + advisor claude-sonnet-5:max → claude-haiku-4-5:xhigh + vision claude-opus-5:max → claude-sonnet-5:max → claude-haiku-4-5:xhigh + smol claude-sonnet-5:max + tiny claude-sonnet-5:max + commit claude-haiku-4-5:xhigh + diff --git a/theme.go b/theme.go new file mode 100644 index 0000000..d78c7eb --- /dev/null +++ b/theme.go @@ -0,0 +1,60 @@ +package main + +import ( + "regexp" + + clikit "github.com/atyrode/cli-kit" + "github.com/charmbracelet/lipgloss" +) + +// ── palette ────────────────────────────────────────────────────────────────── +// The palette, glyphs, and styles now live in the shared cli-kit; these are +// ergonomic local aliases so the rest of the file reads unchanged. cli-kit is +// the single source both `code` and `atyrode` build on. +const ( + cAcc = clikit.CAcc + cBord = clikit.CBord + cHead = clikit.CHead + cSelBg = clikit.CSelBg + cGreen = clikit.CGreen +) + +const ( + gWarn = clikit.GWarn + gReset = clikit.GReset +) + +var ( + meterRamp = clikit.MeterRamp + + stDim = clikit.StDim + stHead = clikit.StHead + stWarn = clikit.StWarn + stBrk = clikit.StBrk + stStruck = clikit.StStruck + + // stKey renders an inline key cue (r, a, s, p) — visually secondary but + // readable against the background, per the section-chrome convention. + stKey = lipgloss.NewStyle().Foreground(lipgloss.Color(cHead)) + + // Title-local hotkey cues (d · defaults, p · hide, s · hide) are quieter + // than the footer help: terminals have no portable alpha, so these are + // dedicated pre-blended tokens — CHead/CDim mixed ~40% toward the app's + // dark backdrop — applied to the whole cue, key included. Pre-blending is + // used instead of ANSI faint because faint's dimming factor varies wildly + // across terminals (and would double-dim already-muted text). Footer + // recovery cues keep the brighter help styles for readability. + stCueKey = lipgloss.NewStyle().Foreground(lipgloss.Color("#646b76")) // CHead → backdrop + stCue = lipgloss.NewStyle().Foreground(lipgloss.Color("#4f5768")) // CDim → backdrop + + // layout + meter primitives now live in cli-kit + padLeft = clikit.PadLeft + pad = clikit.Pad + windowList = clikit.WindowList + + // Provider-qualified ids: bare catalog ids today (gpt-…, claude-…) plus + // slash-scoped ones (stealth/ox-alpha, local-qwen/qwen3.8-27b). The level + // suffix with its colon is what keeps prose out; the word boundary keeps + // "maxed" from reading as a model. + modelRe = regexp.MustCompile(`([a-z][a-z0-9._/-]*):(minimal|low|medium|high|xhigh|max)\b`) +) diff --git a/update.go b/update.go new file mode 100644 index 0000000..a47a5f8 --- /dev/null +++ b/update.go @@ -0,0 +1,340 @@ +package main + +import ( + "time" + + tea "github.com/charmbracelet/bubbletea" + + clikit "github.com/atyrode/cli-kit" + "github.com/charmbracelet/bubbles/spinner" +) + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.w, m.h = msg.Width, msg.Height + m.relayout() + case ompVersionMsg: + if msg.ok { + m.ompMajor, m.ompMinor = msg.major, msg.minor + } + return m, nil + case usageMsg: + scoped, scopedStale := reconcileUsage(m.avail, msg.avail) + refreshAt := time.Now().Add(refreshEvery) + first := !m.hadUsage && msg.avail.ok + m.avail, m.usageStale = scoped, scopedStale + m.hadUsage = m.hadUsage || msg.avail.ok + m.fetching = false + if msg.avail.ok { + saveUsageCache(m.usageCache, scoped) + } + m.nextRefresh = refreshAt + m.relayout() + if first { + // The first real data replaces the skeleton: run the one-time + // bounded bar fill. Refreshes never reach this branch again. + m.barAnim = 1 + return m, barAnimCmd(2) + } + case barAnimMsg: + // Bounded and self-terminating: apply the frame, arm the next tick, + // and stop at the final step (barAnim 0 = inactive, bars at value). + if m.barAnim == 0 { + return m, nil + } + if msg.step >= barAnimSteps { + m.barAnim = 0 + return m, nil + } + m.barAnim = msg.step + return m, barAnimCmd(msg.step + 1) + case refreshTickMsg: + // re-arm the 1s tick; auto-refresh once the interval elapses. + cmds := []tea.Cmd{tickCmd()} + if !m.fetching && !m.nextRefresh.IsZero() && !time.Now().Before(m.nextRefresh) { + cmds = append(cmds, m.startUsageFetch()) + } + return m, tea.Batch(cmds...) + case spinner.TickMsg: + if m.fetching || !m.avail.ok { + var cmd tea.Cmd + m.spin, cmd = m.spin.Update(msg) + return m, cmd + } + case admittedWheelMsg: + m.applyWheelStep(msg.Button) + return m, nil + case tea.MouseMsg: + // Wheel dispatch by pointer position: inside the visible Routing pane + // the viewport owns vertical scrolling — continuous, ungated, clamped + // by the viewport itself, with horizontal wheel deliberately inert. + // Everywhere else direct Update calls apply one generator step; live + // input is coalesced before dispatch by wheelInputFilter. + if msg.Action == tea.MouseActionPress { + if m.wheelInRouting(msg.X, msg.Y) { + switch msg.Button { + case tea.MouseButtonWheelUp: + m.vp.LineDown(1) // inverted: operator-confirmed trackpad direction + case tea.MouseButtonWheelDown: + m.vp.LineUp(1) + } + return m, nil + } + m.applyWheelStep(msg.Button) + } + case tea.KeyMsg: + if m.manager { + return m.updateManager(msg) + } + switch msg.String() { + case "q", "esc", "ctrl+c": + return m, tea.Quit + case "p": + switch { + case m.showUsage && m.sizeMode() == sizeNarrow: + m.showUsage = false + m.showResult = true + m.collapse = false + case m.collapse: + m.collapse = false // restore the hidden preview + case m.mode() == modeCollapsed: + m.showResult = !m.showResult // narrow+short: swap list ↔ result full-screen + default: + m.collapse = true // split/stacked: hide the preview, list full-screen + } + m.relayout() + case "s": + // Toggle the rendered section, not the pre-toggle size class. Usage + // changes the responsive minima, so a layout that fits while it is + // hidden may become narrow when it returns. In that case open the + // dedicated view atomically. It overlays the existing generator / + // Routing state so closing it restores that exact composition. + switch { + case m.showUsage: + m.showUsage = false + m.hideUsage = true + case !m.usageShown(): + m.hideUsage = false + m.showUsage = m.sizeMode() == sizeNarrow + default: + m.showUsage = false + m.hideUsage = true + } + m.relayout() + case "i": + m.fullUsageIDs = !m.fullUsageIDs + m.relayout() + case "?": + m.help.ShowAll = !m.help.ShowAll + m.relayout() // the taller/shorter footer changes the body height + case "d": + m.sel = defaultSel() + if len(m.runtimeTargets) > 0 { + m.sel["runtime"] = "hosted" + } + m.clampSel() // the defaults assume a full catalog; this one may not be + m.persistSelection() + m.syncPreview() + case "f": + m.depth = (m.depth + 1) % 2 + m.syncPreview() + case "r": + if m.broker.URL != "" && !m.fetching { + return m, m.startUsageFetch() + } + case "v": + m.manager = true + m.clampManagerCursor() + m.relayout() + case "up", "k": + m.moveUp() + case "down", "j": + m.moveDown() + case "left", "h": + m.cycleFacet(-1) + case "right", "l": + m.cycleFacet(1) + case "u": + // Untrusted sandbox: hand off to CODE_OMP_UNTRUSTED (ompu), which owns its + // own routing/policy — no generated --config is passed to it. Inert when + // no sandbox binary exists (the help hides the key too), so a stranger + // can't kill the TUI with a stray keypress. + if !m.hasSandbox { + return m, nil + } + m.launchUntrusted = true + return m, tea.Quit + case "pgup", "ctrl+u": + m.vp.HalfViewUp() // scroll the preview (mouse capture is off, see main) + case "pgdown", "ctrl+d": + m.vp.HalfViewDown() + case "m": + // Managed-defaults omp: omp-managed with no generated overlay. The + // explicit keybind keeps every Enter launch a generated profile. + m.launchManaged = true + return m, tea.Quit + case "enter": + if target, local := m.selectedRuntime(); local { + m.launchRuntime = target.Name + return m, tea.Quit + } + // Enter always launches the generated profile for the current facets — + // the untouched default combo is a generated profile like any other. + // Never for a combo the catalog doesn't carry, though: genConfigYAML + // would walk a nil block and emit an overlay whose modelRoles map is + // empty, handing omp a session with no routing at all. The preview + // already says "no profile for this combination", so the key does + // nothing rather than launching something broken. + if _, ok := m.generated[comboID(m.sel, m.hasRelief)]; !ok { + return m, nil + } + m.genConfig = m.genConfigYAML() + return m, tea.Quit + } + + case clikit.ActionsProposedMsg: + // Live preview: snapshot the current selection, then apply the proposal to + // the generator so the user sees the change while deciding. Report the FULL + // applied diff back to the box (the model's picks plus the derived toggles), + // so its "applied" list reflects everything that changed, not just the three + // facets the model named directly. + m.savedSel = map[string]string{} + for k, v := range m.sel { + m.savedSel[k] = v + } + m.applyActions(msg.Actions) + // applyActions' repair rules know lanes and quota, not catalog contents: + // a "critical" proposal switches fable on even where no fable combo was + // generated. Clamp and re-render before reporting what was applied. + m.clampSel() + m.syncPreview() + return m, func() tea.Msg { return clikit.AppliedActionsMsg{Actions: m.appliedDiff()} } + + case clikit.ActionsConfirmedMsg: + // Kept: the preview stays; remember the prompt for the launched session. + m.savedSel = nil + m.firstPrompt = msg.Prompt + m.persistSelection() + + case clikit.ActionsRevertedMsg: + // Rejected: restore the pre-preview selection. + if m.savedSel != nil { + m.sel = m.savedSel + m.savedSel = nil + m.syncPreview() + } + } + return m, nil +} + +// wheelInRouting reports whether a pointer position (terminal cells, 0-based) +// falls inside the visible Routing pane, whose viewport then owns vertical +// wheel scrolling: the wide split's right pane, medium's lower-right pane, or +// the narrow routing-only swap's full body. Hidden/collapsed routing claims +// nothing, so the generator keeps the wheel everywhere else. +func (m model) wheelInRouting(x, y int) bool { + if !m.routingShown() { + return false + } + ch := m.contentH() + switch m.mode() { + case modeCollapsed: // routing-only swap: the whole body is Routing + return y >= topGap && y < topGap+ch + case modeMedium: // the secondary row's right column, under the divider + genH, secH := m.mediumSplit(ch) + secTop := topGap + genH + 1 + return y >= secTop && y < secTop+secH && x >= m.w-m.routingColW() + default: // split: the right pane, from the list's right edge on + return y >= topGap && y < topGap+ch && x >= m.listW() + } +} + +// routingWheelCanMove reports whether a vertical routing scroll would change +// the viewport. No-op events at either clamp are filtered before redraw. +func (m model) routingWheelCanMove(b tea.MouseButton) bool { + switch b { + case tea.MouseButtonWheelUp: + return m.vp.YOffset < m.vp.TotalLineCount()-m.vp.Height + case tea.MouseButtonWheelDown: + return m.vp.YOffset > 0 + default: + return false + } +} + +// applyWheelStep translates one admitted wheel event into the matching facet +// action: vertical scroll moves the selection, horizontal scroll changes the +// value. The raw mapping is INVERTED on both axes — operator-confirmed trackpad +// direction: WheelUp moves the selection down, WheelDown up; WheelLeft cycles +// to the next (right) option, WheelRight to the previous. Arrow keys keep their +// literal semantics. +func (m *model) applyWheelStep(b tea.MouseButton) { + switch b { + case tea.MouseButtonWheelUp: + m.moveDown() + case tea.MouseButtonWheelDown: + m.moveUp() + case tea.MouseButtonWheelLeft: + m.cycleFacet(1) + case tea.MouseButtonWheelRight: + m.cycleFacet(-1) + } +} + +func (m *model) moveUp() { + if m.fcur > 0 { + m.fcur-- + } +} +func (m *model) moveDown() { + if m.fcur < len(m.visibleFacets())-1 { + m.fcur++ + } +} +func (m *model) cycleFacet(dir int) { + vf := m.visibleFacets() + if m.fcur >= len(vf) { + m.fcur = len(vf) - 1 + } + f := vf[m.fcur] + cur := m.sel[f.key] + idx := 0 + for i, v := range f.values { + if v == cur { + idx = i + } + } + next := idx + dir + if next < 0 { + next = 0 + } else if next >= len(f.values) { + next = len(f.values) - 1 + } + if next == idx { + return + } + m.sel[f.key] = f.values[next] + // lead/blend are lane's rendered halves: recompose the canonical value + // before anything re-derives them (visibleFacets syncs from lane). + if f.key == "lead" || f.key == "blend" { + m.sel["lane"] = laneJoin(m.sel["lead"], m.sel["blend"]) + } + // main is fable's sub-setting: whenever fable leaves "on" it must clear too, + // so a later fable re-enable never silently resurrects the (expensive) + // fable-as-main escalation — it is re-chosen deliberately every time. + if m.sel["fable"] != "on" { + m.sel["main"] = "off" + } + // changing the lane can hide/show facets; keep the cursor in range. + if nv := len(m.visibleFacets()); m.fcur >= nv { + m.fcur = nv - 1 + } + m.syncPreview() + m.persistSelection() +} + +// prevPadL is the split preview pane's left padding. Width() counts it, so the +// viewport's usable text width is the box width minus this — the viewport must +// be sized to that inner area (see previewDims), else lines wrap 1:1 with the +// box and their tail overflows to the pane's left edge. diff --git a/usage.go b/usage.go new file mode 100644 index 0000000..dd92da8 --- /dev/null +++ b/usage.go @@ -0,0 +1,1521 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "sort" + "strconv" + "strings" + "sync" + "time" + "unicode" + + "github.com/charmbracelet/lipgloss" +) + +// ── usage + availability ───────────────────────────────────────────────────── +type usageWin struct { + label string + pct int + tier string + secs int64 // seconds until reset (relative) + dur int64 // window length in seconds + prov string + stale bool // retained from the last successful fetch after a refresh omitted this window + missing bool // never observed: rendered as a deterministic placeholder row + observed int64 // Unix timestamp of the last real value; retained across cache fallback +} + +// resetCredits tracks OpenAI reset credits: how many are currently available +// and the seconds until each available credit expires (relative, unsorted). +type resetCredits struct { + avail int + exp []int64 +} + +type availability struct { + bucket map[string]string // bucket -> "ok" | "maxed" | "unauthed" + reset map[string]int64 + wins []usageWin + credits resetCredits + accountCredits map[accountKey]resetCredits + ok bool + accounts map[string][]account + accountUsage map[accountKey][]usageWin + accountsOK bool + selectionApplied bool + accountsStale bool + // deepseek is the DeepSeek prepaid balance: nil when the snapshot carries + // no DeepSeek credential (the group is hidden entirely — an absent API key + // is the normal state, unlike a metered subscription). + deepseek *deepseekBalance +} + +func fetchBrokerUsage(broker brokerConfig) ([]byte, error) { + if broker.URL == "" || broker.Token == "" { + return nil, errors.New("central auth broker is not configured") + } + req, err := http.NewRequest(http.MethodGet, strings.TrimRight(broker.URL, "/")+"/v1/usage", nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+broker.Token) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + _, _ = io.Copy(io.Discard, resp.Body) + return nil, fmt.Errorf("usage endpoint returned %s", resp.Status) + } + return io.ReadAll(resp.Body) +} + +type usageCacheWin struct { + Label string `json:"label"` + Pct int `json:"pct"` + Tier string `json:"tier,omitempty"` + ResetsAt int64 `json:"resetsAt"` + Dur int64 `json:"dur"` + Provider string `json:"provider"` + Observed int64 `json:"observed"` +} + +type usageCacheAccount struct { + Provider string `json:"provider"` + IdentityKey string `json:"identityKey"` + Wins []usageCacheWin `json:"wins"` +} + +type usageCacheFile struct { + SavedAt int64 `json:"savedAt"` + Accounts map[string][]account `json:"accounts"` + Deepseek *usageCacheBalance `json:"deepseekBalance,omitempty"` + Usage []usageCacheAccount `json:"usage"` +} + +// usageCacheBalance caches the DeepSeek balance VALUE only — never the key. +type usageCacheBalance struct { + Currency string `json:"currency"` + Total string `json:"totalBalance"` + FetchedAt int64 `json:"fetchedAt"` +} + +func emptyAvailability() availability { + return availability{ + bucket: map[string]string{}, reset: map[string]int64{}, + accounts: map[string][]account{}, accountUsage: map[accountKey][]usageWin{}, + accountCredits: map[accountKey]resetCredits{}, + } +} + +// parseAvailability associates broker usage with stable identities from the +// same snapshot. observedAt is the cache observation time; reset countdowns +// remain relative to now because the broker payload stores absolute deadlines. +func parseAvailability(accounts map[string][]account, accountsOK bool, out []byte, observedAt int64) availability { + a := emptyAvailability() + a.accounts, a.accountsOK = accounts, accountsOK + type limit struct { + Label string `json:"label"` + Scope struct { + Tier string `json:"tier"` + } `json:"scope"` + Amount struct { + UsedFraction float64 `json:"usedFraction"` + } `json:"amount"` + Window struct { + ResetsAt int64 `json:"resetsAt"` + DurationMs int64 `json:"durationMs"` + } `json:"window"` + } + var doc struct { + Reports []struct { + Provider string `json:"provider"` + Email string `json:"email"` + AccountID string `json:"accountId"` + Metadata struct { + Email string `json:"email"` + AccountID string `json:"accountId"` + } `json:"metadata"` + Limits []limit `json:"limits"` + ResetCredits struct { + AvailableCount int `json:"availableCount"` + Credits []struct { + ExpiresAt string `json:"expiresAt"` + Status string `json:"status"` + } `json:"credits"` + } `json:"resetCredits"` + } `json:"reports"` + } + if len(out) == 0 || json.Unmarshal(out, &doc) != nil { + return a + } + a.ok = true + provSeen := map[string]bool{} + now := time.Now().Unix() + if observedAt <= 0 { + observedAt = now + } + for _, r := range doc.Reports { + provSeen[r.Provider] = true + reportWins := make([]usageWin, 0, len(r.Limits)) + for _, l := range r.Limits { + pct := int(l.Amount.UsedFraction*100 + 0.5) + win := usageWin{label: l.Label, pct: pct, tier: l.Scope.Tier, + secs: l.Window.ResetsAt/1000 - now, dur: l.Window.DurationMs / 1000, + prov: r.Provider, observed: observedAt} + reportWins = append(reportWins, win) + a.wins = append(a.wins, win) + bkt := bucketForProviderTier(r.Provider, l.Scope.Tier) + if bkt == "" { + continue + } + if pct >= 100 { + a.bucket[bkt] = "maxed" + a.reset[bkt] = l.Window.ResetsAt/1000 - now + } else if a.bucket[bkt] != "maxed" { + a.bucket[bkt] = "ok" + } + } + email, accountID := r.Metadata.Email, r.Metadata.AccountID + if email == "" { + email = r.Email + } + if accountID == "" { + accountID = r.AccountID + } + var matchedKey accountKey + matched := false + for _, acct := range a.accounts[r.Provider] { + if (email != "" && acct.Email != "" && strings.EqualFold(email, acct.Email)) || + (accountID != "" && accountID == acct.IdentityKey) { + matchedKey = accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey} + a.accountUsage[matchedKey] = append(a.accountUsage[matchedKey], reportWins...) + matched = true + break + } + } + if r.Provider == openAIProvider { + credits := resetCredits{avail: r.ResetCredits.AvailableCount} + for _, c := range r.ResetCredits.Credits { + if c.Status != "available" { + continue + } + if t, err := time.Parse(time.RFC3339, c.ExpiresAt); err == nil { + credits.exp = append(credits.exp, t.Unix()-now) + } + } + a.credits.avail += credits.avail + a.credits.exp = append(a.credits.exp, credits.exp...) + if matched { + attributed := a.accountCredits[matchedKey] + attributed.avail += credits.avail + attributed.exp = append(attributed.exp, credits.exp...) + a.accountCredits[matchedKey] = attributed + } + } + } + for _, prov := range providerRegistry { + if !prov.Metered { + continue + } + for _, b := range prov.buckets() { + if !provSeen[prov.ID] { + a.bucket[b] = "unauthed" + } else if _, ok := a.bucket[b]; !ok { + a.bucket[b] = "ok" + } + } + } + return a +} + +// loadAvailability reads one central snapshot and one aggregate usage report, +// plus — when the snapshot carries a DeepSeek api_key — the upstream prepaid +// balance, fetched concurrently so neither request delays the other. +func loadAvailability(broker brokerConfig) availability { + accounts, err := loadAccounts(broker) + accountsOK := err == nil + if !accountsOK { + accounts = map[string][]account{} + } + var ds *deepseekBalance + var wg sync.WaitGroup + if key := deepseekAPIKey(accounts); key != "" { + wg.Add(1) + go func() { + defer wg.Done() + bal, err := fetchDeepSeekBalance(key) + if err != nil { + // Degrade to an explicit "unavailable" row; other providers + // are unaffected. + bal = deepseekBalance{fetchedAt: time.Now().Unix()} + } + ds = &bal + }() + } + out, err := fetchBrokerUsage(broker) + if err != nil { + out = nil + } + wg.Wait() + a := parseAvailability(accounts, accountsOK, out, 0) + a.deepseek = ds + return a +} + +func loadUsageCache(path string) availability { + a := emptyAvailability() + if path == "" { + return a + } + body, err := os.ReadFile(path) + if err != nil { + return a + } + var cached usageCacheFile + if json.Unmarshal(body, &cached) != nil || cached.SavedAt <= 0 || len(cached.Usage) == 0 { + return a + } + a.accounts, a.accountsOK, a.ok = cached.Accounts, true, true + provSeen := map[string]bool{} + for _, entry := range cached.Usage { + key := accountKey{Provider: entry.Provider, IdentityKey: entry.IdentityKey} + for _, cachedWin := range entry.Wins { + win := usageWin{ + label: cachedWin.Label, pct: cachedWin.Pct, tier: cachedWin.Tier, + secs: cachedWin.ResetsAt - time.Now().Unix(), dur: cachedWin.Dur, prov: cachedWin.Provider, + observed: cachedWin.Observed, stale: true, + } + a.accountUsage[key] = append(a.accountUsage[key], win) + a.wins = append(a.wins, win) + provSeen[win.prov] = true + bucket := bucketForProviderTier(win.prov, win.tier) + if bucket == "" { + continue + } + if win.pct >= 100 { + a.bucket[bucket], a.reset[bucket] = "maxed", win.secs + } else if a.bucket[bucket] != "maxed" { + a.bucket[bucket] = "ok" + } + } + } + if cached.Deepseek != nil { + a.deepseek = &deepseekBalance{ + ok: true, currency: cached.Deepseek.Currency, total: cached.Deepseek.Total, + fetchedAt: cached.Deepseek.FetchedAt, stale: true, + } + } + for _, prov := range providerRegistry { + if !prov.Metered { + continue + } + for _, bucket := range prov.buckets() { + if !provSeen[prov.ID] { + a.bucket[bucket] = "unauthed" + } else if _, ok := a.bucket[bucket]; !ok { + a.bucket[bucket] = "ok" + } + } + } + return a +} + +func saveUsageCache(path string, a availability) { + if path == "" || !a.ok { + return + } + now := time.Now().Unix() + cached := usageCacheFile{SavedAt: now, Accounts: a.accounts} + if a.deepseek != nil && a.deepseek.ok { + cached.Deepseek = &usageCacheBalance{ + Currency: a.deepseek.currency, Total: a.deepseek.total, FetchedAt: a.deepseek.fetchedAt, + } + } + for key, wins := range a.accountUsage { + entry := usageCacheAccount{Provider: key.Provider, IdentityKey: key.IdentityKey} + for _, win := range wins { + if win.missing { + continue + } + observed := win.observed + if observed <= 0 { + observed = now + } + entry.Wins = append(entry.Wins, usageCacheWin{ + Label: win.label, Pct: win.pct, Tier: win.tier, + ResetsAt: observed + win.secs, Dur: win.dur, + Provider: win.prov, Observed: observed, + }) + } + if len(entry.Wins) > 0 { + cached.Usage = append(cached.Usage, entry) + } + } + if len(cached.Usage) == 0 { + return + } + sort.Slice(cached.Usage, func(i, j int) bool { + if cached.Usage[i].Provider != cached.Usage[j].Provider { + return cached.Usage[i].Provider < cached.Usage[j].Provider + } + return cached.Usage[i].IdentityKey < cached.Usage[j].IdentityKey + }) + body, err := json.Marshal(cached) + if err != nil { + return + } + body = append(body, '\n') + _ = atomicPrivateWrite(path, body) +} + +// bucketForProviderTier maps a usage report's (provider, tier) scope onto the +// quota bucket it constrains: the provider's main window, or a special tier's +// dedicated window. Unmetered and unknown providers own no buckets. +func bucketForProviderTier(prov, tier string) string { + p := providerByID(prov) + if p == nil || !p.Metered { + return "" + } + if tier == "" || tier == "-" { + return p.mainBucket() + } + for _, s := range p.Special { + if s.Bucket == tier { + return p.BucketBase + "-" + s.Bucket + } + } + return "" +} + +func (a availability) down(bucket string) bool { + return a.bucket[bucket] == "maxed" || a.bucket[bucket] == "unauthed" +} + +// reconcileUsage folds a freshly fetched availability over the one currently +// shown, so a flaky upstream never wipes known-good data. It returns the +// availability to display plus whether the whole panel is stale: +// +// - a total fetch failure after any prior success keeps the previous +// availability wholesale and reports it stale — the control row shows a +// refresh-failed warning instead of dropping to the unauthenticated error; +// - a successful payload that omits an account's usage retains that +// account's last observed rows, visibly marked stale with their age; +// - a successful Anthropic payload that only omits the flaky Fable window +// retains the last Fable row, along with its bucket/reset routing state; +// - a successful Anthropic payload with no Fable window ever observed +// appends a deterministic unavailable placeholder, so the datum appearing +// on a later refresh never pops the panel geometry. +// +// Fresh values always win; nothing is fabricated — retained rows are visibly +// marked stale and placeholders carry no numbers. +func reconcileUsage(prev, next availability) (availability, bool) { + if !next.ok { + if prev.ok { + return prev, true + } + return next, false + } + if !next.accountsOK && prev.accountsOK { + next.accounts, next.accountsOK, next.accountsStale = prev.accounts, true, true + } + next.accountUsage = reconcileAccountUsage(prev.accountUsage, next.accountUsage, next.accounts) + if next.deepseek != nil && !next.deepseek.ok && prev.deepseek != nil && prev.deepseek.ok { + // A failed balance refresh keeps the last known value, visibly stale — + // same retention contract as the metered windows. + retained := *prev.deepseek + retained.stale = true + next.deepseek = &retained + } + hasClaude, hasFable := false, false + for _, w := range next.wins { + if w.prov != anthropicProvider { + continue + } + hasClaude = true + if w.tier == "fable" { + hasFable = true + } + } + if !hasClaude || hasFable { + return next, false + } + for _, w := range prev.wins { + if w.prov == anthropicProvider && w.tier == "fable" && !w.missing { + w.stale = true + next.wins = append(next.wins, w) + // Carry the bucket/reset state observed with the retained window: + // loadAvailability defaults an unseen bucket to "ok", which would + // route onto a fable the last real datum said was maxed. + if st, ok := prev.bucket["claude-fable"]; ok { + next.bucket["claude-fable"] = st + } + if r, ok := prev.reset["claude-fable"]; ok { + next.reset["claude-fable"] = r + } + return next, false + } + } + next.wins = append(next.wins, fablePlaceholder) + return next, false +} + +func reconcileAccountUsage(prev, next map[accountKey][]usageWin, accounts map[string][]account) map[accountKey][]usageWin { + if next == nil { + next = map[accountKey][]usageWin{} + } + active := map[accountKey]bool{} + for _, providerAccounts := range accounts { + for _, acct := range providerAccounts { + active[accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey}] = true + } + } + if len(active) == 0 { + for key := range next { + active[key] = true + } + } + for key := range active { + wins := next[key] + hasFresh := false + for _, w := range wins { + if !w.missing { + hasFresh = true + break + } + } + if !hasFresh { + retained := make([]usageWin, 0, len(prev[key])) + for _, w := range prev[key] { + if w.missing { + continue + } + w.stale = true + retained = append(retained, w) + } + if len(retained) > 0 { + next[key] = retained + } + continue + } + if key.Provider != anthropicProvider { + continue + } + hasClaude, hasFable := false, false + for _, w := range wins { + if w.prov != anthropicProvider { + continue + } + if !w.missing { + hasClaude = true + } + if w.tier == "fable" { + hasFable = true + } + } + if !hasClaude || hasFable { + continue + } + retained := false + for _, w := range prev[key] { + if w.prov == anthropicProvider && w.tier == "fable" && !w.missing { + w.stale = true + next[key] = append(next[key], w) + retained = true + break + } + } + if !retained { + next[key] = append(next[key], fablePlaceholder) + } + } + return next +} + +// fablePlaceholder is the never-observed fable window's deterministic +// stand-in: the real payload label (so shortWin renders the same "7d fable" +// tag) and window length, with no usage numbers to fabricate. +var fablePlaceholder = usageWin{label: "Claude 7 Day (Fable)", tier: "fable", dur: 7 * 24 * 3600, prov: anthropicProvider, missing: true} + +type usageGroupKey struct { + prov string + tier string + dur int64 + label string +} + +type usageGroup struct { + win usageWin + count int64 + pctSum int64 + secsSum int64 + observed int64 +} + +func knownUsageWindow(w usageWin) bool { + label := shortWin(w.label) + bucket := bucketForProviderTier(w.prov, w.tier) + p := providerByID(w.prov) + if bucket == "" || p == nil { + return false + } + if bucket == p.mainBucket() { + return label == "5h" || label == "7d" + } + suffix := strings.TrimPrefix(bucket, p.BucketBase+"-") + return label == "5h "+suffix || label == "7d "+suffix +} + +// selectedAvailability derives account-sensitive usage and routing availability +// solely from enabled broker identities. Unmatched reports never enter this seam. +func selectedAvailability(a availability, disabled map[accountKey]bool) availability { + selected := a + selected.selectionApplied = true + selected.accounts = map[string][]account{} + selected.accountUsage = map[accountKey][]usageWin{} + selected.accountCredits = map[accountKey]resetCredits{} + selected.bucket = map[string]string{} + selected.reset = map[string]int64{} + selected.wins = nil + selected.credits = resetCredits{} + + enabledProviders := map[string]bool{} + groups := map[usageGroupKey]*usageGroup{} + missing := map[usageGroupKey]usageWin{} + var groupOrder []usageGroupKey + for prov, accounts := range a.accounts { + for _, acct := range accounts { + key := accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey} + if disabled[key] { + continue + } + selected.accounts[prov] = append(selected.accounts[prov], acct) + enabledProviders[acct.Provider] = true + wins := a.accountUsage[key] + if credits, ok := a.accountCredits[key]; ok { + selected.accountCredits[key] = credits + selected.credits.avail += credits.avail + selected.credits.exp = append(selected.credits.exp, credits.exp...) + } + for _, win := range wins { + if win.prov != acct.Provider || !knownUsageWindow(win) { + continue + } + selected.accountUsage[key] = append(selected.accountUsage[key], win) + groupKey := usageGroupKey{prov: win.prov, tier: win.tier, dur: win.dur, label: shortWin(win.label)} + if win.missing { + if _, ok := missing[groupKey]; !ok { + placeholder := win + placeholder.label = groupKey.label + missing[groupKey] = placeholder + groupOrder = append(groupOrder, groupKey) + } + continue + } + group := groups[groupKey] + if group == nil { + aggregate := win + aggregate.label = groupKey.label + group = &usageGroup{win: aggregate} + groups[groupKey] = group + groupOrder = append(groupOrder, groupKey) + } + pct, secs := int64(win.pct), win.secs + if pct < 0 { + pct = 0 + } + if secs < 0 { + secs = 0 + } + group.count++ + group.pctSum += pct + group.secsSum += secs + group.win.stale = group.win.stale || win.stale + if win.observed > 0 && (group.observed == 0 || win.observed < group.observed) { + group.observed = win.observed + } + } + } + } + seen := map[usageGroupKey]bool{} + for _, key := range groupOrder { + if seen[key] { + continue + } + seen[key] = true + if group := groups[key]; group != nil { + group.win.pct = int((group.pctSum + group.count/2) / group.count) + group.win.secs = (group.secsSum + group.count/2) / group.count + group.win.observed = group.observed + selected.wins = append(selected.wins, group.win) + } else { + selected.wins = append(selected.wins, missing[key]) + } + } + for _, prov := range providerRegistry { + if !prov.Metered { + continue + } + for _, bucket := range prov.buckets() { + if enabledProviders[prov.ID] { + selected.bucket[bucket] = "ok" + } else { + selected.bucket[bucket] = "unauthed" + } + } + } + for _, win := range selected.wins { + bucket := bucketForProviderTier(win.prov, win.tier) + if bucket == "" || win.missing || win.pct < 100 { + continue + } + selected.bucket[bucket] = "maxed" + // Multiple quota windows can constrain one route. The route becomes + // usable only after the last maxed aggregate resets, so retain the + // longest selected reset rather than whichever account/map came last. + if win.secs > selected.reset[bucket] { + selected.reset[bucket] = win.secs + } + } + return selected +} + +const usageBarNaturalW = 10 + +func barStr(p, width int) string { + if width < 0 { + width = 0 + } + var r, g float64 + if p <= 50 { + r, g = 90+float64(p)*3, 200 + } else { + r, g = 235, 200-float64(p-50)*3 + } + if r > 235 { + r = 235 + } + if g < 60 { + g = 60 + } + fill := (p*width + 50) / 100 + if fill > width { + fill = width + } + if fill < 0 { + fill = 0 + } + filled := lipgloss.NewStyle().Foreground(lipgloss.Color(fmt.Sprintf("#%02x%02x46", clampByte(r), clampByte(g)))).Render(strings.Repeat("█", fill)) + return filled + stDim.Render(strings.Repeat("░", width-fill)) +} + +func fmtReset(s int64) string { + if s < 0 { + s = 0 + } + switch { + case s >= 86400: + return fmt.Sprintf("%dd%dh", s/86400, (s%86400)/3600) + case s >= 3600: + return fmt.Sprintf("%dh%dm", s/3600, (s%3600)/60) + } + return fmt.Sprintf("%dm", s/60) +} + +func shortWin(l string) string { + switch l { + case "5 hours", "Claude 5 Hour", "Codex 5 Hour", "OpenAI 5 Hour": + return "5h" + case "7 days", "Claude 7 Day", "Codex 7 Day", "OpenAI 7 Day": + return "7d" + case "5 hours (Spark)", "Codex 5 Hour (Spark)", "OpenAI 5 Hour (Spark)": + return "5h spark" + case "7 days (Spark)", "Codex 7 Day (Spark)", "OpenAI 7 Day (Spark)": + return "7d spark" + case "Claude 7 Day (Fable)": + return "7d fable" + } + return l +} + +// usageCtrlLine is the Usage chrome's bottom action row: central refresh state, +// account-manager access, and any account persistence error. +func (m *model) usageCtrlLine() string { + var parts []string + if m.broker.URL != "" { + switch { + case !m.avail.ok && (m.fetching || m.nextRefresh.IsZero()): + parts = append(parts, stDim.Render(m.spin.View()+" fetching usage…")) + case m.fetching: + parts = append(parts, stWarn.Render(gReset+" refreshing…")) + case m.usageStale: + // A failed refresh kept the previous data on screen: the warning + // takes the countdown's slot (same row, similar width) so the + // measured panel geometry — and with it the medium/collapsed + // breakpoint — barely moves on a flaky refresh. + parts = append(parts, stWarn.Render("refresh failed · stale")+ + stDim.Render(" · ")+stKey.Render("r")+stDim.Render(" retry")) + default: + rem := time.Until(m.nextRefresh) + if rem < 0 { + rem = 0 + } + s := int(rem.Seconds()) + parts = append(parts, + stDim.Render(fmt.Sprintf("next refresh %d:%02d · ", s/60, s%60))+ + stKey.Render("r")+stDim.Render(" now")) + } + } + identityAction := "full ids" + if m.fullUsageIDs { + identityAction = "short ids" + } + parts = append(parts, stKey.Render("i")+stDim.Render(" "+identityAction)) + parts = append(parts, stKey.Render("v")+stDim.Render(" accounts")) + if len(parts) == 0 { + return "" + } + line := " " + strings.Join(parts, stDim.Render(" · ")) + if m.accountErr != "" { + line += "\n" + stBrk.Render(" account update failed: "+m.accountErr) + } + return line +} + +// compactDisplayIdentity produces a deliberately lossy display label. Email +// matching continues to use the untouched broker identity; this helper is only +// for the compact Usage heading. +func compactDisplayIdentity(identity string) string { + normalized := strings.ToLower(strings.TrimSpace(identity)) + at := strings.IndexByte(normalized, '@') + if at > 0 && at == strings.LastIndexByte(normalized, '@') { + local, domain := normalized[:at], normalized[at+1:] + dot := strings.LastIndexByte(domain, '.') + valid := dot > 0 && dot < len(domain)-1 && + !strings.HasPrefix(local, ".") && !strings.HasSuffix(local, ".") && + !strings.Contains(local, "..") && !strings.Contains(domain, "..") && + strings.IndexFunc(normalized, func(r rune) bool { + return unicode.IsSpace(r) || unicode.IsControl(r) + }) < 0 + if valid { + localRunes := []rune(local) + if len(localRunes) > 2 { + localRunes = localRunes[:2] + } + return string(localRunes) + "*" + } + } + if normalized == "" { + return "id unavailable" + } + runes := []rune(normalized) + if len(runes) > 2 { + runes = runes[:2] + } + return string(runes) + "*" +} + +func usageDisplayIdentity(identity string, full bool) string { + if full { + if identity = strings.TrimSpace(identity); identity != "" { + return identity + } + return "id unavailable" + } + return compactDisplayIdentity(identity) +} + +type compactProviderIdentity struct { + label string + reporting bool +} + +// providerIdentities preserves broker snapshot order and collapses repeated +// copies of the same stable account. Compact ambiguity is intentional; pressing +// i reveals full addresses when disambiguation matters. +func providerIdentities(a availability, prov string, full bool) []compactProviderIdentity { + accounts := a.accounts[prov] + identities := make([]compactProviderIdentity, 0, len(accounts)) + seenAccounts := map[string]bool{} + for _, acct := range accounts { + stableID := acct.IdentityKey + if stableID == "" { + stableID = acct.Email + } + if stableID != "" { + stableID = acct.Provider + "\x00" + stableID + if seenAccounts[stableID] { + continue + } + seenAccounts[stableID] = true + } + + identity := acct.Email + if identity == "" { + identity = acct.IdentityKey + } + label := usageDisplayIdentity(identity, full) + + reporting := false + for _, win := range a.accountUsage[accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey}] { + if !win.missing { + reporting = true + break + } + } + identities = append(identities, compactProviderIdentity{ + label: label, reporting: reporting, + }) + } + return identities +} + +// providerHeading keeps the provider's established color and puts compact, +// enabled snapshot identities in a dim parenthetical suffix. +func providerHeading(prov string, identities []compactProviderIdentity) string { + col, name := "#8a93a6", prov + if p := providerByID(prov); p != nil { + col, name = p.Color, p.Label + } + heading := lipgloss.NewStyle().Foreground(lipgloss.Color(col)).Bold(true).Render(name) + if len(identities) == 0 { + return heading + } + labels := make([]string, 0, len(identities)) + for _, identity := range identities { + labels = append(labels, identity.label) + } + return heading + " " + stDim.Render("("+strings.Join(labels, " + ")+")") +} + +// providerIdentityBlockFor keeps missing usage explicit without spending a +// separate row on accounts already represented by aggregate usage bars. +func providerIdentityBlockFor(a availability, prov string, checking, full bool) []string { + identities := providerIdentities(a, prov, full) + if checking && len(identities) == 0 { + identities = []compactProviderIdentity{{label: "checking account…"}} + } + rows := []string{padLeft(providerHeading(prov, identities), gut)} + if checking { + return rows + } + if !a.accountsOK { + return append(rows, stWarn.Render(" account status unavailable")) + } + if len(identities) == 0 { + if a.selectionApplied { + return append(rows, stDim.Render(" no enabled accounts")) + } + return append(rows, stBrk.Render(" not authenticated")) + } + unavailable := 0 + for _, identity := range identities { + if !identity.reporting { + unavailable++ + } + } + if unavailable > 0 { + status := " usage unavailable" + if len(identities) > 1 { + noun := "account" + if unavailable > 1 { + noun = "accounts" + } + status += fmt.Sprintf(" for %d %s", unavailable, noun) + } + rows = append(rows, stWarn.Render(status)) + } + if a.accountsStale { + rows = append(rows, stWarn.Render(" identity cached")) + } + return rows +} + +func providerIdentityBlock(a availability, prov string, checking bool) []string { + return providerIdentityBlockFor(a, prov, checking, false) +} + +// identityLines keeps provider and broker-reported account state visible even +// when no usage rows exist. +func identityLinesFor(a availability) string { + var lines []string + for i, prov := range meteredProviderIDs() { + if i > 0 { + lines = append(lines, "") + } + lines = append(lines, providerIdentityBlock(a, prov, false)...) + } + return strings.Join(lines, "\n") +} + +func (m *model) selectedLaunchAvailability() availability { + return selectedAvailability(m.avail, m.accountSelections.CurrentDisabled()) +} + +func (m *model) selectedUsageAvailability() availability { + disabled := m.accountSelections.CurrentDisabled() + if m.manager { + disabled = m.managerDisplayedDisabled() + } + return selectedAvailability(m.avail, disabled) +} + +func (m *model) identityLines() string { + return identityLinesFor(m.selectedUsageAvailability()) +} + +// usagePanel is the composition-agnostic Usage band sized for the current +// terminal width — the wide layout's full-width footer form. +func (m *model) usagePanel() string { return m.usagePanelFor(m.w) } + +// usagePanelFor renders central account usage with local visibility and account +// manager cues. There is no selectable vault or profile identity. +func (m *model) usagePanelFor(w int) string { + return m.usagePanelLayout(w, false) +} + +func (m *model) usagePanelStackedFor(w int) string { + return m.usagePanelLayout(w, true) +} + +func (m *model) usagePanelLayout(w int, stacked bool) string { + title := m.pill("usage") + title += " " + stCueKey.Render("s") + stCue.Render(" · hide") + innerWidth := max(0, w-gut) + out := padLeft(title, gut) + "\n" + + "\n" + m.usageBodyLayout(innerWidth, stacked) + if ctrl := m.usageCtrlLine(); ctrl != "" && !m.manager { + out += "\n\n" + ctrl // blank row: air between provider content and the control row + } + return out +} + +// usageRenderGroup keeps provider chrome and usage rows separate until layout +// has assigned the provider its real display width. That is the composition seam +// which lets both stacked and side-by-side layouts grow bars without changing +// headings, notes, or the canonical row grammar. +type usageRenderGroup struct { + prefix []string + rows []usageRowSpec + suffix []string +} + +func (g usageRenderGroup) linesWithUsageLayout(barWidth, noteWidth, prefixHeight int) []string { + lines := make([]string, 0, max(len(g.prefix), prefixHeight)+len(g.rows)+len(g.suffix)) + lines = append(lines, g.prefix...) + for len(lines) < prefixHeight { + lines = append(lines, "") + } + for _, row := range g.rows { + lines = append(lines, row.render(barWidth, noteWidth)) + } + lines = append(lines, g.suffix...) + return lines +} + +func usageRenderWidth(lines []string) int { + width := 0 + for _, line := range lines { + if lineWidth := lipgloss.Width(line); lineWidth > width { + width = lineWidth + } + } + return width +} + +// usageBodyFor renders the provider/account content between the pinned title +// and the bottom control row: the identity-headed usage groups, or the +// loading/unavailable identity block. +func (m *model) usageBodyFor(w int) string { + return m.usageBodyLayout(w, false) +} + +func (m *model) usageBodyLayout(w int, stacked bool) string { + a := m.selectedUsageAvailability() + if !a.ok { + if m.usageLoading() { + return m.skeletonBodyLayout(w, stacked) + } + out := identityLinesFor(a) + if m.broker.URL != "" && !m.fetching && !m.nextRefresh.IsZero() { + out += "\n" + stWarn.Render(" usage unavailable · press v to manage accounts") + } + return out + } + if len(a.wins) == 0 { + return identityLinesFor(a) + "\n" + + stWarn.Render(" no enabled provider usage · press v to manage accounts") + } + wins := append([]usageWin(nil), a.wins...) + provOrder := func(p string) int { + for i := range providerRegistry { + if providerRegistry[i].ID == p { + return i + } + } + return len(providerRegistry) + } + tierOrder := func(t string) int { + if t == "" || t == "-" { + return 0 + } + return 1 + } + sort.SliceStable(wins, func(i, j int) bool { + if o := provOrder(wins[i].prov) - provOrder(wins[j].prov); o != 0 { + return o < 0 + } + if o := tierOrder(wins[i].tier) - tierOrder(wins[j].tier); o != 0 { + return o < 0 + } + return wins[i].dur < wins[j].dur + }) + blocks := map[string]usageRenderGroup{} + var order []string + for _, prov := range meteredProviderIDs() { + if len(a.accounts[prov]) > 0 { + order = append(order, prov) + blocks[prov] = usageRenderGroup{prefix: providerIdentityBlockFor(a, prov, false, m.fullUsageIDs)} + } + } + for _, win := range wins { + block, ok := blocks[win.prov] + if !ok { + order = append(order, win.prov) + block.prefix = providerIdentityBlockFor(a, win.prov, false, m.fullUsageIDs) + } + block.rows = append(block.rows, m.usageRowSpec(win, " ")) + blocks[win.prov] = block + } + if block, ok := blocks[openAIProvider]; ok { + for _, acct := range a.accounts[openAIProvider] { + key := accountKey{Provider: acct.Provider, IdentityKey: acct.IdentityKey} + identity := usageDisplayIdentity(managerAccountLabel(acct), m.fullUsageIDs) + if cl := creditLineForAccount(identity, a.accountCredits[key]); cl != "" { + block.suffix = append(block.suffix, cl) + } + } + blocks[openAIProvider] = block + } + if a.deepseek != nil { + order = append(order, deepseekProvider) + blocks[deepseekProvider] = usageRenderGroup{ + prefix: []string{padLeft(providerHeading(deepseekProvider, nil), gut)}, + suffix: []string{m.deepseekBalanceRow(*a.deepseek)}, + } + } + return layoutGroups(w, order, blocks, stacked) +} + +// layoutGroups assigns provider columns before rendering any usage row. +// Side-by-side groups share the whole panel width; stacked groups each receive +// the whole section width. A zero width is the non-recursive measurement path +// and therefore retains natural ten-cell bars. +func layoutGroups(w int, order []string, blocks map[string]usageRenderGroup, stacked bool) string { + allRows := make([]usageRowSpec, 0) + for _, prov := range order { + allRows = append(allRows, blocks[prov].rows...) + } + naturalBarWidth, noteWidth := usageRowsLayout(0, allRows) + naturalColW := lipgloss.Width(skeletonRow("7d fable")) + 2 + for _, prov := range order { + blockW := usageRenderWidth(blocks[prov].linesWithUsageLayout(naturalBarWidth, noteWidth, 0)) + 2 + if blockW > naturalColW { + naturalColW = blockW + } + } + sideBySide := !stacked && w > 0 && len(order) > 1 && w >= naturalColW*len(order) + layoutWidth := w + if sideBySide { + for i := range order { + colW := w*(i+1)/len(order) - w*i/len(order) + if i == 0 || colW < layoutWidth { + layoutWidth = colW + } + } + } + barWidth, noteWidth := usageRowsLayout(layoutWidth, allRows) + if sideBySide { + prefixHeight := 0 + for _, prov := range order { + if len(blocks[prov].prefix) > prefixHeight { + prefixHeight = len(blocks[prov].prefix) + } + } + cols := make([]string, 0, len(order)) + for i, prov := range order { + colW := w*(i+1)/len(order) - w*i/len(order) + content := strings.Join(blocks[prov].linesWithUsageLayout(barWidth, noteWidth, prefixHeight), "\n") + cols = append(cols, lipgloss.NewStyle().Width(colW).Render(content)) + } + return lipgloss.JoinHorizontal(lipgloss.Top, cols...) + } + var lines []string + for i, prov := range order { + if i > 0 { + lines = append(lines, "") + } + lines = append(lines, blocks[prov].linesWithUsageLayout(barWidth, noteWidth, 0)...) + } + return strings.Join(lines, "\n") +} + +// usageLoading reports the initial central fetch window where the layout-stable +// skeleton replaces the not-yet-known account and usage rows. +func (m *model) usageLoading() bool { + return m.broker.URL != "" && !m.avail.ok && (m.fetching || m.nextRefresh.IsZero()) +} + +// skeletonWinsByProvider mirrors each provider's stable usage shape. Anthropic +// always reserves the Fable row, even before a first successful fetch. +var skeletonWinsByProvider = func() map[string][]string { + wins := map[string][]string{} + for _, p := range providerRegistry { + if p.Metered { + wins[p.ID] = p.SkeletonWins + } + } + return wins +}() + +// usageRowSpec is the unrendered canonical Usage-row grammar. Every caller +// supplies its indentation and group width, while this one seam reserves the +// label, percentage, reset, and actual note before assigning all safe cells to +// the bar. +type usageRowSpec struct { + indent string + label string + barPct int + percentage string + reset string + note string + reserveNote string +} + +func usageRowsNoteWidth(rows []usageRowSpec) int { + width := 0 + for _, row := range rows { + for _, note := range []string{row.note, row.reserveNote} { + if noteWidth := lipgloss.Width(note); noteWidth > width { + width = noteWidth + } + } + } + return width +} + +const usageResetValueWidth = 6 + +func paddedUsageReset(reset string) string { + width := lipgloss.Width(gReset) + 1 + usageResetValueWidth + return reset + strings.Repeat(" ", max(0, width-lipgloss.Width(reset))) +} + +func (r usageRowSpec) render(barWidth, noteWidth int) string { + note := "" + if r.note != "" { + note = " " + r.note + strings.Repeat(" ", max(0, noteWidth-lipgloss.Width(r.note))) + } else if noteWidth > 0 { + note = strings.Repeat(" ", 2+noteWidth) + } + return fmt.Sprintf("%s%-9s %s %s used %s%s", + r.indent, r.label, barStr(r.barPct, barWidth), r.percentage, paddedUsageReset(r.reset), note) +} + +func (r usageRowSpec) reservedWidth(noteWidth int) int { + return lipgloss.Width(r.render(0, noteWidth)) +} + +func usageRowsLayout(width int, rows []usageRowSpec) (barWidth, noteWidth int) { + noteWidth = usageRowsNoteWidth(rows) + if width == 0 { + return usageBarNaturalW, noteWidth + } + reserved := 0 + for _, row := range rows { + if rowW := row.reservedWidth(noteWidth); rowW > reserved { + reserved = rowW + } + } + return max(0, width-reserved), noteWidth +} + +func usageRowsBarWidth(width int, rows []usageRowSpec) int { + barWidth, _ := usageRowsLayout(width, rows) + return barWidth +} + +func renderUsageRows(width int, rows []usageRowSpec) []string { + barWidth, noteWidth := usageRowsLayout(width, rows) + rendered := make([]string, len(rows)) + for i, row := range rows { + rendered[i] = row.render(barWidth, noteWidth) + } + return rendered +} + +func skeletonUsageRowSpec(label, indent string) usageRowSpec { + return usageRowSpec{ + indent: indent, label: label, + percentage: stDim.Render(" ··%"), + reset: stDim.Render(gReset + " ····"), + reserveNote: "unavailable", + } +} + +// skeletonRow is the natural-width placeholder row used by measurement and +// focused tests. Real layout goes through renderUsageRows with its group width. +func skeletonRow(label string) string { + return renderUsageRows(0, []usageRowSpec{skeletonUsageRowSpec(label, " ")})[0] +} + +// skeletonBody is the pre-first-fetch Usage content: provider headings, +// explicit checking state, and generic placeholder window rows, laid out by +// the same group logic as real data so the first result lands predictably. +func (m *model) skeletonBody(w int) string { + return m.skeletonBodyLayout(w, false) +} + +func (m *model) skeletonBodyLayout(w int, stacked bool) string { + a := m.selectedUsageAvailability() + order := meteredProviderIDs() + blocks := map[string]usageRenderGroup{} + for _, prov := range order { + block := usageRenderGroup{prefix: providerIdentityBlockFor(a, prov, true, m.fullUsageIDs)} + for _, label := range skeletonWinsByProvider[prov] { + block.rows = append(block.rows, skeletonUsageRowSpec(label, " ")) + } + blocks[prov] = block + } + return layoutGroups(w, order, blocks, stacked) +} + +// usageColumn is the medium layout's left-hand Usage section: the panel with +// provider groups forced into a vertical stack — the column is deliberately +// too narrow for side-by-side groups. The panel carries its own title chrome. +func (m model) usageColumn() string { + return m.usagePanelStackedFor(0) +} + +// usageColW is the medium usage column's measured width: the widest rendered +// line of the stacked panel (title, controls, bars, notes) — measured, not guessed. +func (m model) usageColW() int { + return lipgloss.Width(m.usageColumn()) +} + +// secondaryMinH is the medium secondary row's minimum height: routing's pinned +// chrome plus a few useful route rows, or the full stacked usage column when +// that is taller — medium only engages when neither column needs clipping. +func (m model) secondaryMinH() int { + h := prevChromeRows + minRouteRows + if m.hideUsage { + return h + } + if u := lipgloss.Height(m.usageColumn()); u > h { + h = u + } + return h +} + +func formatCachedAge(observed int64, now time.Time) string { + seconds := now.Unix() - observed + if seconds < 0 { + seconds = 0 + } + switch { + case seconds < 60: + return "<1m ago" + case seconds < 60*60: + return fmt.Sprintf("%dm ago", seconds/60) + case seconds < 24*60*60: + return fmt.Sprintf("%dh ago", seconds/(60*60)) + case seconds < 7*24*60*60: + return fmt.Sprintf("%dd ago", seconds/(24*60*60)) + case seconds < 365*24*60*60: + return fmt.Sprintf("%dw ago", seconds/(7*24*60*60)) + default: + return fmt.Sprintf("%dy ago", seconds/(365*24*60*60)) + } +} + +// deepseekBalanceRow renders the DeepSeek group's single body row: the prepaid +// balance — no bar, no window, no reset countdown, because none exist. A +// balance under the suggestion floor carries an explicit "low" cue (the same +// threshold that stops proposals from spending the pool), and the off-peak +// discount window is surfaced while it is live. +func (m *model) deepseekBalanceRow(b deepseekBalance) string { + if !b.ok { + return " " + stWarn.Render("balance unavailable") + } + row := " balance " + stHead.Render("$"+b.total+" "+b.currency) + stDim.Render(" · pay-as-you-go") + if v, err := strconv.ParseFloat(b.total, 64); err == nil && v < deepseekLowBalanceUSD { + row += " " + stWarn.Render("low") + } + if deepseekOffPeak(offPeakNow().UTC()) { + row += " " + stDim.Render("off-peak −50%") + } + if b.stale { + cached := "cached" + if b.fetchedAt > 0 { + cached += " " + formatCachedAge(b.fetchedAt, time.Now()) + } + row += " " + stWarn.Render(cached) + } + return row +} +func (m *model) usageRowSpec(w usageWin, indent string) usageRowSpec { + if w.missing { + // Never-observed windows keep the exact row grammar with dotted values; + // only the status text differs from the loading skeleton. + row := skeletonUsageRowSpec(shortWin(w.label), indent) + row.note = stDim.Render("unavailable") + return row + } + note := "" + if w.pct >= 80 { + note = stWarn.Render("tight") + } + if w.pct >= 100 { + note = stBrk.Render("maxed") + } + if w.tier == "spark" && w.pct == 0 { + note = lipgloss.NewStyle().Foreground(lipgloss.Color(cGreen)).Render("idle") + } + if w.stale { + // Retained after a refresh omitted this window; show its age explicitly. + cached := "cached" + if w.observed > 0 { + cached += " " + formatCachedAge(w.observed, time.Now()) + } + if note != "" { + note += " " + } + note += stWarn.Render(cached) + } + resetText := gReset + " " + pad(fmtReset(w.secs), 4) + reset := stDim.Render(resetText) + if w.dur > 0 && w.secs*10 < w.dur { + reset = lipgloss.NewStyle().Foreground(lipgloss.Color("#c8d0dc")).Bold(true).Render(resetText) + } else if w.dur > 0 && w.secs*4 < w.dur { + reset = lipgloss.NewStyle().Foreground(lipgloss.Color("#c8d0dc")).Render(resetText) + } + // During the one-time first-load fill only the bar is scaled toward its + // target; the label, percentage, reset, and note are real from frame one. + barPct := w.pct + if m.barAnim > 0 { + barPct = barPct * m.barAnim / barAnimSteps + } + return usageRowSpec{ + indent: indent, label: shortWin(w.label), barPct: barPct, + percentage: fmt.Sprintf("%3d%%", w.pct), reset: reset, note: note, + } +} + +// usageRow is the natural-width measurement/test path. Composed panels and +// manager account groups render the same spec through renderUsageRows using +// their actual assigned width. +func (m *model) usageRow(w usageWin) string { + return renderUsageRows(0, []usageRowSpec{m.usageRowSpec(w, " ")})[0] +} + +// Reset-credit expiry urgency tints: each individual expiry (`3d`, `12d`, …) +// in the credit line is colored on a muted red→amber→green ramp so soon +// expiries read as warnings and distant ones as headroom, while the icon, +// count, and connecting prose stay dim. The palette is precomputed and +// deliberately desaturated (no per-frame color math, no saturated alarm +// colors inside a dim summary row); the day text itself stays sufficient +// without color. Thresholds are whole days remaining, exactly as fmtDays +// rounds them (up, so later-today = 1): ≤ creditUrgentDays is muted red, +// ≤ creditSoonDays muted amber, anything later muted green. +const ( + creditUrgentDays = 3 // expiring within three days — spend it or lose it + creditSoonDays = 10 // within ten days — plan around it +) + +var ( + stCreditUrgent = lipgloss.NewStyle().Foreground(lipgloss.Color("#b0716f")) // muted red + stCreditSoon = lipgloss.NewStyle().Foreground(lipgloss.Color("#b39c6b")) // muted amber + stCreditSafe = lipgloss.NewStyle().Foreground(lipgloss.Color("#85a883")) // muted green +) + +// creditDayStyle picks the urgency tint for a credit expiring in s seconds, +// bucketing on the same rounded-up whole days fmtDays renders — the color and +// the text can never disagree about which side of a threshold an expiry is on. +func creditDayStyle(s int64) lipgloss.Style { + d := int64(0) + if s > 0 { + d = (s + 86399) / 86400 + } + switch { + case d <= creditUrgentDays: + return stCreditUrgent + case d <= creditSoonDays: + return stCreditSoon + default: + return stCreditSafe + } +} + +// creditSummary renders the OpenAI reset-credit summary: the available count +// and the days remaining until the three soonest credit expirations, ascending. +// Callers own indentation and any account identity prefix. +func creditSummary(c resetCredits) string { + if c.avail == 0 && len(c.exp) == 0 { + return "" + } + exp := append([]int64(nil), c.exp...) + sort.Slice(exp, func(i, j int) bool { return exp[i] < exp[j] }) + if len(exp) > 3 { + exp = exp[:3] + } + noun := "resets" + if c.avail == 1 { + noun = "reset" + } + line := stDim.Render(gReset + " " + fmt.Sprintf("%d %s", c.avail, noun)) + if len(exp) > 0 { + days := make([]string, len(exp)) + for i, s := range exp { + days[i] = creditDayStyle(s).Render(fmtDays(s)) + } + line += stDim.Render(" · expiring in ") + strings.Join(days, stDim.Render(", ")) + } + return line +} + +func creditLineFor(c resetCredits) string { + if summary := creditSummary(c); summary != "" { + return " " + summary + } + return "" +} + +func creditLineForAccount(identity string, c resetCredits) string { + if summary := creditSummary(c); summary != "" { + return " " + stDim.Render(identity+" · ") + summary + } + return "" +} + +func (m *model) creditLine() string { + return creditLineFor(m.selectedUsageAvailability().credits) +} + +// fmtDays renders a relative duration as whole days remaining, rounding up so +// a credit expiring later today still reads 1d. +func fmtDays(s int64) string { + if s <= 0 { + return "0d" + } + return fmt.Sprintf("%dd", (s+86399)/86400) +} + +// syncPreview re-renders the routing content and jumps back to the top — for +// content changes (facet cycling, depth, reset), where the old scroll offset +// points at rows that no longer exist. diff --git a/vault.go b/vault.go index d1955e7..25ada86 100644 --- a/vault.go +++ b/vault.go @@ -15,15 +15,16 @@ import ( "time" ) -const ( - anthropicProvider = "anthropic" - openAIProvider = "openai-codex" -) - +// account is one broker credential's identity. apiKey is deliberately +// unexported: it exists only in memory for direct balance fetches (DeepSeek) +// and must never reach any serialized artifact — usage cache, account state, +// or the forwarded account pool. type account struct { - Provider string - IdentityKey string - Email string + Provider string + IdentityKey string + Email string + apiKey string + credentialID string } type accountKey struct { @@ -108,12 +109,17 @@ func loadAccounts(broker brokerConfig) (map[string][]account, error) { } func emptyAccounts() map[string][]account { - return map[string][]account{anthropicProvider: {}, openAIProvider: {}} + accounts := make(map[string][]account, len(providerRegistry)) + for _, p := range providerRegistry { + accounts[p.ID] = []account{} + } + return accounts } func parseAccountSnapshot(body []byte) (map[string][]account, error) { var snapshot struct { Credentials *[]struct { + ID json.RawMessage `json:"id"` Provider string `json:"provider"` IdentityKey string `json:"identityKey"` Credential json.RawMessage `json:"credential"` @@ -132,12 +138,13 @@ func parseAccountSnapshot(body []byte) (map[string][]account, error) { accounts := emptyAccounts() seen := make(map[accountKey]bool) for _, item := range *snapshot.Credentials { - if item.Provider != anthropicProvider && item.Provider != openAIProvider { + if providerByID(item.Provider) == nil { continue } var credential struct { Type string `json:"type"` Email string `json:"email"` + Key string `json:"key"` } if len(item.Credential) == 0 || bytes.Equal(item.Credential, []byte("null")) { return nil, fmt.Errorf("broker snapshot account %s/%s has no credential metadata", item.Provider, item.IdentityKey) @@ -145,7 +152,22 @@ func parseAccountSnapshot(body []byte) (map[string][]account, error) { if err := json.Unmarshal(item.Credential, &credential); err != nil { return nil, fmt.Errorf("invalid credential metadata for %s/%s: %w", item.Provider, item.IdentityKey, err) } - if credential.Type != "oauth" { + switch credential.Type { + case "oauth": + case "api_key": + // API-key credentials have no broker identity (identityKey is + // null) and no account-pool routing. For unmetered providers + // (DeepSeek) they are display-only rows whose key is retained in + // memory for direct balance fetches; for metered providers they + // are skipped exactly as before — OAuth is the only usable shape. + if p := providerByID(item.Provider); !p.Metered { + accounts[item.Provider] = append(accounts[item.Provider], account{ + Provider: item.Provider, IdentityKey: item.IdentityKey, + apiKey: credential.Key, credentialID: strings.Trim(string(item.ID), `"`), + }) + } + continue + default: continue } if strings.TrimSpace(item.IdentityKey) == "" { @@ -160,7 +182,8 @@ func parseAccountSnapshot(body []byte) (map[string][]account, error) { Provider: item.Provider, IdentityKey: item.IdentityKey, Email: credential.Email, }) } - for _, provider := range []string{anthropicProvider, openAIProvider} { + for _, p := range providerRegistry { + provider := p.ID sort.Slice(accounts[provider], func(i, j int) bool { return accounts[provider][i].IdentityKey < accounts[provider][j].IdentityKey }) @@ -367,7 +390,7 @@ func decodeAccountStateEntries(entries []accountStateEntry) (map[accountKey]bool } disabled := make(map[accountKey]bool, len(entries)) for _, entry := range entries { - if (entry.Provider != anthropicProvider && entry.Provider != openAIProvider) || entry.IdentityKey == "" { + if providerByID(entry.Provider) == nil || entry.IdentityKey == "" { return nil, false } key := accountKey{Provider: entry.Provider, IdentityKey: entry.IdentityKey} @@ -385,7 +408,7 @@ func encodeAccountStateEntries(disabled map[accountKey]bool) ([]accountStateEntr if !isDisabled { continue } - if (key.Provider != anthropicProvider && key.Provider != openAIProvider) || key.IdentityKey == "" { + if providerByID(key.Provider) == nil || key.IdentityKey == "" { return nil, fmt.Errorf("invalid disabled account %q/%q", key.Provider, key.IdentityKey) } entries = append(entries, accountStateEntry{Provider: key.Provider, IdentityKey: key.IdentityKey}) @@ -488,9 +511,17 @@ func atomicPrivateWrite(path string, body []byte) error { return nil } +// buildAccountPool seeds the account-pool file omp routes OAuth identities +// through. Only metered (OAuth) providers belong: api_key providers have no +// identity keys and no pool routing. func buildAccountPool(accounts map[string][]account, disabled map[accountKey]bool) map[string][]string { - pool := map[string][]string{anthropicProvider: {}, openAIProvider: {}} - for _, provider := range []string{anthropicProvider, openAIProvider} { + pool := make(map[string][]string, len(providerRegistry)) + for _, p := range providerRegistry { + if !p.Metered { + continue + } + provider := p.ID + pool[provider] = []string{} for _, acct := range accounts[provider] { key := accountKey{Provider: provider, IdentityKey: acct.IdentityKey} if acct.Provider == provider && acct.IdentityKey != "" && !disabled[key] { @@ -594,3 +625,86 @@ func stripProfileArgs(args []string) []string { } return clean } + +// ── DeepSeek balance ────────────────────────────────────────────────────────── +// DeepSeek publishes no rate-limit windows (prepaid pay-as-you-go), so omp's +// usage report never carries it. The upstream balance endpoint is the only +// quota-like surface; it is queried directly with the api_key retained from +// the broker snapshot — the key lives in memory only and is never serialized. + +// deepseekBalanceURL is a package var so tests can point it at an httptest +// server. +var deepseekBalanceURL = "https://api.deepseek.com/user/balance" + +// deepseekBalance is the rendered state of the DeepSeek prepaid balance. +// A nil *deepseekBalance means "no DeepSeek credential" (group hidden); +// ok=false means the credential exists but the balance is unavailable. +type deepseekBalance struct { + ok bool + currency string + total string + fetchedAt int64 + stale bool // restored from cache or retained across a failed refresh +} + +// fetchDeepSeekBalance queries the upstream balance endpoint with the +// account's API key. Numeric fields arrive as JSON strings per the DeepSeek +// docs; json.Number tolerates bare numbers too. The USD entry wins when +// present, else the first one; an empty list or is_available=false degrades +// to the unavailable state without error. +func fetchDeepSeekBalance(key string) (deepseekBalance, error) { + req, err := http.NewRequest(http.MethodGet, deepseekBalanceURL, nil) + if err != nil { + return deepseekBalance{}, err + } + req.Header.Set("Authorization", "Bearer "+key) + resp, err := (&http.Client{Timeout: 10 * time.Second}).Do(req) + if err != nil { + return deepseekBalance{}, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + _, _ = io.Copy(io.Discard, resp.Body) + return deepseekBalance{}, fmt.Errorf("balance endpoint returned %s", resp.Status) + } + body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return deepseekBalance{}, err + } + var doc struct { + IsAvailable bool `json:"is_available"` + BalanceInfos []struct { + Currency string `json:"currency"` + TotalBalance json.Number `json:"total_balance"` + } `json:"balance_infos"` + } + if err := json.Unmarshal(body, &doc); err != nil { + return deepseekBalance{}, fmt.Errorf("invalid balance payload: %w", err) + } + now := time.Now().Unix() + if !doc.IsAvailable || len(doc.BalanceInfos) == 0 { + return deepseekBalance{fetchedAt: now}, nil + } + pick := doc.BalanceInfos[0] + for _, info := range doc.BalanceInfos { + if strings.EqualFold(info.Currency, "USD") { + pick = info + break + } + } + return deepseekBalance{ + ok: true, currency: strings.ToUpper(pick.Currency), + total: pick.TotalBalance.String(), fetchedAt: now, + }, nil +} + +// deepseekAPIKey finds the in-memory DeepSeek api_key in a parsed snapshot; +// "" when no such credential exists. +func deepseekAPIKey(accounts map[string][]account) string { + for _, acct := range accounts[deepseekProvider] { + if acct.apiKey != "" { + return acct.apiKey + } + } + return "" +} diff --git a/vault_test.go b/vault_test.go index 897f879..6fa63d2 100644 --- a/vault_test.go +++ b/vault_test.go @@ -38,7 +38,7 @@ func TestLoadAccountsValidatesFiltersAndSortsCentralSnapshot(t *testing.T) { if authorization != "Bearer central-secret" { t.Fatalf("authorization = %q", authorization) } - if len(got) != 2 || len(got[anthropicProvider]) != 3 || len(got[openAIProvider]) != 2 { + if len(got) != len(providerRegistry) || len(got[anthropicProvider]) != 3 || len(got[openAIProvider]) != 2 || len(got[deepseekProvider]) != 0 { t.Fatalf("account groups = %#v", got) } if keys := accountKeys(got[anthropicProvider]); !reflect.DeepEqual(keys, []string{"a-claude", "b-claude", "c-claude"}) { @@ -437,3 +437,123 @@ func strconvQuote(value string) string { body, _ := json.Marshal(value) return string(body) } + +// TestFetchDeepSeekBalance covers the upstream balance contract: numeric +// fields as JSON strings, USD preferred over other currencies, and the +// documented degrade paths (is_available=false, HTTP failure). +func TestFetchDeepSeekBalance(t *testing.T) { + var authorization string + payload := `{"is_available":true,"balance_infos":[ + {"currency":"CNY","total_balance":"110.00","granted_balance":"10.00","topped_up_balance":"100.00"}, + {"currency":"USD","total_balance":"12.34","granted_balance":"0.00","topped_up_balance":"12.34"} + ]}` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authorization = r.Header.Get("Authorization") + _, _ = w.Write([]byte(payload)) + })) + defer server.Close() + saved := deepseekBalanceURL + deepseekBalanceURL = server.URL + defer func() { deepseekBalanceURL = saved }() + + got, err := fetchDeepSeekBalance("sk-test") + if err != nil { + t.Fatal(err) + } + if authorization != "Bearer sk-test" { + t.Fatalf("authorization = %q", authorization) + } + if !got.ok || got.currency != "USD" || got.total != "12.34" { + t.Fatalf("balance = %+v, want the USD entry", got) + } + + payload = `{"is_available":true,"balance_infos":[{"currency":"CNY","total_balance":"7.5"}]}` + if got, err = fetchDeepSeekBalance("sk-test"); err != nil || !got.ok || got.currency != "CNY" || got.total != "7.5" { + t.Fatalf("no-USD fallback = %+v (err %v), want the first entry", got, err) + } + + payload = `{"is_available":false,"balance_infos":[]}` + if got, err = fetchDeepSeekBalance("sk-test"); err != nil || got.ok { + t.Fatalf("unavailable account must degrade without error, got %+v (err %v)", got, err) + } + + server.Close() + if _, err := fetchDeepSeekBalance("sk-test"); err == nil { + t.Fatal("transport failure must surface as an error") + } +} + +// TestFetchDeepSeekBalanceHTTPError: a non-200 answer is an error, not a +// zero balance. +func TestFetchDeepSeekBalanceHTTPError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + saved := deepseekBalanceURL + deepseekBalanceURL = server.URL + defer func() { deepseekBalanceURL = saved }() + if _, err := fetchDeepSeekBalance("sk-test"); err == nil { + t.Fatal("HTTP 500 must surface as an error") + } +} + +// TestDeepSeekKeyStaysInMemory is the security boundary of the balance +// feature: the api_key parsed from the broker snapshot must exist only on the +// in-memory account struct and never reach any serialized artifact — neither +// the usage cache (which marshals the accounts map) nor the account state. +func TestDeepSeekKeyStaysInMemory(t *testing.T) { + snapshot := []byte(`{"credentials":[ + {"id":7,"provider":"deepseek","identityKey":null,"credential":{"type":"api_key","key":"sk-secret-x"}}, + {"provider":"anthropic","identityKey":"a-claude","credential":{"type":"oauth","email":"a@example.com"}} + ]}`) + accounts, err := parseAccountSnapshot(snapshot) + if err != nil { + t.Fatal(err) + } + ds := accounts[deepseekProvider] + if len(ds) != 1 || ds[0].apiKey != "sk-secret-x" || ds[0].credentialID != "7" { + t.Fatalf("deepseek account = %+v, want in-memory key and credential id", ds) + } + if got := deepseekAPIKey(accounts); got != "sk-secret-x" { + t.Fatalf("deepseekAPIKey = %q", got) + } + + // The usage cache serializes the accounts map wholesale: the unexported + // key field must not survive the round trip. + body, err := json.Marshal(usageCacheFile{SavedAt: 1, Accounts: accounts}) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(body), "sk-") { + t.Fatalf("api key leaked into the usage cache payload: %s", body) + } + + // The forwarded account pool is OAuth-only; deepseek must not appear. + pool := buildAccountPool(accounts, nil) + if _, ok := pool[deepseekProvider]; ok { + t.Fatalf("account pool grew an api_key provider: %#v", pool) + } + + // Account state persists only {provider, identityKey} pairs of known + // providers; a deepseek OAuth-style entry would be inventable only by + // hand and must round-trip (registry filter, not the old two-literal + // allow-list). + statePath := filepath.Join(t.TempDir(), "state.json") + state := defaultAccountSelectionState() + state.SetManualDisabled(map[accountKey]bool{{Provider: deepseekProvider, IdentityKey: "k"}: true}) + if err := writeAccountSelectionState(statePath, state); err != nil { + t.Fatal(err) + } + raw, err := os.ReadFile(statePath) + if err != nil { + t.Fatal(err) + } + if strings.Contains(string(raw), "sk-") { + t.Fatalf("api key leaked into account state: %s", raw) + } + loaded := loadAccountSelectionState(statePath) + if !loaded.CurrentDisabled()[accountKey{Provider: deepseekProvider, IdentityKey: "k"}] { + t.Fatalf("registry-known provider entry did not round-trip: %#v", loaded.CurrentDisabled()) + } +} diff --git a/view.go b/view.go new file mode 100644 index 0000000..1d8ba6e --- /dev/null +++ b/view.go @@ -0,0 +1,442 @@ +package main + +import ( + "fmt" + "strings" + + clikit "github.com/atyrode/cli-kit" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/viewport" + "github.com/charmbracelet/lipgloss" +) + +func (m *model) footer() string { + usage := "" + if m.usageInFooter() { + usage = m.usagePanel() + } + return clikit.SeparatedSections( + m.w, + usage, + padLeft(m.help.View(m.contextHelp()), gut), + ) +} + +// usageInFooter says where Usage lives: the wide layout keeps it as the +// full-width bottom band; medium moves it into the secondary column (back to +// the footer while ‹p› hides that row); narrow/short hides it entirely so the +// Generator stays usable first — fetch state and the refresh cadence keep +// running unseen, and nothing is refetched when it reappears. +func (m *model) usageInFooter() bool { + if m.hideUsage { + return false + } + switch m.sizeMode() { + case sizeWide: + return true + case sizeMedium: + return m.collapse + default: + return false + } +} + +// routingShown reports whether the Routing section (and so its title-local +// p · hide cue) is on screen in the current composition. +func (m model) routingShown() bool { + if m.showUsage && m.sizeMode() == sizeNarrow { + return false + } + if m.collapse { + return false + } + if m.mode() == modeCollapsed { + return m.showResult + } + return true +} + +// usageShown reports whether the Usage panel is rendered anywhere — the +// wide/collapsed footer band, medium's secondary column, or narrow's dedicated +// full-screen view. +func (m model) usageShown() bool { + if m.hideUsage { + return false + } + if m.sizeMode() == sizeNarrow { + return m.showUsage + } + return m.usageInFooter() || m.mode() == modeMedium +} + +// usageCanShow reports whether restoring Usage would actually render it. +// Narrow terminals use a dedicated full-width view when they can seat the +// stacked Usage column; extremely small terminals still suppress a dead cue. +func (m model) usageCanShow() bool { + if m.sizeMode() == sizeNarrow { + return m.w >= m.usageColW() + } + t := m + t.hideUsage = false + return t.usageShown() +} + +// generatorShown: the Generator (and its launch footer advertising ⏎/m/u) is +// visible in every composition except a narrow full-screen secondary view. +func (m model) generatorShown() bool { + if m.sizeMode() == sizeNarrow && m.showUsage { + return false + } + return !(m.mode() == modeCollapsed && !m.collapse && m.showResult) +} + +// helpKeys is the state-derived key map handed to the bubbles help view: the +// compact line lists only what contextHelp selected, while ? always exposes +// the complete static reference. +type helpKeys struct { + short []key.Binding + full [][]key.Binding +} + +func (h helpKeys) ShortHelp() []key.Binding { return h.short } +func (h helpKeys) FullHelp() [][]key.Binding { return h.full } + +// contextHelp derives the compact footer from the model state (atyrode/dotfiles#198): +// - navigation, reset, full-help discovery, and quit are always offered; +// - a hidden section contributes its recovery action (show routing/usage) — +// usage only when the current terminal could actually seat it again; +// - account management and manual refresh surface while Usage is off screen, +// with refresh hidden while its one central request is in flight; +// - the launch trio surfaces only while the Generator launch footer is +// hidden (the narrow routing-full-screen swap). +// +// Everything else lives in visible section chrome or behind ?. +func (m model) contextHelp() helpKeys { + short := []key.Binding{keys.Move, keys.Change} + // The generator title advertises d · defaults itself; the compact line + // repeats it only while that chrome is off screen (routing full-screen). + if !m.generatorShown() { + short = append(short, keys.Reset) + } + if !m.routingShown() { + short = append(short, key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "show routing"))) + } + if !m.usageShown() && m.usageCanShow() { + short = append(short, key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "show usage"))) + } + // Keep full-help discovery and quit ahead of optional contextual actions: + // bubbles truncates a compact line from the right on narrow terminals. + short = append(short, keys.Help, keys.Quit) + if !m.usageShown() { + short = append(short, keys.Manager) + if m.broker.URL != "" && !m.fetching { + short = append(short, keys.Refresh) + } + } + if !m.generatorShown() { + short = append(short, keys.Launch, keys.Managed, keys.Untrusted) + } + return helpKeys{short: short, full: keys.FullHelp()} +} + +func (m *model) relayout() { + m.help.Width = m.w - gut // the footer help is gutter-inset on every line; wrap inside the padded width + pw, ph := m.previewDims() + if pw < 10 { + pw = 10 + } + if ph < 3 { + ph = 3 + } + if !m.rdy { + m.vp = viewport.New(pw, ph) + m.rdy = true + } else { + m.vp.Width, m.vp.Height = pw, ph + } + m.syncPreviewKeepScroll() +} + +const prevPadL = 2 + +func (m model) previewPane(w, h int) string { + return lipgloss.NewStyle(). + Border(lipgloss.NormalBorder(), false, false, false, true). + BorderForeground(lipgloss.Color(cBord)).PaddingLeft(prevPadL). + Width(w).Height(h). + Render(m.previewColumn()) +} + +// accent is the context colour — the selected lane in the generator. +// Blue / purple / orange. +func (m model) accent() string { + if _, local := m.selectedRuntime(); local { + return cGreen + } + return laneColor(m.sel["lane"]) +} + +// pill renders an accent-backed section label, coloured by the active lane — +// the shared shape of the generator (left) and routing (right) column heads. +func (m model) pill(label string) string { + return lipgloss.NewStyle().Padding(0, 1). + Background(lipgloss.Color(m.accent())).Foreground(lipgloss.Color("#12161d")).Bold(true). + Render(label) +} + +// sectionTitle is the accent-pilled "generator" label at the top of the left +// column. +func (m model) sectionTitle() string { + return m.pill("generator") +} + +// sectionHead is the gutter-inset title row — the pill plus its local +// reset-to-defaults cue (d · defaults) — and a blank separator (headRows +// tall); it stays pinned above the scrolling facet list. +func (m model) sectionHead() string { + return padLeft(m.sectionTitle()+" "+stCueKey.Render("d")+stCue.Render(" · defaults"), gut) + "\n\n" +} + +// prevChromeRows is the Routing column's pinned chrome around the scrolling +// viewport: the title row with its local collapse cue and a blank separator +// above, plus the fallback-display cue pinned beneath the viewport. +const prevChromeRows = headRows + 1 + +// previewColumn assembles the Routing section: the pinned title row carrying +// the section-local collapse cue (p · hide), the scrolling routing viewport, +// then the fallback-display cue pinned at the section's bottom edge — bottom +// chrome, where the chains it toggles end. The f wording makes clear it only +// changes what is DISPLAYED: the launched profile always keeps its fallback +// chains. +func (m model) previewColumn() string { + verb := "show" + if m.depth == 1 { + verb = "hide" + } + return m.pill("routing") + " " + stCueKey.Render("p") + stCue.Render(" · hide") + "\n\n" + + m.vp.View() + "\n" + + stKey.Render("f") + stDim.Render(" · "+verb+" fallback chains") +} + +// leftColumn renders the pinned section head plus the scrolling list body, the +// whole column inset by the shared gutter, sized to totalH rows and w columns. +func (m model) leftColumn(w, totalH int) string { + iw := w - gut + body, bcur := m.bodyLines() + listH := totalH - headRows - launchFooterRows + if listH < 1 { + listH = 1 + } + list := padLeft(windowList(body, bcur, listH, iw), gut) + footer := padLeft(strings.Join(m.launchFooter(), "\n"), gut) + return m.sectionHead() + list + "\n" + footer +} + +// mediumContent is the generator-dominant layout: the full-width facet list on +// top (primary), a divider, then Usage (left) and Routing (right) side by side +// in a secondary row, separated by a one-cell border column. Usage stacks its +// provider groups vertically inside the measured-width left column; Routing +// keeps its own scrolling viewport in whatever the row leaves free. +func (m model) mediumContent(bodyH int) string { + genH, secH := m.mediumSplit(bodyH) + top := m.leftColumn(m.w, genH) + div := stDim.Render(strings.Repeat("─", m.w)) + rw := m.routingColW() + // clip each column before fixing its width — Width() alone would wrap any + // over-wide line onto an extra physical row and break the row's height. + routing := lipgloss.NewStyle().Width(rw).MaxHeight(secH).Render( + lipgloss.NewStyle().MaxWidth(rw).Render(padLeft(m.previewColumn(), gut))) + sec := routing + if !m.hideUsage { + uw := m.w - rw - secSepW // the measured usage column's share, left of the border + sep := lipgloss.NewStyle().Foreground(lipgloss.Color(cBord)).Render( + strings.TrimSuffix(strings.Repeat("│\n", secH), "\n")) + usage := lipgloss.NewStyle().Width(uw).MaxHeight(secH).Render( + lipgloss.NewStyle().MaxWidth(uw).Render(m.usagePanelStackedFor(uw))) + sec = lipgloss.JoinHorizontal(lipgloss.Top, usage, sep, routing) + } + return lipgloss.JoinVertical(lipgloss.Left, top, div, sec) +} + +func (m model) View() string { + if !m.rdy { + return "loading…" + } + if m.manager { + return m.managerView() + } + foot := m.footer() + bodyH := m.bodyH() + ch := m.contentH() + var content string + switch m.mode() { + case modeCollapsed: + switch { + case m.showUsage && m.sizeMode() == sizeNarrow: + content = padLeft(m.usagePanelFor(m.w-gut), gut) + case m.showResult && !m.collapse: + content = padLeft(m.previewColumn(), gut) + default: + content = m.leftColumn(m.w, ch) + } + case modeMedium: + content = m.mediumContent(ch) + default: // split + content = lipgloss.JoinHorizontal(lipgloss.Top, + m.leftColumn(m.listW(), ch), + m.previewPane(m.w-m.listW()-3, ch)) + } + // Pin the body into a fixed box (top-left) with lipgloss, then clip: any + // stray overflow (e.g. a glyph a terminal renders wider than measured) is + // absorbed here, never pushing the pinned footer off-screen. A top gap above + // the content gives the section tabs vertical breathing room. + body := lipgloss.NewStyle().MaxHeight(bodyH).Render( + strings.Repeat("\n", topGap) + + lipgloss.Place(m.w, ch, lipgloss.Left, lipgloss.Top, content)) + return lipgloss.NewStyle().MaxWidth(m.w).MaxHeight(m.h).Render( + lipgloss.JoinVertical(lipgloss.Left, body, foot)) +} + +func (m model) genLines() ([]string, int) { + acc := m.accent() + var lines []string + cursor := 0 + selected := m.selectedLaunchAvailability() + for i, f := range m.visibleFacets() { + onRow := i == m.fcur + glyCol := laneColor(m.sel["lane"]) + gly := lipgloss.NewStyle().Foreground(lipgloss.Color(glyCol)).Width(2).Render(f.glyph) + ptr := " " + if onRow { + ptr = lipgloss.NewStyle().Foreground(lipgloss.Color(acc)).Render("▸ ") + cursor = len(lines) + } + // main renders as fable's tabulated child "default": the indent + the + // default-role row lighting up Fable in the preview explain themselves, + // so it carries no flavor text (which would wrap on narrow panes anyway). + label, childPad, childW := f.key, "", 0 + if f.key == "main" { + // tree-style L connector: reads as fable's child, like `tree`. + label, childPad, childW = "default", stDim.Render("└ "), 2 + } + if f.key == "blend" { + // blend is lane's sub-setting: the lead row picks who drives, + // this child picks how exclusively. + childPad, childW = stDim.Render("└ "), 2 + } + row := fmt.Sprintf("%s%s%s%s", ptr, childPad, gly, stDim.Render(pad(label, 9-childW))) + if f.key == "thinking" || f.key == "model" || f.key == "advisor" { + row += " " + m.segmentGauge(f, onRow, acc) + } else { + for _, v := range f.values { + display := v + if f.key == "runtime" { + display = m.runtimeValueLabel(v) + } + switch { + case v == m.sel[f.key]: + col := acc + if f.key == "lead" { + col = laneColor(m.sel["lane"]) + } else if (f.key == "spark" || f.key == "fable" || f.key == "main") && v == "on" { + col = cGreen + } + st := lipgloss.NewStyle().Foreground(lipgloss.Color(col)).Bold(true) + if onRow { // the cursor sits on the selected value of the focused row + st = st.Background(lipgloss.Color(cSelBg)) + } + row += " " + st.Render(" "+display+" ") + default: + row += " " + stDim.Render(display) + } + } + } + switch { + case (f.key == "fable" || f.key == "spark") && m.sel[f.key] == "on": + bkt, lbl := "claude-fable", "Fable" + if f.key == "spark" { + bkt, lbl = "codex-spark", "Spark" + } + if selected.down(bkt) { + w := lbl + " maxed · " + gReset + " " + fmtReset(selected.reset[bkt]) + if selected.bucket[bkt] == "unauthed" { + w = lbl + " unavailable" + } + row += " " + stWarn.Render(gWarn+" "+w+" — no usage left") + } + case f.key == "fast" && m.sel["fast"] == "on": + row += " " + stDim.Render("GPT only") + case f.key == "relief": + // relief is the least self-explanatory dial: say what it does, + // in the state it currently does it. + note := "drained chains wait for quota reset" + if m.sel["relief"] == "on" { + note = "drained chains spill into " + optionalPoolLabels() + } + row += " " + stDim.Render(note) + } + lines = append(lines, row) + } + return lines, cursor +} + +// segmentGauge renders a stepped dial (model, thinking, advisor) as a notched +// meter: one cell per selectable option, filled to the selection, with the +// selected word riding along so the level still reads at a glance. A leading +// "off" value is the dial's zero — it takes no cell, so off renders an empty +// track and the first real step lights exactly one. ←/→ behave exactly as on +// a word dial — only the rendering differs; the facet's values are untouched. +func (m model) segmentGauge(f facet, onRow bool, acc string) string { + sel := -1 + for i, v := range f.values { + if v == m.sel[f.key] { + sel = i + } + } + steps := f.values + fill := sel + 1 + if len(steps) > 0 && steps[0] == "off" { + steps = steps[1:] + fill = sel // off (sel 0) lights nothing + } + if fill < 0 { + fill = 0 + } + lit := lipgloss.NewStyle().Foreground(lipgloss.Color(acc)).Bold(true) + var b strings.Builder + // The leading space mirrors the word dials' selected-cell padding, so the + // track starts on the same column the value words do. + b.WriteString(" ") + for i := range steps { + if i < fill { + b.WriteString(lit.Render("▰")) + } else { + b.WriteString(stDim.Render("▱")) + } + } + word := lit + if onRow { + word = word.Background(lipgloss.Color(cSelBg)) + } + label := m.sel[f.key] + if sel < 0 && label == "" { + label = "?" + } + return b.String() + " " + word.Render(" "+label+" ") +} + +// defaultGlyphs is the built-in facet-glyph set (Nerd Font, Font Awesome PUA +// range), written as explicit \u escapes so the codepoints stay visible and +// verifiable in source — a literal PUA glyph is invisible in most editors and +// was once wiped by an edit exactly because of that. CODE_FACET_GLYPHS may +// override any entry (see main). +// +// runtime 🖥 (f108) lane ⇄ (f127) model ⚙ (f085) thinking 💡 (f0eb) advisor 🧭 (f14e) +// spark 🚀 (f135) fable 📖 (f02d) default 🎯 (f140) fast ⚡ (f0e7) +func defaultGlyphs() map[string]string { + return map[string]string{ + "runtime": "\uf108", "lane": "\uf127", "model": "\uf085", "thinking": "\uf0eb", "advisor": "\uf14e", + "spark": "\uf135", "fable": "\uf02d", "main": "\uf140", "fast": "\uf0e7", + "relief": "\uf132", + } +} diff --git a/wheel.go b/wheel.go new file mode 100644 index 0000000..9aa3dbe --- /dev/null +++ b/wheel.go @@ -0,0 +1,101 @@ +package main + +import ( + "time" + + tea "github.com/charmbracelet/bubbletea" +) + +// ── trackpad / mouse wheel ─────────────────────────────────────────────────── +// The wheel drives the generator directly: vertical scroll moves the facet +// selection, horizontal scroll changes the selected facet's value. Terminal +// mouse protocols expose direction-only press events rather than trackpad +// distance, so require a small burst before committing one generator step. +// This gives fine Mac trackpad motion room to settle without making every raw +// event select a new row or option. Routing remains continuous and ungated. +const ( + wheelStepEvents = 5 + wheelGestureGap = 200 * time.Millisecond +) + +const ( + wheelAxisNone = iota + wheelAxisV + wheelAxisH +) + +// admittedWheelMsg marks a generator wheel event whose same-direction burst +// crossed the step threshold before Bubble Tea's Update/render cycle. +type admittedWheelMsg struct{ tea.MouseMsg } + +// wheelTarget is the layout state the pre-dispatch filter needs. Keeping this +// interface narrow also lets the raw-input regression test wrap model while +// preserving the exact production filter path. +type wheelTarget interface { + wheelInRouting(int, int) bool + routingWheelCanMove(tea.MouseButton) bool +} + +// wheelInputFilter removes mouse traffic before Bubble Tea's unconditional +// redraw-after-Update. Generator motion accumulates by axis and direction; +// only each complete threshold reaches Update. Motion, non-wheel presses, +// routing horizontal wheel, and clamped routing scroll are dropped because +// none can change the view. +type wheelInputFilter struct { + axis int + button tea.MouseButton + count int + last time.Time +} + +func (f *wheelInputFilter) Filter(app tea.Model, msg tea.Msg) tea.Msg { + + mouse, ok := msg.(tea.MouseMsg) + if !ok { + return msg + } + if mouse.Action != tea.MouseActionPress { + return nil + } + target, ok := app.(wheelTarget) + if !ok { + return msg + } + if target.wheelInRouting(mouse.X, mouse.Y) { + switch mouse.Button { + case tea.MouseButtonWheelUp, tea.MouseButtonWheelDown: + if target.routingWheelCanMove(mouse.Button) { + return mouse + } + } + return nil + } + + axis := wheelAxisNone + switch mouse.Button { + case tea.MouseButtonWheelUp, tea.MouseButtonWheelDown: + axis = wheelAxisV + case tea.MouseButtonWheelLeft, tea.MouseButtonWheelRight: + axis = wheelAxisH + default: + return nil + } + now := time.Now() + if f.count > 0 && now.Sub(f.last) <= wheelGestureGap && axis != f.axis { + // Ignore brief orthogonal trackpad jitter without discarding progress + // along the operator's dominant gesture axis. + return nil + } + if axis != f.axis || mouse.Button != f.button || now.Sub(f.last) > wheelGestureGap { + f.axis = axis + f.button = mouse.Button + f.count = 0 + } + f.last = now + f.count++ + if f.count < wheelStepEvents { + return nil + } + f.count = 0 + return admittedWheelMsg{MouseMsg: mouse} +}