@workerdeck/ui 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/{SessionPanel-CnNYEX80.d.mts → SessionPanel-CnU_IJ3-.d.mts} +24 -9
- package/build/{SessionPanel-DMPhsNlW.mjs → SessionPanel-DPx8Iz8a.mjs} +439 -74
- package/build/SessionPanel-DPx8Iz8a.mjs.map +1 -0
- package/build/{format-DfI_je9S.d.mts → format-ljc3lKpA.d.mts} +1 -1
- package/build/format.d.mts +16 -5
- package/build/format.mjs +2 -3
- package/build/index.d.mts +237 -8
- package/build/index.mjs +399 -167
- package/build/index.mjs.map +1 -1
- package/build/{format-DqR56Y8l.mjs → status-BE-zg88x.mjs} +154 -2
- package/build/status-BE-zg88x.mjs.map +1 -0
- package/build/workspace.d.mts +4 -1
- package/build/workspace.mjs +4 -3
- package/build/workspace.mjs.map +1 -1
- package/package.json +4 -4
- package/src/components/agent/ContextRing.tsx +41 -0
- package/src/components/agent/EngineIcon.tsx +40 -0
- package/src/components/agent/SessionBrowser.tsx +115 -22
- package/src/components/agent/SessionPanel.tsx +153 -3
- package/src/components/agent/SessionSteps.tsx +233 -0
- package/src/components/agent/SessionWorkspace.tsx +4 -0
- package/src/components/agent/StatusBar.tsx +4 -2
- package/src/components/agent/SubagentStrip.tsx +134 -0
- package/src/components/agent/Transcript.tsx +138 -19
- package/src/components/agent/transcript-rows.ts +9 -1
- package/src/components/terminal/TerminalTranscript.tsx +77 -3
- package/src/components/terminal/affordances.tsx +34 -0
- package/src/components/terminal/blocks.ts +28 -0
- package/src/components/terminal/height.ts +24 -0
- package/src/components/terminal/items.tsx +1 -1
- package/src/components/terminal/scrubber.tsx +28 -3
- package/src/components/terminal/tool-run.ts +49 -5
- package/src/index.ts +19 -1
- package/src/lib/status.ts +16 -3
- package/src/styles/terminal.css +10 -0
- package/src/styles/theme.css +42 -0
- package/build/SessionPanel-DMPhsNlW.mjs.map +0 -1
- package/build/format-DqR56Y8l.mjs.map +0 -1
- package/build/status-Ydzi7n6j.mjs +0 -143
- package/build/status-Ydzi7n6j.mjs.map +0 -1
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["AlertDialogPrimitive","SonnerToaster","Map"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/ui/Empty.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/terminal/StatusLine.tsx","../src/components/agent/SessionList.tsx","../src/components/agent/ProjectIcon.tsx","../src/components/agent/SessionBrowser.tsx","../src/components/agent/EngineIcon.tsx"],"sourcesContent":["import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","import type { ReactNode } from 'react'\nimport { Button } from './Button.tsx'\n\n/**\n * A view with nothing in it: icon, title, description, and at most one action.\n *\n * Centered and given room, because an empty view is the first thing a new\n * install shows and a flush-left sentence in the top corner reads like a bug\n * report. It lives here rather than in one client because every panel that can\n * be empty should be empty the same way — the extension's views and the\n * dashboard's four sidebars are the same shape of thing.\n *\n * **At most one action, and never one the view header already offers.** Creating\n * a session and adding a gateway are the `+` in the native title bar, exclusively\n * — so those empty states point at it in words rather than growing a second\n * button that does the same thing two inches lower. A button here is for the way\n * out of a state the header has no answer to: clearing a filter, widening a\n * scope.\n */\nexport function Empty({\n icon,\n title,\n description,\n action,\n onAction,\n}: {\n icon: ReactNode\n title: string\n description?: ReactNode\n action?: string\n onAction?: () => void\n}) {\n return (\n <div className='flex flex-col items-center gap-2 px-5 py-8 text-center'>\n <div className='text-fg-4 opacity-60 [&_svg]:size-7'>{icon}</div>\n <p className='text-body-sm font-medium text-fg-2'>{title}</p>\n {description ? (\n <p className='max-w-[26ch] text-balance text-label leading-relaxed text-fg-4'>\n {description}\n </p>\n ) : null}\n {action && onAction ? (\n <Button variant='outline' size='sm' className='mt-1' onClick={onAction}>\n {action}\n </Button>\n ) : null}\n </div>\n )\n}\n\n/** A key or icon named inline in an empty state's description — \"use the + above\". */\nexport function EmptyKey({ children }: { children: ReactNode }) {\n return (\n <span className='rounded border border-border px-1 font-mono text-fg-3'>{children}</span>\n )\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","import type { RateLimitInfo } from '@workerdeck/protocol'\nimport type { ConnectionState, TranscriptState } from '@workerdeck/react'\nimport { formatCost, formatTokens } from '../../lib/format.ts'\nimport {\n contextSeverity,\n meterSeverity,\n modelLabel,\n statusPresentation,\n tightestWindow,\n windowLabel,\n type StatusSeverity,\n} from '../../lib/status.ts'\nimport { Ink, Row, type Tone } from './row.tsx'\n\n/**\n * The session's readings, as one line.\n *\n * The styled bar puts each of these in its own affordance — a progress ring, a\n * badge, a tooltip — which is right for a dashboard and wrong here for the usual\n * reason: a terminal has one line height, and five widgets of five different\n * heights is five different rhythms in a row that is supposed to be quiet. So\n * they are words, `·`-separated, exactly as the CLI's own status line writes\n * them.\n *\n * The readings themselves come from the same helpers the styled bar uses\n * (`lib/status.ts`), and that matters more than it looks: `statusPresentation`\n * is where the rule lives that **a dropped socket outranks the session status**,\n * because a status held over a dead socket is a stale reading being presented as\n * a live one. Re-deriving that here would be a second answer to a question that\n * already has one.\n */\n\n/** Severity → tone. The 80/95 thresholds are `lib/status.ts`'s, not new ones. */\nconst SEVERITY_TONE: Record<StatusSeverity, Tone> = {\n none: 'dim',\n warning: 'yellow',\n error: 'red',\n}\n\nexport interface TerminalStatusLineProps {\n state: TranscriptState\n /** Plan windows to read from, when they should not be the session's own — the\n * gateway's per-profile state merged over this transcript's. See\n * {@link StatusBarProps.rateLimits}. Absent = `state.rateLimits`. */\n rateLimits?: Record<string, RateLimitInfo>\n /** How the client is doing at reaching the gateway. Wins the status slot when\n * the socket is down — see above. */\n connection?: ConnectionState\n /** Opens the panel that answers each reading's own question. Omit and the\n * reading is text rather than something to press. */\n onOpenStatus?: () => void\n onOpenContext?: () => void\n onOpenUsage?: () => void\n}\n\n/** One reading. A button only when it leads somewhere — a segment that looks\n * pressable and does nothing is worse than plain text. */\nfunction Reading({\n tone,\n onPress,\n label,\n children,\n}: {\n tone?: Tone\n onPress?: () => void\n label?: string\n children: React.ReactNode\n}) {\n if (!onPress) return <Ink tone={tone}>{children}</Ink>\n return (\n <button type='button' className='term-reading' onClick={onPress} title={label}>\n <Ink tone={tone}>{children}</Ink>\n </button>\n )\n}\n\nexport function TerminalStatusLine({\n state,\n rateLimits,\n connection,\n onOpenStatus,\n onOpenContext,\n onOpenUsage,\n}: TerminalStatusLineProps) {\n const presentation = statusPresentation({ status: state.status, connection })\n const usage = state.contextUsage\n const window = tightestWindow(rateLimits ?? state.rateLimits)\n const model = modelLabel({ model: state.model, models: state.models ?? [] })\n\n const parts: React.ReactNode[] = [\n <Reading\n key='status'\n tone={SEVERITY_TONE[presentation.severity]}\n onPress={onOpenStatus}\n label='Session status'>\n {presentation.label}\n </Reading>,\n ]\n\n if (usage) {\n parts.push(\n <Reading\n key='context'\n tone={SEVERITY_TONE[contextSeverity(usage)]}\n onPress={onOpenContext}\n label='Context window'>\n {Math.round(usage.percentage)}% ctx ({formatTokens(usage.totalTokens)})\n </Reading>,\n )\n }\n\n if (window) {\n parts.push(\n <Reading\n key='usage'\n tone={SEVERITY_TONE[meterSeverity(window.info.utilization)]}\n onPress={onOpenUsage}\n label='Plan usage'>\n {Math.round(window.info.utilization ?? 0)}% {windowLabel(window.key)}\n </Reading>,\n )\n }\n\n // Cost last of the numbers: it is the reading you check, never the one you act\n // on, and nothing about a turn changes because of it.\n if (state.totalCostUsd > 0) {\n parts.push(\n <Ink key='cost' tone='faint'>\n {formatCost(state.totalCostUsd)}\n </Ink>,\n )\n }\n\n if (model) {\n parts.push(\n <Ink key='model' tone='faint'>\n {model}\n </Ink>,\n )\n }\n if (state.permissionMode && state.permissionMode !== 'default') {\n parts.push(\n <Ink key='mode' tone='yellow'>\n {state.permissionMode}\n </Ink>,\n )\n }\n\n return (\n <Row data-slot='status-line' tone='dim'>\n {parts.map((part, index) => (\n <span key={index}>\n {index > 0 ? <Ink tone='faint'> · </Ink> : null}\n {part}\n </span>\n ))}\n </Row>\n )\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n","import type { ProjectIcon as ProjectIconSpec } from '@workerdeck/protocol'\nimport {\n Anchor, Atom, Beaker, Bike, Binary, Blocks, Bot, Box, Brain, Briefcase, Brush, Bug,\n Building2, Cable, Camera, Car, Cat, CircuitBoard, Cloud, Code, Coffee, Cog, Compass,\n Container, Cpu, CreditCard, Database, Diamond, Dog, Feather, Film, Flame, FlaskConical,\n Folder, Gamepad2, Gauge, Gem, Ghost, Gift, GitBranch, Globe, GraduationCap, Hammer,\n Hexagon, House, Image, Key, Landmark, Laptop, Layers, Leaf, Library, Lightbulb, Lock,\n Mail, Map, MessageCircle, Mic, Monitor, Moon, Mountain, Music, Network, Newspaper,\n Notebook, Orbit, Package, Palette, PenTool, Phone, PiggyBank, Plane, Plug, Puzzle,\n Radar, Radio, Receipt, Rocket, Ruler, Satellite, Scale, Scissors, Server, Shield, Ship,\n ShoppingCart, Signal, Smartphone, Sparkles, Sprout, Square, Star, Sun, Table, Tag,\n Target, Telescope, Terminal, TestTube, TreePine, Trophy, Truck, Umbrella, Users, Wallet,\n Wand, Waves, Webhook, Wifi, Wrench, Zap,\n} from 'lucide-react'\nimport type { ComponentType } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * A project's icon — the render side of protocol's `ProjectIcon`, for a list\n * row or a group header.\n *\n * **The glyph arm is a curated set, and that is the design rather than a\n * shortcut.** The gateway validates a glyph name by *shape* only (lucide's\n * lowercase-kebab convention) and explicitly declines to grow an icon catalog,\n * so the contract already says a client must fall back on a name it does not\n * know.\n *\n * The alternative was measured rather than argued, against the VS Code\n * sidebar's own bundle: `sidebar.js` is **31 KB** with one glyph, **77 KB**\n * with the 110 below (~418 bytes each), and **927 KB** with a\n * `import * as` over lucide's ~1,600 — which is what it costs, because a\n * namespace import defeats tree-shaking by construction. (`DynamicIcon` is the\n * third option and worse in a different currency: ~1,600 chunk files shipped\n * inside the `.vsix`.) Twelve times the bundle to render a name nobody has\n * declared yet is not a trade worth making; 46 KB to cover the names people\n * actually use is.\n *\n * The set is chosen to cover what people call a project; an unlisted-but-valid\n * name draws {@link Folder}, which is what the row drew before this feature\n * existed. **Grow it freely** — each addition is ~400 bytes and the fallback\n * means a missing one is a shrug, not a hole.\n *\n * **The image arm draws nothing until its bytes arrive**, and takes them as a\n * resolved `src` rather than fetching: the wire carries only an address\n * (media type, size, content hash), and *who* fetches it differs per client —\n * a VS Code webview cannot reach a gateway at all and must be handed a data\n * URL by its extension host, while a browser can fetch and `createObjectURL`.\n * A component that fetched would be wrong on one of them. Absent `src` renders\n * nothing rather than a placeholder box: an icon is decoration beside a name\n * that is already there, and a box that becomes a picture a beat later is more\n * movement than the picture is worth.\n *\n * One thing an image icon cannot do, and a glyph can: **take the row's\n * colour.** An `<img>`-embedded SVG is its own document, so `currentColor` does\n * not reach it and its own `prefers-color-scheme` resolves against the *OS*\n * rather than the host's theme (measured: it does resolve, which is the\n * surprise — so a monochrome mark tuned for both schemes is right on a dark OS\n * in a dark editor and wrong on a light OS in a dark one). A repo that wants a\n * mark that always matches its row should declare a glyph; one that wants its\n * brand should declare the image and accept that it is a picture.\n */\nconst GLYPHS: Record<string, ComponentType<{ className?: string }>> = {\n anchor: Anchor, atom: Atom, beaker: Beaker, bike: Bike, binary: Binary, blocks: Blocks,\n bot: Bot, box: Box, brain: Brain, briefcase: Briefcase, brush: Brush, bug: Bug,\n 'building-2': Building2, cable: Cable, camera: Camera, car: Car, cat: Cat,\n 'circuit-board': CircuitBoard, cloud: Cloud, code: Code, coffee: Coffee, cog: Cog,\n compass: Compass, container: Container, cpu: Cpu, 'credit-card': CreditCard,\n database: Database, diamond: Diamond, dog: Dog, feather: Feather, film: Film,\n flame: Flame, 'flask-conical': FlaskConical, folder: Folder, 'gamepad-2': Gamepad2,\n gauge: Gauge, gem: Gem, ghost: Ghost, gift: Gift, 'git-branch': GitBranch, globe: Globe,\n 'graduation-cap': GraduationCap, hammer: Hammer, hexagon: Hexagon, house: House,\n image: Image, key: Key, landmark: Landmark, laptop: Laptop, layers: Layers, leaf: Leaf,\n library: Library, lightbulb: Lightbulb, lock: Lock, mail: Mail, map: Map,\n 'message-circle': MessageCircle, mic: Mic, monitor: Monitor, moon: Moon,\n mountain: Mountain, music: Music, network: Network, newspaper: Newspaper,\n notebook: Notebook, orbit: Orbit, package: Package, palette: Palette, 'pen-tool': PenTool,\n phone: Phone, 'piggy-bank': PiggyBank, plane: Plane, plug: Plug, puzzle: Puzzle,\n radar: Radar, radio: Radio, receipt: Receipt, rocket: Rocket, ruler: Ruler,\n satellite: Satellite, scale: Scale, scissors: Scissors, server: Server, shield: Shield,\n ship: Ship, 'shopping-cart': ShoppingCart, signal: Signal, smartphone: Smartphone,\n sparkles: Sparkles, sprout: Sprout, square: Square, star: Star, sun: Sun, table: Table,\n tag: Tag, target: Target, telescope: Telescope, terminal: Terminal, 'test-tube': TestTube,\n 'tree-pine': TreePine, trophy: Trophy, truck: Truck, umbrella: Umbrella, users: Users,\n wallet: Wallet, wand: Wand, waves: Waves, webhook: Webhook, wifi: Wifi, wrench: Wrench,\n zap: Zap,\n}\n\nexport function ProjectIcon({\n icon,\n src,\n name,\n className,\n}: {\n icon: ProjectIconSpec | undefined\n /** Resolved bytes for the `image` arm — an object URL or a data URL. The\n * caller fetches (see the note above); absent draws nothing. */\n src?: string\n /** The project's name, for the alt text. */\n name?: string\n className?: string\n}) {\n if (!icon) return null\n if (icon.type === 'image') {\n if (!src) return null\n return (\n <img\n src={src}\n alt=''\n aria-hidden\n title={name}\n // `object-contain` because a declared icon is whatever aspect the repo\n // checked in, and a squashed logo reads worse than a letterboxed one.\n className={cn('inline-block size-3 shrink-0 object-contain', className)}\n />\n )\n }\n const Glyph = GLYPHS[icon.name] ?? Folder\n return <Glyph className={cn('inline-block size-3 shrink-0', className)} />\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n BellRing,\n CircleAlert,\n CircleSlash,\n Layers,\n Moon,\n PauseCircle,\n Pencil,\n Search,\n SearchX,\n Trash2,\n X,\n} from 'lucide-react'\nimport {\n STATE_LABELS,\n STATE_ORDER,\n adaptersOf,\n clearFilters,\n filterRows,\n groupRows,\n hasFacetFilter,\n projectLabel,\n projectsOf,\n sessionLabel,\n subsetSummary,\n} from '@workerdeck/protocol'\nimport type {\n GroupBy,\n SessionRow,\n SessionState,\n SortBy,\n ViewConfig,\n WorkspaceScope,\n} from '@workerdeck/protocol'\nimport { Button } from '../ui/Button.tsx'\nimport { Empty } from '../ui/Empty.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue } from '../ui/Select.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { ProjectIcon } from './ProjectIcon.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime, friendlyModel } from '../../lib/format.ts'\n\n/**\n * A sessions list with the affordances a list of thirty needs: search, facets,\n * grouping, sorting, unread counts, and one honest line about what is hidden.\n *\n * The *rules* are `@workerdeck/protocol`'s (`filterRows`/`groupRows`/\n * `subsetSummary`), not this component's — the VS Code sidebar renders the same\n * model with workbench chrome, its activity-bar badge counts the same rows this\n * would show, and iOS mirrors them in Swift. What lives here is the styled\n * rendering of that model, so a host that wants the dashboard's look gets it\n * without reimplementing the model behind it.\n *\n * `SessionList` remains beside this for the plain case (a fixed set of rows, no\n * controls); this is what you reach for when the list is the screen.\n */\n\nexport interface SessionBrowserProps {\n rows: SessionRow[]\n config: ViewConfig\n onConfigChange: (config: ViewConfig) => void\n /** The host's own folders, if it has such a notion. Absent — a dashboard, a\n * phone — makes the scope filter genuinely inert rather than empty. */\n scope?: WorkspaceScope\n activeId?: string\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n /**\n * Rename, from the row's pencil. Empty string restores the derived title. A\n * gateway edit (`PATCH /sessions/:id`), never a local override — every client\n * should see the same name. Omit to make titles read-only.\n *\n * A hover affordance rather than the extension's double-click-the-title,\n * because here a single click on the row navigates: the *first* click of a\n * double-click would have already left the page.\n */\n onRename?: (row: SessionRow, title: string) => void\n /** Rendered when nothing at all exists (as opposed to nothing matching). */\n emptyState?: React.ReactNode\n /**\n * Whether the search + facet bar is shown. Defaults to `true` — a list that\n * *is* the screen shows its controls.\n *\n * A host with somewhere better to put the toggle (a view title bar) passes\n * `false` and owns the boolean itself, the way the VS Code extension does: the\n * key lives where the commands do. Two rules come with it, and they are why\n * this hides only the bar and nothing else — **closing the bar never clears\n * the filters**, and the subset line below it renders either way, so a list\n * filtered by a control you can't currently see still says so.\n */\n showControls?: boolean\n /**\n * Resolved project-icon bytes by content hash — `useProjectIcons`' output.\n *\n * Passed in rather than fetched here for the reason `ProjectIcon` states: the\n * wire carries an *address*, and who can fetch it differs per client. Absent,\n * or a hash not in it yet, simply draws no picture; the project's name is\n * already there.\n */\n projectIcons?: Record<string, string>\n className?: string\n}\n\n/**\n * How a list row is drawn, in one place, so `SidebarRow` in `web` matches this\n * exactly rather than approximating it — the dashboard's other three sidebars\n * are that component, and a sessions list that hovered differently from the\n * gateways list beside it would read as a different product.\n *\n * Two rules are load-bearing:\n *\n * - **Fill means hover, and only hover.** It stays on the row whether or not\n * the row is selected, because a selected row still has to answer the\n * pointer. Selection gets the gutter instead.\n * - **`ml-0` on the selected row is not cosmetic.** It hands the accent border\n * the 4px the margin was holding, so the text does not shift sideways as a\n * row becomes the selected one. The squared left corners are what let the bar\n * sit flush against the sidebar edge.\n */\nexport function rowShapeClass(active: boolean): string {\n return cn(\n 'px-2 py-1.5 hover:bg-row-hover',\n active ? 'mr-1 ml-0 rounded-r-md border-l-4 border-l-accent' : 'mx-1 rounded-md',\n )\n}\n\nexport function SessionBrowser({\n rows,\n config,\n onConfigChange,\n scope,\n activeId,\n onSelect,\n onDelete,\n onRename,\n emptyState,\n showControls = true,\n projectIcons,\n className,\n}: SessionBrowserProps) {\n const visible = useMemo(() => filterRows(rows, config, scope), [rows, config, scope])\n const groups = useMemo(() => groupRows(visible, config), [visible, config])\n const subset = subsetSummary(config, scope, visible.length, rows.length)\n // Derived, not enumerated: a new engine or a new gateway needs no change here,\n // and a facet with one possible value is not a choice worth showing.\n const adapters = useMemo(() => adaptersOf(rows), [rows])\n const projects = useMemo(() => projectsOf(rows), [rows])\n const gateways = useMemo(() => {\n const seen = new Map<string, string>()\n for (const row of rows) seen.set(row.hostId, row.hostName)\n return [...seen].map(([id, name]) => ({ id, name }))\n }, [rows])\n\n const set = (patch: Partial<ViewConfig>) => onConfigChange({ ...config, ...patch })\n\n return (\n <div data-slot='session-browser' className={cn('flex flex-col gap-3', className)}>\n {/* One control per row, label left, input right — the shape VS Code uses\n for its own filter surfaces. A wrapping row of pill-shaped selects\n reflows into an unpredictable number of lines as facets appear and\n disappear; a column of labelled rows is the same height every time and\n reads at sidebar width, which is the only width this has. */}\n <div className={cn('flex flex-col gap-1.5 px-2', !showControls && 'hidden')}>\n <div className='relative'>\n <Search className='pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-fg-4' />\n <Input\n value={config.search}\n onChange={(e) => set({ search: e.target.value })}\n placeholder='Search sessions'\n aria-label='Search sessions'\n className='pl-8'\n />\n </div>\n <FilterRow label='State'>\n <FacetSelect\n label='State'\n value={config.states}\n options={STATE_ORDER.map((s) => ({ value: s, label: STATE_LABELS[s] }))}\n onChange={(states) => set({ states: states as SessionState[] })}\n />\n </FilterRow>\n {adapters.length > 1 ? (\n <FilterRow label='Engine'>\n <FacetSelect\n label='Engine'\n value={config.adapters}\n options={adapters.map((a) => ({ value: a, label: a }))}\n onChange={(adapters) => set({ adapters })}\n />\n </FilterRow>\n ) : null}\n {gateways.length > 1 ? (\n <FilterRow label='Gateway'>\n <FacetSelect\n label='Gateway'\n value={config.gateways}\n options={gateways.map((g) => ({ value: g.id, label: g.name }))}\n onChange={(gateways) => set({ gateways })}\n />\n </FilterRow>\n ) : null}\n {projects.length > 1 ? (\n <FilterRow label='Project'>\n <FacetSelect\n label='Project'\n value={config.projects ?? []}\n options={projects.map((p) => ({ value: p.key, label: p.label }))}\n onChange={(next) => set({ projects: next })}\n />\n </FilterRow>\n ) : null}\n <FilterRow label='Group'>\n <OneOfSelect\n label='Group'\n value={config.groupBy}\n options={[\n { value: 'none', label: 'No grouping' },\n { value: 'state', label: 'By state' },\n { value: 'adapter', label: 'By engine' },\n ...(projects.length > 1 ? [{ value: 'project' as const, label: 'By project' }] : []),\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'By gateway' }] : []),\n ]}\n onChange={(groupBy) => set({ groupBy: groupBy as GroupBy })}\n />\n </FilterRow>\n <FilterRow label='Sort'>\n <OneOfSelect\n label='Sort'\n value={config.sortBy}\n options={[\n { value: 'recent', label: 'Recent' },\n { value: 'name', label: 'Name' },\n { value: 'state', label: 'State' },\n ...(projects.length > 1 ? [{ value: 'project' as const, label: 'Project' }] : []),\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'Gateway' }] : []),\n ]}\n onChange={(sortBy) => set({ sortBy: sortBy as SortBy })}\n />\n </FilterRow>\n </div>\n\n {subset ? (\n <div className='flex items-center gap-2 px-3 text-label text-fg-4'>\n <span>\n {subset.shown} of {subset.total}\n {subset.causes.length ? ` · ${subset.causes.join(' · ')}` : null}\n </span>\n <button\n type='button'\n className='text-fg-3 underline underline-offset-2 hover:text-fg-1'\n onClick={() => onConfigChange(clearFilters(config))}>\n Show all\n </button>\n </div>\n ) : null}\n\n {rows.length === 0 ? (\n (emptyState ?? <Empty icon={<Layers />} title='No sessions yet' />)\n ) : visible.length === 0 ? (\n // Two different dead ends, two different ways out. \"Nothing matches\" is\n // a filter someone set; anything else is the state of the world — and\n // only the first has a button, because an action that does nothing is\n // worse than none.\n hasFacetFilter(config) ? (\n <Empty\n icon={<SearchX />}\n title='No matches'\n description='No session matches the current search and filters.'\n action='Clear filters'\n onAction={() => onConfigChange(clearFilters(config))}\n />\n ) : (\n <Empty icon={<Layers />} title='Nothing here' description='No session to show.' />\n )\n ) : (\n <div className='flex flex-col gap-4'>\n {groups.map((group) => (\n <div key={group.key} className='flex flex-col gap-1'>\n {config.groupBy !== 'none' && group.label ? (\n <div className='flex items-center gap-2 px-3 text-label font-medium text-fg-4'>\n {/* Only the project facet has a mark of its own, and a group\n IS one project root, so the first row is a fair source. */}\n {config.groupBy === 'project' ? (\n <ProjectIcon\n icon={group.rows[0]?.info.project?.icon}\n src={iconSrcOf(group.rows[0], projectIcons)}\n name={group.label}\n />\n ) : null}\n <span className='uppercase tracking-wide'>{group.label}</span>\n <span className='text-fg-4/70'>{group.rows.length}</span>\n </div>\n ) : null}\n {group.rows.map((row) => (\n <SessionRowItem\n key={`${row.hostId}:${row.info.id}`}\n row={row}\n active={row.info.id === activeId}\n showGateway={gateways.length > 1}\n showProject={config.groupBy !== 'project'}\n projectIcons={projectIcons}\n onSelect={onSelect}\n onDelete={onDelete}\n onRename={onRename}\n />\n ))}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n/** The bytes for a row's project icon, if it has an image one and the caller has\n * fetched it yet. Shared by the row and its group header so the two cannot draw\n * different pictures for one project. */\nfunction iconSrcOf(\n row: SessionRow | undefined,\n icons: Record<string, string> | undefined,\n): string | undefined {\n const icon = row?.info.project?.icon\n return icon?.type === 'image' ? icons?.[icon.hash] : undefined\n}\n\ninterface SessionRowItemProps {\n row: SessionRow\n active?: boolean\n showGateway?: boolean\n /** False when the list is already grouped by project — the header has said\n * the name, so the slot goes back to the cwd's basename, which inside a\n * project group is the one thing the header cannot say. The rule `showGateway`\n * follows one facet over. */\n showProject?: boolean\n projectIcons?: Record<string, string>\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n onRename?: (row: SessionRow, title: string) => void\n}\n\nfunction SessionRowItem({\n row,\n active,\n showGateway,\n showProject = true,\n projectIcons,\n onSelect,\n onDelete,\n onRename,\n}: SessionRowItemProps) {\n const { info } = row\n const [editing, setEditing] = useState(false)\n\n // What it is and what it has spent, in one line — the same set the extension\n // shows, joined the same way, so the two lists read as one product.\n const folder = info.cwd.split('/').filter(Boolean).pop() ?? info.cwd\n // protocol's own spelling, so this row, the group header above it and the\n // project facet cannot disagree about what a project is called. It falls back\n // to exactly the basename this line drew before the feature existed.\n const project = showProject ? projectLabel(row) : folder\n const projectIcon = showProject ? info.project?.icon : undefined\n const details = [\n showGateway ? row.hostName : undefined,\n friendlyModel(info.model),\n project,\n info.profile ? `@${info.profile}` : undefined,\n formatCost(info.totalCostUsd),\n ].filter(Boolean)\n // Where the icon goes: immediately before the project's own name, wherever\n // that landed in the joined line. Split rather than interleaved as nodes,\n // because everything here is one truncating mono run and a flex of pieces\n // would each shrink a little and leave several half-words.\n const cut = details.indexOf(project)\n\n return (\n <div\n data-slot='session-row'\n data-active={active || undefined}\n // Selection lives on the whole row, not on the two text buttons inside\n // it: the age, the unread badge and the state glyph sit outside them, and\n // a row where a third of the surface silently does nothing is a row that\n // feels broken. The buttons stay — they are what makes the row reachable\n // by keyboard — but their activation now reaches this handler by\n // bubbling, so there is one code path and no double-fire. Anything that\n // is its own action (rename, close, the name editor) stops the event.\n onClick={() => !editing && onSelect?.(row)}\n className={cn(\n 'group flex cursor-pointer flex-col gap-0.5 text-left transition-colors',\n rowShapeClass(active === true),\n )}>\n {/* Line one: what you scan the list by on the left, how it is doing on the\n right, state last. */}\n <div className='flex items-center gap-1.5'>\n {/* The editor replaces the link rather than sitting inside it — an\n input nested in a button is invalid, and disabling the button to\n protect the edit disables the field with it. */}\n {editing && onRename ? (\n <NameEditor\n initial={info.title ?? ''}\n onCommit={(title) => {\n setEditing(false)\n onRename(row, title)\n }}\n onCancel={() => setEditing(false)}\n />\n ) : (\n <button\n type='button'\n className={cn(\n 'min-w-0 flex-1 truncate text-left text-body-sm outline-none',\n active ? 'font-medium text-fg-1' : 'text-fg-2',\n )}>\n {sessionLabel(info)}\n </button>\n )}\n {row.unseen > 0 ? (\n // Transcript rows since this session was last on screen — the same\n // unit the VS Code badge counts, because turns undercount badly.\n <span\n title={`${row.unseen} new`}\n className='shrink-0 rounded-full bg-accent px-1.5 text-label text-accent-fg'>\n {row.unseen}\n </span>\n ) : null}\n <span className='shrink-0 text-label text-fg-4'>\n {formatRelativeTime(info.lastActivityAt ?? info.createdAt)}\n </span>\n <SessionStatusIcon row={row} />\n </div>\n\n {/* Line two: what it is, with the actions at the far right — away from the\n state icon, and away from the title you are actually reading. */}\n <div className='flex items-center gap-1 text-label text-fg-4'>\n <button\n type='button'\n tabIndex={-1}\n className='min-w-0 flex-1 truncate text-left font-mono outline-none'>\n {cut < 0 ? (\n details.join(' · ')\n ) : (\n <>\n {details.slice(0, cut).map((part) => `${part} · `)}\n <ProjectIcon\n icon={projectIcon}\n src={iconSrcOf(row, projectIcons)}\n name={project}\n /* Nudged onto the text baseline: a 12px glyph box against an\n 11px line sits a hair proud without it. */\n className='mr-1 align-[-0.2em]'\n />\n {details.slice(cut).join(' · ')}\n </>\n )}\n </button>\n {onRename && !editing ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Rename session'\n className='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n setEditing(true)\n }}>\n <Pencil className='size-3 text-fg-3' />\n </Button>\n ) : null}\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n onDelete(row)\n }}>\n <Trash2 className='size-3 text-fg-3' />\n </Button>\n ) : null}\n </div>\n </div>\n )\n}\n\n/**\n * State as one glyph on the right edge — a ringing bell when it wants a human, a\n * spinner while it works, a moon when it is only sleeping. Replaces the text\n * badge: in a sidebar the word costs more room than it earns, and the states\n * that matter are the two you can recognise without reading.\n */\nexport function SessionStatusIcon({ row }: { row: SessionRow }) {\n const { info } = row\n if (info.pendingPermissionCount > 0 || info.status === 'awaiting_approval') {\n return <BellRing className='size-3.5 shrink-0 animate-pulse text-warning' />\n }\n if (info.status === 'running' || info.status === 'starting') {\n return <Spinner className='size-3.5 shrink-0 text-info' />\n }\n switch (info.status) {\n case 'failed':\n return <CircleAlert className='size-3.5 shrink-0 text-danger' />\n case 'closed':\n return <CircleSlash className='size-3.5 shrink-0 text-fg-4' />\n case 'parked':\n return <PauseCircle className='size-3.5 shrink-0 text-fg-3' />\n default:\n return <Moon className='size-3.5 shrink-0 text-fg-4' />\n }\n}\n\n/**\n * Inline rename. Enter commits, Escape cancels, blur commits — but only a blur\n * that is still inside this document: switching windows must not silently commit,\n * nor kill the editor in the frame it opened.\n */\nfunction NameEditor({\n initial,\n onCommit,\n onCancel,\n}: {\n initial: string\n onCommit: (title: string) => void\n onCancel: () => void\n}) {\n const [value, setValue] = useState(initial)\n const ref = useRef<HTMLInputElement>(null)\n useEffect(() => {\n ref.current?.select()\n }, [])\n return (\n <input\n ref={ref}\n value={value}\n autoFocus\n aria-label='Session name'\n // The row is a button; a click in here must not select the session.\n onClick={(e) => e.stopPropagation()}\n onChange={(e) => setValue(e.target.value)}\n onBlur={() => (document.hasFocus() ? onCommit(value.trim()) : undefined)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') onCommit(value.trim())\n else if (e.key === 'Escape') onCancel()\n e.stopPropagation()\n }}\n className='-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-0.5 text-body-sm font-medium text-fg-1 outline-none'\n />\n )\n}\n\n/** A multi-select facet. Empty = no filter, which is why the trigger reads the\n * facet's name rather than \"All\": nothing is being excluded. */\n/**\n * One labelled control row. The label column is fixed so every control starts\n * on the same x — the thing that makes a stack of them read as a form rather\n * than as five unrelated widgets.\n */\nfunction FilterRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className='flex items-center gap-2'>\n <span aria-hidden className='w-14 shrink-0 truncate text-label text-fg-3'>\n {label}\n </span>\n <div className='min-w-0 flex-1'>{children}</div>\n </div>\n )\n}\n\nfunction FacetSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string[]\n options: { value: string; label: string }[]\n onChange: (value: string[]) => void\n}) {\n return (\n <div className='flex items-center gap-1'>\n <Select multiple value={value} onValueChange={(v) => onChange(v as string[])}>\n <SelectTrigger aria-label={label} className='min-w-0 flex-1'>\n <SelectValue>\n {/* \"All\" rather than the label, which the row already carries. */}\n {value.length === 0\n ? 'All'\n : value.length === 1\n ? (options.find((o) => o.value === value[0])?.label ?? 'All')\n : `${value.length} selected`}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {value.length > 0 ? (\n <Button variant='ghost' size='icon-sm' aria-label={`Clear ${label}`} onClick={() => onChange([])}>\n <X className='size-3 text-fg-4' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nfunction OneOfSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string\n options: readonly { value: string; label: string }[]\n onChange: (value: string) => void\n}) {\n return (\n <Select value={value} onValueChange={(v) => onChange(v as string)}>\n <SelectTrigger aria-label={label} className='w-full min-w-0'>\n <SelectValue>{options.find((o) => o.value === value)?.label ?? label}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n","import { cn } from '../../lib/utils.ts'\n\n/**\n * The engine's own mark, monochrome.\n *\n * Inlined rather than depended on: the React package these come from\n * (`@lobehub/icons`) pulls antd, `@lobehub/ui` and a second copy of\n * lucide-react — an absurd amount of bundle for a few glyphs in a sidebar.\n * These are paths from `@lobehub/icons-static-svg` (MIT, © LobeHub), already\n * `fill=\"currentColor\"` single-path marks, so they take the row's colour like\n * any other glyph and need no light/dark variants.\n *\n * The marks are trademarks of their owners (Anthropic, OpenAI, Google,\n * DeepSeek, Moonshot), used here only to identify which engine a session runs\n * on.\n *\n * Codex is drawn as OpenAI: the session's engine *is* OpenAI's, and OpenAI's\n * mark is the one a person recognises at 12px.\n */\nconst PATHS: Record<string, { title: string; d: string }> = {\n claude: {\n title: 'Claude',\n d: 'M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z',\n },\n codex: {\n title: 'OpenAI',\n d: 'M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z',\n },\n gemini: {\n title: 'Gemini',\n d: 'M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z',\n },\n deepseek: {\n title: 'DeepSeek',\n d: 'M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z',\n },\n moonshot: {\n title: 'Moonshot',\n d: 'M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z',\n },\n}\n\n/**\n * Which mark a session wears.\n *\n * The two first-party engines are named directly. A `provider` session is\n * whatever the host wired up, so its *model* is the only thing that says whose\n * it is — sniffed loosely on purpose (`gemini-2.5-pro`, `deepseek-chat`,\n * `kimi-k2`), and falling through to no mark rather than guessing wrong.\n */\nexport function engineMark(engine: string, model?: string): string | undefined {\n if (engine === 'claude') return 'claude'\n if (engine === 'codex') return 'codex'\n const id = model?.toLowerCase() ?? ''\n if (!id) return undefined\n if (id.includes('gemini')) return 'gemini'\n if (id.includes('deepseek')) return 'deepseek'\n if (id.includes('moonshot') || id.includes('kimi')) return 'moonshot'\n if (id.includes('claude')) return 'claude'\n if (id.startsWith('gpt') || id.startsWith('o1') || id.startsWith('o3') || id.includes('openai')) {\n return 'codex'\n }\n return undefined\n}\n\nexport function EngineIcon({\n engine,\n model,\n className,\n}: {\n engine: string\n model?: string\n className?: string\n}) {\n const mark = PATHS[engineMark(engine, model) ?? '']\n // An engine this build has never heard of still gets a slot, so the second\n // line doesn't reflow around a missing glyph.\n if (!mark) {\n return (\n <span aria-hidden title={engine} className={cn('shrink-0 text-fg-3', className)}>\n ·\n </span>\n )\n }\n return (\n <svg\n viewBox='0 0 24 24'\n fill='currentColor'\n fillRule='evenodd'\n role='img'\n aria-label={mark.title}\n className={cn('size-3 shrink-0 text-fg-3', className)}>\n <title>{mark.title}</title>\n <path d={mark.d} />\n </svg>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["AlertDialogPrimitive","SonnerToaster","Map"],"sources":["../src/components/ui/Card.tsx","../src/components/ui/Textarea.tsx","../src/components/ui/AlertDialog.tsx","../src/components/ui/Sonner.tsx","../src/components/ui/Empty.tsx","../src/components/prompt-area/segment-helpers.ts","../src/components/terminal/StatusLine.tsx","../src/components/agent/SessionList.tsx","../src/components/agent/ContextRing.tsx","../src/components/agent/EngineIcon.tsx","../src/components/agent/ProjectIcon.tsx","../src/components/agent/SessionSteps.tsx","../src/components/agent/SessionBrowser.tsx"],"sourcesContent":["import { type HTMLAttributes } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport function Card({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n data-slot='card'\n className={cn('rounded-lg border border-border bg-surface', className)}\n {...props}\n />\n )\n}\n\nexport function CardHeader({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn('flex items-start justify-between gap-3 px-4 pt-3.5 pb-3', className)}\n {...props}\n />\n )\n}\n\nexport function CardTitle({ className, ...props }: HTMLAttributes<HTMLHeadingElement>) {\n return <h3 className={cn('text-heading-3 font-semibold text-text', className)} {...props} />\n}\n\nexport function CardContent({ className, ...props }: HTMLAttributes<HTMLDivElement>) {\n return <div className={cn('px-4 pb-4', className)} {...props} />\n}\n","import { type TextareaHTMLAttributes, forwardRef } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\nexport const Textarea = forwardRef<\n HTMLTextAreaElement,\n TextareaHTMLAttributes<HTMLTextAreaElement>\n>(({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n data-slot='textarea'\n className={cn(\n 'w-full resize-none rounded-md border border-border bg-bg px-2.5 py-2 text-body-sm text-text',\n 'placeholder:text-fg-4 transition-colors outline-none',\n 'hover:border-border-strong focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/40',\n 'disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n {...props}\n />\n))\nTextarea.displayName = 'Textarea'\n","import { type FunctionComponent } from 'react'\nimport { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'\nimport { cn } from '../../lib/utils.ts'\n\nexport const AlertDialog = AlertDialogPrimitive.Root\nexport const AlertDialogTrigger = AlertDialogPrimitive.Trigger\nexport const AlertDialogClose = AlertDialogPrimitive.Close\n\nexport const AlertDialogContent: FunctionComponent<AlertDialogPrimitive.Popup.Props> = ({\n className,\n children,\n ...props\n}) => (\n <AlertDialogPrimitive.Portal>\n <AlertDialogPrimitive.Backdrop\n className={cn(\n 'fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]',\n 'transition-opacity duration-(--motion-base)',\n 'data-starting-style:opacity-0 data-ending-style:opacity-0',\n )}\n />\n <AlertDialogPrimitive.Popup\n data-slot='alert-dialog-content'\n className={cn(\n 'fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2',\n 'rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none',\n 'transition-[opacity,transform] duration-(--motion-base)',\n 'data-starting-style:scale-95 data-starting-style:opacity-0',\n 'data-ending-style:scale-95 data-ending-style:opacity-0',\n className,\n )}\n {...props}>\n {children}\n </AlertDialogPrimitive.Popup>\n </AlertDialogPrimitive.Portal>\n)\n\nexport const AlertDialogTitle: FunctionComponent<AlertDialogPrimitive.Title.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Title\n className={cn('text-heading-3 font-semibold text-text', className)}\n {...props}\n />\n)\n\nexport const AlertDialogDescription: FunctionComponent<AlertDialogPrimitive.Description.Props> = ({\n className,\n ...props\n}) => (\n <AlertDialogPrimitive.Description\n className={cn('mt-1.5 text-body-sm text-muted-foreground', className)}\n {...props}\n />\n)\n","import { Toaster as SonnerToaster, toast } from 'sonner'\n\nexport { toast }\n\n/** Token-themed toaster; relies on the [data-theme] swap, so no `theme` prop needed. */\nexport function Toaster() {\n return (\n <SonnerToaster\n position='bottom-right'\n toastOptions={{\n style: {\n background: 'var(--surface)',\n color: 'var(--text)',\n border: '1px solid var(--border)',\n boxShadow: 'var(--shadow-md)',\n fontFamily: 'var(--cw-font-sans)',\n fontSize: 'var(--text-body-sm)',\n },\n }}\n />\n )\n}\n","import type { ReactNode } from 'react'\nimport { Button } from './Button.tsx'\n\n/**\n * A view with nothing in it: icon, title, description, and at most one action.\n *\n * Centered and given room, because an empty view is the first thing a new\n * install shows and a flush-left sentence in the top corner reads like a bug\n * report. It lives here rather than in one client because every panel that can\n * be empty should be empty the same way — the extension's views and the\n * dashboard's four sidebars are the same shape of thing.\n *\n * **At most one action, and never one the view header already offers.** Creating\n * a session and adding a gateway are the `+` in the native title bar, exclusively\n * — so those empty states point at it in words rather than growing a second\n * button that does the same thing two inches lower. A button here is for the way\n * out of a state the header has no answer to: clearing a filter, widening a\n * scope.\n */\nexport function Empty({\n icon,\n title,\n description,\n action,\n onAction,\n}: {\n icon: ReactNode\n title: string\n description?: ReactNode\n action?: string\n onAction?: () => void\n}) {\n return (\n <div className='flex flex-col items-center gap-2 px-5 py-8 text-center'>\n <div className='text-fg-4 opacity-60 [&_svg]:size-7'>{icon}</div>\n <p className='text-body-sm font-medium text-fg-2'>{title}</p>\n {description ? (\n <p className='max-w-[26ch] text-balance text-label leading-relaxed text-fg-4'>\n {description}\n </p>\n ) : null}\n {action && onAction ? (\n <Button variant='outline' size='sm' className='mt-1' onClick={onAction}>\n {action}\n </Button>\n ) : null}\n </div>\n )\n}\n\n/** A key or icon named inline in an empty state's description — \"use the + above\". */\nexport function EmptyKey({ children }: { children: ReactNode }) {\n return (\n <span className='rounded border border-border px-1 font-mono text-fg-3'>{children}</span>\n )\n}\n","/**\n * Convenience helpers for creating and inspecting Segments.\n *\n * These reduce boilerplate when building AI chat UIs that work with the\n * PromptArea document model.\n *\n * @example\n * ```ts\n * import { text, chip, isSegmentsEmpty, segmentsToPlainText } from './segment-helpers.ts'\n *\n * const greeting = [text('Hello '), chip({ trigger: '@', value: 'u1', displayText: 'Alice' })]\n * isSegmentsEmpty(greeting) // false\n * segmentsToPlainText(greeting) // \"Hello @Alice\"\n * ```\n */\n\nimport type { Segment, TextSegment, ChipSegment } from './types.ts'\nimport { segmentsToPlainText, plainTextToSegments } from './prompt-area-engine.ts'\n\n// Re-export serialization utilities so consumers have a single import.\nexport { segmentsToPlainText, plainTextToSegments }\n\n// ---------------------------------------------------------------------------\n// Factories\n// ---------------------------------------------------------------------------\n\n/** Create a text segment. */\nexport function text(value: string): TextSegment {\n return { type: 'text', text: value }\n}\n\n/** Create a chip segment. */\nexport function chip(opts: Omit<ChipSegment, 'type'>): ChipSegment {\n return { type: 'chip', ...opts }\n}\n\n// ---------------------------------------------------------------------------\n// Predicates\n// ---------------------------------------------------------------------------\n\n/** Returns `true` when the segment array is empty or contains only whitespace text. */\nexport function isSegmentsEmpty(segments: Segment[]): boolean {\n if (segments.length === 0) return true\n return segments.every((seg) => seg.type === 'text' && seg.text.trim() === '')\n}\n\n/** Returns `true` when the segment array contains at least one chip. */\nexport function hasChips(segments: Segment[]): boolean {\n return segments.some((seg) => seg.type === 'chip')\n}\n\n/** Extracts all chip segments from a segment array. */\nexport function getChips(segments: Segment[]): ChipSegment[] {\n return segments.filter((seg): seg is ChipSegment => seg.type === 'chip')\n}\n\n/** Extracts chips matching a specific trigger character. */\nexport function getChipsByTrigger(segments: Segment[], trigger: string): ChipSegment[] {\n return segments.filter(\n (seg): seg is ChipSegment => seg.type === 'chip' && seg.trigger === trigger,\n )\n}\n","import type { RateLimitInfo } from '@workerdeck/protocol'\nimport type { ConnectionState, TranscriptState } from '@workerdeck/react'\nimport { formatCost, formatTokens } from '../../lib/format.ts'\nimport {\n contextSeverity,\n meterSeverity,\n modelLabel,\n statusPresentation,\n tightestWindow,\n windowLabel,\n type StatusSeverity,\n} from '../../lib/status.ts'\nimport { Ink, Row, type Tone } from './row.tsx'\n\n/**\n * The session's readings, as one line.\n *\n * The styled bar puts each of these in its own affordance — a progress ring, a\n * badge, a tooltip — which is right for a dashboard and wrong here for the usual\n * reason: a terminal has one line height, and five widgets of five different\n * heights is five different rhythms in a row that is supposed to be quiet. So\n * they are words, `·`-separated, exactly as the CLI's own status line writes\n * them.\n *\n * The readings themselves come from the same helpers the styled bar uses\n * (`lib/status.ts`), and that matters more than it looks: `statusPresentation`\n * is where the rule lives that **a dropped socket outranks the session status**,\n * because a status held over a dead socket is a stale reading being presented as\n * a live one. Re-deriving that here would be a second answer to a question that\n * already has one.\n */\n\n/** Severity → tone. The 80/95 thresholds are `lib/status.ts`'s, not new ones. */\nconst SEVERITY_TONE: Record<StatusSeverity, Tone> = {\n none: 'dim',\n warning: 'yellow',\n error: 'red',\n}\n\nexport interface TerminalStatusLineProps {\n state: TranscriptState\n /** Plan windows to read from, when they should not be the session's own — the\n * gateway's per-profile state merged over this transcript's. See\n * {@link StatusBarProps.rateLimits}. Absent = `state.rateLimits`. */\n rateLimits?: Record<string, RateLimitInfo>\n /** How the client is doing at reaching the gateway. Wins the status slot when\n * the socket is down — see above. */\n connection?: ConnectionState\n /** Opens the panel that answers each reading's own question. Omit and the\n * reading is text rather than something to press. */\n onOpenStatus?: () => void\n onOpenContext?: () => void\n onOpenUsage?: () => void\n}\n\n/** One reading. A button only when it leads somewhere — a segment that looks\n * pressable and does nothing is worse than plain text. */\nfunction Reading({\n tone,\n onPress,\n label,\n children,\n}: {\n tone?: Tone\n onPress?: () => void\n label?: string\n children: React.ReactNode\n}) {\n if (!onPress) return <Ink tone={tone}>{children}</Ink>\n return (\n <button type='button' className='term-reading' onClick={onPress} title={label}>\n <Ink tone={tone}>{children}</Ink>\n </button>\n )\n}\n\nexport function TerminalStatusLine({\n state,\n rateLimits,\n connection,\n onOpenStatus,\n onOpenContext,\n onOpenUsage,\n}: TerminalStatusLineProps) {\n const presentation = statusPresentation({ status: state.status, connection })\n const usage = state.contextUsage\n const window = tightestWindow(rateLimits ?? state.rateLimits)\n const model = modelLabel({ model: state.model, models: state.models ?? [] })\n\n const parts: React.ReactNode[] = [\n <Reading\n key='status'\n tone={SEVERITY_TONE[presentation.severity]}\n onPress={onOpenStatus}\n label='Session status'>\n {presentation.label}\n </Reading>,\n ]\n\n if (usage) {\n parts.push(\n <Reading\n key='context'\n tone={SEVERITY_TONE[contextSeverity(usage)]}\n onPress={onOpenContext}\n label='Context window'>\n {Math.round(usage.percentage)}% ctx ({formatTokens(usage.totalTokens)})\n </Reading>,\n )\n }\n\n if (window) {\n parts.push(\n <Reading\n key='usage'\n tone={SEVERITY_TONE[meterSeverity(window.info.utilization)]}\n onPress={onOpenUsage}\n label='Plan usage'>\n {Math.round(window.info.utilization ?? 0)}% {windowLabel(window.key)}\n </Reading>,\n )\n }\n\n // Cost last of the numbers: it is the reading you check, never the one you act\n // on, and nothing about a turn changes because of it.\n if (state.totalCostUsd > 0) {\n parts.push(\n <Ink key='cost' tone='faint'>\n {formatCost(state.totalCostUsd)}\n </Ink>,\n )\n }\n\n if (model) {\n parts.push(\n <Ink key='model' tone='faint'>\n {model}\n </Ink>,\n )\n }\n if (state.permissionMode && state.permissionMode !== 'default') {\n parts.push(\n <Ink key='mode' tone='yellow'>\n {state.permissionMode}\n </Ink>,\n )\n }\n\n return (\n <Row data-slot='status-line' tone='dim'>\n {parts.map((part, index) => (\n <span key={index}>\n {index > 0 ? <Ink tone='faint'> · </Ink> : null}\n {part}\n </span>\n ))}\n </Row>\n )\n}\n","import type { SessionInfo } from '@workerdeck/protocol'\nimport { Trash2 } from 'lucide-react'\nimport { Badge } from '../ui/Badge.tsx'\nimport { Button } from '../ui/Button.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime } from '../../lib/format.ts'\nimport { STATUS_META } from './status.ts'\n\nexport interface SessionListItemProps {\n session: SessionInfo\n active?: boolean\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n}\n\nexport function SessionListItem({ session, active, onSelect, onDelete }: SessionListItemProps) {\n const meta = STATUS_META[session.status]\n return (\n <div\n data-slot='session-list-item'\n data-active={active || undefined}\n className={cn(\n 'group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors',\n active ? 'border-border bg-surface' : 'hover:bg-surface-hover',\n )}>\n <button\n type='button'\n onClick={() => onSelect?.(session.id)}\n className='min-w-0 flex-1 text-left outline-none'>\n <div className='flex items-center gap-2'>\n <span className='truncate text-body-sm font-medium text-fg-1'>\n {session.title ?? session.id.slice(0, 8)}\n </span>\n <Badge variant={meta.variant} dot className='shrink-0'>\n {meta.label}\n </Badge>\n </div>\n <div className='mt-0.5 flex items-center gap-2 font-mono text-label text-fg-4'>\n <span className='truncate'>{session.cwd}</span>\n {session.profile ? <span className='shrink-0'>@{session.profile}</span> : null}\n <span className='shrink-0'>{formatCost(session.totalCostUsd)}</span>\n <span className='shrink-0'>{formatRelativeTime(session.lastActivityAt ?? session.createdAt)}</span>\n </div>\n </button>\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='opacity-0 transition-opacity group-hover:opacity-100'\n onClick={() => onDelete(session.id)}>\n <Trash2 className='size-3.5 text-fg-3' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nexport interface SessionListProps {\n sessions: SessionInfo[]\n activeId?: string\n onSelect?: (id: string) => void\n onDelete?: (id: string) => void\n emptyText?: string\n className?: string\n}\n\nexport function SessionList({\n sessions,\n activeId,\n onSelect,\n onDelete,\n emptyText = 'No sessions yet.',\n className,\n}: SessionListProps) {\n return (\n <div data-slot='session-list' className={cn('flex flex-col gap-1', className)}>\n {sessions.length === 0 ? (\n <div className='px-2.5 py-6 text-center text-body-sm text-fg-4'>{emptyText}</div>\n ) : (\n sessions.map((session) => (\n <SessionListItem\n key={session.id}\n session={session}\n active={session.id === activeId}\n onSelect={onSelect}\n onDelete={onDelete}\n />\n ))\n )}\n </div>\n )\n}\n","import type { ContextReading } from '@workerdeck/protocol'\nimport { ProgressRing } from '../ui/ProgressRing.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatTokens } from '../../lib/format.ts'\nimport { meterColorClass } from '../../lib/status.ts'\n\n/**\n * How full a session's context window is, sized for a **list row**.\n *\n * The reading the session screen has always drawn, moved somewhere it answers a\n * question the session screen cannot: *which* of these thirty sessions is\n * bloating. That is also why it is a ring and not a number — across a list, a\n * ring is read at a glance and `71%` has to be read one row at a time.\n *\n * **Absent draws nothing, and absent is not zero.** A promptless session, a\n * parked one from before the field existed, or an engine that reports no window\n * has no reading; an empty ring would claim its context is empty rather than\n * unknown. The title carries the exact numbers for anyone who wants them.\n *\n * Shared rather than spelled per client: the dashboard's row and the VS Code\n * sidebar's card both draw it, and two copies would be two sets of thresholds\n * the day one of them is nudged.\n */\nexport function ContextRing({\n usage,\n size = 11,\n className,\n}: {\n usage: ContextReading | undefined\n size?: number\n className?: string\n}) {\n if (usage === undefined) return null\n return (\n <span\n title={`Context ${usage.percentage.toFixed(0)}% · ${formatTokens(usage.totalTokens)} / ${formatTokens(usage.maxTokens)}`}\n className={cn('shrink-0 leading-none', meterColorClass(usage.percentage), className)}>\n <ProgressRing value={usage.percentage} size={size} strokeWidth={2} />\n </span>\n )\n}\n","import { cn } from '../../lib/utils.ts'\n\n/**\n * The engine's own mark, monochrome.\n *\n * Inlined rather than depended on: the React package these come from\n * (`@lobehub/icons`) pulls antd, `@lobehub/ui` and a second copy of\n * lucide-react — an absurd amount of bundle for a few glyphs in a sidebar.\n * These are paths from `@lobehub/icons-static-svg` (MIT, © LobeHub), already\n * `fill=\"currentColor\"` single-path marks, so they take the row's colour like\n * any other glyph and need no light/dark variants.\n *\n * The marks are trademarks of their owners (Anthropic, OpenAI, Google,\n * DeepSeek, Moonshot), used here only to identify which engine a session runs\n * on.\n *\n * Codex is drawn as OpenAI: the session's engine *is* OpenAI's, and OpenAI's\n * mark is the one a person recognises at 12px.\n */\nconst PATHS: Record<string, { title: string; d: string }> = {\n claude: {\n title: 'Claude',\n d: 'M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z',\n },\n codex: {\n title: 'OpenAI',\n d: 'M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z',\n },\n gemini: {\n title: 'Gemini',\n d: 'M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z',\n },\n deepseek: {\n title: 'DeepSeek',\n d: 'M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z',\n },\n moonshot: {\n title: 'Moonshot',\n d: 'M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z',\n },\n}\n\n/**\n * Which mark a session wears.\n *\n * The two first-party engines are named directly. A `provider` session is\n * whatever the host wired up, so its *model* is the only thing that says whose\n * it is — sniffed loosely on purpose (`gemini-2.5-pro`, `deepseek-chat`,\n * `kimi-k2`), and falling through to no mark rather than guessing wrong.\n */\nexport function engineMark(engine: string, model?: string): string | undefined {\n if (engine === 'claude') return 'claude'\n if (engine === 'codex') return 'codex'\n const id = model?.toLowerCase() ?? ''\n if (!id) return undefined\n if (id.includes('gemini')) return 'gemini'\n if (id.includes('deepseek')) return 'deepseek'\n if (id.includes('moonshot') || id.includes('kimi')) return 'moonshot'\n if (id.includes('claude')) return 'claude'\n if (id.startsWith('gpt') || id.startsWith('o1') || id.startsWith('o3') || id.includes('openai')) {\n return 'codex'\n }\n return undefined\n}\n\n/**\n * Which engines wear their vendor's colour, and **how far it reaches**.\n *\n * `MARK` is the glyph, `TEXT` is the model name beside it. They are the same for\n * Anthropic and deliberately not for OpenAI, because the two brands differ in\n * kind rather than in hue: coral is an accent, and OpenAI's guidelines forbid\n * adding colour to the mark at all, so theirs is pure white/black. At full\n * contrast that is right on a 12px glyph and wrong on an 11px label — a\n * pure-white model name is *brighter than the session title above it*, which\n * inverts the row's whole hierarchy to say something the mark has already said.\n * So OpenAI's name keeps the muted line it always had, which is also the most\n * literal reading of \"don't add any colors\".\n *\n * Anything absent falls through to muted on both counts.\n */\nconst VENDOR_MARK: Record<string, string> = {\n claude: 'text-vendor-claude',\n codex: 'text-vendor-openai',\n}\nconst VENDOR_TEXT: Record<string, string> = {\n claude: 'text-vendor-claude',\n}\n\n/** The colour a session's engine mark wears. Pass it to `EngineIcon`'s\n * `className` rather than to a parent: the svg carries its own `text-fg-3`,\n * and only a class on the element itself is merged over it. */\nexport function vendorMarkClass(engine: string, model?: string): string {\n return VENDOR_MARK[engineMark(engine, model) ?? ''] ?? 'text-fg-3'\n}\n\n/** The colour the model *name* wears beside that mark — muted unless the vendor\n * is one whose accent survives at label size. See `VENDOR_TEXT`.\n *\n * The fallback is `text-fg-3` rather than the metadata line's own `text-fg-4`,\n * so an unbranded model still sits one step above the project and gateway\n * beside it — the order the spec lists them in, held without a colour. */\nexport function vendorTextClass(engine: string, model?: string): string {\n return VENDOR_TEXT[engineMark(engine, model) ?? ''] ?? 'text-fg-3'\n}\n\nexport function EngineIcon({\n engine,\n model,\n className,\n}: {\n engine: string\n model?: string\n className?: string\n}) {\n const mark = PATHS[engineMark(engine, model) ?? '']\n // An engine this build has never heard of still gets a slot, so the second\n // line doesn't reflow around a missing glyph.\n if (!mark) {\n return (\n <span aria-hidden title={engine} className={cn('shrink-0 text-fg-3', className)}>\n ·\n </span>\n )\n }\n return (\n <svg\n viewBox='0 0 24 24'\n fill='currentColor'\n fillRule='evenodd'\n role='img'\n aria-label={mark.title}\n className={cn('size-3 shrink-0 text-fg-3', className)}>\n <title>{mark.title}</title>\n <path d={mark.d} />\n </svg>\n )\n}\n","import type { ProjectIcon as ProjectIconSpec } from '@workerdeck/protocol'\nimport {\n Anchor, Atom, Beaker, Bike, Binary, Blocks, Bot, Box, Brain, Briefcase, Brush, Bug,\n Building2, Cable, Camera, Car, Cat, CircuitBoard, Cloud, Code, Coffee, Cog, Compass,\n Container, Cpu, CreditCard, Database, Diamond, Dog, Feather, Film, Flame, FlaskConical,\n Folder, Gamepad2, Gauge, Gem, Ghost, Gift, GitBranch, Globe, GraduationCap, Hammer,\n Hexagon, House, Image, Key, Landmark, Laptop, Layers, Leaf, Library, Lightbulb, Lock,\n Mail, Map, MessageCircle, Mic, Monitor, Moon, Mountain, Music, Network, Newspaper,\n Notebook, Orbit, Package, Palette, PenTool, Phone, PiggyBank, Plane, Plug, Puzzle,\n Radar, Radio, Receipt, Rocket, Ruler, Satellite, Scale, Scissors, Server, Shield, Ship,\n ShoppingCart, Signal, Smartphone, Sparkles, Sprout, Square, Star, Sun, Table, Tag,\n Target, Telescope, Terminal, TestTube, TreePine, Trophy, Truck, Umbrella, Users, Wallet,\n Wand, Waves, Webhook, Wifi, Wrench, Zap,\n} from 'lucide-react'\nimport type { ComponentType } from 'react'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * A project's icon — the render side of protocol's `ProjectIcon`, for a list\n * row or a group header.\n *\n * **The glyph arm is a curated set, and that is the design rather than a\n * shortcut.** The gateway validates a glyph name by *shape* only (lucide's\n * lowercase-kebab convention) and explicitly declines to grow an icon catalog,\n * so the contract already says a client must fall back on a name it does not\n * know.\n *\n * The alternative was measured rather than argued, against the VS Code\n * sidebar's own bundle: `sidebar.js` is **31 KB** with one glyph, **77 KB**\n * with the 110 below (~418 bytes each), and **927 KB** with a\n * `import * as` over lucide's ~1,600 — which is what it costs, because a\n * namespace import defeats tree-shaking by construction. (`DynamicIcon` is the\n * third option and worse in a different currency: ~1,600 chunk files shipped\n * inside the `.vsix`.) Twelve times the bundle to render a name nobody has\n * declared yet is not a trade worth making; 46 KB to cover the names people\n * actually use is.\n *\n * The set is chosen to cover what people call a project; an unlisted-but-valid\n * name draws {@link Folder}, which is what the row drew before this feature\n * existed. **Grow it freely** — each addition is ~400 bytes and the fallback\n * means a missing one is a shrug, not a hole.\n *\n * **The image arm draws nothing until its bytes arrive**, and takes them as a\n * resolved `src` rather than fetching: the wire carries only an address\n * (media type, size, content hash), and *who* fetches it differs per client —\n * a VS Code webview cannot reach a gateway at all and must be handed a data\n * URL by its extension host, while a browser can fetch and `createObjectURL`.\n * A component that fetched would be wrong on one of them. Absent `src` renders\n * nothing rather than a placeholder box: an icon is decoration beside a name\n * that is already there, and a box that becomes a picture a beat later is more\n * movement than the picture is worth.\n *\n * One thing an image icon cannot do, and a glyph can: **take the row's\n * colour.** An `<img>`-embedded SVG is its own document, so `currentColor` does\n * not reach it and its own `prefers-color-scheme` resolves against the *OS*\n * rather than the host's theme (measured: it does resolve, which is the\n * surprise — so a monochrome mark tuned for both schemes is right on a dark OS\n * in a dark editor and wrong on a light OS in a dark one). A repo that wants a\n * mark that always matches its row should declare a glyph; one that wants its\n * brand should declare the image and accept that it is a picture.\n */\nconst GLYPHS: Record<string, ComponentType<{ className?: string }>> = {\n anchor: Anchor, atom: Atom, beaker: Beaker, bike: Bike, binary: Binary, blocks: Blocks,\n bot: Bot, box: Box, brain: Brain, briefcase: Briefcase, brush: Brush, bug: Bug,\n 'building-2': Building2, cable: Cable, camera: Camera, car: Car, cat: Cat,\n 'circuit-board': CircuitBoard, cloud: Cloud, code: Code, coffee: Coffee, cog: Cog,\n compass: Compass, container: Container, cpu: Cpu, 'credit-card': CreditCard,\n database: Database, diamond: Diamond, dog: Dog, feather: Feather, film: Film,\n flame: Flame, 'flask-conical': FlaskConical, folder: Folder, 'gamepad-2': Gamepad2,\n gauge: Gauge, gem: Gem, ghost: Ghost, gift: Gift, 'git-branch': GitBranch, globe: Globe,\n 'graduation-cap': GraduationCap, hammer: Hammer, hexagon: Hexagon, house: House,\n image: Image, key: Key, landmark: Landmark, laptop: Laptop, layers: Layers, leaf: Leaf,\n library: Library, lightbulb: Lightbulb, lock: Lock, mail: Mail, map: Map,\n 'message-circle': MessageCircle, mic: Mic, monitor: Monitor, moon: Moon,\n mountain: Mountain, music: Music, network: Network, newspaper: Newspaper,\n notebook: Notebook, orbit: Orbit, package: Package, palette: Palette, 'pen-tool': PenTool,\n phone: Phone, 'piggy-bank': PiggyBank, plane: Plane, plug: Plug, puzzle: Puzzle,\n radar: Radar, radio: Radio, receipt: Receipt, rocket: Rocket, ruler: Ruler,\n satellite: Satellite, scale: Scale, scissors: Scissors, server: Server, shield: Shield,\n ship: Ship, 'shopping-cart': ShoppingCart, signal: Signal, smartphone: Smartphone,\n sparkles: Sparkles, sprout: Sprout, square: Square, star: Star, sun: Sun, table: Table,\n tag: Tag, target: Target, telescope: Telescope, terminal: Terminal, 'test-tube': TestTube,\n 'tree-pine': TreePine, trophy: Trophy, truck: Truck, umbrella: Umbrella, users: Users,\n wallet: Wallet, wand: Wand, waves: Waves, webhook: Webhook, wifi: Wifi, wrench: Wrench,\n zap: Zap,\n}\n\nexport function ProjectIcon({\n icon,\n src,\n name,\n className,\n}: {\n icon: ProjectIconSpec | undefined\n /** Resolved bytes for the `image` arm — an object URL or a data URL. The\n * caller fetches (see the note above); absent draws nothing. */\n src?: string\n /** The project's name, for the alt text. */\n name?: string\n className?: string\n}) {\n if (!icon) return null\n if (icon.type === 'image') {\n if (!src) return null\n return (\n <img\n src={src}\n alt=''\n aria-hidden\n title={name}\n // `object-contain` because a declared icon is whatever aspect the repo\n // checked in, and a squashed logo reads worse than a letterboxed one.\n className={cn('inline-block size-3 shrink-0 object-contain', className)}\n />\n )\n }\n const Glyph = GLYPHS[icon.name] ?? Folder\n return <Glyph className={cn('inline-block size-3 shrink-0', className)} />\n}\n","import {\n ArrowRight,\n Check,\n ChevronDown,\n ChevronRight,\n CircleAlert,\n Dot,\n PauseCircle,\n} from 'lucide-react'\nimport { isAgentRecord, subagentLabel } from '@workerdeck/protocol'\nimport type { SessionInfo, SubagentInfo } from '@workerdeck/protocol'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { cn } from '../../lib/utils.ts'\n\n/**\n * The work *under* a session row, and the one row shape every client renders it\n * through.\n *\n * This lived inside the VS Code webview first, which is exactly why the\n * dashboard had none of it. Nothing in here is extension-specific: a session's\n * sub-agents are a protocol fact, and a disclosure over them is a list\n * affordance. It sits in `packages/ui` so the sidebar, the dashboard and (in its\n * own idiom) the phone are annotating rows the same way rather than three ways.\n *\n * Today the only source is `SessionInfo.subagents`. The other — the CLI's own\n * **task checklist**, the to-do list it keeps for the current turn — is what the\n * design was drawn from, and it is not built: nothing on the wire carries it yet\n * (see `_docs/features/sub-agent-handling.md`, second thread, which opens with\n * \"check a capture\" rather than \"design a surface\"). When it arrives it is a\n * *source*, not a second row component: checklist when the session has one, its\n * sub-agents otherwise.\n *\n * That is also why `state` has a `pending` arm no sub-agent can produce. A\n * sub-agent record exists only once dispatched, so it is never queued; a to-do\n * is queued for most of its life, and dropping the state would mean widening the\n * union later — the shape is cheaper to state now than to retrofit.\n */\nexport type Step = {\n key: string\n label: string\n /** What one of these is called, for the disclosure's count. */\n noun: string\n /**\n * An **agent** has an identity and work of its own, so it is pressable and\n * wears the sub-agent colour. A **task** is something the model described with\n * no agent behind it (`isAgentRecord`), so it is inert: there is no frame to\n * open, and a row that offered one would show an empty screen.\n *\n * Two values and not a boolean because the checklist source this shape was\n * drawn for (see above) produces the second kind natively — a to-do is a task\n * in exactly this sense, and it will slot in here rather than widening\n * anything.\n */\n kind: 'agent' | 'task'\n state: 'done' | 'running' | 'pending' | 'failed'\n /** A trailing reading — a sub-agent's tool count. Absent draws nothing. */\n detail?: string\n title: string\n onSelect: () => void\n}\n\nexport function sessionSteps(\n info: SessionInfo,\n onSelectSubagent: (toolUseId: string) => void,\n): Step[] {\n // The label is protocol's `subagentLabel`, not a spelling of its own: the\n // dashboard and the phone render the same rows from the same records, and two\n // spellings would be two different answers to \"which agent is this\".\n return (info.subagents ?? []).map((sub) => ({\n key: sub.toolUseId,\n label: subagentLabel(sub),\n noun: 'agent',\n kind: isAgentRecord(sub) ? ('agent' as const) : ('task' as const),\n state: stepState(sub.status),\n detail: sub.toolCount > 0 ? String(sub.toolCount) : undefined,\n title: `${subagentLabel(sub)} · ${sub.toolCount} tool${sub.toolCount === 1 ? '' : 's'}`,\n onSelect: () => onSelectSubagent(sub.toolUseId),\n }))\n}\n\nfunction stepState(status: SubagentInfo['status']): Step['state'] {\n switch (status) {\n case 'running':\n return 'running'\n case 'failed':\n return 'failed'\n default:\n return 'done'\n }\n}\n\n/** How many of these are still going — the live half of the disclosure's count. */\nexport function runningSteps(steps: readonly Step[]): number {\n return steps.filter((s) => s.state === 'running').length\n}\n\n/**\n * The disclosure, which is also the reading: `3 agents` — or `2 of 3 agents`\n * while some have settled, because \"how many are still going\" is the live\n * question and a bare total answers it wrong the moment one finishes.\n *\n * Sub-agents are an annotation on a working row rather than a state of their own\n * (see `runningSubagents` in protocol's `session-list.ts`), so this never\n * competes with the row's status glyph: that still says what the *session* is\n * doing.\n */\nexport function StepToggle({\n expanded,\n running,\n total,\n noun,\n onToggle,\n}: {\n expanded: boolean\n running: number\n total: number\n noun: string\n onToggle: () => void\n}) {\n // Two spellings of one count. The **words** are the honest reading and go in\n // the tooltip and to a screen reader; the line itself gets the digits, because\n // this sits on the second line of a 280px row next to the folder and the age,\n // and `1 of 6 agents` truncated the folder name away to say something the row\n // could say in three characters.\n const label = running > 0 && running < total ? `${running}/${total}` : String(total)\n const words =\n running > 0 && running < total\n ? `${running} of ${total} ${noun}s running`\n : `${total} ${noun}${total === 1 ? '' : 's'}`\n const Chevron = expanded ? ChevronDown : ChevronRight\n return (\n <button\n type='button'\n aria-expanded={expanded}\n aria-label={`${expanded ? 'Hide' : 'Show'} ${words}`}\n title={`${expanded ? 'Hide' : 'Show'} ${words}`}\n onClick={(e) => {\n // The whole row is a button and this one does not mean \"select\" — the\n // same guard the row's overflow needs, and the reason a drag-select\n // inside the row does not toggle it.\n e.stopPropagation()\n onToggle()\n }}\n className={cn(\n 'flex shrink-0 items-center gap-0.5 rounded px-0.5 outline-none',\n 'hover:bg-surface-hover hover:text-fg-2',\n running > 0 ? 'text-info' : 'text-fg-4',\n )}>\n <Chevron className='size-3' />\n <span className='tabular-nums'>{label}</span>\n </button>\n )\n}\n\n/**\n * One step under its session. Pressing an **agent** hands the panel over to that\n * agent's own work — it is not a session and never becomes one, but it does now\n * have a surface (`SessionPanel.openSubagent`). A host that cannot frame one\n * falls back to revealing its `Task` row, which was this row's only meaning\n * before the takeover existed. A **task** is not pressable at all; see\n * {@link Step.kind}.\n *\n * Divided from the row's header and from each other by a rule rather than by\n * indentation: these are a list *inside* the row, and at 11px an indent is not\n * enough to say so. The rule is black at 25% so it darkens whatever the row is\n * filled with, selected or not, without needing a colour per state.\n */\nexport function StepRow({ step, onSelect }: { step: Step; onSelect: () => void }) {\n const agent = step.kind === 'agent'\n // Body colour by *kind*, state carried by the icon — the rule the transcript's\n // own `TaskRow` already follows, where the body is green and the marker holds\n // the beat. Green means sub-agent across this product (it is the one hue\n // nothing else on a session surface claims), so a list that spent blue on\n // \"running\" here would be saying something different from the transcript\n // about the same agent. Failure still outranks it: an alarm is not a category.\n const body = step.state === 'failed' ? 'text-danger' : agent ? 'text-success' : 'text-fg-4'\n const content = (\n <>\n <StepIcon state={step.state} kind={step.kind} />\n <span className='min-w-0 flex-1 truncate'>{step.label}</span>\n {/* The progress reading while it works, and what it cost when it is done.\n Zero draws nothing: `0 tools` beside a thinking agent reads as a stall,\n which is the same call `taskSummary` makes one surface over. */}\n {step.detail ? <span className='shrink-0 tabular-nums text-fg-4'>{step.detail}</span> : null}\n {agent ? <ArrowRight className='size-3 shrink-0 opacity-60' /> : null}\n </>\n )\n const shape =\n 'flex w-full items-center gap-2 border-t border-black/25 py-1 pl-3 pr-2 text-left text-label outline-none'\n\n // A task is not a button, in the markup and not merely in the styling: there\n // is nothing to press, and a disabled-looking button still announces itself as\n // one. It stops the click all the same — the whole session row is pressable\n // underneath, so falling through would open the session from a row that says\n // it does nothing.\n if (!agent) {\n return (\n <div title={step.title} onClick={(e) => e.stopPropagation()} className={cn(shape, body)}>\n {content}\n </div>\n )\n }\n return (\n <button\n type='button'\n title={step.title}\n onClick={(e) => {\n e.stopPropagation()\n onSelect()\n }}\n className={cn(shape, 'hover:bg-surface-hover', body)}>\n {content}\n </button>\n )\n}\n\nfunction StepIcon({ state, kind }: { state: Step['state']; kind: Step['kind'] }) {\n // A task that is neither running nor failed gets a neutral dot rather than a\n // tick: `done` is a claim about work, and nothing here did any.\n if (kind === 'task' && state !== 'running' && state !== 'failed') {\n return <Dot className='size-3 shrink-0' />\n }\n switch (state) {\n case 'running':\n return <Spinner className='size-3 shrink-0' />\n case 'failed':\n return <CircleAlert className='size-3 shrink-0' />\n case 'pending':\n return <PauseCircle className='size-3 shrink-0' />\n default:\n return <Check className='size-3 shrink-0' />\n }\n}\n","import { useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport {\n BellRing,\n CircleAlert,\n CircleSlash,\n Layers,\n Moon,\n PauseCircle,\n Pencil,\n Search,\n SearchX,\n Trash2,\n X,\n} from 'lucide-react'\nimport {\n STATE_LABELS,\n STATE_ORDER,\n adaptersOf,\n clearFilters,\n filterRows,\n groupRows,\n hasFacetFilter,\n projectLabel,\n projectSubpath,\n projectsOf,\n sessionLabel,\n subsetSummary,\n} from '@workerdeck/protocol'\nimport type {\n GroupBy,\n SessionRow,\n SessionState,\n SortBy,\n ViewConfig,\n WorkspaceScope,\n} from '@workerdeck/protocol'\nimport { Button } from '../ui/Button.tsx'\nimport { Empty } from '../ui/Empty.tsx'\nimport { Input } from '../ui/Input.tsx'\nimport { Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue } from '../ui/Select.tsx'\nimport { Spinner } from '../ui/Spinner.tsx'\nimport { ContextRing } from './ContextRing.tsx'\nimport { EngineIcon, vendorMarkClass, vendorTextClass } from './EngineIcon.tsx'\nimport { ProjectIcon } from './ProjectIcon.tsx'\nimport { StepRow, StepToggle, runningSteps, sessionSteps } from './SessionSteps.tsx'\nimport { cn } from '../../lib/utils.ts'\nimport { formatCost, formatRelativeTime, friendlyModel } from '../../lib/format.ts'\n\n/**\n * A sessions list with the affordances a list of thirty needs: search, facets,\n * grouping, sorting, unread counts, and one honest line about what is hidden.\n *\n * The *rules* are `@workerdeck/protocol`'s (`filterRows`/`groupRows`/\n * `subsetSummary`), not this component's — the VS Code sidebar renders the same\n * model with workbench chrome, its activity-bar badge counts the same rows this\n * would show, and iOS mirrors them in Swift. What lives here is the styled\n * rendering of that model, so a host that wants the dashboard's look gets it\n * without reimplementing the model behind it.\n *\n * `SessionList` remains beside this for the plain case (a fixed set of rows, no\n * controls); this is what you reach for when the list is the screen.\n */\n\nexport interface SessionBrowserProps {\n rows: SessionRow[]\n config: ViewConfig\n onConfigChange: (config: ViewConfig) => void\n /** The host's own folders, if it has such a notion. Absent — a dashboard, a\n * phone — makes the scope filter genuinely inert rather than empty. */\n scope?: WorkspaceScope\n activeId?: string\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n /**\n * Rename, from the row's pencil. Empty string restores the derived title. A\n * gateway edit (`PATCH /sessions/:id`), never a local override — every client\n * should see the same name. Omit to make titles read-only.\n *\n * A hover affordance rather than the extension's double-click-the-title,\n * because here a single click on the row navigates: the *first* click of a\n * double-click would have already left the page.\n */\n onRename?: (row: SessionRow, title: string) => void\n /** Open a session *at* one of its sub-agents, when the host can scroll a\n * transcript to a `Task` row. Absent, the sub-agent list still expands and a\n * step just opens its session — see `SessionRowItemProps`. */\n onSelectSubagent?: (row: SessionRow, toolUseId: string) => void\n /** Rendered when nothing at all exists (as opposed to nothing matching). */\n emptyState?: React.ReactNode\n /**\n * Whether the search + facet bar is shown. Defaults to `true` — a list that\n * *is* the screen shows its controls.\n *\n * A host with somewhere better to put the toggle (a view title bar) passes\n * `false` and owns the boolean itself, the way the VS Code extension does: the\n * key lives where the commands do. Two rules come with it, and they are why\n * this hides only the bar and nothing else — **closing the bar never clears\n * the filters**, and the subset line below it renders either way, so a list\n * filtered by a control you can't currently see still says so.\n */\n showControls?: boolean\n /**\n * Resolved project-icon bytes by content hash — `useProjectIcons`' output.\n *\n * Passed in rather than fetched here for the reason `ProjectIcon` states: the\n * wire carries an *address*, and who can fetch it differs per client. Absent,\n * or a hash not in it yet, simply draws no picture; the project's name is\n * already there.\n */\n projectIcons?: Record<string, string>\n className?: string\n}\n\n/**\n * How a list row is drawn, in one place, so `SidebarRow` in `web` matches this\n * exactly rather than approximating it — the dashboard's other three sidebars\n * are that component, and a sessions list that hovered differently from the\n * gateways list beside it would read as a different product.\n *\n * Two rules are load-bearing:\n *\n * - **Fill means hover, and only hover.** It stays on the row whether or not\n * the row is selected, because a selected row still has to answer the\n * pointer. Selection gets the gutter instead.\n * - **`ml-0` on the selected row is not cosmetic.** It hands the accent border\n * the 4px the margin was holding, so the text does not shift sideways as a\n * row becomes the selected one. The squared left corners are what let the bar\n * sit flush against the sidebar edge.\n */\nexport function rowShapeClass(active: boolean): string {\n return cn(\n 'px-2 py-1.5 hover:bg-row-hover',\n active ? 'mr-1 ml-0 rounded-r-md border-l-4 border-l-accent' : 'mx-1 rounded-md',\n )\n}\n\nexport function SessionBrowser({\n rows,\n config,\n onConfigChange,\n scope,\n activeId,\n onSelect,\n onDelete,\n onRename,\n onSelectSubagent,\n emptyState,\n showControls = true,\n projectIcons,\n className,\n}: SessionBrowserProps) {\n const visible = useMemo(() => filterRows(rows, config, scope), [rows, config, scope])\n const groups = useMemo(() => groupRows(visible, config), [visible, config])\n const subset = subsetSummary(config, scope, visible.length, rows.length)\n // Derived, not enumerated: a new engine or a new gateway needs no change here,\n // and a facet with one possible value is not a choice worth showing.\n const adapters = useMemo(() => adaptersOf(rows), [rows])\n const projects = useMemo(() => projectsOf(rows), [rows])\n const gateways = useMemo(() => {\n const seen = new Map<string, string>()\n for (const row of rows) seen.set(row.hostId, row.hostName)\n return [...seen].map(([id, name]) => ({ id, name }))\n }, [rows])\n\n const set = (patch: Partial<ViewConfig>) => onConfigChange({ ...config, ...patch })\n\n return (\n <div data-slot='session-browser' className={cn('flex flex-col gap-3', className)}>\n {/* One control per row, label left, input right — the shape VS Code uses\n for its own filter surfaces. A wrapping row of pill-shaped selects\n reflows into an unpredictable number of lines as facets appear and\n disappear; a column of labelled rows is the same height every time and\n reads at sidebar width, which is the only width this has. */}\n <div className={cn('flex flex-col gap-1.5 px-2', !showControls && 'hidden')}>\n <div className='relative'>\n <Search className='pointer-events-none absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2 text-fg-4' />\n <Input\n value={config.search}\n onChange={(e) => set({ search: e.target.value })}\n placeholder='Search sessions'\n aria-label='Search sessions'\n className='pl-8'\n />\n </div>\n <FilterRow label='State'>\n <FacetSelect\n label='State'\n value={config.states}\n options={STATE_ORDER.map((s) => ({ value: s, label: STATE_LABELS[s] }))}\n onChange={(states) => set({ states: states as SessionState[] })}\n />\n </FilterRow>\n {adapters.length > 1 ? (\n <FilterRow label='Engine'>\n <FacetSelect\n label='Engine'\n value={config.adapters}\n options={adapters.map((a) => ({ value: a, label: a }))}\n onChange={(adapters) => set({ adapters })}\n />\n </FilterRow>\n ) : null}\n {gateways.length > 1 ? (\n <FilterRow label='Gateway'>\n <FacetSelect\n label='Gateway'\n value={config.gateways}\n options={gateways.map((g) => ({ value: g.id, label: g.name }))}\n onChange={(gateways) => set({ gateways })}\n />\n </FilterRow>\n ) : null}\n {projects.length > 1 ? (\n <FilterRow label='Project'>\n <FacetSelect\n label='Project'\n value={config.projects ?? []}\n options={projects.map((p) => ({ value: p.key, label: p.label }))}\n onChange={(next) => set({ projects: next })}\n />\n </FilterRow>\n ) : null}\n <FilterRow label='Group'>\n <OneOfSelect\n label='Group'\n value={config.groupBy}\n options={[\n { value: 'none', label: 'No grouping' },\n { value: 'state', label: 'By state' },\n { value: 'adapter', label: 'By engine' },\n ...(projects.length > 1 ? [{ value: 'project' as const, label: 'By project' }] : []),\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'By gateway' }] : []),\n ]}\n onChange={(groupBy) => set({ groupBy: groupBy as GroupBy })}\n />\n </FilterRow>\n <FilterRow label='Sort'>\n <OneOfSelect\n label='Sort'\n value={config.sortBy}\n options={[\n { value: 'recent', label: 'Recent' },\n { value: 'name', label: 'Name' },\n { value: 'state', label: 'State' },\n ...(projects.length > 1 ? [{ value: 'project' as const, label: 'Project' }] : []),\n ...(gateways.length > 1 ? [{ value: 'gateway' as const, label: 'Gateway' }] : []),\n ]}\n onChange={(sortBy) => set({ sortBy: sortBy as SortBy })}\n />\n </FilterRow>\n </div>\n\n {subset ? (\n <div className='flex items-center gap-2 px-3 text-label text-fg-4'>\n <span>\n {subset.shown} of {subset.total}\n {subset.causes.length ? ` · ${subset.causes.join(' · ')}` : null}\n </span>\n <button\n type='button'\n className='text-fg-3 underline underline-offset-2 hover:text-fg-1'\n onClick={() => onConfigChange(clearFilters(config))}>\n Show all\n </button>\n </div>\n ) : null}\n\n {rows.length === 0 ? (\n (emptyState ?? <Empty icon={<Layers />} title='No sessions yet' />)\n ) : visible.length === 0 ? (\n // Two different dead ends, two different ways out. \"Nothing matches\" is\n // a filter someone set; anything else is the state of the world — and\n // only the first has a button, because an action that does nothing is\n // worse than none.\n hasFacetFilter(config) ? (\n <Empty\n icon={<SearchX />}\n title='No matches'\n description='No session matches the current search and filters.'\n action='Clear filters'\n onAction={() => onConfigChange(clearFilters(config))}\n />\n ) : (\n <Empty icon={<Layers />} title='Nothing here' description='No session to show.' />\n )\n ) : (\n <div className='flex flex-col gap-4'>\n {groups.map((group) => (\n <div key={group.key} className='flex flex-col gap-1'>\n {config.groupBy !== 'none' && group.label ? (\n <div className='flex items-center gap-2 px-3 text-label font-medium text-fg-4'>\n {/* Only the project facet has a mark of its own, and a group\n IS one project root, so the first row is a fair source. */}\n {config.groupBy === 'project' ? (\n <ProjectIcon\n icon={group.rows[0]?.info.project?.icon}\n src={iconSrcOf(group.rows[0], projectIcons)}\n name={group.label}\n />\n ) : null}\n <span className='uppercase tracking-wide'>{group.label}</span>\n <span className='text-fg-4/70'>{group.rows.length}</span>\n </div>\n ) : null}\n {group.rows.map((row) => (\n <SessionRowItem\n key={`${row.hostId}:${row.info.id}`}\n row={row}\n active={row.info.id === activeId}\n showGateway={gateways.length > 1 && config.groupBy !== 'gateway'}\n showProject={config.groupBy !== 'project'}\n projectIcons={projectIcons}\n onSelect={onSelect}\n onDelete={onDelete}\n onRename={onRename}\n onSelectSubagent={onSelectSubagent}\n />\n ))}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n/** The bytes for a row's project icon, if it has an image one and the caller has\n * fetched it yet. Shared by the row and its group header so the two cannot draw\n * different pictures for one project. */\nfunction iconSrcOf(\n row: SessionRow | undefined,\n icons: Record<string, string> | undefined,\n): string | undefined {\n const icon = row?.info.project?.icon\n return icon?.type === 'image' ? icons?.[icon.hash] : undefined\n}\n\ninterface SessionRowItemProps {\n row: SessionRow\n active?: boolean\n showGateway?: boolean\n /** False when the list is already grouped by project — the header has said the\n * name, so the row must not spend its metadata line repeating it. What takes\n * the slot is the *sub-path inside the project* (`projectSubpath`), which is\n * the one thing the header cannot say and the only thing that tells two\n * sessions in the same repo apart; a session sitting at the project root has\n * nothing to add and the slot disappears entirely. The rule `showGateway`\n * follows one facet over. */\n showProject?: boolean\n projectIcons?: Record<string, string>\n onSelect?: (row: SessionRow) => void\n onDelete?: (row: SessionRow) => void\n onRename?: (row: SessionRow, title: string) => void\n /** Open one of a session's sub-agents — which now means handing the session\n * panel over to that agent's own work (`SessionPanel.openSubagent`), and meant\n * \"scroll to its `Task` row\" before that surface existed. Both are honest; a\n * host picks what it can draw. Optional, and its absence is not a missing\n * feature: without it a step just opens the session, which is all a host with\n * neither can offer. Only *agent* steps ever call it — see `Step.kind`. */\n onSelectSubagent?: (row: SessionRow, toolUseId: string) => void\n}\n\nfunction SessionRowItem({\n row,\n active,\n showGateway,\n showProject = true,\n projectIcons,\n onSelect,\n onDelete,\n onRename,\n onSelectSubagent,\n}: SessionRowItemProps) {\n const { info } = row\n const [editing, setEditing] = useState(false)\n // Expansion is the row's own state and deliberately not persisted: a list that\n // reopened yesterday's work on every reload would be showing a settled tail\n // nobody asked for.\n const [expanded, setExpanded] = useState(false)\n\n // What it is and what it has spent, in one line — the same set the extension\n // shows, joined the same way, so the two lists read as one product.\n // protocol's own spelling, so this row, the group header above it and the\n // project facet cannot disagree about what a project is called. Under a\n // project group it is the sub-path instead — see `showProject` — and a session\n // at the root contributes nothing at all rather than a name already on screen.\n const project = showProject ? projectLabel(row) : projectSubpath(row)\n const projectIcon = showProject ? info.project?.icon : undefined\n const engine = info.engine ?? 'claude'\n // Everything after the model. The model itself is drawn separately because it\n // is the one segment that wears a colour — see `vendorTextClass` — and a\n // coloured run inside a joined string would have to be spliced back out.\n const details = [\n project,\n showGateway ? row.hostName : undefined,\n info.profile ? `@${info.profile}` : undefined,\n formatCost(info.totalCostUsd),\n ].filter(Boolean)\n const steps = sessionSteps(info, (toolUseId) =>\n onSelectSubagent ? onSelectSubagent(row, toolUseId) : onSelect?.(row),\n )\n // Where the icon goes: immediately before the project's own name, wherever\n // that landed in the joined line. Split rather than interleaved as nodes,\n // because everything here is one truncating mono run and a flex of pieces\n // would each shrink a little and leave several half-words.\n const cut = project === undefined ? -1 : details.indexOf(project)\n\n return (\n <div\n data-slot='session-row'\n data-active={active || undefined}\n // Selection lives on the whole row, not on the two text buttons inside\n // it: the age, the unread badge and the state glyph sit outside them, and\n // a row where a third of the surface silently does nothing is a row that\n // feels broken. The buttons stay — they are what makes the row reachable\n // by keyboard — but their activation now reaches this handler by\n // bubbling, so there is one code path and no double-fire. Anything that\n // is its own action (rename, close, the name editor) stops the event.\n onClick={() => !editing && onSelect?.(row)}\n className={cn(\n 'group flex cursor-pointer flex-col gap-0.5 text-left transition-colors',\n rowShapeClass(active === true),\n )}>\n {/* Line one: how it is doing, then what you scan the list by, then how\n long since it moved.\n\n State leads. It used to sit last, on the argument that the title is\n what you read and the state is what you glance at — but a glance wants\n a *column*, and a trailing glyph has no fixed x: it lands wherever the\n age and the ring leave it. Leading, every row's state stacks into one\n scannable strip, and the mark on line two lands under it in the same\n gutter. This is the VS Code sidebar's shape, and all three clients are\n on it now. */}\n <div className='flex items-center gap-1.5'>\n <Gutter>\n <SessionStatusIcon row={row} />\n </Gutter>\n {/* The editor replaces the link rather than sitting inside it — an\n input nested in a button is invalid, and disabling the button to\n protect the edit disables the field with it. */}\n {editing && onRename ? (\n <NameEditor\n initial={info.title ?? ''}\n onCommit={(title) => {\n setEditing(false)\n onRename(row, title)\n }}\n onCancel={() => setEditing(false)}\n />\n ) : (\n <button\n type='button'\n className={cn(\n 'min-w-0 flex-1 truncate text-left text-body-sm outline-none',\n active ? 'font-medium text-fg-1' : 'text-fg-2',\n )}>\n {sessionLabel(info)}\n </button>\n )}\n {row.unseen > 0 ? (\n // Transcript rows since this session was last on screen — the same\n // unit the VS Code badge counts, because turns undercount badly.\n <span\n title={`${row.unseen} new`}\n className='shrink-0 rounded-full bg-accent px-1.5 text-label text-accent-fg'>\n {row.unseen}\n </span>\n ) : null}\n <span className='shrink-0 text-label text-fg-4'>\n {formatRelativeTime(info.lastActivityAt ?? info.createdAt)}\n </span>\n <ContextRing usage={info.contextUsage} />\n </div>\n\n {/* Line two: what it is — the engine's mark and its model in the vendor's\n own colour, then where it runs — with the actions at the far right,\n away from the title you are actually reading. */}\n <div className='flex items-center gap-1 text-label text-fg-4'>\n <Gutter>\n {/* The colour goes on the icon, not on this cell: the svg ships its\n own `text-fg-3` and only a class on the element itself merges over\n it. */}\n <EngineIcon\n engine={engine}\n model={info.model}\n className={vendorMarkClass(engine, info.model)}\n />\n </Gutter>\n <button\n type='button'\n tabIndex={-1}\n className='min-w-0 flex-1 truncate text-left font-mono outline-none'>\n <span className={vendorTextClass(engine, info.model)}>\n {/* The model id as a person says it — `claude-opus-5[1m]` is a wire\n value, and a list line has no room for a context-window suffix. */}\n {friendlyModel(info.model)}\n </span>\n {details.length > 0 ? ' · ' : ''}\n {cut < 0 ? (\n details.join(' · ')\n ) : (\n <>\n {details.slice(0, cut).map((part) => `${part} · `)}\n <ProjectIcon\n icon={projectIcon}\n src={iconSrcOf(row, projectIcons)}\n name={project}\n /* Nudged onto the text baseline: a 12px glyph box against an\n 11px line sits a hair proud without it. */\n className='mr-1 align-[-0.2em]'\n />\n {details.slice(cut).join(' · ')}\n </>\n )}\n </button>\n {steps.length > 0 ? (\n <StepToggle\n expanded={expanded}\n running={runningSteps(steps)}\n total={steps.length}\n noun={steps[0]!.noun}\n onToggle={() => setExpanded((v) => !v)}\n />\n ) : null}\n {onRename && !editing ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Rename session'\n className='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n setEditing(true)\n }}>\n <Pencil className='size-3 text-fg-3' />\n </Button>\n ) : null}\n {onDelete ? (\n <Button\n variant='ghost'\n size='icon-sm'\n aria-label='Close session'\n className='size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100'\n onClick={(e) => {\n e.stopPropagation()\n onDelete(row)\n }}>\n <Trash2 className='size-3 text-fg-3' />\n </Button>\n ) : null}\n </div>\n {expanded ? steps.map((step) => <StepRow key={step.key} step={step} onSelect={step.onSelect} />) : null}\n </div>\n )\n}\n\n/**\n * The one 14px cell both lines hang their glyph in.\n *\n * A shared column rather than a leading character: the state and the engine mark\n * are different widths and a bare glyph would let the two text columns start at\n * different x. Centring inside a fixed cell is what makes them agree —\n * `packages/ui`'s terminal-gutter argument at row scale.\n */\nfunction Gutter({ children }: { children: ReactNode }) {\n return <span className='flex w-3.5 shrink-0 items-center justify-center'>{children}</span>\n}\n\n/**\n * State as one glyph — a ringing bell when it wants a human, a spinner while it\n * works, a moon when it is only sleeping. Replaces the text badge: in a sidebar\n * the word costs more room than it earns, and the states that matter are the two\n * you can recognise without reading.\n *\n * **It reads `row.state`, not `info.status`, and that distinction is the whole\n * point of the row model.** `sessionState` already folds in the arm this glyph\n * cannot see for itself: a *background* sub-agent outlives its turn by design, so\n * the turn ends, `status` comes to rest at `idle`, and the agent keeps working.\n * Reading the raw status drew a **moon on a row filed under the \"Working\"\n * header** — the list contradicting itself on one line, which is exactly what a\n * derived view model exists to prevent. The value was in scope and unread.\n *\n * The terminal statuses still come off `info.status`, because `ended` collapses\n * `failed` and `closed` into one bucket and those are worth telling apart here.\n */\nexport function SessionStatusIcon({ row }: { row: SessionRow }) {\n const { info } = row\n if (row.state === 'attention') {\n return <BellRing className='size-3.5 shrink-0 animate-pulse text-warning' />\n }\n if (row.state === 'working') {\n return <Spinner className='size-3.5 shrink-0 text-info' />\n }\n switch (info.status) {\n case 'failed':\n return <CircleAlert className='size-3.5 shrink-0 text-danger' />\n case 'closed':\n return <CircleSlash className='size-3.5 shrink-0 text-fg-4' />\n case 'parked':\n return <PauseCircle className='size-3.5 shrink-0 text-fg-3' />\n default:\n return <Moon className='size-3.5 shrink-0 text-fg-4' />\n }\n}\n\n/**\n * Inline rename. Enter commits, Escape cancels, blur commits — but only a blur\n * that is still inside this document: switching windows must not silently commit,\n * nor kill the editor in the frame it opened.\n */\nfunction NameEditor({\n initial,\n onCommit,\n onCancel,\n}: {\n initial: string\n onCommit: (title: string) => void\n onCancel: () => void\n}) {\n const [value, setValue] = useState(initial)\n const ref = useRef<HTMLInputElement>(null)\n useEffect(() => {\n ref.current?.select()\n }, [])\n return (\n <input\n ref={ref}\n value={value}\n autoFocus\n aria-label='Session name'\n // The row is a button; a click in here must not select the session.\n onClick={(e) => e.stopPropagation()}\n onChange={(e) => setValue(e.target.value)}\n onBlur={() => (document.hasFocus() ? onCommit(value.trim()) : undefined)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') onCommit(value.trim())\n else if (e.key === 'Escape') onCancel()\n e.stopPropagation()\n }}\n className='-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-0.5 text-body-sm font-medium text-fg-1 outline-none'\n />\n )\n}\n\n/** A multi-select facet. Empty = no filter, which is why the trigger reads the\n * facet's name rather than \"All\": nothing is being excluded. */\n/**\n * One labelled control row. The label column is fixed so every control starts\n * on the same x — the thing that makes a stack of them read as a form rather\n * than as five unrelated widgets.\n */\nfunction FilterRow({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className='flex items-center gap-2'>\n <span aria-hidden className='w-14 shrink-0 truncate text-label text-fg-3'>\n {label}\n </span>\n <div className='min-w-0 flex-1'>{children}</div>\n </div>\n )\n}\n\nfunction FacetSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string[]\n options: { value: string; label: string }[]\n onChange: (value: string[]) => void\n}) {\n return (\n <div className='flex items-center gap-1'>\n <Select multiple value={value} onValueChange={(v) => onChange(v as string[])}>\n <SelectTrigger aria-label={label} className='min-w-0 flex-1'>\n <SelectValue>\n {/* \"All\" rather than the label, which the row already carries. */}\n {value.length === 0\n ? 'All'\n : value.length === 1\n ? (options.find((o) => o.value === value[0])?.label ?? 'All')\n : `${value.length} selected`}\n </SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {value.length > 0 ? (\n <Button variant='ghost' size='icon-sm' aria-label={`Clear ${label}`} onClick={() => onChange([])}>\n <X className='size-3 text-fg-4' />\n </Button>\n ) : null}\n </div>\n )\n}\n\nfunction OneOfSelect({\n label,\n value,\n options,\n onChange,\n}: {\n label: string\n value: string\n options: readonly { value: string; label: string }[]\n onChange: (value: string) => void\n}) {\n return (\n <Select value={value} onValueChange={(v) => onChange(v as string)}>\n <SelectTrigger aria-label={label} className='w-full min-w-0'>\n <SelectValue>{options.find((o) => o.value === value)?.label ?? label}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n <SelectItemText>{option.label}</SelectItemText>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n"],"mappings":"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"}
|
|
@@ -157,6 +157,158 @@ function friendlyModel(id) {
|
|
|
157
157
|
return [version.length > 0 ? `${name}${family?.joiner ?? " "}${version.join(".")}` : name, ...words].join(" ");
|
|
158
158
|
}
|
|
159
159
|
//#endregion
|
|
160
|
-
|
|
160
|
+
//#region src/lib/status.ts
|
|
161
|
+
const STATUS_META = {
|
|
162
|
+
starting: {
|
|
163
|
+
icon: "loading~spin",
|
|
164
|
+
label: "Starting",
|
|
165
|
+
severity: "none"
|
|
166
|
+
},
|
|
167
|
+
running: {
|
|
168
|
+
icon: "loading~spin",
|
|
169
|
+
label: "Running",
|
|
170
|
+
severity: "none"
|
|
171
|
+
},
|
|
172
|
+
awaiting_approval: {
|
|
173
|
+
icon: "warning",
|
|
174
|
+
label: "Needs approval",
|
|
175
|
+
severity: "warning"
|
|
176
|
+
},
|
|
177
|
+
idle: {
|
|
178
|
+
icon: "check",
|
|
179
|
+
label: "Idle",
|
|
180
|
+
severity: "none"
|
|
181
|
+
},
|
|
182
|
+
parked: {
|
|
183
|
+
icon: "debug-pause",
|
|
184
|
+
label: "Parked",
|
|
185
|
+
severity: "none"
|
|
186
|
+
},
|
|
187
|
+
failed: {
|
|
188
|
+
icon: "error",
|
|
189
|
+
label: "Failed",
|
|
190
|
+
severity: "error"
|
|
191
|
+
},
|
|
192
|
+
closed: {
|
|
193
|
+
icon: "circle-slash",
|
|
194
|
+
label: "Closed",
|
|
195
|
+
severity: "none"
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* The status slot, connection first. A session status held over a dead socket is
|
|
200
|
+
* the last thing we heard, not the current state — so a lost link takes the slot
|
|
201
|
+
* rather than letting "Running" imply a turn is still streaming.
|
|
202
|
+
*/
|
|
203
|
+
function statusPresentation(vitals) {
|
|
204
|
+
if (!vitals) return {
|
|
205
|
+
icon: "hubot",
|
|
206
|
+
label: "Connecting…",
|
|
207
|
+
severity: "none"
|
|
208
|
+
};
|
|
209
|
+
if (vitals.connection === "offline") return {
|
|
210
|
+
icon: "debug-disconnect",
|
|
211
|
+
label: "Offline",
|
|
212
|
+
severity: "error"
|
|
213
|
+
};
|
|
214
|
+
if (vitals.connection === "reconnecting") return {
|
|
215
|
+
icon: "sync~spin",
|
|
216
|
+
label: "Reconnecting…",
|
|
217
|
+
severity: "warning"
|
|
218
|
+
};
|
|
219
|
+
return STATUS_META[vitals.status] ?? {
|
|
220
|
+
icon: "hubot",
|
|
221
|
+
label: vitals.status,
|
|
222
|
+
severity: "none"
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/** 0–100 → the colour a meter wears. One pair of thresholds for every surface. */
|
|
226
|
+
function meterSeverity(pct) {
|
|
227
|
+
if (pct === void 0) return "none";
|
|
228
|
+
if (pct >= 95) return "error";
|
|
229
|
+
if (pct >= 80) return "warning";
|
|
230
|
+
return "none";
|
|
231
|
+
}
|
|
232
|
+
/** The window a lane points at, or `undefined` when this account has none. */
|
|
233
|
+
function usageWindow(rateLimits, lane) {
|
|
234
|
+
if (!rateLimits) return void 0;
|
|
235
|
+
if (lane === "session") {
|
|
236
|
+
const info = rateLimits.five_hour;
|
|
237
|
+
return info ? {
|
|
238
|
+
key: "five_hour",
|
|
239
|
+
info
|
|
240
|
+
} : void 0;
|
|
241
|
+
}
|
|
242
|
+
if (lane === "weekly") {
|
|
243
|
+
const info = rateLimits.seven_day;
|
|
244
|
+
return info ? {
|
|
245
|
+
key: "seven_day",
|
|
246
|
+
info
|
|
247
|
+
} : void 0;
|
|
248
|
+
}
|
|
249
|
+
return tightestWindow(Object.fromEntries(Object.entries(rateLimits).filter(([key]) => key.startsWith("seven_day_") && key !== "seven_day_oauth_apps")));
|
|
250
|
+
}
|
|
251
|
+
/** The rate-limit window that gets the one visible slot: whichever is fullest,
|
|
252
|
+
* since the binding constraint is the one worth glancing at. Still the right
|
|
253
|
+
* rule for a surface with exactly one slot; {@link usageWindow} is for one with
|
|
254
|
+
* three. */
|
|
255
|
+
function tightestWindow(rateLimits) {
|
|
256
|
+
const entries = Object.entries(rateLimits ?? {});
|
|
257
|
+
if (entries.length === 0) return void 0;
|
|
258
|
+
let best;
|
|
259
|
+
for (const [key, info] of entries) if ((info.status === "rejected" ? Number.POSITIVE_INFINITY : info.utilization ?? -1) > (best === void 0 ? Number.NEGATIVE_INFINITY : best.info.status === "rejected" ? Number.POSITIVE_INFINITY : best.info.utilization ?? -1)) best = {
|
|
260
|
+
key,
|
|
261
|
+
info
|
|
262
|
+
};
|
|
263
|
+
return best;
|
|
264
|
+
}
|
|
265
|
+
/** A rate-limit window's key, named for a human. A model-scoped bucket is named
|
|
266
|
+
* for its model alone (`seven_day_fable` → "Fable"): the lane it sits in
|
|
267
|
+
* already says weekly, and "Seven day fable" in a status bar is three words to
|
|
268
|
+
* say one. */
|
|
269
|
+
function windowLabel(key) {
|
|
270
|
+
if (key === "five_hour") return "Session";
|
|
271
|
+
if (key === "seven_day") return "Weekly";
|
|
272
|
+
const words = (key.startsWith("seven_day_") ? key.slice(10) : key).replaceAll("_", " ");
|
|
273
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* The catalog row a session is actually running, or `undefined` for a model the
|
|
277
|
+
* list doesn't name. Matched leniently: a session reports the *resolved* id
|
|
278
|
+
* (`claude-sonnet-5`) where the row may be keyed on the alias (`sonnet`), and
|
|
279
|
+
* either can carry a `[1m]` context-window suffix.
|
|
280
|
+
*/
|
|
281
|
+
function currentModel(vitals) {
|
|
282
|
+
const id = vitals?.model;
|
|
283
|
+
if (!id) return void 0;
|
|
284
|
+
const bare = (value) => value.replace(/\[.*\]$/, "");
|
|
285
|
+
const wanted = bare(id);
|
|
286
|
+
return vitals.models.find((m) => bare(m.value) === wanted || m.resolvedModel && bare(m.resolvedModel) === wanted);
|
|
287
|
+
}
|
|
288
|
+
/** A session's model, named the way the picker names it. Falls back to the raw
|
|
289
|
+
* id, and to "Default" while the session is on the CLI's own pick. */
|
|
290
|
+
function modelLabel(vitals) {
|
|
291
|
+
if (!vitals?.model) return "Default";
|
|
292
|
+
return currentModel(vitals)?.displayName ?? vitals.model;
|
|
293
|
+
}
|
|
294
|
+
/** Context percentage as its meter severity — the reading and the colour come
|
|
295
|
+
* from one place so a panel and a status bar never disagree.
|
|
296
|
+
*
|
|
297
|
+
* Takes only the number it reads, so the compact `ContextReading` that rides
|
|
298
|
+
* the sessions list and the full `ContextUsage` on the event stream are coloured
|
|
299
|
+
* by one rule rather than two. */
|
|
300
|
+
function contextSeverity(usage) {
|
|
301
|
+
return meterSeverity(usage?.percentage);
|
|
302
|
+
}
|
|
303
|
+
/** {@link meterSeverity} as a text colour class. Here rather than beside each
|
|
304
|
+
* meter because the status bar, the context dialog and a sessions-list row all
|
|
305
|
+
* paint the same reading, and a fourth copy of the thresholds is a fourth
|
|
306
|
+
* chance for one surface to call 81% orange and another call it grey. */
|
|
307
|
+
function meterColorClass(pct) {
|
|
308
|
+
const severity = meterSeverity(pct);
|
|
309
|
+
return severity === "error" ? "text-danger" : severity === "warning" ? "text-warning" : "text-fg-3";
|
|
310
|
+
}
|
|
311
|
+
//#endregion
|
|
312
|
+
export { formatRelativeTime as _, modelLabel as a, rateLimitWindowSeconds as b, usageWindow as c, formatBytes as d, formatCost as f, formatRateLimitWindowLong as g, formatRateLimitWindow as h, meterSeverity as i, windowLabel as l, formatDuration as m, currentModel as n, statusPresentation as o, formatCountdown as p, meterColorClass as r, tightestWindow as s, contextSeverity as t, formatAgoPrecise as u, formatTokens as v, toolInputPreview as x, friendlyModel as y };
|
|
161
313
|
|
|
162
|
-
//# sourceMappingURL=
|
|
314
|
+
//# sourceMappingURL=status-BE-zg88x.mjs.map
|