mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-22 13:25:14 +00:00
* feat(agent): rich rail + header on /agents/:agentId chat Replace the chat screen's legacy AgentEntry rail and binary READY header with the same rich data the /agents page already exposes: adapter glyph, liveness dot, pin star, status badge, adapter · model · reasoning chip line, last-used time, lifetime tokens, queue count, and the Adapter Unavailable warning. Source of truth flips from the merged AgentEntry list to useHarnessAgents() directly. Sort order matches /agents (pinned → recency) — not /home (active-first → recency) — because chat is index-shaped and shuffling rows every 5s as turns transition would be jarring while reading. Lift the inline pin-then-recency comparator out of /agents AgentList.tsx into a shared agents-list-order.ts so both surfaces stay on identical sort semantics. * fix(agent): chat header height + composer sticking to bottom Header was clipping descenders because the strip was vertical-content sized at min-h-14 with tight py-2.5; bump padding and lean on natural content height. Drop the AgentTile glyph (the rail row already shows adapter identity) and the cwd path (too long, pushed the meta line off-screen). Header is now name + pin star + status pill, then adapter · model · reasoning, then last-used · tokens · queued. Composer was floating mid-screen on short chats because the chat grid had no grid-template-rows — the implicit auto row collapsed to content height, so the right-column flex wrapper never received the full container height. Add grid-rows-[minmax(0,1fr)] so the single row claims 100% and ClawChat's flex-1 expands to push the composer flush to the bottom. * fix(agent): composer flush to bottom on short chats Match the sidepanel chat's nested-flex pattern. The right-column wrapper got h-full so it expands to the grid row; the conversation controller's root added flex-1 so ClawChat's existing flex-1 has something to actually fill against. Without these, the grid cell stretched but the inner flex columns shrank to content height, leaving the composer floating mid-screen. * fix(agent): align rail header with chat header in shared top band Pull the rail's "Agents" + back-button into the same horizontal strip as the agent identity header. The two halves now sit on a single row that spans both columns, so they can't drift in height as the chat header gains/loses meta lines (last-used, tokens, queued). The rail below the band keeps its scrollable list only; the chat column below holds the conversation + composer. Border-bottom moves from ConversationHeader to the band wrapper so we don't get a double-rule on the boundary. * fix(agent): reserve header height to prevent layout shift on data load The chat header grew from a single line to three lines once the useHarnessAgents() poll resolved (adapter chips + meta line populate asynchronously), shoving the rail and conversation body downward. Lock min-h-[84px] on both the band's left "Agents" cell and the ConversationHeader root, and always render the meta line slot (non-breaking space when empty) so the typographic frame is stable regardless of data state. * refactor(agent): pull status pill + meta to right side of chat header Two-column header layout instead of three stacked rows: name + pin star + adapter chips on the left, status pill stacked on top of the last-used / tokens / queued meta line on the right. Drops min-h from 84px → 60px so the band reclaims ~24px of vertical space and the chat body starts higher on screen. Band's left "Agents" cell matches the new height.
66 lines
2.1 KiB
TypeScript
66 lines
2.1 KiB
TypeScript
import { type FC, useMemo } from 'react'
|
|
import type {
|
|
HarnessAdapterDescriptor,
|
|
HarnessAgent,
|
|
HarnessAgentAdapter,
|
|
} from '@/entrypoints/app/agents/agent-harness-types'
|
|
import type { AgentAdapterHealth } from '@/entrypoints/app/agents/agent-row/agent-row.types'
|
|
import { orderAgentsByPinThenRecency } from '@/entrypoints/app/agents/agents-list-order'
|
|
import { AgentRailRow } from './AgentRailRow'
|
|
|
|
interface AgentRailProps {
|
|
agents: HarnessAgent[]
|
|
adapters: HarnessAdapterDescriptor[]
|
|
activeAgentId: string
|
|
onSelectAgent: (agent: HarnessAgent) => void
|
|
onPinToggle: (agent: HarnessAgent, next: boolean) => void
|
|
}
|
|
|
|
/**
|
|
* Left-column scrollable list of agents. The "Agents" label + back
|
|
* button live in the shared top band above (so the rail header and
|
|
* the chat header sit on a single aligned strip rather than as two
|
|
* separately-sized headers per column). Sort matches `/agents`:
|
|
* pinned-first → recency, so the rail doesn't reshuffle as turns
|
|
* transition every 5 s.
|
|
*/
|
|
export const AgentRail: FC<AgentRailProps> = ({
|
|
agents,
|
|
adapters,
|
|
activeAgentId,
|
|
onSelectAgent,
|
|
onPinToggle,
|
|
}) => {
|
|
const adapterHealth = useMemo(() => {
|
|
const map = new Map<HarnessAgentAdapter, AgentAdapterHealth>()
|
|
for (const adapter of adapters) {
|
|
if (adapter.health) {
|
|
map.set(adapter.id, {
|
|
healthy: adapter.health.healthy,
|
|
reason: adapter.health.reason,
|
|
})
|
|
}
|
|
}
|
|
return map
|
|
}, [adapters])
|
|
|
|
const ordered = useMemo(() => orderAgentsByPinThenRecency(agents), [agents])
|
|
|
|
return (
|
|
<aside className="hidden min-h-0 flex-col border-border/50 border-r bg-background/70 lg:flex">
|
|
<div className="styled-scrollbar min-h-0 flex-1 space-y-1.5 overflow-y-auto px-3 py-3">
|
|
{ordered.map((agent) => (
|
|
<AgentRailRow
|
|
key={agent.id}
|
|
agent={agent}
|
|
active={agent.id === activeAgentId}
|
|
adapterHealth={adapterHealth.get(agent.adapter) ?? null}
|
|
onSelect={() => onSelectAgent(agent)}
|
|
onPinToggle={(next) => onPinToggle(agent, next)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|