Replace the footer's placeholder usage with a real observation ledger

The footer showed invented percentages. It now shows what two harnesses
actually report, or says it does not know.

lumbridge-core gains an append-only per-profile UsageLedger and a projection
that labels every derived value estimated, withholds a burn rate from a single
sample, withholds a window fraction with no reported ceiling, withholds an
exhaustion estimate that lands after the reset, and reports an expired window
as rolled over rather than freezing its last percentage. A missing fact renders
as missing, never as zero. (0012)

lumbridge-harness is the impure side: processes, clocks, and untrusted wire
text in, observations out. Three adapters:

- Codex's account/rateLimits/read over the app-server's JSON-RPC stdio. The
  client cannot express a request outside a two-variant enum and answers every
  server-to-client request with -32601, so a harness asking Lumbridge for a
  credential is refused by construction. (0013)
- Claude Code's session transcripts, as a byte-offset tail follower that
  reports nothing until the backlog is read to EOF — a partially-read backlog
  is indistinguishable from a burst of spend, and the first run against 20 MB
  reported forty-six billion tokens an hour. The parser models four counters,
  so the conversations in those files are not representable. (0014)
- Claude Code's five-hour and seven-day subscription windows, via a bridge
  installed as its statusLine command. 0014 had claimed no such surface
  existed; it does, and the record is corrected in place rather than quietly
  edited. Lumbridge does not read the OAuth credential to call the account
  usage endpoint, which is what comparable tools do — AGENTS.md forbids it,
  and 0015 says so rather than leaving the gap unexplained.

