@workerdeck/ui 3.0.1 → 3.2.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 (55) hide show
  1. package/build/{SessionPanel-C1bU4yTM.d.mts → SessionPanel-BNNc8grK.d.mts} +9 -7
  2. package/build/index.d.mts +153 -45
  3. package/build/index.mjs +501 -226
  4. package/build/index.mjs.map +1 -1
  5. package/build/scoped.css +15 -6
  6. package/build/{use-path-links-BEWxt0ZM.mjs → use-path-links-C6Fh5zdx.mjs} +1926 -2086
  7. package/build/use-path-links-C6Fh5zdx.mjs.map +1 -0
  8. package/build/workspace.d.mts +1 -1
  9. package/build/workspace.mjs +1 -1
  10. package/package.json +4 -4
  11. package/src/components/agent/ApprovalPrompts.tsx +87 -0
  12. package/src/components/agent/Composer.tsx +25 -47
  13. package/src/components/agent/ContextDialog.tsx +75 -67
  14. package/src/components/agent/FrameStrip.tsx +74 -0
  15. package/src/components/agent/HostFilesDialog.tsx +3 -2
  16. package/src/components/agent/McpDialog.tsx +112 -78
  17. package/src/components/agent/PermissionPrompt.tsx +13 -22
  18. package/src/components/agent/QuestionPrompt.tsx +6 -41
  19. package/src/components/agent/Scrubber.tsx +6 -75
  20. package/src/components/agent/SessionBrowser.tsx +125 -214
  21. package/src/components/agent/SessionFilters.tsx +266 -0
  22. package/src/components/agent/SessionInfoDialog.tsx +47 -63
  23. package/src/components/agent/SessionItem.tsx +27 -7
  24. package/src/components/agent/SessionPanel.tsx +244 -488
  25. package/src/components/agent/SessionSteps.tsx +54 -11
  26. package/src/components/agent/ShellStrip.tsx +27 -43
  27. package/src/components/agent/SubagentStrip.tsx +13 -30
  28. package/src/components/agent/Transcript.tsx +13 -0
  29. package/src/components/agent/TranscriptItemView.tsx +6 -36
  30. package/src/components/agent/composer-commands.ts +80 -1
  31. package/src/components/agent/markdown-image.tsx +49 -1
  32. package/src/components/agent/scrubber-marks.ts +33 -11
  33. package/src/components/agent/scrubber-peek.tsx +122 -0
  34. package/src/components/agent/session-panel-providers.tsx +41 -0
  35. package/src/components/agent/shell-actions.tsx +36 -1
  36. package/src/components/agent/use-catch-up.ts +20 -0
  37. package/src/components/agent/use-frame.ts +88 -0
  38. package/src/components/agent/use-host-image.ts +81 -0
  39. package/src/components/agent/use-shell-frame.ts +12 -70
  40. package/src/components/agent/use-subagent-frame.ts +19 -75
  41. package/src/components/prompt-area/prompt-area-engine.ts +21 -20
  42. package/src/components/prompt-area/prompt-area-list-ops.ts +12 -15
  43. package/src/components/terminal/PermissionPrompt.tsx +19 -32
  44. package/src/components/terminal/QuestionPrompt.tsx +10 -46
  45. package/src/components/terminal/TerminalTranscript.tsx +3 -86
  46. package/src/components/terminal/items.tsx +5 -43
  47. package/src/components/terminal/scrubber.tsx +25 -272
  48. package/src/components/ui/Popover.tsx +29 -0
  49. package/src/components/ui/Select.tsx +25 -0
  50. package/src/index.ts +46 -8
  51. package/src/lib/file-link.ts +3 -16
  52. package/src/lib/permission-prompt.ts +57 -0
  53. package/src/lib/question-answers.ts +65 -0
  54. package/build/use-path-links-BEWxt0ZM.mjs.map +0 -1
  55. package/src/components/prompt-area/use-markdown-mode.ts +0 -103
@@ -5,12 +5,12 @@ 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, Bot, BotOff, 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, Maximize, Maximize2, MessageCircleQuestion, MessageSquareText, Minus, MoreHorizontal, Paperclip, PencilLine, Plug, Plus, Power, PowerOff, Puzzle, RefreshCw, RotateCcw, RotateCw, Search, ShieldCheck, SlashSquare, SlidersHorizontal, Sparkles, Square, SquarePen, SquareTerminal, Star, Terminal, TriangleAlert, Users, UsersRound, WifiOff, Wrench, X, Zap } from "lucide-react";
8
+ import { ArrowDown, ArrowDownCircle, ArrowLeft, ArrowUp, AtSign, Bot, BotOff, Brain, ChartPie, Check, CheckSquare, ChevronDown, ChevronLeft, ChevronRight, ChevronsUpDown, ClipboardList, Clock, Code, Copy, Download, File, FileDiff, FileText, Folder, FolderSearch, FolderTree, Gauge, Globe, Hand, Image, Info, Link2, LoaderCircle, Maximize, Maximize2, MessageCircleQuestion, MessageSquareText, Minus, MoreHorizontal, Paperclip, PencilLine, Plug, Plus, Power, PowerOff, Puzzle, RefreshCw, RotateCcw, RotateCw, Search, ShieldCheck, SlashSquare, SlidersHorizontal, Sparkles, Square, SquarePen, SquareTerminal, Star, Terminal, TriangleAlert, Users, 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 { PRICING_NOTE, PROTOCOL_VERSION, SHELL_INTERACTIVE_NOTE, UNPRICED_WARN_SHARE, canonicalModel, costOf, costOfByModel, isPeerSendTool, mergeUsage, orderUsageWindows, parseTodoWriteInput, parseUserQuestions, peerDeliveredTo, pricingAgeNote, rateFor, sessionTasks, subagentLabel, taskCountLabel, taskSummary, toolTitle, totalTokens, unknownBasisModels, usageInfos, usageIsStale, visibleTasks } from "@workerdeck/protocol";
13
- import { recapLine, scanPromptTokens, summarizeSince, useAttachments, useClaudeSession, useDraft, useHostFileSearch, usePeerSessions, useProfileUsage, useShellTerminal, useToolCallHost } from "@workerdeck/react";
12
+ import { PRICING_NOTE, PROTOCOL_VERSION, SHELL_INTERACTIVE_NOTE, UNPRICED_WARN_SHARE, canonicalModel, costOf, costOfByModel, errorMessage, isPeerSendTool, mergeUsage, orderUsageWindows, parseTodoWriteInput, parseUserQuestions, peerDeliveredTo, pricingAgeNote, rateFor, resolvePosix, sessionTasks, subagentLabel, taskCountLabel, taskSummary, toolTitle, totalTokens, unknownBasisModels, usageInfos, usageIsStale } from "@workerdeck/protocol";
13
+ import { recapLine, scanPromptTokens, summarizeSince, useAsync, useAttachments, useClaudeSession, useDraft, useHostFileSearch, usePeerSessions, useProfileUsage, useShellTerminal, useToolCallHost } from "@workerdeck/react";
14
14
  import { Streamdown, defaultRehypePlugins } from "streamdown";
15
15
  import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
16
16
  import { createPortal } from "react-dom";
@@ -170,6 +170,20 @@ const SelectItem = ({ className, children, ...props }) => /* @__PURE__ */ jsxs(S
170
170
  children: /* @__PURE__ */ jsx(Check, { className: "size-3.5" })
171
171
  })]
172
172
  });
173
+ function OptionSelect({ label, value, options, onChange, className }) {
174
+ return /* @__PURE__ */ jsxs(Select$1, {
175
+ value,
176
+ onValueChange: (next) => onChange(next),
177
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
178
+ "aria-label": label,
179
+ className,
180
+ children: /* @__PURE__ */ jsx(SelectValue, { children: options.find((o) => o.value === value)?.label ?? label })
181
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
182
+ value: option.value,
183
+ children: /* @__PURE__ */ jsx(SelectItemText, { children: option.label })
184
+ }, option.value)) })]
185
+ });
186
+ }
173
187
  //#endregion
174
188
  //#region src/components/ui/Menu.tsx
175
189
  const Menu$1 = Menu.Root;
