Skip to content

fix(tui): name each row's provider once the pool serves more than one - #392

Merged
MagicalTux merged 2 commits into
KarpelesLab:masterfrom
hedgehogcandy:fix/tui-show-provider-in-mixed-pool
Sep 15, 2026
Merged

MagicalTux merged 2 commits into
KarpelesLab:masterfrom
hedgehogcandy:fix/tui-show-provider-in-mixed-pool

Conversation

@hedgehogcandy

Copy link
Copy Markdown
Contributor

The account column beside the name shows a.type, which is oauth on every
subscription row. In a single-provider pool that is at least a distinction from an API
key. In a mixed pool it is the one fact the operator already had.

What it needed to say instead is which provider the row belongs to — because one
person's ChatGPT and Claude subscriptions are usually the same email. A pool holding both
lists that address twice, identically, and nothing on the row tells them apart, including
the row the operator is about to act on with s.

Observed on an eight-account pool (three Anthropic, five Codex, two addresses on both
sides) — before:

► howzero@gigworkers.io       oauth  active   Ses ▓▓▓░ 28m   Wk ▓▓▓░ 1d20h
  hedgehogcandy23@gmail.com   oauth  active   Ses ░░░░  0%   Wk ████ 2d11h
  hedgehogcandy23@gmail.com   oauth  active   Ses ░░░░ 4h57m  Wk ▓░░░ 5d
  howzero@gigworkers.io       oauth  active   Ses ░░░░  5h   Wk ▓░░░ 5d

Rows 2 and 3 are the same person's two subscriptions. Rows 1 and 4 likewise. After:

► howzero@gigworkers.io       Anthropic  active  ...
  hedgehogcandy23@gmail.com   Anthropic  active  ...
  hedgehogcandy23@gmail.com   Codex      active  ...
  howzero@gigworkers.io       Codex      active  ...

What changed

When more than one provider is present the column names the provider, and its width
follows the labels actually there, so the longer one is not truncated.

A single-provider pool keeps the column it has today — repeating one name down every row
would trade one uninformative column for another.

providerOf gains its return type on the way past, which takes provider.js from 16
strict-mode diagnostics to 13; tui.js is unchanged at 225.

Tests

npm test 1792 pass / 0 fail, npm run lint and npm run typecheck clean.

Reverting src/ turns two of the four added tests red; the other two are the
single-provider guards, which hold either way by design.

Independent of the other open PRs — different file, no conflict.

hedgehogcandy and others added 2 commits September 14, 2026 20:27
The account column beside the name shows `a.type`, which is `oauth` on every
subscription row. In a single-provider pool that is at least a distinction from
an API key. In a mixed pool it is the one fact the operator already had.

What it needed to say instead is which provider the row belongs to, because one
person's ChatGPT and Claude subscriptions are usually the same email. A pool
holding both lists that address twice, identically, and nothing on the row tells
them apart — including the row the operator is about to act on with `s`.

Observed on an eight-account pool: three Anthropic, five Codex, two addresses
appearing on both sides.

So when more than one provider is present the column names the provider, and its
width follows the labels actually there, so the longer one is not truncated. A
single-provider pool keeps the column it has today, since repeating one name down
every row would trade one uninformative column for another.

`providerOf` gains its return type on the way past, which takes provider.js from
16 strict-mode diagnostics to 13.

Reverting src/ turns two of the four added tests red; the other two are the
single-provider guards, which hold either way by design.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MagicalTux
MagicalTux merged commit 8bdcdf9 into KarpelesLab:master Sep 15, 2026
5 checks passed
jgautheron added a commit to jgautheron/teamclaude that referenced this pull request Sep 15, 2026
The account row's width budget reserved an 8-column type cell whatever the
column held. Since KarpelesLab#392 a mixed pool writes the provider there, padded to
the longest label present, and `Anthropic` is 9, so every row in a mixed
pool ran 2 columns past the budget. fitLine then cut the tail: the reset
label of the last bar, or the whole `$` tag behind a `⊘ Fable`. It only
shows on a row that carries every reserved tag itself, which is how the
budget's other slack hid it.

The row and the budget now take the width from one helper.

