@workerdeck/ui 2.9.0 → 2.11.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 (46) hide show
  1. package/build/index.d.mts +54 -7
  2. package/build/index.mjs +34 -16
  3. package/build/index.mjs.map +1 -1
  4. package/build/scoped.css +76 -1
  5. package/build/{SessionPanel-BClL7zUS.mjs → use-path-links-DO_79-wX.mjs} +834 -303
  6. package/build/use-path-links-DO_79-wX.mjs.map +1 -0
  7. package/build/workspace.mjs +3 -83
  8. package/build/workspace.mjs.map +1 -1
  9. package/package.json +4 -4
  10. package/src/components/agent/Composer.tsx +7 -4
  11. package/src/components/agent/PromptTokenText.tsx +11 -3
  12. package/src/components/agent/SessionInfoDialog.tsx +1 -1
  13. package/src/components/agent/SessionItem.tsx +13 -8
  14. package/src/components/agent/SessionList.tsx +1 -1
  15. package/src/components/agent/SessionPanel.tsx +6 -1
  16. package/src/components/agent/StatusBar.tsx +1 -1
  17. package/src/components/agent/Transcript.tsx +83 -79
  18. package/src/components/agent/TranscriptItemView.tsx +3 -1
  19. package/src/components/agent/TranscriptRows.tsx +60 -56
  20. package/src/components/agent/UsageDialog.tsx +101 -7
  21. package/src/components/agent/composer-commands.ts +14 -2
  22. package/src/components/agent/pulse.tsx +53 -16
  23. package/src/components/agent/use-path-links.ts +5 -19
  24. package/src/components/agent/use-transcript-jumps.ts +25 -0
  25. package/src/components/prompt-area/clipboard-helpers.ts +13 -4
  26. package/src/components/prompt-area/dom-helpers.ts +10 -1
  27. package/src/components/prompt-area/index.ts +10 -1
  28. package/src/components/prompt-area/prompt-area-engine.ts +23 -14
  29. package/src/components/prompt-area/types.ts +20 -0
  30. package/src/components/prompt-area/use-prompt-area-keydown.ts +2 -1
  31. package/src/components/prompt-area/use-prompt-area.ts +8 -2
  32. package/src/components/terminal/StatusLine.tsx +3 -2
  33. package/src/components/terminal/TerminalTranscript.tsx +31 -27
  34. package/src/components/terminal/blocks.ts +15 -7
  35. package/src/components/terminal/height.ts +14 -5
  36. package/src/components/terminal/items.tsx +99 -12
  37. package/src/components/terminal/peer-names.tsx +16 -0
  38. package/src/components/terminal/row.tsx +1 -1
  39. package/src/components/terminal/tool-run.ts +68 -0
  40. package/src/index.ts +3 -1
  41. package/src/lib/path-match.ts +51 -0
  42. package/src/lib/session-cost.ts +75 -0
  43. package/src/lib/tool-icon.ts +5 -1
  44. package/src/styles/terminal.css +93 -0
  45. package/src/styles/theme.css +1 -1
  46. package/build/SessionPanel-BClL7zUS.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { C as rateLimitWindowSeconds, _ as formatDuration, a as meterTintClass, b as formatRelativeTime, f as compactionText, g as formatCountdown, h as formatCost, m as formatBytes, p as formatAgoPrecise, r as meterColorClass, w as toolInputPreview, x as formatTokens, y as formatRateLimitWindowLong } from "./status-kmsBhuFG.mjs";
