@workerdeck/ui 0.20.0 → 0.21.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/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { $ as toolIcon, A as PERMISSION_MODES, At as MenuSeparator, B as skillPrompt, Bt as badgeVariants, C as TerminalQuestionPrompt, Ct as DialogContent, D as parseUserQuestions, Dt as Menu, E as QuestionPrompt, Et as DialogTrigger, F as SkillsDialog, Ft as SelectItemText, G as Band, H as TerminalMarkdown, Ht as buttonVariants, I as McpDialog, It as SelectTrigger, J as Row, K as Blank, L as HostFilesDialog, Lt as SelectValue, M as permissionModeChoices, Mt as Select, N as permissionModeMeta, Nt as SelectContent, O as SubagentStrip, Ot as MenuContent, P as ModelSelect, Pt as SelectItem, Q as isMutatingTool, R as ContextDialog, Rt as Input, S as SessionInfoDialog, St as DialogClose, T as QUESTION_BEHAVIORS, Tt as DialogRow, U as TerminalDiff, Ut as cn, V as TerminalSurface, Vt as Button, W as previewPatch, X as WithActions, Y as CopyAction, Z as useAffordances, _ as Conversation, _t as Tip, a as Transcript, at as hashtagTrigger, b as StatusBar, bt as Dialog, c as ToolCallCard, ct as PromptArea, d as Response, dt as ProgressRing, et as TranscriptDensityProvider, f as PromptTokenText, ft as Splitter, g as FileCard, gt as copyText, h as Loader, ht as CopyButton, i as useMinuteClock, it as commandTrigger, j as PermissionModeSelect, jt as MenuTrigger, k as PermissionPrompt, kt as MenuItem, l as SessionEmptyState, lt as plainTextToSegments, m as MessageContent, mt as Spinner, n as UsageDialog, nt as useTranscriptDensity, o as TerminalItemView, ot as mentionTrigger, p as Message, pt as CodeBlock, q as Ink, r as UsageMeters, rt as useTranscriptVariant, s as TerminalTranscript, st as usePromptAreaState, t as SessionPanel, tt as TranscriptVariantProvider, u as Reasoning, ut as segmentsToPlainText, v as ConversationContent, vt as TooltipContent, w as TerminalPermissionPrompt, wt as DialogHeader, x as STATUS_META, xt as DialogBody, y as ConversationScrollButton, yt as TooltipProvider, z as Composer, zt as Badge } from "./SessionPanel-CHktI2CF.mjs";
1
+ import { $ as toolIcon, A as PERMISSION_MODES, At as MenuSeparator, B as skillPrompt, Bt as badgeVariants, C as TerminalQuestionPrompt, Ct as DialogContent, D as parseUserQuestions, Dt as Menu, E as QuestionPrompt, Et as DialogTrigger, F as SkillsDialog, Ft as SelectItemText, G as Band, H as TerminalMarkdown, Ht as buttonVariants, I as McpDialog, It as SelectTrigger, J as Row, K as Blank, L as HostFilesDialog, Lt as SelectValue, M as permissionModeChoices, Mt as Select, N as permissionModeMeta, Nt as SelectContent, O as SubagentStrip, Ot as MenuContent, P as ModelSelect, Pt as SelectItem, Q as isMutatingTool, R as ContextDialog, Rt as Input, S as SessionInfoDialog, St as DialogClose, T as QUESTION_BEHAVIORS, Tt as DialogRow, U as TerminalDiff, Ut as cn, V as TerminalSurface, Vt as Button, W as previewPatch, X as WithActions, Y as CopyAction, Z as useAffordances, _ as Conversation, _t as Tip, a as Transcript, at as hashtagTrigger, b as StatusBar, bt as Dialog, c as ToolCallCard, ct as PromptArea, d as Response, dt as ProgressRing, et as TranscriptDensityProvider, f as PromptTokenText, ft as Splitter, g as FileCard, gt as copyText, h as Loader, ht as CopyButton, i as useMinuteClock, it as commandTrigger, j as PermissionModeSelect, jt as MenuTrigger, k as PermissionPrompt, kt as MenuItem, l as SessionEmptyState, lt as plainTextToSegments, m as MessageContent, mt as Spinner, n as UsageDialog, nt as useTranscriptDensity, o as TerminalItemView, ot as mentionTrigger, p as Message, pt as CodeBlock, q as Ink, r as UsageMeters, rt as useTranscriptVariant, s as TerminalTranscript, st as usePromptAreaState, t as SessionPanel, tt as TranscriptVariantProvider, u as Reasoning, ut as segmentsToPlainText, v as ConversationContent, vt as TooltipContent, w as TerminalPermissionPrompt, wt as DialogHeader, x as STATUS_META, xt as DialogBody, y as ConversationScrollButton, yt as TooltipProvider, z as Composer, zt as Badge } from "./SessionPanel-DNuPKg5c.mjs";
2
2
  import { _ as formatRelativeTime, a as modelLabel, b as rateLimitWindowSeconds, d as formatBytes, f as formatCost, g as formatRateLimitWindowLong, h as formatRateLimitWindow, i as meterSeverity, l as windowLabel, m as formatDuration, o as statusPresentation, p as formatCountdown, r as meterColorClass, s as tightestWindow, t as contextSeverity, u as formatAgoPrecise, v as formatTokens, x as toolInputPreview, y as friendlyModel } from "./status-BE-zg88x.mjs";
3
- import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
3
+ import { Fragment, forwardRef, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { Anchor, ArrowRight, Atom, Beaker, BellRing, Bike, Binary, Blocks, Bot, Box, Brain, Briefcase, Brush, Bug, Building2, Cable, Camera, Car, Cat, Check, ChevronDown, ChevronRight, CircleAlert, CircleSlash, CircuitBoard, Cloud, Code, Coffee, Cog, Compass, Container, Cpu, CreditCard, Database, Diamond, Dog, Dot, Eraser, Feather, Film, Flame, FlaskConical, Folder, Gamepad2, Gauge, Gem, Ghost, Gift, GitBranch, Globe, GraduationCap, Hammer, Hexagon, House, Image, Key, Landmark, Laptop, Layers, Leaf, Library, Lightbulb, Lock, Mail, Map as Map$1, MessageCircle, Mic, Monitor, Moon, Mountain, Music, Network, Newspaper, Notebook, Orbit, Package, Palette, PauseCircle, PenTool, Pencil, Phone, PiggyBank, Plane, Plug, Puzzle, Radar, Radio, Receipt, Rocket, Ruler, Satellite, Scale, Scissors, Search, SearchX, Server, Shield, Ship, ShoppingCart, Signal, Smartphone, Sparkles, Sprout, Square, Star, Sun, Table, Tag, Target, Telescope, Terminal, TestTube, Trash2, TreePine, Trophy, Truck, Umbrella, Users, Wallet, Wand, Waves, Webhook, Wifi, Wrench, X, Zap } from "lucide-react";
6
6
  import { AlertDialog as AlertDialog$1 } from "@base-ui/react/alert-dialog";
@@ -310,150 +310,6 @@ function SessionList({ sessions, activeId, onSelect, onDelete, emptyText = "No s
310
310
  });
311
311
  }
312
312
  //#endregion