Claude-Session: https://claude.ai/code/session_01YCbpDukDtRkSth9MkN7gFQ
jgautheron added a commit to jgautheron/teamclaude that referenced this pull request Sep 15, 2026
The account row's width budget reserved an 8-column type cell whatever the
column held. Since KarpelesLab#392 a mixed pool writes the provider there, padded to
the longest label present, and `Anthropic` is 9, so every row in a mixed
pool ran 2 columns past the budget. fitLine then cut the tail: the reset
label of the last bar, or the whole `$` tag behind a `⊘ Fable`. It only
shows on a row that carries every reserved tag itself, which is how the
budget's other slack hid it.

The row and the budget now take the width from one helper.
MagicalTux added a commit that referenced this pull request Sep 20, 2026
* fix(tui): budget the provider column at the width it draws

The account row's width budget reserved an 8-column type cell whatever the
column held. Since #392 a mixed pool writes the provider there, padded to
the longest label present, and `Anthropic` is 9, so every row in a mixed
pool ran 2 columns past the budget. fitLine then cut the tail: the reset
label of the last bar, or the whole `$` tag behind a `⊘ Fable`. It only
shows on a row that carries every reserved tag itself, which is how the
budget's other slack hid it.

The row and the budget now take the width from one helper.

* fix(tui): draw a subscription account's own bars before its first reading

The row category keyed on readings alone, so an OAuth account that had not
answered a request or a probe yet drew the API-key `Tok`/`Req` pair, and was
budgeted with the metered rows: its empty bars sat at a different width from
the subscription rows around it. A subscription meters Ses/Wk whether or not
it has reported yet, so the category now also follows the auth kind.

* feat(tui): mark each pool's current account in a mixed fleet

A pool holding Claude and Codex accounts rotates each provider on a cursor
of its own, but ► read the manager's single `currentIndex`, which names
whichever pool moved last. The other pool showed no current account at all,
and switching one side with `s` took the marker away from the other.

A mixed pool now marks the account each provider's cursor names, the same
answer `currentAccounts` already gives in /teamclaude/status. That can be a
shared API-key account, since a key serves either pool. The set is read once
per frame. A single-provider pool is unchanged.

Attach mode reads the same answer from the status payload. A name alone
cannot tell a shared API key from a Codex login at the same address, so the
payload now also carries `currentIndexes`, each pool's current account by
position in `accounts`. Attach mode prefers it, falls back to the name
matched on provider, and against a server that sends neither marks its one
cursor as before.

* feat(tui): draw a two-provider pool as two panes, side by side

A pool holding Claude and Codex accounts is one table today, each row naming
its provider, so the table grows by the sum of both pools, and one person's
two subscriptions, usually the same address, sit next to each other told
apart by a single column.

The dashboard now draws one pane per provider, Anthropic on the left and
Codex on the right, so the table is as tall as the larger pool. The pane
titles take the spacer line the table always had, so the split costs no
height. A pane drops the type column, since its title names the provider,
reserves route cells only for its own provider's routes, and caps its bars
narrower. Each pane is laid out with the per-category budget the full table
uses.

Width is dealt out in the order a pane spends it: bars wide enough for
their reset label, whole names, then wider bars. Both panes reach one step
before either starts the next, a step that cannot be met in full is shared
in proportion to what each pane still wants, so a pane with three bars and
a `⊘ Fable` tag does not cut its names while the other pads. The table
splits only when both panes reach the first step and keep every bar the
rows draw in one column; otherwise it stays one column, now grouped by
provider, which is also the order selection walks. Like the bars in one
column, the split can change when a `⊘` tag appears near that width.

A Codex plan need not state a five-hour limit. When the Codex accounts in a
list have all reported and none states one, the list draws no Ses bar and
starts at Wk. An account that has not reported yet keeps the column, so it
does not come and go at startup.

* docs(usage): describe the two-provider panes and the currentIndexes status field

