@workerdeck/ui 2.8.0 → 2.9.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.
@@ -5,11 +5,11 @@ import { clsx } from "clsx";
5
5
  import { extendTailwindMerge } from "tailwind-merge";
6
6
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { Select } from "@base-ui/react/select";
8
- import { ArrowDown, ArrowDownCircle, ArrowLeft, ArrowRight, ArrowUp, AtSign, Brain, ChartPie, Check, CheckSquare, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, Circle, CircleAlert, ClipboardList, Clock, Code, Copy, Download, File, FileDiff, FileText, Folder, FolderSearch, FolderTree, Gauge, Globe, Hand, Image, Info, Link2, LoaderCircle, MessageCircleQuestion, MessageSquareText, MoreHorizontal, Paperclip, PencilLine, Plug, Power, PowerOff, Puzzle, RefreshCw, RotateCcw, RotateCw, Search, ShieldCheck, SlashSquare, Sparkles, Square, SquarePen, Terminal, TriangleAlert, UsersRound, WifiOff, Wrench, X, Zap } from "lucide-react";
8
+ import { ArrowDown, ArrowDownCircle, ArrowLeft, ArrowRight, ArrowUp, AtSign, Brain, ChartPie, Check, CheckSquare, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, Circle, CircleAlert, ClipboardList, Clock, Code, Copy, Download, File, FileDiff, FileText, Folder, FolderSearch, FolderTree, Gauge, Globe, Hand, Image, Info, Link2, LoaderCircle, MessageCircleQuestion, MessageSquareText, MoreHorizontal, Paperclip, PencilLine, Plug, Power, PowerOff, Puzzle, RefreshCw, RotateCcw, RotateCw, Search, ShieldCheck, SlashSquare, SlidersHorizontal, Sparkles, Square, SquarePen, Terminal, TriangleAlert, UsersRound, WifiOff, Wrench, X, Zap } from "lucide-react";
9
9
  import { Menu } from "@base-ui/react/menu";
10
10
  import { Dialog } from "@base-ui/react/dialog";
11
11
  import { Tooltip } from "@base-ui/react/tooltip";
12
- import { PROTOCOL_VERSION, mergeUsage, orderUsageWindows, parseTodoWriteInput, parseUserQuestions, sessionTasks, subagentLabel, taskCountLabel, taskSummary, toolTitle, usageInfos, visibleTasks } from "@workerdeck/protocol";
12
+ import { PROTOCOL_VERSION, mergeUsage, orderUsageWindows, parseTodoWriteInput, parseUserQuestions, sessionTasks, subagentLabel, taskCountLabel, taskSummary, toolTitle, usageInfos, usageIsStale, visibleTasks } from "@workerdeck/protocol";
13
13
  import { recapLine, scanPromptTokens, summarizeSince, useAttachments, useClaudeSession, useDraft, useHostFileSearch, useProfileUsage, useToolCallHost } from "@workerdeck/react";
14
14
  import { Streamdown } from "streamdown";
15
15
  import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
@@ -4411,6 +4411,116 @@ function launchTrigger(opts) {
4411
4411
  };
4412
4412
  }
4413
4413
  //#endregion
4414
+ //#region src/components/agent/composer-commands.ts
4415
+ const KIND_ORDER = {
4416
+ command: 0,
4417
+ client: 1,
4418
+ skill: 2
4419
+ };
4420
+ function cleanCommandName(name) {
4421
+ return name.replace(/\s*\(MCP\)$/i, "");
4422
+ }
4423
+ function skillPrompt(skill) {
4424
+ const base = skill.defaultPrompt?.trim() || `Use the ${skill.displayName ?? skill.name} skill:`;
4425
+ return /\s$/.test(base) ? base : base + " ";
4426
+ }
4427
+ function mergeComposerRows(sources) {
4428
+ const rows = [];
4429
+ const engineNames = /* @__PURE__ */ new Set();
4430
+ for (const command of sources.commands ?? []) {
4431
+ const name = cleanCommandName(command.name);
4432
+ if (engineNames.has(name)) continue;
4433
+ engineNames.add(name);
4434
+ for (const alias of command.aliases ?? []) engineNames.add(cleanCommandName(alias));
4435
+ rows.push({
4436
+ kind: "command",
4437
+ name,
4438
+ description: command.description,
4439
+ argumentHint: command.argumentHint,
4440
+ aliases: command.aliases
4441
+ });
4442
+ }
4443
+ for (const command of sources.clientCommands ?? []) {
4444
+ if (engineNames.has(command.name)) continue;
4445
+ rows.push({
4446
+ kind: "client",
4447
+ name: command.name,
4448
+ description: command.description,
4449
+ argumentHint: command.argumentHint,
4450
+ command
4451
+ });
4452
+ }
4453
+ for (const skill of sources.skills ?? []) rows.push({
4454
+ kind: "skill",
4455
+ name: skill.name,
4456
+ label: skill.displayName ?? skill.name,
4457
+ description: skill.shortDescription ?? skill.description,
4458
+ enabled: skill.enabled,
4459
+ scope: skill.scope,
4460
+ skill
4461
+ });
4462
+ return rows;
4463
+ }
4464
+ function haystacks(row) {
4465
+ if (row.kind === "skill") return [
4466
+ row.name,
4467
+ row.label,
4468
+ ...row.name.split(/[-:_]/)
4469
+ ];
4470
+ return [
4471
+ row.name,
4472
+ ...row.kind === "command" ? row.aliases ?? [] : [],
4473
+ ...row.name.split(":")
4474
+ ];
4475
+ }
4476
+ function matchScore(query, candidates) {
4477
+ const needle = query.toLowerCase();
4478
+ const lowered = candidates.map((s) => s.toLowerCase());
4479
+ if (lowered.some((h) => h.startsWith(needle))) return 2;
4480
+ return lowered.some((h) => h.includes(needle)) ? 1 : 0;
4481
+ }
4482
+ function rankComposerRows(query, rows) {
4483
+ const scored = [];
4484
+ rows.forEach((row, index) => {
4485
+ if (row.kind === "skill" && !row.enabled) return;
4486
+ const score = matchScore(query, haystacks(row));
4487
+ if (score > 0) scored.push({
4488
+ score,
4489
+ index,
4490
+ row
4491
+ });
4492
+ });
4493
+ scored.sort((a, b) => b.score - a.score || KIND_ORDER[a.row.kind] - KIND_ORDER[b.row.kind] || a.index - b.index);
4494
+ return scored.map(({ row }) => row);
4495
+ }
4496
+ function matchClientCommand(text, commands) {
4497
+ const match = /^\/([\w:-]+)[ \t]*([\s\S]*)$/.exec(text.trim());
4498
+ if (!match) return;
4499
+ const command = commands.find((c) => c.name === match[1]);
4500
+ return command ? {
4501
+ command,
4502
+ args: match[2].trim()
4503
+ } : void 0;
4504
+ }
4505
+ function composerCommandRows(rows) {
4506
+ return rows.map((row) => row.kind === "skill" ? {
4507
+ kind: row.kind,
4508
+ name: row.name,
4509
+ label: row.label,
4510
+ description: row.description,
4511
+ scope: row.scope,
4512
+ enabled: row.enabled,
4513
+ insertText: skillPrompt(row.skill)
4514
+ } : {
4515
+ kind: row.kind,
4516
+ name: row.name,
4517
+ label: `/${row.name}`,
4518
+ description: row.description,
4519
+ enabled: true,
4520
+ insertText: `/${row.name} `
4521
+ });
4522
+ }
4523
+ //#endregion
4414
4524
  //#region src/components/agent/transcript-variant.tsx
4415
4525
  const VariantContext = createContext("cards");
4416
4526
  function TranscriptVariantProvider({ value, children }) {
@@ -4749,6 +4859,69 @@ function TerminalDiff({ patch }) {
4749
4859
  });
4750
4860
  }
4751
4861
  //#endregion
4862
+ //#region src/lib/file-link.ts
4863
+ const SCHEME = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
4864
+ const TRAILING_LINE = /^(.*?):(\d+)(?::\d+)?$/;
4865
+ const FRAGMENT_LINE = /^L(\d+)/;
4866
+ function parseFileLink(href, cwd) {
4867
+ const raw = href?.trim();
4868
+ if (!raw || raw.startsWith("#") || raw.startsWith("//")) return;
4869
+ let target = raw;
4870
+ if (SCHEME.test(target)) {
4871
+ if (!target.startsWith("file:")) return;
4872
+ try {
4873
+ target = decodeURIComponent(new URL(target).pathname);
4874
+ } catch {
4875
+ return;
4876
+ }
4877
+ }
4878
+ let line;
4879
+ const hash = target.indexOf("#");
4880
+ if (hash !== -1) {
4881
+ line = readLine(FRAGMENT_LINE.exec(target.slice(hash + 1))?.[1]);
4882
+ target = target.slice(0, hash);
4883
+ }
4884
+ const suffix = TRAILING_LINE.exec(target);
4885
+ if (suffix) {
4886
+ target = suffix[1];
4887
+ line ??= readLine(suffix[2]);
4888
+ }
4889
+ if (!target) return;
4890
+ const path = target.startsWith("/") ? normalize(target) : cwd ? normalize(`${cwd}/${target}`) : void 0;
4891
+ return path ? {
4892
+ path,
4893
+ ...line === void 0 ? {} : { line }
4894
+ } : void 0;
4895
+ }
4896
+ function readLine(value) {
4897
+ const parsed = value === void 0 ? NaN : Number(value);
4898
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
4899
+ }
4900
+ function normalize(path) {
4901
+ const out = [];
4902
+ for (const part of path.split("/")) {
4903
+ if (part === "" || part === ".") continue;
4904
+ if (part === "..") {
4905
+ out.pop();
4906
+ continue;
4907
+ }
4908
+ out.push(part);
4909
+ }
4910
+ return `/${out.join("/")}`;
4911
+ }
4912
+ //#endregion
4913
+ //#region src/components/terminal/file-link.tsx
4914
+ const FileLinkContext = createContext(void 0);
4915
+ function FileLinkProvider({ value, children }) {
4916
+ return /* @__PURE__ */ jsx(FileLinkContext.Provider, {
4917
+ value,
4918
+ children
4919
+ });
4920
+ }
4921
+ function useFileLinks() {
4922
+ return useContext(FileLinkContext);
4923
+ }
4924
+ //#endregion
4752
4925
  //#region src/components/terminal/markdown.tsx
4753
4926
  function codeText(node) {
4754
4927
  if (node === null || node === void 0 || typeof node === "boolean") return "";
@@ -4788,6 +4961,29 @@ function heading(tone) {
4788
4961
  });
4789
4962
  };
4790
4963
  }