313
- //#region src/components/agent/ContextRing.tsx
314
- /**
315
- * How full a session's context window is, sized for a **list row**.
316
- *
317
- * The reading the session screen has always drawn, moved somewhere it answers a
318
- * question the session screen cannot: *which* of these thirty sessions is
319
- * bloating. That is also why it is a ring and not a number — across a list, a
320
- * ring is read at a glance and `71%` has to be read one row at a time.
321
- *
322
- * **Absent draws nothing, and absent is not zero.** A promptless session, a
323
- * parked one from before the field existed, or an engine that reports no window
324
- * has no reading; an empty ring would claim its context is empty rather than
325
- * unknown. The title carries the exact numbers for anyone who wants them.
326
- *
327
- * Shared rather than spelled per client: the dashboard's row and the VS Code
328
- * sidebar's card both draw it, and two copies would be two sets of thresholds
329
- * the day one of them is nudged.
330
- */
331
- function ContextRing({ usage, size = 11, className }) {
332
- if (usage === void 0) return null;
333
- return /* @__PURE__ */ jsx("span", {
334
- title: `Context ${usage.percentage.toFixed(0)}% · ${formatTokens(usage.totalTokens)} / ${formatTokens(usage.maxTokens)}`,
335
- className: cn("shrink-0 leading-none", meterColorClass(usage.percentage), className),
336
- children: /* @__PURE__ */ jsx(ProgressRing, {
337
- value: usage.percentage,
338
- size,
339
- strokeWidth: 2
340
- })
341
- });
342
- }
343
- //#endregion
344
- //#region src/components/agent/EngineIcon.tsx
345
- /**
346
- * The engine's own mark, monochrome.
347
- *
348
- * Inlined rather than depended on: the React package these come from
349
- * (`@lobehub/icons`) pulls antd, `@lobehub/ui` and a second copy of
350
- * lucide-react — an absurd amount of bundle for a few glyphs in a sidebar.
351
- * These are paths from `@lobehub/icons-static-svg` (MIT, © LobeHub), already
352
- * `fill="currentColor"` single-path marks, so they take the row's colour like
353
- * any other glyph and need no light/dark variants.
354
- *
355
- * The marks are trademarks of their owners (Anthropic, OpenAI, Google,
356
- * DeepSeek, Moonshot), used here only to identify which engine a session runs
357
- * on.
358
- *
359
- * Codex is drawn as OpenAI: the session's engine *is* OpenAI's, and OpenAI's
360
- * mark is the one a person recognises at 12px.
361
- */
362
- const PATHS = {
363
- claude: {
364
- title: "Claude",
365
- 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"
366
- },
367
- codex: {
368
- title: "OpenAI",
369
- 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"
370
- },
371
- gemini: {
372
- title: "Gemini",
373
- 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"
374
- },
375
- deepseek: {
376
- title: "DeepSeek",
377
- 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"
378
- },
379
- moonshot: {
380
- title: "Moonshot",
381
- 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"
382
- }
383
- };
384
- /**
385
- * Which mark a session wears.
386
- *
387
- * The two first-party engines are named directly. A `provider` session is
388
- * whatever the host wired up, so its *model* is the only thing that says whose
389
- * it is — sniffed loosely on purpose (`gemini-2.5-pro`, `deepseek-chat`,
390
- * `kimi-k2`), and falling through to no mark rather than guessing wrong.
391
- */
392
- function engineMark(engine, model) {
393
- if (engine === "claude") return "claude";
394
- if (engine === "codex") return "codex";
395
- const id = model?.toLowerCase() ?? "";
396
- if (!id) return void 0;
397
- if (id.includes("gemini")) return "gemini";
398
- if (id.includes("deepseek")) return "deepseek";
399
- if (id.includes("moonshot") || id.includes("kimi")) return "moonshot";
400
- if (id.includes("claude")) return "claude";
401
- if (id.startsWith("gpt") || id.startsWith("o1") || id.startsWith("o3") || id.includes("openai")) return "codex";
402
- }
403
- /**
404
- * Which engines wear their vendor's colour, and **how far it reaches**.
405
- *
406
- * `MARK` is the glyph, `TEXT` is the model name beside it. They are the same for
407
- * Anthropic and deliberately not for OpenAI, because the two brands differ in
408
- * kind rather than in hue: coral is an accent, and OpenAI's guidelines forbid
409
- * adding colour to the mark at all, so theirs is pure white/black. At full
410
- * contrast that is right on a 12px glyph and wrong on an 11px label — a
411
- * pure-white model name is *brighter than the session title above it*, which
412
- * inverts the row's whole hierarchy to say something the mark has already said.
413
- * So OpenAI's name keeps the muted line it always had, which is also the most
414
- * literal reading of "don't add any colors".
415
- *
416
- * Anything absent falls through to muted on both counts.
417
- */
418
- const VENDOR_MARK = {
419
- claude: "text-vendor-claude",
420
- codex: "text-vendor-openai"
421
- };
422
- const VENDOR_TEXT = { claude: "text-vendor-claude" };
423
- /** The colour a session's engine mark wears. Pass it to `EngineIcon`'s
424
- * `className` rather than to a parent: the svg carries its own `text-fg-3`,
425
- * and only a class on the element itself is merged over it. */
426
- function vendorMarkClass(engine, model) {
427
- return VENDOR_MARK[engineMark(engine, model) ?? ""] ?? "text-fg-3";
428
- }
429
- /** The colour the model *name* wears beside that mark — muted unless the vendor
430
- * is one whose accent survives at label size. See `VENDOR_TEXT`.
431
- *
432
- * The fallback is `text-fg-3` rather than the metadata line's own `text-fg-4`,
433
- * so an unbranded model still sits one step above the project and gateway
434
- * beside it — the order the spec lists them in, held without a colour. */
435
- function vendorTextClass(engine, model) {
436
- return VENDOR_TEXT[engineMark(engine, model) ?? ""] ?? "text-fg-3";
437
- }
438
- function EngineIcon({ engine, model, className }) {
439
- const mark = PATHS[engineMark(engine, model) ?? ""];
440
- if (!mark) return /* @__PURE__ */ jsx("span", {
441
- "aria-hidden": true,
442
- title: engine,
443
- className: cn("shrink-0 text-fg-3", className),
444
- children: "·"
445
- });
446
- return /* @__PURE__ */ jsxs("svg", {
447
- viewBox: "0 0 24 24",
448
- fill: "currentColor",
449
- fillRule: "evenodd",
450
- role: "img",
451
- "aria-label": mark.title,
452
- className: cn("size-3 shrink-0 text-fg-3", className),
453
- children: [/* @__PURE__ */ jsx("title", { children: mark.title }), /* @__PURE__ */ jsx("path", { d: mark.d })]
454
- });
455
- }
456
- //#endregion
457
313
  //#region src/components/agent/ProjectIcon.tsx
