@workerdeck/ui 0.20.0 → 0.22.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.
Files changed (43) hide show
  1. package/README.md +3 -2
  2. package/build/{SessionPanel-CnU_IJ3-.d.mts → SessionPanel-13l25ubU.d.mts} +55 -7
  3. package/build/{SessionPanel-CHktI2CF.mjs → SessionPanel-DgwjH4Ve.mjs} +642 -231
  4. package/build/SessionPanel-DgwjH4Ve.mjs.map +1 -0
  5. package/build/index.d.mts +309 -53
  6. package/build/index.mjs +534 -397
  7. package/build/index.mjs.map +1 -1
  8. package/build/scoped.css +3547 -0
  9. package/build/workspace.d.mts +10 -1
  10. package/build/workspace.mjs +4 -2
  11. package/build/workspace.mjs.map +1 -1
  12. package/package.json +15 -7
  13. package/src/components/agent/Composer.tsx +10 -10
  14. package/src/components/agent/ContextDialog.tsx +3 -2
  15. package/src/components/agent/Conversation.tsx +1 -1
  16. package/src/components/agent/McpDialog.tsx +3 -1
  17. package/src/components/agent/Scrubber.tsx +248 -0
  18. package/src/components/agent/SessionBrowser.tsx +113 -293
  19. package/src/components/agent/SessionItem.tsx +538 -0
  20. package/src/components/agent/SessionList.tsx +3 -1
  21. package/src/components/agent/SessionPanel.tsx +112 -21
  22. package/src/components/agent/SessionStatusIcon.tsx +43 -0
  23. package/src/components/agent/SessionSteps.tsx +118 -61
  24. package/src/components/agent/SessionWorkspace.tsx +11 -0
  25. package/src/components/agent/SkillsDialog.tsx +3 -2
  26. package/src/components/agent/StatusBar.tsx +1 -1
  27. package/src/components/agent/Transcript.tsx +147 -73
  28. package/src/components/agent/UsageDialog.tsx +3 -1
  29. package/src/components/agent/scrubber-marks.ts +277 -0
  30. package/src/components/terminal/PermissionPrompt.tsx +3 -0
  31. package/src/components/terminal/QuestionPrompt.tsx +3 -0
  32. package/src/components/terminal/StatusLine.tsx +3 -1
  33. package/src/components/ui/AlertDialog.tsx +23 -20
  34. package/src/components/ui/Dialog.tsx +26 -23
  35. package/src/components/ui/Menu.tsx +20 -17
  36. package/src/components/ui/PortalScope.tsx +27 -0
  37. package/src/components/ui/Select.tsx +19 -16
  38. package/src/components/ui/Tooltip.tsx +13 -10
  39. package/src/index.ts +5 -1
  40. package/src/styles/scoped.entry.css +16 -0
  41. package/src/styles/terminal.css +1 -1
  42. package/src/styles/theme.css +223 -8
  43. package/build/SessionPanel-CHktI2CF.mjs.map +0 -1
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 Badge, 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 Button, 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 PortalScope, S as SessionInfoDialog, St as DialogClose, T as QUESTION_BEHAVIORS, Tt as DialogRow, U as TerminalDiff, Ut as buttonVariants, V as TerminalSurface, Vt as badgeVariants, W as previewPatch, Wt as cn, 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 Input } from "./SessionPanel-DgwjH4Ve.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";
@@ -46,12 +46,12 @@ Textarea.displayName = "Textarea";
46
46
  const AlertDialog = AlertDialog$1.Root;
47
47
  const AlertDialogTrigger = AlertDialog$1.Trigger;
48
48
  const AlertDialogClose = AlertDialog$1.Close;
49
- const AlertDialogContent = ({ className, children, ...props }) => /* @__PURE__ */ jsxs(AlertDialog$1.Portal, { children: [/* @__PURE__ */ jsx(AlertDialog$1.Backdrop, { className: cn("fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]", "transition-opacity duration-(--motion-base)", "data-starting-style:opacity-0 data-ending-style:opacity-0") }), /* @__PURE__ */ jsx(AlertDialog$1.Popup, {
49
+ const AlertDialogContent = ({ className, children, ...props }) => /* @__PURE__ */ jsx(AlertDialog$1.Portal, { children: /* @__PURE__ */ jsxs(PortalScope, { children: [/* @__PURE__ */ jsx(AlertDialog$1.Backdrop, { className: cn("fixed inset-0 z-70 bg-black/40 backdrop-blur-[1px]", "transition-opacity duration-(--motion-base)", "data-starting-style:opacity-0 data-ending-style:opacity-0") }), /* @__PURE__ */ jsx(AlertDialog$1.Popup, {
50
50
  "data-slot": "alert-dialog-content",
51
51
  className: cn("fixed top-1/2 left-1/2 z-70 w-[min(28rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2", "rounded-lg border border-border bg-surface p-5 shadow-(--shadow-lg) outline-none", "transition-[opacity,transform] duration-(--motion-base)", "data-starting-style:scale-95 data-starting-style:opacity-0", "data-ending-style:scale-95 data-ending-style:opacity-0", className),
52
52
  ...props,
53
53
  children
54
- })] });
54
+ })] }) });
55
55
  const AlertDialogTitle = ({ className, ...props }) => /* @__PURE__ */ jsx(AlertDialog$1.Title, {
56
56
  className: cn("text-heading-3 font-semibold text-text", className),
57
57
  ...props
@@ -181,7 +181,7 @@ function Reading({ tone, onPress, label, children }) {
181
181
  })
182
182
  });