2
- import { Fragment, createContext, forwardRef, memo, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import { Fragment, createContext, forwardRef, memo, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
3
  import { cva } from "class-variance-authority";
4
4
  import { clsx } from "clsx";
5
5
  import { extendTailwindMerge } from "tailwind-merge";
@@ -9,7 +9,7 @@ import { ArrowDown, ArrowDownCircle, ArrowLeft, ArrowRight, ArrowUp, AtSign, Bra
9
9
  import { Menu } from "@base-ui/react/menu";
10
10
  import { Dialog } from "@base-ui/react/dialog";
11
11
  import { Tooltip } from "@base-ui/react/tooltip";
12
- import { PROTOCOL_VERSION, mergeUsage, orderUsageWindows, parseTodoWriteInput, parseUserQuestions, sessionTasks, subagentLabel, taskCountLabel, taskSummary, toolTitle, usageInfos, usageIsStale, visibleTasks } from "@workerdeck/protocol";
12
+ import { PRICING_NOTE, PROTOCOL_VERSION, 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
13
  import { recapLine, scanPromptTokens, summarizeSince, useAttachments, useClaudeSession, useDraft, useHostFileSearch, useProfileUsage, useToolCallHost } from "@workerdeck/react";
14
14
  import { Streamdown } from "streamdown";
15
15
  import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
@@ -464,13 +464,19 @@ function ProgressRing({ value, size = 13, strokeWidth = 2, className }) {
464
464
  //#endregion
465
465
  //#region src/components/prompt-area/prompt-area-engine.ts
466
466
  /**
467
+ * A chip's plain-text form: `{sigil ?? trigger}{displayText}` (e.g., "@Alice").
468
+ */
469
+ function chipPlainText(seg) {
470
+ return `${seg.sigil ?? seg.trigger}${seg.displayText}`;
471
+ }
472
+ /**
467
473
  * Converts an array of segments to a plain text string.
468
- * Chips are represented as `{trigger}{displayText}` (e.g., "@Alice").
474
+ * Chips are represented by {@link chipPlainText}.
469
475
  */
470
476
  function segmentsToPlainText(segments) {
471
477
  return segments.map((seg) => {
472
478
  if (seg.type === "text") return seg.text;
473
- return `${seg.trigger}${seg.displayText}`;
479
+ return chipPlainText(seg);
474
480
  }).join("");
475
481
  }
476
482
  /**
@@ -495,7 +501,7 @@ function truncateSegmentsToLength(segments, maxLength) {
495
501
  const result = [];
496
502
  let length = 0;
497
503
  for (const seg of segments) {
498
- const segLength = seg.type === "text" ? seg.text.length : seg.trigger.length + seg.displayText.length;
504
+ const segLength = seg.type === "text" ? seg.text.length : chipPlainText(seg).length;
499
505
  if (length + segLength <= maxLength) {
500
506
  result.push(seg);
501
507
  length += segLength;
@@ -597,13 +603,13 @@ function detectActiveTrigger(text, cursorPos, triggers) {
597
603
  * @param chip - The chip data (value, displayText, optional data)
598
604
  * @returns New segments array with the chip inserted, and the new cursor position
599
605
  */
600
- function resolveChip(segments, activeTrigger, chip) {
606
+ function resolveChip(segments, activeTrigger, chip, trailingText = " ") {
601
607
  const triggerStart = activeTrigger.startOffset;
602
608
  const triggerEnd = triggerStart + 1 + activeTrigger.query.length;
603
609
  const newSegments = [];
604
610
  let offset = 0;
605
611
  for (const seg of segments) if (seg.type === "chip") {
606
- const chipText = `${seg.trigger}${seg.displayText}`;
612
+ const chipText = chipPlainText(seg);
607
613
  const chipStart = offset;
608
614
  const chipEnd = offset + chipText.length;
609
615
  if (chipEnd <= triggerStart || chipStart >= triggerEnd) newSegments.push(seg);
@@ -625,17 +631,18 @@ function resolveChip(segments, activeTrigger, chip) {
625
631
  trigger: activeTrigger.config.char,
626
632
  value: chip.value,
627
633
  displayText: chip.displayText,
634
+ ...chip.sigil !== void 0 ? { sigil: chip.sigil } : {},
628
635
  ...chip.data !== void 0 ? { data: chip.data } : {},
629
636
  ...chip.autoResolved ? { autoResolved: true } : {}
630
637
  };
631
638
  newSegments.push(newChip);
632
639
  if (afterText) newSegments.push({
633
640
  type: "text",
634
- text: " " + afterText.replace(/^\s/, "")
641
+ text: trailingText + afterText.replace(/^\s/, "")
635
642
  });
636
643
  else newSegments.push({
637
644
  type: "text",
638
- text: " "
645
+ text: trailingText
639
646
  });
640
647
  }
641
648
  offset = textEnd;
@@ -645,12 +652,12 @@ function resolveChip(segments, activeTrigger, chip) {
645
652
  let runningOffset = 0;
646
653
  for (const seg of merged) if (seg.type === "text") runningOffset += seg.text.length;
647
654
  else {
648
- runningOffset += seg.trigger.length + seg.displayText.length;
655
+ runningOffset += chipPlainText(seg).length;
649
656
  if (seg.value === chip.value && seg.displayText === chip.displayText && seg.trigger === activeTrigger.config.char) lastChipEndOffset = runningOffset;
650
657
  }
651
658
  return {
652
659
  segments: merged,
653
- cursorOffset: lastChipEndOffset === -1 ? runningOffset : lastChipEndOffset + 1
660
+ cursorOffset: lastChipEndOffset === -1 ? runningOffset : lastChipEndOffset + trailingText.length
654
661
  };
655
662
  }
656
663
  /**
@@ -671,7 +678,7 @@ function resolveText(segments, activeTrigger, text) {
671
678
  let cursorOffset = triggerStart + text.length;
672
679
  for (const seg of segments) {
673
680
  if (seg.type === "chip") {
674
- const chipEnd = offset + `${seg.trigger}${seg.displayText}`.length;
681
+ const chipEnd = offset + chipPlainText(seg).length;
675
682
  if (chipEnd <= triggerStart || offset >= triggerEnd) newSegments.push(seg);
676
683
  offset = chipEnd;
677
684
  continue;
@@ -729,7 +736,7 @@ function revertChipAtIndex(segments, index) {
729
736
  if (index < 0 || index >= segments.length) return null;
730
737
  const seg = segments[index];
731
738
  if (seg.type !== "chip" || !seg.autoResolved) return null;
732
- const revertedText = `${seg.trigger}${seg.displayText}`;
739
+ const revertedText = chipPlainText(seg);
733
740
  return {
734
741
  segments: mergeAdjacentTextSegments([
735
742
  ...segments.slice(0, index),
@@ -826,7 +833,7 @@ function replaceTextRange(segments, start, end, replacement) {
826
833
  let offset = 0;
827
834
  let inserted = false;
828
835
  for (const seg of segments) if (seg.type === "chip") {
829
- const chipText = `${seg.trigger}${seg.displayText}`;
836
+ const chipText = chipPlainText(seg);
830
837
  const chipStart = offset;
831
838
  const chipEnd = offset + chipText.length;
832
839
  if (!inserted && start === end && chipStart === start) {
@@ -1388,6 +1395,10 @@ function getChipData(node) {
1388
1395
  if (!raw) return;
1389
1396
  return safeJsonParse(raw);
1390
1397
  }
1398
+ function getChipSigil(node) {
1399
+ if (!isChipElement(node)) return;
1400
+ return node.dataset.chipSigil;
1401
+ }
1391
1402
  /**
1392
1403
  * Length of a chip's plain-text representation (`trigger + displayText`).
1393
1404
  *
@@ -1397,7 +1408,7 @@ function getChipData(node) {
1397
1408
  * `textContent` for resilience against externally-mutated nodes.
1398
1409
  */
1399
1410
  function chipNodeTextLength(node) {
1400
- const trigger = node.dataset.chipTrigger ?? "";
1411
+ const trigger = node.dataset.chipSigil ?? node.dataset.chipTrigger ?? "";
1401
1412
  const display = node.dataset.chipDisplay ?? node.textContent ?? "";
1402
1413
  return trigger.length + display.length;
1403
1414
  }
@@ -1416,6 +1427,7 @@ function chipNodeToSegment(node) {
1416
1427
  const value = getChipValue(node);
1417
1428
  const displayText = getChipDisplay(node);
1418
1429
  if (!trigger || value === void 0 || !displayText) return null;
1430
+ const sigil = getChipSigil(node);
1419
1431
  const data = getChipData(node);
1420
1432
  const autoResolved = getChipAutoResolved(node);
1421
1433
  return {
@@ -1423,6 +1435,7 @@ function chipNodeToSegment(node) {
1423
1435
  trigger,
1424
1436
  value,
1425
1437
  displayText,
1438
+ ...sigil !== void 0 ? { sigil } : {},
1426
1439
  ...data !== void 0 ? { data } : {},
1427
1440
  ...autoResolved ? { autoResolved: true } : {}
1428
1441
  };
@@ -2052,7 +2065,7 @@ function serializeFragmentToPlainText(fragment) {
2052
2065
  text += value;
2053
2066
  },
2054
2067
  onChip: (node) => {
2055
- text += (getChipTrigger(node) ?? "") + (getChipDisplay(node) ?? "");
2068
+ text += (getChipSigil(node) ?? getChipTrigger(node) ?? "") + (getChipDisplay(node) ?? "");
2056
2069
  },
2057
2070
  onBreak: () => {
2058
2071
  text += "\n";
@@ -2106,6 +2119,7 @@ function parseSegmentsFromClipboard(json) {
2106
2119
  trigger: item.trigger,
2107
2120
  value: item.value,
2108
2121
  displayText: item.displayText,
2122
+ ...typeof item.sigil === "string" ? { sigil: item.sigil } : {},
2109
2123
  ...item.data !== void 0 ? { data: item.data } : {},
2110
2124
  ...item.autoResolved ? { autoResolved: true } : {}
2111
2125
  };
@@ -2140,7 +2154,7 @@ function insertSegmentsAtCursor(currentSegments, pastedSegments, editor) {
2140
2154
  };
2141
2155
  for (const seg of currentSegments) {
2142
2156
  if (seg.type === "chip") {
2143
- const chipLen = seg.trigger.length + seg.displayText.length;
2157
+ const chipLen = chipPlainText(seg).length;
2144
2158
  if (offset >= cursorOffset) insertOnce();
2145
2159
  result.push(seg);
2146
2160
  offset += chipLen;
@@ -2618,7 +2632,7 @@ function usePromptAreaKeydown({ editorRef, triggers, markdownEnabled, submitOnEn
2618
2632
  for (let i = 0; i < segIdx; i++) {
2619
2633
  const s = segments[i];
2620
2634
  if (s.type === "text") targetOffset += s.text.length;
2621
- else targetOffset += s.trigger.length + s.displayText.length;
2635
+ else targetOffset += chipPlainText(s).length;
2622
2636
  }
2623
2637
  targetOffset += result.revertedText.length;
2624
2638
  onChange(result.segments);
@@ -3229,6 +3243,7 @@ function usePromptArea({ value, onChange, triggers = NO_TRIGGERS, disabled = fal
3229
3243
  chip.dataset.chipTrigger = seg.trigger;
3230
3244
  chip.dataset.chipValue = seg.value;
3231
3245
  chip.dataset.chipDisplay = seg.displayText;
3246
+ if (seg.sigil !== void 0) chip.dataset.chipSigil = seg.sigil;
3232
3247
  if (seg.data !== void 0) {
3233
3248
  const json = safeJsonStringify(seg.data);
3234
3249
  if (json) chip.dataset.chipData = json;
@@ -3238,7 +3253,7 @@ function usePromptArea({ value, onChange, triggers = NO_TRIGGERS, disabled = fal
3238
3253
  const chipStyle = triggerConfig?.chipStyle ?? "pill";
3239
3254
  chip.dataset.chipStyle = chipStyle;
3240
3255
  chip.className = cn("prompt-area-chip", chipStyle === "inline" && "prompt-area-chip--inline", triggerConfig?.chipClassName);
3241
- chip.textContent = `${seg.trigger}${seg.displayText}`;
3256
+ chip.textContent = chipPlainText(seg);
3242
3257
  chip.setAttribute("role", "button");
3243
3258
  chip.setAttribute("tabindex", "-1");
3244
3259
  editor.appendChild(chip);
@@ -3483,9 +3498,11 @@ function usePromptArea({ value, onChange, triggers = NO_TRIGGERS, disabled = fal
3483
3498
  return;
3484
3499
  }
3485
3500
  const displayText = activeTrigger.config.onSelect?.(suggestion) ?? suggestion.label;
3501
+ const options = activeTrigger.config.chipOptions?.(suggestion);
3486
3502
  const chipData = {
3487
3503
  value: suggestion.value,
3488
3504
  displayText: displayText || suggestion.label,
3505
+ ...options?.sigil !== void 0 ? { sigil: options.sigil } : {},
3489
3506
  data: suggestion.data
3490
3507
  };
3491
3508
  if (chipEditing.trySelectEditingChip(segments, chipData, activeTrigger.config.char, events.pushUndo)) {
@@ -3495,7 +3512,7 @@ function usePromptArea({ value, onChange, triggers = NO_TRIGGERS, disabled = fal
3495
3512
  }, 0);
3496
3513
  return;
3497
3514
  }
3498
- const result = resolveChip(segments, activeTrigger, chipData);
3515
+ const result = resolveChip(segments, activeTrigger, chipData, options?.trailingText);
3499
3516
  onChange(result.segments);
3500
3517
  renderSegmentsToDOM(result.segments);
3501
3518
  onChipAdd?.({
@@ -4420,9 +4437,16 @@ const KIND_ORDER = {
4420
4437
  function cleanCommandName(name) {
4421
4438
  return name.replace(/\s*\(MCP\)$/i, "");
4422
4439
  }
4440
+ function skillToken(skill) {
4441
+ return `$${skill.name}`;
4442
+ }
4443
+ function skillTrailingText(skill) {
4444
+ const token = skillToken(skill);
4445
+ const rest = (skill.defaultPrompt ?? "").split(token).join(" ").replace(/\s+/g, " ").trim();
4446
+ return rest ? ` ${rest} ` : " ";
4447
+ }
4423
4448
  function skillPrompt(skill) {
4424
- const base = skill.defaultPrompt?.trim() || `Use the ${skill.displayName ?? skill.name} skill:`;
4425
- return /\s$/.test(base) ? base : base + " ";
4449
+ return skillToken(skill) + skillTrailingText(skill);
4426
4450
  }
4427
4451
  function mergeComposerRows(sources) {
4428
4452
  const rows = [];
@@ -4582,7 +4606,11 @@ function toolIcon(toolName) {
4582
4606
  case "WebSearch": return Globe;
4583
4607
  case "Task":
4584
4608
  case "Agent": return UsersRound;
4585
- case "TodoWrite": return CheckSquare;
4609
+ case "TodoWrite":
4610
+ case "TaskCreate":
4611
+ case "TaskUpdate":
4612
+ case "TaskGet":
4613
+ case "TaskList": return CheckSquare;
4586
4614
  case "Skill": return Sparkles;
4587
4615
  case "AskUserQuestion": return MessageCircleQuestion;
4588
4616
  case "CodexCommand": return Terminal;
@@ -4610,12 +4638,53 @@ function isMutatingTool(toolName) {
4610
4638
  //#endregion
4611
4639
  //#region src/components/agent/pulse.tsx
4612
4640
  const PULSE_FRAMES = [
4613
- "⋄",
4614
- "◇",
4615
- "◈",
4616
- "◆"
4641
+ "⠋",
4642
+ "⠙",
4643
+ "⠹",
4644
+ "⠸",
4645
+ "⠼",
4646
+ "⠴",
4647
+ "⠦",
4648
+ "⠧",
4649
+ "⠇",
4650
+ "⠏"
4617
4651
  ];
4618
- const PULSE_REST = PULSE_FRAMES[PULSE_FRAMES.length - 1];
4652
+ const listeners = /* @__PURE__ */ new Set();
4653
+ let ticker;
4654
+ let current = "⠿";
4655
+ function pulseFrameAt(now) {
4656
+ return PULSE_FRAMES[Math.floor(now / 90) % PULSE_FRAMES.length];
4657
+ }
4658
+ function tick() {
4659
+ const next = pulseFrameAt(Date.now());
4660
+ if (next === current) return;
4661
+ current = next;
4662
+ for (const listener of listeners) listener();
4663
+ }
4664
+ function subscribe(listener) {
4665
+ listeners.add(listener);
4666
+ if (ticker === void 0) {
4667
+ current = pulseFrameAt(Date.now());
4668
+ ticker = setInterval(tick, 90);
4669
+ }
4670
+ return () => {
4671
+ listeners.delete(listener);
4672
+ if (listeners.size === 0 && ticker !== void 0) {
4673
+ clearInterval(ticker);
4674
+ ticker = void 0;
4675
+ }
4676
+ };
4677
+ }
4678
+ function subscribeRest() {
4679
+ return unsubscribeRest;
4680
+ }
4681
+ function unsubscribeRest() {}
4682
+ function readFrame() {
4683
+ return current;
4684
+ }
4685
+ function readRest() {
4686
+ return "⠿";
4687
+ }
4619
4688
  function usePrefersReducedMotion() {
4620
4689
  const [reduced, setReduced] = useState(false);
4621
4690
  useEffect(() => {
@@ -4631,14 +4700,39 @@ function usePrefersReducedMotion() {
4631
4700
  function usePulse(animated) {
4632
4701
  const reduced = usePrefersReducedMotion();
4633
4702
  const running = animated && !reduced;
4634
- const [frame, setFrame] = useState(0);
4635
- useEffect(() => {
4636
- if (!running) return;
4637
- const timer = setInterval(() => setFrame((f) => f + 1), 150);
4638
- return () => clearInterval(timer);
4639
- }, [running]);
4640
- if (!running) return PULSE_REST;
4641
- return PULSE_FRAMES[frame % PULSE_FRAMES.length];
4703
+ return useSyncExternalStore(running ? subscribe : subscribeRest, running ? readFrame : readRest, readRest);
4704
+ }
4705
+ //#endregion
4706
+ //#region src/components/agent/PromptTokenText.tsx
4707
+ const SkillNamesContext = createContext(void 0);
4708
+ function SkillNamesProvider({ names, children }) {
4709
+ return /* @__PURE__ */ jsx(SkillNamesContext.Provider, {
4710
+ value: names,
4711
+ children
4712
+ });
4713
+ }
4714
+ function PromptTokenText({ text, className }) {
4715
+ const skills = useContext(SkillNamesContext);
4716
+ const tokens = scanPromptTokens(text, { skills });
4717
+ if (tokens.length === 0) return /* @__PURE__ */ jsx("span", {
4718
+ className,
4719
+ children: text
4720
+ });
4721
+ const parts = [];
4722
+ let cursor = 0;
4723
+ for (const [index, token] of tokens.entries()) {
4724
+ if (token.start > cursor) parts.push(text.slice(cursor, token.start));
4725
+ parts.push(/* @__PURE__ */ jsx("span", {
4726
+ className: cn("font-mono", token.kind === "file" ? "text-accent" : "text-info"),
4727
+ children: token.text
4728
+ }, `${token.start}-${index}`));
4729
+ cursor = token.end;
4730
+ }
4731
+ if (cursor < text.length) parts.push(text.slice(cursor));
4732
+ return /* @__PURE__ */ jsx("span", {
4733
+ className,
4734
+ children: parts.map((part, index) => /* @__PURE__ */ jsx(Fragment, { children: part }, index))
4735
+ });
4642
4736
  }
4643
4737
  //#endregion
4644
4738
  //#region src/components/terminal/affordances.tsx
@@ -5075,6 +5169,129 @@ const TerminalMarkdown = memo(function TerminalMarkdown({ children, streaming, c
5075
5169
  });
5076
5170
  }, (prev, next) => prev.children === next.children && prev.streaming === next.streaming && prev.className === next.className);
5077
5171
  //#endregion
5172
+ //#region src/components/terminal/tool-run.ts
5173
+ function isPeerSend(item) {
5174
+ return item.kind === "tool_call" && isPeerSendTool(item.name);
5175
+ }
5176
+ function peerName(origin) {
5177
+ return origin.name ?? origin.sessionId.slice(0, 8);
5178
+ }
5179
+ function peerNamesOf(items) {
5180
+ const names = /* @__PURE__ */ new Map();
5181
+ for (const item of items) {
5182
+ if (item.kind === "user") {
5183
+ if (item.origin?.name) names.set(item.origin.sessionId, item.origin.name);
5184
+ continue;
5185
+ }
5186
+ if (item.kind === "tool_call" && isPeerSendTool(item.name) && item.result && !item.result.isError) {
5187
+ const delivered = peerDeliveredTo(item.result.text);
5188
+ if (delivered?.name) names.set(delivered.sessionId, delivered.name);
5189
+ }
5190
+ }
5191
+ return names;
5192
+ }
5193
+ function peerSendTarget(item, names) {
5194
+ const delivered = item.result && !item.result.isError ? peerDeliveredTo(item.result.text) : void 0;
5195
+ const addressed = item.input?.sessionId;
5196
+ const sessionId = delivered?.sessionId ?? (typeof addressed === "string" ? addressed : void 0);
5197
+ if (sessionId === void 0) return "peer";
5198
+ return delivered?.name ?? names?.get(sessionId) ?? sessionId.slice(0, 8);
5199
+ }
5200
+ function peerSendText(item) {
5201
+ const text = item.input?.text;
5202
+ return typeof text === "string" ? text : "";
5203
+ }
5204
+ function peerOneLine(text) {
5205
+ return text.replace(/\s+/g, " ").trim();
5206
+ }
5207
+ function foldsTogether(a, b) {
5208
+ if (isPeerSend(a) || isPeerSend(b)) return false;
5209
+ return a.parentToolUseId === b.parentToolUseId;
5210
+ }
5211
+ function toolFamily(name) {
5212
+ if (isShellTool(name)) return "shell";
5213
+ const mcp = /^mcp__([^_]+(?:_[^_]+)*?)__/.exec(name);
5214
+ if (mcp?.[1]) return mcp[1].replace(/_/g, "-");
5215
+ return name.toLowerCase();
5216
+ }
5217
+ function planRun(items) {
5218
+ return items.length === 1 ? {
5219
+ kind: "call",
5220
+ item: items[0]
5221
+ } : {
5222
+ kind: "summary",
5223
+ items
5224
+ };
5225
+ }
5226
+ function runSummary(items, busy) {
5227
+ const verb = busy ? "Running " : "Ran ";
5228
+ const tail = busy ? "…" : "";
5229
+ const n = items.length;
5230
+ const counts = /* @__PURE__ */ new Map();
5231
+ for (const item of items) {
5232
+ const family = toolFamily(item.name);
5233
+ counts.set(family, (counts.get(family) ?? 0) + 1);
5234
+ }
5235
+ if (counts.size === 1 && counts.has("shell")) return `${verb}${n} shell command${n === 1 ? "" : "s"}${tail}`;
5236
+ const breakdown = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([family, count]) => `${count} ${family}`).join(", ");
5237
+ return `${verb}${n} tool${n === 1 ? "" : "s"} · ${breakdown}${tail}`;
5238
+ }
5239
+ function clip(text, max = 80) {
5240
+ return text.length > max ? text.slice(0, max - 1) + "…" : text;
5241
+ }
5242
+ function trimmed(value) {
5243
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
5244
+ }
5245
+ function taskLabel(task) {
5246
+ return `${task.name}(${taskIdentity(task)})`;
5247
+ }
5248
+ function taskIdentity(task) {
5249
+ const input = task.input;
5250
+ const description = trimmed(input?.description);
5251
+ const agent = trimmed(input?.subagent_type);
5252
+ return agent && description ? `${agent} · ${clip(description)}` : agent ?? (description ? clip(description) : toolInputPreview(task.input));
5253
+ }
5254
+ function taskBrief(task) {
5255
+ const input = task.input;
5256
+ return trimmed(input?.prompt);
5257
+ }
5258
+ function callBusy(call) {
5259
+ return call.status === "running" || call.status === "pending";
5260
+ }
5261
+ function callFailed(call) {
5262
+ return call.status === "failed" || call.result?.isError === true;
5263
+ }
5264
+ function runFailed(items) {
5265
+ const last = items[items.length - 1];
5266
+ return last !== void 0 && callFailed(last);
5267
+ }
5268
+ function taskBusy(task, children) {
5269
+ return callBusy(task) || children.some((child) => child.kind === "tool_call" && callBusy(child));
5270
+ }
5271
+ function taskFailed(task) {
5272
+ return callFailed(task);
5273
+ }
5274
+ function taskSummary$1(task, children) {
5275
+ const busy = taskBusy(task, children);
5276
+ const calls = children.reduce((n, child) => n + (child.kind === "tool_call" ? 1 : 0), 0);
5277
+ const label = taskLabel(task);
5278
+ if (calls === 0) return busy ? `${label} · working…` : `${label} · done`;
5279
+ return `${label} · ${calls} tool${calls === 1 ? "" : "s"}${busy ? "…" : ""}`;
5280
+ }
5281
+ //#endregion
5282
+ //#region src/components/terminal/peer-names.tsx
5283
+ const PeerNamesContext = createContext(/* @__PURE__ */ new Map());
5284
+ function PeerNamesProvider({ items, children }) {
5285
+ const names = useMemo(() => peerNamesOf(items), [items]);
5286
+ return /* @__PURE__ */ jsx(PeerNamesContext.Provider, {
5287
+ value: names,
5288
+ children
5289
+ });
5290
+ }
5291
+ function usePeerNames() {
5292
+ return useContext(PeerNamesContext);
5293
+ }
5294
+ //#endregion
5078
5295
  //#region src/components/terminal/press.tsx
5079
5296
  const DRAG_SLOP = 4;
5080
5297
  function Pressable({ onPress, expanded, className, children }) {
@@ -5299,72 +5516,6 @@ function evict(cache, keep) {
5299
5516
  }
5300
5517
  }
5301
5518
  //#endregion
5302
- //#region src/components/terminal/tool-run.ts
5303
- function foldsTogether(a, b) {
5304
- return a.parentToolUseId === b.parentToolUseId;
5305
- }
5306
- function toolFamily(name) {
5307
- if (isShellTool(name)) return "shell";
5308
- const mcp = /^mcp__([^_]+(?:_[^_]+)*?)__/.exec(name);
5309
- if (mcp?.[1]) return mcp[1].replace(/_/g, "-");
5310
- return name.toLowerCase();
5311
- }
5312
- function runSummary(items, busy) {
5313
- const verb = busy ? "Running " : "Ran ";
5314
- const tail = busy ? "…" : "";
5315
- const n = items.length;
5316
- const counts = /* @__PURE__ */ new Map();
5317
- for (const item of items) {
5318
- const family = toolFamily(item.name);
5319
- counts.set(family, (counts.get(family) ?? 0) + 1);
5320
- }
5321
- if (counts.size === 1 && counts.has("shell")) return `${verb}${n} shell command${n === 1 ? "" : "s"}${tail}`;
5322
- const breakdown = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([family, count]) => `${count} ${family}`).join(", ");
5323
- return `${verb}${n} tool${n === 1 ? "" : "s"} · ${breakdown}${tail}`;
5324
- }
5325
- function clip(text, max = 80) {
5326
- return text.length > max ? text.slice(0, max - 1) + "…" : text;
5327
- }
5328
- function trimmed(value) {
5329
- return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
5330
- }
5331
- function taskLabel(task) {
5332
- return `${task.name}(${taskIdentity(task)})`;
5333
- }
5334
- function taskIdentity(task) {
5335
- const input = task.input;
5336
- const description = trimmed(input?.description);
5337
- const agent = trimmed(input?.subagent_type);
5338
- return agent && description ? `${agent} · ${clip(description)}` : agent ?? (description ? clip(description) : toolInputPreview(task.input));
5339
- }
5340
- function taskBrief(task) {
5341
- const input = task.input;
5342
- return trimmed(input?.prompt);
5343
- }
5344
- function callBusy(call) {
5345
- return call.status === "running" || call.status === "pending";
5346
- }
5347
- function callFailed(call) {
5348
- return call.status === "failed" || call.result?.isError === true;
5349
- }
5350
- function runFailed(items) {
5351
- const last = items[items.length - 1];
5352
- return last !== void 0 && callFailed(last);
5353
- }
5354
- function taskBusy(task, children) {
5355
- return callBusy(task) || children.some((child) => child.kind === "tool_call" && callBusy(child));
5356
- }
5357
- function taskFailed(task) {
5358
- return callFailed(task);
5359
- }
5360
- function taskSummary$1(task, children) {
5361
- const busy = taskBusy(task, children);
5362
- const calls = children.reduce((n, child) => n + (child.kind === "tool_call" ? 1 : 0), 0);
5363
- const label = taskLabel(task);
5364
- if (calls === 0) return busy ? `${label} · working…` : `${label} · done`;
5365
- return `${label} · ${calls} tool${calls === 1 ? "" : "s"}${busy ? "…" : ""}`;
5366
- }
5367
- //#endregion
5368
5519
  //#region src/components/terminal/todos.ts
5369
5520
  const PREVIEW_TODOS = 8;
5370
5521
  const GLYPH = {
@@ -5399,21 +5550,81 @@ function clipToChars(lines, maxChars) {
5399
5550
  }
5400
5551
  return out;
5401
5552
  }
5553
+ function peerLabel(origin) {
5554
+ const who = origin.name ? `${origin.name} (${origin.sessionId.slice(0, 8)})` : origin.sessionId.slice(0, 8);
5555
+ return `message from ${origin.engine ? `${origin.engine} session ` : "session "}${who}`;
5556
+ }
5557
+ function PeerRow({ glyph, who, body, tone = "peer", clip }) {
5558
+ const line = /* @__PURE__ */ jsxs(Fragment$1, { children: [
5559
+ /* @__PURE__ */ jsx(Ink, {
5560
+ tone,
5561
+ bold: true,
5562
+ children: who
5563
+ }),
5564
+ ": ",
5565
+ body
5566
+ ] });
5567
+ return /* @__PURE__ */ jsx(Row, {
5568
+ glyph,
5569
+ glyphTone: tone,
5570
+ tone,
5571
+ children: clip ? /* @__PURE__ */ jsx("span", {
5572
+ className: "term-clip-1",
5573
+ children: line
5574
+ }) : line
5575
+ });
5576
+ }
5577
+ function PeerSendRow({ item }) {
5578
+ const [open, setOpen] = useState(false);
5579
+ const names = usePeerNames();
5580
+ const reveal = useRevealOnOpen(open);
5581
+ const lines = peerSendText(item).split("\n");
5582
+ const tone = item.status === "failed" || item.result?.isError === true ? "red" : "peer";
5583
+ return /* @__PURE__ */ jsx(WithActions, {
5584
+ actions: /* @__PURE__ */ jsx(BookmarkAction, { id: item.id }),
5585
+ children: /* @__PURE__ */ jsxs("div", {
5586
+ ref: reveal,
5587
+ className: open ? "term-open" : void 0,
5588
+ children: [/* @__PURE__ */ jsx(Pressable, {
5589
+ onPress: () => setOpen((v) => !v),
5590
+ expanded: open,
5591
+ children: /* @__PURE__ */ jsx(PeerRow, {
5592
+ glyph: "↤",
5593
+ who: peerSendTarget(item, names),
5594
+ tone,
5595
+ clip: !open,
5596
+ body: open ? lines[0] || "\xA0" : peerOneLine(lines.join(" "))
5597
+ })
5598
+ }), open ? /* @__PURE__ */ jsxs("div", { children: [lines.slice(1).map((line, index) => /* @__PURE__ */ jsx(Row, {
5599
+ tone,
5600
+ children: line || "\xA0"
5601
+ }, index)), item.result ? /* @__PURE__ */ jsx(Row, {
5602
+ tone: "faint",
5603
+ children: peerOneLine(item.result.text)
5604
+ }) : null] }) : null]
5605
+ })
5606
+ });
5607
+ }
5402
5608
  function UserRow({ item }) {
5403
5609
  return /* @__PURE__ */ jsx(WithActions, {
5404
5610
  actions: /* @__PURE__ */ jsx(BookmarkAction, { id: item.id }),
5405
5611
  children: /* @__PURE__ */ jsxs("div", {
5406
5612
  className: "term-user",
5613
+ "data-peer": item.origin ? "" : void 0,
5407
5614
  children: [item.attachments?.length ? /* @__PURE__ */ jsx(Row, {
5408
5615
  glyph: "❯",
5409
5616
  glyphTone: "dim",
5410
5617
  tone: "dim",
5411
5618
  children: item.attachments.map((attachment) => attachment.name).join(", ")
5412
- }) : null, item.text ? item.text.split("\n").map((line, index) => /* @__PURE__ */ jsx(Row, {
5413
- glyph: index === 0 ? "❯" : void 0,
5619
+ }) : null, item.text ? item.text.split("\n").map((line, index) => item.origin && index === 0 ? /* @__PURE__ */ jsx(PeerRow, {
5620
+ glyph: "↦",
5621
+ who: peerName(item.origin),
5622
+ body: line ? /* @__PURE__ */ jsx(PromptTokenText, { text: line }) : "\xA0"
5623
+ }, index) : /* @__PURE__ */ jsx(Row, {
5624
+ glyph: !item.origin && index === 0 ? "❯" : void 0,
5414
5625
  glyphTone: "dim",
5415
- tone: "fg",
5416
- children: line || "\xA0"
5626
+ tone: item.origin ? "peer" : "fg",
5627
+ children: line ? /* @__PURE__ */ jsx(PromptTokenText, { text: line }) : "\xA0"
5417
5628
  }, index)) : null]
5418
5629
  })
5419
5630
  });
@@ -5497,6 +5708,7 @@ function ToolRow({ item }) {
5497
5708
  /* @__PURE__ */ jsx(Ink, {
5498
5709
  bold: true,
5499
5710
  tone: "bright",
5711
+ className: busy ? "term-shimmer" : void 0,
5500
5712
  children: title ?? item.name
5501
5713
  }),
5502
5714
  /* @__PURE__ */ jsxs(Ink, {
@@ -5632,11 +5844,19 @@ function ToolRunRow({ items }) {
5632
5844
  glyph: busy ? pulse : void 0,
5633
5845
  glyphTone: busy ? "mark" : void 0,
5634
5846
  tone: failed ? "red" : "dim",
5635
- children: runSummary(items, busy)
5847
+ children: busy ? /* @__PURE__ */ jsx("span", {
5848
+ className: "term-shimmer",
5849
+ children: runSummary(items, busy)
5850
+ }) : runSummary(items, busy)
5636
5851
  })
5637
5852
  }), open ? /* @__PURE__ */ jsx("div", { children: items.map((item) => /* @__PURE__ */ jsx(ToolRow, { item }, item.id)) }) : null]
5638
5853
  });
5639
5854
  }
5855
+ function RunRow({ items }) {
5856
+ const plan = planRun(items);
5857
+ if (plan.kind === "summary") return /* @__PURE__ */ jsx(ToolRunRow, { items: plan.items });
5858
+ return isPeerSend(plan.item) ? /* @__PURE__ */ jsx(PeerSendRow, { item: plan.item }) : /* @__PURE__ */ jsx(ToolRow, { item: plan.item });
5859
+ }
5640
5860
  function TurnResultRow$1({ item }) {
5641
5861
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs(Row, {
5642
5862
  tone: item.isError ? "red" : "faint",
@@ -5645,7 +5865,7 @@ function TurnResultRow$1({ item }) {
5645
5865
  " · ",
5646
5866
  formatDuration(item.durationMs),
5647
5867
  " · ",
5648
- formatCost(item.totalCostUsd)
5868
+ formatCost(item.costUsd ?? item.totalCostUsd)
5649
5869
  ]
5650
5870
  }), item.errors?.map((message, index) => /* @__PURE__ */ jsx(Row, {
5651
5871
  tone: "red",
@@ -5714,7 +5934,10 @@ function WorkingRow({ label, startedAt, tokens }) {
5714
5934
  glyph: pulse,
5715
5935
  glyphTone: "mark",
5716
5936
  tone: "mark",
5717
- children: [label, readings.length ? /* @__PURE__ */ jsxs(Ink, {
5937
+ children: [/* @__PURE__ */ jsx("span", {
5938
+ className: "term-shimmer",
5939
+ children: label
5940
+ }), readings.length ? /* @__PURE__ */ jsxs(Ink, {
5718
5941
  tone: "faint",
5719
5942
  children: [
5720
5943
  " (",
@@ -5795,9 +6018,15 @@ function Composer({ onSend, onInterrupt, busy, disabled, placeholder = "Message
5795
6018
  }),
5796
6019
  insertAsText: (suggestion) => {
5797
6020
  const row = byTag.get(String(suggestion.data));
5798
- if (row?.kind === "skill") return skillPrompt(row.skill);
5799
6021
  return row?.kind === "client" && row.command.requiresArgs ? `/${row.name} ` : void 0;
5800
6022
  },
6023
+ chipOptions: (suggestion) => {
6024
+ const row = byTag.get(String(suggestion.data));
6025
+ return row?.kind === "skill" ? {
6026
+ sigil: "$",
6027
+ trailingText: skillTrailingText(row.skill)
6028
+ } : void 0;
6029
+ },
5801
6030
  onSelect: (suggestion) => suggestion.value,
5802
6031
  chipClassName: "font-mono"
5803
6032
  }));
@@ -7384,7 +7613,7 @@ function taskChildItems(block) {
7384
7613
  }
7385
7614
  function pushLeaf(out, item, index) {
7386
7615
  const previous = out.at(-1);
7387
- if (isRunCall(item)) {
7616
+ if (isRunCall(item) && !isPeerSend(item)) {
7388
7617
  if (previous && "run" in previous && foldsTogether(previous.run[0], item)) {
7389
7618
  previous.run.push(item);
7390
7619
  previous.indices.push(index);
@@ -7450,12 +7679,15 @@ function terminalBlocks(items, offset = 0, fold = true) {
7450
7679
  return out;
7451
7680
  }
7452
7681
  function needsBlank(previous, next) {
7453
- if (previous.kind === "tool_call" && next.kind === "tool_call") return false;
7454
- return true;
7682
+ if (isPeerSend(previous) || isPeerSend(next)) return true;
7683
+ return !(previous.kind === "tool_call" && next.kind === "tool_call");
7684
+ }
7685
+ function blockKind(block) {
7686
+ if (!("item" in block)) return "tool_call";
7687
+ return isPeerSend(block.item) ? "peer_send" : block.item.kind;
7455
7688
  }
7456
7689
  function blockNeedsBlank(previous, next) {
7457
- const kind = (block) => "item" in block ? block.item.kind : "tool_call";
7458
- return !(kind(previous) === "tool_call" && kind(next) === "tool_call");
7690
+ return !(blockKind(previous) === "tool_call" && blockKind(next) === "tool_call");
7459
7691
  }
7460
7692
  //#endregion
7461
7693
  //#region src/components/agent/use-subagent-frame.ts
@@ -8218,7 +8450,7 @@ function SessionInfoDialog({ state, client, sessionId, open, onOpenChange }) {
8218
8450
  /* @__PURE__ */ jsx(DialogRow, {
8219
8451
  label: "Cost",
8220
8452
  mono: true,
8221
- children: formatCost(state.totalCostUsd)
8453
+ children: formatCost(state.costUsd ?? (state.totalCostUsd || void 0))
8222
8454
  })
8223
8455
  ]
8224
8456
  }),
@@ -8528,7 +8760,7 @@ function StatusBar({ state, rateLimits, connected, connection, onOpenStatus, onO
8528
8760
  /* @__PURE__ */ jsx("span", { className: "flex-1" }),
8529
8761
  /* @__PURE__ */ jsx("span", {
8530
8762
  className: "font-mono text-label text-fg-3",
8531
- children: formatCost(state.totalCostUsd)
8763
+ children: formatCost(state.costUsd ?? (state.totalCostUsd || void 0))
8532
8764
  }),
8533
8765
  actions ? /* @__PURE__ */ jsx("span", {
8534
8766
  className: "self-center",
@@ -9210,12 +9442,20 @@ function itemHeight(item, m) {
9210
9442
  exact: true
9211
9443
  };
9212
9444
  if (item.attachments?.length) acc = add(acc, rowH(item.attachments.map((a) => a.name).join(", "), m, { extraPx }));
9213
- if (item.text) for (const line of item.text.split("\n")) acc = add(acc, rowH(line || " ", m, { extraPx }));
9445
+ if (item.text) {
9446
+ const who = item.origin ? `${peerName(item.origin)}: ` : "";
9447
+ for (const [index, line] of item.text.split("\n").entries()) acc = add(acc, rowH((index === 0 ? who : "") + (line || " "), m, { extraPx }));
9448
+ }
9214
9449
  return acc;
9215
9450
  }
9216
9451
  case "assistant_text": return markdownHeight(item.text, m, extraPx);
9217
9452
  case "thinking": return rowH(item.text, m, { extraPx });
9218
- case "tool_call": return toolRowHeight(item, m, extraPx);
9453
+ case "tool_call":
9454
+ if (isPeerSend(item)) return {
9455
+ px: m.line + extraPx,
9456
+ exact: true
9457
+ };
9458
+ return toolRowHeight(item, m, extraPx);
9219
9459
  case "turn_result": {
9220
9460
  let acc = rowH(`${item.isError ? item.subtype : "done"} · ${formatDuration(item.durationMs)} · ${formatCost(item.totalCostUsd)}`, m, { extraPx });
9221
9461
  for (const message of item.errors ?? []) acc = add(acc, rowH(message, m, { extraPx }));
@@ -9233,8 +9473,10 @@ function itemHeight(item, m) {
9233
9473
  function blockHeight(block, m) {
9234
9474
  if ("task" in block) return rowH(taskSummary$1(block.task, taskChildItems(block)), m);
9235
9475
  if ("run" in block) {
9236
- const busy = block.run.some((item) => item.status === "running" || item.status === "pending");
9237
- return rowH(runSummary(block.run, busy), m);
9476
+ const plan = planRun(block.run);
9477
+ if (plan.kind === "call") return itemHeight(plan.item, m);
9478
+ const busy = plan.items.some((item) => item.status === "running" || item.status === "pending");
9479
+ return rowH(runSummary(plan.items, busy), m);
9238
9480
  }
9239
9481
  return itemHeight(block.item, m);
9240
9482
  }
@@ -10070,6 +10312,20 @@ function repinToBottom(stick) {
10070
10312
  };
10071
10313
  state.scrollTop = state.calculatedTargetScrollTop;
10072
10314
  }
10315
+ function absorbScrollerResize(stick, difference) {
10316
+ const { state } = stick;
10317
+ state.resizeDifference = difference;
10318
+ requestAnimationFrame(() => {
10319
+ setTimeout(() => {
10320
+ if (state.resizeDifference === difference) state.resizeDifference = 0;
10321
+ }, 1);
10322
+ });
10323
+ if (state.animation?.ignoreEscapes) {
10324
+ state.scrollTop = state.calculatedTargetScrollTop;
10325
+ return;
10326
+ }
10327
+ stick.scrollToBottom("instant");
10328
+ }
10073
10329
  function useTranscriptJumps(options) {
10074
10330
  const { rows, terminal, stickyPrompt, epoch, promptRows, scrollElement, rowsRef, virtualizer, stick, jumpToRecapRef, repinRef } = options;
10075
10331
  const aimTimer = useRef(void 0);
@@ -10147,7 +10403,7 @@ function TerminalItemView({ item, fileUrl }) {
10147
10403
  case "user": return /* @__PURE__ */ jsx(UserRow, { item });
10148
10404
  case "assistant_text": return /* @__PURE__ */ jsx(AssistantRow, { item });
10149
10405
  case "thinking": return /* @__PURE__ */ jsx(ThinkingRow, { item });
10150
- case "tool_call": return /* @__PURE__ */ jsx(ToolRow, { item });
10406
+ case "tool_call": return isPeerSend(item) ? /* @__PURE__ */ jsx(PeerSendRow, { item }) : /* @__PURE__ */ jsx(ToolRow, { item });
10151
10407
  case "turn_result": return /* @__PURE__ */ jsx(TurnResultRow$1, { item });
10152
10408
  case "notice": return /* @__PURE__ */ jsx(NoticeRow$1, { item });
10153
10409
  case "compaction": return /* @__PURE__ */ jsx(CompactionRow$1, { item });
@@ -10201,14 +10457,17 @@ function TaskRow({ block, fileUrl, onOpenSubagent }) {
10201
10457
  glyph: busy ? pulse : "●",
10202
10458
  glyphTone: failed ? "red" : busy ? "mark" : "dim",
10203
10459
  tone: failed ? "red" : "green",
10204
- children: taskSummary$1(block.task, children)
10460
+ children: busy ? /* @__PURE__ */ jsx("span", {
10461
+ className: "term-shimmer",
10462
+ children: taskSummary$1(block.task, children)
10463
+ }) : taskSummary$1(block.task, children)
10205
10464
  })
10206
10465
  }), open ? /* @__PURE__ */ jsxs("div", {
10207
10466
  className: "term-nested",
10208
10467
  children: [brief ? /* @__PURE__ */ jsx(BriefRow, {
10209
10468
  text: brief,
10210
10469
  terminal: true
10211
- }) : null, block.children.map((leaf, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(block.children[index - 1], leaf) ? /* @__PURE__ */ jsx(Blank, {}) : null, "run" in leaf ? /* @__PURE__ */ jsx(ToolRunRow, { items: leaf.run }) : /* @__PURE__ */ jsx(TerminalItemView, {
10470
+ }) : null, block.children.map((leaf, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(block.children[index - 1], leaf) ? /* @__PURE__ */ jsx(Blank, {}) : null, "run" in leaf ? /* @__PURE__ */ jsx(RunRow, { items: leaf.run }) : /* @__PURE__ */ jsx(TerminalItemView, {
10212
10471
  item: leaf.item,
10213
10472
  fileUrl
10214
10473
  })] }, leaf.key))]
@@ -10238,23 +10497,26 @@ function working(state) {
10238
10497
  function TerminalTranscript({ state, fileUrl, fontSize, lineHeight, affordances, className }) {
10239
10498
  const runStartedAt = useRunStart$1(state.status);
10240
10499
  const blocks = useMemo(() => terminalBlocks(state.items), [state.items]);
10241
- return /* @__PURE__ */ jsxs(TerminalSurface, {
10500
+ return /* @__PURE__ */ jsx(TerminalSurface, {
10242
10501
  fontSize,
10243
10502
  lineHeight,
10244
10503
  affordances,
10245
10504
  bleed: "1ch",
10246
10505
  className: cn("term-transcript", className),
10247
- children: [blocks.map((block, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(blocks[index - 1], block) ? /* @__PURE__ */ jsx(Blank, {}) : null, "run" in block ? /* @__PURE__ */ jsx(ToolRunRow, { items: block.run }) : "item" in block ? /* @__PURE__ */ jsx(TerminalItemView, {
10248
- item: block.item,
10249
- fileUrl
10250
- }) : /* @__PURE__ */ jsx(TaskRow, {
10251
- block,
10252
- fileUrl
10253
- })] }, block.key)), working(state) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx(Blank, {}) : null, /* @__PURE__ */ jsx(WorkingRow, {
10254
- label: state.status === "starting" ? "Starting…" : "Working…",
10255
- startedAt: runStartedAt,
10256
- tokens: state.contextUsage?.totalTokens
10257
- })] }) : null]
10506
+ children: /* @__PURE__ */ jsxs(PeerNamesProvider, {
10507
+ items: state.items,
10508
+ children: [blocks.map((block, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && blockNeedsBlank(blocks[index - 1], block) ? /* @__PURE__ */ jsx(Blank, {}) : null, "run" in block ? /* @__PURE__ */ jsx(RunRow, { items: block.run }) : "item" in block ? /* @__PURE__ */ jsx(TerminalItemView, {
10509
+ item: block.item,
10510
+ fileUrl
10511
+ }) : /* @__PURE__ */ jsx(TaskRow, {
10512
+ block,
10513
+ fileUrl
10514
+ })] }, block.key)), working(state) ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx(Blank, {}) : null, /* @__PURE__ */ jsx(WorkingRow, {
10515
+ label: state.status === "starting" ? "Starting…" : "Working…",
10516
+ startedAt: runStartedAt,
10517
+ tokens: state.contextUsage?.totalTokens
10518
+ })] }) : null]
10519
+ })
10258
10520
  });
10259
10521
  }
10260
10522
  //#endregion
@@ -10304,30 +10566,6 @@ function MessageContent({ className, ...props }) {
10304
10566
  });
10305
10567
  }
10306
10568
  //#endregion
10307
- //#region src/components/agent/PromptTokenText.tsx
10308
- function PromptTokenText({ text, className }) {
10309
- const tokens = scanPromptTokens(text);
10310
- if (tokens.length === 0) return /* @__PURE__ */ jsx("span", {
10311
- className,
10312
- children: text
10313
- });
10314
- const parts = [];
10315
- let cursor = 0;
10316
- for (const [index, token] of tokens.entries()) {
10317
- if (token.start > cursor) parts.push(text.slice(cursor, token.start));
10318
- parts.push(/* @__PURE__ */ jsx("span", {
10319
- className: cn("font-mono", token.kind === "file" ? "text-accent" : "text-info"),
10320
- children: token.text
10321
- }, `${token.start}-${index}`));
10322
- cursor = token.end;
10323
- }
10324
- if (cursor < text.length) parts.push(text.slice(cursor));
10325
- return /* @__PURE__ */ jsx("span", {
10326
- className,
10327
- children: parts.map((part, index) => /* @__PURE__ */ jsx(Fragment, { children: part }, index))
10328
- });
10329
- }
10330
- //#endregion
10331
10569
  //#region src/components/agent/Reasoning.tsx
10332
10570
  function Reasoning({ children, isStreaming = false, defaultOpen, className }) {
10333
10571
  const [open, setOpen] = useState(defaultOpen ?? isStreaming);
@@ -10635,7 +10873,7 @@ function TurnResultRow({ item }) {
10635
10873
  " · ",
10636
10874
  formatDuration(item.durationMs),
10637
10875
  " · ",
10638
- formatCost(item.totalCostUsd)
10876
+ formatCost(item.costUsd ?? item.totalCostUsd)
10639
10877
  ]
10640
10878
  }),
10641
10879
  /* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-border" })
@@ -10664,10 +10902,17 @@ function TranscriptItemView({ item, fileUrl, attachmentUrl, hostImage, terminal
10664
10902
  switch (item.kind) {
10665
10903
  case "user": return /* @__PURE__ */ jsxs(Message, {
10666
10904
  from: "user",
10667
- children: [item.attachments?.length ? /* @__PURE__ */ jsx(SentAttachments, {
10668
- attachments: item.attachments,
10669
- attachmentUrl
10670
- }) : null, item.text ? /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(PromptTokenText, { text: item.text }) }) : null]
10905
+ children: [
10906
+ item.origin ? /* @__PURE__ */ jsx("div", {
10907
+ className: "wd-peer-origin text-xs text-muted-foreground",
10908
+ children: peerLabel(item.origin)
10909
+ }) : null,
10910
+ item.attachments?.length ? /* @__PURE__ */ jsx(SentAttachments, {
10911
+ attachments: item.attachments,
10912
+ attachmentUrl
10913
+ }) : null,
10914
+ item.text ? /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(PromptTokenText, { text: item.text }) }) : null
10915
+ ]
10671
10916
  });
10672
10917
  case "assistant_text": return /* @__PURE__ */ jsx(Message, {
10673
10918
  from: "assistant",
@@ -10842,8 +11087,9 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
10842
11087
  const observer = new ResizeObserver(() => {
10843
11088
  const height = scrollElement.clientHeight;
10844
11089
  if (height === last) return;
11090
+ const difference = height - last;
10845
11091
  last = height;
10846
- if (stick.state.isAtBottom) stick.scrollToBottom("instant");
11092
+ if (stick.state.isAtBottom) absorbScrollerResize(stick, difference);
10847
11093
  });
10848
11094
  observer.observe(scrollElement);
10849
11095
  return () => observer.disconnect();
@@ -10963,59 +11209,62 @@ function TranscriptRows({ rows, boundary, since, terminal, replaying, stickyProm
10963
11209
  "data-slot": "transcript-rows",
10964
11210
  className: "relative w-full",
10965
11211
  style: { height: virtualizer.getTotalSize() },
10966
- children: [virtualizer.getVirtualItems().map((virtualRow) => {
10967
- const row = rows[virtualRow.index];
10968
- const gapClass = virtualRow.index > 0 && (!terminal || gapBefore(rows, virtualRow.index)) ? gap.className : void 0;
10969
- const content = "run" in row ? /* @__PURE__ */ jsx("div", {
10970
- className: cn(read(boundary, row.index) && "opacity-45"),
10971
- children: /* @__PURE__ */ jsx(ToolRunRow, { items: row.run })
10972
- }) : "item" in row ? /* @__PURE__ */ jsx("div", {
10973
- className: cn(read(boundary, row.index) && "opacity-45", nestedClass(row.item, frameParentId)),
10974
- children: /* @__PURE__ */ jsx(TranscriptItemView, {
10975
- item: row.item,
10976
- fileUrl,
10977
- attachmentUrl,
10978
- hostImage,
11212
+ children: [/* @__PURE__ */ jsx(PeerNamesProvider, {
11213
+ items,
11214
+ children: virtualizer.getVirtualItems().map((virtualRow) => {
11215
+ const row = rows[virtualRow.index];
11216
+ const gapClass = virtualRow.index > 0 && (!terminal || gapBefore(rows, virtualRow.index)) ? gap.className : void 0;
11217
+ const content = "run" in row ? /* @__PURE__ */ jsx("div", {
11218
+ className: cn(read(boundary, row.index) && "opacity-45"),
11219
+ children: /* @__PURE__ */ jsx(RunRow, { items: row.run })
11220
+ }) : "item" in row ? /* @__PURE__ */ jsx("div", {
11221
+ className: cn(read(boundary, row.index) && "opacity-45", nestedClass(row.item, frameParentId)),
11222
+ children: /* @__PURE__ */ jsx(TranscriptItemView, {
11223
+ item: row.item,
11224
+ fileUrl,
11225
+ attachmentUrl,
11226
+ hostImage,
11227
+ terminal
11228
+ })
11229
+ }) : "text" in row && row.key === "brief" ? /* @__PURE__ */ jsx(BriefRow, {
11230
+ text: row.text,
10979
11231
  terminal
10980
- })
10981
- }) : "text" in row && row.key === "brief" ? /* @__PURE__ */ jsx(BriefRow, {
10982
- text: row.text,
10983
- terminal
10984
- }) : "line" in row ? /* @__PURE__ */ jsx(RecapRow, {
10985
- line: row.line,
10986
- since,
10987
- terminal
10988
- }) : /* @__PURE__ */ jsx("div", {
10989
- className: cn(read(boundary, row.index) && "opacity-45"),
10990
- children: /* @__PURE__ */ jsx(TaskRow, {
10991
- block: row,
10992
- fileUrl,
10993
- onOpenSubagent
10994
- })
10995
- });
10996
- if (stickyPrompt && "item" in row && row.item.kind === "user" && parentOf(row.item) === void 0) {
10997
- const next = promptRows.find((index) => index > virtualRow.index);
10998
- const laneEnd = next === void 0 ? virtualizer.getTotalSize() : measurements[next]?.start ?? virtualRow.start;
10999
- return /* @__PURE__ */ jsx(StickyPromptLane, {
11000
- top: virtualRow.start,
11001
- height: Math.max(laneEnd - virtualRow.start, 0),
11002
- gapClass,
11003
- gapPx: gap.px,
11004
- terminal,
11005
- scrollRoot: scrollElement,
11006
- index: virtualRow.index,
11007
- measureRef: virtualizer.measureElement,
11008
- head: terminal ? content : promptHeadText(row.item),
11009
- content
11232
+ }) : "line" in row ? /* @__PURE__ */ jsx(RecapRow, {
11233
+ line: row.line,
11234
+ since,
11235
+ terminal
11236
+ }) : /* @__PURE__ */ jsx("div", {
11237
+ className: cn(read(boundary, row.index) && "opacity-45"),
11238
+ children: /* @__PURE__ */ jsx(TaskRow, {
11239
+ block: row,
11240
+ fileUrl,
11241
+ onOpenSubagent
11242
+ })
11243
+ });
11244
+ if (stickyPrompt && "item" in row && row.item.kind === "user" && parentOf(row.item) === void 0) {
11245
+ const next = promptRows.find((index) => index > virtualRow.index);
11246
+ const laneEnd = next === void 0 ? virtualizer.getTotalSize() : measurements[next]?.start ?? virtualRow.start;
11247
+ return /* @__PURE__ */ jsx(StickyPromptLane, {
11248
+ top: virtualRow.start,
11249
+ height: Math.max(laneEnd - virtualRow.start, 0),
11250
+ gapClass,
11251
+ gapPx: gap.px,
11252
+ terminal,
11253
+ scrollRoot: scrollElement,
11254
+ index: virtualRow.index,
11255
+ measureRef: virtualizer.measureElement,
11256
+ head: terminal ? content : promptHeadText(row.item),
11257
+ content
11258
+ }, row.key);
11259
+ }
11260
+ return /* @__PURE__ */ jsx("div", {
11261
+ ref: virtualizer.measureElement,
11262
+ "data-index": virtualRow.index,
11263
+ className: cn("absolute inset-x-0 top-0", gapClass),
11264
+ style: { transform: `translateY(${virtualRow.start}px)` },
11265
+ children: content
11010
11266
  }, row.key);
11011
- }
11012
- return /* @__PURE__ */ jsx("div", {
11013
- ref: virtualizer.measureElement,
11014
- "data-index": virtualRow.index,
11015
- className: cn("absolute inset-x-0 top-0", gapClass),
11016
- style: { transform: `translateY(${virtualRow.start}px)` },
11017
- children: content
11018
- }, row.key);
11267
+ })
11019
11268
  }), scrubber && scrollElement?.parentElement ? createPortal(terminal ? /* @__PURE__ */ jsx(TerminalScrubber, {
11020
11269
  items,
11021
11270
  pendingApprovals,
@@ -11108,85 +11357,119 @@ function Transcript({ state, fileUrl, attachmentUrl, canBrowseFiles, hostImage,
11108
11357
  terminal,
11109
11358
  brief
11110
11359
  ]);
11360
+ const skillNames = useMemo(() => state.skills?.map((skill) => skill.name), [state.skills]);
11111
11361
  return /* @__PURE__ */ jsx(TranscriptVariantProvider, {
11112
11362
  value: variant,
11113
- children: /* @__PURE__ */ jsxs(Conversation, {
11114
- className: cn(replaying && "invisible", className),
11115
- children: [
11116
- /* @__PURE__ */ jsx(ConversationContent, {
11117
- className: cn(terminal && "gap-0 p-0"),
11118
- children: /* @__PURE__ */ jsxs(TerminalShell, {
11119
- active: terminal,
11120
- fontSize,
11121
- lineHeight,
11122
- affordances,
11123
- children: [
11124
- frame ? frameTask === void 0 && !replaying ? /* @__PURE__ */ jsx("div", {
11125
- className: cn(terminal ? "term-row text-fg-4" : "p-4 text-body-sm text-fg-4"),
11126
- children: "This sub-agent's work is not in this transcript."
11127
- }) : null : items.length === 0 && state.status !== "starting" ? emptyState !== void 0 ? emptyState : /* @__PURE__ */ jsx(SessionEmptyState, {
11128
- cwd: state.cwd,
11129
- hasCommands: !!state.commands?.length,
11130
- hasSkills: !!state.skills?.some((s) => s.enabled),
11131
- canBrowseFiles
11132
- }) : null,
11133
- frame && frameTask === void 0 && !replaying ? null : /* @__PURE__ */ jsx(TranscriptRows, {
11134
- rows,
11135
- boundary,
11136
- since: catchUp?.since,
11137
- terminal,
11138
- replaying,
11139
- stickyPrompt: !frame && stickyPrompt,
11140
- gap,
11141
- fontSize,
11142
- lineHeight,
11143
- items,
11144
- pendingApprovals: state.pendingApprovals,
11145
- scrubber,
11146
- bookmarks: frame ? void 0 : bookmarks,
11147
- affordances,
11148
- fileUrl,
11149
- attachmentUrl,
11150
- hostImage,
11151
- jumpToRecapRef,
11152
- repinRef,
11153
- reveal: frame ? void 0 : reveal,
11154
- frameParentId: frame?.parentToolUseId,
11155
- onOpenSubagent: frame ? void 0 : onOpenSubagent
11156
- }),
11157
- (frame ? frameTask !== void 0 && taskBusy(frameTask, items) : showLoader(state)) ? terminal ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx("div", {
11158
- className: "term-blank",
11159
- "aria-hidden": true
11160
- }) : null, /* @__PURE__ */ jsx(WorkingRow, {
11161
- label: state.status === "starting" ? "Starting…" : "Working…",
11162
- startedAt: runStartedAt,
11163
- tokens: state.contextUsage?.totalTokens
11164
- })] }) : /* @__PURE__ */ jsx(Loader, { label: state.status === "starting" ? "Starting session…" : void 0 }) : null
11165
- ]
11166
- })
11167
- }),
11168
- /* @__PURE__ */ jsx(ConversationScrollButton, {}),
11169
- replaying ? /* @__PURE__ */ jsx("div", {
11170
- "data-slot": "transcript-hold",
11171
- "aria-hidden": true,
11172
- className: "wd-hold-appear visible pointer-events-none absolute inset-0 overflow-hidden",
11173
- children: /* @__PURE__ */ jsx("div", {
11174
- className: cn("mx-auto w-full max-w-[var(--wd-transcript-max-width)]", !terminal && "px-4 py-4"),
11175
- children: terminal ? /* @__PURE__ */ jsx(TerminalSurface, {
11363
+ children: /* @__PURE__ */ jsx(SkillNamesProvider, {
11364
+ names: skillNames,
11365
+ children: /* @__PURE__ */ jsxs(Conversation, {
11366
+ className: cn(replaying && "invisible", className),
11367
+ children: [
11368
+ /* @__PURE__ */ jsx(ConversationContent, {
11369
+ className: cn(terminal && "gap-0 p-0"),
11370
+ children: /* @__PURE__ */ jsxs(TerminalShell, {
11371
+ active: terminal,
11176
11372
  fontSize,
11177
11373
  lineHeight,
11178
- affordances: false,
11179
- bleed: "1ch",
11180
- className: "term-transcript",
11181
- children: /* @__PURE__ */ jsx(WorkingRow, { label: "Loading…" })
11182
- }) : /* @__PURE__ */ jsx(Loader, { label: "Loading session…" })
11183
- })
11184
- }) : null
11185
- ]
11374
+ affordances,
11375
+ children: [
11376
+ frame ? frameTask === void 0 && !replaying ? /* @__PURE__ */ jsx("div", {
11377
+ className: cn(terminal ? "term-row text-fg-4" : "p-4 text-body-sm text-fg-4"),
11378
+ children: "This sub-agent's work is not in this transcript."
11379
+ }) : null : items.length === 0 && state.status !== "starting" ? emptyState !== void 0 ? emptyState : /* @__PURE__ */ jsx(SessionEmptyState, {
11380
+ cwd: state.cwd,
11381
+ hasCommands: !!state.commands?.length,
11382
+ hasSkills: !!state.skills?.some((s) => s.enabled),
11383
+ canBrowseFiles
11384
+ }) : null,
11385
+ frame && frameTask === void 0 && !replaying ? null : /* @__PURE__ */ jsx(TranscriptRows, {
11386
+ rows,
11387
+ boundary,
11388
+ since: catchUp?.since,
11389
+ terminal,
11390
+ replaying,
11391
+ stickyPrompt: !frame && stickyPrompt,
11392
+ gap,
11393
+ fontSize,
11394
+ lineHeight,
11395
+ items,
11396
+ pendingApprovals: state.pendingApprovals,
11397
+ scrubber,
11398
+ bookmarks: frame ? void 0 : bookmarks,
11399
+ affordances,
11400
+ fileUrl,
11401
+ attachmentUrl,
11402
+ hostImage,
11403
+ jumpToRecapRef,
11404
+ repinRef,
11405
+ reveal: frame ? void 0 : reveal,
11406
+ frameParentId: frame?.parentToolUseId,
11407
+ onOpenSubagent: frame ? void 0 : onOpenSubagent
11408
+ }),
11409
+ (frame ? frameTask !== void 0 && taskBusy(frameTask, items) : showLoader(state)) ? terminal ? /* @__PURE__ */ jsxs(Fragment$1, { children: [state.items.length > 0 ? /* @__PURE__ */ jsx("div", {
11410
+ className: "term-blank",
11411
+ "aria-hidden": true
11412
+ }) : null, /* @__PURE__ */ jsx(WorkingRow, {
11413
+ label: state.status === "starting" ? "Starting…" : "Working…",
11414
+ startedAt: runStartedAt,
11415
+ tokens: state.contextUsage?.totalTokens
11416
+ })] }) : /* @__PURE__ */ jsx(Loader, { label: state.status === "starting" ? "Starting session…" : void 0 }) : null
11417
+ ]
11418
+ })
11419
+ }),
11420
+ /* @__PURE__ */ jsx(ConversationScrollButton, {}),
11421
+ replaying ? /* @__PURE__ */ jsx("div", {
11422
+ "data-slot": "transcript-hold",
11423
+ "aria-hidden": true,
11424
+ className: "wd-hold-appear visible pointer-events-none absolute inset-0 overflow-hidden",
11425
+ children: /* @__PURE__ */ jsx("div", {
11426
+ className: cn("mx-auto w-full max-w-[var(--wd-transcript-max-width)]", !terminal && "px-4 py-4"),
11427
+ children: terminal ? /* @__PURE__ */ jsx(TerminalSurface, {
11428
+ fontSize,
11429
+ lineHeight,
11430
+ affordances: false,
11431
+ bleed: "1ch",
11432
+ className: "term-transcript",
11433
+ children: /* @__PURE__ */ jsx(WorkingRow, { label: "Loading…" })
11434
+ }) : /* @__PURE__ */ jsx(Loader, { label: "Loading session…" })
11435
+ })
11436
+ }) : null
11437
+ ]
11438
+ })
11186
11439
  })
11187
11440
  });
11188
11441
  }
11189
11442
  //#endregion
11443
+ //#region src/lib/session-cost.ts
11444
+ const GAP_SHARE = .02;
11445
+ function sessionCost(input) {
11446
+ const byModel = input.usageByModel ?? {};
11447
+ const pricing = input.pricing;
11448
+ const breakdown = costOfByModel(byModel, pricing);
11449
+ const computedUsd = input.costUsd ?? (breakdown.unpriced || Object.keys(byModel).length === 0 ? void 0 : breakdown.total);
11450
+ const reportedUsd = input.totalCostUsd;
11451
+ const rows = Object.entries(byModel).map(([model, usage]) => ({
11452
+ model: canonicalModel(model),
11453
+ usage,
11454
+ tokens: totalTokens(usage),
11455
+ costUsd: rateFor(model, pricing) ? costOf(usage, model, pricing).total : void 0,
11456
+ costBasis: usage.costBasis
11457
+ })).sort((a, b) => b.tokens - a.tokens);
11458
+ const gap = computedUsd !== void 0 && reportedUsd !== void 0 ? reportedUsd - computedUsd : void 0;
11459
+ const worthShowing = gap !== void 0 && Math.abs(gap) > Math.max(.01, GAP_SHARE * Math.max(computedUsd ?? 0, reportedUsd ?? 0));
11460
+ return {
11461
+ displayUsd: computedUsd ?? reportedUsd,
11462
+ computedUsd,
11463
+ reportedUsd,
11464
+ breakdown,
11465
+ rows,
11466
+ unpricedShare: breakdown.unpricedShare,
11467
+ showUnpricedWarning: breakdown.unpricedShare > UNPRICED_WARN_SHARE,
11468
+ unknownBasisModels: unknownBasisModels(byModel),
11469
+ gapUsd: worthShowing ? gap : void 0
11470
+ };
11471
+ }
11472
+ //#endregion
11190
11473
  //#region src/components/agent/UsageMeters.tsx
11191
11474
  function useMinuteClock(active = true) {
11192
11475
  const [now, setNow] = useState(() => Date.now());
@@ -11270,8 +11553,148 @@ function UsageMeter({ window, now }) {
11270
11553
  }
11271
11554
  //#endregion
11272
11555
  //#region src/components/agent/UsageDialog.tsx
11273
- function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, updatedAt, open, onOpenChange, className }) {
11556
+ function engineLabel(engine) {
11557
+ return engine === "claude" ? "Claude Code" : engine;
11558
+ }
11559
+ function Line({ label, value, tone }) {
11560
+ return /* @__PURE__ */ jsxs("div", {
11561
+ className: "flex items-baseline justify-between gap-4",
11562
+ children: [/* @__PURE__ */ jsx("span", {
11563
+ className: tone === "muted" ? "text-label text-fg-4" : "text-label text-fg-3",
11564
+ children: label
11565
+ }), /* @__PURE__ */ jsx("span", {
11566
+ className: `font-mono text-body-sm ${tone === "muted" ? "text-fg-4" : "text-fg-1"}`,
11567
+ children: value
11568
+ })]
11569
+ });
11570
+ }
11571
+ function CostEvidence({ cost, engine }) {
11572
+ const { breakdown, rows } = cost;
11573
+ if (rows.length === 0) return /* @__PURE__ */ jsx(Line, {
11574
+ label: "This session has cost",
11575
+ value: formatCost(cost.displayUsd)
11576
+ });
11577
+ return /* @__PURE__ */ jsxs("div", {
11578
+ className: "space-y-3",
11579
+ children: [
11580
+ /* @__PURE__ */ jsxs("div", {
11581
+ className: "flex items-baseline justify-between gap-4",
11582
+ children: [/* @__PURE__ */ jsx("span", {
11583
+ className: "text-label text-fg-3",
11584
+ children: "This session has cost"
11585
+ }), /* @__PURE__ */ jsx("span", {
11586
+ className: "font-mono text-body text-fg-1",
11587
+ children: formatCost(cost.displayUsd)
11588
+ })]
11589
+ }),
11590
+ /* @__PURE__ */ jsxs("div", {
11591
+ className: "space-y-1 border-t border-border pt-2",
11592
+ children: [
11593
+ /* @__PURE__ */ jsx(Line, {
11594
+ label: "input",
11595
+ value: formatCost(breakdown.input),
11596
+ tone: "muted"
11597
+ }),
11598
+ /* @__PURE__ */ jsx(Line, {
11599
+ label: "output",
11600
+ value: formatCost(breakdown.output),
11601
+ tone: "muted"
11602
+ }),
11603
+ /* @__PURE__ */ jsx(Line, {
11604
+ label: "cache write",
11605
+ value: formatCost(breakdown.cacheWrite),
11606
+ tone: "muted"
11607
+ }),
11608
+ /* @__PURE__ */ jsx(Line, {
11609
+ label: "cache read",
11610
+ value: formatCost(breakdown.cacheRead),
11611
+ tone: "muted"
11612
+ })
11613
+ ]
11614
+ }),
11615
+ /* @__PURE__ */ jsx("div", {
11616
+ className: "space-y-1 border-t border-border pt-2",
11617
+ children: rows.map((row) => /* @__PURE__ */ jsxs("div", {
11618
+ className: "flex items-baseline justify-between gap-4",
11619
+ children: [/* @__PURE__ */ jsx("span", {
11620
+ className: "truncate text-label text-fg-4",
11621
+ children: row.model
11622
+ }), /* @__PURE__ */ jsxs("span", {
11623
+ className: "shrink-0 font-mono text-label text-fg-4",
11624
+ children: [
11625
+ formatTokens(row.tokens),
11626
+ " · ",
11627
+ row.costUsd === void 0 ? "no rate" : formatCost(row.costUsd)
11628
+ ]
11629
+ })]
11630
+ }, row.model))
11631
+ }),
11632
+ cost.showUnpricedWarning ? /* @__PURE__ */ jsxs("p", {
11633
+ className: "text-label text-warning",
11634
+ children: [Math.round(cost.unpricedShare * 100), "% of these tokens ran on a model with no bundled rate, so the total is an understatement rather than an estimate."]
11635
+ }) : null,
11636
+ cost.unknownBasisModels.length > 0 ? /* @__PURE__ */ jsxs("p", {
11637
+ className: "text-label text-warning",
11638
+ children: [
11639
+ engineLabel(engine),
11640
+ " matched no rate table of its own to ",
11641
+ cost.unknownBasisModels.join(", "),
11642
+ ", so the figure it reports for those tokens is a guess rather than a rate."
11643
+ ]
11644
+ }) : null,
11645
+ cost.gapUsd !== void 0 ? /* @__PURE__ */ jsxs("p", {
11646
+ className: "text-label text-fg-4",
11647
+ children: [
11648
+ engineLabel(engine),
11649
+ " reports ",
11650
+ formatCost(cost.reportedUsd),
11651
+ " for the same tokens."
11652
+ ]
11653
+ }) : null
11654
+ ]
11655
+ });
11656
+ }
11657
+ function ProfileSpendSection({ spend }) {
11658
+ const { subscription } = spend;
11659
+ return /* @__PURE__ */ jsxs("div", {
11660
+ className: "mt-4 space-y-1 border-t border-border pt-3",
11661
+ children: [
11662
+ /* @__PURE__ */ jsx(Line, {
11663
+ label: "This profile, last 7 days",
11664
+ value: formatCost(spend.weekUsd)
11665
+ }),
11666
+ /* @__PURE__ */ jsx(Line, {
11667
+ label: "Last 30 days",
11668
+ value: formatCost(spend.monthUsd),
11669
+ tone: "muted"
11670
+ }),
11671
+ subscription ? /* @__PURE__ */ jsxs("p", {
11672
+ className: "pt-1 text-label text-fg-4",
11673
+ children: [
11674
+ formatMultiple(subscription.ratio),
11675
+ " the ",
11676
+ formatCost(subscription.weeklyShareUsd),
11677
+ " weekly share of your",
11678
+ " ",
11679
+ formatCost(subscription.monthlyUsd),
11680
+ " a month. A subscription is a flat fee, so this is leverage, not a bill."
11681
+ ]
11682
+ }) : null
11683
+ ]
11684
+ });
11685
+ }
11686
+ function formatMultiple(ratio) {
11687
+ return ratio >= 10 ? `${Math.round(ratio)}x` : `${ratio.toFixed(1)}x`;
11688
+ }
11689
+ function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, costUsd, usageByModel, pricing, spend, updatedAt, open, onOpenChange, className }) {
11274
11690
  const now = useMinuteClock(open);
11691
+ const cost = sessionCost({
11692
+ costUsd,
11693
+ totalCostUsd,
11694
+ usageByModel,
11695
+ pricing
11696
+ });
11697
+ const ageNote = pricingAgeNote(now);
11275
11698
  return /* @__PURE__ */ jsx(Dialog$1, {
11276
11699
  open,
11277
11700
  onOpenChange,
@@ -11279,7 +11702,7 @@ function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, updat
11279
11702
  className,
11280
11703
  children: [/* @__PURE__ */ jsx(DialogHeader, {
11281
11704
  title: "Usage",
11282
- description: engine === "claude" ? "Claude Code" : engine,
11705
+ description: engineLabel(engine),
11283
11706
  actions: subscriptionType ? /* @__PURE__ */ jsx(Badge, {
11284
11707
  variant: "accent",
11285
11708
  className: "mt-0.5 shrink-0 capitalize",
@@ -11293,15 +11716,17 @@ function UsageDialog({ rateLimits, subscriptionType, engine, totalCostUsd, updat
11293
11716
  windows: rateLimits,
11294
11717
  now
11295
11718
  }),
11296
- /* @__PURE__ */ jsxs("div", {
11297
- className: "mt-5 flex items-baseline justify-between gap-4 border-t border-border pt-3",
11298
- children: [/* @__PURE__ */ jsx("span", {
11299
- className: "text-label text-fg-3",
11300
- children: "This session has cost"
11301
- }), /* @__PURE__ */ jsx("span", {
11302
- className: "font-mono text-body-sm text-fg-1",
11303
- children: formatCost(totalCostUsd)
11304
- })]
11719
+ /* @__PURE__ */ jsx("div", {
11720
+ className: "mt-5 border-t border-border pt-3",
11721
+ children: /* @__PURE__ */ jsx(CostEvidence, {
11722
+ cost,
11723
+ engine
11724
+ })
11725
+ }),
11726
+ spend ? /* @__PURE__ */ jsx(ProfileSpendSection, { spend }) : null,
11727
+ /* @__PURE__ */ jsx("p", {
11728
+ className: "mt-3 text-label text-fg-4",
11729
+ children: ageNote ? `${PRICING_NOTE} ${ageNote}` : PRICING_NOTE
11305
11730
  }),
11306
11731
  updatedAt ? /* @__PURE__ */ jsxs("p", {
11307
11732
  className: "mt-2 text-label text-fg-4",
@@ -11353,7 +11778,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11353
11778
  const [panel, setPanel] = useState();
11354
11779
  const [showCompletedTasks, setShowCompletedTasks] = useState(false);
11355
11780
  const [taskReveal, setTaskReveal] = useState();
11356
- const { state, connection, replaying, protocolMismatch, shell, models, effectiveModel, handle, send, runShell, approve, deny, interrupt, clearContext, setModel, setPermissionMode, reconnectNow, loadFullResult } = useClaudeSession(client, sessionId, {
11781
+ const { state, connection, replaying, protocolMismatch, shell, pricing, models, effectiveModel, handle, send, runShell, approve, deny, interrupt, clearContext, setModel, setPermissionMode, reconnectNow, loadFullResult } = useClaudeSession(client, sessionId, {
11357
11782
  onProtocolError: setProtocolError,
11358
11783
  cacheTranscript
11359
11784
  });
@@ -11393,7 +11818,7 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11393
11818
  } : toolHost);
11394
11819
  const terminal = transcriptVariant === "terminal";
11395
11820
  const capabilities = state.capabilities;
11396
- const { usage: profileUsage } = useProfileUsage(client, state.session?.profile, { enabled: capabilities.rateLimits });
11821
+ const { usage: profileUsage, spend: profileSpend } = useProfileUsage(client, state.session?.profile, { enabled: capabilities.rateLimits });
11397
11822
  const usage = useMemo(() => mergeUsage({
11398
11823
  rateLimits: state.rateLimits,
11399
11824
  updatedAt: state.rateLimitsUpdatedAt
@@ -11869,6 +12294,10 @@ function SessionPanel({ client, sessionId, header, panelSurface = "internal", st
11869
12294
  subscriptionType: state.subscriptionType,
11870
12295
  engine: state.engine ?? "claude",
11871
12296
  totalCostUsd: state.totalCostUsd,
12297
+ costUsd: state.costUsd,
12298
+ usageByModel: state.usageByModel,
12299
+ pricing,
12300
+ spend: profileSpend,
11872
12301
  updatedAt: usageUpdatedAt,
11873
12302
  open: panel === "usage",
11874
12303
  onOpenChange: (next) => setPanel(next ? "usage" : void 0)
@@ -11977,6 +12406,108 @@ function Notice({ level, onDismiss, children }) {
11977
12406
  });
11978
12407
  }
11979
12408
  //#endregion
11980
- export { Band as $, Input as $t, PERMISSION_MODES as A, Tip as At, TaskList as B, Menu$1 as Bt, TerminalPermissionPrompt as C, segmentsToPlainText as Ct, SubagentStrip as D, Spinner as Dt, parseUserQuestions as E, CodeBlock as Et, SkillsDialog as F, DialogClose as Ft, ToolTitleProvider as G, Select$1 as Gt, contextNote as H, MenuItem as Ht, McpDialog as I, DialogContent as It, FileLinkProvider as J, SelectItemText as Jt, useToolTitle as K, SelectContent as Kt, HostFilesDialog as L, DialogHeader as Lt, permissionModeChoices as M, TooltipProvider as Mt, permissionModeMeta as N, Dialog$1 as Nt, PermissionPrompt as O, CopyButton as Ot, ModelSelect as P, DialogBody as Pt, previewPatch as Q, Textarea as Qt, EntryIcon as R, DialogRow as Rt, TerminalQuestionPrompt as S, plainTextToSegments as St, QuestionPrompt as T, Splitter as Tt, Composer as U, MenuSeparator as Ut, ContextDialog as V, MenuContent as Vt, TerminalSurface as W, MenuTrigger as Wt, parseFileLink as X, SelectValue as Xt, useFileLinks as Y, SelectTrigger as Yt, TerminalDiff as Z, PortalScope as Zt, ConversationContent as _, hashtagTrigger as _t, Transcript as a, useAffordances as at, STATUS_META as b, usePromptAreaState as bt, PromptTokenText as c, TranscriptDensityProvider as ct, FileCard as d, useTranscriptVariant as dt, Badge as en, Blank as et, TerminalItemView as f, matchClientCommand as ft, Conversation as g, commandTrigger as gt, Loader as h, skillPrompt as ht, useMinuteClock as i, cn as in, WithActions as it, PermissionModeSelect as j, TooltipContent as jt, Response as k, copyText as kt, Message as l, TranscriptVariantProvider as lt, SessionEmptyState as m, rankComposerRows as mt, UsageDialog as n, Button as nn, Row as nt, ToolCallCard as o, isMutatingTool as ot, TerminalTranscript as p, mergeComposerRows as pt, TerminalMarkdown as q, SelectItem as qt, UsageMeters as r, buttonVariants as rn, CopyAction as rt, Reasoning as s, toolIcon as st, SessionPanel as t, badgeVariants as tn, Ink as tt, MessageContent as u, useTranscriptDensity as ut, ConversationScrollButton as v, launchTrigger as vt, QUESTION_BEHAVIORS as w, ProgressRing as wt, SessionInfoDialog as x, PromptArea as xt, StatusBar as y, mentionTrigger as yt, TasksDialog as z, DialogTrigger as zt };
12409
+ //#region src/lib/path-match.ts
12410
+ const SEG = "[^\\s:'\"`()\\[\\]{}<>|]+";
12411
+ const PATH_PATTERN = new RegExp(`(?:^|[\\s'"\`([{<@,])((?:\\.\\.?)?(?:/${SEG})+|[^\\s:'"\`()\\[\\]{}<>|@,]+(?:/${SEG})+)(?::(\\d+))?`);
12412
+ const FILE_PATTERN = /^([\w.@-]+\.[A-Za-z0-9]{1,10})(?::(\d+))?$/;
12413
+ const TRAILING = /[.,;:!?)\]}'"`]+$/;
12414
+ const FILENAME = /\.[A-Za-z0-9]{1,10}$/;
12415
+ function matchPath(text, inCode = false) {
12416
+ const source = text ?? "";
12417
+ const match = PATH_PATTERN.exec(source);
12418
+ if (!match) return inCode ? matchFilename(source) : void 0;
12419
+ const path = match[1].replace(TRAILING, "");
12420
+ if (!path.includes("/") || path.endsWith("/")) return;
12421
+ if (!(path.startsWith("/") || path.startsWith("./") || path.startsWith("../")) && !FILENAME.test(path)) return;
12422
+ return {
12423
+ path,
12424
+ line: match[2] ? Number(match[2]) : void 0,
12425
+ length: path.length + (match[2] ? match[2].length + 1 : 0)
12426
+ };
12427
+ }
12428
+ function matchFilename(text) {
12429
+ const hit = FILE_PATTERN.exec(text.trim());
12430
+ if (!hit) return;
12431
+ const line = hit[2] ? Number(hit[2]) : void 0;
12432
+ return {
12433
+ path: hit[1],
12434
+ line,
12435
+ length: hit[0].length
12436
+ };
12437
+ }
12438
+ //#endregion
12439
+ //#region src/components/agent/use-path-links.ts
12440
+ const LINKISH = "wd-path-link";
12441
+ const COVERAGE = .6;
12442
+ function hitFor(element) {
12443
+ if (!element) return;
12444
+ const inCode = element.closest("code") !== null;
12445
+ const text = element.textContent?.trim() ?? "";
12446
+ const hit = matchPath(text, inCode);
12447
+ if (!hit) return;
12448
+ if (!inCode && hit.length < text.length * COVERAGE) return;
12449
+ return {
12450
+ path: hit.path,
12451
+ line: hit.line
12452
+ };
12453
+ }
12454
+ function usePathLinks({ container, onOpen, enabled = true, ignore }) {
12455
+ useEffect(() => {
12456
+ const root = container.current;
12457
+ if (!root || !enabled) return;
12458
+ const excluded = (element) => ignore !== void 0 && element?.closest(ignore) !== null && element !== void 0;
12459
+ let marked;
12460
+ const unmark = () => {
12461
+ marked?.classList.remove(LINKISH);
12462
+ marked = void 0;
12463
+ };
12464
+ const onClick = (event) => {
12465
+ if (!event.metaKey && !event.ctrlKey) return;
12466
+ const target = event.target instanceof HTMLElement ? event.target : void 0;
12467
+ if (excluded(target)) return;
12468
+ const hit = hitFor(target);
12469
+ if (!hit) return;
12470
+ event.preventDefault();
12471
+ event.stopPropagation();
12472
+ unmark();
12473
+ onOpen(hit);
12474
+ };
12475
+ const onMove = (event) => {
12476
+ if (!event.metaKey && !event.ctrlKey) return unmark();
12477
+ const element = event.target instanceof HTMLElement ? event.target : void 0;
12478
+ if (element === marked) return;
12479
+ unmark();
12480
+ if (!element || excluded(element) || !hitFor(element)) return;
12481
+ marked = element;
12482
+ element.classList.add(LINKISH);
12483
+ };
12484
+ const onKey = (event) => {
12485
+ if (!event.metaKey && !event.ctrlKey) unmark();
12486
+ };
12487
+ root.addEventListener("click", onClick, true);
12488
+ root.addEventListener("mousemove", onMove);
12489
+ document.addEventListener("keydown", onKey);
12490
+ document.addEventListener("keyup", onKey);
12491
+ window.addEventListener("blur", unmark);
12492
+ return () => {
12493
+ unmark();
12494
+ root.removeEventListener("click", onClick, true);
12495
+ root.removeEventListener("mousemove", onMove);
12496
+ document.removeEventListener("keydown", onKey);
12497
+ document.removeEventListener("keyup", onKey);
12498
+ window.removeEventListener("blur", unmark);
12499
+ };
12500
+ }, [
12501
+ container,
12502
+ onOpen,
12503
+ enabled,
12504
+ ignore
12505
+ ]);
12506
+ }
12507
+ function resolveAgainstCwd(path, cwd) {
12508
+ return parseFileLink(path, cwd)?.path ?? path;
12509
+ }
12510
+ //#endregion
12511
+ export { TerminalDiff as $, SelectValue as $t, PermissionPrompt as A, Spinner as At, EntryIcon as B, DialogHeader as Bt, SessionInfoDialog as C, usePromptAreaState as Ct, QuestionPrompt as D, ProgressRing as Dt, QUESTION_BEHAVIORS as E, segmentsToPlainText as Et, permissionModeMeta as F, TooltipProvider as Ft, Composer as G, MenuItem as Gt, TaskList as H, DialogTrigger as Ht, ModelSelect as I, Dialog$1 as It, useToolTitle as J, Select$1 as Jt, TerminalSurface as K, MenuSeparator as Kt, SkillsDialog as L, DialogBody as Lt, PERMISSION_MODES as M, copyText as Mt, PermissionModeSelect as N, Tip as Nt, parseUserQuestions as O, Splitter as Ot, permissionModeChoices as P, TooltipContent as Pt, parseFileLink as Q, SelectTrigger as Qt, McpDialog as R, DialogClose as Rt, STATUS_META as S, mentionTrigger as St, TerminalPermissionPrompt as T, plainTextToSegments as Tt, ContextDialog as U, Menu$1 as Ut, TasksDialog as V, DialogRow as Vt, contextNote as W, MenuContent as Wt, FileLinkProvider as X, SelectItem as Xt, TerminalMarkdown as Y, SelectContent as Yt, useFileLinks as Z, SelectItemText as Zt, Loader as _, rankComposerRows as _t, UsageDialog as a, Button as an, CopyAction as at, ConversationScrollButton as b, hashtagTrigger as bt, Transcript as c, PromptTokenText as ct, Message as d, TranscriptDensityProvider as dt, PortalScope as en, previewPatch as et, MessageContent as f, TranscriptVariantProvider as ft, SessionEmptyState as g, mergeComposerRows as gt, TerminalTranscript as h, matchClientCommand as ht, SessionPanel as i, badgeVariants as in, Row as it, Response as j, CopyButton as jt, SubagentStrip as k, CodeBlock as kt, ToolCallCard as l, isMutatingTool as lt, TerminalItemView as m, useTranscriptVariant as mt, usePathLinks as n, Input as nn, Blank as nt, UsageMeters as o, buttonVariants as on, WithActions as ot, FileCard as p, useTranscriptDensity as pt, ToolTitleProvider as q, MenuTrigger as qt, matchPath as r, Badge as rn, Ink as rt, useMinuteClock as s, cn as sn, useAffordances as st, resolveAgainstCwd as t, Textarea as tn, Band as tt, Reasoning as u, toolIcon as ut, Conversation as v, skillPrompt as vt, TerminalQuestionPrompt as w, PromptArea as wt, StatusBar as x, launchTrigger as xt, ConversationContent as y, commandTrigger as yt, HostFilesDialog as z, DialogContent as zt };
11981
12512
 
11982
- //# sourceMappingURL=SessionPanel-BClL7zUS.mjs.map
12513
+ //# sourceMappingURL=use-path-links-DO_79-wX.mjs.map