458
314
  /**
459
315
  * A project's icon — the render side of protocol's `ProjectIcon`, for a list
@@ -624,21 +480,224 @@ function ProjectIcon({ icon, src, name, className }) {
624
480
  className: cn("inline-block size-3 shrink-0 object-contain", className)
625
481
  });
626
482
  }
627
- return /* @__PURE__ */ jsx(GLYPHS[icon.name] ?? Folder, { className: cn("inline-block size-3 shrink-0", className) });
483
+ return /* @__PURE__ */ jsx(GLYPHS[icon.name] ?? Folder, { className: cn("inline-block size-3 shrink-0", className) });
484
+ }
485
+ //#endregion
486
+ //#region src/components/agent/ContextRing.tsx
487
+ /**
488
+ * How full a session's context window is, sized for a **list row**.
489
+ *
490
+ * The reading the session screen has always drawn, moved somewhere it answers a
491
+ * question the session screen cannot: *which* of these thirty sessions is
492
+ * bloating. That is also why it is a ring and not a number — across a list, a
493
+ * ring is read at a glance and `71%` has to be read one row at a time.
494
+ *
495
+ * **Absent draws nothing, and absent is not zero.** A promptless session, a
496
+ * parked one from before the field existed, or an engine that reports no window
497
+ * has no reading; an empty ring would claim its context is empty rather than
498
+ * unknown. The title carries the exact numbers for anyone who wants them.
499
+ *
500
+ * Shared rather than spelled per client: the dashboard's row and the VS Code
501
+ * sidebar's card both draw it, and two copies would be two sets of thresholds
502
+ * the day one of them is nudged.
503
+ */
504
+ function ContextRing({ usage, size = 11, className }) {
505
+ if (usage === void 0) return null;
506
+ return /* @__PURE__ */ jsx("span", {
507
+ title: `Context ${usage.percentage.toFixed(0)}% · ${formatTokens(usage.totalTokens)} / ${formatTokens(usage.maxTokens)}`,
508
+ className: cn("shrink-0 leading-none", meterColorClass(usage.percentage), className),
509
+ children: /* @__PURE__ */ jsx(ProgressRing, {
510
+ value: usage.percentage,
511
+ size,
512
+ strokeWidth: 2
513
+ })
514
+ });
515
+ }
516
+ //#endregion
517
+ //#region src/components/agent/EngineIcon.tsx
518
+ /**
519
+ * The engine's own mark, monochrome.
520
+ *
521
+ * Inlined rather than depended on: the React package these come from
522
+ * (`@lobehub/icons`) pulls antd, `@lobehub/ui` and a second copy of
523
+ * lucide-react — an absurd amount of bundle for a few glyphs in a sidebar.
524
+ * These are paths from `@lobehub/icons-static-svg` (MIT, © LobeHub), already
525
+ * `fill="currentColor"` single-path marks, so they take the row's colour like
526
+ * any other glyph and need no light/dark variants.
527
+ *
528
+ * The marks are trademarks of their owners (Anthropic, OpenAI, Google,
529
+ * DeepSeek, Moonshot), used here only to identify which engine a session runs
530
+ * on.
531
+ *
532
+ * Codex is drawn as OpenAI: the session's engine *is* OpenAI's, and OpenAI's
533
+ * mark is the one a person recognises at 12px.
534
+ */
535
+ const PATHS = {
536
+ claude: {
537
+ title: "Claude",
538
+ 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"
539
+ },
540
+ codex: {
541
+ title: "OpenAI",
542
+ 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"
543
+ },
544
+ gemini: {
545
+ title: "Gemini",
546
+ 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"
547
+ },
548
+ deepseek: {
549
+ title: "DeepSeek",
550
+ 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"
551
+ },
552
+ moonshot: {
553
+ title: "Moonshot",
554
+ 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"
555
+ }
556
+ };
557
+ /**
558
+ * Which mark a session wears.
559
+ *
560
+ * The two first-party engines are named directly. A `provider` session is
561
+ * whatever the host wired up, so its *model* is the only thing that says whose
562
+ * it is — sniffed loosely on purpose (`gemini-2.5-pro`, `deepseek-chat`,
563
+ * `kimi-k2`), and falling through to no mark rather than guessing wrong.
564
+ */
565
+ function engineMark(engine, model) {
566
+ if (engine === "claude") return "claude";
567
+ if (engine === "codex") return "codex";
568
+ const id = model?.toLowerCase() ?? "";
569
+ if (!id) return void 0;
570
+ if (id.includes("gemini")) return "gemini";
571
+ if (id.includes("deepseek")) return "deepseek";
572
+ if (id.includes("moonshot") || id.includes("kimi")) return "moonshot";
573
+ if (id.includes("claude")) return "claude";
574
+ if (id.startsWith("gpt") || id.startsWith("o1") || id.startsWith("o3") || id.includes("openai")) return "codex";
575
+ }
576
+ /**
577
+ * Which engines wear their vendor's colour, and **how far it reaches**.
578
+ *
579
+ * `MARK` is the glyph, `TEXT` is the model name beside it. They are the same for
580
+ * Anthropic and deliberately not for OpenAI, because the two brands differ in
581
+ * kind rather than in hue: coral is an accent, and OpenAI's guidelines forbid
582
+ * adding colour to the mark at all, so theirs is pure white/black. At full
583
+ * contrast that is right on a 12px glyph and wrong on an 11px label — a
584
+ * pure-white model name is *brighter than the session title above it*, which
585
+ * inverts the row's whole hierarchy to say something the mark has already said.
586
+ * So OpenAI's name keeps the muted line it always had, which is also the most
587
+ * literal reading of "don't add any colors".
588
+ *
589
+ * Anything absent falls through to muted on both counts.
590
+ */
591
+ const VENDOR_MARK = {
592
+ claude: "text-vendor-claude",
593
+ codex: "text-vendor-openai"
594
+ };
595
+ const VENDOR_TEXT = { claude: "text-vendor-claude" };
596
+ /** The colour a session's engine mark wears. Pass it to `EngineIcon`'s
597
+ * `className` rather than to a parent: the svg carries its own `text-fg-3`,
598
+ * and only a class on the element itself is merged over it. */
599
+ function vendorMarkClass(engine, model) {
600
+ return VENDOR_MARK[engineMark(engine, model) ?? ""] ?? "text-fg-3";
601
+ }
602
+ /** The colour the model *name* wears beside that mark — muted unless the vendor
603
+ * is one whose accent survives at label size. See `VENDOR_TEXT`.
604
+ *
605
+ * The fallback is `text-fg-3` rather than the metadata line's own `text-fg-4`,
606
+ * so an unbranded model still sits one step above the project and gateway
607
+ * beside it — the order the spec lists them in, held without a colour. */
608
+ function vendorTextClass(engine, model) {
609
+ return VENDOR_TEXT[engineMark(engine, model) ?? ""] ?? "text-fg-3";
610
+ }
611
+ function EngineIcon({ engine, model, className }) {
612
+ const mark = PATHS[engineMark(engine, model) ?? ""];
613
+ if (!mark) return /* @__PURE__ */ jsx("span", {
614
+ "aria-hidden": true,
615
+ title: engine,
616
+ className: cn("shrink-0 text-fg-3", className),
617
+ children: "·"
618
+ });
619
+ return /* @__PURE__ */ jsxs("svg", {
620
+ viewBox: "0 0 24 24",
621
+ fill: "currentColor",
622
+ fillRule: "evenodd",
623
+ role: "img",
624
+ "aria-label": mark.title,
625
+ className: cn("size-3 shrink-0 text-fg-3", className),
626
+ children: [/* @__PURE__ */ jsx("title", { children: mark.title }), /* @__PURE__ */ jsx("path", { d: mark.d })]
627
+ });
628
+ }
629
+ //#endregion
630
+ //#region src/components/agent/SessionStatusIcon.tsx
631
+ /**
632
+ * State as one glyph — a ringing bell when it wants a human, a spinner while it
633
+ * works, a moon when it is only sleeping. Replaces the text badge: in a sidebar
634
+ * the word costs more room than it earns, and the states that matter are the two
635
+ * you can recognise without reading.
636
+ *
637
+ * **It reads `row.state`, not `info.status`, and that distinction is the whole
638
+ * point of the row model.** `sessionState` already folds in the arm this glyph
639
+ * cannot see for itself: a *background* sub-agent outlives its turn by design,
640
+ * so the turn ends, `status` comes to rest at `idle`, and the agent keeps
641
+ * working. Reading the raw status drew a **moon on a row filed under the
642
+ * "Working" header** — the list contradicting itself on one line, which is
643
+ * exactly what a derived view model exists to prevent. The value was in scope
644
+ * and unread.
645
+ *
646
+ * The terminal statuses still come off `info.status`, because `ended` collapses
647
+ * `failed` and `closed` into one bucket and those are worth telling apart here.
648
+ *
649
+ * It lives in its own file rather than inside the dashboard's browser because
650
+ * the extension's cards draw it too, and a second copy there is how the two
651
+ * lists last disagreed about what a parked session looks like.
652
+ */
653
+ function SessionStatusIcon({ row, className }) {
654
+ const { info } = row;
655
+ const size = cn("size-4 shrink-0", className);
656
+ if (row.state === "attention") return /* @__PURE__ */ jsx(BellRing, { className: cn(size, "animate-pulse text-warning") });
657
+ if (row.state === "working") return /* @__PURE__ */ jsx(Spinner, { className: cn(size, "text-info") });
658
+ switch (info.status) {
659
+ case "failed": return /* @__PURE__ */ jsx(CircleAlert, { className: cn(size, "text-danger") });
660
+ case "closed": return /* @__PURE__ */ jsx(CircleSlash, { className: cn(size, "text-fg-4") });
661
+ case "parked": return /* @__PURE__ */ jsx(PauseCircle, { className: cn(size, "text-fg-3") });
662
+ default: return /* @__PURE__ */ jsx(Moon, { className: cn(size, "text-fg-4") });
663
+ }
628
664
  }
629
665
  //#endregion
630
666
  //#region src/components/agent/SessionSteps.tsx