The panes change what an operator sees on a wide terminal and the status
payload gains a field, but neither was documented. usage.md now says when the
account table splits into one pane per provider (two providers, 127 columns or
more, every bar still drawable), that each pool has its own current marker, when
the Ses bar is dropped, and what currentIndexes holds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Mark Karpeles <magicaltux@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
@MagicalTux MagicalTux mentioned this pull request Sep 20, 2026
MagicalTux added a commit that referenced this pull request Sep 20, 2026
Thirty-six commits since 1.1.20. Several change what a client sees on a
failure, so read the first section before upgrading a shared deployment.

Behaviour changes
  #439 a 401 from upstream is no longer relayed to the client: the request
       fails over like a 403, and an API-key account or an OAuth account with
       no refresh token leaves rotation (it used to be picked again on every
       request). With nothing left the client gets the proxy's own error
  #439 a path under /teamclaude/ that no control route claims — a typo, or the
       wrong verb — answers 404 locally instead of being forwarded upstream
       under a fleet credential
  #429 the synthetic 429's retry-after is the real reset of the windows
       blocking the request's candidate accounts, not a flat 60s, and the
       message counts only those candidates; #408 names accounts that need a
       re-login instead of calling them "at quota"
  #438 session pins are per conversation (session id plus a digest of the
       first message), so a session's subagents spread across accounts.
       `sessions.items[].id` in status is the composite key, load is counted
       per conversation, and a persisted concurrency cap re-learns
  #378 a `thread: continue` bound for a per-account third-party upstream is
       refused with the 400 Anthropic gives, so the client resends the whole
       conversation; `messageThreads: true` opts a relay out
  #434 a 429 whose x-codex-* headers show a spent account-wide window holds
       the account like an Anthropic rejection; a spent model-scoped bucket
       only moves the request
  #437 a Codex response head is awaited for five minutes (Anthropic unchanged)
  #411 idle keep-alive connections are held 120s on both listeners
  #389 with session distribution on, requests carrying no session id rotate on
       a cursor of their own instead of all resting on the current account
  #405 `defaultClientMode` ("mitm" | "base-url") sets what `run` and `env` do
       without a flag; `--mitm` / `--no-mitm` decide per launch, and in
       base-URL mode `env` unsets a stale proxy export naming this proxy
  #439 route `--bucket` is validated; an array `switchThreshold` reads as the
       default with one line saying so

Features
  #419 an MCP management endpoint at POST /teamclaude/mcp, off unless
       `proxy.mcp` is "read" or "full"; a named client key is read-only even
       in full mode, and with no proxy key it serves only this machine
  #428 per-account `switchThreshold`, a number or a per-bucket table
  #406 a Claude+Codex pool is drawn as two panes on a wide terminal, each with
       its own current marker; #392 names the provider in a mixed list; #418
       lets the operator arrange the list (`displayOrder`); #376 draws
       loopback-served accounts last; #435 shows the percentage beside a bar's
       countdown; #394 shows the running version in the header
  #430 free Codex rate-limit reset credits in status, the TUI and the dashboard
  #385 `proxy.terminalOnly` tunnels chatgpt.com so ChatGPT Desktop stays out

Fixes
  #404 a TUI paint can no longer block the proxy (stdout non-blocking, frames
       dropped while the terminal is behind); #410 a dead terminal no longer
       takes the proxy with it, and SIGHUP shuts down cleanly
  #433 token usage is booked from Codex Responses streams
  #386 #387 #388 the Codex five-hour window is read from the model-scoped
       family and the usage probe, and extra limits are named from their entries
  #431 a headerless 429 that follows the request is retried once
  #432 #439 startup and collaborator log lines reach the TUI's activity pane
       and log file instead of the covered terminal
  #415 #403 #439 reload mirrors `priority`, `disabled`, `stripRequestFields`
       onto the config entry, and a reload during a removal does not re-add it
  #439 the Host check uses the address actually bound; sx.org calls time out
  #381 the dashboard polls status before asking for a key
  #403 session outcome accounting classifies the decoded path; account names in
       daemon log lines are sanitised

Tooling
  #371 #372 #373 `npm run typecheck` (tsc over the JS sources) in CI, with a
       strict-mode ratchet: per-file strict diagnostics may not grow past the
       pre-merge commit (2006 at introduction, 1735 now)
  #401 docker workflow actions bumped

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants