Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
- [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.
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.](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
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.](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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@
"post-commit": "bun x @rubriclab/package post-commit"
},
"type": "module",
"version": "0.0.65"
"version": "0.0.68"
}
41 changes: 41 additions & 0 deletions src/config-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,47 @@ 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())
const written = await readCodexConfig()
expect(() => Bun.TOML.parse(written)).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('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())
Expand Down
10 changes: 9 additions & 1 deletion src/config-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,15 @@ export async function installStatus(): Promise<InstallStatus> {
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 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 &&
Expand Down
19 changes: 16 additions & 3 deletions src/tui/dashboard.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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 ?? '',
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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()
})

Expand Down Expand Up @@ -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) => {
Expand Down