Also in here: a capability-check ordering fix in the workspace reducer, where
the applied-request replay table was consulted before the capability check and
so answered questions the caller had no right to ask; the GPUI spike wired to
the live probes with per-harness gauges and provenance chips; and a launcher
that matches its own window by PID, because GPUI sets WM_NAME but not
_NET_WM_NAME and a title match never succeeded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Metal Agent
2026-08-31 21:47:11 -07:00
co-authored by Claude Opus 5
parent 7fe84f71e2
commit ef52aa7ce2
34 changed files with 7319 additions and 137 deletions
+559 -101
View File
@@ -1,4 +1,5 @@
mod panel_registry;
mod usage_feed;
use std::collections::{BTreeMap, VecDeque};
use std::path::PathBuf;
@@ -8,13 +9,14 @@ use gpui::{
App, Application, Bounds, Context, FocusHandle, FontWeight, KeyBinding, KeyDownEvent, Pixels,
Size, Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, relative, rgb, size,
};
use lumbridge_core::UsageProvenance;
use lumbridge_runtime::{
CommandConfig, PtyOptions, RuntimeActorError, RuntimeActorOptions, RuntimeCommand,
RuntimeEvent, RuntimeRegistry, RuntimeRegistryError, TerminalSize,
};
use lumbridge_spike_model::{
ActionOutcome, FOOTER_RIGHT, OutputSource, PaneId as FixturePaneId, PaneStatus, ShellAction,
ShellModel, SurfaceKind, WORKSPACES,
ActionOutcome, OutputSource, PaneId as FixturePaneId, PaneStatus, ShellAction, ShellModel,
SurfaceKind, WORKSPACES,
};
use lumbridge_storage::Store;
use lumbridge_terminal::{
@@ -24,6 +26,7 @@ use lumbridge_terminal::{
};
use panel_registry::{PanelId, PanelKind, PanelRegistry, SeedPane};
use usage_feed::{UsageFeed, UsageSegment};
const BG: u32 = 0x090c12;
const PANEL: u32 = 0x101620;
@@ -43,7 +46,7 @@ const WORKSPACE_SNAPSHOT_ID: &str = "lumbridge-code-gpui-spike";
const SIDEBAR_WIDTH: f32 = 248.0;
const APP_HEADER_HEIGHT: f32 = 48.0;
const TAB_BAR_HEIGHT: f32 = 38.0;
const APP_FOOTER_HEIGHT: f32 = 32.0;
const APP_FOOTER_HEIGHT: f32 = 46.0;
const TERMINAL_CONTENT_VERTICAL_INSET: f32 = 24.0;
const TERMINAL_HORIZONTAL_INSET: f32 = 32.0;
const TERMINAL_CELL_WIDTH: f32 = 8.4;
@@ -84,10 +87,90 @@ struct LumbridgeShell {
live_terminals: BTreeMap<PanelId, LiveTerminalState>,
store: Option<Store>,
persistence_status: String,
usage: UsageFeed,
/// Which surface tab each panel is showing. A panel is absent until the
/// user picks something other than the surface it provides natively.
surfaces: BTreeMap<PanelId, SurfaceTab>,
/// Which decision-shelf choice each panel has selected. Selecting one is
/// inert by design: it prepares nothing and runs nothing.
shelf_choice: BTreeMap<PanelId, usize>,
active_worktree: usize,
add_panel_chooser_open: bool,
root_focus: FocusHandle,
}
/// The surfaces a pane can be viewed through.
///
/// A pane is the durable unit of work and its surface is a view over that work.
/// Switching one never launches a process, moves the pane, or changes which
/// agent owns the session — it only changes what is drawn.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SurfaceTab {
Terminal,
Browser,
Tools,
Context,
Goal,
Review,
}
impl SurfaceTab {
const ALL: [Self; 6] = [
Self::Terminal,
Self::Browser,
Self::Tools,
Self::Context,
Self::Goal,
Self::Review,
];
const fn label(self) -> &'static str {
match self {
Self::Terminal => "TERMINAL",
Self::Browser => "BROWSER",
Self::Tools => "TOOLS",
Self::Context => "CONTEXT",
Self::Goal => "GOAL",
Self::Review => "REVIEW",
}
}
const fn ordinal(self) -> u64 {
match self {
Self::Terminal => 0,
Self::Browser => 1,
Self::Tools => 2,
Self::Context => 3,
Self::Goal => 4,
Self::Review => 5,
}
}
/// The one surface a pane of this kind actually provides.
const fn native_for(kind: SurfaceKind) -> Self {
match kind {
SurfaceKind::Terminal => Self::Terminal,
SurfaceKind::Browser => Self::Browser,
SurfaceKind::Markdown => Self::Context,
SurfaceKind::Review => Self::Review,
}
}
/// Why this pane cannot show this surface. Stated plainly, because the
/// product boundary says an unsupported surface is shown as unavailable
/// rather than simulated.
const fn unavailable_reason(self) -> &'static str {
match self {
Self::Terminal => "Only a terminal pane owns a live shell.",
Self::Browser => "No isolated web engine is wired yet.",
Self::Tools => "Tool calls arrive with the ACP client.",
Self::Context => "Context projection is not built yet.",
Self::Goal => "Goal tracking is not built yet.",
Self::Review => "Review is not wired to Git yet.",
}
}
}
#[derive(Clone)]
struct PanelView {
id: PanelId,
@@ -488,6 +571,8 @@ impl LumbridgeShell {
if this
.update(cx, |shell, cx| {
shell.dispatch(ShellAction::SyntheticStreamTick);
shell.usage.advance();
shell.usage.tick();
cx.notify();
})
.is_err()
@@ -554,6 +639,10 @@ impl LumbridgeShell {
live_terminals,
store,
persistence_status,
usage: UsageFeed::start(),
surfaces: BTreeMap::new(),
shelf_choice: BTreeMap::new(),
active_worktree: 0,
add_panel_chooser_open: false,
root_focus,
}
@@ -778,6 +867,54 @@ impl LumbridgeShell {
cx.notify();
}
/// Changes which surface a pane is viewed through.
///
/// This is a view change only. It never touches the PTY, the agent, or the
/// pane's execution target, so a pane keeps running whatever it was running.
fn select_surface(
&mut self,
pane: PanelId,
tab: SurfaceTab,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.timing.mark_dispatch();
self.surfaces.insert(pane, tab);
if self.panels.select(pane) {
self.persist_panels();
}
window.focus(&self.root_focus);
cx.notify();
}
/// Records which suggestion the user picked. Deliberately does nothing else.
fn select_shelf_choice(
&mut self,
pane: PanelId,
index: usize,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.timing.mark_dispatch();
if self.shelf_choice.get(&pane) == Some(&index) {
self.shelf_choice.remove(&pane);
} else {
self.shelf_choice.insert(pane, index);
}
if self.panels.select(pane) {
self.persist_panels();
}
window.focus(&self.root_focus);
cx.notify();
}
fn select_worktree(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
self.timing.mark_dispatch();
self.active_worktree = index.min(WORKSPACES.len().saturating_sub(1));
window.focus(&self.root_focus);
cx.notify();
}
fn select_pane_at(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
self.timing.mark_dispatch();
if self.panels.select_attached_at(index) {
@@ -1215,26 +1352,38 @@ impl LumbridgeShell {
}
},
);
let surface = match pane.kind {
SurfaceKind::Terminal => "TERMINAL",
SurfaceKind::Markdown => "CONTEXT",
SurfaceKind::Browser => "BROWSER",
SurfaceKind::Review => "REVIEW",
};
let tabs = ["TERMINAL", "BROWSER", "TOOLS", "CONTEXT", "GOAL", "REVIEW"]
let native = SurfaceTab::native_for(pane.kind);
let shown = self.surface_for(pane.id, pane.kind);
let tabs = SurfaceTab::ALL
.into_iter()
.map(|label| {
.map(|tab| {
let active = tab == shown;
let provided = tab == native;
div()
.id(("surface-tab", pane_id.get() * 16 + tab.ordinal()))
.cursor_pointer()
.h_full()
.flex()
.items_center()
.px_3()
.text_xs()
.text_color(rgb(if label == surface { ACCENT } else { MUTED }))
.when(label == surface, |view| {
view.border_b_2().border_color(rgb(ACCENT))
})
.child(label)
// Provided but unselected reads as available; unprovided
// stays dim, so the tab strip shows what this pane can do
// before you click rather than after.
.text_color(rgb(if active {
ACCENT
} else if provided {
TEXT
} else {
MUTED
}))
.when(active, |view| view.border_b_2().border_color(rgb(ACCENT)))
.hover(|view| view.bg(rgb(PANEL_ACTIVE)).text_color(rgb(TEXT)))
.child(tab.label())
.on_click(cx.listener(move |shell, _, window, cx| {
cx.stop_propagation();
shell.select_surface(pane_id, tab, window, cx);
}))
})
.collect::<Vec<_>>();
@@ -1304,6 +1453,12 @@ impl LumbridgeShell {
.border_color(rgb(BORDER))
.text_xs()
.text_color(rgb(if can_detach { MUTED } else { BORDER }))
.when(can_detach, |view| {
view.hover(|view| {
view.border_color(rgb(ATTENTION))
.text_color(rgb(ATTENTION))
})
})
.child(if can_detach {
" DETACH"
} else {
@@ -1347,7 +1502,67 @@ impl LumbridgeShell {
.into_any_element()
}
/// The surface a panel is currently being viewed through.
fn surface_for(&self, id: PanelId, kind: SurfaceKind) -> SurfaceTab {
self.surfaces
.get(&id)
.copied()
.unwrap_or_else(|| SurfaceTab::native_for(kind))
}
/// What a pane shows when asked for a surface it does not provide.
///
/// It says so, and it says what is still running underneath, because the
/// pane's process identity does not change when the view does.
fn unavailable_surface(&self, pane: &PanelView, shown: SurfaceTab) -> gpui::AnyElement {
let native = SurfaceTab::native_for(pane.kind);
div()
.flex()
.flex_col()
.size_full()
.min_w_0()
.min_h_0()
.overflow_hidden()
.bg(rgb(BG))
.border_y_1()
.border_color(rgb(BORDER))
.child(
div()
.flex()
.flex_col()
.gap_2()
.p_4()
.child(
div()
.text_sm()
.text_color(rgb(TEXT))
.child(format!("{} unavailable", shown.label())),
)
.child(
div()
.text_xs()
.text_color(rgb(MUTED))
.child(shown.unavailable_reason()),
)
.child(
div()
.mt_2()
.text_xs()
.text_color(rgb(SUCCESS))
.child(format!(
"{} is still this pane's live surface. Nothing was stopped.",
native.label()
)),
),
)
.into_any_element()
}
fn pane_work_surface(&self, pane: &PanelView) -> gpui::AnyElement {
let shown = self.surface_for(pane.id, pane.kind);
if shown != SurfaceTab::native_for(pane.kind) {
return self.unavailable_surface(pane, shown);
}
let external = pane.output_source == OutputSource::External;
let content = if external {
self.terminal_view(pane.id)
@@ -1387,35 +1602,22 @@ impl LumbridgeShell {
.into_any_element()
}
fn pane_decision_region(&self, pane: &PanelView) -> gpui::AnyElement {
let choice = |label: &'static str, detail: &'static str, attention: bool| {
div()
.flex()
.items_center()
.justify_between()
.min_w_0()
.px_2()
.py_1()
.rounded(px(4.0))
.border_1()
.border_color(rgb(if attention { ATTENTION } else { BORDER }))
.bg(rgb(PANEL_ALT))
.text_xs()
.text_color(rgb(TEXT))
.child(label)
.child(div().truncate().text_color(rgb(MUTED)).child(detail))
};
let choices = if pane.needs_input() {
fn pane_decision_region(&self, pane: &PanelView, cx: &mut Context<Self>) -> gpui::AnyElement {
let pane_id = pane.id;
let picked = self.shelf_choice.get(&pane_id).copied();
// Every choice names the capability it would need. None of them holds
// that capability: choosing prepares, the command plane executes.
let choices: [(&'static str, &'static str, &'static str, bool); 3] = if pane.needs_input() {
[
("Review request", "inspect scope", true),
("Steer…", "edit response", false),
("Dismiss", "leave inert", false),
("Review request", "inspect scope", "needs Observe", true),
("Steer…", "edit response", "needs Execute", false),
("Dismiss", "leave inert", "inert", false),
]
} else {
[
("Continue", "keep moving", false),
("Review plan", "inspect commands", false),
("Ask…", "refine prompt", false),
("Continue", "keep moving", "needs Execute", false),
("Review plan", "inspect commands", "needs Observe", false),
("Ask…", "refine prompt", "inert", false),
]
};
div()
@@ -1447,8 +1649,55 @@ impl LumbridgeShell {
),
)
.child(div().flex().flex_col().gap_1().mt_2().children(
choices.map(|(label, detail, attention)| choice(label, detail, attention)),
choices.into_iter().enumerate().map(
|(index, (label, detail, _capability, attention))| {
let chosen = picked == Some(index);
div()
.id(("shelf-choice", pane_id.get() * 8 + index as u64))
.cursor_pointer()
.flex()
.items_center()
.justify_between()
.min_w_0()
.px_2()
.py_1()
.rounded(px(4.0))
.border_1()
.border_color(rgb(if chosen {
ACCENT
} else if attention {
ATTENTION
} else {
BORDER
}))
.bg(rgb(if chosen { PANEL_ACTIVE } else { PANEL_ALT }))
.hover(|view| view.border_color(rgb(ACCENT)))
.text_xs()
.text_color(rgb(TEXT))
.child(label)
.child(div().truncate().text_color(rgb(MUTED)).child(detail))
.on_click(cx.listener(move |shell, _, window, cx| {
cx.stop_propagation();
shell.select_shelf_choice(pane_id, index, window, cx);
}))
},
),
))
.child(
div()
.mt_2()
.text_xs()
.text_color(rgb(if picked.is_some() { ACCENT } else { MUTED }))
.child(picked.map_or_else(
|| "Pick one to see what it would need".to_owned(),
|index| {
format!(
"{} · {} · nothing has run",
choices[index].0, choices[index].2
)
},
)),
)
.into_any_element()
}
@@ -1461,6 +1710,7 @@ impl LumbridgeShell {
let pane_id = pane.id;
div()
.id(("workspace-panel", pane_id.get()))
.cursor_pointer()
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_pane(pane_id, window, cx);
}))
@@ -1494,7 +1744,7 @@ impl LumbridgeShell {
.flex_none()
.min_h_0()
.overflow_hidden()
.child(self.pane_decision_region(pane)),
.child(self.pane_decision_region(pane, cx)),
)
.into_any_element()
}
@@ -1795,6 +2045,209 @@ impl LumbridgeShell {
),
)
}
/// Attached panels whose agent is blocked on a human answer.
///
/// The count is derived rather than declared: a sidebar that claims one
/// pane needs input while no pane is blocked is the same class of untruth
/// as an invented usage number.
fn attention_panels(&self) -> Vec<PanelView> {
self.panels
.attached_ids()
.into_iter()
.map(|id| self.panel_view(id))
.filter(PanelView::needs_input)
.collect()
}
/// Where sessions are owned, and what each host is actually doing.
fn runtime_rows(&self) -> Vec<RuntimeRow> {
let running = self
.live_terminals
.values()
.filter(|terminal| matches!(terminal.status, LiveRuntimeStatus::Running { .. }))
.count();
let total = self.live_terminals.len();
let local = RuntimeRow {
label: "metal",
detail: format!("{running}/{total} live PTYs"),
tone: if running == 0 { MUTED } else { SUCCESS },
};
// Remote runtimes are a Phase 1 deliverable. Until the SSH stdio
// transport exists, the only honest status for a saved host is that
// nothing is connected to it.
let remote = RuntimeRow {
label: "amd-server",
detail: "saved host · no runtime connected".to_owned(),
tone: MUTED,
};
// The usage adapter is neither a host nor an endpoint, but its health
// belongs beside them: it is the reason the footer can or cannot answer.
let adapter = RuntimeRow {
label: "usage adapter",
detail: format!(
"{} · {}/{} profiles reporting",
self.usage.probe_status(),
self.usage.reporting_profile_count(),
self.usage.declared_profile_count()
),
tone: if self.usage.reporting_profile_count() == 0 {
MUTED
} else {
SUCCESS
},
};
vec![local, remote, adapter]
}
/// The standing of every harness, always, regardless of what is selected.
///
/// The old footer answered only for the selected pane, so the moment you
/// focused a plain shell your quota vanished. This strip keeps every
/// harness on screen and leads with what is left, because that is the
/// question. The selected harness expands with its rate and its trust.
fn footer_usage_zone(&self) -> impl IntoElement {
let active = self
.panels
.panel(self.panels.selected())
.and_then(|panel| self.usage.profile_for_seed(panel.seed))
.cloned();
let segments = self.usage.strip();
let orphan = active.is_none();
div()
.flex()
.items_center()
.gap_4()
.when(orphan, |view| {
view.child(
div()
.flex_none()
.text_color(rgb(MUTED))
.child("no harness on this pane"),
)
})
.children(segments.into_iter().map(|segment| {
let selected = active.as_ref() == Some(&segment.id);
usage_segment(segment, selected)
}))
}
}
/// The provenance of a value is carried by the colour of its meter, so the
/// strip reads at a glance without a legend on every segment.
const fn provenance_color(provenance: UsageProvenance) -> u32 {
match provenance {
UsageProvenance::ProviderReported => SUCCESS,
UsageProvenance::HarnessReported => ACCENT,
UsageProvenance::LocallyMeasured => TEXT,
UsageProvenance::Estimated => ATTENTION,
UsageProvenance::Unavailable => MUTED,
}
}
/// A quota meter. The filled part is spent; the empty part is what is left,
/// which is the way round an engineer reads it.
fn usage_meter(consumed_permille: Option<u64>, color: u32) -> impl IntoElement {
let Some(permille) = consumed_permille else {
// No capsule. A full-length empty gauge reads as "plenty left" from
// across the room, which is the opposite of the truth. A hairline is
// visibly not a measurement.
return div()
.w(px(80.0))
.h(px(2.0))
.flex_none()
.bg(rgb(BORDER))
.into_any_element();
};
let clamped = u16::try_from(permille.min(1_000)).unwrap_or(1_000);
div()
.w(px(80.0))
.h(px(8.0))
.flex_none()
.rounded(px(4.0))
.bg(rgb(BORDER_QUIET))
.overflow_hidden()
.child(
div()
.h_full()
.w(relative(f32::from(clamped) / 1_000.0))
.bg(rgb(color)),
)
.into_any_element()
}
fn usage_segment(segment: UsageSegment, selected: bool) -> impl IntoElement {
let color = provenance_color(segment.provenance);
let name_color = if selected { TEXT } else { MUTED };
div()
.flex()
.items_center()
.gap_2()
.when(selected, |view| {
view.px_2().py_1().rounded(px(4.0)).bg(rgb(PANEL_ACTIVE))
})
.child(
div()
.flex_none()
.text_color(rgb(name_color))
.child(segment.label),
)
.child(usage_meter(segment.consumed_permille, color))
.child(
div()
.flex_none()
.text_color(rgb(if segment.headline.is_some() {
TEXT
} else {
MUTED
}))
.child(segment.headline.unwrap_or_else(|| "no reading".to_owned())),
)
.when_some(segment.reset, |view, reset| {
view.child(div().flex_none().text_color(rgb(MUTED)).child(reset))
})
// Only the harness you are looking at spends footer width on its rate
// and its trust. The rest stay one glance wide.
.when(selected, |view| {
view.child(footer_separator())
.child(
div()
.flex_none()
.text_color(rgb(provenance_color(segment.burn_provenance)))
.child(segment.burn),
)
.child(provenance_chip(&segment.trust, segment.provenance))
})
}
/// One host or endpoint row in the sidebar's runtime list.
struct RuntimeRow {
label: &'static str,
detail: String,
tone: u32,
}
fn footer_separator() -> impl IntoElement {
div().text_color(rgb(BORDER)).child("")
}
/// The trust marker. Its colour is the provenance, never the value.
fn provenance_chip(text: &str, provenance: UsageProvenance) -> impl IntoElement {
let color = match provenance {
UsageProvenance::ProviderReported => SUCCESS,
UsageProvenance::HarnessReported => ACCENT,
UsageProvenance::LocallyMeasured => TEXT,
UsageProvenance::Estimated => ATTENTION,
UsageProvenance::Unavailable => MUTED,
};
div()
.px_2()
.py(px(1.0))
.rounded(px(3.0))
.border_1()
.border_color(rgb(color))
.text_color(rgb(color))
.child(text.to_owned())
}
impl Render for LumbridgeShell {
@@ -1827,6 +2280,7 @@ impl Render for LumbridgeShell {
"{running_runtime_count}/{} LIVE PTYS",
self.live_terminals.len()
);
let attention_panels = self.attention_panels();
let sidebar = div()
.flex()
.flex_col()
@@ -1841,11 +2295,30 @@ impl Render for LumbridgeShell {
.pt_4()
.pb_2()
.text_xs()
.text_color(rgb(ATTENTION))
.child("ATTENTION · 1"),
.text_color(rgb(if attention_panels.is_empty() {
MUTED
} else {
ATTENTION
}))
.child(format!("ATTENTION · {}", attention_panels.len())),
)
.child(
.when(attention_panels.is_empty(), |view| {
view.child(
div()
.mx_2()
.mb_3()
.px_3()
.py_2()
.text_xs()
.text_color(rgb(MUTED))
.child("No pane is waiting on you"),
)
})
.children(attention_panels.into_iter().map(|pane| {
let id = pane.id;
div()
.id(("attention-panel", id.get()))
.cursor_pointer()
.mx_2()
.mb_3()
.px_3()
@@ -1854,20 +2327,19 @@ impl Render for LumbridgeShell {
.bg(rgb(PANEL_ACTIVE))
.border_1()
.border_color(rgb(ATTENTION))
.child(
div()
.text_sm()
.text_color(rgb(TEXT))
.child("Claude Code · UI"),
)
.hover(|view| view.bg(rgb(PANEL_ALT)))
.child(div().text_sm().text_color(rgb(TEXT)).child(pane.title))
.child(
div()
.mt_1()
.text_xs()
.text_color(rgb(MUTED))
.child("Waiting for a split decision"),
),
)
.child(pane.target),
)
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_pane(id, window, cx);
}))
}))
.child(
div()
.px_4()
@@ -1877,18 +2349,20 @@ impl Render for LumbridgeShell {
.child("WORKTREES"),
)
.children(WORKSPACES.into_iter().enumerate().map(|(index, name)| {
let active = index == self.active_worktree;
div()
.id(("worktree", index as u64))
.cursor_pointer()
.mx_2()
.mb_1()
.px_3()
.py_2()
.rounded(px(5.0))
.when(index == 0, |view| {
view.bg(rgb(PANEL_ALT)).text_color(rgb(TEXT))
})
.when(index != 0, |view| view.text_color(rgb(MUTED)))
.when(active, |view| view.bg(rgb(PANEL_ALT)).text_color(rgb(TEXT)))
.when(!active, |view| view.text_color(rgb(MUTED)))
.hover(|view| view.bg(rgb(PANEL_ACTIVE)).text_color(rgb(TEXT)))
.child(name)
.when(index == 0, |view| {
.when(active, |view| {
view.child(
div()
.mt_1()
@@ -1897,6 +2371,9 @@ impl Render for LumbridgeShell {
.child("main · metal"),
)
})
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_worktree(index, window, cx);
}))
}))
.child(
div()
@@ -1907,30 +2384,13 @@ impl Render for LumbridgeShell {
.text_color(rgb(MUTED))
.child("RUNTIMES"),
)
.child(
.children(self.runtime_rows().into_iter().map(|row| {
div()
.px_4()
.py_1()
.text_sm()
.text_color(rgb(SUCCESS))
.child(format!("metal · {runtime_summary}")),
)
.child(
div()
.px_4()
.py_1()
.text_sm()
.text_color(rgb(MUTED))
.child("amd-server · connected"),
)
.child(
div()
.px_4()
.py_1()
.text_sm()
.text_color(rgb(MUTED))
.child("spark-1 · sleeping"),
)
.child(div().text_sm().text_color(rgb(row.tone)).child(row.label))
.child(div().text_xs().text_color(rgb(MUTED)).child(row.detail))
}))
.child(
div()
.px_4()
@@ -1959,6 +2419,7 @@ impl Render for LumbridgeShell {
.rounded(px(4.0))
.border_1()
.border_color(rgb(BORDER_QUIET))
.hover(|view| view.border_color(rgb(ACCENT)).text_color(rgb(TEXT)))
.text_xs()
.text_color(rgb(MUTED))
.child(format!("{title}"))
@@ -1994,6 +2455,8 @@ impl Render for LumbridgeShell {
.bg(rgb(PANEL))
.border_b_1()
.border_color(rgb(BORDER_QUIET))
// One tab, because there is one workspace. Two more used to sit
// here looking like navigation and doing nothing.
.child(
div()
.h_full()
@@ -2003,21 +2466,7 @@ impl Render for LumbridgeShell {
.border_b_2()
.border_color(rgb(ACCENT))
.text_sm()
.child("Agent workspace"),
)
.child(
div()
.px_3()
.text_sm()
.text_color(rgb(MUTED))
.child("Architecture.md"),
)
.child(
div()
.px_3()
.text_sm()
.text_color(rgb(MUTED))
.child("Review"),
.child(WORKSPACES[self.active_worktree]),
)
.child(div().flex_1())
.child(div().px_3().text_xs().text_color(rgb(MUTED)).child(format!(
@@ -2145,7 +2594,7 @@ impl Render for LumbridgeShell {
.ml_3()
.text_sm()
.text_color(rgb(MUTED))
.child("Lumbridge Code / main"),
.child(format!("{} / main", WORKSPACES[self.active_worktree])),
)
.child(div().flex_1())
.child(
@@ -2180,17 +2629,26 @@ impl Render for LumbridgeShell {
.flex()
.items_center()
.justify_between()
.h(px(32.0))
.h(px(APP_FOOTER_HEIGHT))
.flex_none()
.px_3()
.gap_4()
.bg(rgb(PANEL_ALT))
.border_t_1()
.border_color(rgb(BORDER_QUIET))
.text_xs()
.text_color(rgb(MUTED))
.child(footer_left)
.child(timing)
.child(FOOTER_RIGHT),
.child(
div()
.flex()
.flex_col()
.flex_none()
.min_w_0()
.child(div().truncate().child(footer_left))
.child(div().truncate().child(timing)),
)
.child(div().flex_1())
.child(self.footer_usage_zone()),
)
.when(self.model.command_palette().is_open(), |view| {
view.child(self.command_palette())
+555
View File
@@ -0,0 +1,555 @@
//! Footer usage for the spike, fed by real probes through the real ledger.
//!
//! Profiles are *declared*; numbers are not. A profile exists as soon as the
//! shell knows a pane is running a harness, so the footer can name the account
//! it cannot read. Observations only ever come from a [`UsageProbe`]. A harness
//! with no adapter yet therefore renders the honest-gap path under its own
//! name — "Claude Code · Anthropic · … usage unavailable" — rather than
//! borrowing another pane's number or showing a zero.
//!
//! The only adapter that exists today is the Codex quota probe. It is started
//! at launch unless `LUMBRIDGE_CODEX_PROBE=0` is set, and if the `codex` binary
//! is absent the probe simply fails to start and its profiles keep rendering as
//! unavailable.
use std::collections::BTreeMap;
use lumbridge_core::{
AccountProfile, AccountProfileId, FooterUsage, UsageLedger, UsageProjection, UsageProvenance,
format_duration_ms,
};
use lumbridge_harness::claude::{ClaudeCodeProbe, ClaudeCodeProbeOptions};
use lumbridge_harness::codex::{CodexProbe, CodexProbeOptions};
use lumbridge_harness::{MonotonicWallClock, ProbeHealth, UsageProbe};
use crate::panel_registry::SeedPane;
/// Harnesses the seeded panes run, whether or not an adapter exists for them.
struct DeclaredProfile {
seed: SeedPane,
id: &'static str,
harness: &'static str,
provider: &'static str,
model: &'static str,
account: &'static str,
}
/// The Codex pane maps onto the probe's own primary-window profile, so a live
/// reading lands under the pane that produced it. The rest are declarations
/// with no adapter behind them yet.
const DECLARED: &[DeclaredProfile] = &[
DeclaredProfile {
seed: SeedPane::CodexRuntime,
id: "codex-app-server-primary",
harness: "Codex",
provider: "ChatGPT",
model: "primary window",
account: "subscription",
},
// The Claude pane maps to the five-hour window, not the token total: it is
// the limit that actually stops work, and it is what "how much do I have
// left" means. The transcript total is still declared by the probe and
// still shows in the strip once it reports.
DeclaredProfile {
seed: SeedPane::ClaudeUi,
id: "claude-code-five-hour",
harness: "Claude Code",
provider: "Anthropic",
model: "five-hour window",
account: "subscription",
},
DeclaredProfile {
seed: SeedPane::PiDocs,
id: "spark-1-local",
harness: "Pi",
provider: "spark-1",
model: "laguna-s-2.1",
account: "self-hosted",
},
];
pub(crate) struct UsageFeed {
ledger: UsageLedger,
clock: MonotonicWallClock,
profiles: BTreeMap<AccountProfileId, AccountProfile>,
seeds: Vec<(SeedPane, AccountProfileId)>,
probes: Vec<Box<dyn UsageProbe>>,
health: BTreeMap<AccountProfileId, ProbeHealth>,
probe_status: String,
}
impl UsageFeed {
pub(crate) fn start() -> Self {
let mut profiles = BTreeMap::new();
let mut seeds = Vec::new();
for declared in DECLARED {
let Ok(profile) = AccountProfile::new(
declared.id,
declared.harness,
declared.provider,
declared.model,
declared.account,
) else {
continue;
};
seeds.push((declared.seed, profile.id().clone()));
profiles.insert(profile.id().clone(), profile);
}
let mut feed = Self {
ledger: UsageLedger::new(),
clock: MonotonicWallClock::start(),
profiles,
seeds,
probes: Vec::new(),
health: BTreeMap::new(),
probe_status: "no usage adapter running".to_owned(),
};
feed.start_codex_probe();
feed.start_claude_probe();
feed
}
/// Starts the Codex quota probe unless the user opted out.
///
/// A failure here is not an error state for the shell: the profiles stay
/// declared and render as unavailable, which is exactly what they should do
/// when no adapter can run.
fn start_codex_probe(&mut self) {
if std::env::var_os("LUMBRIDGE_CODEX_PROBE").is_some_and(|value| value == "0") {
self.probe_status = "codex probe disabled".to_owned();
return;
}
match CodexProbe::start(CodexProbeOptions::default()) {
Ok(probe) => {
for profile in probe.profiles() {
self.profiles
.entry(profile.id().clone())
.or_insert_with(|| profile.clone());
self.health
.insert(profile.id().clone(), ProbeHealth::Starting);
}
self.probes.push(Box::new(probe));
self.probe_status = "codex probe starting".to_owned();
}
Err(error) => {
self.probe_status = format!("codex probe unavailable · {error}");
}
}
}
/// Starts the Claude Code transcript probe unless the user opted out.
///
/// This one reads files rather than launching anything, so it has no
/// harness to fail to find — a missing projects directory simply reports
/// nothing until Claude Code creates it.
fn start_claude_probe(&mut self) {
if std::env::var_os("LUMBRIDGE_CLAUDE_PROBE").is_some_and(|value| value == "0") {
return;
}
match ClaudeCodeProbe::start(ClaudeCodeProbeOptions::default()) {
Ok(probe) => {
for profile in probe.profiles() {
self.profiles
.entry(profile.id().clone())
.or_insert_with(|| profile.clone());
self.health
.insert(profile.id().clone(), ProbeHealth::Starting);
}
self.probes.push(Box::new(probe));
}
Err(error) => {
self.probe_status = format!("claude probe unavailable · {error}");
}
}
}
/// Which declared profile a seeded panel belongs to.
///
/// Panels created at runtime are plain shells with no harness, so they map
/// to nothing rather than borrowing another pane's account.
pub(crate) fn profile_for_seed(&self, seed: Option<SeedPane>) -> Option<&AccountProfileId> {
let seed = seed?;
self.seeds
.iter()
.find(|(candidate, _)| *candidate == seed)
.map(|(_, id)| id)
}
/// Drains every probe into the ledger. Returns whether anything changed.
pub(crate) fn tick(&mut self) -> bool {
let mut changed = false;
for probe in &mut self.probes {
let outcome = probe.poll();
let health = outcome.health();
for profile in probe.profiles() {
if self.health.insert(profile.id().clone(), health) != Some(health) {
changed = true;
}
}
for observation in outcome.into_observations() {
// A rejected observation is a real signal, not a crash: the
// ledger refuses anything that would move a profile's stream
// backwards. Dropping it preserves the append-only invariant.
if self.ledger.record(observation).is_ok() {
changed = true;
}
}
}
if changed {
self.probe_status = self.summarize_health();
}
changed
}
fn summarize_health(&self) -> String {
if self.probes.is_empty() {
return "no usage adapter running".to_owned();
}
let ready = self
.health
.values()
.filter(|health| matches!(health, ProbeHealth::Ready))
.count();
let faulted = self
.health
.values()
.filter(|health| health.is_faulted())
.count();
if faulted > 0 {
return format!("{} probes · {faulted} faulted", self.probes.len());
}
format!("{} probes · {ready} ready", self.probes.len())
}
pub(crate) fn probe_status(&self) -> &str {
&self.probe_status
}
fn projection(&self, id: &AccountProfileId) -> UsageProjection {
self.ledger.project(id, self.clock.last_emitted_ms())
}
/// Advances the feed's read clock. Kept separate from [`Self::tick`] so
/// rendering never mutates the clock mid-frame.
pub(crate) fn advance(&mut self) {
let _ = self.clock.now_ms();
}
/// Every harness the footer should account for, in a stable order.
///
/// The seeded panes come first so the strip does not reorder itself as
/// readings arrive, then any probe profile that has actually reported. A
/// probe profile with nothing to say — Codex's secondary window on an
/// account that has none — is left out rather than shown as an empty rail,
/// because the strip is for harnesses the user is running, not for every
/// row a probe could theoretically fill.
pub(crate) fn strip(&self) -> Vec<UsageSegment> {
let mut ordered: Vec<&AccountProfileId> = self.seeds.iter().map(|(_, id)| id).collect();
for id in self.profiles.keys() {
if !ordered.contains(&id) && self.projection(id).is_available() {
ordered.push(id);
}
}
let mut segments: Vec<UsageSegment> = ordered
.into_iter()
.filter_map(|id| self.segment(id))
.collect();
// Two windows on one account would otherwise both read "CODEX". A
// strip that names two different quotas the same thing is worse than
// a longer label.
let duplicated: Vec<String> = segments
.iter()
.filter(|segment| {
segments
.iter()
.filter(|other| other.label == segment.label)
.count()
> 1
})
.map(|segment| segment.label.clone())
.collect();
for segment in &mut segments {
if duplicated.contains(&segment.label) {
segment.label = format!("{} {}", segment.label, segment.model.to_uppercase());
}
}
segments
}
fn segment(&self, id: &AccountProfileId) -> Option<UsageSegment> {
let profile = self.profiles.get(id)?;
let projection = self.projection(id);
let footer = FooterUsage::new(profile, &projection);
let consumed_permille = projection.consumed_permille();
Some(UsageSegment {
id: id.clone(),
label: profile.harness().to_uppercase(),
model: profile.model().to_owned(),
consumed_permille,
// "How much do I have left" is the question an engineer actually
// asks. Consumption stays available in the expanded detail.
// With a ceiling, lead with what is left. Without one, the honest
// headline is what was spent — a profile that reports real
// consumption but no quota must not read as "no reading".
//
// Show a decimal only when the value actually has one. Codex
// reports whole percents, so "81.0% left" would claim a tenth of a
// percent of resolution that no one measured.
headline: consumed_permille
.map(|permille| {
let left = 1_000_u64.saturating_sub(permille);
if left % 10 == 0 {
format!("{}% left", left / 10)
} else {
format!("{}.{}% left", left / 10, left % 10)
}
})
.or_else(|| {
let consumed = projection.consumed()?;
let unit = projection.unit()?;
Some(format!("{} used", unit.format_amount(consumed)))
}),
// A profile that reports spend but no ceiling and no reset should
// say so where the reset would go, rather than leaving a silent
// gap that reads as "we just haven't shown it yet".
reset: projection.resets_in_ms().map_or_else(
|| {
(projection.is_available() && projection.limit().is_none())
.then(|| "no quota reported".to_owned())
},
|remaining| Some(format!("resets {}", format_duration_ms(remaining))),
),
burn: footer.burn(),
burn_provenance: projection.burn_provenance(),
trust: footer.trust(),
provenance: projection.provenance(),
})
}
/// Profiles that have produced at least one usable reading.
pub(crate) fn reporting_profile_count(&self) -> usize {
self.profiles
.keys()
.filter(|id| self.projection(id).is_available())
.count()
}
pub(crate) fn declared_profile_count(&self) -> usize {
self.profiles.len()
}
pub(crate) fn shutdown(&mut self) {
for probe in &mut self.probes {
probe.shutdown();
}
self.probes.clear();
}
}
impl Drop for UsageFeed {
fn drop(&mut self) {
self.shutdown();
}
}
/// One harness's standing in the footer strip.
///
/// `remaining` and `reset` are `None` when there is nothing to report. That is
/// deliberately not an empty string: the renderer has to decide what a gap
/// looks like rather than printing a blank where a number belongs.
pub(crate) struct UsageSegment {
pub(crate) id: AccountProfileId,
pub(crate) label: String,
pub(crate) model: String,
pub(crate) consumed_permille: Option<u64>,
/// What to lead with: how much is left when a ceiling is known, how much
/// was spent when it is not, and nothing at all when there is no reading.
pub(crate) headline: Option<String>,
pub(crate) reset: Option<String>,
pub(crate) burn: String,
pub(crate) burn_provenance: UsageProvenance,
pub(crate) trust: String,
pub(crate) provenance: UsageProvenance,
}
#[cfg(test)]
mod tests {
use super::{DECLARED, UsageFeed};
use crate::panel_registry::SeedPane;
use lumbridge_core::UsageProvenance;
/// Never starts a probe, so the test cannot depend on an installed harness,
/// on the user's transcripts, or on a status-line bridge being present.
fn declared_only_feed() -> UsageFeed {
// SAFETY-FREE: this only sets environment variables for this process.
unsafe {
std::env::set_var("LUMBRIDGE_CODEX_PROBE", "0");
std::env::set_var("LUMBRIDGE_CLAUDE_PROBE", "0");
}
UsageFeed::start()
}
#[test]
fn a_declared_harness_with_no_adapter_shows_a_gap_under_its_own_name() {
let feed = declared_only_feed();
let segment = feed
.strip()
.into_iter()
.find(|segment| segment.label.starts_with("CLAUDE CODE"))
.expect("Claude Code is declared");
assert!(
segment.headline.is_none(),
"a harness with no adapter must not show a number"
);
assert_eq!(segment.trust, "no usage source");
}
#[test]
fn panels_without_a_seed_have_no_profile() {
let feed = declared_only_feed();
assert!(feed.profile_for_seed(None).is_none());
assert!(
feed.profile_for_seed(Some(SeedPane::Architecture))
.is_none()
);
}
#[test]
fn nothing_reports_until_a_probe_produces_a_reading() {
let feed = declared_only_feed();
assert_eq!(feed.declared_profile_count(), DECLARED.len());
assert_eq!(
feed.reporting_profile_count(),
0,
"declaring a profile must not imply a reading"
);
}
#[test]
fn the_codex_pane_maps_onto_the_probe_profile_id() {
let feed = declared_only_feed();
let id = feed
.profile_for_seed(Some(SeedPane::CodexRuntime))
.expect("the Codex pane declares a profile");
assert_eq!(
id.as_str(),
"codex-app-server-primary",
"a live reading must land under the pane that produced it"
);
}
#[test]
fn the_strip_lists_every_declared_harness_even_with_no_readings() {
let feed = declared_only_feed();
let strip = feed.strip();
assert_eq!(strip.len(), DECLARED.len());
let labels: Vec<&str> = strip.iter().map(|segment| segment.label.as_str()).collect();
assert_eq!(labels, ["CODEX", "CLAUDE CODE", "PI"]);
for segment in &strip {
assert!(
segment.headline.is_none() && segment.reset.is_none(),
"a harness with no adapter must report no figure at all"
);
assert!(!segment.provenance.is_available());
assert_eq!(segment.provenance, UsageProvenance::Unavailable);
}
}
#[test]
fn a_fractional_reading_keeps_its_decimal() {
use lumbridge_core::{UsageObservation, UsageUnit, UsageWindow};
let mut feed = declared_only_feed();
let id = feed
.profile_for_seed(Some(SeedPane::CodexRuntime))
.expect("declared")
.clone();
feed.advance();
let now_ms = feed.clock.last_emitted_ms();
feed.ledger
.record(
UsageObservation::counted(
id.clone(),
UsageUnit::WindowPermille,
185,
UsageProvenance::ProviderReported,
now_ms.saturating_sub(1_000),
)
.expect("available")
.with_limit(1_000)
.expect("a positive limit")
.with_window(UsageWindow::until(now_ms + 3_600_000))
.expect("inside the window"),
)
.expect("ordered");
let segment = feed
.strip()
.into_iter()
.find(|segment| segment.id == id)
.expect("present");
assert_eq!(segment.headline.as_deref(), Some("81.5% left"));
}
#[test]
fn a_segment_reports_what_is_left_not_what_was_used() {
use lumbridge_core::{UsageObservation, UsageUnit, UsageWindow};
let mut feed = declared_only_feed();
let id = feed
.profile_for_seed(Some(SeedPane::CodexRuntime))
.expect("the Codex pane declares a profile")
.clone();
// The feed reads a real wall clock, so the fixture has to sit inside a
// window that is still open now rather than at an arbitrary epoch.
feed.advance();
let now_ms = feed.clock.last_emitted_ms();
feed.ledger
.record(
UsageObservation::counted(
id.clone(),
UsageUnit::WindowPermille,
180,
UsageProvenance::ProviderReported,
now_ms.saturating_sub(1_000),
)
.expect("available")
.with_limit(1_000)
.expect("a positive limit")
.with_window(UsageWindow::until(now_ms + 3_600_000))
.expect("inside the window"),
)
.expect("ordered");
let segment = feed
.strip()
.into_iter()
.find(|segment| segment.id == id)
.expect("the Codex segment is present");
assert_eq!(
segment.headline.as_deref(),
Some("82% left"),
"a whole-percent source must not render a tenth it never measured"
);
assert_eq!(segment.consumed_permille, Some(180));
assert_eq!(
segment.reset.as_deref(),
Some("resets 1h 0m"),
"a windowed reading reports its reset"
);
}
#[test]
fn a_segment_with_no_reading_offers_no_figure_to_render() {
let feed = declared_only_feed();
for segment in feed.strip() {
assert!(segment.headline.is_none());
assert!(segment.reset.is_none());
assert!(
!segment.burn.contains("/hr"),
"a rate needs readings this segment does not have"
);
assert_eq!(segment.consumed_permille, None);
}
}
}