Files
LLeMbas-CLI/src/tui/components/status.tsx
T
HomerandClaude Fable 5.1 2183040db8 Harness spec 2.0.0: background work is a job — commands and subagents alike, seen and steered
One registry per process, every job owned by a session: bash's background commands (and a command
still running at its timeout, which now goes to the background instead of being killed) and
subagents started with task background: true. job_list, job_output, job_stop (the former
bash_list, bash_output, bash_kill) and the new job_send and report; a finished job wakes the owning
session in every front end with its marked note. The TUI counts jobs, lists them (/jobs), opens a
command's output, enters a subagent and messages it, moves a command to the background (ctrl+b)
and asks before quitting with jobs running. The link gains the jobs capability. Subagents get a
session row of their own, the spec's preamble and agent texts, and their errors reach the report.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-20 16:17:00 +00:00

70 lines
3.9 KiB
TypeScript

import { basename } from "node:path"
import { Show } from "solid-js"
import { isLocal } from "../../config/paths.ts"
import { useTui } from "../context.ts"
const MODE_LABEL = { manual: "manual", edit: "edit", auto: "AUTO", plan: "plan" } as const
export function fmtTokens(n: number): string {
return n >= 10_000 ? `${Math.round(n / 1000)}k` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)
}
/** The status bar's word for the session's jobs: the running count, `!` when one ended unseen or
* holds output the model has not read; nothing while there is nothing to say. */
export function jobsWord(jobs: { state: string; unread: number; unseen?: boolean }[]): string {
const running = jobs.filter((j) => j.state === "running").length
const news = jobs.some((j) => j.unseen || j.unread > 0)
if (!running && !news) return ""
return `${running} job${running === 1 ? "" : "s"}${news ? "!" : ""}`
}
/** One line: mode · model · effort on the left; the jobs, context use and where on the right — or,
* for a moment, a flash (a copy) in their place. The jobs word sits on the right, which is never
* truncated: at 80 columns a long project name cuts the left short, and a running job is the one
* thing that must stay visible. */
export function StatusBar(p: { mode: () => string; model: () => string; effort: () => string; branch?: string; voice?: () => string; flash?: () => string | undefined; remote?: () => string; credits?: () => string }) {
const { theme, view, app } = useTui()
const c = () => theme().colors
const modeColor = () => ({ manual: c().statusText, edit: c().statusGood, auto: c().statusCritical, plan: c().info })[p.mode() as keyof typeof MODE_LABEL] ?? c().statusText
const ctx = () => {
const u = view.state.usage
if (!u) return { text: "ctx —", color: c().statusDim }
const used = view.state.used ?? u.input + u.output
const total = view.state.context
if (!total) return { text: `ctx ${fmtTokens(used)}${u.estimated ? "~" : ""}`, color: c().statusText }
const pct = Math.round((used / total) * 100)
const color = pct >= 90 ? c().statusCritical : pct >= 75 ? c().statusBad : pct >= 50 ? c().statusWarn : c().statusGood
return { text: `ctx ${fmtTokens(used)}${u.estimated ? "~" : ""}/${fmtTokens(total)} ${pct}%`, color }
}
return (
<box height={1} flexDirection="row" justifyContent="space-between" backgroundColor={c().panel} paddingLeft={1} paddingRight={1}>
<text wrapMode="none" truncate>
<span style={{ fg: modeColor() }}>{MODE_LABEL[p.mode() as keyof typeof MODE_LABEL] ?? p.mode()}</span>
<span style={{ fg: c().statusDim }}> · </span>
<span style={{ fg: c().statusStrong }}>{p.model()}</span>
<span style={{ fg: c().statusDim }}>{p.effort() ? ` · ${p.effort()}` : ""}</span>
<span style={{ fg: p.voice?.().startsWith("●") ? c().statusCritical : c().statusText }}>{p.voice?.() ? ` · ${p.voice()}` : ""}</span>
<span style={{ fg: c().info }}>{p.remote?.() ? ` · ${p.remote()}` : ""}</span>
</text>
<Show
when={p.flash?.()}
fallback={
<text wrapMode="none">
<span style={{ fg: jobsWord(view.state.jobs).endsWith("!") ? c().statusWarn : c().statusText }}>{jobsWord(view.state.jobs) ? `${jobsWord(view.state.jobs)} · ` : ""}</span>
<span style={{ fg: c().statusDim }}>{view.state.rate ? `${Math.round(view.state.rate)} tok/s · ` : ""}</span>
<span style={{ fg: c().statusText }}>{p.credits?.() ? `${p.credits()} · ` : ""}</span>
<span style={{ fg: ctx().color }}>{ctx().text}</span>
<span style={{ fg: c().statusDim }}> · </span>
<span style={{ fg: c().info }}>{isLocal() ? "local " : ""}</span>
<span style={{ fg: c().statusDim }}>{basename(app.project.root)}{p.branch ? `@${p.branch}` : ""}</span>
</text>
}
>
<text wrapMode="none" fg={c().statusGood}>
{p.flash!()}
</text>
</Show>
</box>
)
}