From db617e8573c9e5390f43a9f9d732275b2a205c5e Mon Sep 17 00:00:00 2001 From: dallascrilley Date: Tue, 1 Sep 2026 16:50:12 -0500 Subject: [PATCH 1/3] keep settings routing state live MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard received routing once at launch (options.routing) and never re-read it, so Settings kept showing the launch-time snapshot for the whole session. Routing actually lives in the harness config files and can change while the dashboard is open — tokenmaxx install/uninstall from another shell, first-login auto-enable, or the daemon's post-update heal — leaving Settings contradicting the on-disk config (e.g. showing codex routing off while model_provider still points at the proxy). reload() now re-reads installStatus() alongside piStatus(), so the 2s tick and manual refresh both bring the display back to the files' truth, and the routing toggle flips from the live value instead of the stale snapshot. --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- src/tui/dashboard.ts | 19 ++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52ee4e4..0b0d7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +- [2026-09-01] keep settings routing state live + +The dashboard received routing once at launch (options.routing) and never +re-read it, so Settings kept showing the launch-time snapshot for the whole +session. Routing actually lives in the harness config files and can change +while the dashboard is open — tokenmaxx install/uninstall from another +shell, first-login auto-enable, or the daemon's post-update heal — leaving +Settings contradicting the on-disk config (e.g. showing codex routing off +while model_provider still points at the proxy). + +reload() now re-reads installStatus() alongside piStatus(), so the 2s tick +and manual refresh both bring the display back to the files' truth, and the +routing toggle flips from the live value instead of the stale snapshot. - [2026-08-18] meter clients that hang up early - [2026-08-18] reclaim bare provider tables - [2026-08-18] settings shows pi diff --git a/package.json b/package.json index 04a0258..7ec8ed3 100644 --- a/package.json +++ b/package.json @@ -59,5 +59,5 @@ "post-commit": "bun x @rubriclab/package post-commit" }, "type": "module", - "version": "0.0.65" + "version": "0.0.66" } diff --git a/src/tui/dashboard.ts b/src/tui/dashboard.ts index 61209ad..4cebbfb 100644 --- a/src/tui/dashboard.ts +++ b/src/tui/dashboard.ts @@ -1,5 +1,11 @@ import { Box, createCliRenderer, parseColor, type RGBA, Text } from '@opentui/core' -import { installPiConfig, type PiStatus, piStatus, uninstallPiConfig } from '../config-install.ts' +import { + installPiConfig, + installStatus, + type PiStatus, + piStatus, + uninstallPiConfig +} from '../config-install.ts' import type { Account, AnalyticsSnapshot, @@ -1347,6 +1353,11 @@ export async function runTuiDashboard( : buildScenario(fixture.name, simulatedNow) let rows = orderedRows(analytics.snapshot) let pi: PiStatus = live ? await piStatus() : { present: true, routed: true } + // Routing is derived from the harness config files, which can change while + // this dashboard is open (tokenmaxx install/uninstall from another shell, + // first-login auto-enable, daemon heal after an update). options.routing is + // only the launch-time snapshot; reload() keeps this current. + let routing = options.routing const state: ViewState = { addConfirm: null, alert: options.alert ?? '', @@ -1387,7 +1398,7 @@ export async function runTuiDashboard( columns, now: live ? Date.now() : simulatedNow, pi, - routing: options.routing, + routing, rows: process.stdout.rows ?? 24, switchFlagMs: fixture !== undefined && fixture.timewarp > 0 ? 24 * 60_000 : 120_000, theme: currentTheme(), @@ -1436,6 +1447,8 @@ export async function runTuiDashboard( analytics = await readAnalytics(socketPath) rows = orderedRows(analytics.snapshot) pi = await piStatus() + const status = await installStatus() + routing = { anthropic: status.claudeRouted, openai: status.codexRouted } clampSelection() }) @@ -1573,7 +1586,7 @@ export async function runTuiDashboard( } const toggleRouting = (provider: ProviderId) => { - finish({ enable: !options.routing[provider], kind: 'routing', provider }) + finish({ enable: !routing[provider], kind: 'routing', provider }) } const adjustSetting = (delta: number) => { From 008b7297c177f2aa122f4ec78517e931f46b47fb Mon Sep 17 00:00:00 2001 From: dallascrilley Date: Tue, 1 Sep 2026 16:59:30 -0500 Subject: [PATCH 2/3] installStatus reads routed state when Bun.TOML cannot parse the config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bun.TOML.parse rejects bare table keys that start with a digit — a real-world example is [mcp_servers.1password], which codex itself writes and accepts. On any config containing such a table, installStatus() threw into its catch and reported codexRouted = false no matter what the file actually said. The visible damage: the dashboard's Settings page showed codex routing off while model_provider = "tokenmaxx" was actively sending traffic through the proxy, and pressing the routing toggle computed enable from that wrong false and re-installed the managed block instead of removing it — turning routing off through the dashboard was impossible on such a config. On parse failure, fall back to detecting our own active selection line (commented-out lines never match). The semantic TOML check stays the primary path for configs that parse. --- CHANGELOG.md | 20 ++++++++++++++++++-- package.json | 2 +- src/config-install.test.ts | 21 +++++++++++++++++++++ src/config-install.ts | 7 ++++++- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b0d7de..262d357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,20 @@ -- [2026-09-01] keep settings routing state live +- [2026-09-01] installStatus reads routed state when Bun.TOML cannot parse the config + +Bun.TOML.parse rejects bare table keys that start with a digit — a real-world +example is [mcp_servers.1password], which codex itself writes and accepts. +On any config containing such a table, installStatus() threw into its catch +and reported codexRouted = false no matter what the file actually said. + +The visible damage: the dashboard's Settings page showed codex routing off +while model_provider = "tokenmaxx" was actively sending traffic through the +proxy, and pressing the routing toggle computed enable from that wrong false +and re-installed the managed block instead of removing it — turning routing +off through the dashboard was impossible on such a config. + +On parse failure, fall back to detecting our own active selection line +(commented-out lines never match). The semantic TOML check stays the primary +path for configs that parse. +- [2026-09-01] [keep settings routing state live The dashboard received routing once at launch (options.routing) and never re-read it, so Settings kept showing the launch-time snapshot for the whole @@ -10,7 +26,7 @@ while model_provider still points at the proxy). reload() now re-reads installStatus() alongside piStatus(), so the 2s tick and manual refresh both bring the display back to the files' truth, and the -routing toggle flips from the live value instead of the stale snapshot. +routing toggle flips from the live value instead of the stale snapshot.](https://github.com/dallascrilley/tokenmaxx/commit/db617e8573c9e5390f43a9f9d732275b2a205c5e) - [2026-08-18] meter clients that hang up early - [2026-08-18] reclaim bare provider tables - [2026-08-18] settings shows pi diff --git a/package.json b/package.json index 7ec8ed3..ac9da2d 100644 --- a/package.json +++ b/package.json @@ -59,5 +59,5 @@ "post-commit": "bun x @rubriclab/package post-commit" }, "type": "module", - "version": "0.0.66" + "version": "0.0.67" } diff --git a/src/config-install.test.ts b/src/config-install.test.ts index a7da7c3..7046ba7 100644 --- a/src/config-install.test.ts +++ b/src/config-install.test.ts @@ -89,6 +89,27 @@ describe('installCodexConfig', () => { expect(status.codexStale).toBe(false) }) + test('installStatus stays truthful when Bun.TOML cannot parse the config', async () => { + // Bun.TOML rejects bare table keys that start with a digit, like + // [mcp_servers.1password]; codex accepts them. A parse failure must not + // read as "not routed" while our selection line is active. + await writeCodexConfig( + `${legacyBrokenConfig}\n[mcp_servers.1password]\ncommand = "1password-mcp"\nenabled = false\n` + ) + await installCodexConfig(paths()) + expect(() => Bun.TOML.parse(legacyBrokenConfig)).not.toThrow() + const status = await installStatus() + expect(status.codexRouted).toBe(true) + }) + + test('an unparseable config without our selection still reads as not routed', async () => { + await writeCodexConfig( + 'model = "gpt-5.6-sol"\n\n[mcp_servers.1password]\ncommand = "1password-mcp"\n' + ) + const status = await installStatus() + expect(status.codexRouted).toBe(false) + }) + test('reinstall is idempotent', async () => { await writeCodexConfig(legacyBrokenConfig) await installCodexConfig(paths()) diff --git a/src/config-install.ts b/src/config-install.ts index fb10149..d57d7f4 100644 --- a/src/config-install.ts +++ b/src/config-install.ts @@ -196,7 +196,12 @@ export async function installStatus(): Promise { const baseUrl = selected === null ? undefined : parsed.model_providers?.[selected]?.base_url codexRouted = typeof baseUrl === 'string' && baseUrl.includes('127.0.0.1') } catch { - codexRouted = false + // Bun.TOML rejects configs codex accepts — bare table keys starting with a + // digit, like [mcp_servers.1password]. Reading that as "not routed" makes + // the dashboard show routing off while traffic flows through the proxy, + // and turns the routing toggle into a re-install. Fall back to detecting + // our own active selection line; commented-out lines never match. + codexRouted = codexRaw.split('\n').some(line => ownProviderSelection.test(line)) } const codexStale = !codexRouted && From f6acf054241a04fb3b4285db506a6b2473fa1c04 Mon Sep 17 00:00:00 2001 From: dallascrilley Date: Tue, 1 Sep 2026 17:52:47 -0500 Subject: [PATCH 3/3] scope the installStatus fallback to top-level keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parse-failure fallback matched our selection line anywhere in the file, but a model_provider under a table belongs to that table: a legacy selection swallowed into [notice], or a [profiles.x] naming our provider while another is active, would read as routed — and healInstalledConfigs would re-enable routing from that. Scan only the region before the first table header. Also pin that the installed fixture genuinely breaks Bun.TOML, so the regression test keeps exercising the fallback if bun's parser improves. --- CHANGELOG.md | 13 +++++++++++-- package.json | 2 +- src/config-install.test.ts | 22 +++++++++++++++++++++- src/config-install.ts | 9 ++++++--- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 262d357..68f7ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -- [2026-09-01] installStatus reads routed state when Bun.TOML cannot parse the config +- [2026-09-01] scope the installStatus fallback to top-level keys + +The parse-failure fallback matched our selection line anywhere in the file, +but a model_provider under a table belongs to that table: a legacy selection +swallowed into [notice], or a [profiles.x] naming our provider while another +is active, would read as routed — and healInstalledConfigs would re-enable +routing from that. Scan only the region before the first table header. +Also pin that the installed fixture genuinely breaks Bun.TOML, so the +regression test keeps exercising the fallback if bun's parser improves. +- [2026-09-01] [installStatus reads routed state when Bun.TOML cannot parse the config Bun.TOML.parse rejects bare table keys that start with a digit — a real-world example is [mcp_servers.1password], which codex itself writes and accepts. @@ -13,7 +22,7 @@ off through the dashboard was impossible on such a config. On parse failure, fall back to detecting our own active selection line (commented-out lines never match). The semantic TOML check stays the primary -path for configs that parse. +path for configs that parse.](https://github.com/dallascrilley/tokenmaxx/commit/008b7297c177f2aa122f4ec78517e931f46b47fb) - [2026-09-01] [keep settings routing state live The dashboard received routing once at launch (options.routing) and never diff --git a/package.json b/package.json index ac9da2d..aab76fc 100644 --- a/package.json +++ b/package.json @@ -59,5 +59,5 @@ "post-commit": "bun x @rubriclab/package post-commit" }, "type": "module", - "version": "0.0.67" + "version": "0.0.68" } diff --git a/src/config-install.test.ts b/src/config-install.test.ts index 7046ba7..4fca51e 100644 --- a/src/config-install.test.ts +++ b/src/config-install.test.ts @@ -97,7 +97,8 @@ describe('installCodexConfig', () => { `${legacyBrokenConfig}\n[mcp_servers.1password]\ncommand = "1password-mcp"\nenabled = false\n` ) await installCodexConfig(paths()) - expect(() => Bun.TOML.parse(legacyBrokenConfig)).not.toThrow() + const written = await readCodexConfig() + expect(() => Bun.TOML.parse(written)).toThrow() const status = await installStatus() expect(status.codexRouted).toBe(true) }) @@ -110,6 +111,25 @@ describe('installCodexConfig', () => { expect(status.codexRouted).toBe(false) }) + test('the fallback ignores a swallowed legacy selection under a table', async () => { + // legacyBrokenConfig's model_provider = "tokmax" sits under [notice], so + // codex never routes through it; the digit table only breaks parsing. + await writeCodexConfig( + `${legacyBrokenConfig}\n[mcp_servers.1password]\ncommand = "1password-mcp"\n` + ) + const status = await installStatus() + expect(status.codexRouted).toBe(false) + expect(status.codexStale).toBe(true) + }) + + test('the fallback ignores our provider named inside a codex profile', async () => { + await writeCodexConfig( + 'model_provider = "ollama"\n\n[profiles.work]\nmodel_provider = "tokenmaxx"\n\n[mcp_servers.1password]\ncommand = "1password-mcp"\n' + ) + const status = await installStatus() + expect(status.codexRouted).toBe(false) + }) + test('reinstall is idempotent', async () => { await writeCodexConfig(legacyBrokenConfig) await installCodexConfig(paths()) diff --git a/src/config-install.ts b/src/config-install.ts index d57d7f4..033a999 100644 --- a/src/config-install.ts +++ b/src/config-install.ts @@ -199,9 +199,12 @@ export async function installStatus(): Promise { // Bun.TOML rejects configs codex accepts — bare table keys starting with a // digit, like [mcp_servers.1password]. Reading that as "not routed" makes // the dashboard show routing off while traffic flows through the proxy, - // and turns the routing toggle into a re-install. Fall back to detecting - // our own active selection line; commented-out lines never match. - codexRouted = codexRaw.split('\n').some(line => ownProviderSelection.test(line)) + // and turns the routing toggle into a re-install. Fall back to our own + // active selection line, scanning only the top-level region: a + // model_provider line under a table belongs to that table, not to codex. + const firstTable = codexRaw.search(/^\[/m) + const topLevel = firstTable === -1 ? codexRaw : codexRaw.slice(0, firstTable) + codexRouted = topLevel.split('\n').some(line => ownProviderSelection.test(line)) } const codexStale = !codexRouted &&