@@ -608,7 +622,8 @@ function resolveChip(segments, activeTrigger, chip, trailingText = " ") {
608
622
  const triggerEnd = triggerStart + 1 + activeTrigger.query.length;
609
623
  const newSegments = [];
610
624
  let offset = 0;
611
- for (const seg of segments) if (seg.type === "chip") {
625
+ let insertedChip;
626
+ for (const seg of mergeAdjacentTextSegments(segments)) if (seg.type === "chip") {
612
627
  const chipText = chipPlainText(seg);
613
628
  const chipStart = offset;
614
629
  const chipEnd = offset + chipText.length;
@@ -636,6 +651,7 @@ function resolveChip(segments, activeTrigger, chip, trailingText = " ") {
636
651
  ...chip.autoResolved ? { autoResolved: true } : {}
637
652
  };
638
653
  newSegments.push(newChip);
654
+ insertedChip = newChip;
639
655
  if (afterText) newSegments.push({
640
656
  type: "text",
641
657
  text: trailingText + afterText.replace(/^\s/, "")
@@ -648,16 +664,9 @@ function resolveChip(segments, activeTrigger, chip, trailingText = " ") {
648
664
  offset = textEnd;
649
665
  }
650
666
  const merged = mergeAdjacentTextSegments(newSegments);
651
- let lastChipEndOffset = -1;
652
- let runningOffset = 0;
653
- for (const seg of merged) if (seg.type === "text") runningOffset += seg.text.length;
654
- else {
655
- runningOffset += chipPlainText(seg).length;
656
- if (seg.value === chip.value && seg.displayText === chip.displayText && seg.trigger === activeTrigger.config.char) lastChipEndOffset = runningOffset;
657
- }
658
667
  return {
659
668
  segments: merged,
660
- cursorOffset: lastChipEndOffset === -1 ? runningOffset : lastChipEndOffset + trailingText.length
669
+ cursorOffset: insertedChip ? triggerStart + chipPlainText(insertedChip).length + trailingText.length : segmentsToPlainText(merged).length
661
670
  };
662
671
  }
663
672
  /**
@@ -676,7 +685,7 @@ function resolveText(segments, activeTrigger, text) {
676
685
  const newSegments = [];
677
686
  let offset = 0;
678
687
  let cursorOffset = triggerStart + text.length;
679
- for (const seg of segments) {
688
+ for (const seg of mergeAdjacentTextSegments(segments)) {
680
689
  if (seg.type === "chip") {
681
690
  const chipEnd = offset + chipPlainText(seg).length;
682
691
  if (chipEnd <= triggerStart || offset >= triggerEnd) newSegments.push(seg);
@@ -843,7 +852,23 @@ function replaceTextRange(segments, start, end, replacement) {
843
852
  });
844
853
  inserted = true;
845
854
  }
846
- if (chipEnd <= start || chipStart >= end) newSegments.push(seg);
855
+ if (start === end) {
856
+ newSegments.push(seg);
857
+ if (!inserted && chipStart < start && start < chipEnd) {
858
+ newSegments.push({
859
+ type: "text",
860
+ text: replacement
861
+ });
862
+ inserted = true;
863
+ }
864
+ } else if (chipEnd <= start || chipStart >= end) newSegments.push(seg);
865
+ else if (!inserted) {
866
+ newSegments.push({
867
+ type: "text",
868
+ text: replacement
869
+ });
870
+ inserted = true;
871
+ }
847
872
  offset = chipEnd;
848
873
  } else {
849
874
  const textStart = offset;
@@ -927,7 +952,7 @@ function segmentsEqual(a, b) {
927
952
  if (sa.type !== sb.type) return false;
928
953
  if (sa.type === "text") {
929
954
  if (sb.type !== "text" || sa.text !== sb.text) return false;
930
- } else if (sb.type !== "chip" || sa.trigger !== sb.trigger || sa.value !== sb.value || sa.displayText !== sb.displayText || sa.autoResolved !== sb.autoResolved) return false;
955
+ } else if (sb.type !== "chip" || sa.trigger !== sb.trigger || sa.value !== sb.value || sa.displayText !== sb.displayText || sa.sigil !== sb.sigil || sa.autoResolved !== sb.autoResolved) return false;
931
956
  }
932
957
  return true;
933
958
  }
@@ -1144,20 +1169,19 @@ function normalizeListPrefixText(text, markdownEnabled) {
1144
1169
  * preserved, while an unterminated fence does not suppress later bullets.
1145
1170
  */
1146
1171
  function normalizeListPrefixes(segments, markdownEnabled) {
1147
- const globalLines = [];
1148
- segments.forEach((seg) => {
1149
- if (seg.type === "text") globalLines.push(...seg.text.split("\n"));
1150
- });
1151
- const protectedLines = fenceProtectedLineIndices(globalLines);
1152
- let globalIndex = 0;
1172
+ const protectedLines = fenceProtectedLineIndices(segmentsToPlainText(segments).split("\n"));
1173
+ let line = 0;
1174
+ let atLineStart = true;
1153
1175
  let changed = false;
1154
1176
  const result = segments.map((seg) => {
1155
- if (seg.type !== "text") return seg;
1156
- const newText = seg.text.split("\n").map((line) => {
1157
- const out = protectedLines.has(globalIndex) ? line : swapListPrefixLine(line, markdownEnabled);
1158
- globalIndex++;
1159
- return out;
1160
- }).join("\n");
1177
+ if (seg.type !== "text") {
1178
+ atLineStart = false;
1179
+ return seg;
1180
+ }
1181
+ const pieces = seg.text.split("\n");
1182
+ const newText = pieces.map((piece, k) => (k > 0 || atLineStart) && !protectedLines.has(line + k) ? swapListPrefixLine(piece, markdownEnabled) : piece).join("\n");
1183
+ line += pieces.length - 1;
1184
+ if (pieces.length > 1 || seg.text !== "") atLineStart = pieces[pieces.length - 1] === "";
1161
1185
  if (newText === seg.text) return seg;
1162
1186
  changed = true;
1163
1187
  return {
@@ -4564,6 +4588,68 @@ function composerCommandRows(rows) {
4564
4588
  insertText: `/${row.name} `
4565
4589
  });
4566
4590
  }
4591
+ function buildClientCommands({ capabilities, hasModels, setModel, setPermissionMode, clearContext, openPanel }) {
4592
+ const modes = capabilities.permissionModes;
4593
+ const built = [];
4594
+ if (hasModels) built.push({
4595
+ name: "model",
4596
+ description: "Switch the model for this session",
4597
+ argumentHint: "<model>",
4598
+ requiresArgs: true,
4599
+ run: (args) => {
4600
+ const wanted = args.split(/\s+/)[0];
4601
+ if (!wanted) return false;
4602
+ setModel(wanted);
4603
+ return true;
4604
+ }
4605
+ });
4606
+ if (modes.length > 1) built.push({
4607
+ name: "permissions",
4608
+ description: `Set the permission mode (${modes.join(", ")})`,
4609
+ argumentHint: "<mode>",
4610
+ requiresArgs: true,
4611
+ run: (args) => {
4612
+ const wanted = args.split(/\s+/)[0];
4613
+ if (!modes.includes(wanted)) return false;
4614
+ setPermissionMode(wanted);
4615
+ return true;
4616
+ }
4617
+ });
4618
+ if (capabilities.clearContext) built.push({
4619
+ name: "clear",
4620
+ description: "Clear the conversation - the session keeps running and the old one stays resumable",
4621
+ run: () => {
4622
+ clearContext();
4623
+ return true;
4624
+ }
4625
+ });
4626
+ if (capabilities.mcpStatus) built.push({
4627
+ name: "mcp",
4628
+ description: "MCP servers and their status",
4629
+ run: () => (openPanel("mcp"), true)
4630
+ });
4631
+ if (capabilities.contextUsage) built.push({
4632
+ name: "context",
4633
+ description: "Context window usage",
4634
+ run: () => (openPanel("context"), true)
4635
+ });
4636
+ built.push({
4637
+ name: "status",
4638
+ description: "Session details",
4639
+ run: () => (openPanel("info"), true)
4640
+ });
4641
+ if (capabilities.rateLimits) built.push({
4642
+ name: "usage",
4643
+ description: "Rate limits and spend",
4644
+ run: () => (openPanel("usage"), true)
4645
+ });
4646
+ if (capabilities.skillsList) built.push({
4647
+ name: "skills",
4648
+ description: "Browse the skills this session can use",
4649
+ run: () => (openPanel("skills"), true)
4650
+ });
4651
+ return built;
4652
+ }
4567
4653
  //#endregion
4568
4654
  //#region src/components/agent/transcript-variant.tsx
4569
4655
  const VariantContext = createContext("cards");
@@ -5061,7 +5147,7 @@ function parseFileLink(href, cwd) {
5061
5147
  line ??= readLine(suffix[2]);
5062
5148
  }
5063
5149
  if (!target) return;
5064
- const path = target.startsWith("/") ? normalize(target) : cwd ? normalize(`${cwd}/${target}`) : void 0;
5150
+ const path = resolvePosix(target, cwd);
5065
5151
  return path ? {
5066
5152
  path,
5067
5153
  ...line === void 0 ? {} : { line }
@@ -5071,18 +5157,6 @@ function readLine(value) {
5071
5157
  const parsed = value === void 0 ? NaN : Number(value);
5072
5158
  return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
5073
5159
  }
5074
- function normalize(path) {
5075
- const out = [];
5076
- for (const part of path.split("/")) {
5077
- if (part === "" || part === ".") continue;
5078
- if (part === "..") {
5079
- out.pop();
5080
- continue;
5081
- }
5082
- out.push(part);
5083
- }
5084
- return `/${out.join("/")}`;
5085
- }
5086
5160
  //#endregion
5087
5161
  //#region src/components/terminal/file-link.tsx
5088
5162
  const FileLinkContext = createContext(void 0);
@@ -5559,6 +5633,8 @@ function evict(cache, keep) {
5559
5633
  //#region src/components/agent/markdown-image.tsx
5560
5634
  const LOCAL_PREFIX = "https://local-image.invalid/";
5561
5635
  const WEB_SOURCE = /^(?:https?:|data:image\/|blob:)/i;
5636
+ const REMOTE_SOURCE = /^https?:/i;
5637
+ const REMOTE_IMAGE_NOTICE = "remote image, not loaded";
5562
5638
  function shieldLocalImages() {
5563
5639
  return (tree) => {
5564
5640
  walk(tree);
@@ -5580,9 +5656,50 @@ function localImagePath(src) {
5580
5656
  return;
5581
5657
  }
5582
5658
  }
5659
+ function remoteImageHost(src) {
5660
+ if (!src || !REMOTE_SOURCE.test(src.trim()) || localImagePath(src) !== void 0) return;
5661
+ try {
5662
+ return new URL(src.trim()).host;
5663
+ } catch {
5664
+ return;
5665
+ }
5666
+ }
5583
5667
  function MarkdownImage({ src, alt, terminal }) {
5584
- const links = useFileLinks();
5585
5668
  const raw = typeof src === "string" ? src : void 0;
5669
+ const remoteHost = remoteImageHost(raw);
5670
+ if (remoteHost !== void 0) return /* @__PURE__ */ jsx(RemoteImageLink, {
5671
+ href: raw.trim(),
5672
+ host: remoteHost,
5673
+ alt,
5674
+ terminal
5675
+ });
5676
+ return /* @__PURE__ */ jsx(LoadedMarkdownImage, {
5677
+ raw,
5678
+ alt,
5679
+ terminal
5680
+ });
5681
+ }
5682
+ function RemoteImageLink({ href, host, alt, terminal }) {
5683
+ const text = `${typeof alt === "string" && alt ? `${alt} · ` : ""}${host} (${REMOTE_IMAGE_NOTICE})`;
5684
+ if (terminal) return /* @__PURE__ */ jsx("a", {
5685
+ href,
5686
+ target: "_blank",
5687
+ rel: "noopener noreferrer",
5688
+ "data-tone": "faint",
5689
+ title: href,
5690
+ children: text
5691
+ });
5692
+ return /* @__PURE__ */ jsx("a", {
5693
+ href,
5694
+ target: "_blank",
5695
+ rel: "noopener noreferrer",
5696
+ title: href,
5697
+ className: "my-1 inline-block rounded-md border border-border bg-surface-hover px-2 py-1 text-label text-fg-4",
5698
+ children: text
5699
+ });
5700
+ }
5701
+ function LoadedMarkdownImage({ raw, alt, terminal }) {
5702
+ const links = useFileLinks();
5586
5703
  const local = localImagePath(raw);
5587
5704
  const path = local === void 0 ? void 0 : parseFileLink(local, links?.cwd)?.path;
5588
5705
  const host = useHostImageSrc(path);
@@ -6063,23 +6180,6 @@ function todoLine(todo) {
6063
6180
  return `${GLYPH[todo.status]} ${todo.text}`;
6064
6181
  }
6065
6182
  //#endregion
6066
- //#region src/components/agent/shell-actions.tsx
6067
- const NOOP = {
6068
- loadOutput: async () => false,
6069
- verify: async () => false,
6070
- kill: async () => false
6071
- };
6072
- const ShellActionsContext = createContext(NOOP);
6073
- function ShellActionsProvider({ value, children }) {
6074
- return /* @__PURE__ */ jsx(ShellActionsContext.Provider, {
6075
- value: value ?? NOOP,
6076
- children
6077
- });
6078
- }
6079
- function useShellActions() {
6080
- return useContext(ShellActionsContext);
6081
- }
6082
- //#endregion
6083
6183
  //#region src/components/terminal/shell-row.ts
6084
6184
  const SHELL_GLYPH = "$";
6085
6185
  const SHELL_KILL_GLYPH = "✕";
@@ -6146,6 +6246,53 @@ function shellFooterText(item, open, shown) {
6146
6246
  if (total !== void 0 && total > shown) return `… +${(total - shown).toLocaleString()} lines not shown`;
6147
6247
  if (item.expanded === void 0 && item.truncated) return "… fetching the full output";
6148
6248
  }
6249
+ //#endregion
6250
+ //#region src/components/agent/shell-actions.tsx
6251
+ const NOOP = {
6252
+ loadOutput: async () => false,
6253
+ verify: async () => false,
6254
+ kill: async () => false
6255
+ };
6256
+ const ShellActionsContext = createContext(NOOP);
6257
+ function ShellActionsProvider({ value, children }) {
6258
+ return /* @__PURE__ */ jsx(ShellActionsContext.Provider, {
6259
+ value: value ?? NOOP,
6260
+ children
6261
+ });
6262
+ }
6263
+ function useShellActions() {
6264
+ return useContext(ShellActionsContext);
6265
+ }
6266
+ function useVerifyRunning(item) {
6267
+ const verify = useShellActions().verify;
6268
+ const running = item.shell.status === "running";
6269
+ const shellId = item.shell.id;
6270
+ useEffect(() => {
6271
+ if (running) verify(shellId);
6272
+ }, [
6273
+ running,
6274
+ shellId,
6275
+ verify
6276
+ ]);
6277
+ }
6278
+ function ShellItemActions({ item }) {
6279
+ const actions = useShellActions();
6280
+ const shellId = item.shell.id;
6281
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6282
+ actions.open ? /* @__PURE__ */ jsx(OpenShellAction, { onOpen: () => actions.open?.(shellId) }) : null,
6283
+ actions.agentWrite && shellGrantable(item.shell) ? /* @__PURE__ */ jsx(AgentWriteAction, {
6284
+ granted: item.shell.agentWrite === true,
6285
+ label: shellAgentWriteLabel(item.shell),
6286
+ onToggle: () => void actions.agentWrite?.(shellId, item.shell.agentWrite !== true)
6287
+ }) : null,
6288
+ item.shell.status === "running" ? /* @__PURE__ */ jsx(KillShellAction, { onKill: () => void actions.kill(shellId) }) : null,
6289
+ /* @__PURE__ */ jsx(BookmarkAction, { id: item.id }),
6290
+ /* @__PURE__ */ jsx(CopyAction, {
6291
+ text: item.shell.command,
6292
+ label: "Copy command"
6293
+ })
6294
+ ] });
6295
+ }
6149
6296
  const RESULT_PREVIEW_CHARS$1 = 2e3;
6150
6297
  function clipToChars(lines, maxChars) {
6151
6298
  const out = [];
@@ -6555,14 +6702,7 @@ function ShellRow({ item }) {
6555
6702
  const lines = shellBodyLines(item, open);
6556
6703
  const footer = shellFooterText(item, open, lines.length);
6557
6704
  const shellId = item.shell.id;
6558
- const verify = actions.verify;
6559
- useEffect(() => {
6560
- if (running) verify(shellId);
6561
- }, [
6562
- running,
6563
- shellId,
6564
- verify
6565
- ]);
6705
+ useVerifyRunning(item);
6566
6706
  const press = (modifiers) => {
6567
6707
  if ((modifiers.meta || modifiers.ctrl) && actions.open) {
6568
6708
  actions.open(shellId);
@@ -6578,20 +6718,7 @@ function ShellRow({ item }) {
6578
6718
  ref: reveal,
6579
6719
  className: open ? "term-open" : void 0,
6580
6720
  children: /* @__PURE__ */ jsxs(WithActions, {
6581
- actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [
6582
- actions.open ? /* @__PURE__ */ jsx(OpenShellAction, { onOpen: () => actions.open?.(shellId) }) : null,
6583
- actions.agentWrite && shellGrantable(item.shell) ? /* @__PURE__ */ jsx(AgentWriteAction, {
6584
- granted: item.shell.agentWrite === true,
6585
- label: shellAgentWriteLabel(item.shell),
6586
- onToggle: () => void actions.agentWrite?.(shellId, item.shell.agentWrite !== true)
6587
- }) : null,
6588
- running ? /* @__PURE__ */ jsx(KillShellAction, { onKill: () => void actions.kill(shellId) }) : null,
6589
- /* @__PURE__ */ jsx(BookmarkAction, { id: item.id }),
6590
- /* @__PURE__ */ jsx(CopyAction, {
6591
- text: item.shell.command,
6592
- label: "Copy command"
6593
- })
6594
- ] }),
6721
+ actions: /* @__PURE__ */ jsx(ShellItemActions, { item }),
6595
6722
  children: [
6596
6723
  /* @__PURE__ */ jsx(Pressable, {
6597
6724
  onPress: press,
@@ -6991,6 +7118,22 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
6991
7118
  })
6992
7119
  ]
6993
7120
  }) : null;
7121
+ const stagedStrip = staged.length > 0 && attachments ? /* @__PURE__ */ jsx(AttachmentStrip, { attachments }) : null;
7122
+ const field = (minHeight, maxHeight, fieldClass) => /* @__PURE__ */ jsx(PromptArea, {
7123
+ ...bind,
7124
+ triggers,
7125
+ markdown: false,
7126
+ normalizeBullets: false,
7127
+ onSubmit: submit,
7128
+ disabled,
7129
+ placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
7130
+ minHeight,
7131
+ maxHeight,
7132
+ "aria-label": shellMode ? "Run a shell command" : "Message the agent",
7133
+ className: fieldClass,
7134
+ onImagePaste: (file) => attachments?.add([file]),
7135
+ ...shellKeys
7136
+ });
6994
7137
  if (terminal) {
6995
7138
  const line = lineHeight ?? 18;
6996
7139
  return /* @__PURE__ */ jsxs("div", {
@@ -7007,7 +7150,7 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
7007
7150
  children: /* @__PURE__ */ jsxs("div", {
7008
7151
  className: "term-composer-body",
7009
7152
  children: [
7010
- staged.length > 0 && attachments ? /* @__PURE__ */ jsx(AttachmentStrip, { attachments }) : null,
7153
+ stagedStrip,
7011
7154
  /* @__PURE__ */ jsxs("div", {
7012
7155
  className: "term-row",
7013
7156
  children: [
@@ -7015,21 +7158,7 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
7015
7158
  gutter,
7016
7159
  /* @__PURE__ */ jsxs("div", {
7017
7160
  className: "flex min-w-0 items-start",
7018
- children: [/* @__PURE__ */ jsx(PromptArea, {
7019
- ...bind,
7020
- triggers,
7021
- markdown: false,
7022
- normalizeBullets: false,
7023
- onSubmit: submit,
7024
- disabled,
7025
- placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
7026
- minHeight: line,
7027
- maxHeight: line * 10,
7028
- "aria-label": shellMode ? "Run a shell command" : "Message the agent",
7029
- className: "term-composer-field min-w-0 flex-1",
7030
- onImagePaste: (file) => attachments?.add([file]),
7031
- ...shellKeys
7032
- }), submitButton]
7161
+ children: [field(line, line * 10, "term-composer-field min-w-0 flex-1"), submitButton]
7033
7162
  })
7034
7163
  ]
7035
7164
  }),
@@ -7077,42 +7206,14 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
7077
7206
  /* @__PURE__ */ jsxs("div", {
7078
7207
  ...dropHandlers,
7079
7208
  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"),
7080
- children: [staged.length > 0 && attachments ? /* @__PURE__ */ jsx(AttachmentStrip, { attachments }) : null, inline ? /* @__PURE__ */ jsxs("div", {
7209
+ children: [stagedStrip, inline ? /* @__PURE__ */ jsxs("div", {
7081
7210
  className: "flex items-end gap-1 p-1",
7082
7211
  children: [
7083
7212
  shellMode ? /* @__PURE__ */ jsx(ShellBadge, { onLeave: () => leaveShellMode(false) }) : attach,
7084
- /* @__PURE__ */ jsx(PromptArea, {
7085
- ...bind,
7086
- triggers,
7087
- markdown: false,
7088
- normalizeBullets: false,
7089
- onSubmit: submit,
7090
- disabled,
7091
- placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
7092
- minHeight: 20,
7093
- maxHeight: 192,
7094
- "aria-label": shellMode ? "Run a shell command" : "Message the agent",
7095
- className: "min-w-0 flex-1 py-1 text-body-sm text-text",
7096
- onImagePaste: (file) => attachments?.add([file]),
7097
- ...shellKeys
7098
- }),
7213
+ field(20, 192, "min-w-0 flex-1 py-1 text-body-sm text-text"),
7099
7214
  submitButton
7100
7215
  ]
7101
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PromptArea, {
7102
- ...bind,
7103
- triggers,
7104
- markdown: false,
7105
- normalizeBullets: false,
7106
- onSubmit: submit,
7107
- disabled,
7108
- placeholder: disabled ? "Session ended" : shellMode ? SHELL_PLACEHOLDER : placeholder,
7109
- minHeight: 28,
7110
- maxHeight: 192,
7111
- "aria-label": shellMode ? "Run a shell command" : "Message the agent",
7112
- className: "px-3 pt-2.5 pb-0 text-body-sm text-text",
7113
- onImagePaste: (file) => attachments?.add([file]),
7114
- ...shellKeys
7115
- }), /* @__PURE__ */ jsxs("div", {
7216
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [field(28, 192, "px-3 pt-2.5 pb-0 text-body-sm text-text"), /* @__PURE__ */ jsxs("div", {
7116
7217
  className: "flex items-center justify-between gap-2 px-2 pb-2",
7117
7218
  children: [/* @__PURE__ */ jsxs("div", {
7118
7219
  className: "flex min-w-0 items-center gap-1",
@@ -7228,7 +7329,6 @@ function contextNote(engine) {
7228
7329
  //#endregion
7229
7330
  //#region src/components/agent/ContextDialog.tsx
7230
7331
  function ContextDialog({ usage, engine, open, onOpenChange, className }) {
7231
- const note = contextNote(engine);
7232
7332
  return /* @__PURE__ */ jsx(Dialog$1, {
7233
7333
  open,
7234
7334
  onOpenChange,
@@ -7237,191 +7337,102 @@ function ContextDialog({ usage, engine, open, onOpenChange, className }) {
7237
7337
  children: [/* @__PURE__ */ jsx(DialogHeader, {
7238
7338
  title: "Context",
7239
7339
  description: usage?.model
7240
- }), /* @__PURE__ */ jsxs(DialogBody, { children: [!usage ? /* @__PURE__ */ jsx("p", {
7241
- className: "py-6 text-center text-body-sm text-fg-4",
7242
- children: "No reading yet - the context window is measured after a turn completes."
7243
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
7244
- /* @__PURE__ */ jsxs("div", {
7245
- className: "flex items-baseline justify-between gap-4",
7246
- children: [/* @__PURE__ */ jsx("span", {
7247
- className: "text-label text-fg-3",
7248
- children: "Used"
7249
- }), /* @__PURE__ */ jsxs("span", {
7250
- className: "font-mono text-body-sm text-fg-1",
7251
- children: [
7252
- formatTokens(usage.totalTokens),
7253
- " / ",
7254
- formatTokens(usage.maxTokens),
7255
- " · ",
7256
- usage.percentage.toFixed(0),
7257
- "%"
7258
- ]
7259
- })]
7260
- }),
7261
- /* @__PURE__ */ jsx("div", {
7262
- className: "mt-2 h-2 overflow-hidden rounded-full bg-border",
7263
- children: /* @__PURE__ */ jsx("div", {
7264
- className: cn("h-full rounded-full", meterTintClass(usage.percentage)),
7265
- style: { width: `${Math.min(100, Math.max(2, usage.percentage))}%` }
7266
- })
7267
- }),
7268
- usage.categories.length > 0 ? /* @__PURE__ */ jsxs("div", {
7269
- className: "mt-5",
7270
- children: [/* @__PURE__ */ jsx("h3", {
7271
- className: "text-label font-medium text-fg-3",
7272
- children: "Breakdown"
7273
- }), /* @__PURE__ */ jsx("div", {
7274
- className: "mt-2 flex flex-col gap-3",
7275
- children: usage.categories.map((category) => {
7276
- const share = category.tokens / Math.max(usage.maxTokens, 1) * 100;
7277
- const color = cssColor(category.color);
7278
- return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
7279
- className: "flex items-baseline justify-between gap-3",
7280
- children: [/* @__PURE__ */ jsxs("span", {
7281
- className: "flex min-w-0 items-center gap-2",
7282
- children: [/* @__PURE__ */ jsx("span", {
7283
- className: "size-2 shrink-0 rounded-full bg-fg-4",
7284
- style: color ? { backgroundColor: color } : void 0
7285
- }), /* @__PURE__ */ jsx("span", {
7286
- className: "truncate text-body-sm text-fg-1",
7287
- children: category.name
7288
- })]
7289
- }), /* @__PURE__ */ jsx("span", {
7290
- className: "shrink-0 font-mono text-label text-fg-3",
7291
- children: formatTokens(category.tokens)
7292
- })]
7293
- }), /* @__PURE__ */ jsx("div", {
7294
- className: "mt-1 h-1 overflow-hidden rounded-full bg-border",
7295
- children: /* @__PURE__ */ jsx("div", {
7296
- className: "h-full rounded-full bg-fg-4",
7297
- style: {
7298
- width: `${Math.min(100, share)}%`,
7299
- backgroundColor: color
7300
- }
7301
- })
7302
- })] }, category.name);
7303
- })
7304
- })]
7305
- }) : null
7306
- ] }), note ? /* @__PURE__ */ jsxs("div", {
7307
- className: "mt-5 border-t border-border pt-4",
7308
- children: [
7309
- /* @__PURE__ */ jsx("h3", {
7310
- className: "text-label font-medium text-fg-3",
7311
- children: "Why this window is 272K"
7312
- }),
7313
- /* @__PURE__ */ jsx("p", {
7314
- className: "mt-2 text-body-sm text-fg-2",
7315
- children: note.summary
7316
- }),
7317
- note.detail.map((line) => /* @__PURE__ */ jsx("p", {
7318
- className: "mt-2 text-body-sm text-fg-3",
7319
- children: line
7320
- }, line)),
7321
- /* @__PURE__ */ jsx("p", {
7322
- className: "mt-2 text-body-sm text-warning",
7323
- children: note.caveat
7324
- })
7325
- ]
7326
- }) : null] })]
7340
+ }), /* @__PURE__ */ jsx(DialogBody, { children: /* @__PURE__ */ jsx(ContextPanel, {
7341
+ usage,
7342
+ engine
7343
+ }) })]
7327
7344
  })
7328
7345
  });
7329
7346
  }
7330
- //#endregion
7331
- //#region src/components/agent/TaskList.tsx
7332
- function TaskList({ tasks, showCompleted, onShowCompletedChange, onSelectTask, className }) {
7333
- const summary = taskSummary(tasks);
7334
- const shown = visibleTasks(tasks, showCompleted);
7335
- const hidden = tasks.length - shown.length;
7336
- return /* @__PURE__ */ jsxs("div", {
7337
- className: cn("flex flex-col", className),
7338
- children: [/* @__PURE__ */ jsxs("div", {
7339
- className: "flex items-baseline justify-between gap-4 pb-1.5",
7340
- children: [/* @__PURE__ */ jsxs("span", {
7347
+ function ContextPanel({ usage, engine }) {
7348
+ const note = contextNote(engine);
7349
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [!usage ? /* @__PURE__ */ jsx("p", {
7350
+ className: "py-6 text-center text-body-sm text-fg-4",
7351
+ children: "No reading yet - the context window is measured after a turn completes."
7352
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
7353
+ /* @__PURE__ */ jsxs("div", {
7354
+ className: "flex items-baseline justify-between gap-4",
7355
+ children: [/* @__PURE__ */ jsx("span", {
7341
7356
  className: "text-label text-fg-3",
7357
+ children: "Used"
7358
+ }), /* @__PURE__ */ jsxs("span", {
7359
+ className: "font-mono text-body-sm text-fg-1",
7342
7360
  children: [
7343
- summary.done,
7344
- " of ",
7345
- summary.total,
7346
- " done",
7347
- summary.failed > 0 ? ` · ${summary.failed} failed` : ""
7361
+ formatTokens(usage.totalTokens),
7362
+ " / ",
7363
+ formatTokens(usage.maxTokens),
7364
+ " · ",
7365
+ usage.percentage.toFixed(0),
7366
+ "%"
7348
7367
  ]
7349
- }), summary.done > 0 && onShowCompletedChange ? /* @__PURE__ */ jsx("button", {
7350
- type: "button",
7351
- onClick: () => onShowCompletedChange(!showCompleted),
7352
- className: "shrink-0 rounded-[4px] px-1 py-0.5 text-label text-fg-4 outline-none hover:bg-row-hover hover:text-fg-2",
7353
- children: showCompleted ? "Hide completed" : `Show completed (${summary.done})`
7354
- }) : null]
7355
- }), shown.length === 0 ? /* @__PURE__ */ jsx("p", {
7356
- className: "py-6 text-center text-body-sm text-fg-4",
7357
- children: tasks.length === 0 ? "No tasks yet - a checklist appears once the agent plans one." : `${hidden} completed, all hidden.`
7358
- }) : /* @__PURE__ */ jsx("div", {
7359
- className: "flex flex-col",
7360
- children: shown.map((task) => /* @__PURE__ */ jsx(TaskRow$1, {
7361
- task,
7362
- onSelect: onSelectTask
7363
- }, task.key))
7364
- })]
7365
- });
7366
- }
7367
- function TaskRow$1({ task, onSelect }) {
7368
- const pressable = onSelect !== void 0 && task.toolUseId !== void 0;
7369
- const body = task.state === "failed" ? "text-danger" : task.state === "done" ? "text-fg-4" : "text-fg-2";
7370
- const row = /* @__PURE__ */ jsxs(Fragment$1, { children: [
7371
- /* @__PURE__ */ jsx(TaskIcon, { state: task.state }),
7372
- /* @__PURE__ */ jsx("span", {
7373
- className: cn("min-w-0 flex-1 truncate text-left", task.state === "done" ? "line-through" : void 0),
7374
- children: task.label
7368
+ })]
7375
7369
  }),
7376
- task.detail ? /* @__PURE__ */ jsx("span", {
7377
- className: "shrink-0 tabular-nums text-fg-4",
7378
- children: task.detail
7379
- }) : null,
7380
- pressable ? /* @__PURE__ */ jsx(ArrowRight, { className: "size-3.5 shrink-0 text-fg-4" }) : null
7381
- ] });
7382
- const className = cn("flex w-full items-center gap-1.5 rounded-[4px] px-1.5 py-1 text-body-sm outline-none", body);
7383
- if (!pressable) return /* @__PURE__ */ jsx("div", {
7384
- className,
7385
- children: row
7386
- });
7387
- return /* @__PURE__ */ jsx("button", {
7388
- type: "button",
7389
- title: task.label,
7390
- onClick: () => onSelect(task),
7391
- className: cn(className, "hover:bg-row-active"),
7392
- children: row
7393
- });
7394
- }
7395
- function TaskIcon({ state }) {
7396
- switch (state) {
7397
- case "running": return /* @__PURE__ */ jsx(Spinner, { className: "size-[13px] shrink-0" });
7398
- case "failed": return /* @__PURE__ */ jsx(CircleAlert, { className: "size-[13px] shrink-0" });
7399
- case "done": return /* @__PURE__ */ jsx(Check, { className: "size-[13px] shrink-0" });
7400
- default: return /* @__PURE__ */ jsx(Circle, { className: "size-[13px] shrink-0 text-fg-4" });
7401
- }
7402
- }
7403
- //#endregion
7404
- //#region src/components/agent/TasksDialog.tsx
7405
- function TasksDialog({ tasks, showCompleted, onShowCompletedChange, onSelectTask, open, onOpenChange, className }) {
7406
- return /* @__PURE__ */ jsx(Dialog$1, {
7407
- open,
7408
- onOpenChange,
7409
- children: /* @__PURE__ */ jsxs(DialogContent, {
7410
- className,
7411
- children: [/* @__PURE__ */ jsx(DialogHeader, {
7412
- title: "Tasks",
7413
- description: "The agent's checklist and the tasks it has spawned"
7414
- }), /* @__PURE__ */ jsx(DialogBody, { children: /* @__PURE__ */ jsx(TaskList, {
7415
- tasks,
7416
- showCompleted,
7417
- onShowCompletedChange,
7418
- onSelectTask: onSelectTask ? (task) => {
7419
- onOpenChange(false);
7420
- onSelectTask(task);
7421
- } : void 0
7422
- }) })]
7423
- })
7424
- });
7370
+ /* @__PURE__ */ jsx("div", {
7371
+ className: "mt-2 h-2 overflow-hidden rounded-full bg-border",
7372
+ children: /* @__PURE__ */ jsx("div", {
7373
+ className: cn("h-full rounded-full", meterTintClass(usage.percentage)),
7374
+ style: { width: `${Math.min(100, Math.max(2, usage.percentage))}%` }
7375
+ })
7376
+ }),
7377
+ usage.categories.length > 0 ? /* @__PURE__ */ jsxs("div", {
7378
+ className: "mt-5",
7379
+ children: [/* @__PURE__ */ jsx("h3", {
7380
+ className: "text-label font-medium text-fg-3",
7381
+ children: "Breakdown"
7382
+ }), /* @__PURE__ */ jsx("div", {
7383
+ className: "mt-2 flex flex-col gap-3",
7384
+ children: usage.categories.map((category) => {
7385
+ const share = category.tokens / Math.max(usage.maxTokens, 1) * 100;
7386
+ const color = cssColor(category.color);
7387
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
7388
+ className: "flex items-baseline justify-between gap-3",
7389
+ children: [/* @__PURE__ */ jsxs("span", {
7390
+ className: "flex min-w-0 items-center gap-2",
7391
+ children: [/* @__PURE__ */ jsx("span", {
7392
+ className: "size-2 shrink-0 rounded-full bg-fg-4",
7393
+ style: color ? { backgroundColor: color } : void 0
7394
+ }), /* @__PURE__ */ jsx("span", {
7395
+ className: "truncate text-body-sm text-fg-1",
7396
+ children: category.name
7397
+ })]
7398
+ }), /* @__PURE__ */ jsx("span", {
7399
+ className: "shrink-0 font-mono text-label text-fg-3",
7400
+ children: formatTokens(category.tokens)
7401
+ })]
7402
+ }), /* @__PURE__ */ jsx("div", {
7403
+ className: "mt-1 h-1 overflow-hidden rounded-full bg-border",
7404
+ children: /* @__PURE__ */ jsx("div", {
7405
+ className: "h-full rounded-full bg-fg-4",
7406
+ style: {
7407
+ width: `${Math.min(100, share)}%`,
7408
+ backgroundColor: color
7409
+ }
7410
+ })
7411
+ })] }, category.name);
7412
+ })
7413
+ })]
7414
+ }) : null
7415
+ ] }), note ? /* @__PURE__ */ jsxs("div", {
7416
+ className: "mt-5 border-t border-border pt-4",
7417
+ children: [
7418
+ /* @__PURE__ */ jsx("h3", {
7419
+ className: "text-label font-medium text-fg-3",
7420
+ children: "Why this window is 272K"
7421
+ }),
7422
+ /* @__PURE__ */ jsx("p", {
7423
+ className: "mt-2 text-body-sm text-fg-2",
7424
+ children: note.summary
7425
+ }),
7426
+ note.detail.map((line) => /* @__PURE__ */ jsx("p", {
7427
+ className: "mt-2 text-body-sm text-fg-3",
7428
+ children: line
7429
+ }, line)),
7430
+ /* @__PURE__ */ jsx("p", {
7431
+ className: "mt-2 text-body-sm text-warning",
7432
+ children: note.caveat
7433
+ })
7434
+ ]
7435
+ }) : null] });
7425
7436
  }
7426
7437
  //#endregion
7427
7438
  //#region src/components/agent/EntryIcon.tsx
@@ -7450,7 +7461,7 @@ function HostFilesDialog({ client, cwd, open, onOpenChange }) {
7450
7461
  setEntries(response.entries);
7451
7462
  setTruncated(response.truncated ?? false);
7452
7463
  } catch (e) {
7453
- setError(e instanceof Error ? e.message : "Could not read that directory");
7464
+ setError(errorMessage(e, "Could not read that directory"));
7454
7465
  } finally {
7455
7466
  setLoading(false);
7456
7467
  }
@@ -7495,7 +7506,7 @@ function HostFilesDialog({ client, cwd, open, onOpenChange }) {
7495
7506
  content: response.encoding === "utf8" ? response.content : "(binary file - not shown)"
7496
7507
  });
7497
7508
  } catch (e) {
7498
- setError(e instanceof Error ? e.message : "Could not read that file");
7509
+ setError(errorMessage(e, "Could not read that file"));
7499
7510
  } finally {
7500
7511
  setLoading(false);
7501
7512
  }
@@ -7595,85 +7606,108 @@ const STATUS_VARIANT = {
7595
7606
  disabled: "neutral"
7596
7607
  };
7597
7608
  function McpDialog({ client, sessionId, open, onOpenChange, canManageServers = true, className }) {
7598
- const [servers, setServers] = useState();
7599
- const [error, setError] = useState();
7600
- const [loading, setLoading] = useState(false);
7601
- const [busyServer, setBusyServer] = useState();
7602
- const [selectedServer, setSelectedServer] = useState();
7603
- const [selectedTool, setSelectedTool] = useState();
7604
- const load = useCallback(async () => {
7605
- if (!sessionId) return;
7606
- setLoading(true);
7607
- setError(void 0);
7608
- try {
7609
- setServers(await client.listMcpServers(sessionId));
7610
- } catch (e) {
7611
- setError(e instanceof Error ? e.message : "Could not read MCP status");
7612
- } finally {
7613
- setLoading(false);
7614
- }
7615
- }, [client, sessionId]);
7616
- useEffect(() => {
7617
- if (!open) return;
7618
- setSelectedServer(void 0);
7619
- setSelectedTool(void 0);
7620
- load();
7621
- }, [open, load]);
7622
- const act = async (name, action) => {
7623
- if (!sessionId) return;
7624
- setBusyServer(name);
7625
- setError(void 0);
7626
- try {
7627
- setServers(await client.mcpServerAction(sessionId, name, action));
7628
- } catch (e) {
7629
- setError(e instanceof Error ? e.message : `Could not ${action} ${name}`);
7630
- } finally {
7631
- setBusyServer(void 0);
7632
- }
7633
- };
7634
- const server = servers?.find((s) => s.name === selectedServer);
7635
- const tool = server?.tools?.find((t) => t.name === selectedTool);
7636
- const title = tool?.name ?? server?.name ?? "MCP servers";
7609
+ const mcp = useMcpPanel(client, sessionId, open);
7637
7610
  return /* @__PURE__ */ jsx(Dialog$1, {
7638
7611
  open,
7639
7612
  onOpenChange,
7640
7613
  children: /* @__PURE__ */ jsxs(DialogContent, {
7641
7614
  className,
7642
7615
  children: [/* @__PURE__ */ jsx(DialogHeader, {
7643
- title,
7644
- description: tool ? `${server?.name} tool` : server ? server.serverInfo?.name : void 0,
7645
- actions: selectedServer ? /* @__PURE__ */ jsxs(Button, {
7646
- variant: "ghost",
7647
- size: "xs",
7648
- onClick: () => selectedTool ? setSelectedTool(void 0) : setSelectedServer(void 0),
7649
- children: [/* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" }), "Back"]
7650
- }) : /* @__PURE__ */ jsxs(Button, {
7651
- variant: "ghost",
7652
- size: "xs",
7653
- onClick: () => void load(),
7654
- disabled: loading,
7655
- children: [loading ? /* @__PURE__ */ jsx(Spinner, { className: "size-3 text-current" }) : /* @__PURE__ */ jsx(RotateCw, { className: "size-3" }), "Refresh"]
7656
- })
7657
- }), /* @__PURE__ */ jsxs(DialogBody, { children: [error ? /* @__PURE__ */ jsx("div", {
7658
- className: "mb-3 rounded-md bg-danger-bg px-3 py-2 text-body-sm text-danger",
7659
- children: error
7660
- }) : null, tool ? /* @__PURE__ */ jsx(ToolView, { tool }) : server ? /* @__PURE__ */ jsx(ServerView, {
7661
- server,
7662
- busy: busyServer === server.name,
7663
- canManage: canManageServers,
7664
- onAct: (action) => void act(server.name, action),
7665
- onSelectTool: setSelectedTool
7666
- }) : error && !servers ? null : /* @__PURE__ */ jsx(ServerList, {
7667
- servers,
7668
- loading,
7669
- onSelect: setSelectedServer
7670
- })] })]
7616
+ title: mcp.title,
7617
+ description: mcp.description,
7618
+ actions: /* @__PURE__ */ jsx(McpPanelActions, { model: mcp })
7619
+ }), /* @__PURE__ */ jsx(DialogBody, { children: /* @__PURE__ */ jsx(McpPanel, {
7620
+ model: mcp,
7621
+ canManageServers
7622
+ }) })]
7671
7623
  })
7672
7624
  });
7673
7625
  }
7674
- function ServerList({ servers, loading, onSelect }) {
7675
- if (loading && !servers) return /* @__PURE__ */ jsx("div", {
7676
- className: "py-6 text-center",
7626
+ function useMcpPanel(client, sessionId, active = true) {
7627
+ const listed = useAsync(() => client.listMcpServers(sessionId), [client, sessionId], { enabled: active && !!client && !!sessionId });
7628
+ const [actError, setActError] = useState();
7629
+ const [busyServer, setBusyServer] = useState();
7630
+ const [selectedServer, setSelectedServer] = useState();
7631
+ const [selectedTool, setSelectedTool] = useState();
7632
+ useEffect(() => {
7633
+ if (active) {
7634
+ setSelectedServer(void 0);
7635
+ setSelectedTool(void 0);
7636
+ setActError(void 0);
7637
+ }
7638
+ }, [
7639
+ active,
7640
+ client,
7641
+ sessionId
7642
+ ]);
7643
+ const servers = listed.data;
7644
+ const server = servers?.find((s) => s.name === selectedServer);
7645
+ const tool = server?.tools?.find((t) => t.name === selectedTool);
7646
+ return {
7647
+ servers,
7648
+ server,
7649
+ tool,
7650
+ loading: listed.loading,
7651
+ busyServer,
7652
+ error: actError ?? (listed.error === void 0 ? void 0 : errorMessage(listed.error, "Could not read MCP status")),
7653
+ title: tool?.name ?? server?.name ?? "MCP servers",
7654
+ description: tool ? `${server?.name} tool` : server ? server.serverInfo?.name : void 0,
7655
+ selectServer: setSelectedServer,
7656
+ selectTool: setSelectedTool,
7657
+ back: selectedServer ? () => selectedTool ? setSelectedTool(void 0) : setSelectedServer(void 0) : void 0,
7658
+ reload: () => {
7659
+ setActError(void 0);
7660
+ listed.reload();
7661
+ },
7662
+ act: async (name, action) => {
7663
+ if (!client || !sessionId) return;
7664
+ setBusyServer(name);
7665
+ setActError(void 0);
7666
+ try {
7667
+ listed.setData(await client.mcpServerAction(sessionId, name, action));
7668
+ } catch (e) {
7669
+ setActError(errorMessage(e, `Could not ${action} ${name}`));
7670
+ } finally {
7671
+ setBusyServer(void 0);
7672
+ }
7673
+ }
7674
+ };
7675
+ }
7676
+ function McpPanelActions({ model }) {
7677
+ if (model.back) return /* @__PURE__ */ jsxs(Button, {
7678
+ variant: "ghost",
7679
+ size: "xs",
7680
+ onClick: model.back,
7681
+ children: [/* @__PURE__ */ jsx(ChevronLeft, { className: "size-3.5" }), "Back"]
7682
+ });
7683
+ return /* @__PURE__ */ jsxs(Button, {
7684
+ variant: "ghost",
7685
+ size: "xs",
7686
+ onClick: model.reload,
7687
+ disabled: model.loading,
7688
+ children: [model.loading ? /* @__PURE__ */ jsx(Spinner, { className: "size-3 text-current" }) : /* @__PURE__ */ jsx(RotateCw, { className: "size-3" }), "Refresh"]
7689
+ });
7690
+ }
7691
+ function McpPanel({ model, canManageServers = true }) {
7692
+ const { error, servers, server, tool } = model;
7693
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [error ? /* @__PURE__ */ jsx("div", {
7694
+ className: "mb-3 rounded-md bg-danger-bg px-3 py-2 text-body-sm text-danger",
7695
+ children: error
7696
+ }) : null, tool ? /* @__PURE__ */ jsx(ToolView, { tool }) : server ? /* @__PURE__ */ jsx(ServerView, {
7697
+ server,
7698
+ busy: model.busyServer === server.name,
7699
+ canManage: canManageServers,
7700
+ onAct: (action) => void model.act(server.name, action),
7701
+ onSelectTool: model.selectTool
7702
+ }) : error && !servers ? null : /* @__PURE__ */ jsx(ServerList, {
7703
+ servers,
7704
+ loading: model.loading,
7705
+ onSelect: model.selectServer
7706
+ })] });
7707
+ }
7708
+ function ServerList({ servers, loading, onSelect }) {
7709
+ if (loading && !servers) return /* @__PURE__ */ jsx("div", {
7710
+ className: "py-6 text-center",
7677
7711
  children: /* @__PURE__ */ jsx(Spinner, { className: "size-4 text-fg-4" })
7678
7712
  });
7679
7713
  if (!servers?.length) return /* @__PURE__ */ jsx("p", {
@@ -8139,556 +8173,338 @@ function PermissionModeSelect({ mode, onModeChange, modes, canBypass, variant =
8139
8173
  });
8140
8174
  }
8141
8175
  //#endregion
8142
- //#region src/lib/plan-request.ts
8143
- function planFromRequest(request) {
8144
- if (request?.toolName !== "ExitPlanMode") return;
8145
- const input = request.input;
8146
- if (input === null || typeof input !== "object") return;
8147
- const plan = input.plan;
8148
- if (typeof plan !== "string" || plan.trim().length === 0) return;
8149
- return plan;
8176
+ //#region src/components/agent/FrameStrip.tsx
8177
+ const TERMINAL_TONE = {
8178
+ shell: {
8179
+ name: "magenta",
8180
+ live: "magenta"
8181
+ },
8182
+ subagent: {
8183
+ name: "green",
8184
+ live: "mark"
8185
+ }
8186
+ };
8187
+ const CARDS_LIVE_CLASS = {
8188
+ shell: "text-[var(--wd-shell-accent)]",
8189
+ subagent: "text-accent"
8190
+ };
8191
+ function FrameStrip({ kind, name, status, detail, failed, live, onBack, actions, terminal, fontSize, lineHeight }) {
8192
+ if (terminal) {
8193
+ const tones = TERMINAL_TONE[kind];
8194
+ const line = (label) => /* @__PURE__ */ jsxs(Row, {
8195
+ glyph: "←",
8196
+ glyphTone: "dim",
8197
+ indent: 1,
8198
+ tone: failed ? "red" : tones.name,
8199
+ children: [
8200
+ label,
8201
+ status ? /* @__PURE__ */ jsxs(Ink, {
8202
+ tone: failed ? "red" : live ? tones.live : "dim",
8203
+ children: [" · ", status]
8204
+ }) : null,
8205
+ detail ? /* @__PURE__ */ jsxs(Ink, {
8206
+ tone: "faint",
8207
+ children: [" · ", detail]
8208
+ }) : null
8209
+ ]
8210
+ });
8211
+ return /* @__PURE__ */ jsx(TerminalSurface, {
8212
+ fontSize,
8213
+ lineHeight,
8214
+ className: "shrink-0",
8215
+ children: actions === void 0 ? /* @__PURE__ */ jsx("button", {
8216
+ type: "button",
8217
+ onClick: onBack,
8218
+ "aria-label": "Back to the session",
8219
+ className: "block w-full cursor-pointer text-left",
8220
+ children: line(name)
8221
+ }) : /* @__PURE__ */ jsx(WithActions, {
8222
+ placement: "inline",
8223
+ actions,
8224
+ children: line(/* @__PURE__ */ jsx("button", {
8225
+ type: "button",
8226
+ onClick: onBack,
8227
+ "aria-label": "Back to the session",
8228
+ className: "cursor-pointer text-left",
8229
+ children: name
8230
+ }))
8231
+ })
8232
+ });
8233
+ }
8234
+ return /* @__PURE__ */ jsxs("div", {
8235
+ className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-1.5",
8236
+ children: [
8237
+ /* @__PURE__ */ jsxs(Button, {
8238
+ variant: "ghost",
8239
+ size: "sm",
8240
+ onClick: onBack,
8241
+ className: "h-6 gap-1 px-1.5",
8242
+ children: [/* @__PURE__ */ jsx(ArrowLeft, { className: "size-3.5" }), "Back"]
8243
+ }),
8244
+ /* @__PURE__ */ jsx("span", {
8245
+ className: cn("min-w-0 flex-1 truncate text-body-sm", kind === "shell" && "font-mono", failed ? "text-danger" : "text-fg-2"),
8246
+ children: name
8247
+ }),
8248
+ status ? /* @__PURE__ */ jsx("span", {
8249
+ className: cn("shrink-0 text-label", failed ? "text-danger" : live ? CARDS_LIVE_CLASS[kind] : "text-fg-3"),
8250
+ children: status
8251
+ }) : null,
8252
+ detail ? /* @__PURE__ */ jsx("span", {
8253
+ className: "shrink-0 text-label text-fg-4",
8254
+ children: detail
8255
+ }) : null,
8256
+ actions
8257
+ ]
8258
+ });
8150
8259
  }
8151
8260
  //#endregion
8152
- //#region src/lib/shell-request.ts
8153
- const CONTROL_NAMES = {
8154
- "\r": "enter",
8155
- "\n": "newline",
8156
- " ": "tab",
8157
- "\x1B": "escape",
8158
- "": "backspace"
8159
- };
8160
- const SHELL_WRITE_TOOLS = [
8161
- "shell_run",
8162
- "shell_write",
8163
- "shell_kill",
8164
- "shell_request_write"
8165
- ];
8166
- function shellToolOf(toolName) {
8167
- if (typeof toolName !== "string") return;
8168
- const bare = toolName.startsWith("mcp__") ? toolName.slice(toolName.indexOf("__", 5) + 2) : toolName;
8169
- return SHELL_WRITE_TOOLS.includes(bare) ? bare : void 0;
8261
+ //#region src/components/agent/SubagentStrip.tsx
8262
+ function SubagentStrip({ task, items, label, onBack, terminal, fontSize, lineHeight }) {
8263
+ const busy = task ? taskBusy(task, items) : false;
8264
+ const failed = task ? taskFailed(task) : false;
8265
+ const pulse = usePulse(busy);
8266
+ const tools = items.reduce((n, item) => n + (item.kind === "tool_call" ? 1 : 0), 0);
8267
+ const startedAt = busy ? task?.ts : void 0;
8268
+ const now = useTicker(startedAt !== void 0);
8269
+ const elapsed = startedAt === void 0 ? void 0 : formatDuration(now - startedAt);
8270
+ const name = task ? taskIdentity(task) : label;
8271
+ const status = !task ? void 0 : failed ? "failed" : busy ? `${pulse} working…` : "done";
8272
+ const detail = [tools > 0 ? `${tools} tool${tools === 1 ? "" : "s"}` : void 0, elapsed].filter(Boolean).join(" · ");
8273
+ return /* @__PURE__ */ jsx(FrameStrip, {
8274
+ kind: "subagent",
8275
+ name,
8276
+ status,
8277
+ detail,
8278
+ failed,
8279
+ live: busy,
8280
+ onBack,
8281
+ terminal,
8282
+ fontSize,
8283
+ lineHeight
8284
+ });
8170
8285
  }
8171
- function shellRequestPayload(request) {
8172
- const tool = shellToolOf(request?.toolName);
8173
- if (!tool) return;
8174
- const input = request?.input !== null && typeof request?.input === "object" ? request.input : {};
8175
- const shellId = typeof input.shellId === "string" ? input.shellId : "";
8176
- switch (tool) {
8177
- case "shell_run": return {
8178
- kind: "run",
8179
- command: typeof input.command === "string" ? input.command : ""
8180
- };
8181
- case "shell_write": {
8182
- const keys = Array.isArray(input.keys) ? input.keys.filter((key) => typeof key === "string") : void 0;
8183
- return {
8184
- kind: "write",
8185
- shellId,
8186
- ...typeof input.data === "string" ? { data: input.data } : {},
8187
- ...keys && keys.length > 0 ? { keys } : {}
8188
- };
8189
- }
8190
- case "shell_kill": return {
8191
- kind: "kill",
8192
- shellId
8193
- };
8194
- case "shell_request_write": return {
8195
- kind: "grant",
8196
- shellId,
8197
- reason: typeof input.reason === "string" ? input.reason : ""
8198
- };
8199
- }
8286
+ //#endregion
8287
+ //#region src/components/terminal/blocks.ts
8288
+ function parentOf(item) {
8289
+ return ("parentToolUseId" in item ? item.parentToolUseId : void 0) ?? void 0;
8200
8290
  }
8201
- function shellRequestTitle(payload) {
8202
- switch (payload.kind) {
8203
- case "run": return "Agent wants to start a shell";
8204
- case "write": return `Agent wants to type into shell ${payload.shellId}`;
8205
- case "kill": return `Agent wants to kill shell ${payload.shellId}`;
8206
- case "grant": return `Agent asks to type into your shell ${payload.shellId}`;
8207
- }
8291
+ function subagentItems(items, parentToolUseId) {
8292
+ return items.filter((item) => parentOf(item) === parentToolUseId);
8208
8293
  }
8209
- function shellRequestLines(payload) {
8210
- switch (payload.kind) {
8211
- case "run": return payload.command.split("\n").map((line, index) => index === 0 ? `$ ${line}` : ` ${line}`);
8212
- case "write": {
8213
- const lines = [];
8214
- if (payload.data !== void 0) lines.push(`types: ${visibleControls(payload.data)}`);
8215
- if (payload.keys) lines.push(`presses: ${payload.keys.map((key) => `[${key}]`).join(" ")}`);
8216
- return lines;
8217
- }
8218
- case "kill": return [`kill ${payload.shellId}`];
8219
- case "grant": return [`why: ${visibleControls(payload.reason)}`, "until the shell ends or you revoke it"];
8294
+ function isRunCall(item) {
8295
+ return item.kind === "tool_call";
8296
+ }
8297
+ function taskChildItems(block) {
8298
+ return block.children.flatMap((child) => "run" in child ? child.run : [child.item]);
8299
+ }
8300
+ function pushLeaf(out, item, index) {
8301
+ const previous = out.at(-1);
8302
+ if (isRunCall(item) && !isPeerSend(item)) {
8303
+ if (previous && "run" in previous && foldsTogether(previous.run[0], item)) {
8304
+ previous.run.push(item);
8305
+ previous.indices.push(index);
8306
+ } else out.push({
8307
+ key: `run:${item.id}`,
8308
+ run: [item],
8309
+ indices: [index],
8310
+ index
8311
+ });
8312
+ return;
8220
8313
  }
8314
+ out.push({
8315
+ key: `${item.kind}:${item.id}`,
8316
+ item,
8317
+ index
8318
+ });
8221
8319
  }
8222
- function visibleControls(text) {
8223
- let out = "";
8224
- for (const char of text) {
8225
- const code = char.codePointAt(0);
8226
- if (code >= 32 && code !== 127) {
8227
- out += char;
8228
- continue;
8320
+ function terminalBlocks(items, offset = 0, fold = true) {
8321
+ if (!fold) return items.map((item, position) => ({
8322
+ key: `${item.kind}:${item.id}`,
8323
+ item,
8324
+ index: offset + position
8325
+ }));
8326
+ const topLevelCalls = /* @__PURE__ */ new Set();
8327
+ for (const item of items) if (item.kind === "tool_call" && parentOf(item) === void 0) topLevelCalls.add(item.id);
8328
+ const childrenOf = /* @__PURE__ */ new Map();
8329
+ items.forEach((item, position) => {
8330
+ const parent = parentOf(item);
8331
+ if (parent !== void 0 && topLevelCalls.has(parent)) {
8332
+ const list = childrenOf.get(parent);
8333
+ if (list) list.push({
8334
+ item,
8335
+ index: offset + position
8336
+ });
8337
+ else childrenOf.set(parent, [{
8338
+ item,
8339
+ index: offset + position
8340
+ }]);
8229
8341
  }
8230
- const named = CONTROL_NAMES[char];
8231
- out += named ? `[${named}]` : `[ctrl-${String.fromCharCode(code + 64).toLowerCase()}]`;
8342
+ });
8343
+ const out = [];
8344
+ for (const [position, item] of items.entries()) {
8345
+ const index = offset + position;
8346
+ const parent = parentOf(item);
8347
+ if (parent !== void 0 && childrenOf.has(parent)) continue;
8348
+ if (item.kind === "tool_call") {
8349
+ const children = childrenOf.get(item.id);
8350
+ if (children) {
8351
+ const folded = [];
8352
+ for (const child of children) pushLeaf(folded, child.item, child.index);
8353
+ out.push({
8354
+ key: `task:${item.id}`,
8355
+ task: item,
8356
+ children: folded,
8357
+ childIndices: children.map((child) => child.index),
8358
+ index
8359
+ });
8360
+ continue;
8361
+ }
8362
+ }
8363
+ pushLeaf(out, item, index);
8232
8364
  }
8233
8365
  return out;
8234
8366
  }
8367
+ function needsBlank(previous, next) {
8368
+ if (isPeerSend(previous) || isPeerSend(next)) return true;
8369
+ return !(previous.kind === "tool_call" && next.kind === "tool_call");
8370
+ }
8371
+ function blockKind(block) {
8372
+ if (!("item" in block)) return "tool_call";
8373
+ return isPeerSend(block.item) ? "peer_send" : block.item.kind;
8374
+ }
8375
+ function blockNeedsBlank(previous, next) {
8376
+ return !(blockKind(previous) === "tool_call" && blockKind(next) === "tool_call");
8377
+ }
8235
8378
  //#endregion
8236
- //#region src/components/agent/Response.tsx
8237
- const COMPONENTS = { img: ({ src, alt }) => /* @__PURE__ */ jsx(MarkdownImage, {
8238
- src,
8239
- alt
8240
- }) };
8241
- const Response = memo(function Response({ children, streaming, className }) {
8242
- return /* @__PURE__ */ jsx(Streamdown, {
8243
- mode: streaming ? "streaming" : "static",
8244
- parseIncompleteMarkdown: streaming,
8245
- shikiTheme: ["github-light", "github-dark"],
8246
- linkSafety: { enabled: false },
8247
- components: COMPONENTS,
8248
- rehypePlugins: MARKDOWN_REHYPE_PLUGINS,
8249
- className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
8250
- children
8251
- });
8252
- }, (prev, next) => prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className);
8379
+ //#region src/components/agent/use-frame.ts
8380
+ function useFrame({ sessionId, request, reveal, onChange, returnTo }) {
8381
+ const [id, setId] = useState(void 0);
8382
+ const [returnReveal, setReturnReveal] = useState(void 0);
8383
+ useEffect(() => {
8384
+ setId(void 0);
8385
+ setReturnReveal(void 0);
8386
+ }, [sessionId]);
8387
+ const returnToRef = useRef(returnTo);
8388
+ returnToRef.current = returnTo;
8389
+ const leave = useCallback(() => {
8390
+ setId((current) => {
8391
+ const toolUseId = current === void 0 ? void 0 : returnToRef.current(current);
8392
+ if (toolUseId !== void 0) setReturnReveal({
8393
+ toolUseId,
8394
+ nonce: Date.now()
8395
+ });
8396
+ });
8397
+ }, []);
8398
+ const requestNonce = request?.nonce;
8399
+ const requestId = request?.id;
8400
+ useEffect(() => {
8401
+ if (requestId === void 0) leave();
8402
+ else setId(requestId);
8403
+ }, [requestNonce]);
8404
+ const revealNonce = reveal?.nonce;
8405
+ useEffect(() => {
8406
+ if (revealNonce !== void 0) setId(void 0);
8407
+ }, [revealNonce]);
8408
+ useEffect(() => {
8409
+ if (id === void 0) return;
8410
+ const onKey = (event) => {
8411
+ if (event.key === "Escape" && !event.defaultPrevented) leave();
8412
+ };
8413
+ window.addEventListener("keydown", onKey);
8414
+ return () => window.removeEventListener("keydown", onKey);
8415
+ }, [id, leave]);
8416
+ const onChangeRef = useRef(onChange);
8417
+ onChangeRef.current = onChange;
8418
+ const reported = useRef(void 0);
8419
+ useEffect(() => {
8420
+ if (reported.current === id) return;
8421
+ reported.current = id;
8422
+ onChangeRef.current?.(id);
8423
+ }, [id]);
8424
+ return {
8425
+ id,
8426
+ enter: setId,
8427
+ leave,
8428
+ returnReveal
8429
+ };
8430
+ }
8253
8431
  //#endregion
8254
- //#region src/components/agent/PermissionPrompt.tsx
8255
- function PermissionPrompt({ request, onApprove, onDeny, className }) {
8256
- const [showInput, setShowInput] = useState(false);
8257
- const [denying, setDenying] = useState(false);
8258
- const [reason, setReason] = useState("");
8259
- const deny = (interrupt) => {
8260
- const message = reason.trim();
8261
- onDeny(request.id, message || void 0, interrupt || void 0);
8262
- setReason("");
8263
- setDenying(false);
8432
+ //#region src/components/agent/use-subagent-frame.ts
8433
+ function useSubagentFrame(options) {
8434
+ const { sessionId, items, session, reveal, openSubagent, onSubagentChange } = options;
8435
+ const frame = useFrame({
8436
+ sessionId,
8437
+ request: openSubagent && {
8438
+ id: openSubagent.toolUseId,
8439
+ nonce: openSubagent.nonce
8440
+ },
8441
+ reveal,
8442
+ onChange: onSubagentChange,
8443
+ returnTo: (toolUseId) => toolUseId
8444
+ });
8445
+ const subagentId = frame.id;
8446
+ const frameItems = useMemo(() => subagentId === void 0 ? [] : subagentItems(items, subagentId), [items, subagentId]);
8447
+ const task = useMemo(() => subagentId === void 0 ? void 0 : items.find((item) => item.kind === "tool_call" && item.id === subagentId), [items, subagentId]);
8448
+ const fallbackLabel = useMemo(() => {
8449
+ const record = session?.subagents?.find((sub) => sub.toolUseId === subagentId);
8450
+ return record ? subagentLabel(record) : "Sub-agent";
8451
+ }, [session, subagentId]);
8452
+ return {
8453
+ subagentId,
8454
+ enterSubagent: frame.enter,
8455
+ leaveSubagent: frame.leave,
8456
+ returnReveal: frame.returnReveal,
8457
+ frameItems,
8458
+ task,
8459
+ fallbackLabel
8264
8460
  };
8265
- const plan = planFromRequest(request);
8266
- const shell = plan ? void 0 : shellRequestPayload(request);
8267
- const summary = toolInputPreview(request.input);
8268
- const ToolIcon = toolIcon(request.toolName);
8269
- return /* @__PURE__ */ jsxs("div", {
8270
- "data-slot": "permission-prompt",
8271
- className: cn("rounded-lg border border-warning/40 bg-warning-bg p-3", className),
8272
- children: [/* @__PURE__ */ jsxs("div", {
8273
- className: "flex items-start gap-2.5",
8274
- children: [
8275
- /* @__PURE__ */ jsx(Hand, { className: "mt-0.5 size-4 shrink-0 text-warning" }),
8276
- /* @__PURE__ */ jsxs("div", {
8277
- className: "min-w-0 flex-1",
8278
- children: [
8279
- /* @__PURE__ */ jsx("div", {
8280
- className: "text-body-sm font-medium text-fg-1",
8281
- children: plan ? "Plan ready for review" : shell ? shellRequestTitle(shell) : request.title ?? request.displayName ?? "Permission needed"
8282
- }),
8283
- request.description ? /* @__PURE__ */ jsx("div", {
8284
- className: "mt-0.5 text-label text-fg-3",
8285
- children: request.description
8286
- }) : null,
8287
- request.decisionReason ? /* @__PURE__ */ jsx("div", {
8288
- className: "mt-0.5 text-label text-fg-4",
8289
- children: request.decisionReason
8290
- }) : null,
8291
- plan ? /* @__PURE__ */ jsx("div", {
8292
- className: "mt-1.5 max-h-72 overflow-y-auto rounded-md bg-code-bg px-2.5 py-2 text-body-sm text-fg-2",
8293
- children: /* @__PURE__ */ jsx(Response, { children: plan })
8294
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
8295
- shell ? /* @__PURE__ */ jsx("pre", {
8296
- "data-slot": "shell-request",
8297
- className: "mt-1.5 max-h-48 overflow-auto rounded-md bg-code-bg px-2.5 py-1.5 font-mono text-body-sm whitespace-pre-wrap text-fg-1",
8298
- children: shellRequestLines(shell).join("\n")
8299
- }) : null,
8300
- /* @__PURE__ */ jsxs("div", {
8301
- className: "mt-1.5 flex min-w-0 items-center gap-2",
8302
- children: [
8303
- /* @__PURE__ */ jsx(ToolIcon, { className: "size-3 shrink-0 text-fg-3" }),
8304
- /* @__PURE__ */ jsx("span", {
8305
- className: "shrink-0 font-mono text-label font-medium text-fg-2",
8306
- children: request.toolName
8307
- }),
8308
- summary && !shell ? /* @__PURE__ */ jsx("span", {
8309
- className: "min-w-0 truncate font-mono text-label text-fg-4",
8310
- children: summary
8311
- }) : null
8312
- ]
8313
- }),
8314
- /* @__PURE__ */ jsxs("button", {
8315
- type: "button",
8316
- className: "mt-1 font-mono text-label text-fg-3 underline-offset-2 hover:underline",
8317
- onClick: () => setShowInput((v) => !v),
8318
- children: [
8319
- showInput ? "Hide" : "Show",
8320
- " ",
8321
- request.toolName,
8322
- " input"
8323
- ]
8324
- }),
8325
- showInput ? /* @__PURE__ */ jsx("pre", {
8326
- className: "mt-1.5 max-h-48 overflow-auto rounded-md bg-code-bg px-2.5 py-1.5 font-mono text-label whitespace-pre-wrap text-fg-2",
8327
- children: JSON.stringify(request.input, null, 2)
8328
- }) : null
8329
- ] })
8330
- ]
8331
- }),
8332
- /* @__PURE__ */ jsxs("div", {
8333
- className: "flex shrink-0 flex-wrap justify-end gap-1.5",
8334
- children: [
8335
- /* @__PURE__ */ jsx(Button, {
8336
- size: "sm",
8337
- onClick: () => onApprove(request.id),
8338
- children: plan ? "Approve plan" : "Allow"
8339
- }),
8340
- /* @__PURE__ */ jsx(Button, {
8341
- size: "sm",
8342
- variant: "outline",
8343
- onClick: () => setDenying((v) => !v),
8344
- children: plan ? "Keep planning" : "Deny"
8345
- }),
8346
- /* @__PURE__ */ jsx(Button, {
8347
- size: "sm",
8348
- variant: "outline",
8349
- onClick: () => deny(true),
8350
- children: plan ? "Stop the turn" : "Deny & stop"
8351
- })
8352
- ]
8353
- })
8354
- ]
8355
- }), denying ? /* @__PURE__ */ jsxs("div", {
8356
- className: "mt-2.5 flex items-center gap-2 border-t border-warning/30 pt-2.5",
8357
- children: [
8358
- /* @__PURE__ */ jsx(Input, {
8359
- autoFocus: true,
8360
- value: reason,
8361
- onChange: (e) => setReason(e.target.value),
8362
- onKeyDown: (e) => {
8363
- if (e.key === "Enter") deny(false);
8364
- if (e.key === "Escape") setDenying(false);
8365
- },
8366
- placeholder: plan ? "What should change? (optional) - the agent keeps planning and reads this" : "Reason (optional) - the agent reads this and can try something else",
8367
- className: "h-7 flex-1"
8368
- }),
8369
- /* @__PURE__ */ jsx(Button, {
8370
- size: "sm",
8371
- variant: "outline",
8372
- onClick: () => setDenying(false),
8373
- children: "Cancel"
8374
- }),
8375
- /* @__PURE__ */ jsx(Button, {
8376
- size: "sm",
8377
- variant: "destructive",
8378
- onClick: () => deny(false),
8379
- children: plan ? "Keep planning" : "Deny"
8380
- })
8381
- ]
8382
- }) : null]
8383
- });
8384
8461
  }
8385
8462
  //#endregion
8386
- //#region src/components/agent/SubagentStrip.tsx
8387
- function SubagentStrip({ task, items, label, onBack, terminal, fontSize, lineHeight }) {
8388
- const busy = task ? taskBusy(task, items) : false;
8389
- const failed = task ? taskFailed(task) : false;
8390
- const pulse = usePulse(busy);
8391
- const tools = items.reduce((n, item) => n + (item.kind === "tool_call" ? 1 : 0), 0);
8392
- const startedAt = busy ? task?.ts : void 0;
8393
- const now = useTicker(startedAt !== void 0);
8394
- const elapsed = startedAt === void 0 ? void 0 : formatDuration(now - startedAt);
8395
- const name = task ? taskIdentity(task) : label;
8396
- const status = !task ? void 0 : failed ? "failed" : busy ? `${pulse} working…` : "done";
8397
- const detail = [tools > 0 ? `${tools} tool${tools === 1 ? "" : "s"}` : void 0, elapsed].filter(Boolean).join(" · ");
8398
- if (terminal) return /* @__PURE__ */ jsx(TerminalSurface, {
8399
- fontSize,
8400
- lineHeight,
8401
- className: "shrink-0",
8402
- children: /* @__PURE__ */ jsx("button", {
8403
- type: "button",
8404
- onClick: onBack,
8405
- "aria-label": "Back to the session",
8406
- className: "block w-full cursor-pointer text-left",
8407
- children: /* @__PURE__ */ jsxs(Row, {
8408
- glyph: "←",
8409
- glyphTone: "dim",
8410
- indent: 1,
8411
- tone: failed ? "red" : "green",
8412
- children: [
8413
- name,
8414
- status ? /* @__PURE__ */ jsxs(Ink, {
8415
- tone: failed ? "red" : busy ? "mark" : "dim",
8416
- children: [" · ", status]
8417
- }) : null,
8418
- detail ? /* @__PURE__ */ jsxs(Ink, {
8419
- tone: "faint",
8420
- children: [" · ", detail]
8421
- }) : null
8422
- ]
8423
- })
8463
+ //#region src/components/agent/ShellStrip.tsx
8464
+ function ShellStrip({ shell, label, cols, onBack, onKill, onAgentWrite, terminal, fontSize, lineHeight }) {
8465
+ const running = shell?.status === "running";
8466
+ const failed = shell ? shellInfoFailed(shell) : false;
8467
+ const name = shell ? shellTitle(shell) : label;
8468
+ const status = shell ? shellInfoStatusText(shell) : void 0;
8469
+ const width = cols ?? shell?.cols;
8470
+ const detail = width === void 0 ? void 0 : `${width} cols`;
8471
+ const granted = shell?.agentWrite === true;
8472
+ const grant = shell && onAgentWrite && shellGrantable(shell) ? () => onAgentWrite(!granted) : void 0;
8473
+ const actions = terminal ? /* @__PURE__ */ jsxs(Fragment$1, { children: [grant && shell ? /* @__PURE__ */ jsx(AgentWriteAction, {
8474
+ granted,
8475
+ label: shellAgentWriteLabel(shell),
8476
+ onToggle: grant
8477
+ }) : null, running && onKill ? /* @__PURE__ */ jsx(KillShellAction, { onKill }) : null] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [grant && shell ? /* @__PURE__ */ jsx("button", {
8478
+ type: "button",
8479
+ "aria-label": shellAgentWriteLabel(shell),
8480
+ "aria-pressed": granted,
8481
+ title: shellAgentWriteLabel(shell),
8482
+ className: cn("shrink-0 text-label", granted ? "text-warning" : "text-fg-3 hover:text-fg-1"),
8483
+ onClick: grant,
8484
+ children: /* @__PURE__ */ jsx(AgentWriteIcon, {
8485
+ granted,
8486
+ className: "size-3.5"
8424
8487
  })
8425
- });
8426
- return /* @__PURE__ */ jsxs("div", {
8427
- className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-1.5",
8428
- children: [
8429
- /* @__PURE__ */ jsxs(Button, {
8430
- variant: "ghost",
8431
- size: "sm",
8432
- onClick: onBack,
8433
- className: "h-6 gap-1 px-1.5",
8434
- children: [/* @__PURE__ */ jsx(ArrowLeft, { className: "size-3.5" }), "Back"]
8435
- }),
8436
- /* @__PURE__ */ jsx("span", {
8437
- className: cn("min-w-0 flex-1 truncate text-body-sm", failed ? "text-danger" : "text-fg-2"),
8438
- children: name
8439
- }),
8440
- status ? /* @__PURE__ */ jsx("span", {
8441
- className: cn("shrink-0 text-label", failed ? "text-danger" : busy ? "text-accent" : "text-fg-3"),
8442
- children: status
8443
- }) : null,
8444
- detail ? /* @__PURE__ */ jsx("span", {
8445
- className: "shrink-0 text-label text-fg-4",
8446
- children: detail
8447
- }) : null
8448
- ]
8449
- });
8450
- }
8451
- //#endregion
8452
- //#region src/components/terminal/blocks.ts
8453
- function parentOf(item) {
8454
- return ("parentToolUseId" in item ? item.parentToolUseId : void 0) ?? void 0;
8455
- }
8456
- function subagentItems(items, parentToolUseId) {
8457
- return items.filter((item) => parentOf(item) === parentToolUseId);
8458
- }
8459
- function isRunCall(item) {
8460
- return item.kind === "tool_call";
8461
- }
8462
- function taskChildItems(block) {
8463
- return block.children.flatMap((child) => "run" in child ? child.run : [child.item]);
8464
- }
8465
- function pushLeaf(out, item, index) {
8466
- const previous = out.at(-1);
8467
- if (isRunCall(item) && !isPeerSend(item)) {
8468
- if (previous && "run" in previous && foldsTogether(previous.run[0], item)) {
8469
- previous.run.push(item);
8470
- previous.indices.push(index);
8471
- } else out.push({
8472
- key: `run:${item.id}`,
8473
- run: [item],
8474
- indices: [index],
8475
- index
8476
- });
8477
- return;
8478
- }
8479
- out.push({
8480
- key: `${item.kind}:${item.id}`,
8481
- item,
8482
- index
8483
- });
8484
- }
8485
- function terminalBlocks(items, offset = 0, fold = true) {
8486
- if (!fold) return items.map((item, position) => ({
8487
- key: `${item.kind}:${item.id}`,
8488
- item,
8489
- index: offset + position
8490
- }));
8491
- const topLevelCalls = /* @__PURE__ */ new Set();
8492
- for (const item of items) if (item.kind === "tool_call" && parentOf(item) === void 0) topLevelCalls.add(item.id);
8493
- const childrenOf = /* @__PURE__ */ new Map();
8494
- items.forEach((item, position) => {
8495
- const parent = parentOf(item);
8496
- if (parent !== void 0 && topLevelCalls.has(parent)) {
8497
- const list = childrenOf.get(parent);
8498
- if (list) list.push({
8499
- item,
8500
- index: offset + position
8501
- });
8502
- else childrenOf.set(parent, [{
8503
- item,
8504
- index: offset + position
8505
- }]);
8506
- }
8507
- });
8508
- const out = [];
8509
- for (const [position, item] of items.entries()) {
8510
- const index = offset + position;
8511
- const parent = parentOf(item);
8512
- if (parent !== void 0 && childrenOf.has(parent)) continue;
8513
- if (item.kind === "tool_call") {
8514
- const children = childrenOf.get(item.id);
8515
- if (children) {
8516
- const folded = [];
8517
- for (const child of children) pushLeaf(folded, child.item, child.index);
8518
- out.push({
8519
- key: `task:${item.id}`,
8520
- task: item,
8521
- children: folded,
8522
- childIndices: children.map((child) => child.index),
8523
- index
8524
- });
8525
- continue;
8526
- }
8527
- }
8528
- pushLeaf(out, item, index);
8529
- }
8530
- return out;
8531
- }
8532
- function needsBlank(previous, next) {
8533
- if (isPeerSend(previous) || isPeerSend(next)) return true;
8534
- return !(previous.kind === "tool_call" && next.kind === "tool_call");
8535
- }
8536
- function blockKind(block) {
8537
- if (!("item" in block)) return "tool_call";
8538
- return isPeerSend(block.item) ? "peer_send" : block.item.kind;
8539
- }
8540
- function blockNeedsBlank(previous, next) {
8541
- return !(blockKind(previous) === "tool_call" && blockKind(next) === "tool_call");
8542
- }
8543
- //#endregion
8544
- //#region src/components/agent/use-subagent-frame.ts
8545
- function useSubagentFrame(options) {
8546
- const { sessionId, items, session, reveal, openSubagent, onSubagentChange } = options;
8547
- const [subagentId, setSubagentId] = useState(void 0);
8548
- const [returnReveal, setReturnReveal] = useState(void 0);
8549
- useEffect(() => {
8550
- setSubagentId(void 0);
8551
- setReturnReveal(void 0);
8552
- }, [sessionId]);
8553
- const leaveSubagent = useCallback(() => {
8554
- setSubagentId((current) => {
8555
- if (current !== void 0) setReturnReveal({
8556
- toolUseId: current,
8557
- nonce: Date.now()
8558
- });
8559
- });
8560
- }, []);
8561
- const openSubagentNonce = openSubagent?.nonce;
8562
- const openSubagentId = openSubagent?.toolUseId;
8563
- useEffect(() => {
8564
- if (openSubagentId === void 0) leaveSubagent();
8565
- else setSubagentId(openSubagentId);
8566
- }, [openSubagentNonce]);
8567
- const revealNonce = reveal?.nonce;
8568
- useEffect(() => {
8569
- if (revealNonce === void 0) return;
8570
- setSubagentId(void 0);
8571
- }, [revealNonce]);
8572
- useEffect(() => {
8573
- if (subagentId === void 0) return;
8574
- const onKey = (event) => {
8575
- if (event.key !== "Escape" || event.defaultPrevented) return;
8576
- leaveSubagent();
8577
- };
8578
- window.addEventListener("keydown", onKey);
8579
- return () => window.removeEventListener("keydown", onKey);
8580
- }, [subagentId, leaveSubagent]);
8581
- const onSubagentChangeRef = useRef(onSubagentChange);
8582
- onSubagentChangeRef.current = onSubagentChange;
8583
- const reportedSubagentId = useRef(void 0);
8584
- useEffect(() => {
8585
- if (reportedSubagentId.current === subagentId) return;
8586
- reportedSubagentId.current = subagentId;
8587
- onSubagentChangeRef.current?.(subagentId);
8588
- }, [subagentId]);
8589
- return {
8590
- subagentId,
8591
- enterSubagent: setSubagentId,
8592
- leaveSubagent,
8593
- returnReveal,
8594
- frameItems: useMemo(() => subagentId === void 0 ? [] : subagentItems(items, subagentId), [items, subagentId]),
8595
- task: useMemo(() => subagentId === void 0 ? void 0 : items.find((item) => item.kind === "tool_call" && item.id === subagentId), [items, subagentId]),
8596
- fallbackLabel: useMemo(() => {
8597
- const record = session?.subagents?.find((sub) => sub.toolUseId === subagentId);
8598
- return record ? subagentLabel(record) : "Sub-agent";
8599
- }, [session, subagentId])
8600
- };
8601
- }
8602
- //#endregion
8603
- //#region src/components/agent/ShellStrip.tsx
8604
- function ShellStrip({ shell, label, cols, onBack, onKill, onAgentWrite, terminal, fontSize, lineHeight }) {
8605
- const running = shell?.status === "running";
8606
- const failed = shell ? shellInfoFailed(shell) : false;
8607
- const name = shell ? shellTitle(shell) : label;
8608
- const status = shell ? shellInfoStatusText(shell) : void 0;
8609
- const width = cols ?? shell?.cols;
8610
- const detail = width === void 0 ? void 0 : `${width} cols`;
8611
- const granted = shell?.agentWrite === true;
8612
- const grant = shell && onAgentWrite && shellGrantable(shell) ? () => onAgentWrite(!granted) : void 0;
8613
- if (terminal) return /* @__PURE__ */ jsx(TerminalSurface, {
8614
- fontSize,
8615
- lineHeight,
8616
- className: "shrink-0",
8617
- children: /* @__PURE__ */ jsx(WithActions, {
8618
- placement: "inline",
8619
- actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [grant && shell ? /* @__PURE__ */ jsx(AgentWriteAction, {
8620
- granted,
8621
- label: shellAgentWriteLabel(shell),
8622
- onToggle: grant
8623
- }) : null, running && onKill ? /* @__PURE__ */ jsx(KillShellAction, { onKill }) : null] }),
8624
- children: /* @__PURE__ */ jsxs(Row, {
8625
- glyph: "←",
8626
- glyphTone: "dim",
8627
- indent: 1,
8628
- tone: failed ? "red" : "magenta",
8629
- children: [
8630
- /* @__PURE__ */ jsx("button", {
8631
- type: "button",
8632
- onClick: onBack,
8633
- "aria-label": "Back to the session",
8634
- className: "cursor-pointer text-left",
8635
- children: name
8636
- }),
8637
- status ? /* @__PURE__ */ jsxs(Ink, {
8638
- tone: failed ? "red" : running ? "magenta" : "dim",
8639
- children: [" · ", status]
8640
- }) : null,
8641
- detail ? /* @__PURE__ */ jsxs(Ink, {
8642
- tone: "faint",
8643
- children: [" · ", detail]
8644
- }) : null
8645
- ]
8646
- })
8647
- })
8648
- });
8649
- return /* @__PURE__ */ jsxs("div", {
8650
- className: "flex shrink-0 items-center gap-2 border-b border-border px-3 py-1.5",
8651
- children: [
8652
- /* @__PURE__ */ jsxs(Button, {
8653
- variant: "ghost",
8654
- size: "sm",
8655
- onClick: onBack,
8656
- className: "h-6 gap-1 px-1.5",
8657
- children: [/* @__PURE__ */ jsx(ArrowLeft, { className: "size-3.5" }), "Back"]
8658
- }),
8659
- /* @__PURE__ */ jsx("span", {
8660
- className: cn("min-w-0 flex-1 truncate font-mono text-body-sm", failed ? "text-danger" : "text-fg-2"),
8661
- children: name
8662
- }),
8663
- status ? /* @__PURE__ */ jsx("span", {
8664
- className: cn("shrink-0 text-label", failed ? "text-danger" : running ? "text-[var(--wd-shell-accent)]" : "text-fg-3"),
8665
- children: status
8666
- }) : null,
8667
- detail ? /* @__PURE__ */ jsx("span", {
8668
- className: "shrink-0 text-label text-fg-4",
8669
- children: detail
8670
- }) : null,
8671
- grant && shell ? /* @__PURE__ */ jsx("button", {
8672
- type: "button",
8673
- "aria-label": shellAgentWriteLabel(shell),
8674
- "aria-pressed": granted,
8675
- title: shellAgentWriteLabel(shell),
8676
- className: cn("shrink-0 text-label", granted ? "text-warning" : "text-fg-3 hover:text-fg-1"),
8677
- onClick: grant,
8678
- children: /* @__PURE__ */ jsx(AgentWriteIcon, {
8679
- granted,
8680
- className: "size-3.5"
8681
- })
8682
- }) : null,
8683
- running && onKill ? /* @__PURE__ */ jsx("button", {
8684
- type: "button",
8685
- "aria-label": "Kill this shell",
8686
- title: "Kill this shell",
8687
- className: "shrink-0 text-label text-fg-3 hover:text-danger",
8688
- onClick: onKill,
8689
- children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
8690
- }) : null
8691
- ]
8488
+ }) : null, running && onKill ? /* @__PURE__ */ jsx("button", {
8489
+ type: "button",
8490
+ "aria-label": "Kill this shell",
8491
+ title: "Kill this shell",
8492
+ className: "shrink-0 text-label text-fg-3 hover:text-danger",
8493
+ onClick: onKill,
8494
+ children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
8495
+ }) : null] });
8496
+ return /* @__PURE__ */ jsx(FrameStrip, {
8497
+ kind: "shell",
8498
+ name,
8499
+ status,
8500
+ detail,
8501
+ failed,
8502
+ live: running,
8503
+ onBack,
8504
+ actions,
8505
+ terminal,
8506
+ fontSize,
8507
+ lineHeight
8692
8508
  });
8693
8509
  }
8694
8510
  //#endregion
@@ -8801,111 +8617,444 @@ function ShellTerminal({ handle, shellId, onEnded, onShell, fontSize, className
8801
8617
  })]
8802
8618
  });
8803
8619
  }
8804
- function useDocumentTheme() {
8805
- const [theme, setTheme] = useState(() => typeof document === "undefined" ? null : document.documentElement.getAttribute("data-theme"));
8806
- useEffect(() => {
8807
- const root = document.documentElement;
8808
- const sync = () => setTheme(root.getAttribute("data-theme"));
8809
- sync();
8810
- const observer = new MutationObserver(sync);
8811
- observer.observe(root, {
8812
- attributes: true,
8813
- attributeFilter: ["data-theme"]
8814
- });
8815
- return () => observer.disconnect();
8816
- }, []);
8817
- return theme;
8620
+ function useDocumentTheme() {
8621
+ const [theme, setTheme] = useState(() => typeof document === "undefined" ? null : document.documentElement.getAttribute("data-theme"));
8622
+ useEffect(() => {
8623
+ const root = document.documentElement;
8624
+ const sync = () => setTheme(root.getAttribute("data-theme"));
8625
+ sync();
8626
+ const observer = new MutationObserver(sync);
8627
+ observer.observe(root, {
8628
+ attributes: true,
8629
+ attributeFilter: ["data-theme"]
8630
+ });
8631
+ return () => observer.disconnect();
8632
+ }, []);
8633
+ return theme;
8634
+ }
8635
+ function xtermTheme(theme) {
8636
+ return theme === "light" ? {
8637
+ background: "#00000000",
8638
+ foreground: "#1f2328",
8639
+ cursor: "#1f2328",
8640
+ selectionBackground: "#b9c7d980"
8641
+ } : {
8642
+ background: "#00000000",
8643
+ foreground: "#d4d4d4",
8644
+ cursor: "#d4d4d4",
8645
+ selectionBackground: "#3a4a5f80"
8646
+ };
8647
+ }
8648
+ let xtermPromise;
8649
+ function loadXterm() {
8650
+ xtermPromise ??= (async () => {
8651
+ const [core, fitAddon] = await Promise.all([import("@xterm/xterm"), import("@xterm/addon-fit")]);
8652
+ return {
8653
+ Terminal: core.Terminal,
8654
+ FitAddon: fitAddon.FitAddon
8655
+ };
8656
+ })();
8657
+ return xtermPromise;
8658
+ }
8659
+ //#endregion
8660
+ //#region src/components/agent/use-shell-frame.ts
8661
+ function useShellFrame(options) {
8662
+ const { sessionId, items, session, shells, reveal, openShell, onShellChange } = options;
8663
+ const rowIdRef = useRef(void 0);
8664
+ const frame = useFrame({
8665
+ sessionId,
8666
+ request: openShell && {
8667
+ id: openShell.shellId,
8668
+ nonce: openShell.nonce
8669
+ },
8670
+ reveal,
8671
+ onChange: onShellChange,
8672
+ returnTo: () => rowIdRef.current
8673
+ });
8674
+ const shellId = frame.id;
8675
+ const row = useMemo(() => shellId === void 0 ? void 0 : items.find((item) => item.kind === "shell" && item.shell.id === shellId), [items, shellId]);
8676
+ rowIdRef.current = row?.id;
8677
+ const shell = useMemo(() => {
8678
+ if (shellId === void 0) return;
8679
+ return row?.shell ?? shells?.find((record) => record.id === shellId) ?? session?.shells?.find((record) => record.id === shellId);
8680
+ }, [
8681
+ row,
8682
+ shells,
8683
+ session,
8684
+ shellId
8685
+ ]);
8686
+ const label = shell ? shell.label || shell.command.split("\n")[0] || "shell" : "shell";
8687
+ return {
8688
+ shellId,
8689
+ enterShell: frame.enter,
8690
+ leaveShell: frame.leave,
8691
+ returnReveal: frame.returnReveal,
8692
+ shell,
8693
+ label
8694
+ };
8695
+ }
8696
+ //#endregion
8697
+ //#region src/lib/plan-request.ts
8698
+ function planFromRequest(request) {
8699
+ if (request?.toolName !== "ExitPlanMode") return;
8700
+ const input = request.input;
8701
+ if (input === null || typeof input !== "object") return;
8702
+ const plan = input.plan;
8703
+ if (typeof plan !== "string" || plan.trim().length === 0) return;
8704
+ return plan;
8705
+ }
8706
+ //#endregion
8707
+ //#region src/lib/shell-request.ts
8708
+ const CONTROL_NAMES = {
8709
+ "\r": "enter",
8710
+ "\n": "newline",
8711
+ " ": "tab",
8712
+ "\x1B": "escape",
8713
+ "": "backspace"
8714
+ };
8715
+ const SHELL_WRITE_TOOLS = [
8716
+ "shell_run",
8717
+ "shell_write",
8718
+ "shell_kill",
8719
+ "shell_request_write"
8720
+ ];
8721
+ function shellToolOf(toolName) {
8722
+ if (typeof toolName !== "string") return;
8723
+ const bare = toolName.startsWith("mcp__") ? toolName.slice(toolName.indexOf("__", 5) + 2) : toolName;
8724
+ return SHELL_WRITE_TOOLS.includes(bare) ? bare : void 0;
8725
+ }
8726
+ function shellRequestPayload(request) {
8727
+ const tool = shellToolOf(request?.toolName);
8728
+ if (!tool) return;
8729
+ const input = request?.input !== null && typeof request?.input === "object" ? request.input : {};
8730
+ const shellId = typeof input.shellId === "string" ? input.shellId : "";
8731
+ switch (tool) {
8732
+ case "shell_run": return {
8733
+ kind: "run",
8734
+ command: typeof input.command === "string" ? input.command : ""
8735
+ };
8736
+ case "shell_write": {
8737
+ const keys = Array.isArray(input.keys) ? input.keys.filter((key) => typeof key === "string") : void 0;
8738
+ return {
8739
+ kind: "write",
8740
+ shellId,
8741
+ ...typeof input.data === "string" ? { data: input.data } : {},
8742
+ ...keys && keys.length > 0 ? { keys } : {}
8743
+ };
8744
+ }
8745
+ case "shell_kill": return {
8746
+ kind: "kill",
8747
+ shellId
8748
+ };
8749
+ case "shell_request_write": return {
8750
+ kind: "grant",
8751
+ shellId,
8752
+ reason: typeof input.reason === "string" ? input.reason : ""
8753
+ };
8754
+ }
8755
+ }
8756
+ function shellRequestTitle(payload) {
8757
+ switch (payload.kind) {
8758
+ case "run": return "Agent wants to start a shell";
8759
+ case "write": return `Agent wants to type into shell ${payload.shellId}`;
8760
+ case "kill": return `Agent wants to kill shell ${payload.shellId}`;
8761
+ case "grant": return `Agent asks to type into your shell ${payload.shellId}`;
8762
+ }
8763
+ }
8764
+ function shellRequestLines(payload) {
8765
+ switch (payload.kind) {
8766
+ case "run": return payload.command.split("\n").map((line, index) => index === 0 ? `$ ${line}` : ` ${line}`);
8767
+ case "write": {
8768
+ const lines = [];
8769
+ if (payload.data !== void 0) lines.push(`types: ${visibleControls(payload.data)}`);
8770
+ if (payload.keys) lines.push(`presses: ${payload.keys.map((key) => `[${key}]`).join(" ")}`);
8771
+ return lines;
8772
+ }
8773
+ case "kill": return [`kill ${payload.shellId}`];
8774
+ case "grant": return [`why: ${visibleControls(payload.reason)}`, "until the shell ends or you revoke it"];
8775
+ }
8776
+ }
8777
+ function visibleControls(text) {
8778
+ let out = "";
8779
+ for (const char of text) {
8780
+ const code = char.codePointAt(0);
8781
+ if (code >= 32 && code !== 127) {
8782
+ out += char;
8783
+ continue;
8784
+ }
8785
+ const named = CONTROL_NAMES[char];
8786
+ out += named ? `[${named}]` : `[ctrl-${String.fromCharCode(code + 64).toLowerCase()}]`;
8787
+ }
8788
+ return out;
8789
+ }
8790
+ //#endregion
8791
+ //#region src/lib/permission-prompt.ts
8792
+ function permissionPromptModel(request) {
8793
+ const plan = planFromRequest(request);
8794
+ const shell = plan ? void 0 : shellRequestPayload(request);
8795
+ const context = {
8796
+ description: request.description,
8797
+ decisionReason: request.decisionReason
8798
+ };
8799
+ if (plan) return {
8800
+ plan,
8801
+ heading: "Plan ready for review",
8802
+ question: "Ready to implement this plan?",
8803
+ ...context,
8804
+ choices: [
8805
+ {
8806
+ key: "allow",
8807
+ button: "Approve plan",
8808
+ option: "Approve plan"
8809
+ },
8810
+ {
8811
+ key: "deny",
8812
+ button: "Keep planning",
8813
+ option: "Keep planning - tell it what to change"
8814
+ },
8815
+ {
8816
+ key: "stop",
8817
+ button: "Stop the turn",
8818
+ option: "No, and stop the turn",
8819
+ danger: true
8820
+ }
8821
+ ],
8822
+ denyPlaceholder: "What should change? (optional) - the agent keeps planning and reads this"
8823
+ };
8824
+ return {
8825
+ shell,
8826
+ heading: shell ? shellRequestTitle(shell) : request.title ?? request.displayName ?? "Permission needed",
8827
+ question: shell ? "Do you want to let it?" : "Do you want to proceed?",
8828
+ ...context,
8829
+ choices: [
8830
+ {
8831
+ key: "allow",
8832
+ button: "Allow",
8833
+ option: "Yes"
8834
+ },
8835
+ {
8836
+ key: "deny",
8837
+ button: "Deny",
8838
+ option: "No, and tell the agent what to do differently"
8839
+ },
8840
+ {
8841
+ key: "stop",
8842
+ button: "Deny & stop",
8843
+ option: "No, and stop the turn",
8844
+ danger: true
8845
+ }
8846
+ ],
8847
+ denyPlaceholder: "Reason (optional) - the agent reads this and can try something else"
8848
+ };
8849
+ }
8850
+ //#endregion
8851
+ //#region src/components/agent/Response.tsx
8852
+ const COMPONENTS = { img: ({ src, alt }) => /* @__PURE__ */ jsx(MarkdownImage, {
8853
+ src,
8854
+ alt
8855
+ }) };
8856
+ const Response = memo(function Response({ children, streaming, className }) {
8857
+ return /* @__PURE__ */ jsx(Streamdown, {
8858
+ mode: streaming ? "streaming" : "static",
8859
+ parseIncompleteMarkdown: streaming,
8860
+ shikiTheme: ["github-light", "github-dark"],
8861
+ linkSafety: { enabled: false },
8862
+ components: COMPONENTS,
8863
+ rehypePlugins: MARKDOWN_REHYPE_PLUGINS,
8864
+ className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
8865
+ children
8866
+ });
8867
+ }, (prev, next) => prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className);
8868
+ //#endregion
8869
+ //#region src/components/agent/PermissionPrompt.tsx
8870
+ function PermissionPrompt({ request, onApprove, onDeny, className }) {
8871
+ const [showInput, setShowInput] = useState(false);
8872
+ const [denying, setDenying] = useState(false);
8873
+ const [reason, setReason] = useState("");
8874
+ const deny = (interrupt) => {
8875
+ const message = reason.trim();
8876
+ onDeny(request.id, message || void 0, interrupt || void 0);
8877
+ setReason("");
8878
+ setDenying(false);
8879
+ };
8880
+ const model = permissionPromptModel(request);
8881
+ const { plan, shell } = model;
8882
+ const [allow, keep, stop] = model.choices;
8883
+ const summary = toolInputPreview(request.input);
8884
+ const ToolIcon = toolIcon(request.toolName);
8885
+ return /* @__PURE__ */ jsxs("div", {
8886
+ "data-slot": "permission-prompt",
8887
+ className: cn("rounded-lg border border-warning/40 bg-warning-bg p-3", className),
8888
+ children: [/* @__PURE__ */ jsxs("div", {
8889
+ className: "flex items-start gap-2.5",
8890
+ children: [
8891
+ /* @__PURE__ */ jsx(Hand, { className: "mt-0.5 size-4 shrink-0 text-warning" }),
8892
+ /* @__PURE__ */ jsxs("div", {
8893
+ className: "min-w-0 flex-1",
8894
+ children: [
8895
+ /* @__PURE__ */ jsx("div", {
8896
+ className: "text-body-sm font-medium text-fg-1",
8897
+ children: model.heading
8898
+ }),
8899
+ model.description ? /* @__PURE__ */ jsx("div", {
8900
+ className: "mt-0.5 text-label text-fg-3",
8901
+ children: model.description
8902
+ }) : null,
8903
+ model.decisionReason ? /* @__PURE__ */ jsx("div", {
8904
+ className: "mt-0.5 text-label text-fg-4",
8905
+ children: model.decisionReason
8906
+ }) : null,
8907
+ plan ? /* @__PURE__ */ jsx("div", {
8908
+ className: "mt-1.5 max-h-72 overflow-y-auto rounded-md bg-code-bg px-2.5 py-2 text-body-sm text-fg-2",
8909
+ children: /* @__PURE__ */ jsx(Response, { children: plan })
8910
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
8911
+ shell ? /* @__PURE__ */ jsx("pre", {
8912
+ "data-slot": "shell-request",
8913
+ className: "mt-1.5 max-h-48 overflow-auto rounded-md bg-code-bg px-2.5 py-1.5 font-mono text-body-sm whitespace-pre-wrap text-fg-1",
8914
+ children: shellRequestLines(shell).join("\n")
8915
+ }) : null,
8916
+ /* @__PURE__ */ jsxs("div", {
8917
+ className: "mt-1.5 flex min-w-0 items-center gap-2",
8918
+ children: [
8919
+ /* @__PURE__ */ jsx(ToolIcon, { className: "size-3 shrink-0 text-fg-3" }),
8920
+ /* @__PURE__ */ jsx("span", {
8921
+ className: "shrink-0 font-mono text-label font-medium text-fg-2",
8922
+ children: request.toolName
8923
+ }),
8924
+ summary && !shell ? /* @__PURE__ */ jsx("span", {
8925
+ className: "min-w-0 truncate font-mono text-label text-fg-4",
8926
+ children: summary
8927
+ }) : null
8928
+ ]
8929
+ }),
8930
+ /* @__PURE__ */ jsxs("button", {
8931
+ type: "button",
8932
+ className: "mt-1 font-mono text-label text-fg-3 underline-offset-2 hover:underline",
8933
+ onClick: () => setShowInput((v) => !v),
8934
+ children: [
8935
+ showInput ? "Hide" : "Show",
8936
+ " ",
8937
+ request.toolName,
8938
+ " input"
8939
+ ]
8940
+ }),
8941
+ showInput ? /* @__PURE__ */ jsx("pre", {
8942
+ className: "mt-1.5 max-h-48 overflow-auto rounded-md bg-code-bg px-2.5 py-1.5 font-mono text-label whitespace-pre-wrap text-fg-2",
8943
+ children: JSON.stringify(request.input, null, 2)
8944
+ }) : null
8945
+ ] })
8946
+ ]
8947
+ }),
8948
+ /* @__PURE__ */ jsxs("div", {
8949
+ className: "flex shrink-0 flex-wrap justify-end gap-1.5",
8950
+ children: [
8951
+ /* @__PURE__ */ jsx(Button, {
8952
+ size: "sm",
8953
+ onClick: () => onApprove(request.id),
8954
+ children: allow.button
8955
+ }),
8956
+ /* @__PURE__ */ jsx(Button, {
8957
+ size: "sm",
8958
+ variant: "outline",
8959
+ onClick: () => setDenying((v) => !v),
8960
+ children: keep.button
8961
+ }),
8962
+ /* @__PURE__ */ jsx(Button, {
8963
+ size: "sm",
8964
+ variant: "outline",
8965
+ onClick: () => deny(true),
8966
+ children: stop.button
8967
+ })
8968
+ ]
8969
+ })
8970
+ ]
8971
+ }), denying ? /* @__PURE__ */ jsxs("div", {
8972
+ className: "mt-2.5 flex items-center gap-2 border-t border-warning/30 pt-2.5",
8973
+ children: [
8974
+ /* @__PURE__ */ jsx(Input, {
8975
+ autoFocus: true,
8976
+ value: reason,
8977
+ onChange: (e) => setReason(e.target.value),
8978
+ onKeyDown: (e) => {
8979
+ if (e.key === "Enter") deny(false);
8980
+ if (e.key === "Escape") setDenying(false);
8981
+ },
8982
+ placeholder: model.denyPlaceholder,
8983
+ className: "h-7 flex-1"
8984
+ }),
8985
+ /* @__PURE__ */ jsx(Button, {
8986
+ size: "sm",
8987
+ variant: "outline",
8988
+ onClick: () => setDenying(false),
8989
+ children: "Cancel"
8990
+ }),
8991
+ /* @__PURE__ */ jsx(Button, {
8992
+ size: "sm",
8993
+ variant: "destructive",
8994
+ onClick: () => deny(false),
8995
+ children: keep.button
8996
+ })
8997
+ ]
8998
+ }) : null]
8999
+ });
9000
+ }
9001
+ //#endregion
9002
+ //#region src/lib/question-answers.ts
9003
+ const EMPTY_SELECTION = {
9004
+ labels: [],
9005
+ other: "",
9006
+ otherActive: false
9007
+ };
9008
+ function answerFor(selection) {
9009
+ const parts = [...selection.labels];
9010
+ if (selection.otherActive && selection.other.trim()) parts.push(selection.other.trim());
9011
+ return parts.join(", ");
9012
+ }
9013
+ function toggleLabel(selection, label, multiSelect) {
9014
+ if (!multiSelect) return {
9015
+ ...selection,
9016
+ labels: [label],
9017
+ otherActive: false
9018
+ };
9019
+ const labels = selection.labels.includes(label) ? selection.labels.filter((l) => l !== label) : [...selection.labels, label];
9020
+ return {
9021
+ ...selection,
9022
+ labels
9023
+ };
8818
9024
  }
8819
- function xtermTheme(theme) {
8820
- return theme === "light" ? {
8821
- background: "#00000000",
8822
- foreground: "#1f2328",
8823
- cursor: "#1f2328",
8824
- selectionBackground: "#b9c7d980"
8825
- } : {
8826
- background: "#00000000",
8827
- foreground: "#d4d4d4",
8828
- cursor: "#d4d4d4",
8829
- selectionBackground: "#3a4a5f80"
9025
+ function toggleOther(selection, multiSelect) {
9026
+ const otherActive = !selection.otherActive;
9027
+ return {
9028
+ ...selection,
9029
+ otherActive,
9030
+ labels: otherActive && !multiSelect ? [] : selection.labels
8830
9031
  };
8831
9032
  }
8832
- let xtermPromise;
8833
- function loadXterm() {
8834
- xtermPromise ??= (async () => {
8835
- const [core, fitAddon] = await Promise.all([import("@xterm/xterm"), import("@xterm/addon-fit")]);
8836
- return {
8837
- Terminal: core.Terminal,
8838
- FitAddon: fitAddon.FitAddon
8839
- };
8840
- })();
8841
- return xtermPromise;
9033
+ function answersFor(questions, selections) {
9034
+ const answers = {};
9035
+ questions.forEach((question, index) => {
9036
+ answers[question.question] = answerFor(selections[index] ?? EMPTY_SELECTION);
9037
+ });
9038
+ return answers;
8842
9039
  }
8843
- //#endregion
8844
- //#region src/components/agent/use-shell-frame.ts
8845
- function useShellFrame(options) {
8846
- const { sessionId, items, session, shells, reveal, openShell, onShellChange } = options;
8847
- const [shellId, setShellId] = useState(void 0);
8848
- const [returnReveal, setReturnReveal] = useState(void 0);
8849
- useEffect(() => {
8850
- setShellId(void 0);
8851
- setReturnReveal(void 0);
8852
- }, [sessionId]);
8853
- const rowIdRef = useRef(void 0);
8854
- const row = useMemo(() => shellId === void 0 ? void 0 : items.find((item) => item.kind === "shell" && item.shell.id === shellId), [items, shellId]);
8855
- rowIdRef.current = row?.id;
8856
- const leaveShell = useCallback(() => {
8857
- setShellId((current) => {
8858
- const rowId = rowIdRef.current;
8859
- if (current !== void 0 && rowId !== void 0) setReturnReveal({
8860
- toolUseId: rowId,
8861
- nonce: Date.now()
8862
- });
8863
- });
8864
- }, []);
8865
- const openShellNonce = openShell?.nonce;
8866
- const openShellId = openShell?.shellId;
8867
- useEffect(() => {
8868
- if (openShellId === void 0) leaveShell();
8869
- else setShellId(openShellId);
8870
- }, [openShellNonce]);
8871
- const revealNonce = reveal?.nonce;
8872
- useEffect(() => {
8873
- if (revealNonce === void 0) return;
8874
- setShellId(void 0);
8875
- }, [revealNonce]);
8876
- useEffect(() => {
8877
- if (shellId === void 0) return;
8878
- const onKey = (event) => {
8879
- if (event.key !== "Escape" || event.defaultPrevented) return;
8880
- leaveShell();
8881
- };
8882
- window.addEventListener("keydown", onKey);
8883
- return () => window.removeEventListener("keydown", onKey);
8884
- }, [shellId, leaveShell]);
8885
- const onShellChangeRef = useRef(onShellChange);
8886
- onShellChangeRef.current = onShellChange;
8887
- const reportedShellId = useRef(void 0);
8888
- useEffect(() => {
8889
- if (reportedShellId.current === shellId) return;
8890
- reportedShellId.current = shellId;
8891
- onShellChangeRef.current?.(shellId);
8892
- }, [shellId]);
8893
- const shell = useMemo(() => {
8894
- if (shellId === void 0) return;
8895
- return row?.shell ?? shells?.find((record) => record.id === shellId) ?? session?.shells?.find((record) => record.id === shellId);
8896
- }, [
8897
- row,
8898
- shells,
8899
- session,
8900
- shellId
8901
- ]);
9040
+ function useQuestionAnswers(questions) {
9041
+ const [selections, setSelections] = useState(() => questions.map(() => EMPTY_SELECTION));
9042
+ const selectionFor = (index) => selections[index] ?? EMPTY_SELECTION;
9043
+ const answered = (index) => answerFor(selectionFor(index)) !== "";
9044
+ const edit = (index, change) => setSelections((prev) => questions.map((_, i) => i === index ? change(prev[i] ?? EMPTY_SELECTION) : prev[i] ?? EMPTY_SELECTION));
9045
+ const multiSelect = (index) => questions[index]?.multiSelect === true;
8902
9046
  return {
8903
- shellId,
8904
- enterShell: setShellId,
8905
- leaveShell,
8906
- returnReveal,
8907
- shell,
8908
- label: shell ? shell.label || shell.command.split("\n")[0] || "shell" : "shell"
9047
+ selections,
9048
+ selectionFor,
9049
+ answered,
9050
+ complete: questions.every((_, index) => answered(index)),
9051
+ update: (index, patch) => edit(index, (selection) => ({
9052
+ ...selection,
9053
+ ...patch
9054
+ })),
9055
+ toggle: (index, label) => edit(index, (selection) => toggleLabel(selection, label, multiSelect(index))),
9056
+ toggleOther: (index) => edit(index, (selection) => toggleOther(selection, multiSelect(index))),
9057
+ answers: () => answersFor(questions, selections)
8909
9058
  };
8910
9059
  }
8911
9060
  //#endregion
@@ -8927,44 +9076,13 @@ const QUESTION_BEHAVIORS = [
8927
9076
  description: "the agent is told to decide on its own"
8928
9077
  }
8929
9078
  ];
8930
- const EMPTY_SELECTION = {
8931
- labels: [],
8932
- other: "",
8933
- otherActive: false
8934
- };
8935
- function answerFor$1(selection) {
8936
- const parts = [...selection.labels];
8937
- if (selection.otherActive && selection.other.trim()) parts.push(selection.other.trim());
8938
- return parts.join(", ");
8939
- }
8940
9079
  function QuestionPrompt({ request, onAnswer, onDismiss, className }) {
8941
9080
  const questions = parseUserQuestions(request.input);
8942
- const [selections, setSelections] = useState(() => questions.map(() => EMPTY_SELECTION));
8943
- const update = (index, patch) => {
8944
- setSelections((prev) => prev.map((s, i) => i === index ? {
8945
- ...s,
8946
- ...patch
8947
- } : s));
8948
- };
8949
- const toggle = (index, label, multiSelect) => {
8950
- const current = selections[index] ?? EMPTY_SELECTION;
8951
- if (multiSelect) update(index, { labels: current.labels.includes(label) ? current.labels.filter((l) => l !== label) : [...current.labels, label] });
8952
- else update(index, {
8953
- labels: current.labels[0] === label ? [] : [label],
8954
- otherActive: false
8955
- });
8956
- };
8957
- const complete = questions.every((_, i) => answerFor$1(selections[i] ?? EMPTY_SELECTION) !== "");
8958
- const submit = () => {
8959
- const answers = {};
8960
- questions.forEach((q, i) => {
8961
- answers[q.question] = answerFor$1(selections[i] ?? EMPTY_SELECTION);
8962
- });
8963
- onAnswer(request.id, {
8964
- ...request.input,
8965
- answers
8966
- });
8967
- };
9081
+ const { selectionFor, update, toggle, toggleOther, complete, answers } = useQuestionAnswers(questions);
9082
+ const submit = () => onAnswer(request.id, {
9083
+ ...request.input,
9084
+ answers: answers()
9085
+ });
8968
9086
  return /* @__PURE__ */ jsx("div", {
8969
9087
  "data-slot": "question-prompt",
8970
9088
  className: cn("rounded-lg border border-info/40 bg-info-bg p-3", className),
@@ -8975,7 +9093,7 @@ function QuestionPrompt({ request, onAnswer, onDismiss, className }) {
8975
9093
  /* @__PURE__ */ jsxs("div", {
8976
9094
  className: "flex min-w-0 flex-1 flex-col gap-3",
8977
9095
  children: [questions.map((q, index) => {
8978
- const selection = selections[index] ?? EMPTY_SELECTION;
9096
+ const selection = selectionFor(index);
8979
9097
  return /* @__PURE__ */ jsxs("div", {
8980
9098
  className: "flex flex-col gap-1.5",
8981
9099
  children: [/* @__PURE__ */ jsxs("div", {
@@ -8993,7 +9111,7 @@ function QuestionPrompt({ request, onAnswer, onDismiss, className }) {
8993
9111
  const selected = selection.labels.includes(option.label);
8994
9112
  return /* @__PURE__ */ jsxs("button", {
8995
9113
  type: "button",
8996
- onClick: () => toggle(index, option.label, q.multiSelect === true),
9114
+ onClick: () => toggle(index, option.label),
8997
9115
  className: cn("rounded-md border px-2.5 py-1.5 text-left transition-colors", selected ? "border-info bg-bg" : "border-border bg-bg/50 hover:border-border-strong hover:bg-bg"),
8998
9116
  children: [
8999
9117
  /* @__PURE__ */ jsx("span", {
@@ -9014,7 +9132,7 @@ function QuestionPrompt({ request, onAnswer, onDismiss, className }) {
9014
9132
  className: "flex items-center gap-2",
9015
9133
  children: [/* @__PURE__ */ jsx("button", {
9016
9134
  type: "button",
9017
- onClick: () => update(index, { otherActive: !selection.otherActive }),
9135
+ onClick: () => toggleOther(index),
9018
9136
  className: cn("shrink-0 rounded-md border px-2.5 py-1.5 text-body-sm font-medium transition-colors", selection.otherActive ? "border-info bg-bg text-fg-1" : "border-border bg-bg/50 text-fg-3 hover:border-border-strong hover:bg-bg"),
9019
9137
  children: "Other…"
9020
9138
  }), selection.otherActive ? /* @__PURE__ */ jsx(Textarea, {
@@ -9223,50 +9341,24 @@ function TerminalPermissionPrompt({ request, onApprove, onDeny, className }) {
9223
9341
  setReason("");
9224
9342
  setDenying(false);
9225
9343
  };
9226
- const plan = planFromRequest(request);
9227
- const shell = plan ? void 0 : shellRequestPayload(request);
9344
+ const model = permissionPromptModel(request);
9345
+ const { plan, shell, heading } = model;
9228
9346
  const patch = plan || shell ? void 0 : previewPatch(request.input);
9229
9347
  const summary = plan || shell ? "" : toolInputPreview(request.input);
9230
- const heading = plan ? "Plan ready for review" : shell ? shellRequestTitle(shell) : request.displayName ?? request.title ?? "Permission needed";
9231
9348
  const subject = patch?.path ?? (summary || void 0);
9232
9349
  const reasonInput = denying ? /* @__PURE__ */ jsx(PromptInput, {
9233
9350
  value: reason,
9234
9351
  onChange: setReason,
9235
9352
  onSubmit: () => deny(false),
9236
9353
  onCancel: () => setDenying(false),
9237
- placeholder: plan ? "What should change? (optional) - the agent keeps planning and reads this" : "Reason (optional) - the agent reads this and can try something else"
9354
+ placeholder: model.denyPlaceholder
9238
9355
  }) : void 0;
9239
- const options = plan ? [
9240
- {
9241
- key: "allow",
9242
- label: "Approve plan"
9243
- },
9244
- {
9245
- key: "deny",
9246
- label: "Keep planning - tell it what to change",
9247
- detail: reasonInput
9248
- },
9249
- {
9250
- key: "stop",
9251
- label: "No, and stop the turn",
9252
- danger: true
9253
- }
9254
- ] : [
9255
- {
9256
- key: "allow",
9257
- label: "Yes"
9258
- },
9259
- {
9260
- key: "deny",
9261
- label: "No, and tell the agent what to do differently",
9262
- detail: reasonInput
9263
- },
9264
- {
9265
- key: "stop",
9266
- label: "No, and stop the turn",
9267
- danger: true
9268
- }
9269
- ];
9356
+ const options = model.choices.map((choice) => ({
9357
+ key: choice.key,
9358
+ label: choice.option,
9359
+ danger: choice.danger,
9360
+ detail: choice.key === "deny" ? reasonInput : void 0
9361
+ }));
9270
9362
  return /* @__PURE__ */ jsxs("div", {
9271
9363
  "data-slot": "permission-prompt",
9272
9364
  className,
@@ -9301,15 +9393,15 @@ function TerminalPermissionPrompt({ request, onApprove, onDeny, className }) {
9301
9393
  /* @__PURE__ */ jsx(TerminalDiff, { patch }),
9302
9394
  /* @__PURE__ */ jsx(Blank, {}),
9303
9395
  /* @__PURE__ */ jsx(Rule, { dashed: true })
9304
- ] }) : request.description ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Row, {
9396
+ ] }) : model.description ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Row, {
9305
9397
  tone: "dim",
9306
- children: request.description
9398
+ children: model.description
9307
9399
  }), /* @__PURE__ */ jsx(Blank, {})] }) : null,
9308
- request.decisionReason ? /* @__PURE__ */ jsx(Row, {
9400
+ model.decisionReason ? /* @__PURE__ */ jsx(Row, {
9309
9401
  tone: "faint",
9310
- children: request.decisionReason
9402
+ children: model.decisionReason
9311
9403
  }) : null,
9312
- /* @__PURE__ */ jsx(Row, { children: plan ? "Ready to implement this plan?" : shell ? "Do you want to let it?" : request.title ?? `Do you want to proceed?` }),
9404
+ /* @__PURE__ */ jsx(Row, { children: model.question }),
9313
9405
  /* @__PURE__ */ jsx(Choices, {
9314
9406
  label: heading,
9315
9407
  options,
@@ -9317,9 +9409,10 @@ function TerminalPermissionPrompt({ request, onApprove, onDeny, className }) {
9317
9409
  onFocus: setFocused,
9318
9410
  active: !denying,
9319
9411
  onChoose: (index) => {
9320
- if (index === 0) onApprove(request.id);
9321
- else if (index === 1) setDenying(true);
9322
- else deny(true);
9412
+ const key = model.choices[index]?.key;
9413
+ if (key === "allow") onApprove(request.id);
9414
+ else if (key === "deny") setDenying(true);
9415
+ else if (key === "stop") deny(true);
9323
9416
  }
9324
9417
  }),
9325
9418
  /* @__PURE__ */ jsx(Blank, {}),
@@ -9329,54 +9422,18 @@ function TerminalPermissionPrompt({ request, onApprove, onDeny, className }) {
9329
9422
  }
9330
9423
  //#endregion
9331
9424
  //#region src/components/terminal/QuestionPrompt.tsx
9332
- const EMPTY = {
9333
- labels: [],
9334
- other: "",
9335
- otherActive: false
9336
- };
9337
- function answerFor(selection) {
9338
- const parts = [...selection.labels];
9339
- if (selection.otherActive && selection.other.trim()) parts.push(selection.other.trim());
9340
- return parts.join(", ");
9341
- }
9342
9425
  function TerminalQuestionPrompt({ request, onAnswer, onDismiss, className }) {
9343
9426
  const questions = parseUserQuestions(request.input);
9344
- const [selections, setSelections] = useState(() => questions.map(() => EMPTY));
9427
+ const { selectionFor, answered, complete, update, toggle, toggleOther, answers } = useQuestionAnswers(questions);
9345
9428
  const [cursors, setCursors] = useState(() => questions.map(() => 0));
9346
9429
  const [tab, setTab] = useState(0);
9347
9430
  const [reviewCursor, setReviewCursor] = useState(0);
9348
9431
  const review = tab >= questions.length;
9349
- const selection = selections[tab] ?? EMPTY;
9350
- const answered = (index) => answerFor(selections[index] ?? EMPTY) !== "";
9351
- const complete = questions.every((_, index) => answered(index));
9352
- const update = (index, patch) => setSelections((prev) => prev.map((s, i) => i === index ? {
9353
- ...s,
9354
- ...patch
9355
- } : s));
9356
- const toggle = (index, label, multiSelect) => {
9357
- setSelections((prev) => prev.map((current, i) => {
9358
- if (i !== index) return current;
9359
- if (!multiSelect) return {
9360
- ...current,
9361
- labels: [label],
9362
- otherActive: false
9363
- };
9364
- return {
9365
- ...current,
9366
- labels: current.labels.includes(label) ? current.labels.filter((l) => l !== label) : [...current.labels, label]
9367
- };
9368
- }));
9369
- };
9370
- const submit = () => {
9371
- const answers = {};
9372
- questions.forEach((question, index) => {
9373
- answers[question.question] = answerFor(selections[index] ?? EMPTY);
9374
- });
9375
- onAnswer(request.id, {
9376
- ...request.input,
9377
- answers
9378
- });
9379
- };
9432
+ const selection = selectionFor(tab);
9433
+ const submit = () => onAnswer(request.id, {
9434
+ ...request.input,
9435
+ answers: answers()
9436
+ });
9380
9437
  const dismiss = () => onDismiss(request.id, "Question dismissed by user");
9381
9438
  const move = (delta) => setTab((current) => Math.min(questions.length, Math.max(0, current + delta)));
9382
9439
  return /* @__PURE__ */ jsxs("div", {
@@ -9411,7 +9468,7 @@ function TerminalQuestionPrompt({ request, onAnswer, onDismiss, className }) {
9411
9468
  /* @__PURE__ */ jsx(Blank, {}),
9412
9469
  review ? /* @__PURE__ */ jsx(ReviewStep, {
9413
9470
  questions,
9414
- answers: questions.map((_, index) => answerFor(selections[index] ?? EMPTY)),
9471
+ answers: questions.map((_, index) => answerFor(selectionFor(index))),
9415
9472
  complete,
9416
9473
  cursor: reviewCursor,
9417
9474
  onCursor: setReviewCursor,
@@ -9422,8 +9479,9 @@ function TerminalQuestionPrompt({ request, onAnswer, onDismiss, className }) {
9422
9479
  selection,
9423
9480
  cursor: cursors[tab] ?? 0,
9424
9481
  onCursor: (next) => setCursors((prev) => prev.map((c, i) => i === tab ? next : c)),
9425
- onToggle: (label) => toggle(tab, label, questions[tab].multiSelect === true),
9482
+ onToggle: (label) => toggle(tab, label),
9426
9483
  onOther: (patch) => update(tab, patch),
9484
+ onToggleOther: () => toggleOther(tab),
9427
9485
  onAdvance: () => move(1),
9428
9486
  onDismiss: dismiss
9429
9487
  }, tab),
@@ -9432,7 +9490,7 @@ function TerminalQuestionPrompt({ request, onAnswer, onDismiss, className }) {
9432
9490
  ]
9433
9491
  });
9434
9492
  }
9435
- function QuestionStep({ question, selection, cursor, onCursor, onToggle, onOther, onAdvance, onDismiss }) {
9493
+ function QuestionStep({ question, selection, cursor, onCursor, onToggle, onOther, onToggleOther, onAdvance, onDismiss }) {
9436
9494
  const multiSelect = question.multiSelect === true;
9437
9495
  const options = [
9438
9496
  ...question.options.map((option, index) => {
@@ -9496,7 +9554,7 @@ function QuestionStep({ question, selection, cursor, onCursor, onToggle, onOther
9496
9554
  return;
9497
9555
  }
9498
9556
  if (index === question.options.length) {
9499
- onOther({ otherActive: !selection.otherActive });
9557
+ onToggleOther();
9500
9558
  return;
9501
9559
  }
9502
9560
  if (multiSelect && index === question.options.length + 1) {
@@ -9548,89 +9606,259 @@ function ReviewStep({ questions, answers, complete, cursor, onCursor, onSubmit,
9548
9606
  ] });
9549
9607
  }
9550
9608
  //#endregion
9609
+ //#region src/components/agent/ApprovalPrompts.tsx
9610
+ function ApprovalPrompts({ requests, terminal, fontSize, lineHeight, affordances, hostPrompts, onApprove, onDeny }) {
9611
+ const dismissQuestion = (id) => onDeny(id, "Question dismissed by user");
9612
+ return /* @__PURE__ */ jsx("div", {
9613
+ className: cn(terminal ? "pb-2" : "px-3 pb-2"),
9614
+ children: /* @__PURE__ */ jsx(PromptSurface, {
9615
+ terminal,
9616
+ fontSize,
9617
+ lineHeight,
9618
+ affordances,
9619
+ children: requests.map((request) => {
9620
+ const HostPrompt = hostPrompts?.[request.toolName];
9621
+ if (HostPrompt) return /* @__PURE__ */ jsx(HostPrompt, {
9622
+ request,
9623
+ onApprove,
9624
+ onDeny
9625
+ }, request.id);
9626
+ const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
9627
+ if (terminal) return isQuestion ? /* @__PURE__ */ jsx(TerminalQuestionPrompt, {
9628
+ request,
9629
+ onAnswer: onApprove,
9630
+ onDismiss: dismissQuestion
9631
+ }, request.id) : /* @__PURE__ */ jsx(TerminalPermissionPrompt, {
9632
+ request,
9633
+ onApprove,
9634
+ onDeny
9635
+ }, request.id);
9636
+ return isQuestion ? /* @__PURE__ */ jsx(QuestionPrompt, {
9637
+ request,
9638
+ onAnswer: onApprove,
9639
+ onDismiss: dismissQuestion
9640
+ }, request.id) : /* @__PURE__ */ jsx(PermissionPrompt, {
9641
+ request,
9642
+ onApprove,
9643
+ onDeny
9644
+ }, request.id);
9645
+ })
9646
+ })
9647
+ });
9648
+ }
9649
+ function PromptSurface({ terminal, fontSize, lineHeight, affordances, children }) {
9650
+ if (!terminal) return /* @__PURE__ */ jsx("div", {
9651
+ className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] flex-col gap-2",
9652
+ children
9653
+ });
9654
+ return /* @__PURE__ */ jsx(TerminalSurface, {
9655
+ fontSize,
9656
+ lineHeight,
9657
+ affordances,
9658
+ bleed: "1ch",
9659
+ className: "term-transcript",
9660
+ children
9661
+ });
9662
+ }
9663
+ //#endregion
9664
+ //#region src/components/agent/session-panel-providers.tsx
9665
+ function SessionPanelProviders({ variant, fileLinks, loadFullResult, shellActions, toolTitles, resultImages, children }) {
9666
+ return /* @__PURE__ */ jsx(TranscriptVariantProvider, {
9667
+ value: variant,
9668
+ children: /* @__PURE__ */ jsx(FileLinkProvider, {
9669
+ value: fileLinks,
9670
+ children: /* @__PURE__ */ jsx(ToolResultFetchProvider, {
9671
+ value: loadFullResult,
9672
+ children: /* @__PURE__ */ jsx(ShellActionsProvider, {
9673
+ value: shellActions,
9674
+ children: /* @__PURE__ */ jsx(ToolTitleProvider, {
9675
+ value: toolTitles,
9676
+ children: /* @__PURE__ */ jsx(ToolResultImageProvider, {
9677
+ value: resultImages,
9678
+ children
9679
+ })
9680
+ })
9681
+ })
9682
+ })
9683
+ })
9684
+ });
9685
+ }
9686
+ //#endregion
9687
+ //#region src/components/agent/use-catch-up.ts
9688
+ function useCatchUp(sessionId, unseen) {
9689
+ const [held, setHeld] = useState({
9690
+ sessionId,
9691
+ mark: unseen,
9692
+ caughtUp: false
9693
+ });
9694
+ let current = held;
9695
+ if (held.sessionId !== sessionId) {
9696
+ current = {
9697
+ sessionId,
9698
+ mark: unseen,
9699
+ caughtUp: false
9700
+ };
9701
+ setHeld(current);
9702
+ }
9703
+ const dismiss = useCallback(() => setHeld((prev) => prev.caughtUp ? prev : {
9704
+ ...prev,
9705
+ caughtUp: true
9706
+ }), []);
9707
+ return {
9708
+ mark: current.caughtUp ? void 0 : current.mark,
9709
+ dismiss
9710
+ };
9711
+ }
9712
+ //#endregion
9713
+ //#region src/components/agent/use-host-image.ts
9714
+ const IMAGE_MEDIA_TYPES = {
9715
+ png: "image/png",
9716
+ jpg: "image/jpeg",
9717
+ jpeg: "image/jpeg",
9718
+ gif: "image/gif",
9719
+ webp: "image/webp"
9720
+ };
9721
+ function useHostImage(client, sessionId, producedFiles) {
9722
+ const store = useMemo(() => ({
9723
+ cache: /* @__PURE__ */ new Map(),
9724
+ urls: /* @__PURE__ */ new Set(),
9725
+ live: true
9726
+ }), [client, sessionId]);
9727
+ useEffect(() => {
9728
+ store.live = true;
9729
+ return () => {
9730
+ store.live = false;
9731
+ for (const url of store.urls) URL.revokeObjectURL(url);
9732
+ store.urls.clear();
9733
+ store.cache.clear();
9734
+ };
9735
+ }, [store]);
9736
+ return useCallback((path) => {
9737
+ const produced = producedFiles?.[path];
9738
+ const key = produced ? `produced:${produced.fileId}` : `fs:${path}`;
9739
+ const hit = store.cache.get(key);
9740
+ if (hit) return hit;
9741
+ const pending = (produced && sessionId ? readProduced(client, sessionId, produced.fileId, store) : readHostImage(client, path)).catch(() => {
9742
+ if (store.cache.get(key) === pending) store.cache.delete(key);
9743
+ });
9744
+ store.cache.set(key, pending);
9745
+ return pending;
9746
+ }, [
9747
+ client,
9748
+ sessionId,
9749
+ producedFiles,
9750
+ store
9751
+ ]);
9752
+ }
9753
+ async function readProduced(client, sessionId, fileId, store) {
9754
+ const blob = await client.readProducedFile(sessionId, fileId);
9755
+ if (blob.size === 0) return;
9756
+ const url = URL.createObjectURL(blob);
9757
+ if (!store.live) {
9758
+ URL.revokeObjectURL(url);
9759
+ throw new Error("host image store disposed");
9760
+ }
9761
+ store.urls.add(url);
9762
+ return url;
9763
+ }
9764
+ async function readHostImage(client, path) {
9765
+ const file = await client.readHostFile(path);
9766
+ if (file.encoding !== "base64") return;
9767
+ const mediaType = IMAGE_MEDIA_TYPES[path.slice(path.lastIndexOf(".") + 1).toLowerCase()];
9768
+ return mediaType ? `data:${mediaType};base64,${file.content}` : void 0;
9769
+ }
9770
+ //#endregion
9551
9771
  //#region src/components/agent/SessionInfoDialog.tsx
9552
9772
  function SessionInfoDialog({ state, client, sessionId, open, onOpenChange }) {
9553
- const session = state.session;
9554
- const mode = state.permissionMode ? permissionModeMeta(state.permissionMode) : void 0;
9555
9773
  return /* @__PURE__ */ jsx(Dialog$1, {
9556
9774
  open,
9557
9775
  onOpenChange,
9558
9776
  children: /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsx(DialogHeader, {
9559
9777
  title: "Session info",
9560
- description: session?.title
9561
- }), /* @__PURE__ */ jsx(DialogBody, { children: /* @__PURE__ */ jsxs("div", {
9562
- className: "flex flex-col divide-y divide-border",
9563
- children: [
9564
- /* @__PURE__ */ jsxs("div", {
9565
- className: "pb-2",
9566
- children: [
9567
- /* @__PURE__ */ jsx(DialogRow, {
9568
- label: "Engine",
9569
- children: state.engine ?? "claude"
9570
- }),
9571
- session?.profile ? /* @__PURE__ */ jsx(DialogRow, {
9572
- label: "Profile",
9573
- children: session.profile
9574
- }) : null,
9575
- state.model ? /* @__PURE__ */ jsx(DialogRow, {
9576
- label: "Model",
9577
- mono: true,
9578
- children: state.model
9579
- }) : null,
9580
- mode ? /* @__PURE__ */ jsx(DialogRow, {
9581
- label: "Permission mode",
9582
- children: mode.label
9583
- }) : null,
9584
- session?.apiKeySource ? /* @__PURE__ */ jsx(DialogRow, {
9585
- label: "Credentials",
9586
- children: session.apiKeySource
9587
- }) : null
9588
- ]
9589
- }),
9590
- /* @__PURE__ */ jsxs("div", {
9591
- className: "py-2",
9592
- children: [
9593
- state.cwd ? /* @__PURE__ */ jsx(CopyRow, {
9594
- label: "Working directory",
9595
- value: state.cwd
9596
- }) : null,
9597
- state.sdkSessionId ? /* @__PURE__ */ jsx(CopyRow, {
9598
- label: "Engine session id",
9599
- value: state.sdkSessionId
9600
- }) : null,
9601
- session ? /* @__PURE__ */ jsx(CopyRow, {
9602
- label: "Gateway session id",
9603
- value: session.id
9604
- }) : null
9605
- ]
9606
- }),
9607
- /* @__PURE__ */ jsxs("div", {
9608
- className: "py-2",
9609
- children: [
9610
- session?.createdAt ? /* @__PURE__ */ jsx(DialogRow, {
9611
- label: "Started",
9612
- children: formatRelativeTime(session.createdAt)
9613
- }) : null,
9614
- session?.numTurns !== void 0 ? /* @__PURE__ */ jsx(DialogRow, {
9615
- label: "Turns",
9616
- children: session.numTurns
9617
- }) : null,
9618
- /* @__PURE__ */ jsx(DialogRow, {
9619
- label: "Cost",
9620
- mono: true,
9621
- children: formatCost(state.costUsd ?? (state.totalCostUsd || void 0))
9622
- })
9623
- ]
9624
- }),
9625
- state.capabilities.vfs ? /* @__PURE__ */ jsx(SessionFiles, {
9626
- client,
9627
- sessionId,
9628
- open
9629
- }) : null
9630
- ]
9778
+ description: state.session?.title
9779
+ }), /* @__PURE__ */ jsx(DialogBody, { children: /* @__PURE__ */ jsx(SessionInfoPanel, {
9780
+ state,
9781
+ client,
9782
+ sessionId,
9783
+ active: open
9631
9784
  }) })] })
9632
9785
  });
9633
9786
  }
9787
+ function SessionInfoPanel({ state, client, sessionId, active = true }) {
9788
+ const session = state.session;
9789
+ const mode = state.permissionMode ? permissionModeMeta(state.permissionMode) : void 0;
9790
+ return /* @__PURE__ */ jsxs("div", {
9791
+ className: "flex flex-col divide-y divide-border",
9792
+ children: [
9793
+ /* @__PURE__ */ jsxs("div", {
9794
+ className: "pb-2",
9795
+ children: [
9796
+ /* @__PURE__ */ jsx(DialogRow, {
9797
+ label: "Engine",
9798
+ children: state.engine ?? "claude"
9799
+ }),
9800
+ session?.profile ? /* @__PURE__ */ jsx(DialogRow, {
9801
+ label: "Profile",
9802
+ children: session.profile
9803
+ }) : null,
9804
+ state.model ? /* @__PURE__ */ jsx(DialogRow, {
9805
+ label: "Model",
9806
+ mono: true,
9807
+ children: state.model
9808
+ }) : null,
9809
+ mode ? /* @__PURE__ */ jsx(DialogRow, {
9810
+ label: "Permission mode",
9811
+ children: mode.label
9812
+ }) : null,
9813
+ session?.apiKeySource ? /* @__PURE__ */ jsx(DialogRow, {
9814
+ label: "Credentials",
9815
+ children: session.apiKeySource
9816
+ }) : null
9817
+ ]
9818
+ }),
9819
+ /* @__PURE__ */ jsxs("div", {
9820
+ className: "py-2",
9821
+ children: [
9822
+ state.cwd ? /* @__PURE__ */ jsx(CopyRow, {
9823
+ label: "Working directory",
9824
+ value: state.cwd
9825
+ }) : null,
9826
+ state.sdkSessionId ? /* @__PURE__ */ jsx(CopyRow, {
9827
+ label: "Engine session id",
9828
+ value: state.sdkSessionId
9829
+ }) : null,
9830
+ session ? /* @__PURE__ */ jsx(CopyRow, {
9831
+ label: "Gateway session id",
9832
+ value: session.id
9833
+ }) : null
9834
+ ]
9835
+ }),
9836
+ /* @__PURE__ */ jsxs("div", {
9837
+ className: "py-2",
9838
+ children: [
9839
+ session?.createdAt ? /* @__PURE__ */ jsx(DialogRow, {
9840
+ label: "Started",
9841
+ children: formatRelativeTime(session.createdAt)
9842
+ }) : null,
9843
+ session?.numTurns !== void 0 ? /* @__PURE__ */ jsx(DialogRow, {
9844
+ label: "Turns",
9845
+ children: session.numTurns
9846
+ }) : null,
9847
+ /* @__PURE__ */ jsx(DialogRow, {
9848
+ label: "Cost",
9849
+ mono: true,
9850
+ children: formatCost(state.costUsd ?? (state.totalCostUsd || void 0))
9851
+ })
9852
+ ]
9853
+ }),
9854
+ state.capabilities.vfs ? /* @__PURE__ */ jsx(SessionFiles, {
9855
+ client,
9856
+ sessionId,
9857
+ active
9858
+ }) : null
9859
+ ]
9860
+ });
9861
+ }
9634
9862
  function CopyRow({ label, value }) {
9635
9863
  return /* @__PURE__ */ jsxs("div", {
9636
9864
  className: "flex items-start justify-between gap-2 py-1.5",
@@ -9649,28 +9877,8 @@ function CopyRow({ label, value }) {
9649
9877
  })]
9650
9878
  });
9651
9879
  }
9652
- function SessionFiles({ client, sessionId, open }) {
9653
- const [files, setFiles] = useState();
9654
- const [loading, setLoading] = useState(false);
9655
- useEffect(() => {
9656
- if (!open || !sessionId) return;
9657
- let cancelled = false;
9658
- setLoading(true);
9659
- client.listSessionFiles(sessionId).then((list) => {
9660
- if (!cancelled) setFiles(list);
9661
- }).catch(() => {
9662
- if (!cancelled) setFiles([]);
9663
- }).finally(() => {
9664
- if (!cancelled) setLoading(false);
9665
- });
9666
- return () => {
9667
- cancelled = true;
9668
- };
9669
- }, [
9670
- client,
9671
- sessionId,
9672
- open
9673
- ]);
9880
+ function SessionFiles({ client, sessionId, active }) {
9881
+ const { data: files, loading } = useAsync(() => client.listSessionFiles(sessionId), [client, sessionId], { enabled: active && !!sessionId });
9674
9882
  return /* @__PURE__ */ jsxs("div", {
9675
9883
  className: "pt-3",
9676
9884
  children: [/* @__PURE__ */ jsx("h3", {
@@ -10669,13 +10877,13 @@ function blockHeight(block, m) {
10669
10877
  return itemHeight(block.item, m);
10670
10878
  }
10671
10879
  //#endregion
10672
- //#region src/components/terminal/scrubber.tsx
10673
- function nearestMember$1(cluster, y) {
10880
+ //#region src/components/agent/scrubber-marks.ts
10881
+ function nearestMember(cluster, y) {
10674
10882
  let best;
10675
10883
  for (const member of cluster.marks) if (!best || Math.abs(member.y - y) < Math.abs(best.y - y)) best = member;
10676
10884
  return best?.mark;
10677
10885
  }
10678
- const LANE$1 = {
10886
+ const LANE = {
10679
10887
  user: "l",
10680
10888
  subagent: "l",
10681
10889
  shell: "l",
@@ -10687,7 +10895,7 @@ const LANE$1 = {
10687
10895
  recap: "f",
10688
10896
  bookmark: "f"
10689
10897
  };
10690
- const LOUDNESS$1 = {
10898
+ const LOUDNESS = {
10691
10899
  approval: 7,
10692
10900
  error: 6,
10693
10901
  turnFailed: 5,
@@ -10699,7 +10907,7 @@ const LOUDNESS$1 = {
10699
10907
  shell: 1,
10700
10908
  recap: 0
10701
10909
  };
10702
- const KIND_NAME$1 = {
10910
+ const KIND_NAME = {
10703
10911
  user: "you",
10704
10912
  subagent: "sub-agent",
10705
10913
  shell: "shell",
@@ -10711,14 +10919,10 @@ const KIND_NAME$1 = {
10711
10919
  recap: "catch-up boundary",
10712
10920
  bookmark: "bookmark"
10713
10921
  };
10714
- const MIN_MARK$1 = 2;
10715
- function railScale(railH, totalSize, viewportH) {
10716
- return totalSize > 0 ? railH / Math.max(totalSize, viewportH) : 0;
10717
- }
10718
- function doneLine$1(turn) {
10922
+ function doneLine(turn) {
10719
10923
  return `${turn.isError ? turn.subtype : "done"} · ${formatDuration(turn.durationMs)} · ${formatCost(turn.totalCostUsd)}`;
10720
10924
  }
10721
- function excerpt$1(item) {
10925
+ function excerpt(item) {
10722
10926
  switch (item.kind) {
10723
10927
  case "user":
10724
10928
  case "assistant_text":
@@ -10726,31 +10930,35 @@ function excerpt$1(item) {
10726
10930
  case "notice": return item.text;
10727
10931
  case "shell": return item.shell.command;
10728
10932
  case "tool_call": return `${item.name}(${toolInputPreview(item.input)})`;
10729
- case "turn_result": return doneLine$1(item);
10933
+ case "turn_result": return doneLine(item);
10730
10934
  case "file_delivered": return item.path;
10731
10935
  default: return "";
10732
10936
  }
10733
10937
  }
10734
- function buildClusters(props, railH) {
10735
- const { items, bookmarks, frameParentId, recapRow, pendingApprovals, rowIndexFor, offsetOfRow, sizeOfRow, positionInRow, totalSize, viewportH } = props;
10938
+ function toolCallFailed(item) {
10939
+ return item.kind === "tool_call" && (item.status === "failed" || item.result?.isError === true);
10940
+ }
10941
+ function rowOutcomes(items, frameParentId, rowIndexFor) {
10942
+ const outcome = /* @__PURE__ */ new Map();
10943
+ items.forEach((item, index) => {
10944
+ if (item.kind === "tool_call" && parentOf(item) === frameParentId) outcome.set(rowIndexFor(index), index);
10945
+ });
10946
+ return new Set(outcome.values());
10947
+ }
10948
+ function buildMarks(items, { frameParentId, bookmarks = [], recapItemIndex, rowIndexFor = (index) => index } = {}) {
10736
10949
  const marks = [];
10737
10950
  const subagentParents = /* @__PURE__ */ new Set();
10738
10951
  for (const item of items) {
10739
10952
  const parent = parentOf(item);
10740
10953
  if (parent !== void 0) subagentParents.add(parent);
10741
10954
  }
10742
- const rowOutcome = /* @__PURE__ */ new Map();
10743
- items.forEach((item, index) => {
10744
- if (item.kind !== "tool_call" || parentOf(item) !== frameParentId) return;
10745
- rowOutcome.set(rowIndexFor(index), index);
10746
- });
10955
+ const outcomes = rowOutcomes(items, frameParentId, rowIndexFor);
10747
10956
  let segment = {};
10748
10957
  const closeSegment = () => {
10749
10958
  const anchor = segment.response ?? segment.turn;
10750
10959
  if (anchor !== void 0) marks.push({
10751
10960
  kind: segment.failed ? "turnFailed" : "turn",
10752
10961
  itemIndex: anchor,
10753
- rowIndex: rowIndexFor(anchor),
10754
10962
  turnIndex: segment.turn
10755
10963
  });
10756
10964
  segment = {};
@@ -10758,15 +10966,13 @@ function buildClusters(props, railH) {
10758
10966
  items.forEach((item, index) => {
10759
10967
  if (item.kind === "tool_call" && subagentParents.has(item.id)) marks.push({
10760
10968
  kind: "subagent",
10761
- itemIndex: index,
10762
- rowIndex: rowIndexFor(index)
10969
+ itemIndex: index
10763
10970
  });
10764
10971
  if (item.kind === "user" && parentOf(item) === frameParentId) {
10765
10972
  closeSegment();
10766
10973
  marks.push({
10767
10974
  kind: "user",
10768
- itemIndex: index,
10769
- rowIndex: rowIndexFor(index)
10975
+ itemIndex: index
10770
10976
  });
10771
10977
  } else if (item.kind === "turn_result") {
10772
10978
  segment.turn = index;
@@ -10774,25 +10980,21 @@ function buildClusters(props, railH) {
10774
10980
  closeSegment();
10775
10981
  } else if (item.kind === "notice" && item.level === "error") marks.push({
10776
10982
  kind: "error",
10777
- itemIndex: index,
10778
- rowIndex: rowIndexFor(index)
10983
+ itemIndex: index
10779
10984
  });
10780
10985
  else if (item.kind === "shell") marks.push({
10781
10986
  kind: "shell",
10782
- itemIndex: index,
10783
- rowIndex: rowIndexFor(index)
10987
+ itemIndex: index
10784
10988
  });
10785
- else if (item.kind === "tool_call" && (item.status === "failed" || item.result?.isError === true) && rowOutcome.get(rowIndexFor(index)) === index) marks.push({
10989
+ else if (toolCallFailed(item) && outcomes.has(index)) marks.push({
10786
10990
  kind: "toolFailed",
10787
- itemIndex: index,
10788
- rowIndex: rowIndexFor(index)
10991
+ itemIndex: index
10789
10992
  });
10790
10993
  else if (item.kind === "assistant_text" && parentOf(item) === frameParentId) {
10791
10994
  if (frameParentId !== void 0) {
10792
10995
  marks.push({
10793
10996
  kind: "turn",
10794
- itemIndex: index,
10795
- rowIndex: rowIndexFor(index)
10997
+ itemIndex: index
10796
10998
  });
10797
10999
  return;
10798
11000
  }
@@ -10802,27 +11004,30 @@ function buildClusters(props, railH) {
10802
11004
  closeSegment();
10803
11005
  for (const index of bookmarks) if (index >= 0 && index < items.length) marks.push({
10804
11006
  kind: "bookmark",
10805
- itemIndex: index,
10806
- rowIndex: rowIndexFor(index)
11007
+ itemIndex: index
10807
11008
  });
10808
- if (recapRow) marks.push({
11009
+ if (recapItemIndex !== void 0) marks.push({
10809
11010
  kind: "recap",
10810
- itemIndex: -1,
10811
- rowIndex: recapRow.rowIndex
11011
+ itemIndex: recapItemIndex
10812
11012
  });
10813
- const scale = railScale(railH, totalSize, viewportH);
11013
+ return marks;
11014
+ }
11015
+ function proportionalPlacement(railH, itemCount) {
11016
+ const count = Math.max(1, itemCount);
11017
+ const h = Math.max(2, Math.round(railH / count));
11018
+ return (mark) => ({
11019
+ y: Math.min(Math.max(0, railH - h), Math.round(mark.itemIndex / count * railH)),
11020
+ h
11021
+ });
11022
+ }
11023
+ function clusterMarks(marks, place) {
10814
11024
  const lanes = /* @__PURE__ */ new Map();
10815
11025
  for (const mark of marks) {
10816
- const within = mark.itemIndex >= 0 ? positionInRow?.(mark.itemIndex) : void 0;
10817
- const rowH = sizeOfRow(mark.rowIndex);
10818
- const h = within ? MIN_MARK$1 : Math.max(MIN_MARK$1, Math.round(rowH * scale));
10819
- const y = Math.min(Math.max(0, railH - h), Math.round((offsetOfRow(mark.rowIndex) + (within ? within.ordinal / within.count * rowH : 0)) * scale));
10820
- const lane = LANE$1[mark.kind];
11026
+ const lane = LANE[mark.kind];
10821
11027
  const list = lanes.get(lane) ?? [];
10822
11028
  list.push({
10823
11029
  mark,
10824
- y,
10825
- h
11030
+ ...place(mark)
10826
11031
  });
10827
11032
  lanes.set(lane, list);
10828
11033
  }
@@ -10832,7 +11037,7 @@ function buildClusters(props, railH) {
10832
11037
  let current = null;
10833
11038
  for (const { mark, y, h } of list) if (current && y <= current.y + current.h + 1) {
10834
11039
  current.h = Math.max(current.h, y + h - current.y);
10835
- if (LOUDNESS$1[mark.kind] > LOUDNESS$1[current.kind]) current.kind = mark.kind;
11040
+ if (LOUDNESS[mark.kind] > LOUDNESS[current.kind]) current.kind = mark.kind;
10836
11041
  current.marks.push({
10837
11042
  mark,
10838
11043
  y
@@ -10851,74 +11056,130 @@ function buildClusters(props, railH) {
10851
11056
  clusters.push(current);
10852
11057
  }
10853
11058
  }
10854
- if (pendingApprovals.length > 0) clusters.push({
10855
- lane: LANE$1.approval,
11059
+ return clusters;
11060
+ }
11061
+ function approvalCluster(railH) {
11062
+ return {
11063
+ lane: LANE.approval,
10856
11064
  kind: "approval",
10857
- y: Math.max(0, railH - MIN_MARK$1),
10858
- h: MIN_MARK$1,
11065
+ y: Math.max(0, railH - 2),
11066
+ h: 2,
10859
11067
  marks: []
10860
- });
10861
- return clusters;
11068
+ };
10862
11069
  }
10863
- function peekContent$1(cluster, first, { items, pendingApprovals, recapRow }) {
11070
+ //#endregion
11071
+ //#region src/components/agent/scrubber-peek.tsx
11072
+ const CARD_PEEK = {
11073
+ excerptClass: "wd-scrub-ex",
11074
+ muted: "muted",
11075
+ danger: "danger",
11076
+ glyph: "muted",
11077
+ detail: "muted"
11078
+ };
11079
+ const TERMINAL_PEEK = {
11080
+ excerptClass: "term-scrub-ex",
11081
+ muted: "faint",
11082
+ danger: "red",
11083
+ glyph: "dim",
11084
+ text: "fg",
11085
+ strong: "bright",
11086
+ detail: "fg",
11087
+ answerGlyph: "● "
11088
+ };
11089
+ function peekContent({ cluster, first, items, pendingApprovals, recapLabel }, skin) {
10864
11090
  const more = cluster.marks.length > 1 ? ` · ${cluster.marks.length} marks` : "";
10865
- let body = null;
10866
- if (cluster.kind === "approval") {
10867
- const request = pendingApprovals[0];
10868
- body = request ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
10869
- "data-tone": "bright",
10870
- children: request.title ?? "Permission required"
10871
- }), /* @__PURE__ */ jsx("div", {
10872
- className: "term-scrub-ex",
10873
- "data-tone": "fg",
10874
- children: `${request.displayName ?? request.toolName}(${toolInputPreview(request.input)})`
10875
- })] }) : null;
10876
- } else if (cluster.kind === "recap" && !first) body = /* @__PURE__ */ jsxs("div", {
10877
- "data-tone": "faint",
10878
- children: ["※ ", recapRow?.label]
10879
- });
10880
- else if (first) {
10881
- const item = items[first.itemIndex];
10882
- if (first.kind === "recap") body = /* @__PURE__ */ jsxs("div", {
10883
- "data-tone": "faint",
10884
- children: ["※ ", recapRow?.label]
10885
- });
10886
- else if (first.kind === "turn" || first.kind === "turnFailed") {
10887
- const turn = first.turnIndex === void 0 ? void 0 : items[first.turnIndex];
10888
- body = /* @__PURE__ */ jsxs(Fragment$1, { children: [item?.kind === "assistant_text" ? /* @__PURE__ */ jsxs("div", {
10889
- className: "term-scrub-ex",
10890
- "data-tone": "fg",
10891
- children: [/* @__PURE__ */ jsx("span", {
10892
- "data-tone": "dim",
10893
- children: "● "
10894
- }), item.text]
10895
- }) : null, turn?.kind === "turn_result" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
10896
- "data-tone": turn.isError ? "red" : "faint",
10897
- children: doneLine$1(turn)
10898
- }), turn.errors?.map((message, index) => /* @__PURE__ */ jsx("div", {
10899
- "data-tone": "red",
10900
- children: message
10901
- }, index))] }) : null] });
10902
- } else if (item) {
10903
- const failure = first.kind === "toolFailed" && item.kind === "tool_call" ? item.result?.text.split("\n").find((line) => line.trim() !== "") : void 0;
10904
- body = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
10905
- className: "term-scrub-ex",
10906
- "data-tone": first.kind === "error" || first.kind === "toolFailed" ? "red" : "fg",
10907
- children: [first.kind === "user" ? /* @__PURE__ */ jsx("span", {
10908
- "data-tone": "dim",
10909
- children: "❯ "
10910
- }) : null, excerpt$1(item)]
10911
- }), failure ? /* @__PURE__ */ jsx("div", {
10912
- className: "term-scrub-ex",
10913
- "data-tone": "red",
10914
- children: failure
10915
- }) : null] });
10916
- }
11091
+ const kind = first?.kind ?? cluster.kind;
11092
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
11093
+ "data-tone": skin.muted,
11094
+ children: [KIND_NAME[kind], more]
11095
+ }), cluster.kind === "approval" ? approvalBody(pendingApprovals[0], skin) : kind === "recap" ? recapBody(recapLabel, skin) : markBody(first, items, skin)] });
11096
+ }
11097
+ function approvalBody(request, skin) {
11098
+ if (!request) return null;
11099
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
11100
+ "data-tone": skin.strong,
11101
+ children: permissionPromptModel(request).heading
11102
+ }), /* @__PURE__ */ jsx("div", {
11103
+ className: skin.excerptClass,
11104
+ "data-tone": skin.detail,
11105
+ children: `${request.displayName ?? request.toolName}(${toolInputPreview(request.input)})`
11106
+ })] });
11107
+ }
11108
+ function recapBody(label, skin) {
11109
+ return label === void 0 ? null : /* @__PURE__ */ jsxs("div", {
11110
+ "data-tone": skin.muted,
11111
+ children: ["※ ", label]
11112
+ });
11113
+ }
11114
+ function markBody(first, items, skin) {
11115
+ if (!first) return null;
11116
+ const item = items[first.itemIndex];
11117
+ if (first.kind === "turn" || first.kind === "turnFailed") {
11118
+ const turn = first.turnIndex === void 0 ? void 0 : items[first.turnIndex];
11119
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [item?.kind === "assistant_text" ? /* @__PURE__ */ jsxs("div", {
11120
+ className: skin.excerptClass,
11121
+ "data-tone": skin.text,
11122
+ children: [skin.answerGlyph ? /* @__PURE__ */ jsx("span", {
11123
+ "data-tone": skin.glyph,
11124
+ children: skin.answerGlyph
11125
+ }) : null, item.text]
11126
+ }) : null, turn?.kind === "turn_result" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
11127
+ "data-tone": turn.isError ? skin.danger : skin.muted,
11128
+ children: doneLine(turn)
11129
+ }), turn.errors?.map((message, index) => /* @__PURE__ */ jsx("div", {
11130
+ "data-tone": skin.danger,
11131
+ children: message
11132
+ }, index))] }) : null] });
10917
11133
  }
11134
+ if (!item) return null;
11135
+ const failure = first.kind === "toolFailed" && item.kind === "tool_call" ? item.result?.text.split("\n").find((line) => line.trim() !== "") : void 0;
10918
11136
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
10919
- "data-tone": "faint",
10920
- children: [KIND_NAME$1[first?.kind ?? cluster.kind], more]
10921
- }), body] });
11137
+ className: skin.excerptClass,
11138
+ "data-tone": first.kind === "error" || first.kind === "toolFailed" ? skin.danger : skin.text,
11139
+ children: [first.kind === "user" ? /* @__PURE__ */ jsx("span", {
11140
+ "data-tone": skin.glyph,
11141
+ children: "❯ "
11142
+ }) : null, excerpt(item)]
11143
+ }), failure ? /* @__PURE__ */ jsx("div", {
11144
+ className: skin.excerptClass,
11145
+ "data-tone": skin.danger,
11146
+ children: failure
11147
+ }) : null] });
11148
+ }
11149
+ //#endregion
11150
+ //#region src/components/terminal/scrubber.tsx
11151
+ function railScale(railH, totalSize, viewportH) {
11152
+ return totalSize > 0 ? railH / Math.max(totalSize, viewportH) : 0;
11153
+ }
11154
+ function buildClusters(props, railH) {
11155
+ const { items, bookmarks, frameParentId, recapRow, pendingApprovals, rowIndexFor, offsetOfRow, sizeOfRow, positionInRow, totalSize, viewportH } = props;
11156
+ const marks = buildMarks(items, {
11157
+ frameParentId,
11158
+ bookmarks,
11159
+ rowIndexFor
11160
+ }).map((mark) => ({
11161
+ ...mark,
11162
+ rowIndex: rowIndexFor(mark.itemIndex)
11163
+ }));
11164
+ if (recapRow) marks.push({
11165
+ kind: "recap",
11166
+ itemIndex: -1,
11167
+ rowIndex: recapRow.rowIndex
11168
+ });
11169
+ const scale = railScale(railH, totalSize, viewportH);
11170
+ const clusters = clusterMarks(marks, (mark) => {
11171
+ const rowIndex = mark.rowIndex ?? rowIndexFor(mark.itemIndex);
11172
+ const within = mark.itemIndex >= 0 ? positionInRow?.(mark.itemIndex) : void 0;
11173
+ const rowH = sizeOfRow(rowIndex);
11174
+ const h = within ? 2 : Math.max(2, Math.round(rowH * scale));
11175
+ const offset = offsetOfRow(rowIndex) + (within ? within.ordinal / within.count * rowH : 0);
11176
+ return {
11177
+ y: Math.min(Math.max(0, railH - h), Math.round(offset * scale)),
11178
+ h
11179
+ };
11180
+ });
11181
+ if (pendingApprovals.length > 0) clusters.push(approvalCluster(railH));
11182
+ return clusters;
10922
11183
  }
10923
11184
  function TerminalScrubber(props) {
10924
11185
  const { scrollOffset, viewportH, totalSize, onJumpToRow, interactive, fontSize, lineHeight } = props;
@@ -10993,12 +11254,12 @@ function TerminalScrubber(props) {
10993
11254
  stick.scrollToBottom();
10994
11255
  return;
10995
11256
  }
10996
- const mark = nearestMember$1(cluster, railY(clientY));
10997
- if (mark) onJumpToRow(mark.rowIndex);
11257
+ const mark = nearestMember(cluster, railY(clientY));
11258
+ if (mark) onJumpToRow(mark.rowIndex ?? props.rowIndexFor(mark.itemIndex));
10998
11259
  };
10999
11260
  const showPeek = (cluster, clientY) => {
11000
11261
  const y = railY(clientY);
11001
- const mark = nearestMember$1(cluster, y);
11262
+ const mark = nearestMember(cluster, y);
11002
11263
  setPeek((previous) => previous && previous.cluster === cluster && previous.mark === mark ? previous : {
11003
11264
  cluster,
11004
11265
  mark,
@@ -11070,243 +11331,29 @@ function TerminalScrubber(props) {
11070
11331
  ...interactive ? {
11071
11332
  onPointerEnter: (event) => showPeek(cluster, event.clientY),
11072
11333
  onPointerMove: (event) => {
11073
- if (!drag.current) showPeek(cluster, event.clientY);
11074
- },
11075
- onPointerLeave: () => setPeek(null)
11076
- } : null
11077
- }, index)),
11078
- peek ? /* @__PURE__ */ jsx("div", {
11079
- ref: peekRef,
11080
- className: "term-scrub-peek",
11081
- style: { top: peek.y },
11082
- children: peekContent$1(peek.cluster, peek.mark, props)
11083
- }) : null
11084
- ]
11085
- })
11086
- });
11087
- }
11088
- //#endregion
11089
- //#region src/components/agent/scrubber-marks.ts
11090
- function nearestMember(cluster, y) {
11091
- let best;
11092
- for (const member of cluster.marks) if (!best || Math.abs(member.y - y) < Math.abs(best.y - y)) best = member;
11093
- return best?.mark;
11094
- }
11095
- const LANE = {
11096
- user: "l",
11097
- subagent: "l",
11098
- shell: "l",
11099
- turn: "r",
11100
- turnFailed: "r",
11101
- toolFailed: "r",
11102
- error: "r",
11103
- approval: "f",
11104
- recap: "f",
11105
- bookmark: "f"
11106
- };
11107
- const LOUDNESS = {
11108
- approval: 7,
11109
- error: 6,
11110
- turnFailed: 5,
11111
- toolFailed: 4,
11112
- user: 3,
11113
- turn: 2,
11114
- bookmark: 1,
11115
- subagent: 1,
11116
- shell: 1,
11117
- recap: 0
11118
- };
11119
- const KIND_NAME = {
11120
- user: "you",
11121
- subagent: "sub-agent",
11122
- shell: "shell",
11123
- turn: "response · turn end",
11124
- turnFailed: "turn failed",
11125
- toolFailed: "tool failed",
11126
- error: "error",
11127
- approval: "pending approval",
11128
- recap: "catch-up boundary",
11129
- bookmark: "bookmark"
11130
- };
11131
- function doneLine(turn) {
11132
- return `${turn.isError ? turn.subtype : "done"} · ${formatDuration(turn.durationMs)} · ${formatCost(turn.totalCostUsd)}`;
11133
- }
11134
- function excerpt(item) {
11135
- switch (item.kind) {
11136
- case "user":
11137
- case "assistant_text":
11138
- case "thinking":
11139
- case "notice": return item.text;
11140
- case "shell": return item.shell.command;
11141
- case "tool_call": return `${item.name}(${toolInputPreview(item.input)})`;
11142
- case "turn_result": return doneLine(item);
11143
- case "file_delivered": return item.path;
11144
- default: return "";
11145
- }
11146
- }
11147
- function buildMarks(items, { frameParentId, bookmarks = [], recapItemIndex } = {}) {
11148
- const marks = [];
11149
- const subagentParents = /* @__PURE__ */ new Set();
11150
- for (const item of items) {
11151
- const parent = parentOf(item);
11152
- if (parent !== void 0) subagentParents.add(parent);
11153
- }
11154
- let segment = {};
11155
- const closeSegment = () => {
11156
- const anchor = segment.response ?? segment.turn;
11157
- if (anchor !== void 0) marks.push({
11158
- kind: segment.failed ? "turnFailed" : "turn",
11159
- itemIndex: anchor,
11160
- turnIndex: segment.turn
11161
- });
11162
- segment = {};
11163
- };
11164
- items.forEach((item, index) => {
11165
- if (item.kind === "tool_call" && subagentParents.has(item.id)) marks.push({
11166
- kind: "subagent",
11167
- itemIndex: index
11168
- });
11169
- if (item.kind === "user" && parentOf(item) === frameParentId) {
11170
- closeSegment();
11171
- marks.push({
11172
- kind: "user",
11173
- itemIndex: index
11174
- });
11175
- } else if (item.kind === "turn_result") {
11176
- segment.turn = index;
11177
- segment.failed = item.isError;
11178
- closeSegment();
11179
- } else if (item.kind === "notice" && item.level === "error") marks.push({
11180
- kind: "error",
11181
- itemIndex: index
11182
- });
11183
- else if (item.kind === "shell") marks.push({
11184
- kind: "shell",
11185
- itemIndex: index
11186
- });
11187
- else if (item.kind === "tool_call" && parentOf(item) === frameParentId && (item.status === "failed" || item.result?.isError === true)) marks.push({
11188
- kind: "toolFailed",
11189
- itemIndex: index
11190
- });
11191
- else if (item.kind === "assistant_text" && parentOf(item) === frameParentId) {
11192
- if (frameParentId !== void 0) {
11193
- marks.push({
11194
- kind: "turn",
11195
- itemIndex: index
11196
- });
11197
- return;
11198
- }
11199
- segment.response = index;
11200
- }
11201
- });
11202
- closeSegment();
11203
- for (const index of bookmarks) if (index >= 0 && index < items.length) marks.push({
11204
- kind: "bookmark",
11205
- itemIndex: index
11206
- });
11207
- if (recapItemIndex !== void 0) marks.push({
11208
- kind: "recap",
11209
- itemIndex: recapItemIndex
11334
+ if (!drag.current) showPeek(cluster, event.clientY);
11335
+ },
11336
+ onPointerLeave: () => setPeek(null)
11337
+ } : null
11338
+ }, index)),
11339
+ peek ? /* @__PURE__ */ jsx("div", {
11340
+ ref: peekRef,
11341
+ className: "term-scrub-peek",
11342
+ style: { top: peek.y },
11343
+ children: peekContent({
11344
+ cluster: peek.cluster,
11345
+ first: peek.mark,
11346
+ items: props.items,
11347
+ pendingApprovals: props.pendingApprovals,
11348
+ recapLabel: props.recapRow?.label
11349
+ }, TERMINAL_PEEK)
11350
+ }) : null
11351
+ ]
11352
+ })
11210
11353
  });
11211
- return marks;
11212
- }
11213
- function clusterMarks(marks, railH, itemCount) {
11214
- const count = Math.max(1, itemCount);
11215
- const h = Math.max(2, Math.round(railH / count));
11216
- const lanes = /* @__PURE__ */ new Map();
11217
- for (const mark of marks) {
11218
- const y = Math.min(Math.max(0, railH - h), Math.round(mark.itemIndex / count * railH));
11219
- const lane = LANE[mark.kind];
11220
- const list = lanes.get(lane) ?? [];
11221
- list.push({
11222
- mark,
11223
- y
11224
- });
11225
- lanes.set(lane, list);
11226
- }
11227
- const clusters = [];
11228
- for (const [lane, list] of lanes) {
11229
- list.sort((a, b) => a.y - b.y);
11230
- let current = null;
11231
- for (const { mark, y } of list) if (current && y <= current.y + current.h + 1) {
11232
- current.h = Math.max(current.h, y + h - current.y);
11233
- if (LOUDNESS[mark.kind] > LOUDNESS[current.kind]) current.kind = mark.kind;
11234
- current.marks.push({
11235
- mark,
11236
- y
11237
- });
11238
- } else {
11239
- current = {
11240
- lane,
11241
- kind: mark.kind,
11242
- y,
11243
- h,
11244
- marks: [{
11245
- mark,
11246
- y
11247
- }]
11248
- };
11249
- clusters.push(current);
11250
- }
11251
- }
11252
- return clusters;
11253
- }
11254
- function approvalCluster(railH) {
11255
- return {
11256
- lane: LANE.approval,
11257
- kind: "approval",
11258
- y: Math.max(0, railH - 2),
11259
- h: 2,
11260
- marks: []
11261
- };
11262
11354
  }
11263
11355
  //#endregion
11264
11356
  //#region src/components/agent/Scrubber.tsx
11265
- function peekContent(cluster, first, items, pendingApprovals) {
11266
- const more = cluster.marks.length > 1 ? ` · ${cluster.marks.length} marks` : "";
11267
- let body = null;
11268
- if (cluster.kind === "approval") {
11269
- const request = pendingApprovals[0];
11270
- body = request ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", { children: request.title ?? "Permission required" }), /* @__PURE__ */ jsx("div", {
11271
- className: "wd-scrub-ex",
11272
- "data-tone": "muted",
11273
- children: `${request.displayName ?? request.toolName}(${toolInputPreview(request.input)})`
11274
- })] }) : null;
11275
- } else if (first && first.kind !== "recap") {
11276
- const item = items[first.itemIndex];
11277
- if (first.kind === "turn" || first.kind === "turnFailed") {
11278
- const turn = first.turnIndex === void 0 ? void 0 : items[first.turnIndex];
11279
- body = /* @__PURE__ */ jsxs(Fragment$1, { children: [item?.kind === "assistant_text" ? /* @__PURE__ */ jsx("div", {
11280
- className: "wd-scrub-ex",
11281
- children: item.text
11282
- }) : null, turn?.kind === "turn_result" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
11283
- "data-tone": turn.isError ? "danger" : "muted",
11284
- children: doneLine(turn)
11285
- }), turn.errors?.map((message, index) => /* @__PURE__ */ jsx("div", {
11286
- "data-tone": "danger",
11287
- children: message
11288
- }, index))] }) : null] });
11289
- } else if (item) {
11290
- const failure = first.kind === "toolFailed" && item.kind === "tool_call" ? item.result?.text.split("\n").find((line) => line.trim() !== "") : void 0;
11291
- body = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
11292
- className: "wd-scrub-ex",
11293
- "data-tone": first.kind === "error" || first.kind === "toolFailed" ? "danger" : void 0,
11294
- children: [first.kind === "user" ? /* @__PURE__ */ jsx("span", {
11295
- "data-tone": "muted",
11296
- children: "❯ "
11297
- }) : null, excerpt(item)]
11298
- }), failure ? /* @__PURE__ */ jsx("div", {
11299
- className: "wd-scrub-ex",
11300
- "data-tone": "danger",
11301
- children: failure
11302
- }) : null] });
11303
- }
11304
- }
11305
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
11306
- "data-tone": "muted",
11307
- children: [KIND_NAME[first?.kind ?? cluster.kind], more]
11308
- }), body] });
11309
- }
11310
11357
  function Scrubber({ items, pendingApprovals, recapItemIndex, bookmarks, frameParentId, interactive = false, onJumpToItem, className }) {
11311
11358
  const railRef = useRef(null);
11312
11359
  const peekRef = useRef(null);
@@ -11336,7 +11383,7 @@ function Scrubber({ items, pendingApprovals, recapItemIndex, bookmarks, framePar
11336
11383
  frameParentId,
11337
11384
  bookmarks,
11338
11385
  recapItemIndex
11339
- }), railH, items.length);
11386
+ }), proportionalPlacement(railH, items.length));
11340
11387
  if (pendingApprovals.length > 0) built.push(approvalCluster(railH));
11341
11388
  return built;
11342
11389
  }, [
@@ -11388,7 +11435,12 @@ function Scrubber({ items, pendingApprovals, recapItemIndex, bookmarks, framePar
11388
11435
  ref: peekRef,
11389
11436
  className: "wd-scrub-peek",
11390
11437
  style: { top: peek.y },
11391
- children: peekContent(peek.cluster, peek.mark, items, pendingApprovals)
11438
+ children: peekContent({
11439
+ cluster: peek.cluster,
11440
+ first: peek.mark,
11441
+ items,
11442
+ pendingApprovals
11443
+ }, CARD_PEEK)
11392
11444
  }) : null]
11393
11445
  });
11394
11446
  }
@@ -11694,53 +11746,6 @@ function TaskRow({ block, fileUrl, onOpenSubagent }) {
11694
11746
  children: row
11695
11747
  });
11696
11748
  }
11697
- function useRunStart$1(status) {
11698
- const running = status === "running" || status === "starting";
11699
- const [startedAt, setStartedAt] = useState(void 0);
11700
- useEffect(() => {
11701
- setStartedAt((previous) => running ? previous ?? Date.now() : void 0);
11702
- }, [running]);
11703
- return running ? startedAt : void 0;
11704
- }
11705
- function working(state) {
11706
- if (state.status !== "running" && state.status !== "starting") return false;
11707
- const last = state.items.at(-1);
11708
- if (!last) return true;
11709
- if (last.kind === "assistant_text" && last.streaming) return false;
11710
- if (last.kind === "thinking" && last.id === "streaming-thinking") return false;
11711
- return true;
11712
- }
11713
- function TerminalTranscript({ state, fileUrl, fontSize, lineHeight, affordances, className }) {
11714
- const runStartedAt = useRunStart$1(state.status);
11715
- const blocks = useMemo(() => terminalBlocks(state.items), [state.items]);
11716
- return /* @__PURE__ */ jsx(TerminalSurface, {
11717
- fontSize,
11718
- lineHeight,
11719
- affordances,
11720
- bleed: "1ch",
11721
- className: cn("term-transcript", className),
11722
- children: /* @__PURE__ */ jsxs(PeerNamesProvider, {
11723
- items: state.items,
11724
- children: [blocks.map((block, index) => {
11725
- const next = blocks[index + 1];
11726
- return /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(blocks[index - 1], block) ? /* @__PURE__ */ jsx(Blank, {}) : null, /* @__PURE__ */ jsx(ActionPlacementProvider, {
11727
- value: next && !blockNeedsBlank(block, next) ? "inline" : "below",
11728
- children: "run" in block ? /* @__PURE__ */ jsx(RunRow, { items: block.run }) : "item" in block ? /* @__PURE__ */ jsx(TerminalItemView, {
11729
- item: block.item,
11730
- fileUrl
11731
- }) : /* @__PURE__ */ jsx(TaskRow, {
11732
- block,
11733
- fileUrl
11734
- })
11735
- })] }, block.key);
11736
- }), working(state) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx(Blank, {}) : null, /* @__PURE__ */ jsx(WorkingRow, {
11737
- label: state.status === "starting" ? "Starting…" : "Working…",
11738
- startedAt: runStartedAt,
11739
- tokens: state.contextUsage?.totalTokens
11740
- })] }) : null]
11741
- })
11742
- });
11743
- }
11744
11749
  //#endregion
