mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-22 05:15:13 +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.
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import type { FC } from 'react'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { adapterLabel } from '@/entrypoints/app/agents/AdapterIcon'
|
|
import type { HarnessAgent } from '@/entrypoints/app/agents/agent-harness-types'
|
|
import { AgentSummaryChips } from '@/entrypoints/app/agents/agent-row/AgentSummaryChips'
|
|
import { AgentTile } from '@/entrypoints/app/agents/agent-row/AgentTile'
|
|
import type { AgentAdapterHealth } from '@/entrypoints/app/agents/agent-row/agent-row.types'
|
|
import { PinToggle } from '@/entrypoints/app/agents/agent-row/PinToggle'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
interface AgentRailRowProps {
|
|
agent: HarnessAgent
|
|
active: boolean
|
|
adapterHealth: AgentAdapterHealth | null
|
|
onSelect: () => void
|
|
onPinToggle: (next: boolean) => void
|
|
}
|
|
|
|
/**
|
|
* Compact rail row for the chat-screen sidebar. Slims `<AgentRowCard>`
|
|
* down to the essentials that fit a ~280 px rail: tile + name + status
|
|
* badge + pin star, with the adapter / model / reasoning chips on a
|
|
* second line. Token totals, sparkline, last-message preview all stay
|
|
* on the `/agents` page where rows are full-width.
|
|
*/
|
|
export const AgentRailRow: FC<AgentRailRowProps> = ({
|
|
agent,
|
|
active,
|
|
adapterHealth,
|
|
onSelect,
|
|
onPinToggle,
|
|
}) => {
|
|
const status = agent.status ?? 'unknown'
|
|
const lastUsedAt = agent.lastUsedAt ?? null
|
|
const pinned = agent.pinned ?? false
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onSelect}
|
|
className={cn(
|
|
'group w-full rounded-2xl border px-3 py-3 text-left transition-colors',
|
|
active
|
|
? 'border-[var(--accent-orange)]/30 bg-[var(--accent-orange)]/8'
|
|
: 'border-transparent bg-transparent hover:border-border/60 hover:bg-card',
|
|
)}
|
|
>
|
|
<div className="flex min-w-0 items-start gap-3">
|
|
<AgentTile
|
|
adapter={agent.adapter}
|
|
status={status}
|
|
lastUsedAt={lastUsedAt}
|
|
/>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="truncate font-semibold text-[14px] leading-5">
|
|
{agent.name}
|
|
</span>
|
|
{status === 'working' && (
|
|
<Badge
|
|
variant="secondary"
|
|
className="h-5 bg-amber-50 px-1.5 text-[10px] text-amber-900 hover:bg-amber-50"
|
|
>
|
|
Working
|
|
</Badge>
|
|
)}
|
|
{status === 'asleep' && (
|
|
<Badge
|
|
variant="outline"
|
|
className="h-5 px-1.5 text-[10px] text-muted-foreground"
|
|
>
|
|
Asleep
|
|
</Badge>
|
|
)}
|
|
{status === 'error' && (
|
|
<Badge variant="destructive" className="h-5 px-1.5 text-[10px]">
|
|
Attention
|
|
</Badge>
|
|
)}
|
|
<div className="ml-auto">
|
|
<PinToggle pinned={pinned} onToggle={onPinToggle} />
|
|
</div>
|
|
</div>
|
|
<AgentSummaryChips
|
|
adapter={agent.adapter}
|
|
modelLabel={agent.modelId ?? null}
|
|
reasoningEffort={agent.reasoningEffort ?? null}
|
|
adapterHealth={adapterHealth}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Tooltip-only label helper kept exported in case the tile row needs to
|
|
* show "Codex agent" or similar in a future state. Inlined fallback for
|
|
* the rare `unknown` adapter rendering path.
|
|
*/
|
|
export function railRowAdapterLabel(agent: HarnessAgent): string {
|
|
return adapterLabel(agent.adapter)
|
|
}
|