631
- function sessionSteps(info, onSelectSubagent) {
632
- return (info.subagents ?? []).map((sub) => ({
633
- key: sub.toolUseId,
634
- label: subagentLabel(sub),
635
- noun: "agent",
636
- kind: isAgentRecord(sub) ? "agent" : "task",
637
- state: stepState(sub.status),
638
- detail: sub.toolCount > 0 ? String(sub.toolCount) : void 0,
639
- title: `${subagentLabel(sub)} · ${sub.toolCount} tool${sub.toolCount === 1 ? "" : "s"}`,
640
- onSelect: () => onSelectSubagent(sub.toolUseId)
641
- }));
667
+ /**
668
+ * The steps under one session, **agents first**.
669
+ *
670
+ * The two kinds do different things when pressed and are worth different
671
+ * amounts of attention: an agent has work of its own to go and read, a task is a
672
+ * marker in a transcript. Interleaved in dispatch order they read as one
673
+ * undifferentiated list, and the rows you can actually open are scattered
674
+ * through it. Grouped, the openable ones are a block at the top and the markers
675
+ * are a tail you can skip.
676
+ *
677
+ * Stable **within** each group, deliberately: dispatch order is the only order
678
+ * these records have that means anything (it is the order the work was started
679
+ * in), so the sort partitions and never reorders inside a partition.
680
+ *
681
+ * `onSelect` is handed the **kind** as well as the id, because the two kinds go
682
+ * to different places — see `SessionItem`, which is what routes them. Passing
683
+ * only the id is what let a task be opened as if it were an agent, and a framed
684
+ * task id matches no items, so the panel drew an **empty agent view**.
685
+ */
686
+ function sessionSteps(info, onSelect) {
687
+ const steps = (info.subagents ?? []).map((sub) => {
688
+ const kind = isAgentRecord(sub) ? "agent" : "task";
689
+ return {
690
+ key: sub.toolUseId,
691
+ label: subagentLabel(sub),
692
+ noun: "agent",
693
+ kind,
694
+ state: stepState(sub.status),
695
+ detail: sub.toolCount > 0 ? String(sub.toolCount) : void 0,
696
+ title: `${subagentLabel(sub)} · ${sub.toolCount} tool${sub.toolCount === 1 ? "" : "s"}`,
697
+ onSelect: () => onSelect(sub.toolUseId, kind)
698
+ };
699
+ });
700
+ return [...steps.filter((s) => s.kind === "agent"), ...steps.filter((s) => s.kind === "task")];
642
701
  }
643
702
  function stepState(status) {
644
703
  switch (status) {
@@ -674,7 +733,7 @@ function StepToggle({ expanded, running, total, noun, onToggle }) {
674
733
  e.stopPropagation();
675
734
  onToggle();
676
735
  },
677
- className: cn("flex shrink-0 items-center gap-0.5 rounded px-0.5 outline-none", "hover:bg-surface-hover hover:text-fg-2", running > 0 ? "text-info" : "text-fg-4"),
736
+ className: cn("flex shrink-0 items-center gap-0.5 rounded-[4px] py-0.5 pr-1 pl-0.5 outline-none", "text-[0.75rem] leading-3 hover:bg-row-hover hover:text-fg-2", running > 0 ? "text-info" : "text-fg-4"),
678
737
  children: [/* @__PURE__ */ jsx(Chevron, { className: "size-3" }), /* @__PURE__ */ jsx("span", {
679
738
  className: "tabular-nums",
680
739
  children: label
@@ -682,64 +741,276 @@ function StepToggle({ expanded, running, total, noun, onToggle }) {
682
741
  });
683
742
  }
684
743
  /**
685
- * One step under its session. Pressing an **agent** hands the panel over to that
686
- * agent's own work — it is not a session and never becomes one, but it does now
687
- * have a surface (`SessionPanel.openSubagent`). A host that cannot frame one
688
- * falls back to revealing its `Task` row, which was this row's only meaning
689
- * before the takeover existed. A **task** is not pressable at all; see
690
- * {@link Step.kind}.
744
+ * One step under its session **pressable, all of them**, and what a press
745
+ * means is what tells the two kinds apart.
746
+ *
747
+ * Pressing an **agent** hands the panel over to that agent's own work
748
+ * (`SessionPanel.openSubagent`): it is not a session and never becomes one, but
749
+ * it has a surface, so it can be the selected thing. Pressing a **task** selects
750
+ * the *session* and travels to that task's marker inside it — a task is a
751
+ * reference to a place in a transcript, not a thing with a screen, so it can be
752
+ * followed but never held.
691
753
  *
692
- * Divided from the row's header and from each other by a rule rather than by
693
- * indentation: these are a list *inside* the row, and at 11px an indent is not
694
- * enough to say so. The rule is black at 25% so it darkens whatever the row is
695
- * filled with, selected or not, without needing a colour per state.
754
+ * That is a reversal, and a deliberate one. A task used to be inert markup on
755
+ * the argument that "a disabled-looking button still announces itself as one"
756
+ * correct about the markup, wrong about the premise, because there *was* always
757
+ * somewhere to go and the row simply swallowed the click on its way there. A row
758
+ * that looks like a list item, sits in a list, and does nothing when pressed is
759
+ * the worse lie. So every step answers the pointer and every step answers a
760
+ * press; only what the press does differs.
761
+ *
762
+ * Divided from the card's header by **indentation and its own hit shape**, not
763
+ * by a rule. The rules came first, on the argument that at 11px an indent is not
764
+ * enough to say "list inside a row" — and they were right about the reading and
765
+ * wrong about the cost: a stack of hairlines across every open card turned the
766
+ * list into a ledger, and a rule cannot answer a pointer. A step that lights up
767
+ * under the cursor and fills when it is the one on screen says *list* far more
768
+ * plainly than a line between two of them, and it says it while doing the job
769
+ * the rule could not.
770
+ *
771
+ * **Only an agent can wear the selection**, and `active` is guarded on that here
772
+ * rather than trusted from the caller: a host that hands back a task's key is
773
+ * describing where it navigated, not what it selected, and the row must not
774
+ * paint itself blue for it.
775
+ *
776
+ * The hover is `--row-active` — **alpha, not a flat fill** — because this row
777
+ * has to answer the pointer on three different grounds: a transparent card, a
778
+ * blue one (its session is selected), and a grey one (a sibling agent is). A
779
+ * flat value tuned for any of those is wrong on the other two; a tint darkens or
780
+ * lifts whatever it lands on. It is the one place in the list where the alpha
781
+ * token earns its keep.
696
782
  */
697
- function StepRow({ step, onSelect }) {
783
+ function StepRow({ step, active = false, onSelect }) {
698
784
  const agent = step.kind === "agent";
785
+ const selected = active && agent;
699
786
  const body = step.state === "failed" ? "text-danger" : agent ? "text-success" : "text-fg-4";
700
- const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
701
- /* @__PURE__ */ jsx(StepIcon, {
702
- state: step.state,
703
- kind: step.kind
704
- }),
705
- /* @__PURE__ */ jsx("span", {
706
- className: "min-w-0 flex-1 truncate",
707
- children: step.label
708
- }),
709
- step.detail ? /* @__PURE__ */ jsx("span", {
710
- className: "shrink-0 tabular-nums text-fg-4",
711
- children: step.detail
712
- }) : null,
713
- agent ? /* @__PURE__ */ jsx(ArrowRight, { className: "size-3 shrink-0 opacity-60" }) : null
714
- ] });
715
- const shape = "flex w-full items-center gap-2 border-t border-black/25 py-1 pl-3 pr-2 text-left text-label outline-none";
716
- if (!agent) return /* @__PURE__ */ jsx("div", {
717
- title: step.title,
718
- onClick: (e) => e.stopPropagation(),
719
- className: cn(shape, body),
720
- children: content
721
- });
722
- return /* @__PURE__ */ jsx("button", {
787
+ return /* @__PURE__ */ jsxs("button", {
723
788
  type: "button",
724
789
  title: step.title,
790
+ "aria-current": selected || void 0,
725
791
  onClick: (e) => {
726
792
  e.stopPropagation();
727
793
  onSelect();
728
794
  },
729
- className: cn(shape, "hover:bg-surface-hover", body),
730
- children: content
795
+ className: cn("flex w-full items-center gap-1.5 rounded-[4px] py-1 pr-2.5 pl-3.5", "text-left text-micro outline-none", selected ? "bg-row-selected" : "hover:bg-row-active", body),
796
+ children: [
797
+ /* @__PURE__ */ jsx(StepIcon, {
798
+ state: step.state,
799
+ kind: step.kind
800
+ }),
801
+ /* @__PURE__ */ jsx("span", {
802
+ className: "min-w-0 flex-1 truncate",
803
+ children: step.label
804
+ }),
805
+ step.detail ? /* @__PURE__ */ jsx("span", {
806
+ className: "shrink-0 tabular-nums text-fg-4",
807
+ children: step.detail
808
+ }) : null,
809
+ agent ? /* @__PURE__ */ jsx(ArrowRight, { className: "size-3.5 shrink-0 text-fg-4" }) : null
810
+ ]
731
811
  });
732
812
  }
733
813
  function StepIcon({ state, kind }) {
734
- if (kind === "task" && state !== "running" && state !== "failed") return /* @__PURE__ */ jsx(Dot, { className: "size-3 shrink-0" });
814
+ if (kind === "task" && state !== "running" && state !== "failed") return /* @__PURE__ */ jsx(Dot, { className: "size-[11px] shrink-0" });
735
815
  switch (state) {
736
- case "running": return /* @__PURE__ */ jsx(Spinner, { className: "size-3 shrink-0" });
737
- case "failed": return /* @__PURE__ */ jsx(CircleAlert, { className: "size-3 shrink-0" });
738
- case "pending": return /* @__PURE__ */ jsx(PauseCircle, { className: "size-3 shrink-0" });
739
- default: return /* @__PURE__ */ jsx(Check, { className: "size-3 shrink-0" });
816
+ case "running": return /* @__PURE__ */ jsx(Spinner, { className: "size-[11px] shrink-0" });
817
+ case "failed": return /* @__PURE__ */ jsx(CircleAlert, { className: "size-[11px] shrink-0" });
818
+ case "pending": return /* @__PURE__ */ jsx(PauseCircle, { className: "size-[11px] shrink-0" });
819
+ default: return /* @__PURE__ */ jsx(Check, { className: "size-[11px] shrink-0" });
740
820
  }
741
821
  }
742
822
  //#endregion
823
+ //#region src/components/agent/SessionItem.tsx
824
+ function SessionItem({ row, active = false, activeStepKey, showGateway, showProject = true, projectIcons, expanded, onExpandedChange, onSelect, onSelectSubagent, onRevealStep, onRename, renameOn = "doubleClick", editing, onEditingChange, actions, className }) {
825
+ const { info } = row;
826
+ const [ownExpanded, setOwnExpanded] = useState(false);
827
+ const open = expanded ?? ownExpanded;
828
+ const setOpen = (next) => {
829
+ setOwnExpanded(next);
830
+ onExpandedChange?.(next);
831
+ };
832
+ const [ownEditing, setOwnEditing] = useState(false);
833
+ const isEditing = editing ?? ownEditing;
834
+ const setEditing = (next) => {
835
+ setOwnEditing(next);
836
+ onEditingChange?.(next);
837
+ };
838
+ const engine = info.engine ?? "claude";
839
+ const project = showProject ? projectLabel(row) : projectSubpath(row);
840
+ const projectIcon = showProject ? info.project?.icon : void 0;
841
+ const iconSrc = projectIcon?.type === "image" ? projectIcons?.[projectIcon.hash] : void 0;
842
+ const cost = formatCost(info.totalCostUsd);
843
+ const extras = [
844
+ showGateway ? row.hostName : void 0,
845
+ info.profile ? `@${info.profile}` : void 0,
846
+ cost === "—" ? void 0 : cost
847
+ ].filter((part) => Boolean(part));
848
+ const model = friendlyModel(info.model);
849
+ const parts = [];
850
+ if (model) parts.push(/* @__PURE__ */ jsx("span", {
851
+ className: vendorTextClass(engine, info.model),
852
+ children: model
853
+ }, "model"));
854
+ if (project !== void 0) parts.push(/* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx(ProjectIcon, {
855
+ icon: projectIcon,
856
+ src: iconSrc,
857
+ name: project,
858
+ className: "mr-1.5 size-4 align-[-0.3em]"
859
+ }), project] }, "project"));
860
+ for (const extra of extras) parts.push(/* @__PURE__ */ jsx("span", { children: extra }, extra));
861
+ const steps = sessionSteps(info, (toolUseId, kind) => {
862
+ if (kind === "agent") return onSelectSubagent ? onSelectSubagent(toolUseId) : onSelect?.();
863
+ return onRevealStep ? onRevealStep(toolUseId) : onSelect?.();
864
+ });
865
+ const holdsOpenAgent = steps.some((s) => s.kind === "agent" && s.key === activeStepKey);
866
+ return /* @__PURE__ */ jsxs("div", {
867
+ "data-slot": "session-item",
868
+ "data-active": active || void 0,
869
+ role: "button",
870
+ tabIndex: 0,
871
+ onClick: (e) => {
872
+ if (e.detail > 1 || isEditing) return;
873
+ onSelect?.();
874
+ },
875
+ onKeyDown: (e) => {
876
+ if (e.target !== e.currentTarget) return;
877
+ if (e.key === "Enter" || e.key === " ") {
878
+ e.preventDefault();
879
+ onSelect?.();
880
+ }
881
+ },
882
+ className: cn("group flex w-full cursor-pointer flex-col p-1 text-left outline-none", "rounded-[4px] transition-colors focus-visible:ring-2 focus-visible:ring-ring", holdsOpenAgent ? "bg-row-selected-weak" : active ? "bg-row-selected" : "hover:bg-row-hover", className),
883
+ children: [/* @__PURE__ */ jsxs("div", {
884
+ className: "flex flex-col gap-1 py-0.5 pr-0.5 pl-1.5",
885
+ children: [/* @__PURE__ */ jsxs("div", {
886
+ className: "flex h-5 items-center gap-1.5 overflow-hidden",
887
+ children: [
888
+ /* @__PURE__ */ jsx(Gutter, { children: /* @__PURE__ */ jsx(SessionStatusIcon, { row }) }),
889
+ isEditing && onRename ? /* @__PURE__ */ jsx(NameEditor, {
890
+ initial: info.title ?? "",
891
+ onCommit: (title) => {
892
+ setEditing(false);
893
+ if (title !== (info.title ?? "")) onRename(title);
894
+ },
895
+ onCancel: () => setEditing(false)
896
+ }) : /* @__PURE__ */ jsx("span", {
897
+ onDoubleClick: onRename && renameOn === "doubleClick" ? (e) => {
898
+ e.stopPropagation();
899
+ setEditing(true);
900
+ } : void 0,
901
+ className: "min-w-0 flex-1 truncate text-body-sm font-medium tracking-[-0.005em] text-fg-1",
902
+ children: sessionLabel(info)
903
+ }),
904
+ row.unseen > 0 ? /* @__PURE__ */ jsx("span", {
905
+ title: `${row.unseen} new`,
906
+ className: cn("flex h-4 min-w-6 shrink-0 items-center justify-center rounded-full px-2", "text-[0.75rem] leading-none tracking-[-0.005em] tabular-nums", row.state === "working" || row.state === "attention" ? "bg-accent text-accent-fg" : "bg-badge text-badge-fg"),
907
+ children: row.unseen
908
+ }) : null,
909
+ /* @__PURE__ */ jsx(ContextRing, {
910
+ usage: info.contextUsage,
911
+ size: 16,
912
+ className: "p-0.5"
913
+ })
914
+ ]
915
+ }), /* @__PURE__ */ jsxs("div", {
916
+ className: "flex h-5 items-center gap-1.5 overflow-hidden text-body-sm tracking-[-0.005em]",
917
+ children: [
918
+ /* @__PURE__ */ jsx(Gutter, { children: /* @__PURE__ */ jsx(EngineIcon, {
919
+ engine,
920
+ model: info.model,
921
+ className: cn("size-4", vendorMarkClass(engine, info.model))
922
+ }) }),
923
+ /* @__PURE__ */ jsx("span", {
924
+ className: "min-w-0 truncate text-fg-4",
925
+ children: parts.map((part, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 ? " · " : "", part] }, i))
926
+ }),
927
+ /* @__PURE__ */ jsxs("span", {
928
+ className: "shrink-0 text-fg-4",
929
+ children: [parts.length > 0 ? "· " : "", formatRelativeTime(info.lastActivityAt ?? info.createdAt)]
930
+ }),
931
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
932
+ steps.length > 0 ? /* @__PURE__ */ jsx(StepToggle, {
933
+ expanded: open,
934
+ running: runningSteps(steps),
935
+ total: steps.length,
936
+ noun: steps[0].noun,
937
+ onToggle: () => setOpen(!open)
938
+ }) : null,
939
+ actions
940
+ ]
941
+ })]
942
+ }), open && steps.length > 0 ? /* @__PURE__ */ jsx("div", {
943
+ className: "flex flex-col",
944
+ children: steps.map((step) => /* @__PURE__ */ jsx(StepRow, {
945
+ step,
946
+ active: step.key === activeStepKey,
947
+ onSelect: step.onSelect
948
+ }, step.key))
949
+ }) : null]
950
+ });
951
+ }
952
+ /**
953
+ * The one 16px cell both lines hang their glyph in.
954
+ *
955
+ * A shared column rather than a leading character: the state and the engine mark
956
+ * are different widths and a bare glyph would let the two text columns start at
957
+ * different x. Centring inside a fixed cell is what makes them agree.
958
+ */
959
+ function Gutter({ children }) {
960
+ return /* @__PURE__ */ jsx("span", {
961
+ className: "flex size-4 shrink-0 items-center justify-center",
962
+ children
963
+ });
964
+ }
965
+ /**
966
+ * Enter commits, Escape cancels, blur commits — the editor's inline-rename feel.
967
+ * An empty value is a deliberate "clear the name", which the gateway answers by
968
+ * handing back the derived title.
969
+ *
970
+ * The blur rule needs one guard. Selecting a session focuses a different surface
971
+ * — in the extension, a different VS Code view — so this document can lose focus
972
+ * a tick after a double-click opened the editor, and an unguarded blur reads
973
+ * that as "the user clicked away" and closes the editor in the frame it
974
+ * appeared. `document.hasFocus()` tells the two apart: focus moving WITHIN this
975
+ * document is the user leaving the field; the whole view losing focus is not.
976
+ * When the window comes back, so does the caret.
977
+ */
978
+ function NameEditor({ initial, onCommit, onCancel }) {
979
+ const [value, setValue] = useState(initial);
980
+ const ref = useRef(null);
981
+ useEffect(() => {
982
+ ref.current?.focus();
983
+ ref.current?.select();
984
+ }, []);
985
+ useEffect(() => {
986
+ const onWindowFocus = () => {
987
+ ref.current?.focus();
988
+ ref.current?.select();
989
+ };
990
+ window.addEventListener("focus", onWindowFocus);
991
+ return () => window.removeEventListener("focus", onWindowFocus);
992
+ }, []);
993
+ return /* @__PURE__ */ jsx("input", {
994
+ ref,
995
+ value,
996
+ spellCheck: false,
997
+ placeholder: "Session name",
998
+ "aria-label": "Session name",
999
+ onClick: (e) => e.stopPropagation(),
1000
+ onDoubleClick: (e) => e.stopPropagation(),
1001
+ onChange: (e) => setValue(e.target.value),
1002
+ onBlur: () => {
1003
+ if (document.hasFocus()) onCommit(value.trim());
1004
+ },
1005
+ onKeyDown: (e) => {
1006
+ e.stopPropagation();
1007
+ if (e.key === "Enter") onCommit(value.trim());
1008
+ else if (e.key === "Escape") onCancel();
1009
+ },
1010
+ className: cn("-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-px", "text-body-sm leading-5 text-fg-1 outline-none")
1011
+ });
1012
+ }
1013
+ //#endregion
743
1014
  //#region src/components/agent/SessionBrowser.tsx
744
1015
  /**
745
1016
  * How a list row is drawn, in one place, so `SidebarRow` in `web` matches this
@@ -760,7 +1031,7 @@ function StepIcon({ state, kind }) {
760
1031
  function rowShapeClass(active) {
761
1032
  return cn("px-2 py-1.5 hover:bg-row-hover", active ? "mr-1 ml-0 rounded-r-md border-l-4 border-l-accent" : "mx-1 rounded-md");
762
1033
  }
763
- function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelect, onDelete, onRename, onClearContext, onSelectSubagent, emptyState, showControls = true, projectIcons, className }) {
1034
+ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, activeSubagentId, onSelect, onDelete, onRename, onClearContext, onSelectSubagent, onRevealStep, emptyState, showControls = true, projectIcons, className }) {
764
1035
  const visible = useMemo(() => filterRows(rows, config, scope), [
765
1036
  rows,
766
1037
  config,
@@ -937,11 +1208,11 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelec
937
1208
  title: "Nothing here",
938
1209
  description: "No session to show."
939
1210
  }) : /* @__PURE__ */ jsx("div", {
940
- className: "flex flex-col gap-4",
1211
+ className: "flex flex-col gap-4 px-1",
941
1212
  children: groups.map((group) => /* @__PURE__ */ jsxs("div", {
942
1213
  className: "flex flex-col gap-1",
943
1214
  children: [config.groupBy !== "none" && group.label ? /* @__PURE__ */ jsxs("div", {
944
- className: "flex items-center gap-2 px-3 text-label font-medium text-fg-4",
1215
+ className: "flex items-center gap-2 px-2 text-label font-medium text-fg-4",
945
1216
  children: [
946
1217
  config.groupBy === "project" ? /* @__PURE__ */ jsx(ProjectIcon, {
947
1218
  icon: group.rows[0]?.info.project?.icon,
@@ -960,6 +1231,7 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelec
960
1231
  }) : null, group.rows.map((row) => /* @__PURE__ */ jsx(SessionRowItem, {
961
1232
  row,
962
1233
  active: row.info.id === activeId,
1234
+ activeSubagentId,
963
1235
  showGateway: gateways.length > 1 && config.groupBy !== "gateway",
964
1236
  showProject: config.groupBy !== "project",
965
1237
  projectIcons,
@@ -967,7 +1239,8 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelec
967
1239
  onDelete,
968
1240
  onRename,
969
1241
  onClearContext,
970
- onSelectSubagent
1242
+ onSelectSubagent,
1243
+ onRevealStep
971
1244
  }, `${row.hostId}:${row.info.id}`))]
972
1245
  }, group.key))
973
1246
  })
@@ -981,202 +1254,65 @@ function iconSrcOf(row, icons) {
981
1254
  const icon = row?.info.project?.icon;
982
1255
  return icon?.type === "image" ? icons?.[icon.hash] : void 0;
983
1256
  }
984
- function SessionRowItem({ row, active, showGateway, showProject = true, projectIcons, onSelect, onDelete, onRename, onClearContext, onSelectSubagent }) {
1257
+ function SessionRowItem({ row, active, activeSubagentId, showGateway, showProject = true, projectIcons, onSelect, onDelete, onRename, onClearContext, onSelectSubagent, onRevealStep }) {
985
1258
  const { info } = row;
986
1259
  const [editing, setEditing] = useState(false);
987
- const [expanded, setExpanded] = useState(false);
988
- const project = showProject ? projectLabel(row) : projectSubpath(row);
989
- const projectIcon = showProject ? info.project?.icon : void 0;
990
- const engine = info.engine ?? "claude";
991
- const details = [
992
- project,
993
- showGateway ? row.hostName : void 0,
994
- info.profile ? `@${info.profile}` : void 0,
995
- formatCost(info.totalCostUsd)
996
- ].filter(Boolean);
997
- const steps = sessionSteps(info, (toolUseId) => onSelectSubagent ? onSelectSubagent(row, toolUseId) : onSelect?.(row));
998
- const cut = project === void 0 ? -1 : details.indexOf(project);
999
- return /* @__PURE__ */ jsxs("div", {
1000
- "data-slot": "session-row",
1001
- "data-active": active || void 0,
1002
- onClick: () => !editing && onSelect?.(row),
1003
- className: cn("group flex cursor-pointer flex-col gap-0.5 text-left transition-colors", rowShapeClass(active === true)),
1004
- children: [
1005
- /* @__PURE__ */ jsxs("div", {
1006
- className: "flex items-center gap-1.5",
1007
- children: [
1008
- /* @__PURE__ */ jsx(Gutter, { children: /* @__PURE__ */ jsx(SessionStatusIcon, { row }) }),
1009
- editing && onRename ? /* @__PURE__ */ jsx(NameEditor, {
1010
- initial: info.title ?? "",
1011
- onCommit: (title) => {
1012
- setEditing(false);
1013
- onRename(row, title);
1014
- },
1015
- onCancel: () => setEditing(false)
1016
- }) : /* @__PURE__ */ jsx("button", {
1017
- type: "button",
1018
- className: cn("min-w-0 flex-1 truncate text-left text-body-sm outline-none", active ? "font-medium text-fg-1" : "text-fg-2"),
1019
- children: sessionLabel(info)
1020
- }),
1021
- row.unseen > 0 ? /* @__PURE__ */ jsx("span", {
1022
- title: `${row.unseen} new`,
1023
- className: "shrink-0 rounded-full bg-accent px-1.5 text-label text-accent-fg",
1024
- children: row.unseen
1025
- }) : null,
1026
- /* @__PURE__ */ jsx("span", {
1027
- className: "shrink-0 text-label text-fg-4",
1028
- children: formatRelativeTime(info.lastActivityAt ?? info.createdAt)
1029
- }),
1030
- /* @__PURE__ */ jsx(ContextRing, { usage: info.contextUsage })
1031
- ]
1032
- }),
1033
- /* @__PURE__ */ jsxs("div", {
1034
- className: "flex items-center gap-1 text-label text-fg-4",
1035
- children: [
1036
- /* @__PURE__ */ jsx(Gutter, { children: /* @__PURE__ */ jsx(EngineIcon, {
1037
- engine,
1038
- model: info.model,
1039
- className: vendorMarkClass(engine, info.model)
1040
- }) }),
1041
- /* @__PURE__ */ jsxs("button", {
1042
- type: "button",
1043
- tabIndex: -1,
1044
- className: "min-w-0 flex-1 truncate text-left font-mono outline-none",
1045
- children: [
1046
- /* @__PURE__ */ jsx("span", {
1047
- className: vendorTextClass(engine, info.model),
1048
- children: friendlyModel(info.model)
1049
- }),
1050
- details.length > 0 ? " · " : "",
1051
- cut < 0 ? details.join(" · ") : /* @__PURE__ */ jsxs(Fragment$1, { children: [
1052
- details.slice(0, cut).map((part) => `${part} · `),
1053
- /* @__PURE__ */ jsx(ProjectIcon, {
1054
- icon: projectIcon,
1055
- src: iconSrcOf(row, projectIcons),
1056
- name: project,
1057
- className: "mr-1 align-[-0.2em]"
1058
- }),
1059
- details.slice(cut).join(" · ")
1060
- ] })
1061
- ]
1062
- }),
1063
- steps.length > 0 ? /* @__PURE__ */ jsx(StepToggle, {
1064
- expanded,
1065
- running: runningSteps(steps),
1066
- total: steps.length,
1067
- noun: steps[0].noun,
1068
- onToggle: () => setExpanded((v) => !v)
1069
- }) : null,
1070
- onRename && !editing ? /* @__PURE__ */ jsx(Button, {
1071
- variant: "ghost",
1072
- size: "icon-sm",
1073
- "aria-label": "Rename session",
1074
- className: "size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100",
1075
- onClick: (e) => {
1076
- e.stopPropagation();
1077
- setEditing(true);
1078
- },
1079
- children: /* @__PURE__ */ jsx(Pencil, { className: "size-3 text-fg-3" })
1080
- }) : null,
1081
- onClearContext && info.capabilities?.clearContext ? /* @__PURE__ */ jsx(Button, {
1082
- variant: "ghost",
1083
- size: "icon-sm",
1084
- "aria-label": "Clear context",
1085
- title: "Clear the conversation — the session keeps running and the old conversation stays resumable",
1086
- className: "size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100",
1087
- onClick: (e) => {
1088
- e.stopPropagation();
1089
- onClearContext(row);
1090
- },
1091
- children: /* @__PURE__ */ jsx(Eraser, { className: "size-3 text-fg-3" })
1092
- }) : null,
1093
- onDelete ? /* @__PURE__ */ jsx(Button, {
1094
- variant: "ghost",
1095
- size: "icon-sm",
1096
- "aria-label": "Close session",
1097
- className: "size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100",
1098
- onClick: (e) => {
1099
- e.stopPropagation();
1100
- onDelete(row);
1101
- },
1102
- children: /* @__PURE__ */ jsx(Trash2, { className: "size-3 text-fg-3" })
1103
- }) : null
1104
- ]
1105
- }),
1106
- expanded ? steps.map((step) => /* @__PURE__ */ jsx(StepRow, {
1107
- step,
1108
- onSelect: step.onSelect
1109
- }, step.key)) : null
1110
- ]
1111
- });
1112
- }
1113
- /**
1114
- * The one 14px cell both lines hang their glyph in.
1115
- *
1116
- * A shared column rather than a leading character: the state and the engine mark
1117
- * are different widths and a bare glyph would let the two text columns start at
1118
- * different x. Centring inside a fixed cell is what makes them agree —
1119
- * `packages/ui`'s terminal-gutter argument at row scale.
1120
- */
1121
- function Gutter({ children }) {
1122
- return /* @__PURE__ */ jsx("span", {
1123
- className: "flex w-3.5 shrink-0 items-center justify-center",
1124
- children
1260
+ return /* @__PURE__ */ jsx(SessionItem, {
1261
+ row,
1262
+ active: active === true,
1263
+ activeStepKey: activeSubagentId,
1264
+ showGateway,
1265
+ showProject,
1266
+ projectIcons,
1267
+ onSelect: () => onSelect?.(row),
1268
+ onSelectSubagent: onSelectSubagent ? (id) => onSelectSubagent(row, id) : void 0,
1269
+ onRevealStep: onRevealStep ? (id) => onRevealStep(row, id) : void 0,
1270
+ onRename: onRename ? (title) => onRename(row, title) : void 0,
1271
+ renameOn: "external",
1272
+ editing,
1273
+ onEditingChange: setEditing,
1274
+ actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [
1275
+ onRename && !editing ? /* @__PURE__ */ jsx(RowAction, {
1276
+ label: "Rename session",
1277
+ onClick: () => setEditing(true),
1278
+ children: /* @__PURE__ */ jsx(Pencil, { className: "size-3 text-fg-3" })
1279
+ }) : null,
1280
+ onClearContext && info.capabilities?.clearContext ? /* @__PURE__ */ jsx(RowAction, {
1281
+ label: "Clear context",
1282
+ title: "Clear the conversation the session keeps running and the old conversation stays resumable",
1283
+ onClick: () => onClearContext(row),
1284
+ children: /* @__PURE__ */ jsx(Eraser, { className: "size-3 text-fg-3" })
1285
+ }) : null,
1286
+ onDelete ? /* @__PURE__ */ jsx(RowAction, {
1287
+ label: "Close session",
1288
+ onClick: () => onDelete(row),
1289
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3 text-fg-3" })
1290
+ }) : null
1291
+ ] })
1125
1292
  });
1126
1293
  }
1127
1294
  /**
1128
- * State as one glyph a ringing bell when it wants a human, a spinner while it
1129
- * works, a moon when it is only sleeping. Replaces the text badge: in a sidebar
1130
- * the word costs more room than it earns, and the states that matter are the two
1131
- * you can recognise without reading.
1295
+ * One of the dashboard's hover actions.
1132
1296
  *
1133
- * **It reads `row.state`, not `info.status`, and that distinction is the whole
1134
- * point of the row model.** `sessionState` already folds in the arm this glyph
1135
- * cannot see for itself: a *background* sub-agent outlives its turn by design, so
1136
- * the turn ends, `status` comes to rest at `idle`, and the agent keeps working.
1137
- * Reading the raw status drew a **moon on a row filed under the "Working"
1138
- * header** the list contradicting itself on one line, which is exactly what a
1139
- * derived view model exists to prevent. The value was in scope and unread.
1140
- *
1141
- * The terminal statuses still come off `info.status`, because `ended` collapses
1142
- * `failed` and `closed` into one bucket and those are worth telling apart here.
1143
- */
1144
- function SessionStatusIcon({ row }) {
1145
- const { info } = row;
1146
- if (row.state === "attention") return /* @__PURE__ */ jsx(BellRing, { className: "size-3.5 shrink-0 animate-pulse text-warning" });
1147
- if (row.state === "working") return /* @__PURE__ */ jsx(Spinner, { className: "size-3.5 shrink-0 text-info" });
1148
- switch (info.status) {
1149
- case "failed": return /* @__PURE__ */ jsx(CircleAlert, { className: "size-3.5 shrink-0 text-danger" });
1150
- case "closed": return /* @__PURE__ */ jsx(CircleSlash, { className: "size-3.5 shrink-0 text-fg-4" });
1151
- case "parked": return /* @__PURE__ */ jsx(PauseCircle, { className: "size-3.5 shrink-0 text-fg-3" });
1152
- default: return /* @__PURE__ */ jsx(Moon, { className: "size-3.5 shrink-0 text-fg-4" });
1153
- }
1154
- }
1155
- /**
1156
- * Inline rename. Enter commits, Escape cancels, blur commits — but only a blur
1157
- * that is still inside this document: switching windows must not silently commit,
1158
- * nor kill the editor in the frame it opened.
1297
+ * Hover-revealed rather than always on, which is the opposite of the
1298
+ * extension's single overflow glyph and the right call for each: there are
1299
+ * three of them here and a sidebar card has room for one, so the extension
1300
+ * spends that room on a menu and this spends the row's hover on the actions
1301
+ * themselves. Both stop the click the whole card is pressable underneath, and
1302
+ * an action that also selected the session would do two things per press.
1159
1303
  */
1160
- function NameEditor({ initial, onCommit, onCancel }) {
1161
- const [value, setValue] = useState(initial);
1162
- const ref = useRef(null);
1163
- useEffect(() => {
1164
- ref.current?.select();
1165
- }, []);
1166
- return /* @__PURE__ */ jsx("input", {
1167
- ref,
1168
- value,
1169
- autoFocus: true,
1170
- "aria-label": "Session name",
1171
- onClick: (e) => e.stopPropagation(),
1172
- onChange: (e) => setValue(e.target.value),
1173
- onBlur: () => document.hasFocus() ? onCommit(value.trim()) : void 0,
1174
- onKeyDown: (e) => {
1175
- if (e.key === "Enter") onCommit(value.trim());
1176
- else if (e.key === "Escape") onCancel();
1304
+ function RowAction({ label, title, onClick, children }) {
1305
+ return /* @__PURE__ */ jsx(Button, {
1306
+ variant: "ghost",
1307
+ size: "icon-sm",
1308
+ "aria-label": label,
1309
+ title: title ?? label,
1310
+ className: "size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100",
1311
+ onClick: (e) => {
1177
1312
  e.stopPropagation();
1313
+ onClick();
1178
1314
  },
1179
- 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"
1315
+ children
1180
1316
  });
1181
1317
  }
1182
1318
  /** A multi-select facet. Empty = no filter, which is why the trigger reads the
@@ -1238,6 +1374,6 @@ function OneOfSelect({ label, value, options, onChange }) {
1238
1374
  });
1239
1375
  }
1240
1376
  //#endregion
1241
- export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, AlertDialogTrigger, Badge, Band, Blank, Button, Card, CardContent, CardHeader, CardTitle, CodeBlock, Composer, ContextDialog, ContextRing, Conversation, ConversationContent, ConversationScrollButton, CopyAction, CopyButton, Dialog, DialogBody, DialogClose, DialogContent, DialogHeader, DialogRow, DialogTrigger, Empty, EmptyKey, EngineIcon, FileCard, HostFilesDialog, Ink, Input, Loader, McpDialog, Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, Message, MessageContent, ModelSelect, PERMISSION_MODES, PermissionModeSelect, PermissionPrompt, ProgressRing, ProjectIcon, PromptArea, PromptTokenText, QUESTION_BEHAVIORS, QuestionPrompt, Reasoning, Response, Row, STATUS_META, Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue, SessionBrowser, SessionEmptyState, SessionInfoDialog, SessionList, SessionListItem, SessionPanel, SessionStatusIcon, SkillsDialog, Spinner, Splitter, StatusBar, StepRow, StepToggle, SubagentStrip, TerminalDiff, TerminalItemView, TerminalMarkdown, TerminalPermissionPrompt, TerminalQuestionPrompt, TerminalStatusLine, TerminalSurface, TerminalTranscript, Textarea, Tip, Toaster, ToolCallCard, TooltipContent, TooltipProvider, Transcript, TranscriptDensityProvider, TranscriptVariantProvider, UsageDialog, UsageMeters, WithActions, badgeVariants, buttonVariants, cn, commandTrigger, copyText, engineMark, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, getChipsByTrigger, hashtagTrigger, isMutatingTool, isSegmentsEmpty, mentionTrigger, parseUserQuestions, permissionModeChoices, permissionModeMeta, plainTextToSegments, previewPatch, rateLimitWindowSeconds, rowShapeClass, runningSteps, segmentsToPlainText, sessionSteps, skillPrompt, toast, toolIcon, toolInputPreview, useAffordances, useMinuteClock, usePromptAreaState, useTranscriptDensity, useTranscriptVariant, vendorMarkClass, vendorTextClass };
1377
+ export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogTitle, AlertDialogTrigger, Badge, Band, Blank, Button, Card, CardContent, CardHeader, CardTitle, CodeBlock, Composer, ContextDialog, ContextRing, Conversation, ConversationContent, ConversationScrollButton, CopyAction, CopyButton, Dialog, DialogBody, DialogClose, DialogContent, DialogHeader, DialogRow, DialogTrigger, Empty, EmptyKey, EngineIcon, FileCard, HostFilesDialog, Ink, Input, Loader, McpDialog, Menu, MenuContent, MenuItem, MenuSeparator, MenuTrigger, Message, MessageContent, ModelSelect, PERMISSION_MODES, PermissionModeSelect, PermissionPrompt, ProgressRing, ProjectIcon, PromptArea, PromptTokenText, QUESTION_BEHAVIORS, QuestionPrompt, Reasoning, Response, Row, STATUS_META, Select, SelectContent, SelectItem, SelectItemText, SelectTrigger, SelectValue, SessionBrowser, SessionEmptyState, SessionInfoDialog, SessionItem, SessionList, SessionListItem, SessionPanel, SessionStatusIcon, SkillsDialog, Spinner, Splitter, StatusBar, StepRow, StepToggle, SubagentStrip, TerminalDiff, TerminalItemView, TerminalMarkdown, TerminalPermissionPrompt, TerminalQuestionPrompt, TerminalStatusLine, TerminalSurface, TerminalTranscript, Textarea, Tip, Toaster, ToolCallCard, TooltipContent, TooltipProvider, Transcript, TranscriptDensityProvider, TranscriptVariantProvider, UsageDialog, UsageMeters, WithActions, badgeVariants, buttonVariants, cn, commandTrigger, copyText, engineMark, formatAgoPrecise, formatBytes, formatCost, formatCountdown, formatDuration, formatRateLimitWindow, formatRateLimitWindowLong, formatRelativeTime, formatTokens, friendlyModel, getChipsByTrigger, hashtagTrigger, isMutatingTool, isSegmentsEmpty, mentionTrigger, parseUserQuestions, permissionModeChoices, permissionModeMeta, plainTextToSegments, previewPatch, rateLimitWindowSeconds, rowShapeClass, runningSteps, segmentsToPlainText, sessionSteps, skillPrompt, toast, toolIcon, toolInputPreview, useAffordances, useMinuteClock, usePromptAreaState, useTranscriptDensity, useTranscriptVariant, vendorMarkClass, vendorTextClass };
1242
1378
 
1243
1379
  //# sourceMappingURL=index.mjs.map