11745
11750
  //#region src/components/agent/FileCard.tsx
11746
11751
  function FileCard({ item, href, className }) {
@@ -12188,34 +12193,13 @@ function ToolCard({ item, hostImage }) {
12188
12193
  function ShellCard({ item }) {
12189
12194
  const [open, setOpen] = useState(false);
12190
12195
  const actions = useShellActions();
12191
- const running = item.shell.status === "running";
12192
12196
  const failed = shellFailed(item);
12193
12197
  const lines = shellBodyLines(item, open);
12194
12198
  const footer = shellFooterText(item, open, lines.length);
12195
12199
  const shellId = item.shell.id;
12196
- const verify = actions.verify;
12197
- useEffect(() => {
12198
- if (running) verify(shellId);
12199
- }, [
12200
- running,
12201
- shellId,
12202
- verify
12203
- ]);
12200
+ useVerifyRunning(item);
12204
12201
  return /* @__PURE__ */ jsx(WithActions, {
12205
- actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [
12206
- actions.open ? /* @__PURE__ */ jsx(OpenShellAction, { onOpen: () => actions.open?.(shellId) }) : null,
12207
- actions.agentWrite && shellGrantable(item.shell) ? /* @__PURE__ */ jsx(AgentWriteAction, {
12208
- granted: item.shell.agentWrite === true,
12209
- label: shellAgentWriteLabel(item.shell),
12210
- onToggle: () => void actions.agentWrite?.(shellId, item.shell.agentWrite !== true)
12211
- }) : null,
12212
- running ? /* @__PURE__ */ jsx(KillShellAction, { onKill: () => void actions.kill(shellId) }) : null,
12213
- /* @__PURE__ */ jsx(BookmarkAction, { id: item.id }),
12214
- /* @__PURE__ */ jsx(CopyAction, {
12215
- text: item.shell.command,
12216
- label: "Copy command"
12217
- })
12218
- ] }),
12202
+ actions: /* @__PURE__ */ jsx(ShellItemActions, { item }),
12219
12203
  children: /* @__PURE__ */ jsxs("div", {
12220
12204
  "data-slot": "shell",
12221
12205
  className: "overflow-hidden rounded-md border border-border bg-surface",
@@ -12781,6 +12765,12 @@ function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage,
12781
12765
  })
12782
12766
  });
12783
12767
  }