183
183
  }
184
- function TerminalStatusLine({ state, rateLimits, connection, onOpenStatus, onOpenContext, onOpenUsage }) {
184
+ function TerminalStatusLine({ className, state, rateLimits, connection, onOpenStatus, onOpenContext, onOpenUsage }) {
185
185
  const presentation = statusPresentation({
186
186
  status: state.status,
187
187
  connection
@@ -234,6 +234,7 @@ function TerminalStatusLine({ state, rateLimits, connection, onOpenStatus, onOpe
234
234
  return /* @__PURE__ */ jsx(Row, {
235
235
  "data-slot": "status-line",
236
236
  tone: "dim",
237
+ className,
237
238
  children: parts.map((part, index) => /* @__PURE__ */ jsxs("span", { children: [index > 0 ? /* @__PURE__ */ jsx(Ink, {
238
239
  tone: "faint",
239
240
  children: " · "
@@ -242,12 +243,12 @@ function TerminalStatusLine({ state, rateLimits, connection, onOpenStatus, onOpe
242
243
  }
243
244
  //#endregion
244
245
  //#region src/components/agent/SessionList.tsx
245
- function SessionListItem({ session, active, onSelect, onDelete }) {
246
+ function SessionListItem({ session, active, onSelect, onDelete, className }) {
246
247
  const meta = STATUS_META[session.status];
247
248
  return /* @__PURE__ */ jsxs("div", {
248
249
  "data-slot": "session-list-item",
249
250
  "data-active": active || void 0,
250
- className: cn("group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors", active ? "border-border bg-surface" : "hover:bg-surface-hover"),
251
+ className: cn("group flex w-full items-center gap-2 rounded-md border border-transparent px-2.5 py-2 text-left transition-colors", active ? "border-border bg-surface" : "hover:bg-surface-hover", className),
251
252
  children: [/* @__PURE__ */ jsxs("button", {
252
253
  type: "button",
253
254
  onClick: () => onSelect?.(session.id),
@@ -310,150 +311,6 @@ function SessionList({ sessions, activeId, onSelect, onDelete, emptyText = "No s
310
311
  });
311
312
  }
312
313
  //#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
314
  //#region src/components/agent/ProjectIcon.tsx
458
315
  /**
459
316
  * A project's icon — the render side of protocol's `ProjectIcon`, for a list
@@ -624,21 +481,224 @@ function ProjectIcon({ icon, src, name, className }) {
624
481
  className: cn("inline-block size-3 shrink-0 object-contain", className)
625
482
  });
626
483
  }
627
- return /* @__PURE__ */ jsx(GLYPHS[icon.name] ?? Folder, { className: cn("inline-block size-3 shrink-0", className) });
484
+ return /* @__PURE__ */ jsx(GLYPHS[icon.name] ?? Folder, { className: cn("inline-block size-3 shrink-0", className) });
485
+ }
486
+ //#endregion
487
+ //#region src/components/agent/ContextRing.tsx
488
+ /**
489
+ * How full a session's context window is, sized for a **list row**.
490
+ *
491
+ * The reading the session screen has always drawn, moved somewhere it answers a
492
+ * question the session screen cannot: *which* of these thirty sessions is
493
+ * bloating. That is also why it is a ring and not a number — across a list, a
494
+ * ring is read at a glance and `71%` has to be read one row at a time.
495
+ *
496
+ * **Absent draws nothing, and absent is not zero.** A promptless session, a
497
+ * parked one from before the field existed, or an engine that reports no window
498
+ * has no reading; an empty ring would claim its context is empty rather than
499
+ * unknown. The title carries the exact numbers for anyone who wants them.
500
+ *
501
+ * Shared rather than spelled per client: the dashboard's row and the VS Code
502
+ * sidebar's card both draw it, and two copies would be two sets of thresholds
503
+ * the day one of them is nudged.
504
+ */
505
+ function ContextRing({ usage, size = 11, className }) {
506
+ if (usage === void 0) return null;
507
+ return /* @__PURE__ */ jsx("span", {
508
+ title: `Context ${usage.percentage.toFixed(0)}% · ${formatTokens(usage.totalTokens)} / ${formatTokens(usage.maxTokens)}`,
509
+ className: cn("shrink-0 leading-none", meterColorClass(usage.percentage), className),
510
+ children: /* @__PURE__ */ jsx(ProgressRing, {
511
+ value: usage.percentage,
512
+ size,
513
+ strokeWidth: 2
514
+ })
515
+ });
516
+ }
517
+ //#endregion
518
+ //#region src/components/agent/EngineIcon.tsx
519
+ /**
520
+ * The engine's own mark, monochrome.
521
+ *
522
+ * Inlined rather than depended on: the React package these come from
523
+ * (`@lobehub/icons`) pulls antd, `@lobehub/ui` and a second copy of
524
+ * lucide-react — an absurd amount of bundle for a few glyphs in a sidebar.
525
+ * These are paths from `@lobehub/icons-static-svg` (MIT, © LobeHub), already
526
+ * `fill="currentColor"` single-path marks, so they take the row's colour like
527
+ * any other glyph and need no light/dark variants.
528
+ *
529
+ * The marks are trademarks of their owners (Anthropic, OpenAI, Google,
530
+ * DeepSeek, Moonshot), used here only to identify which engine a session runs
531
+ * on.
532
+ *
533
+ * Codex is drawn as OpenAI: the session's engine *is* OpenAI's, and OpenAI's
534
+ * mark is the one a person recognises at 12px.
535
+ */
536
+ const PATHS = {
537
+ claude: {
538
+ title: "Claude",
539
+ 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"
540
+ },
541
+ codex: {
542
+ title: "OpenAI",
543
+ 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"
544
+ },
545
+ gemini: {
546
+ title: "Gemini",
547
+ 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"
548
+ },
549
+ deepseek: {
550
+ title: "DeepSeek",
551
+ 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"
552
+ },
553
+ moonshot: {
554
+ title: "Moonshot",
555
+ 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"
556
+ }
557
+ };
558
+ /**
559
+ * Which mark a session wears.
560
+ *
561
+ * The two first-party engines are named directly. A `provider` session is
562
+ * whatever the host wired up, so its *model* is the only thing that says whose
563
+ * it is — sniffed loosely on purpose (`gemini-2.5-pro`, `deepseek-chat`,
564
+ * `kimi-k2`), and falling through to no mark rather than guessing wrong.
565
+ */
566
+ function engineMark(engine, model) {
567
+ if (engine === "claude") return "claude";
568
+ if (engine === "codex") return "codex";
569
+ const id = model?.toLowerCase() ?? "";
570
+ if (!id) return void 0;
571
+ if (id.includes("gemini")) return "gemini";
572
+ if (id.includes("deepseek")) return "deepseek";
573
+ if (id.includes("moonshot") || id.includes("kimi")) return "moonshot";
574
+ if (id.includes("claude")) return "claude";
575
+ if (id.startsWith("gpt") || id.startsWith("o1") || id.startsWith("o3") || id.includes("openai")) return "codex";
576
+ }
577
+ /**
578
+ * Which engines wear their vendor's colour, and **how far it reaches**.
579
+ *
580
+ * `MARK` is the glyph, `TEXT` is the model name beside it. They are the same for
581
+ * Anthropic and deliberately not for OpenAI, because the two brands differ in
582
+ * kind rather than in hue: coral is an accent, and OpenAI's guidelines forbid
583
+ * adding colour to the mark at all, so theirs is pure white/black. At full
584
+ * contrast that is right on a 12px glyph and wrong on an 11px label — a
585
+ * pure-white model name is *brighter than the session title above it*, which
586
+ * inverts the row's whole hierarchy to say something the mark has already said.
587
+ * So OpenAI's name keeps the muted line it always had, which is also the most
588
+ * literal reading of "don't add any colors".
589
+ *
590
+ * Anything absent falls through to muted on both counts.
591
+ */
592
+ const VENDOR_MARK = {
593
+ claude: "text-vendor-claude",
594
+ codex: "text-vendor-openai"
595
+ };
596
+ const VENDOR_TEXT = { claude: "text-vendor-claude" };
597
+ /** The colour a session's engine mark wears. Pass it to `EngineIcon`'s
598
+ * `className` rather than to a parent: the svg carries its own `text-fg-3`,
599
+ * and only a class on the element itself is merged over it. */
600
+ function vendorMarkClass(engine, model) {
601
+ return VENDOR_MARK[engineMark(engine, model) ?? ""] ?? "text-fg-3";
602
+ }
603
+ /** The colour the model *name* wears beside that mark — muted unless the vendor
604
+ * is one whose accent survives at label size. See `VENDOR_TEXT`.
605
+ *
606
+ * The fallback is `text-fg-3` rather than the metadata line's own `text-fg-4`,
607
+ * so an unbranded model still sits one step above the project and gateway
608
+ * beside it — the order the spec lists them in, held without a colour. */
609
+ function vendorTextClass(engine, model) {
610
+ return VENDOR_TEXT[engineMark(engine, model) ?? ""] ?? "text-fg-3";
611
+ }
612
+ function EngineIcon({ engine, model, className }) {
613
+ const mark = PATHS[engineMark(engine, model) ?? ""];
614
+ if (!mark) return /* @__PURE__ */ jsx("span", {
615
+ "aria-hidden": true,
616
+ title: engine,
617
+ className: cn("shrink-0 text-fg-3", className),
618
+ children: "·"
619
+ });
620
+ return /* @__PURE__ */ jsxs("svg", {
621
+ viewBox: "0 0 24 24",
622
+ fill: "currentColor",
623
+ fillRule: "evenodd",
624
+ role: "img",
625
+ "aria-label": mark.title,
626
+ className: cn("size-3 shrink-0 text-fg-3", className),
627
+ children: [/* @__PURE__ */ jsx("title", { children: mark.title }), /* @__PURE__ */ jsx("path", { d: mark.d })]
628
+ });
629
+ }
630
+ //#endregion
631
+ //#region src/components/agent/SessionStatusIcon.tsx
632
+ /**
633
+ * State as one glyph — a ringing bell when it wants a human, a spinner while it
634
+ * works, a moon when it is only sleeping. Replaces the text badge: in a sidebar
635
+ * the word costs more room than it earns, and the states that matter are the two
636
+ * you can recognise without reading.
637
+ *
638
+ * **It reads `row.state`, not `info.status`, and that distinction is the whole
639
+ * point of the row model.** `sessionState` already folds in the arm this glyph
640
+ * cannot see for itself: a *background* sub-agent outlives its turn by design,
641
+ * so the turn ends, `status` comes to rest at `idle`, and the agent keeps
642
+ * working. Reading the raw status drew a **moon on a row filed under the
643
+ * "Working" header** — the list contradicting itself on one line, which is
644
+ * exactly what a derived view model exists to prevent. The value was in scope
645
+ * and unread.
646
+ *
647
+ * The terminal statuses still come off `info.status`, because `ended` collapses
648
+ * `failed` and `closed` into one bucket and those are worth telling apart here.
649
+ *
650
+ * It lives in its own file rather than inside the dashboard's browser because
651
+ * the extension's cards draw it too, and a second copy there is how the two
652
+ * lists last disagreed about what a parked session looks like.
653
+ */
654
+ function SessionStatusIcon({ row, className }) {
655
+ const { info } = row;
656
+ const size = cn("size-4 shrink-0", className);
657
+ if (row.state === "attention") return /* @__PURE__ */ jsx(BellRing, { className: cn(size, "animate-pulse text-warning") });
658
+ if (row.state === "working") return /* @__PURE__ */ jsx(Spinner, { className: cn(size, "text-info") });
659
+ switch (info.status) {
660
+ case "failed": return /* @__PURE__ */ jsx(CircleAlert, { className: cn(size, "text-danger") });
661
+ case "closed": return /* @__PURE__ */ jsx(CircleSlash, { className: cn(size, "text-fg-4") });
662
+ case "parked": return /* @__PURE__ */ jsx(PauseCircle, { className: cn(size, "text-fg-3") });
663
+ default: return /* @__PURE__ */ jsx(Moon, { className: cn(size, "text-fg-4") });
664
+ }
628
665
  }
629
666
  //#endregion
630
667
  //#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
- }));
668
+ /**
669
+ * The steps under one session, **agents first**.
670
+ *
671
+ * The two kinds do different things when pressed and are worth different
672
+ * amounts of attention: an agent has work of its own to go and read, a task is a
673
+ * marker in a transcript. Interleaved in dispatch order they read as one
674
+ * undifferentiated list, and the rows you can actually open are scattered
675
+ * through it. Grouped, the openable ones are a block at the top and the markers
676
+ * are a tail you can skip.
677
+ *
678
+ * Stable **within** each group, deliberately: dispatch order is the only order
679
+ * these records have that means anything (it is the order the work was started
680
+ * in), so the sort partitions and never reorders inside a partition.
681
+ *
682
+ * `onSelect` is handed the **kind** as well as the id, because the two kinds go
683
+ * to different places — see `SessionItem`, which is what routes them. Passing
684
+ * only the id is what let a task be opened as if it were an agent, and a framed
685
+ * task id matches no items, so the panel drew an **empty agent view**.
686
+ */
687
+ function sessionSteps(info, onSelect) {
688
+ const steps = (info.subagents ?? []).map((sub) => {
689
+ const kind = isAgentRecord(sub) ? "agent" : "task";
690
+ return {
691
+ key: sub.toolUseId,
692
+ label: subagentLabel(sub),
693
+ noun: "agent",
694
+ kind,
695
+ state: stepState(sub.status),
696
+ detail: sub.toolCount > 0 ? String(sub.toolCount) : void 0,
697
+ title: `${subagentLabel(sub)} · ${sub.toolCount} tool${sub.toolCount === 1 ? "" : "s"}`,
698
+ onSelect: () => onSelect(sub.toolUseId, kind)
699
+ };
700
+ });
701
+ return [...steps.filter((s) => s.kind === "agent"), ...steps.filter((s) => s.kind === "task")];
642
702
  }
643
703
  function stepState(status) {
644
704
  switch (status) {
@@ -674,7 +734,7 @@ function StepToggle({ expanded, running, total, noun, onToggle }) {
674
734
  e.stopPropagation();
675
735
  onToggle();
676
736
  },
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"),
737
+ 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
738
  children: [/* @__PURE__ */ jsx(Chevron, { className: "size-3" }), /* @__PURE__ */ jsx("span", {
679
739
  className: "tabular-nums",
680
740
  children: label
@@ -682,64 +742,276 @@ function StepToggle({ expanded, running, total, noun, onToggle }) {
682
742
  });
683
743
  }
684
744
  /**
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}.
745
+ * One step under its session **pressable, all of them**, and what a press
746
+ * means is what tells the two kinds apart.
747
+ *
748
+ * Pressing an **agent** hands the panel over to that agent's own work
749
+ * (`SessionPanel.openSubagent`): it is not a session and never becomes one, but
750
+ * it has a surface, so it can be the selected thing. Pressing a **task** selects
751
+ * the *session* and travels to that task's marker inside it — a task is a
752
+ * reference to a place in a transcript, not a thing with a screen, so it can be
753
+ * followed but never held.
691
754
  *
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.
755
+ * That is a reversal, and a deliberate one. A task used to be inert markup on
756
+ * the argument that "a disabled-looking button still announces itself as one"
757
+ * correct about the markup, wrong about the premise, because there *was* always
758
+ * somewhere to go and the row simply swallowed the click on its way there. A row
759
+ * that looks like a list item, sits in a list, and does nothing when pressed is
760
+ * the worse lie. So every step answers the pointer and every step answers a
761
+ * press; only what the press does differs.
762
+ *
763
+ * Divided from the card's header by **indentation and its own hit shape**, not
764
+ * by a rule. The rules came first, on the argument that at 11px an indent is not
765
+ * enough to say "list inside a row" — and they were right about the reading and
766
+ * wrong about the cost: a stack of hairlines across every open card turned the
767
+ * list into a ledger, and a rule cannot answer a pointer. A step that lights up
768
+ * under the cursor and fills when it is the one on screen says *list* far more
769
+ * plainly than a line between two of them, and it says it while doing the job
770
+ * the rule could not.
771
+ *
772
+ * **Only an agent can wear the selection**, and `active` is guarded on that here
773
+ * rather than trusted from the caller: a host that hands back a task's key is
774
+ * describing where it navigated, not what it selected, and the row must not
775
+ * paint itself blue for it.
776
+ *
777
+ * The hover is `--row-active` — **alpha, not a flat fill** — because this row
778
+ * has to answer the pointer on three different grounds: a transparent card, a
779
+ * blue one (its session is selected), and a grey one (a sibling agent is). A
780
+ * flat value tuned for any of those is wrong on the other two; a tint darkens or
781
+ * lifts whatever it lands on. It is the one place in the list where the alpha
782
+ * token earns its keep.
696
783
  */
697
- function StepRow({ step, onSelect }) {
784
+ function StepRow({ step, active = false, onSelect }) {
698
785
  const agent = step.kind === "agent";
786
+ const selected = active && agent;
699
787
  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", {
788
+ return /* @__PURE__ */ jsxs("button", {
723
789
  type: "button",
724
790
  title: step.title,
791
+ "aria-current": selected || void 0,
725
792
  onClick: (e) => {
726
793
  e.stopPropagation();
727
794
  onSelect();
728
795
  },
729
- className: cn(shape, "hover:bg-surface-hover", body),
730
- children: content
796
+ 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),
797
+ children: [
798
+ /* @__PURE__ */ jsx(StepIcon, {
799
+ state: step.state,
800
+ kind: step.kind
801
+ }),
802
+ /* @__PURE__ */ jsx("span", {
803
+ className: "min-w-0 flex-1 truncate",
804
+ children: step.label
805
+ }),
806
+ step.detail ? /* @__PURE__ */ jsx("span", {
807
+ className: "shrink-0 tabular-nums text-fg-4",
808
+ children: step.detail
809
+ }) : null,
810
+ agent ? /* @__PURE__ */ jsx(ArrowRight, { className: "size-3.5 shrink-0 text-fg-4" }) : null
811
+ ]
731
812
  });
732
813
  }
733
814
  function StepIcon({ state, kind }) {
734
- if (kind === "task" && state !== "running" && state !== "failed") return /* @__PURE__ */ jsx(Dot, { className: "size-3 shrink-0" });
815
+ if (kind === "task" && state !== "running" && state !== "failed") return /* @__PURE__ */ jsx(Dot, { className: "size-[11px] shrink-0" });
735
816
  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" });
817
+ case "running": return /* @__PURE__ */ jsx(Spinner, { className: "size-[11px] shrink-0" });
818
+ case "failed": return /* @__PURE__ */ jsx(CircleAlert, { className: "size-[11px] shrink-0" });
819
+ case "pending": return /* @__PURE__ */ jsx(PauseCircle, { className: "size-[11px] shrink-0" });
820
+ default: return /* @__PURE__ */ jsx(Check, { className: "size-[11px] shrink-0" });
740
821
  }
741
822
  }
742
823
  //#endregion
824
+ //#region src/components/agent/SessionItem.tsx
825
+ function SessionItem({ row, active = false, activeStepKey, showGateway, showProject = true, projectIcons, expanded, onExpandedChange, onSelect, onSelectSubagent, onRevealStep, onRename, renameOn = "doubleClick", editing, onEditingChange, actions, className }) {
826
+ const { info } = row;
827
+ const [ownExpanded, setOwnExpanded] = useState(false);
828
+ const open = expanded ?? ownExpanded;
829
+ const setOpen = (next) => {
830
+ setOwnExpanded(next);
831
+ onExpandedChange?.(next);
832
+ };
833
+ const [ownEditing, setOwnEditing] = useState(false);
834
+ const isEditing = editing ?? ownEditing;
835
+ const setEditing = (next) => {
836
+ setOwnEditing(next);
837
+ onEditingChange?.(next);
838
+ };
839
+ const engine = info.engine ?? "claude";
840
+ const project = showProject ? projectLabel(row) : projectSubpath(row);
841
+ const projectIcon = showProject ? info.project?.icon : void 0;
842
+ const iconSrc = projectIcon?.type === "image" ? projectIcons?.[projectIcon.hash] : void 0;
843
+ const cost = formatCost(info.totalCostUsd);
844
+ const extras = [
845
+ showGateway ? row.hostName : void 0,
846
+ info.profile ? `@${info.profile}` : void 0,
847
+ cost === "—" ? void 0 : cost
848
+ ].filter((part) => Boolean(part));
849
+ const model = friendlyModel(info.model);
850
+ const parts = [];
851
+ if (model) parts.push(/* @__PURE__ */ jsx("span", {
852
+ className: vendorTextClass(engine, info.model),
853
+ children: model
854
+ }, "model"));
855
+ if (project !== void 0) parts.push(/* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx(ProjectIcon, {
856
+ icon: projectIcon,
857
+ src: iconSrc,
858
+ name: project,
859
+ className: "mr-1.5 size-4 align-[-0.3em]"
860
+ }), project] }, "project"));
861
+ for (const extra of extras) parts.push(/* @__PURE__ */ jsx("span", { children: extra }, extra));
862
+ const steps = sessionSteps(info, (toolUseId, kind) => {
863
+ if (kind === "agent") return onSelectSubagent ? onSelectSubagent(toolUseId) : onSelect?.();
864
+ return onRevealStep ? onRevealStep(toolUseId) : onSelect?.();
865
+ });
866
+ const holdsOpenAgent = steps.some((s) => s.kind === "agent" && s.key === activeStepKey);
867
+ return /* @__PURE__ */ jsxs("div", {
868
+ "data-slot": "session-item",
869
+ "data-active": active || void 0,
870
+ role: "button",
871
+ tabIndex: 0,
872
+ onClick: (e) => {
873
+ if (e.detail > 1 || isEditing) return;
874
+ onSelect?.();
875
+ },
876
+ onKeyDown: (e) => {
877
+ if (e.target !== e.currentTarget) return;
878
+ if (e.key === "Enter" || e.key === " ") {
879
+ e.preventDefault();
880
+ onSelect?.();
881
+ }
882
+ },
883
+ 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),
884
+ children: [/* @__PURE__ */ jsxs("div", {
885
+ className: "flex flex-col gap-1 py-0.5 pr-0.5 pl-1.5",
886
+ children: [/* @__PURE__ */ jsxs("div", {
887
+ className: "flex h-5 items-center gap-1.5 overflow-hidden",
888
+ children: [
889
+ /* @__PURE__ */ jsx(Gutter, { children: /* @__PURE__ */ jsx(SessionStatusIcon, { row }) }),
890
+ isEditing && onRename ? /* @__PURE__ */ jsx(NameEditor, {
891
+ initial: info.title ?? "",
892
+ onCommit: (title) => {
893
+ setEditing(false);
894
+ if (title !== (info.title ?? "")) onRename(title);
895
+ },
896
+ onCancel: () => setEditing(false)
897
+ }) : /* @__PURE__ */ jsx("span", {
898
+ onDoubleClick: onRename && renameOn === "doubleClick" ? (e) => {
899
+ e.stopPropagation();
900
+ setEditing(true);
901
+ } : void 0,
902
+ className: "min-w-0 flex-1 truncate text-body-sm font-medium tracking-[-0.005em] text-fg-1",
903
+ children: sessionLabel(info)
904
+ }),
905
+ row.unseen > 0 ? /* @__PURE__ */ jsx("span", {
906
+ title: `${row.unseen} new`,
907
+ 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"),
908
+ children: row.unseen
909
+ }) : null,
910
+ /* @__PURE__ */ jsx(ContextRing, {
911
+ usage: info.contextUsage,
912
+ size: 16,
913
+ className: "p-0.5"
914
+ })
915
+ ]
916
+ }), /* @__PURE__ */ jsxs("div", {
917
+ className: "flex h-5 items-center gap-1.5 overflow-hidden text-body-sm tracking-[-0.005em]",
918
+ children: [
919
+ /* @__PURE__ */ jsx(Gutter, { children: /* @__PURE__ */ jsx(EngineIcon, {
920
+ engine,
921
+ model: info.model,
922
+ className: cn("size-4", vendorMarkClass(engine, info.model))
923
+ }) }),
924
+ /* @__PURE__ */ jsx("span", {
925
+ className: "min-w-0 truncate text-fg-4",
926
+ children: parts.map((part, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 ? " · " : "", part] }, i))
927
+ }),
928
+ /* @__PURE__ */ jsxs("span", {
929
+ className: "shrink-0 text-fg-4",
930
+ children: [parts.length > 0 ? "· " : "", formatRelativeTime(info.lastActivityAt ?? info.createdAt)]
931
+ }),
932
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
933
+ steps.length > 0 ? /* @__PURE__ */ jsx(StepToggle, {
934
+ expanded: open,
935
+ running: runningSteps(steps),
936
+ total: steps.length,
937
+ noun: steps[0].noun,
938
+ onToggle: () => setOpen(!open)
939
+ }) : null,
940
+ actions
941
+ ]
942
+ })]
943
+ }), open && steps.length > 0 ? /* @__PURE__ */ jsx("div", {
944
+ className: "flex flex-col",
945
+ children: steps.map((step) => /* @__PURE__ */ jsx(StepRow, {
946
+ step,
947
+ active: step.key === activeStepKey,
948
+ onSelect: step.onSelect
949
+ }, step.key))
950
+ }) : null]
951
+ });
952
+ }
953
+ /**
954
+ * The one 16px cell both lines hang their glyph in.
955
+ *
956
+ * A shared column rather than a leading character: the state and the engine mark
957
+ * are different widths and a bare glyph would let the two text columns start at
958
+ * different x. Centring inside a fixed cell is what makes them agree.
959
+ */
960
+ function Gutter({ children }) {
961
+ return /* @__PURE__ */ jsx("span", {
962
+ className: "flex size-4 shrink-0 items-center justify-center",
963
+ children
964
+ });
965
+ }
966
+ /**
967
+ * Enter commits, Escape cancels, blur commits — the editor's inline-rename feel.
968
+ * An empty value is a deliberate "clear the name", which the gateway answers by
969
+ * handing back the derived title.
970
+ *
971
+ * The blur rule needs one guard. Selecting a session focuses a different surface
972
+ * — in the extension, a different VS Code view — so this document can lose focus
973
+ * a tick after a double-click opened the editor, and an unguarded blur reads
974
+ * that as "the user clicked away" and closes the editor in the frame it
975
+ * appeared. `document.hasFocus()` tells the two apart: focus moving WITHIN this
976
+ * document is the user leaving the field; the whole view losing focus is not.
977
+ * When the window comes back, so does the caret.
978
+ */
979
+ function NameEditor({ initial, onCommit, onCancel }) {
980
+ const [value, setValue] = useState(initial);
981
+ const ref = useRef(null);
982
+ useEffect(() => {
983
+ ref.current?.focus();
984
+ ref.current?.select();
985
+ }, []);
986
+ useEffect(() => {
987
+ const onWindowFocus = () => {
988
+ ref.current?.focus();
989
+ ref.current?.select();
990
+ };
991
+ window.addEventListener("focus", onWindowFocus);
992
+ return () => window.removeEventListener("focus", onWindowFocus);
993
+ }, []);
994
+ return /* @__PURE__ */ jsx("input", {
995
+ ref,
996
+ value,
997
+ spellCheck: false,
998
+ placeholder: "Session name",
999
+ "aria-label": "Session name",
1000
+ onClick: (e) => e.stopPropagation(),
1001
+ onDoubleClick: (e) => e.stopPropagation(),
1002
+ onChange: (e) => setValue(e.target.value),
1003
+ onBlur: () => {
1004
+ if (document.hasFocus()) onCommit(value.trim());
1005
+ },
1006
+ onKeyDown: (e) => {
1007
+ e.stopPropagation();
1008
+ if (e.key === "Enter") onCommit(value.trim());
1009
+ else if (e.key === "Escape") onCancel();
1010
+ },
1011
+ 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")
1012
+ });
1013
+ }
1014
+ //#endregion
743
1015
  //#region src/components/agent/SessionBrowser.tsx
744
1016
  /**
745
1017
  * How a list row is drawn, in one place, so `SidebarRow` in `web` matches this
@@ -760,7 +1032,7 @@ function StepIcon({ state, kind }) {
760
1032
  function rowShapeClass(active) {
761
1033
  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
1034
  }
763
- function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelect, onDelete, onRename, onClearContext, onSelectSubagent, emptyState, showControls = true, projectIcons, className }) {
1035
+ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, activeSubagentId, onSelect, onDelete, onRename, onClearContext, onSelectSubagent, onRevealStep, emptyState, showControls = true, projectIcons, className }) {
764
1036
  const visible = useMemo(() => filterRows(rows, config, scope), [
765
1037
  rows,
766
1038
  config,
@@ -937,11 +1209,11 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelec
937
1209
  title: "Nothing here",
938
1210
  description: "No session to show."
939
1211
  }) : /* @__PURE__ */ jsx("div", {
940
- className: "flex flex-col gap-4",
1212
+ className: "flex flex-col gap-4 px-1",
941
1213
  children: groups.map((group) => /* @__PURE__ */ jsxs("div", {
942
1214
  className: "flex flex-col gap-1",
943
1215
  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",
1216
+ className: "flex items-center gap-2 px-2 text-label font-medium text-fg-4",
945
1217
  children: [
946
1218
  config.groupBy === "project" ? /* @__PURE__ */ jsx(ProjectIcon, {
947
1219
  icon: group.rows[0]?.info.project?.icon,
@@ -960,6 +1232,7 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelec
960
1232
  }) : null, group.rows.map((row) => /* @__PURE__ */ jsx(SessionRowItem, {
961
1233
  row,
962
1234
  active: row.info.id === activeId,
1235
+ activeSubagentId,
963
1236
  showGateway: gateways.length > 1 && config.groupBy !== "gateway",
964
1237
  showProject: config.groupBy !== "project",
965
1238
  projectIcons,
@@ -967,7 +1240,8 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, onSelec
967
1240
  onDelete,
968
1241
  onRename,
969
1242
  onClearContext,
970
- onSelectSubagent
1243
+ onSelectSubagent,
1244
+ onRevealStep
971
1245
  }, `${row.hostId}:${row.info.id}`))]
972
1246
  }, group.key))
973
1247
  })
@@ -981,202 +1255,65 @@ function iconSrcOf(row, icons) {
981
1255
  const icon = row?.info.project?.icon;
982
1256
  return icon?.type === "image" ? icons?.[icon.hash] : void 0;
983
1257
  }
984
- function SessionRowItem({ row, active, showGateway, showProject = true, projectIcons, onSelect, onDelete, onRename, onClearContext, onSelectSubagent }) {
1258
+ function SessionRowItem({ row, active, activeSubagentId, showGateway, showProject = true, projectIcons, onSelect, onDelete, onRename, onClearContext, onSelectSubagent, onRevealStep }) {
985
1259
  const { info } = row;
986
1260
  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
1261
+ return /* @__PURE__ */ jsx(SessionItem, {
1262
+ row,
1263
+ active: active === true,
1264
+ activeStepKey: activeSubagentId,
1265
+ showGateway,
1266
+ showProject,
1267
+ projectIcons,
1268
+ onSelect: () => onSelect?.(row),
1269
+ onSelectSubagent: onSelectSubagent ? (id) => onSelectSubagent(row, id) : void 0,
1270
+ onRevealStep: onRevealStep ? (id) => onRevealStep(row, id) : void 0,
1271
+ onRename: onRename ? (title) => onRename(row, title) : void 0,
1272
+ renameOn: "external",
1273
+ editing,
1274
+ onEditingChange: setEditing,
1275
+ actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [
1276
+ onRename && !editing ? /* @__PURE__ */ jsx(RowAction, {
1277
+ label: "Rename session",
1278
+ onClick: () => setEditing(true),
1279
+ children: /* @__PURE__ */ jsx(Pencil, { className: "size-3 text-fg-3" })
1280
+ }) : null,
1281
+ onClearContext && info.capabilities?.clearContext ? /* @__PURE__ */ jsx(RowAction, {
1282
+ label: "Clear context",
1283
+ title: "Clear the conversation the session keeps running and the old conversation stays resumable",
1284
+ onClick: () => onClearContext(row),
1285
+ children: /* @__PURE__ */ jsx(Eraser, { className: "size-3 text-fg-3" })
1286
+ }) : null,
1287
+ onDelete ? /* @__PURE__ */ jsx(RowAction, {
1288
+ label: "Close session",
1289
+ onClick: () => onDelete(row),
1290
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3 text-fg-3" })
1291
+ }) : null
1292
+ ] })
1125
1293
  });
1126
1294
  }
1127
1295
  /**
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.
1296
+ * One of the dashboard's hover actions.
1132
1297
  *
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.
1298
+ * Hover-revealed rather than always on, which is the opposite of the
1299
+ * extension's single overflow glyph and the right call for each: there are
1300
+ * three of them here and a sidebar card has room for one, so the extension
1301
+ * spends that room on a menu and this spends the row's hover on the actions
1302
+ * themselves. Both stop the click the whole card is pressable underneath, and
1303
+ * an action that also selected the session would do two things per press.
1159
1304
  */
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();
1305
+ function RowAction({ label, title, onClick, children }) {
1306
+ return /* @__PURE__ */ jsx(Button, {
1307
+ variant: "ghost",
1308
+ size: "icon-sm",
1309
+ "aria-label": label,
1310
+ title: title ?? label,
1311
+ className: "size-5 shrink-0 opacity-0 transition-opacity group-hover:opacity-100",
1312
+ onClick: (e) => {
1177
1313
  e.stopPropagation();
1314
+ onClick();
1178
1315
  },
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"
1316
+ children
1180
1317
  });
1181
1318
  }
1182
1319
  /** A multi-select facet. Empty = no filter, which is why the trigger reads the
@@ -1238,6 +1375,6 @@ function OneOfSelect({ label, value, options, onChange }) {
1238
1375
  });
1239
1376
  }
1240
1377
  //#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 };
1378
+ 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
1379
 
1243
1380
  //# sourceMappingURL=index.mjs.map