4964
+ function Link({ href, children }) {
4965
+ const links = useFileLinks();
4966
+ const file = links ? parseFileLink(href, links.cwd) : void 0;
4967
+ if (file) return /* @__PURE__ */ jsx("button", {
4968
+ type: "button",
4969
+ className: "term-link",
4970
+ "data-tone": "blue",
4971
+ title: file.line === void 0 ? file.path : `${file.path}:${file.line}`,
4972
+ onClick: (event) => {
4973
+ event.stopPropagation();
4974
+ links?.open(file.path, file.line);
4975
+ },
4976
+ children
4977
+ });
4978
+ return /* @__PURE__ */ jsx("a", {
4979
+ className: "term-link",
4980
+ "data-tone": "blue",
4981
+ href,
4982
+ target: "_blank",
4983
+ rel: "noreferrer",
4984
+ children
4985
+ });
4986
+ }
4791
4987
  const TERMINAL_COMPONENTS = {
4792
4988
  p: ({ children }) => /* @__PURE__ */ jsx("div", {
4793
4989
  className: "term-block",
@@ -4844,12 +5040,8 @@ const TERMINAL_COMPONENTS = {
4844
5040
  className: "term-em",
4845
5041
  children
4846
5042
  }),
4847
- a: ({ children, href }) => /* @__PURE__ */ jsx("a", {
4848
- className: "term-link",
4849
- "data-tone": "blue",
5043
+ a: ({ children, href }) => /* @__PURE__ */ jsx(Link, {
4850
5044
  href,
4851
- target: "_blank",
4852
- rel: "noreferrer",
4853
5045
  children
4854
5046
  }),
4855
5047
  table: ({ children }) => /* @__PURE__ */ jsx("div", {
@@ -5557,20 +5749,7 @@ function TerminalSurface({ fontSize, lineHeight, bleed, affordances, className,
5557
5749
  //#region src/components/agent/Composer.tsx
5558
5750
  const SHELL_PLACEHOLDER = "Run a command on the host…";
5559
5751
  const SHELL_HINT = "! shell mode · esc to exit";
5560
- function cleanName(name) {
5561
- return name.replace(/\s*\(MCP\)$/i, "");
5562
- }
5563
- function skillPrompt(skill) {
5564
- const base = skill.defaultPrompt?.trim() || `$${skill.name}`;
5565
- return /\s$/.test(base) ? base : base + " ";
5566
- }
5567
- function matchScore(query, haystacks) {
5568
- const needle = query.toLowerCase();
5569
- const lowered = haystacks.map((s) => s.toLowerCase());
5570
- if (lowered.some((h) => h.startsWith(needle))) return 2;
5571
- return lowered.some((h) => h.includes(needle)) ? 1 : 0;
5572
- }
5573
- function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message the agent…", commands, skills, onSearchFiles, onShellCommand, attachments, toolbar, layout = "stacked", fontSize, lineHeight, affordances, draft, className, ref }) {
5752
+ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message the agent…", commands, skills, clientCommands, onSearchFiles, onShellCommand, attachments, toolbar, layout = "stacked", fontSize, lineHeight, affordances, draft, className, ref }) {
5574
5753
  const inline = layout === "inline";
5575
5754
  const terminal = useTranscriptVariant() === "terminal";
5576
5755
  const [initialDraft] = useState(() => draft?.initialText ?? "");
@@ -5578,6 +5757,7 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5578
5757
  const fileInput = useRef(null);
5579
5758
  const [dragging, setDragging] = useState(false);
5580
5759
  const [shellMode, setShellMode] = useState(false);
5760
+ const [helpOpen, setHelpOpen] = useState(false);
5581
5761
  useImperativeHandle(ref, () => ({
5582
5762
  insertText: (text) => {
5583
5763
  const prefix = plainText.length > 0 && !/\s$/.test(plainText) ? " " : "";
@@ -5592,71 +5772,36 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5592
5772
  ]);
5593
5773
  const triggers = useMemo(() => {
5594
5774
  const configured = [];
5595
- const usableSkills = (skills ?? []).filter((s) => s.enabled);
5596
- if (commands && commands.length > 0) {
5597
- const seen = /* @__PURE__ */ new Set();
5598
- const unique = commands.flatMap((c) => {
5599
- const name = cleanName(c.name);
5600
- if (seen.has(name)) return [];
5601
- seen.add(name);
5602
- return [{
5603
- ...c,
5604
- name
5605
- }];
5606
- });
5775
+ const rows = mergeComposerRows({
5776
+ commands,
5777
+ clientCommands,
5778
+ skills
5779
+ });
5780
+ if (rows.length > 0) {
5781
+ const byTag = new Map(rows.map((row) => [`${row.kind}:${row.name}`, row]));
5607
5782
  configured.push(commandTrigger({
5608
- onSearch: (query) => {
5609
- const scored = [];
5610
- for (const c of unique) {
5611
- const score = matchScore(query, [
5612
- c.name,
5613
- ...c.aliases ?? [],
5614
- ...c.name.split(":")
5615
- ]);
5616
- if (score === 0) continue;
5617
- scored.push({
5618
- score,
5619
- suggestion: {
5620
- value: c.name,
5621
- label: `/${c.name}${c.argumentHint ? ` ${c.argumentHint}` : ""}`,
5622
- description: c.description
5623
- }
5624
- });
5625
- }
5626
- scored.sort((a, b) => b.score - a.score);
5627
- return scored.map(({ suggestion }) => suggestion);
5783
+ onSearch: (query) => rankComposerRows(query, rows).map((row) => row.kind === "skill" ? {
5784
+ value: row.name,
5785
+ label: row.label,
5786
+ description: row.description ? `Skill · ${row.description}` : "Skill · inserts a message you can edit",
5787
+ icon: /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5 text-fg-3" }),
5788
+ data: `${row.kind}:${row.name}`
5789
+ } : {
5790
+ value: row.name,
5791
+ label: `/${row.name}${row.argumentHint ? ` ${row.argumentHint}` : ""}`,
5792
+ description: row.description,
5793
+ icon: row.kind === "client" ? /* @__PURE__ */ jsx(SlidersHorizontal, { className: "size-3.5 text-fg-3" }) : void 0,
5794
+ data: `${row.kind}:${row.name}`
5795
+ }),
5796
+ insertAsText: (suggestion) => {
5797
+ const row = byTag.get(String(suggestion.data));
5798
+ if (row?.kind === "skill") return skillPrompt(row.skill);
5799
+ return row?.kind === "client" && row.command.requiresArgs ? `/${row.name} ` : void 0;
5628
5800
  },
5629
5801
  onSelect: (suggestion) => suggestion.value,
5630
5802
  chipClassName: "font-mono"
5631
5803
  }));
5632
5804
  }
5633
- if (usableSkills.length > 0) configured.push(commandTrigger({
5634
- char: "$",
5635
- accessibilityLabel: "skill",
5636
- onSearch: (query) => {
5637
- const scored = [];
5638
- for (const skill of usableSkills) {
5639
- const score = matchScore(query, [skill.name, ...skill.name.split(/[-:_]/)]);
5640
- if (score === 0) continue;
5641
- const summary = skill.shortDescription ?? skill.description;
5642
- scored.push({
5643
- score,
5644
- suggestion: {
5645
- value: skill.name,
5646
- label: skill.displayName ?? skill.name,
5647
- description: summary ? `Skill · ${summary}` : "Skill · inserts a message you can edit",
5648
- icon: /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5 text-fg-3" })
5649
- }
5650
- });
5651
- }
5652
- scored.sort((a, b) => b.score - a.score);
5653
- return scored.map(({ suggestion }) => suggestion);
5654
- },
5655
- insertAsText: (suggestion) => {
5656
- const skill = usableSkills.find((s) => s.name === suggestion.value);
5657
- return skill ? skillPrompt(skill) : `$${suggestion.value} `;
5658
- }
5659
- }));
5660
5805
  if (onSearchFiles) configured.push(mentionTrigger({
5661
5806
  searchDebounceMs: 150,
5662
5807
  onSearch: async (query, options) => {
@@ -5680,6 +5825,7 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5680
5825
  }, [
5681
5826
  commands,
5682
5827
  skills,
5828
+ clientCommands,
5683
5829
  onSearchFiles,
5684
5830
  onShellCommand
5685
5831
  ]);
@@ -5711,6 +5857,24 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5711
5857
  setShellMode(false);
5712
5858
  focus();
5713
5859
  };
5860
+ const hints = [
5861
+ triggers?.some((t) => t.char === "/") ? {
5862
+ key: "/",
5863
+ what: "commands and skills"
5864
+ } : void 0,
5865
+ onSearchFiles ? {
5866
+ key: "@",
5867
+ what: "mention a file"
5868
+ } : void 0,
5869
+ onShellCommand ? {
5870
+ key: "!",
5871
+ what: "run a shell command"
5872
+ } : void 0,
5873
+ {
5874
+ key: "?",
5875
+ what: "this list, on an empty composer"
5876
+ }
5877
+ ].filter((h) => h !== void 0);
5714
5878
  const shellKeys = shellMode ? {
5715
5879
  onEscape: leaveShellMode,
5716
5880
  onKeyDown: (e) => {
@@ -5719,7 +5883,30 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5719
5883
  leaveShellMode();
5720
5884
  }
5721
5885
  }
5722
- } : {};
5886
+ } : {
5887
+ onEscape: helpOpen ? () => setHelpOpen(false) : void 0,
5888
+ onKeyDown: (e) => {
5889
+ if (e.key === "?" && isEmpty && !e.metaKey && !e.ctrlKey) {
5890
+ e.preventDefault();
5891
+ setHelpOpen(true);
5892
+ return;
5893
+ }
5894
+ if (helpOpen) setHelpOpen(false);
5895
+ }
5896
+ };
5897
+ const helpHints = helpOpen ? /* @__PURE__ */ jsx(Fragment$1, { children: hints.map((hint) => /* @__PURE__ */ jsxs("span", {
5898
+ className: "flex items-center gap-1",
5899
+ children: [/* @__PURE__ */ jsx("kbd", {
5900
+ className: "font-mono text-text",
5901
+ children: hint.key
5902
+ }), hint.what]
5903
+ }, hint.key)) }) : null;
5904
+ const helpRow = helpOpen ? /* @__PURE__ */ jsx("div", {
5905
+ role: "note",
5906
+ "aria-label": "Composer shortcuts",
5907
+ className: "mx-auto mb-1 flex w-full max-w-[var(--wd-transcript-max-width)] flex-wrap items-center gap-x-3 gap-y-1 text-label text-fg-3",
5908
+ children: helpHints
5909
+ }) : null;
5723
5910
  const pick = (files) => {
5724
5911
  if (files && files.length > 0) attachments?.add(files);
5725
5912
  };
@@ -5862,6 +6049,18 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5862
6049
  })
5863
6050
  ]
5864
6051
  }),
6052
+ helpOpen ? /* @__PURE__ */ jsxs("div", {
6053
+ role: "note",
6054
+ "aria-label": "Composer shortcuts",
6055
+ className: "term-row",
6056
+ children: [/* @__PURE__ */ jsx("span", {
6057
+ "aria-hidden": true,
6058
+ className: "term-gutter"
6059
+ }), /* @__PURE__ */ jsx("div", {
6060
+ className: "flex min-w-0 flex-wrap items-center gap-x-[2ch] text-fg-3",
6061
+ children: helpHints
6062
+ })]
6063
+ }) : null,
5865
6064
  shellMode ? /* @__PURE__ */ jsxs("div", {
5866
6065
  className: "term-row",
5867
6066
  children: [/* @__PURE__ */ jsx("span", {
@@ -5889,55 +6088,59 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5889
6088
  return /* @__PURE__ */ jsxs("div", {
5890
6089
  "data-slot": "composer",
5891
6090
  className: cn("px-[var(--wd-composer-padding)] pb-[var(--wd-composer-padding)]", className),
5892
- children: [/* @__PURE__ */ jsxs("div", {
5893
- ...dropHandlers,
5894
- className: cn("mx-auto w-full max-w-[var(--wd-transcript-max-width)] overflow-hidden border border-border bg-bg", "transition-colors rounded-lg shadow-(--shadow-xs)", "focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30", dragging && "border-ring ring-2 ring-ring/30", shellMode && "border-[var(--wd-shell-accent)] focus-within:border-[var(--wd-shell-accent)] focus-within:ring-[var(--wd-shell-accent)]/30", disabled && "opacity-60"),
5895
- children: [staged.length > 0 && attachments ? /* @__PURE__ */ jsx(AttachmentStrip, { attachments }) : null, inline ? /* @__PURE__ */ jsxs("div", {
5896
- className: "flex items-end gap-1 p-1",
5897
- children: [
5898
- shellMode ? /* @__PURE__ */ jsx(ShellBadge, { onLeave: leaveShellMode }) : attach,
5899
- /* @__PURE__ */ jsx(PromptArea, {
5900
- ...bind,
5901
- triggers,
5902
- markdown: false,
5903
- normalizeBullets: false,
5904
- onSubmit: submit,
5905
- disabled,
5906
- placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
5907
- minHeight: 20,
5908
- maxHeight: 192,
5909
- "aria-label": shellMode ? "Run a shell command" : "Message the agent",
5910
- className: "min-w-0 flex-1 py-1 text-body-sm text-text",
5911
- onImagePaste: (file) => attachments?.add([file]),
5912
- ...shellKeys
5913
- }),
5914
- submitButton
5915
- ]
5916
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PromptArea, {
5917
- ...bind,
5918
- triggers,
5919
- markdown: false,
5920
- normalizeBullets: false,
5921
- onSubmit: submit,
5922
- disabled,
5923
- placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
5924
- minHeight: 28,
5925
- maxHeight: 192,
5926
- "aria-label": shellMode ? "Run a shell command" : "Message the agent",
5927
- className: "px-3 pt-2.5 pb-0 text-body-sm text-text",
5928
- onImagePaste: (file) => attachments?.add([file]),
5929
- ...shellKeys
5930
- }), /* @__PURE__ */ jsxs("div", {
5931
- className: "flex items-center justify-between gap-2 px-2 pb-2",
5932
- children: [/* @__PURE__ */ jsxs("div", {
5933
- className: "flex min-w-0 items-center gap-1",
5934
- children: [shellMode ? /* @__PURE__ */ jsx(ShellBadge, { onLeave: leaveShellMode }) : attach, shellMode ? /* @__PURE__ */ jsx("span", {
5935
- className: "text-label text-text-muted",
5936
- children: SHELL_HINT
5937
- }) : toolbar]
5938
- }), submitButton]
5939
- })] })]
5940
- }), errorRow]
6091
+ children: [
6092
+ helpRow,
6093
+ /* @__PURE__ */ jsxs("div", {
6094
+ ...dropHandlers,
6095
+ className: cn("mx-auto w-full max-w-[var(--wd-transcript-max-width)] overflow-hidden border border-border bg-bg", "transition-colors rounded-lg shadow-(--shadow-xs)", "focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/30", dragging && "border-ring ring-2 ring-ring/30", shellMode && "border-[var(--wd-shell-accent)] focus-within:border-[var(--wd-shell-accent)] focus-within:ring-[var(--wd-shell-accent)]/30", disabled && "opacity-60"),
6096
+ children: [staged.length > 0 && attachments ? /* @__PURE__ */ jsx(AttachmentStrip, { attachments }) : null, inline ? /* @__PURE__ */ jsxs("div", {
6097
+ className: "flex items-end gap-1 p-1",
6098
+ children: [
6099
+ shellMode ? /* @__PURE__ */ jsx(ShellBadge, { onLeave: leaveShellMode }) : attach,
6100
+ /* @__PURE__ */ jsx(PromptArea, {
6101
+ ...bind,
6102
+ triggers,
6103
+ markdown: false,
6104
+ normalizeBullets: false,
6105
+ onSubmit: submit,
6106
+ disabled,
6107
+ placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
6108
+ minHeight: 20,
6109
+ maxHeight: 192,
6110
+ "aria-label": shellMode ? "Run a shell command" : "Message the agent",
6111
+ className: "min-w-0 flex-1 py-1 text-body-sm text-text",
6112
+ onImagePaste: (file) => attachments?.add([file]),
6113
+ ...shellKeys
6114
+ }),
6115
+ submitButton
6116
+ ]
6117
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PromptArea, {
6118
+ ...bind,
6119
+ triggers,
6120
+ markdown: false,
6121
+ normalizeBullets: false,
6122
+ onSubmit: submit,
6123
+ disabled,
6124
+ placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
6125
+ minHeight: 28,
6126
+ maxHeight: 192,
6127
+ "aria-label": shellMode ? "Run a shell command" : "Message the agent",
6128
+ className: "px-3 pt-2.5 pb-0 text-body-sm text-text",
6129
+ onImagePaste: (file) => attachments?.add([file]),
6130
+ ...shellKeys
6131
+ }), /* @__PURE__ */ jsxs("div", {
6132
+ className: "flex items-center justify-between gap-2 px-2 pb-2",
6133
+ children: [/* @__PURE__ */ jsxs("div", {
6134
+ className: "flex min-w-0 items-center gap-1",
6135
+ children: [shellMode ? /* @__PURE__ */ jsx(ShellBadge, { onLeave: leaveShellMode }) : attach, shellMode ? /* @__PURE__ */ jsx("span", {
6136
+ className: "text-label text-text-muted",
6137
+ children: SHELL_HINT
6138
+ }) : toolbar]
6139
+ }), submitButton]
6140
+ })] })]
6141
+ }),
6142
+ errorRow
6143
+ ]
5941
6144
  });
5942
6145
  }
5943
6146
  function GlyphButton({ tone, label, disabled, onClick, gutter, className, children }) {
@@ -11009,6 +11212,7 @@ function UsageMeters({ windows, now, className }) {
11009
11212
  function UsageMeter({ window, now }) {
11010
11213
  const { key: windowKey, info, updatedAt, inferredReset } = window;
11011
11214
  const utilization = info.utilization ?? 0;
11215
+ const stale = usageIsStale(window, now);
11012
11216
  const resetsAtMs = info.resetsAt !== void 0 ? info.resetsAt * 1e3 : void 0;
11013
11217
  const duration = rateLimitWindowSeconds(windowKey);
11014
11218
  const remaining = resetsAtMs !== void 0 ? (resetsAtMs - now) / 1e3 : void 0;
@@ -11020,14 +11224,14 @@ function UsageMeter({ window, now }) {
11020
11224
  className: "truncate text-body-sm text-fg-1",
11021
11225
  children: formatRateLimitWindowLong(windowKey)
11022
11226
  }), /* @__PURE__ */ jsxs("span", {
11023
- className: cn("shrink-0 font-mono text-body-sm font-medium", info.status === "rejected" ? "text-danger" : "text-fg-1"),
11227
+ className: cn("shrink-0 font-mono text-body-sm font-medium", info.status === "rejected" ? "text-danger" : stale ? "text-fg-4" : "text-fg-1"),
11024
11228
  children: [utilization.toFixed(0), "% used"]
11025
11229
  })]
11026
11230
  }),
11027
11231
  /* @__PURE__ */ jsxs("div", {
11028
11232
  className: "relative mt-2 h-2 rounded-full bg-border",
11029
11233
  children: [/* @__PURE__ */ jsx("div", {
11030
- className: cn("h-full rounded-full", meterTintClass(utilization)),
11234
+ className: cn("h-full rounded-full", meterTintClass(utilization), stale && "opacity-40"),
11031
11235
  style: { width: `${Math.min(100, Math.max(2, utilization))}%` }
11032
11236
  }), pace !== void 0 ? /* @__PURE__ */ jsx("span", {
11033
11237
  "aria-hidden": true,
@@ -11056,7 +11260,10 @@ function UsageMeter({ window, now }) {
11056
11260
  children: "limit reached"
11057
11261
  }) : null,
11058
11262
  inferredReset ? /* @__PURE__ */ jsx("span", { children: "window reset · nothing reported since" }) : null,
11059
- updatedAt && !inferredReset ? /* @__PURE__ */ jsx("span", { children: formatAgoPrecise(updatedAt, now) }) : null
11263
+ updatedAt && !inferredReset ? /* @__PURE__ */ jsxs("span", {
11264
+ className: cn(stale && "text-warning"),
11265
+ children: [stale ? "last reported " : "", formatAgoPrecise(updatedAt, now)]
11266
+ }) : null
11060
11267
  ]
11061
11268
  })
11062
11269
  ] });
@@ -11135,7 +11342,7 @@ const INTERACTIVE = [
11135
11342
  "[role=\"radio\"]",
11136
11343
  "[role=\"tab\"]"
11137
11344
  ].join(",");
11138
- function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptDensity = "comfortable", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, bookmarks, onToggleBookmark, reveal, subagents, openSubagent, onSubagentChange, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, approvalPrompts, toolHost, clientTools, cacheTranscript, emptyState, onLinkClick, fontSize, className }) {
11345
+ function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptDensity = "comfortable", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, bookmarks, onToggleBookmark, reveal, subagents, openSubagent, onSubagentChange, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, approvalPrompts, toolHost, clientTools, cacheTranscript, emptyState, onLinkClick, onOpenFile, fontSize, className }) {
11139
11346
  const effectiveTermFontSize = terminalMetrics?.fontSize ?? fontSize;
11140
11347
  const effectiveTermLineHeight = terminalMetrics?.lineHeight ?? (fontSize !== void 0 ? Math.round(fontSize * (18 / 13)) : void 0);
11141
11348
  const external = panelSurface === "external";
@@ -11146,7 +11353,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11146
11353
  const [panel, setPanel] = useState();
11147
11354
  const [showCompletedTasks, setShowCompletedTasks] = useState(false);
11148
11355
  const [taskReveal, setTaskReveal] = useState();
11149
- const { state, connection, replaying, protocolMismatch, shell, models, effectiveModel, handle, send, runShell, approve, deny, interrupt, setModel, setPermissionMode, reconnectNow, loadFullResult } = useClaudeSession(client, sessionId, {
11356
+ const { state, connection, replaying, protocolMismatch, shell, models, effectiveModel, handle, send, runShell, approve, deny, interrupt, clearContext, setModel, setPermissionMode, reconnectNow, loadFullResult } = useClaudeSession(client, sessionId, {
11150
11357
  onProtocolError: setProtocolError,
11151
11358
  cacheTranscript
11152
11359
  });
@@ -11208,6 +11415,86 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11208
11415
  subagents,
11209
11416
  state.session?.subagents
11210
11417
  ]);
11418
+ const clientCommands = useMemo(() => {
11419
+ const modes = capabilities.permissionModes;
11420
+ const built = [];
11421
+ if (models.length > 0) built.push({
11422
+ name: "model",
11423
+ description: "Switch the model for this session",
11424
+ argumentHint: "<model>",
11425
+ requiresArgs: true,
11426
+ run: (args) => {
11427
+ const wanted = args.split(/\s+/)[0];
11428
+ if (!wanted) return false;
11429
+ setModel(wanted);
11430
+ return true;
11431
+ }
11432
+ });
11433
+ if (modes.length > 1) built.push({
11434
+ name: "permissions",
11435
+ description: `Set the permission mode (${modes.join(", ")})`,
11436
+ argumentHint: "<mode>",
11437
+ requiresArgs: true,
11438
+ run: (args) => {
11439
+ const wanted = args.split(/\s+/)[0];
11440
+ if (!modes.includes(wanted)) return false;
11441
+ setPermissionMode(wanted);
11442
+ return true;
11443
+ }
11444
+ });
11445
+ if (capabilities.clearContext) built.push({
11446
+ name: "clear",
11447
+ description: "Clear the conversation - the session keeps running and the old one stays resumable",
11448
+ run: () => {
11449
+ clearContext();
11450
+ return true;
11451
+ }
11452
+ });
11453
+ if (capabilities.mcpStatus) built.push({
11454
+ name: "mcp",
11455
+ description: "MCP servers and their status",
11456
+ run: () => (openPanel("mcp"), true)
11457
+ });
11458
+ if (capabilities.contextUsage) built.push({
11459
+ name: "context",
11460
+ description: "Context window usage",
11461
+ run: () => (openPanel("context"), true)
11462
+ });
11463
+ built.push({
11464
+ name: "status",
11465
+ description: "Session details",
11466
+ run: () => (openPanel("info"), true)
11467
+ });
11468
+ if (capabilities.rateLimits) built.push({
11469
+ name: "usage",
11470
+ description: "Rate limits and spend",
11471
+ run: () => (openPanel("usage"), true)
11472
+ });
11473
+ if (capabilities.skillsList) built.push({
11474
+ name: "skills",
11475
+ description: "Browse the skills this session can use",
11476
+ run: () => (openPanel("skills"), true)
11477
+ });
11478
+ return built;
11479
+ }, [
11480
+ capabilities,
11481
+ models.length,
11482
+ openPanel,
11483
+ clearContext,
11484
+ setModel,
11485
+ setPermissionMode
11486
+ ]);
11487
+ const composerCommands = useMemo(() => composerCommandRows(mergeComposerRows({
11488
+ commands: capabilities.slashCommands ? state.commands : void 0,
11489
+ clientCommands,
11490
+ skills: capabilities.skillsList ? state.skills : void 0
11491
+ })), [
11492
+ capabilities.slashCommands,
11493
+ capabilities.skillsList,
11494
+ state.commands,
11495
+ state.skills,
11496
+ clientCommands
11497
+ ]);
11211
11498
  const onVitalsRef = useRef(onVitals);
11212
11499
  onVitalsRef.current = onVitals;
11213
11500
  const vitalsModel = effectiveModel ?? state.model;
@@ -11223,6 +11510,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11223
11510
  permissionMode: state.permissionMode,
11224
11511
  permissionModes,
11225
11512
  skills: state.skills,
11513
+ composerCommands,
11226
11514
  cwd: state.cwd,
11227
11515
  contextUsage: state.contextUsage,
11228
11516
  tasks,
@@ -11241,6 +11529,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11241
11529
  state.permissionMode,
11242
11530
  permissionModes,
11243
11531
  state.skills,
11532
+ composerCommands,
11244
11533
  state.cwd,
11245
11534
  state.contextUsage,
11246
11535
  tasks,
@@ -11296,26 +11585,10 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11296
11585
  const composerRef = useRef(null);
11297
11586
  const jumpToRecap = useRef(null);
11298
11587
  const repinTranscript = useRef(null);
11299
- const commands = useMemo(() => {
11300
- if (!state.commands) return;
11301
- if (state.commands.some((c) => c.name === "model")) return state.commands;
11302
- return [{
11303
- name: "model",
11304
- description: "Switch the model for this session",
11305
- argumentHint: "<model>"
11306
- }, ...state.commands];
11307
- }, [state.commands]);
11308
11588
  const handleSend = (text, attachmentIds) => {
11309
11589
  if (attachmentIds.length === 0) {
11310
- const modelCommand = /^\/model\s+(\S+)$/.exec(text);
11311
- if (modelCommand) {
11312
- setModel(modelCommand[1]);
11313
- return;
11314
- }
11315
- if (capabilities.mcpStatus && text.trim() === "/mcp") {
11316
- openPanel("mcp");
11317
- return;
11318
- }
11590
+ const local = matchClientCommand(text, clientCommands);
11591
+ if (local && local.command.run(local.args)) return;
11319
11592
  }
11320
11593
  setCaughtUp(true);
11321
11594
  repinTranscript.current?.();
@@ -11381,6 +11654,10 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11381
11654
  onOpenTasks: external && !onOpenPanel ? void 0 : () => openPanel("tasks"),
11382
11655
  actions: headerTakesActions ? void 0 : menu
11383
11656
  });
11657
+ const fileLinks = useMemo(() => onOpenFile ? {
11658
+ cwd: state.cwd,
11659
+ open: onOpenFile
11660
+ } : void 0, [onOpenFile, state.cwd]);
11384
11661
  const panelRef = useRef(null);
11385
11662
  useEffect(() => {
11386
11663
  if (!onLinkClick) return;
@@ -11409,224 +11686,228 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11409
11686
  value: transcriptVariant,
11410
11687
  children: /* @__PURE__ */ jsx(TranscriptDensityProvider, {
11411
11688
  value: transcriptDensity,
11412
- children: /* @__PURE__ */ jsx(ToolResultFetchProvider, {
11413
- value: loadFullResult,
11414
- children: /* @__PURE__ */ jsx(ToolTitleProvider, {
11415
- value: state.toolTitles,
11416
- children: /* @__PURE__ */ jsx(ToolResultImageProvider, {
11417
- value: resultImages,
11418
- children: /* @__PURE__ */ jsxs("div", {
11419
- ref: panelRef,
11420
- "data-slot": "session-panel",
11421
- "data-agent-font": transcriptFont,
11422
- onClick: handleClick,
11423
- className: cn("flex h-full min-h-0 flex-col overflow-hidden bg-bg", className),
11424
- style: fontSize !== void 0 ? { "--wd-font-size": `${Math.round(fontSize)}px` } : void 0,
11425
- children: [
11426
- headerTakesActions ? header({ actions: menu }) : header,
11427
- statusPlacement === "top" ? statusBar : null,
11428
- protocolMismatch !== void 0 ? /* @__PURE__ */ jsxs(Notice, {
11429
- level: "warning",
11430
- children: [
11431
- "Server speaks protocol v",
11432
- protocolMismatch,
11433
- ", this build renders v",
11434
- PROTOCOL_VERSION,
11435
- ". Some events may not render."
11436
- ]
11437
- }) : null,
11438
- protocolError ? /* @__PURE__ */ jsx(Notice, {
11439
- level: "error",
11440
- onDismiss: () => setProtocolError(void 0),
11441
- children: protocolError
11442
- }) : null,
11443
- subagentId !== void 0 ? /* @__PURE__ */ jsx(SubagentStrip, {
11444
- task: subagentTask,
11445
- items: subagentFrameItems,
11446
- label: subagentFallbackLabel,
11447
- onBack: leaveSubagent,
11448
- terminal,
11449
- fontSize: effectiveTermFontSize,
11450
- lineHeight: effectiveTermLineHeight
11451
- }) : null,
11452
- /* @__PURE__ */ jsx(BookmarkProvider, {
11453
- value: bookmarkHandle,
11454
- children: /* @__PURE__ */ jsx(Transcript, {
11455
- state,
11456
- fileUrl: sessionId ? (path) => client.sessionFileUrl(sessionId, path) : void 0,
11457
- attachmentUrl: sessionId ? (id) => client.attachmentUrl(sessionId, id) : void 0,
11458
- canBrowseFiles: hostFiles.available,
11459
- hostImage,
11460
- variant: transcriptVariant,
11461
- density: transcriptDensity,
11462
- fontSize: effectiveTermFontSize,
11463
- lineHeight: effectiveTermLineHeight,
11464
- affordances,
11465
- stickyPrompt,
11466
- scrubber,
11467
- bookmarks,
11468
- replaying,
11469
- catchUp: catchUp && newCount > 0 ? {
11470
- from: catchUp.itemCount,
11471
- since: catchUp.since
11472
- } : void 0,
11473
- reveal: taskReveal ?? returnReveal ?? reveal,
11474
- frame: subagentId === void 0 ? void 0 : { parentToolUseId: subagentId },
11475
- onOpenSubagent: enterSubagent,
11476
- emptyState,
11477
- jumpToRecapRef: jumpToRecap,
11478
- repinRef: repinTranscript
11479
- }, subagentId ?? "session")
11480
- }),
11481
- catchUp && newCount > 0 && !replaying && subagentId === void 0 ? /* @__PURE__ */ jsx("div", {
11482
- className: "px-3 pb-1",
11483
- children: /* @__PURE__ */ jsxs("div", {
11484
- "data-slot": "catch-up",
11485
- className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-fg-3",
11689
+ children: /* @__PURE__ */ jsx(FileLinkProvider, {
11690
+ value: fileLinks,
11691
+ children: /* @__PURE__ */ jsx(ToolResultFetchProvider, {
11692
+ value: loadFullResult,
11693
+ children: /* @__PURE__ */ jsx(ToolTitleProvider, {
11694
+ value: state.toolTitles,
11695
+ children: /* @__PURE__ */ jsx(ToolResultImageProvider, {
11696
+ value: resultImages,
11697
+ children: /* @__PURE__ */ jsxs("div", {
11698
+ ref: panelRef,
11699
+ "data-slot": "session-panel",
11700
+ "data-agent-font": transcriptFont,
11701
+ onClick: handleClick,
11702
+ className: cn("flex h-full min-h-0 flex-col overflow-hidden bg-bg", className),
11703
+ style: fontSize !== void 0 ? { "--wd-font-size": `${Math.round(fontSize)}px` } : void 0,
11704
+ children: [
11705
+ headerTakesActions ? header({ actions: menu }) : header,
11706
+ statusPlacement === "top" ? statusBar : null,
11707
+ protocolMismatch !== void 0 ? /* @__PURE__ */ jsxs(Notice, {
11708
+ level: "warning",
11486
11709
  children: [
11487
- /* @__PURE__ */ jsx("span", {
11488
- "aria-hidden": true,
11489
- className: cn("select-none", terminal ? "text-fg-3" : "text-accent"),
11490
- children: "※"
11491
- }),
11492
- /* @__PURE__ */ jsxs("span", {
11493
- className: "min-w-0 flex-1 truncate",
11494
- children: [
11495
- newCount,
11496
- " new ",
11497
- newCount === 1 ? "row" : "rows",
11498
- catchUp.since !== void 0 ? ` since you were last here` : ""
11499
- ]
11500
- }),
11501
- /* @__PURE__ */ jsx("button", {
11502
- type: "button",
11503
- onClick: () => jumpToRecap.current?.(),
11504
- className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
11505
- children: "jump"
11506
- }),
11507
- /* @__PURE__ */ jsx("button", {
11508
- type: "button",
11509
- onClick: () => setCaughtUp(true),
11510
- className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
11511
- children: "dismiss"
11512
- })
11710
+ "Server speaks protocol v",
11711
+ protocolMismatch,
11712
+ ", this build renders v",
11713
+ PROTOCOL_VERSION,
11714
+ ". Some events may not render."
11513
11715
  ]
11514
- })
11515
- }) : null,
11516
- !readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? /* @__PURE__ */ jsx("div", {
11517
- className: cn(terminal ? "pb-2" : "px-3 pb-2"),
11518
- children: /* @__PURE__ */ jsx(PromptSurface, {
11716
+ }) : null,
11717
+ protocolError ? /* @__PURE__ */ jsx(Notice, {
11718
+ level: "error",
11719
+ onDismiss: () => setProtocolError(void 0),
11720
+ children: protocolError
11721
+ }) : null,
11722
+ subagentId !== void 0 ? /* @__PURE__ */ jsx(SubagentStrip, {
11723
+ task: subagentTask,
11724
+ items: subagentFrameItems,
11725
+ label: subagentFallbackLabel,
11726
+ onBack: leaveSubagent,
11519
11727
  terminal,
11520
- metrics: {
11728
+ fontSize: effectiveTermFontSize,
11729
+ lineHeight: effectiveTermLineHeight
11730
+ }) : null,
11731
+ /* @__PURE__ */ jsx(BookmarkProvider, {
11732
+ value: bookmarkHandle,
11733
+ children: /* @__PURE__ */ jsx(Transcript, {
11734
+ state,
11735
+ fileUrl: sessionId ? (path) => client.sessionFileUrl(sessionId, path) : void 0,
11736
+ attachmentUrl: sessionId ? (id) => client.attachmentUrl(sessionId, id) : void 0,
11737
+ canBrowseFiles: hostFiles.available,
11738
+ hostImage,
11739
+ variant: transcriptVariant,
11740
+ density: transcriptDensity,
11521
11741
  fontSize: effectiveTermFontSize,
11522
- lineHeight: effectiveTermLineHeight
11523
- },
11524
- affordances,
11525
- children: state.pendingApprovals.map((request) => {
11526
- const HostPrompt = approvalPrompts?.[request.toolName];
11527
- if (HostPrompt) return /* @__PURE__ */ jsx(HostPrompt, {
11528
- request,
11529
- onApprove: approve,
11530
- onDeny: deny
11531
- }, request.id);
11532
- const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
11533
- if (terminal) return isQuestion ? /* @__PURE__ */ jsx(TerminalQuestionPrompt, {
11534
- request,
11535
- onAnswer: approve,
11536
- onDismiss: (id) => deny(id, "Question dismissed by user")
11537
- }, request.id) : /* @__PURE__ */ jsx(TerminalPermissionPrompt, {
11538
- request,
11539
- onApprove: approve,
11540
- onDeny: deny
11541
- }, request.id);
11542
- return isQuestion ? /* @__PURE__ */ jsx(QuestionPrompt, {
11543
- request,
11544
- onAnswer: approve,
11545
- onDismiss: (id) => deny(id, "Question dismissed by user")
11546
- }, request.id) : /* @__PURE__ */ jsx(PermissionPrompt, {
11547
- request,
11548
- onApprove: approve,
11549
- onDeny: deny
11550
- }, request.id);
11551
- })
11552
- })
11553
- }) : null,
11554
- readOnly || subagentId !== void 0 ? null : /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(Composer, {
11555
- ref: composerRef,
11556
- onSend: handleSend,
11557
- onInterrupt: interrupt,
11558
- busy,
11559
- disabled: ended || !sessionId,
11560
- commands: capabilities.slashCommands ? commands : void 0,
11561
- skills: capabilities.skillsList ? state.skills : void 0,
11562
- attachments,
11563
- draft,
11564
- onSearchFiles: hostFiles.available ? searchComposerFiles : void 0,
11565
- onShellCommand: shell ? runShell : void 0,
11566
- layout: controlsExternal ? "inline" : "stacked",
11567
- toolbar: controlsExternal ? void 0 : sessionControls,
11568
- fontSize: effectiveTermFontSize,
11569
- lineHeight: effectiveTermLineHeight,
11570
- affordances
11571
- }) }),
11572
- statusPlacement === "bottom" ? statusBar : null,
11573
- !external ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
11574
- /* @__PURE__ */ jsx(SessionInfoDialog, {
11575
- state,
11576
- client,
11577
- sessionId,
11578
- open: panel === "info",
11579
- onOpenChange: (next) => setPanel(next ? "info" : void 0)
11580
- }),
11581
- /* @__PURE__ */ jsx(ContextDialog, {
11582
- usage: state.contextUsage,
11583
- engine: state.engine ?? "claude",
11584
- open: panel === "context",
11585
- onOpenChange: (next) => setPanel(next ? "context" : void 0)
11586
- }),
11587
- /* @__PURE__ */ jsx(UsageDialog, {
11588
- rateLimits: windows,
11589
- subscriptionType: state.subscriptionType,
11590
- engine: state.engine ?? "claude",
11591
- totalCostUsd: state.totalCostUsd,
11592
- updatedAt: usageUpdatedAt,
11593
- open: panel === "usage",
11594
- onOpenChange: (next) => setPanel(next ? "usage" : void 0)
11595
- }),
11596
- /* @__PURE__ */ jsx(McpDialog, {
11597
- client,
11598
- sessionId,
11599
- canManageServers: capabilities.mcpServerActions,
11600
- open: panel === "mcp",
11601
- onOpenChange: (next) => setPanel(next ? "mcp" : void 0)
11742
+ lineHeight: effectiveTermLineHeight,
11743
+ affordances,
11744
+ stickyPrompt,
11745
+ scrubber,
11746
+ bookmarks,
11747
+ replaying,
11748
+ catchUp: catchUp && newCount > 0 ? {
11749
+ from: catchUp.itemCount,
11750
+ since: catchUp.since
11751
+ } : void 0,
11752
+ reveal: taskReveal ?? returnReveal ?? reveal,
11753
+ frame: subagentId === void 0 ? void 0 : { parentToolUseId: subagentId },
11754
+ onOpenSubagent: enterSubagent,
11755
+ emptyState,
11756
+ jumpToRecapRef: jumpToRecap,
11757
+ repinRef: repinTranscript
11758
+ }, subagentId ?? "session")
11602
11759
  }),
11603
- /* @__PURE__ */ jsx(TasksDialog, {
11604
- tasks,
11605
- showCompleted: showCompletedTasks,
11606
- onShowCompletedChange: setShowCompletedTasks,
11607
- onSelectTask: (task) => {
11608
- if (task.toolUseId) setTaskReveal({
11609
- toolUseId: task.toolUseId,
11610
- nonce: Date.now()
11611
- });
11612
- },
11613
- open: panel === "tasks",
11614
- onOpenChange: (next) => setPanel(next ? "tasks" : void 0)
11615
- }),
11616
- /* @__PURE__ */ jsx(SkillsDialog, {
11617
- skills: state.skills,
11618
- open: panel === "skills",
11619
- onOpenChange: (next) => setPanel(next ? "skills" : void 0),
11620
- onUse: (skill) => composerRef.current?.insertText(skillPrompt(skill))
11621
- }),
11622
- /* @__PURE__ */ jsx(HostFilesDialog, {
11623
- client,
11624
- cwd: state.cwd,
11625
- open: panel === "files",
11626
- onOpenChange: (next) => setPanel(next ? "files" : void 0)
11627
- })
11628
- ] }) : null
11629
- ]
11760
+ catchUp && newCount > 0 && !replaying && subagentId === void 0 ? /* @__PURE__ */ jsx("div", {
11761
+ className: "px-3 pb-1",
11762
+ children: /* @__PURE__ */ jsxs("div", {
11763
+ "data-slot": "catch-up",
11764
+ className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-fg-3",
11765
+ children: [
11766
+ /* @__PURE__ */ jsx("span", {
11767
+ "aria-hidden": true,
11768
+ className: cn("select-none", terminal ? "text-fg-3" : "text-accent"),
11769
+ children: "※"
11770
+ }),
11771
+ /* @__PURE__ */ jsxs("span", {
11772
+ className: "min-w-0 flex-1 truncate",
11773
+ children: [
11774
+ newCount,
11775
+ " new ",
11776
+ newCount === 1 ? "row" : "rows",
11777
+ catchUp.since !== void 0 ? ` since you were last here` : ""
11778
+ ]
11779
+ }),
11780
+ /* @__PURE__ */ jsx("button", {
11781
+ type: "button",
11782
+ onClick: () => jumpToRecap.current?.(),
11783
+ className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
11784
+ children: "jump"
11785
+ }),
11786
+ /* @__PURE__ */ jsx("button", {
11787
+ type: "button",
11788
+ onClick: () => setCaughtUp(true),
11789
+ className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
11790
+ children: "dismiss"
11791
+ })
11792
+ ]
11793
+ })
11794
+ }) : null,
11795
+ !readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? /* @__PURE__ */ jsx("div", {
11796
+ className: cn(terminal ? "pb-2" : "px-3 pb-2"),
11797
+ children: /* @__PURE__ */ jsx(PromptSurface, {
11798
+ terminal,
11799
+ metrics: {
11800
+ fontSize: effectiveTermFontSize,
11801
+ lineHeight: effectiveTermLineHeight
11802
+ },
11803
+ affordances,
11804
+ children: state.pendingApprovals.map((request) => {
11805
+ const HostPrompt = approvalPrompts?.[request.toolName];
11806
+ if (HostPrompt) return /* @__PURE__ */ jsx(HostPrompt, {
11807
+ request,
11808
+ onApprove: approve,
11809
+ onDeny: deny
11810
+ }, request.id);
11811
+ const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
11812
+ if (terminal) return isQuestion ? /* @__PURE__ */ jsx(TerminalQuestionPrompt, {
11813
+ request,
11814
+ onAnswer: approve,
11815
+ onDismiss: (id) => deny(id, "Question dismissed by user")
11816
+ }, request.id) : /* @__PURE__ */ jsx(TerminalPermissionPrompt, {
11817
+ request,
11818
+ onApprove: approve,
11819
+ onDeny: deny
11820
+ }, request.id);
11821
+ return isQuestion ? /* @__PURE__ */ jsx(QuestionPrompt, {
11822
+ request,
11823
+ onAnswer: approve,
11824
+ onDismiss: (id) => deny(id, "Question dismissed by user")
11825
+ }, request.id) : /* @__PURE__ */ jsx(PermissionPrompt, {
11826
+ request,
11827
+ onApprove: approve,
11828
+ onDeny: deny
11829
+ }, request.id);
11830
+ })
11831
+ })
11832
+ }) : null,
11833
+ readOnly || subagentId !== void 0 ? null : /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(Composer, {
11834
+ ref: composerRef,
11835
+ onSend: handleSend,
11836
+ onInterrupt: interrupt,
11837
+ busy,
11838
+ disabled: ended || !sessionId,
11839
+ commands: capabilities.slashCommands ? state.commands : void 0,
11840
+ skills: capabilities.skillsList ? state.skills : void 0,
11841
+ clientCommands,
11842
+ attachments,
11843
+ draft,
11844
+ onSearchFiles: hostFiles.available ? searchComposerFiles : void 0,
11845
+ onShellCommand: shell ? runShell : void 0,
11846
+ layout: controlsExternal ? "inline" : "stacked",
11847
+ toolbar: controlsExternal ? void 0 : sessionControls,
11848
+ fontSize: effectiveTermFontSize,
11849
+ lineHeight: effectiveTermLineHeight,
11850
+ affordances
11851
+ }) }),
11852
+ statusPlacement === "bottom" ? statusBar : null,
11853
+ !external ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
11854
+ /* @__PURE__ */ jsx(SessionInfoDialog, {
11855
+ state,
11856
+ client,
11857
+ sessionId,
11858
+ open: panel === "info",
11859
+ onOpenChange: (next) => setPanel(next ? "info" : void 0)
11860
+ }),
11861
+ /* @__PURE__ */ jsx(ContextDialog, {
11862
+ usage: state.contextUsage,
11863
+ engine: state.engine ?? "claude",
11864
+ open: panel === "context",
11865
+ onOpenChange: (next) => setPanel(next ? "context" : void 0)
11866
+ }),
11867
+ /* @__PURE__ */ jsx(UsageDialog, {
11868
+ rateLimits: windows,
11869
+ subscriptionType: state.subscriptionType,
11870
+ engine: state.engine ?? "claude",
11871
+ totalCostUsd: state.totalCostUsd,
11872
+ updatedAt: usageUpdatedAt,
11873
+ open: panel === "usage",
11874
+ onOpenChange: (next) => setPanel(next ? "usage" : void 0)
11875
+ }),
11876
+ /* @__PURE__ */ jsx(McpDialog, {
11877
+ client,
11878
+ sessionId,
11879
+ canManageServers: capabilities.mcpServerActions,
11880
+ open: panel === "mcp",
11881
+ onOpenChange: (next) => setPanel(next ? "mcp" : void 0)
11882
+ }),
11883
+ /* @__PURE__ */ jsx(TasksDialog, {
11884
+ tasks,
11885
+ showCompleted: showCompletedTasks,
11886
+ onShowCompletedChange: setShowCompletedTasks,
11887
+ onSelectTask: (task) => {
11888
+ if (task.toolUseId) setTaskReveal({
11889
+ toolUseId: task.toolUseId,
11890
+ nonce: Date.now()
11891
+ });
11892
+ },
11893
+ open: panel === "tasks",
11894
+ onOpenChange: (next) => setPanel(next ? "tasks" : void 0)
11895
+ }),
11896
+ /* @__PURE__ */ jsx(SkillsDialog, {
11897
+ skills: state.skills,
11898
+ open: panel === "skills",
11899
+ onOpenChange: (next) => setPanel(next ? "skills" : void 0),
11900
+ onUse: (skill) => composerRef.current?.insertText(skillPrompt(skill))
11901
+ }),
11902
+ /* @__PURE__ */ jsx(HostFilesDialog, {
11903
+ client,
11904
+ cwd: state.cwd,
11905
+ open: panel === "files",
11906
+ onOpenChange: (next) => setPanel(next ? "files" : void 0)
11907
+ })
11908
+ ] }) : null
11909
+ ]
11910
+ })
11630
11911
  })
11631
11912
  })
11632
11913
  })
@@ -11696,6 +11977,6 @@ function Notice({ level, onDismiss, children }) {
11696
11977
  });
11697
11978
  }
11698
11979
  //#endregion
11699
- export { Ink as $, PERMISSION_MODES as A, DialogContent as At, TaskList as B, SelectContent as Bt, TerminalPermissionPrompt as C, copyText as Ct, SubagentStrip as D, Dialog$1 as Dt, parseUserQuestions as E, TooltipProvider as Et, SkillsDialog as F, MenuContent as Ft, TerminalSurface as G, PortalScope as Gt, contextNote as H, SelectItemText as Ht, McpDialog as I, MenuItem as It, TerminalMarkdown as J, Badge as Jt, ToolTitleProvider as K, Textarea as Kt, HostFilesDialog as L, MenuSeparator as Lt, permissionModeChoices as M, DialogRow as Mt, permissionModeMeta as N, DialogTrigger as Nt, PermissionPrompt as O, DialogBody as Ot, ModelSelect as P, Menu$1 as Pt, Blank as Q, cn as Qt, EntryIcon as R, MenuTrigger as Rt, TerminalQuestionPrompt as S, CopyButton as St, QuestionPrompt as T, TooltipContent as Tt, Composer as U, SelectTrigger as Ut, ContextDialog as V, SelectItem as Vt, skillPrompt as W, SelectValue as Wt, previewPatch as X, Button as Xt, TerminalDiff as Y, badgeVariants as Yt, Band as Z, buttonVariants as Zt, ConversationContent as _, segmentsToPlainText as _t, Transcript as a, toolIcon as at, STATUS_META as b, CodeBlock as bt, PromptTokenText as c, useTranscriptDensity as ct, FileCard as d, hashtagTrigger as dt, Row as et, TerminalItemView as f, launchTrigger as ft, Conversation as g, plainTextToSegments as gt, Loader as h, PromptArea as ht, useMinuteClock as i, isMutatingTool as it, PermissionModeSelect as j, DialogHeader as jt, Response as k, DialogClose as kt, Message as l, useTranscriptVariant as lt, SessionEmptyState as m, usePromptAreaState as mt, UsageDialog as n, WithActions as nt, ToolCallCard as o, TranscriptDensityProvider as ot, TerminalTranscript as p, mentionTrigger as pt, useToolTitle as q, Input as qt, UsageMeters as r, useAffordances as rt, Reasoning as s, TranscriptVariantProvider as st, SessionPanel as t, CopyAction as tt, MessageContent as u, commandTrigger as ut, ConversationScrollButton as v, ProgressRing as vt, QUESTION_BEHAVIORS as w, Tip as wt, SessionInfoDialog as x, Spinner as xt, StatusBar as y, Splitter as yt, TasksDialog as z, Select$1 as zt };
11980
+ export { Band as $, Input as $t, PERMISSION_MODES as A, Tip as At, TaskList as B, Menu$1 as Bt, TerminalPermissionPrompt as C, segmentsToPlainText as Ct, SubagentStrip as D, Spinner as Dt, parseUserQuestions as E, CodeBlock as Et, SkillsDialog as F, DialogClose as Ft, ToolTitleProvider as G, Select$1 as Gt, contextNote as H, MenuItem as Ht, McpDialog as I, DialogContent as It, FileLinkProvider as J, SelectItemText as Jt, useToolTitle as K, SelectContent as Kt, HostFilesDialog as L, DialogHeader as Lt, permissionModeChoices as M, TooltipProvider as Mt, permissionModeMeta as N, Dialog$1 as Nt, PermissionPrompt as O, CopyButton as Ot, ModelSelect as P, DialogBody as Pt, previewPatch as Q, Textarea as Qt, EntryIcon as R, DialogRow as Rt, TerminalQuestionPrompt as S, plainTextToSegments as St, QuestionPrompt as T, Splitter as Tt, Composer as U, MenuSeparator as Ut, ContextDialog as V, MenuContent as Vt, TerminalSurface as W, MenuTrigger as Wt, parseFileLink as X, SelectValue as Xt, useFileLinks as Y, SelectTrigger as Yt, TerminalDiff as Z, PortalScope as Zt, ConversationContent as _, hashtagTrigger as _t, Transcript as a, useAffordances as at, STATUS_META as b, usePromptAreaState as bt, PromptTokenText as c, TranscriptDensityProvider as ct, FileCard as d, useTranscriptVariant as dt, Badge as en, Blank as et, TerminalItemView as f, matchClientCommand as ft, Conversation as g, commandTrigger as gt, Loader as h, skillPrompt as ht, useMinuteClock as i, cn as in, WithActions as it, PermissionModeSelect as j, TooltipContent as jt, Response as k, copyText as kt, Message as l, TranscriptVariantProvider as lt, SessionEmptyState as m, rankComposerRows as mt, UsageDialog as n, Button as nn, Row as nt, ToolCallCard as o, isMutatingTool as ot, TerminalTranscript as p, mergeComposerRows as pt, TerminalMarkdown as q, SelectItem as qt, UsageMeters as r, buttonVariants as rn, CopyAction as rt, Reasoning as s, toolIcon as st, SessionPanel as t, badgeVariants as tn, Ink as tt, MessageContent as u, useTranscriptDensity as ut, ConversationScrollButton as v, launchTrigger as vt, QUESTION_BEHAVIORS as w, ProgressRing as wt, SessionInfoDialog as x, PromptArea as xt, StatusBar as y, mentionTrigger as yt, TasksDialog as z, DialogTrigger as zt };
11700
11981
 
11701
- //# sourceMappingURL=SessionPanel-Dg4xf-ql.mjs.map
11982
+ //# sourceMappingURL=SessionPanel-BClL7zUS.mjs.map