12768
+ function TerminalTranscript(props) {
12769
+ return /* @__PURE__ */ jsx(Transcript, {
12770
+ ...props,
12771
+ variant: "terminal"
12772
+ });
12773
+ }
12784
12774
  //#endregion
12785
12775
  //#region src/lib/session-cost.ts
12786
12776
  const GAP_SHARE = .02;
@@ -13080,20 +13070,6 @@ function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, costU
13080
13070
  }
13081
13071
  //#endregion
13082
13072
  //#region src/components/agent/SessionPanel.tsx
13083
- function PromptSurface({ terminal, metrics, affordances, children }) {
13084
- if (!terminal) return /* @__PURE__ */ jsx("div", {
13085
- className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] flex-col gap-2",
13086
- children
13087
- });
13088
- return /* @__PURE__ */ jsx(TerminalSurface, {
13089
- fontSize: metrics?.fontSize,
13090
- lineHeight: metrics?.lineHeight,
13091
- affordances,
13092
- bleed: "1ch",
13093
- className: "term-transcript",
13094
- children
13095
- });
13096
- }
13097
13073
  const INTERACTIVE = [
13098
13074
  "button",
13099
13075
  "a",
@@ -13109,17 +13085,49 @@ const INTERACTIVE = [
13109
13085
  "[role=\"radio\"]",
13110
13086
  "[role=\"tab\"]"
13111
13087
  ].join(",");
13088
+ const PANEL_ITEMS = [
13089
+ {
13090
+ panel: "context",
13091
+ label: "Context",
13092
+ Icon: ChartPie
13093
+ },
13094
+ {
13095
+ panel: "usage",
13096
+ label: "Usage",
13097
+ Icon: Gauge
13098
+ },
13099
+ {
13100
+ panel: "info",
13101
+ label: "Session info",
13102
+ Icon: Info
13103
+ },
13104
+ {
13105
+ panel: "mcp",
13106
+ label: "MCP servers",
13107
+ Icon: Plug
13108
+ },
13109
+ {
13110
+ panel: "skills",
13111
+ label: "Skills",
13112
+ Icon: Sparkles
13113
+ },
13114
+ {
13115
+ panel: "files",
13116
+ label: "Project files",
13117
+ Icon: FolderTree
13118
+ }
13119
+ ];
13112
13120
  function SessionPanel({ client, sessionId, header, panelSurface = "internal", statusSurface = "internal", statusPlacement = "top", onOpenPanel, onVitals, transcriptVariant = "cards", transcriptFont = "sans", affordances, terminalMetrics, scrubber = false, bookmarks, peerMentions = true, onToggleBookmark, reveal, subagents, shells, openSubagent, onSubagentChange, openShell, onShellChange, stickyPrompt = false, controlsSurface = "internal", onControls, focusComposerOnClick = false, unseen, readOnly = false, approvalPrompts, toolHost, clientTools, cacheTranscript, emptyState, onLinkClick, onOpenFile, fontSize, className }) {
13113
- const effectiveTermFontSize = terminalMetrics?.fontSize ?? fontSize;
13114
- const effectiveTermLineHeight = terminalMetrics?.lineHeight ?? (fontSize !== void 0 ? Math.round(fontSize * (18 / 13)) : void 0);
13121
+ const cell = {
13122
+ fontSize: terminalMetrics?.fontSize ?? fontSize,
13123
+ lineHeight: terminalMetrics?.lineHeight ?? (fontSize !== void 0 ? Math.round(fontSize * (18 / 13)) : void 0)
13124
+ };
13115
13125
  const external = panelSurface === "external";
13116
13126
  const statusExternal = statusSurface === "external";
13117
13127
  const controlsInStatus = controlsSurface === "status" && !statusExternal;
13118
13128
  const controlsExternal = controlsSurface === "external" || controlsInStatus;
13119
13129
  const [protocolError, setProtocolError] = useState(void 0);
13120
13130
  const [panel, setPanel] = useState();
13121
- const [showCompletedTasks, setShowCompletedTasks] = useState(false);
13122
- const [taskReveal, setTaskReveal] = useState();
13123
13131
  const { state, connection, replaying, protocolMismatch, shell, pricing, models, effectiveModel, handle, send, runShell, approve, deny, interrupt, clearContext, setModel, setPermissionMode, reconnectNow, loadFullResult, loadShellOutput, verifyShell, killShell, setShellAgentWrite } = useClaudeSession(client, sessionId, {
13124
13132
  onProtocolError: setProtocolError,
13125
13133
  cacheTranscript
@@ -13160,12 +13168,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
13160
13168
  if (returnReveal && shellReturnReveal) return returnReveal.nonce >= shellReturnReveal.nonce ? returnReveal : shellReturnReveal;
13161
13169
  return returnReveal ?? shellReturnReveal;
13162
13170
  }, [returnReveal, shellReturnReveal]);
13163
- const [caughtUp, setCaughtUp] = useState(false);
13164
- useEffect(() => {
13165
- setCaughtUp(false);
13166
- }, [sessionId]);
13167
- const [catchUpMark] = useState(unseen);
13168
- const catchUp = caughtUp ? void 0 : catchUpMark;
13171
+ const { mark: catchUp, dismiss: dismissCatchUp } = useCatchUp(sessionId, unseen);
13169
13172
  const newCount = catchUp ? Math.max(0, state.items.length - catchUp.itemCount) : 0;
13170
13173
  const openPanel = useCallback((target) => {
13171
13174
  if (external) onOpenPanel?.(target);
@@ -13209,70 +13212,17 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
13209
13212
  subagents,
13210
13213
  state.session?.subagents
13211
13214
  ]);
13212
- const clientCommands = useMemo(() => {
13213
- const modes = capabilities.permissionModes;
13214
- const built = [];
13215
- if (models.length > 0) built.push({
13216
- name: "model",
13217
- description: "Switch the model for this session",
13218
- argumentHint: "<model>",
13219
- requiresArgs: true,
13220
- run: (args) => {
13221
- const wanted = args.split(/\s+/)[0];
13222
- if (!wanted) return false;
13223
- setModel(wanted);
13224
- return true;
13225
- }
13226
- });
13227
- if (modes.length > 1) built.push({
13228
- name: "permissions",
13229
- description: `Set the permission mode (${modes.join(", ")})`,
13230
- argumentHint: "<mode>",
13231
- requiresArgs: true,
13232
- run: (args) => {
13233
- const wanted = args.split(/\s+/)[0];
13234
- if (!modes.includes(wanted)) return false;
13235
- setPermissionMode(wanted);
13236
- return true;
13237
- }
13238
- });
13239
- if (capabilities.clearContext) built.push({
13240
- name: "clear",
13241
- description: "Clear the conversation - the session keeps running and the old one stays resumable",
13242
- run: () => {
13243
- clearContext();
13244
- return true;
13245
- }
13246
- });
13247
- if (capabilities.mcpStatus) built.push({
13248
- name: "mcp",
13249
- description: "MCP servers and their status",
13250
- run: () => (openPanel("mcp"), true)
13251
- });
13252
- if (capabilities.contextUsage) built.push({
13253
- name: "context",
13254
- description: "Context window usage",
13255
- run: () => (openPanel("context"), true)
13256
- });
13257
- built.push({
13258
- name: "status",
13259
- description: "Session details",
13260
- run: () => (openPanel("info"), true)
13261
- });
13262
- if (capabilities.rateLimits) built.push({
13263
- name: "usage",
13264
- description: "Rate limits and spend",
13265
- run: () => (openPanel("usage"), true)
13266
- });
13267
- if (capabilities.skillsList) built.push({
13268
- name: "skills",
13269
- description: "Browse the skills this session can use",
13270
- run: () => (openPanel("skills"), true)
13271
- });
13272
- return built;
13273
- }, [
13215
+ const hasModels = models.length > 0;
13216
+ const clientCommands = useMemo(() => buildClientCommands({
13217
+ capabilities,
13218
+ hasModels,
13219
+ setModel,
13220
+ setPermissionMode,
13221
+ clearContext,
13222
+ openPanel
13223
+ }), [
13274
13224
  capabilities,
13275
- models.length,
13225
+ hasModels,
13276
13226
  openPanel,
13277
13227
  clearContext,
13278
13228
  setModel,
@@ -13293,45 +13243,31 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
13293
13243
  onVitalsRef.current = onVitals;
13294
13244
  const vitalsModel = effectiveModel ?? state.model;
13295
13245
  const permissionModes = useMemo(() => permissionModeChoices(capabilities.permissionModes, state.session?.canBypassPermissions), [capabilities.permissionModes, state.session?.canBypassPermissions]);
13296
- useEffect(() => {
13297
- onVitalsRef.current?.({
13298
- status: state.status,
13299
- connection,
13300
- engine: state.engine,
13301
- capabilities: state.capabilities,
13302
- model: vitalsModel,
13303
- models,
13304
- permissionMode: state.permissionMode,
13305
- permissionModes,
13306
- skills: state.skills,
13307
- composerCommands,
13308
- cwd: state.cwd,
13309
- contextUsage: state.contextUsage,
13310
- tasks,
13311
- rateLimits,
13312
- rateLimitsUpdatedAt: usageUpdatedAt,
13313
- itemCount: state.items.length,
13314
- totalCostUsd: state.totalCostUsd
13315
- });
13316
- }, [
13317
- state.status,
13246
+ const vitals = {
13247
+ status: state.status,
13318
13248
  connection,
13319
- state.engine,
13320
- state.capabilities,
13321
- vitalsModel,
13249
+ engine: state.engine,
13250
+ capabilities: state.capabilities,
13251
+ model: vitalsModel,
13322
13252
  models,
13323
- state.permissionMode,
13253
+ permissionMode: state.permissionMode,
13324
13254
  permissionModes,
13325
- state.skills,
13255
+ skills: state.skills,
13326
13256
  composerCommands,
13327
- state.cwd,
13328
- state.contextUsage,
13257
+ cwd: state.cwd,
13258
+ contextUsage: state.contextUsage,
13329
13259
  tasks,
13330
13260
  rateLimits,
13331
- usageUpdatedAt,
13332
- state.items.length,
13333
- state.totalCostUsd
13334
- ]);
13261
+ rateLimitsUpdatedAt: usageUpdatedAt,
13262
+ itemCount: state.items.length,
13263
+ totalCostUsd: state.totalCostUsd
13264
+ };
13265
+ const lastVitals = useRef(void 0);
13266
+ useEffect(() => {
13267
+ if (lastVitals.current && sameVitals(lastVitals.current, vitals)) return;
13268
+ lastVitals.current = vitals;
13269
+ onVitalsRef.current?.(vitals);
13270
+ });
13335
13271
  const onControlsRef = useRef(onControls);
13336
13272
  onControlsRef.current = onControls;
13337
13273
  const setters = useRef({
@@ -13385,42 +13321,36 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
13385
13321
  const local = matchClientCommand(text, clientCommands);
13386
13322
  if (local && local.command.run(local.args)) return;
13387
13323
  }
13388
- setCaughtUp(true);
13324
+ dismissCatchUp();
13389
13325
  repinTranscript.current?.();
13390
13326
  send(text, attachmentIds);
13391
13327
  };
13328
+ const menuShows = {
13329
+ context: capabilities.contextUsage,
13330
+ usage: capabilities.rateLimits,
13331
+ info: true,
13332
+ mcp: capabilities.mcpStatus,
13333
+ skills: capabilities.skillsList,
13334
+ files: hostFiles.available
13335
+ };
13392
13336
  const actionsMenu = /* @__PURE__ */ jsxs(Menu$1, { children: [/* @__PURE__ */ jsx(MenuTrigger, { render: /* @__PURE__ */ jsx(Button, {
13393
13337
  variant: "ghost",
13394
13338
  size: "icon-sm",
13395
13339
  "aria-label": "Session actions",
13396
13340
  children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-4" })
13397
- }) }), /* @__PURE__ */ jsxs(MenuContent, { children: [
13398
- capabilities.contextUsage ? /* @__PURE__ */ jsxs(MenuItem, {
13399
- onClick: () => openPanel("context"),
13400
- children: [/* @__PURE__ */ jsx(ChartPie, { className: "size-3.5 text-fg-3" }), " Context"]
13401
- }) : null,
13402
- capabilities.rateLimits ? /* @__PURE__ */ jsxs(MenuItem, {
13403
- onClick: () => openPanel("usage"),
13404
- children: [/* @__PURE__ */ jsx(Gauge, { className: "size-3.5 text-fg-3" }), " Usage"]
13405
- }) : null,
13406
- /* @__PURE__ */ jsxs(MenuItem, {
13407
- onClick: () => openPanel("info"),
13408
- children: [/* @__PURE__ */ jsx(Info, { className: "size-3.5 text-fg-3" }), " Session info"]
13409
- }),
13410
- capabilities.mcpStatus ? /* @__PURE__ */ jsxs(MenuItem, {
13411
- onClick: () => openPanel("mcp"),
13412
- children: [/* @__PURE__ */ jsx(Plug, { className: "size-3.5 text-fg-3" }), " MCP servers"]
13413
- }) : null,
13414
- capabilities.skillsList ? /* @__PURE__ */ jsxs(MenuItem, {
13415
- onClick: () => openPanel("skills"),
13416
- children: [/* @__PURE__ */ jsx(Sparkles, { className: "size-3.5 text-fg-3" }), " Skills"]
13417
- }) : null,
13418
- hostFiles.available ? /* @__PURE__ */ jsxs(MenuItem, {
13419
- onClick: () => openPanel("files"),
13420
- children: [/* @__PURE__ */ jsx(FolderTree, { className: "size-3.5 text-fg-3" }), " Project files"]
13421
- }) : null
13422
- ] })] });
13341
+ }) }), /* @__PURE__ */ jsx(MenuContent, { children: PANEL_ITEMS.filter((item) => menuShows[item.panel]).map(({ panel: target, label, Icon }) => /* @__PURE__ */ jsxs(MenuItem, {
13342
+ onClick: () => openPanel(target),
13343
+ children: [
13344
+ /* @__PURE__ */ jsx(Icon, { className: "size-3.5 text-fg-3" }),
13345
+ " ",
13346
+ label
13347
+ ]
13348
+ }, target)) })] });
13423
13349
  const menu = external ? null : actionsMenu;
13350
+ const dialog = (target) => ({
13351
+ open: panel === target,
13352
+ onOpenChange: (next) => setPanel(next ? target : void 0)
13353
+ });
13424
13354
  const headerTakesActions = typeof header === "function";
13425
13355
  const sessionControls = /* @__PURE__ */ jsxs(Fragment$1, { children: [models.length ? /* @__PURE__ */ jsx(ModelSelect, {
13426
13356
  models,
@@ -13445,8 +13375,6 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
13445
13375
  onOpenStatus: external && !onOpenPanel ? void 0 : () => openPanel("info"),
13446
13376
  onOpenContext: external && !onOpenPanel ? void 0 : () => openPanel("context"),
13447
13377
  onOpenUsage: external && !onOpenPanel ? void 0 : () => openPanel("usage"),
13448
- tasks,
13449
- onOpenTasks: external && !onOpenPanel ? void 0 : () => openPanel("tasks"),
13450
13378
  actions: headerTakesActions ? void 0 : menu
13451
13379
  });
13452
13380
  const fileLinks = useMemo(() => onOpenFile ? {
@@ -13477,296 +13405,208 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
13477
13405
  if (window.getSelection()?.isCollapsed === false) return;
13478
13406
  composerRef.current?.focus();
13479
13407
  };
13480
- return /* @__PURE__ */ jsx(TranscriptVariantProvider, {
13481
- value: transcriptVariant,
13482
- children: /* @__PURE__ */ jsx(FileLinkProvider, {
13483
- value: fileLinks,
13484
- children: /* @__PURE__ */ jsx(ToolResultFetchProvider, {
13485
- value: loadFullResult,
13486
- children: /* @__PURE__ */ jsx(ShellActionsProvider, {
13487
- value: shellActions,
13488
- children: /* @__PURE__ */ jsx(ToolTitleProvider, {
13489
- value: state.toolTitles,
13490
- children: /* @__PURE__ */ jsx(ToolResultImageProvider, {
13491
- value: resultImages,
13492
- children: /* @__PURE__ */ jsx("div", {
13493
- ref: panelRef,
13494
- "data-slot": "session-panel",
13495
- "data-agent-font": transcriptFont,
13496
- onClick: handleClick,
13497
- className: cn("relative flex h-full min-h-0 flex-col overflow-hidden bg-bg", className),
13498
- style: fontSize !== void 0 ? { "--wd-font-size": `${Math.round(fontSize)}px` } : void 0,
13499
- children: /* @__PURE__ */ jsxs(ImageViewerProvider, { children: [
13500
- headerTakesActions ? header({ actions: menu }) : header,
13501
- statusPlacement === "top" ? statusBar : null,
13502
- protocolMismatch !== void 0 ? /* @__PURE__ */ jsxs(Notice, {
13503
- level: "warning",
13504
- children: [
13505
- "Server speaks protocol v",
13506
- protocolMismatch,
13507
- ", this build renders v",
13508
- PROTOCOL_VERSION,
13509
- ". Some events may not render."
13510
- ]
13511
- }) : null,
13512
- protocolError ? /* @__PURE__ */ jsx(Notice, {
13513
- level: "error",
13514
- onDismiss: () => setProtocolError(void 0),
13515
- children: protocolError
13516
- }) : null,
13517
- framedShellId !== void 0 ? /* @__PURE__ */ jsx(ShellStrip, {
13518
- shell: framedShell,
13519
- label: framedShellLabel,
13520
- onBack: leaveShell,
13521
- onKill: () => void killShell(framedShellId),
13522
- onAgentWrite: agentWrite ? (enabled) => void agentWrite(framedShellId, enabled) : void 0,
13523
- terminal,
13524
- fontSize: effectiveTermFontSize,
13525
- lineHeight: effectiveTermLineHeight
13526
- }) : null,
13527
- framedShellId === void 0 && subagentId !== void 0 ? /* @__PURE__ */ jsx(SubagentStrip, {
13528
- task: subagentTask,
13529
- items: subagentFrameItems,
13530
- label: subagentFallbackLabel,
13531
- onBack: leaveSubagent,
13532
- terminal,
13533
- fontSize: effectiveTermFontSize,
13534
- lineHeight: effectiveTermLineHeight
13535
- }) : null,
13536
- framedShellId !== void 0 ? /* @__PURE__ */ jsx(ShellTerminal, {
13537
- handle,
13538
- shellId: framedShellId,
13539
- fontSize: effectiveTermFontSize
13540
- }, framedShellId) : /* @__PURE__ */ jsx(BookmarkProvider, {
13541
- value: bookmarkHandle,
13542
- children: /* @__PURE__ */ jsx(Transcript, {
13543
- state,
13544
- fileUrl: sessionId ? (path) => client.sessionFileUrl(sessionId, path) : void 0,
13545
- attachmentUrl: sessionId ? (id) => client.attachmentUrl(sessionId, id) : void 0,
13546
- canBrowseFiles: hostFiles.available,
13547
- sessionNames: peers.names,
13548
- hostImage,
13549
- variant: transcriptVariant,
13550
- fontSize: effectiveTermFontSize,
13551
- lineHeight: effectiveTermLineHeight,
13552
- affordances,
13553
- stickyPrompt,
13554
- scrubber,
13555
- bookmarks,
13556
- replaying,
13557
- catchUp: catchUp && newCount > 0 ? {
13558
- from: catchUp.itemCount,
13559
- since: catchUp.since
13560
- } : void 0,
13561
- reveal: taskReveal ?? frameReturnReveal ?? reveal,
13562
- frame: subagentId === void 0 ? void 0 : { parentToolUseId: subagentId },
13563
- onOpenSubagent: enterSubagent,
13564
- emptyState,
13565
- jumpToRecapRef: jumpToRecap,
13566
- repinRef: repinTranscript
13567
- }, subagentId ?? "session")
13568
- }),
13569
- catchUp && newCount > 0 && !replaying && subagentId === void 0 && framedShellId === void 0 ? /* @__PURE__ */ jsx("div", {
13570
- className: "px-3 pb-1",
13571
- children: /* @__PURE__ */ jsxs("div", {
13572
- "data-slot": "catch-up",
13573
- className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-fg-3",
13574
- children: [
13575
- /* @__PURE__ */ jsx("span", {
13576
- "aria-hidden": true,
13577
- className: cn("select-none", terminal ? "text-fg-3" : "text-accent"),
13578
- children: "※"
13579
- }),
13580
- /* @__PURE__ */ jsxs("span", {
13581
- className: "min-w-0 flex-1 truncate",
13582
- children: [
13583
- newCount,
13584
- " new ",
13585
- newCount === 1 ? "row" : "rows",
13586
- catchUp.since !== void 0 ? ` since you were last here` : ""
13587
- ]
13588
- }),
13589
- /* @__PURE__ */ jsx("button", {
13590
- type: "button",
13591
- onClick: () => jumpToRecap.current?.(),
13592
- className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
13593
- children: "jump"
13594
- }),
13595
- /* @__PURE__ */ jsx("button", {
13596
- type: "button",
13597
- onClick: () => setCaughtUp(true),
13598
- className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
13599
- children: "dismiss"
13600
- })
13601
- ]
13602
- })
13603
- }) : null,
13604
- !readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? /* @__PURE__ */ jsx("div", {
13605
- className: cn(terminal ? "pb-2" : "px-3 pb-2"),
13606
- children: /* @__PURE__ */ jsx(PromptSurface, {
13607
- terminal,
13608
- metrics: {
13609
- fontSize: effectiveTermFontSize,
13610
- lineHeight: effectiveTermLineHeight
13611
- },
13612
- affordances,
13613
- children: state.pendingApprovals.map((request) => {
13614
- const HostPrompt = approvalPrompts?.[request.toolName];
13615
- if (HostPrompt) return /* @__PURE__ */ jsx(HostPrompt, {
13616
- request,
13617
- onApprove: approve,
13618
- onDeny: deny
13619
- }, request.id);
13620
- const isQuestion = request.toolName === "AskUserQuestion" && parseUserQuestions(request.input).length > 0;
13621
- if (terminal) return isQuestion ? /* @__PURE__ */ jsx(TerminalQuestionPrompt, {
13622
- request,
13623
- onAnswer: approve,
13624
- onDismiss: (id) => deny(id, "Question dismissed by user")
13625
- }, request.id) : /* @__PURE__ */ jsx(TerminalPermissionPrompt, {
13626
- request,
13627
- onApprove: approve,
13628
- onDeny: deny
13629
- }, request.id);
13630
- return isQuestion ? /* @__PURE__ */ jsx(QuestionPrompt, {
13631
- request,
13632
- onAnswer: approve,
13633
- onDismiss: (id) => deny(id, "Question dismissed by user")
13634
- }, request.id) : /* @__PURE__ */ jsx(PermissionPrompt, {
13635
- request,
13636
- onApprove: approve,
13637
- onDeny: deny
13638
- }, request.id);
13639
- })
13640
- })
13641
- }) : null,
13642
- readOnly || subagentId !== void 0 || framedShellId !== void 0 ? null : /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(Composer, {
13643
- ref: composerRef,
13644
- onSend: handleSend,
13645
- onInterrupt: interrupt,
13646
- busy,
13647
- disabled: ended || !sessionId,
13648
- commands: capabilities.slashCommands ? state.commands : void 0,
13649
- skills: capabilities.skillsList ? state.skills : void 0,
13650
- clientCommands,
13651
- attachments,
13652
- draft,
13653
- onSearchFiles: hostFiles.available ? searchComposerFiles : void 0,
13654
- peers: peers.peers,
13655
- onShellCommand: shell ? runShell : void 0,
13656
- layout: controlsExternal ? "inline" : "stacked",
13657
- toolbar: controlsExternal ? void 0 : sessionControls,
13658
- fontSize: effectiveTermFontSize,
13659
- lineHeight: effectiveTermLineHeight,
13660
- affordances
13661
- }) }),
13662
- statusPlacement === "bottom" ? statusBar : null,
13663
- !external ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
13664
- /* @__PURE__ */ jsx(SessionInfoDialog, {
13665
- state,
13666
- client,
13667
- sessionId,
13668
- open: panel === "info",
13669
- onOpenChange: (next) => setPanel(next ? "info" : void 0)
13670
- }),
13671
- /* @__PURE__ */ jsx(ContextDialog, {
13672
- usage: state.contextUsage,
13673
- engine: state.engine ?? "claude",
13674
- open: panel === "context",
13675
- onOpenChange: (next) => setPanel(next ? "context" : void 0)
13676
- }),
13677
- /* @__PURE__ */ jsx(UsageDialog, {
13678
- rateLimits: windows,
13679
- subscriptionType: state.subscriptionType,
13680
- engine: state.engine ?? "claude",
13681
- totalCostUsd: state.totalCostUsd,
13682
- costUsd: state.costUsd,
13683
- usageByModel: state.usageByModel,
13684
- pricing,
13685
- spend: profileSpend,
13686
- updatedAt: usageUpdatedAt,
13687
- open: panel === "usage",
13688
- onOpenChange: (next) => setPanel(next ? "usage" : void 0)
13689
- }),
13690
- /* @__PURE__ */ jsx(McpDialog, {
13691
- client,
13692
- sessionId,
13693
- canManageServers: capabilities.mcpServerActions,
13694
- open: panel === "mcp",
13695
- onOpenChange: (next) => setPanel(next ? "mcp" : void 0)
13696
- }),
13697
- /* @__PURE__ */ jsx(TasksDialog, {
13698
- tasks,
13699
- showCompleted: showCompletedTasks,
13700
- onShowCompletedChange: setShowCompletedTasks,
13701
- onSelectTask: (task) => {
13702
- if (task.toolUseId) setTaskReveal({
13703
- toolUseId: task.toolUseId,
13704
- nonce: Date.now()
13705
- });
13706
- },
13707
- open: panel === "tasks",
13708
- onOpenChange: (next) => setPanel(next ? "tasks" : void 0)
13709
- }),
13710
- /* @__PURE__ */ jsx(SkillsDialog, {
13711
- skills: state.skills,
13712
- open: panel === "skills",
13713
- onOpenChange: (next) => setPanel(next ? "skills" : void 0),
13714
- onUse: (skill) => composerRef.current?.insertText(skillPrompt(skill))
13715
- }),
13716
- /* @__PURE__ */ jsx(HostFilesDialog, {
13717
- client,
13718
- cwd: state.cwd,
13719
- open: panel === "files",
13720
- onOpenChange: (next) => setPanel(next ? "files" : void 0)
13721
- })
13722
- ] }) : null
13723
- ] })
13724
- })
13725
- })
13408
+ return /* @__PURE__ */ jsx(SessionPanelProviders, {
13409
+ variant: transcriptVariant,
13410
+ fileLinks,
13411
+ loadFullResult,
13412
+ shellActions,
13413
+ toolTitles: state.toolTitles,
13414
+ resultImages,
13415
+ children: /* @__PURE__ */ jsx("div", {
13416
+ ref: panelRef,
13417
+ "data-slot": "session-panel",
13418
+ "data-agent-font": transcriptFont,
13419
+ onClick: handleClick,
13420
+ className: cn("relative flex h-full min-h-0 flex-col overflow-hidden bg-bg", className),
13421
+ style: fontSize !== void 0 ? { "--wd-font-size": `${Math.round(fontSize)}px` } : void 0,
13422
+ children: /* @__PURE__ */ jsxs(ImageViewerProvider, { children: [
13423
+ headerTakesActions ? header({ actions: menu }) : header,
13424
+ statusPlacement === "top" ? statusBar : null,
13425
+ protocolMismatch !== void 0 ? /* @__PURE__ */ jsxs(Notice, {
13426
+ level: "warning",
13427
+ children: [
13428
+ "Server speaks protocol v",
13429
+ protocolMismatch,
13430
+ ", this build renders v",
13431
+ PROTOCOL_VERSION,
13432
+ ". Some events may not render."
13433
+ ]
13434
+ }) : null,
13435
+ protocolError ? /* @__PURE__ */ jsx(Notice, {
13436
+ level: "error",
13437
+ onDismiss: () => setProtocolError(void 0),
13438
+ children: protocolError
13439
+ }) : null,
13440
+ framedShellId !== void 0 ? /* @__PURE__ */ jsx(ShellStrip, {
13441
+ shell: framedShell,
13442
+ label: framedShellLabel,
13443
+ onBack: leaveShell,
13444
+ onKill: () => void killShell(framedShellId),
13445
+ onAgentWrite: agentWrite ? (enabled) => void agentWrite(framedShellId, enabled) : void 0,
13446
+ terminal,
13447
+ ...cell
13448
+ }) : null,
13449
+ framedShellId === void 0 && subagentId !== void 0 ? /* @__PURE__ */ jsx(SubagentStrip, {
13450
+ task: subagentTask,
13451
+ items: subagentFrameItems,
13452
+ label: subagentFallbackLabel,
13453
+ onBack: leaveSubagent,
13454
+ terminal,
13455
+ ...cell
13456
+ }) : null,
13457
+ framedShellId !== void 0 ? /* @__PURE__ */ jsx(ShellTerminal, {
13458
+ handle,
13459
+ shellId: framedShellId,
13460
+ fontSize: cell.fontSize
13461
+ }, framedShellId) : /* @__PURE__ */ jsx(BookmarkProvider, {
13462
+ value: bookmarkHandle,
13463
+ children: /* @__PURE__ */ jsx(Transcript, {
13464
+ state,
13465
+ fileUrl: sessionId ? (path) => client.sessionFileUrl(sessionId, path) : void 0,
13466
+ attachmentUrl: sessionId ? (id) => client.attachmentUrl(sessionId, id) : void 0,
13467
+ canBrowseFiles: hostFiles.available,
13468
+ sessionNames: peers.names,
13469
+ hostImage,
13470
+ variant: transcriptVariant,
13471
+ ...cell,
13472
+ affordances,
13473
+ stickyPrompt,
13474
+ scrubber,
13475
+ bookmarks,
13476
+ replaying,
13477
+ catchUp: catchUp && newCount > 0 ? {
13478
+ from: catchUp.itemCount,
13479
+ since: catchUp.since
13480
+ } : void 0,
13481
+ reveal: frameReturnReveal ?? reveal,
13482
+ frame: subagentId === void 0 ? void 0 : { parentToolUseId: subagentId },
13483
+ onOpenSubagent: enterSubagent,
13484
+ emptyState,
13485
+ jumpToRecapRef: jumpToRecap,
13486
+ repinRef: repinTranscript
13487
+ }, subagentId ?? "session")
13488
+ }),
13489
+ catchUp && newCount > 0 && !replaying && subagentId === void 0 && framedShellId === void 0 ? /* @__PURE__ */ jsx(CatchUpBanner, {
13490
+ count: newCount,
13491
+ since: catchUp.since,
13492
+ terminal,
13493
+ onJump: () => jumpToRecap.current?.(),
13494
+ onDismiss: dismissCatchUp
13495
+ }) : null,
13496
+ !readOnly && capabilities.interactiveApprovals && state.pendingApprovals.length > 0 ? /* @__PURE__ */ jsx(ApprovalPrompts, {
13497
+ requests: state.pendingApprovals,
13498
+ terminal,
13499
+ ...cell,
13500
+ affordances,
13501
+ hostPrompts: approvalPrompts,
13502
+ onApprove: approve,
13503
+ onDeny: deny
13504
+ }) : null,
13505
+ readOnly || subagentId !== void 0 || framedShellId !== void 0 ? null : /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsx(Composer, {
13506
+ ref: composerRef,
13507
+ onSend: handleSend,
13508
+ onInterrupt: interrupt,
13509
+ busy,
13510
+ disabled: ended || !sessionId,
13511
+ commands: capabilities.slashCommands ? state.commands : void 0,
13512
+ skills: capabilities.skillsList ? state.skills : void 0,
13513
+ clientCommands,
13514
+ attachments,
13515
+ draft,
13516
+ onSearchFiles: hostFiles.available ? searchComposerFiles : void 0,
13517
+ peers: peers.peers,
13518
+ onShellCommand: shell ? runShell : void 0,
13519
+ layout: controlsExternal ? "inline" : "stacked",
13520
+ toolbar: controlsExternal ? void 0 : sessionControls,
13521
+ ...cell,
13522
+ affordances
13523
+ }) }),
13524
+ statusPlacement === "bottom" ? statusBar : null,
13525
+ !external ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
13526
+ /* @__PURE__ */ jsx(SessionInfoDialog, {
13527
+ state,
13528
+ client,
13529
+ sessionId,
13530
+ ...dialog("info")
13531
+ }),
13532
+ /* @__PURE__ */ jsx(ContextDialog, {
13533
+ usage: state.contextUsage,
13534
+ engine: state.engine ?? "claude",
13535
+ ...dialog("context")
13536
+ }),
13537
+ /* @__PURE__ */ jsx(UsageDialog, {
13538
+ rateLimits: windows,
13539
+ subscriptionType: state.subscriptionType,
13540
+ engine: state.engine ?? "claude",
13541
+ totalCostUsd: state.totalCostUsd,
13542
+ costUsd: state.costUsd,
13543
+ usageByModel: state.usageByModel,
13544
+ pricing,
13545
+ spend: profileSpend,
13546
+ updatedAt: usageUpdatedAt,
13547
+ ...dialog("usage")
13548
+ }),
13549
+ /* @__PURE__ */ jsx(McpDialog, {
13550
+ client,
13551
+ sessionId,
13552
+ canManageServers: capabilities.mcpServerActions,
13553
+ ...dialog("mcp")
13554
+ }),
13555
+ /* @__PURE__ */ jsx(SkillsDialog, {
13556
+ skills: state.skills,
13557
+ ...dialog("skills"),
13558
+ onUse: (skill) => composerRef.current?.insertText(skillPrompt(skill))
13559
+ }),
13560
+ /* @__PURE__ */ jsx(HostFilesDialog, {
13561
+ client,
13562
+ cwd: state.cwd,
13563
+ ...dialog("files")
13726
13564
  })
13565
+ ] }) : null
13566
+ ] })
13567
+ })
13568
+ });
13569
+ }
13570
+ function CatchUpBanner({ count, since, terminal, onJump, onDismiss }) {
13571
+ return /* @__PURE__ */ jsx("div", {
13572
+ className: "px-3 pb-1",
13573
+ children: /* @__PURE__ */ jsxs("div", {
13574
+ "data-slot": "catch-up",
13575
+ className: "mx-auto flex w-full max-w-[var(--wd-transcript-max-width)] items-center gap-2 text-label text-fg-3",
13576
+ children: [
13577
+ /* @__PURE__ */ jsx("span", {
13578
+ "aria-hidden": true,
13579
+ className: cn("select-none", terminal ? "text-fg-3" : "text-accent"),
13580
+ children: "※"
13581
+ }),
13582
+ /* @__PURE__ */ jsxs("span", {
13583
+ className: "min-w-0 flex-1 truncate",
13584
+ children: [
13585
+ count,
13586
+ " new ",
13587
+ count === 1 ? "row" : "rows",
13588
+ since !== void 0 ? ` since you were last here` : ""
13589
+ ]
13590
+ }),
13591
+ /* @__PURE__ */ jsx("button", {
13592
+ type: "button",
13593
+ onClick: onJump,
13594
+ className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
13595
+ children: "jump"
13596
+ }),
13597
+ /* @__PURE__ */ jsx("button", {
13598
+ type: "button",
13599
+ onClick: onDismiss,
13600
+ className: "shrink-0 underline-offset-2 hover:text-fg-1 hover:underline",
13601
+ children: "dismiss"
13727
13602
  })
13728
- })
13603
+ ]
13729
13604
  })
13730
13605
  });
13731
13606
  }
13732
- function useHostImage(client, sessionId, producedFiles) {
13733
- const cache = useRef(/* @__PURE__ */ new Map());
13734
- const objectUrls = useRef([]);
13735
- useEffect(() => () => {
13736
- for (const url of objectUrls.current) URL.revokeObjectURL(url);
13737
- objectUrls.current = [];
13738
- }, []);
13739
- return useCallback((path) => {
13740
- const produced = producedFiles?.[path];
13741
- const key = produced ? `produced:${produced.fileId}` : `fs:${path}`;
13742
- const hit = cache.current.get(key);
13743
- if (hit) return hit;
13744
- const pending = produced && sessionId ? client.readProducedFile(sessionId, produced.fileId).then((blob) => {
13745
- if (blob.size === 0) return;
13746
- const url = URL.createObjectURL(blob);
13747
- objectUrls.current.push(url);
13748
- return url;
13749
- }).catch(() => void 0) : client.readHostFile(path).then((file) => {
13750
- if (file.encoding !== "base64") return;
13751
- const extension = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
13752
- const mediaType = IMAGE_MEDIA_TYPES[extension];
13753
- return mediaType ? `data:${mediaType};base64,${file.content}` : void 0;
13754
- }).catch(() => void 0);
13755
- cache.current.set(key, pending);
13756
- return pending;
13757
- }, [
13758
- client,
13759
- sessionId,
13760
- producedFiles
13761
- ]);
13607
+ function sameVitals(a, b) {
13608
+ return Object.keys(b).every((key) => Object.is(a[key], b[key]));
13762
13609
  }
13763
- const IMAGE_MEDIA_TYPES = {
13764
- png: "image/png",
13765
- jpg: "image/jpeg",
13766
- jpeg: "image/jpeg",
13767
- gif: "image/gif",
13768
- webp: "image/webp"
13769
- };
13770
13610
  function Notice({ level, onDismiss, children }) {
13771
13611
  return /* @__PURE__ */ jsx("div", {
13772
13612
  className: "px-3 pt-2",
@@ -13893,6 +13733,6 @@ function resolveAgainstCwd(path, cwd) {
13893
13733
  return parseFileLink(path, cwd)?.path ?? path;
13894
13734
  }
13895
13735
  //#endregion
13896
- export { shellAgentWriteLabel as $, Tip as $t, ShellTerminal as A, isMutatingTool as At, SkillsDialog as B, hashtagTrigger as Bt, SessionInfoDialog as C, Badge as Cn, AgentWriteIcon as Ct, QuestionPrompt as D, cn as Dn, PromptTokenText as Dt, QUESTION_BEHAVIORS as E, buttonVariants as En, useAffordances as Et, PERMISSION_MODES as F, mergeComposerRows as Ft, TaskList as G, plainTextToSegments as Gt, HostFilesDialog as H, mentionTrigger as Ht, PermissionModeSelect as I, rankComposerRows as It, Composer as J, Splitter as Jt, ContextDialog as K, segmentsToPlainText as Kt, permissionModeChoices as L, rankPeerSessions as Lt, SubagentStrip as M, TranscriptVariantProvider as Mt, PermissionPrompt as N, useTranscriptVariant as Nt, parseUserQuestions as O, SessionNamesProvider as Ot, Response as P, matchClientCommand as Pt, SHELL_KILL_GLYPH as Q, copyText as Qt, permissionModeMeta as R, skillPrompt as Rt, STATUS_META as S, Input as Sn, Row as St, TerminalPermissionPrompt as T, Button as Tn, WithActions as Tt, EntryIcon as U, usePromptAreaState as Ut, McpDialog as V, launchTrigger as Vt, TasksDialog as W, PromptArea as Wt, SHELL_AGENT_WRITE_NOTE as X, Spinner as Xt, TerminalSurface as Y, CodeBlock as Yt, SHELL_GLYPH as Z, CopyButton as Zt, Loader as _, SelectItemText as _n, TerminalDiff as _t, UsageDialog as a, DialogContent as an, ShellActionsProvider as at, ConversationScrollButton as b, PortalScope as bn, Blank as bt, Transcript as c, DialogTrigger as cn, useToolTitle as ct, Message as d, MenuItem as dn, ImageViewerProvider as dt, TooltipContent as en, shellGrantable as et, MessageContent as f, MenuSeparator as fn, ViewableImage as ft, SessionEmptyState as g, SelectItem as gn, parseFileLink as gt, TerminalTranscript as h, SelectContent as hn, useFileLinks as ht, SessionPanel as i, DialogClose as in, shellTitle as it, ShellStrip as j, toolIcon as jt, useShellFrame as k, SkillNamesProvider as kt, ToolCallCard as l, Menu$1 as ln, TerminalMarkdown as lt, TerminalItemView as m, Select$1 as mn, FileLinkProvider as mt, usePathLinks as n, Dialog$1 as nn, shellInfoLabel as nt, UsageMeters as o, DialogHeader as on, useShellActions as ot, FileCard as p, MenuTrigger as pn, useOpenImage as pt, contextNote as q, ProgressRing as qt, matchPath as r, DialogBody as rn, shellInfoStatusText as rt, useMinuteClock as s, DialogRow as sn, ToolTitleProvider as st, resolveAgainstCwd as t, TooltipProvider as tn, shellInfoFailed as tt, Reasoning as u, MenuContent as un, ImageViewer as ut, Conversation as v, SelectTrigger as vn, previewPatch as vt, TerminalQuestionPrompt as w, badgeVariants as wn, CopyAction as wt, StatusBar as x, Textarea as xn, Ink as xt, ConversationContent as y, SelectValue as yn, Band as yt, ModelSelect as z, commandTrigger as zt };
13736
+ export { ShellActionsProvider as $, Spinner as $t, PermissionPrompt as A, buttonVariants as An, PromptTokenText as At, ModelSelect as B, rankPeerSessions as Bt, SessionInfoDialog as C, SelectValue as Cn, Blank as Ct, QUESTION_BEHAVIORS as D, Badge as Dn, CopyAction as Dt, TerminalPermissionPrompt as E, Input as En, AgentWriteIcon as Et, SubagentStrip as F, TranscriptVariantProvider as Ft, useMcpPanel as G, mentionTrigger as Gt, McpDialog as H, commandTrigger as Ht, PERMISSION_MODES as I, useTranscriptVariant as It, ContextDialog as J, plainTextToSegments as Jt, HostFilesDialog as K, usePromptAreaState as Kt, PermissionModeSelect as L, matchClientCommand as Lt, useShellFrame as M, SkillNamesProvider as Mt, ShellTerminal as N, isMutatingTool as Nt, QuestionPrompt as O, badgeVariants as On, WithActions as Ot, ShellStrip as P, toolIcon as Pt, TerminalSurface as Q, CodeBlock as Qt, permissionModeChoices as R, mergeComposerRows as Rt, STATUS_META as S, SelectTrigger as Sn, Band as St, TerminalQuestionPrompt as T, Textarea as Tn, Row as Tt, McpPanel as U, hashtagTrigger as Ut, SkillsDialog as V, skillPrompt as Vt, McpPanelActions as W, launchTrigger as Wt, contextNote as X, ProgressRing as Xt, ContextPanel as Y, segmentsToPlainText as Yt, Composer as Z, Splitter as Zt, Loader as _, OptionSelect as _n, FileLinkProvider as _t, UsageDialog as a, Dialog$1 as an, shellGrantable as at, ConversationScrollButton as b, SelectItem as bn, TerminalDiff as bt, TerminalTranscript as c, DialogContent as cn, shellInfoStatusText as ct, Reasoning as d, DialogTrigger as dn, useToolTitle as dt, CopyButton as en, useShellActions as et, Message as f, Menu$1 as fn, TerminalMarkdown as ft, SessionEmptyState as g, MenuTrigger as gn, useOpenImage as gt, TerminalItemView as h, MenuSeparator as hn, ViewableImage as ht, SessionPanel as i, TooltipProvider as in, shellAgentWriteLabel as it, Response as j, cn as jn, SessionNamesProvider as jt, parseUserQuestions as k, Button as kn, useAffordances as kt, Transcript as l, DialogHeader as ln, shellTitle as lt, FileCard as m, MenuItem as mn, ImageViewerProvider as mt, usePathLinks as n, Tip as nn, SHELL_GLYPH as nt, UsageMeters as o, DialogBody as on, shellInfoFailed as ot, MessageContent as p, MenuContent as pn, ImageViewer as pt, EntryIcon as q, PromptArea as qt, matchPath as r, TooltipContent as rn, SHELL_KILL_GLYPH as rt, useMinuteClock as s, DialogClose as sn, shellInfoLabel as st, resolveAgainstCwd as t, copyText as tn, SHELL_AGENT_WRITE_NOTE as tt, ToolCallCard as u, DialogRow as un, ToolTitleProvider as ut, Conversation as v, Select$1 as vn, useFileLinks as vt, SessionInfoPanel as w, PortalScope as wn, Ink as wt, StatusBar as x, SelectItemText as xn, previewPatch as xt, ConversationContent as y, SelectContent as yn, parseFileLink as yt, permissionModeMeta as z, rankComposerRows as zt };
13897
13737
 
13898
- //# sourceMappingURL=use-path-links-BEWxt0ZM.mjs.map
13738
+ //# sourceMappingURL=use-path-links-C6Fh5zdx.mjs.map