nolo-cli 0.33.0-alpha.20 → 0.33.0-alpha.21

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.
@@ -7,7 +7,7 @@ import {
7
7
  prependSubjectDialogMarker,
8
8
  resolveWorkflowReference,
9
9
  runAgentRunCommand
10
- } from "./chunk-FR2AIV4Q.js";
10
+ } from "./chunk-46JSKTPF.js";
11
11
  import {
12
12
  parseAgentRunArgs
13
13
  } from "./chunk-ZHYK3N56.js";
@@ -388,6 +388,9 @@ function displayWidth(str) {
388
388
  for (const char of str) {
389
389
  const code = char.codePointAt(0) ?? 0;
390
390
  if (code < 32 || code === 127) continue;
391
+ if (code >= 768 && code <= 879 || code >= 6832 && code <= 6911 || code >= 7616 && code <= 7679 || code >= 8203 && code <= 8207 || code >= 8234 && code <= 8238 || code >= 8288 && code <= 8303 || code >= 8400 && code <= 8447 || code >= 65024 && code <= 65039 || code >= 65056 && code <= 65071 || code >= 917760 && code <= 917999) {
392
+ continue;
393
+ }
391
394
  if (code >= 4352 && code <= 4447 || // 0x2768-0x2775 (ornamental brackets, incl. the ❯ prompt at U+276F)
392
395
  // render narrow in terminals; counting them wide drifts the cursor.
393
396
  code >= 9728 && code <= 10175 && !(code >= 10088 && code <= 10101) || // 0x2B00-0x2BFF (Misc Symbols and Arrows, incl. ⬢ U+2B22 used as the
@@ -474,6 +477,7 @@ function padOrTruncateToWidth(text, width) {
474
477
  return `${text}${" ".repeat(width - textWidth)}`;
475
478
  }
476
479
  var SGR_RESET_REGEX = /^\x1b\[0?m$/;
480
+ var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
477
481
  function tokenizeAnsiLine(line) {
478
482
  const tokens = [];
479
483
  let index = 0;
@@ -481,28 +485,119 @@ function tokenizeAnsiLine(line) {
481
485
  if (line[index] === "\x1B") {
482
486
  const osc = line.slice(index).match(/^\x1b\]8;;[^\x07\x1b]*(?:\x07|\x1b\\)/);
483
487
  if (osc) {
484
- tokens.push({ kind: "sgr", value: osc[0], width: 0 });
488
+ tokens.push({ kind: "sgr", value: osc[0], width: 0, charIndex: index });
485
489
  index += osc[0].length;
486
490
  continue;
487
491
  }
488
492
  const sgr = SGR_SEQUENCE_REGEX.exec(line.slice(index));
489
493
  if (sgr) {
490
- tokens.push({ kind: "sgr", value: sgr[0], width: 0 });
494
+ tokens.push({ kind: "sgr", value: sgr[0], width: 0, charIndex: index });
491
495
  index += sgr[0].length;
492
496
  continue;
493
497
  }
498
+ const csi = line.slice(index).match(/^\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/);
499
+ if (csi) {
500
+ tokens.push({ kind: "sgr", value: csi[0], width: 0, charIndex: index });
501
+ index += csi[0].length;
502
+ continue;
503
+ }
504
+ }
505
+ let nextEsc = line.indexOf("\x1B", index);
506
+ if (nextEsc === -1) nextEsc = line.length;
507
+ const chunk = line.slice(index, nextEsc);
508
+ for (const item of graphemeSegmenter.segment(chunk)) {
509
+ tokens.push({
510
+ kind: "char",
511
+ value: item.segment,
512
+ width: displayWidth(item.segment),
513
+ charIndex: index + item.index
514
+ });
494
515
  }
495
- const codePoint = line.codePointAt(index) ?? 0;
496
- const value = String.fromCodePoint(codePoint);
497
- tokens.push({ kind: "char", value, width: displayWidth(value) });
498
- index += value.length;
516
+ index = nextEsc;
499
517
  }
500
518
  return tokens;
501
519
  }
502
- function wrapTranscriptLine(line, columns, hangingIndent = "") {
503
- if (line === "") return [""];
520
+ function buildSourceMapping(rawLine, styledLine, prefixCharCount) {
521
+ let contentStart = 0;
522
+ const headingMatch = rawLine.match(/^(#{1,3})\s+(.+)$/);
523
+ let lineToParse = rawLine;
524
+ if (headingMatch) {
525
+ contentStart = headingMatch[1].length + 1;
526
+ while (contentStart < rawLine.length && rawLine[contentStart] === " ") contentStart++;
527
+ lineToParse = rawLine.slice(contentStart);
528
+ }
529
+ const INLINE_RE = /(\[([^\]]+)\]\(([^)\s]+)\)|`([^`]+)`|\*\*(.+?)\*\*|(?<!\*)\*([^*]+?)\*(?!\*)|~~([^~]+?)~~)/g;
530
+ const mapping = [];
531
+ let lastIdx = 0;
532
+ let match;
533
+ while ((match = INLINE_RE.exec(lineToParse)) !== null) {
534
+ const plainBefore = lineToParse.slice(lastIdx, match.index);
535
+ for (let c = 0; c < plainBefore.length; c++) {
536
+ mapping.push(contentStart + lastIdx + c);
537
+ }
538
+ const fullMatch = match[0];
539
+ if (match[2] !== void 0 && match[3] !== void 0) {
540
+ const linkText = match[2];
541
+ const linkUrl = match[3];
542
+ const textOffset = contentStart + match.index + 1;
543
+ for (let c = 0; c < linkText.length; c++) {
544
+ mapping.push(textOffset + c);
545
+ }
546
+ const parenOffset = textOffset + linkText.length;
547
+ mapping.push(parenOffset);
548
+ mapping.push(parenOffset + 1);
549
+ const urlOffset = parenOffset + 2;
550
+ for (let c = 0; c < linkUrl.length; c++) {
551
+ mapping.push(urlOffset + c);
552
+ }
553
+ mapping.push(urlOffset + linkUrl.length);
554
+ } else if (match[4] !== void 0) {
555
+ const codeText = match[4];
556
+ const codeOffset = contentStart + match.index + 1;
557
+ for (let c = 0; c < codeText.length; c++) {
558
+ mapping.push(codeOffset + c);
559
+ }
560
+ } else if (match[5] !== void 0) {
561
+ const boldText = match[5];
562
+ const boldOffset = contentStart + match.index + 2;
563
+ for (let c = 0; c < boldText.length; c++) {
564
+ mapping.push(boldOffset + c);
565
+ }
566
+ } else if (match[6] !== void 0) {
567
+ const italicText = match[6];
568
+ const italicOffset = contentStart + match.index + 1;
569
+ for (let c = 0; c < italicText.length; c++) {
570
+ mapping.push(italicOffset + c);
571
+ }
572
+ } else if (match[7] !== void 0) {
573
+ const strikeText = match[7];
574
+ const strikeOffset = contentStart + match.index + 2;
575
+ for (let c = 0; c < strikeText.length; c++) {
576
+ mapping.push(strikeOffset + c);
577
+ }
578
+ }
579
+ lastIdx = match.index + fullMatch.length;
580
+ }
581
+ const plainRest = lineToParse.slice(lastIdx);
582
+ for (let c = 0; c < plainRest.length; c++) {
583
+ mapping.push(contentStart + lastIdx + c);
584
+ }
585
+ return mapping;
586
+ }
587
+ function wrapTranscriptLineWithLayout(line, columns, hangingIndent = "", lineSourceStart = 0, prefixWidth = 0, prefixCharCount = 0, sourceMapping, rawLineLength) {
588
+ if (line === "") {
589
+ const rawLen = rawLineLength ?? 0;
590
+ return [
591
+ {
592
+ rendered: "",
593
+ sourceStart: lineSourceStart,
594
+ sourceEnd: lineSourceStart + rawLen,
595
+ prefixWidth
596
+ }
597
+ ];
598
+ }
504
599
  const tokens = tokenizeAnsiLine(line);
505
- const result = [];
600
+ const rows = [];
506
601
  let activeStyles = [];
507
602
  const applyStyleToken = (value) => {
508
603
  if (SGR_RESET_REGEX.test(value)) {
@@ -514,10 +609,11 @@ function wrapTranscriptLine(line, columns, hangingIndent = "") {
514
609
  let start = 0;
515
610
  while (start < tokens.length) {
516
611
  if (tokens.slice(start).every((token) => token.kind === "sgr")) {
517
- if (result.length > 0) break;
612
+ if (rows.length > 0) break;
518
613
  }
519
614
  const openingStyles = [...activeStyles];
520
- const isContinuation = result.length > 0 && hangingIndent.length > 0;
615
+ const isContinuation = rows.length > 0;
616
+ const currentPrefixWidth = isContinuation ? visibleWidth(hangingIndent) : prefixWidth;
521
617
  const indentWidth = isContinuation ? visibleWidth(hangingIndent) : 0;
522
618
  const maxSegmentWidth = Math.max(1, columns - indentWidth);
523
619
  let width = 0;
@@ -532,7 +628,7 @@ function wrapTranscriptLine(line, columns, hangingIndent = "") {
532
628
  if (width + token.width > maxSegmentWidth && width > 0) break;
533
629
  width += token.width;
534
630
  end += 1;
535
- if (token.value === " " || token.value === " ") {
631
+ if ((token.value === " " || token.value === " ") && token.charIndex >= prefixCharCount) {
536
632
  lastBreak = end;
537
633
  }
538
634
  }
@@ -557,18 +653,71 @@ function wrapTranscriptLine(line, columns, hangingIndent = "") {
557
653
  const prefix = openingStyles.join("");
558
654
  const needsReset = (sawStyle || activeStyles.length > 0) && !segment.endsWith("\x1B[0m");
559
655
  const lineContent = `${prefix}${segment}${needsReset ? "\x1B[0m" : ""}`;
560
- result.push(isContinuation ? `${hangingIndent}${lineContent}` : lineContent);
656
+ const rowRendered = isContinuation && hangingIndent.length > 0 ? `${hangingIndent}${lineContent}` : lineContent;
657
+ let segSourceStart = lineSourceStart;
658
+ let segSourceEnd = lineSourceStart;
659
+ let foundFirst = false;
660
+ const rowMapping = [];
661
+ let plainCharIndex = 0;
662
+ for (let i = 0; i < start; i++) {
663
+ const tok = tokens[i];
664
+ if (tok.kind === "char" && tok.charIndex >= prefixCharCount) {
665
+ plainCharIndex += tok.value.length;
666
+ }
667
+ }
668
+ for (let i = start; i < segmentEnd; i++) {
669
+ const tok = tokens[i];
670
+ if (tok.kind === "char" && tok.charIndex >= prefixCharCount) {
671
+ const mappedOffset = sourceMapping && sourceMapping[plainCharIndex] !== void 0 ? sourceMapping[plainCharIndex] : plainCharIndex;
672
+ if (!foundFirst) {
673
+ segSourceStart = lineSourceStart + mappedOffset;
674
+ foundFirst = true;
675
+ }
676
+ for (let c = 0; c < tok.value.length; c++) {
677
+ const idx = plainCharIndex + c;
678
+ const mapped = sourceMapping && sourceMapping[idx] !== void 0 ? sourceMapping[idx] : idx;
679
+ rowMapping.push(lineSourceStart + mapped);
680
+ }
681
+ const lastCharIdx = plainCharIndex + tok.value.length - 1;
682
+ const mappedEndOffset = sourceMapping && sourceMapping[lastCharIdx] !== void 0 ? sourceMapping[lastCharIdx] + 1 : plainCharIndex + tok.value.length;
683
+ segSourceEnd = lineSourceStart + mappedEndOffset;
684
+ plainCharIndex += tok.value.length;
685
+ }
686
+ }
561
687
  start = segmentEnd;
562
688
  while (start < tokens.length) {
563
689
  const token = tokens[start];
564
- if (token.kind === "char" && token.value === " ") {
690
+ if (token.kind === "char" && token.value === " " && token.charIndex >= prefixCharCount) {
691
+ const mappedOffset = sourceMapping && sourceMapping[plainCharIndex] !== void 0 ? sourceMapping[plainCharIndex] + 1 : plainCharIndex + 1;
692
+ segSourceEnd = lineSourceStart + mappedOffset;
693
+ plainCharIndex += token.value.length;
565
694
  start += 1;
566
695
  continue;
567
696
  }
568
697
  break;
569
698
  }
699
+ if (start >= tokens.length && rawLineLength !== void 0) {
700
+ segSourceEnd = lineSourceStart + rawLineLength;
701
+ }
702
+ rows.push({
703
+ rendered: rowRendered,
704
+ sourceStart: segSourceStart,
705
+ sourceEnd: segSourceEnd,
706
+ prefixWidth: currentPrefixWidth,
707
+ sourceMapping: rowMapping.length > 0 ? rowMapping : void 0
708
+ });
570
709
  }
571
- return result.length > 0 ? result : [""];
710
+ return rows.length > 0 ? rows : [
711
+ {
712
+ rendered: "",
713
+ sourceStart: lineSourceStart,
714
+ sourceEnd: lineSourceStart + (rawLineLength ?? 0),
715
+ prefixWidth
716
+ }
717
+ ];
718
+ }
719
+ function wrapTranscriptLine(line, columns, hangingIndent = "") {
720
+ return wrapTranscriptLineWithLayout(line, columns, hangingIndent).map((r) => r.rendered);
572
721
  }
573
722
  function buildWindowTitle(title) {
574
723
  const plain = stripAnsi(title).replace(/[\x00-\x1f\x7f]+/g, " ");
@@ -5371,6 +5520,9 @@ export {
5371
5520
  countPhysicalLines,
5372
5521
  takeDisplayWidth,
5373
5522
  padOrTruncateToWidth,
5523
+ tokenizeAnsiLine,
5524
+ buildSourceMapping,
5525
+ wrapTranscriptLineWithLayout,
5374
5526
  wrapTranscriptLine,
5375
5527
  buildWindowTitle,
5376
5528
  wrapTextToLines,
package/index.js CHANGED
@@ -126,7 +126,7 @@ function createDaemonShortcutCommand(path, description, command2) {
126
126
  // packages/cli/agentInternalCommandEntries.ts
127
127
  function getAgentInternalCommandEntries() {
128
128
  const createAgentRunCommand = (path, description) => createContextCommand(path, description, async (args2, ctx) => {
129
- const { runAgentRunCommand } = await import("./agentRunCommand-IWXLTYGK.js");
129
+ const { runAgentRunCommand } = await import("./agentRunCommand-OJX27NII.js");
130
130
  return runAgentRunCommand(args2, {
131
131
  env: ctx.env,
132
132
  scriptDir: ctx.scriptDir,
@@ -796,7 +796,7 @@ async function runScript(script, forwardedArgs, env) {
796
796
  return proc.exited;
797
797
  }
798
798
  async function launchTuiWorkspace(args2) {
799
- const { startTuiWorkspace } = await import("./readlineWorkspace-EH77JVMA.js");
799
+ const { startTuiWorkspace } = await import("./readlineWorkspace-J2ARMEUC.js");
800
800
  const { createTuiSummaryLlmCaller } = await import("./tuiSummaryLlmCaller-WUVAOIYU.js");
801
801
  return startTuiWorkspace({
802
802
  ...args2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nolo-cli",
3
- "version": "0.33.0-alpha.20",
3
+ "version": "0.33.0-alpha.21",
4
4
  "type": "module",
5
5
  "description": "Agent-first terminal workspace for Nolo",
6
6
  "bin": {
@@ -24,6 +24,7 @@ import {
24
24
  applyDetectedBackground,
25
25
  applyTerminalOutputToText,
26
26
  buildSkillContextBlocks,
27
+ buildSourceMapping,
27
28
  buildWindowTitle,
28
29
  clearCollapsedPasteStore,
29
30
  countPhysicalLines,
@@ -65,12 +66,14 @@ import {
65
66
  takeDisplayWidth,
66
67
  themeColorSequence,
67
68
  themeText,
69
+ tokenizeAnsiLine,
68
70
  truncateAnsi,
69
71
  userSurfaceBackgroundSequence,
70
72
  visibleWidth,
71
73
  wrapTextToLines,
72
- wrapTranscriptLine
73
- } from "./chunk-FR2AIV4Q.js";
74
+ wrapTranscriptLine,
75
+ wrapTranscriptLineWithLayout
76
+ } from "./chunk-46JSKTPF.js";
74
77
  import "./chunk-ZHYK3N56.js";
75
78
  import {
76
79
  getReadableCliDb
@@ -1754,197 +1757,161 @@ function createSelectionState() {
1754
1757
  };
1755
1758
  }
1756
1759
  function compareSelectionPoints(a, b) {
1757
- if (a.turnIndex !== b.turnIndex) {
1758
- return a.turnIndex - b.turnIndex;
1760
+ if (a.globalRow !== b.globalRow) {
1761
+ return a.globalRow - b.globalRow;
1759
1762
  }
1760
- return a.sourceOffset - b.sourceOffset;
1763
+ return a.col - b.col;
1761
1764
  }
1762
1765
  function areSelectionPointsEqual(a, b) {
1763
1766
  if (a === null && b === null) return true;
1764
1767
  if (a === null || b === null) return false;
1765
- return a.turnIndex === b.turnIndex && a.sourceOffset === b.sourceOffset;
1766
- }
1767
- function hitTestHistory(history, screenRow, screenCol, contentWidth, scrollTop) {
1768
- if (history.turns.length === 0) return null;
1769
- const { entries } = buildTurnOffsets2(history, contentWidth);
1770
- if (entries.length === 0) return null;
1771
- const globalRow = scrollTop + screenRow;
1772
- const firstEntry = entries[0];
1773
- if (globalRow < firstEntry.startRow) {
1774
- return { turnIndex: 0, sourceOffset: 0 };
1775
- }
1776
- const lastIdx = entries.length - 1;
1777
- const lastEntry = entries[lastIdx];
1778
- const lastTurn = history.turns[lastIdx];
1779
- if (globalRow >= lastEntry.startRow + lastEntry.lineCount) {
1780
- return { turnIndex: lastIdx, sourceOffset: lastTurn.content.length };
1781
- }
1782
- for (let i = 0; i < entries.length; i++) {
1783
- const entry = entries[i];
1784
- const turn = history.turns[i];
1785
- const turnStart = entry.startRow;
1786
- const turnEnd = turnStart + entry.lineCount;
1787
- if (entry.separatorAbove > 0 && globalRow === turnStart - 1) {
1788
- return { turnIndex: i, sourceOffset: 0 };
1789
- }
1790
- if (globalRow >= turnStart && globalRow < turnEnd) {
1791
- const rowInTurn = globalRow - turnStart;
1792
- const sourceOffset = mapRowColToSourceOffset(
1793
- turn.content,
1794
- turn.role,
1795
- contentWidth,
1796
- rowInTurn,
1797
- screenCol,
1798
- turn.command
1799
- );
1800
- return { turnIndex: i, sourceOffset };
1801
- }
1802
- }
1803
- return null;
1804
- }
1805
- function mapRowColToSourceOffset(content, role, contentWidth, targetRow, targetCol, command) {
1806
- const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1807
- const logicalLines = normalized.split("\n");
1808
- let currentRow = 0;
1809
- let charAccumulator = 0;
1810
- if (role === "local" && command) {
1811
- const cmdWrapped = wrapTranscriptLine(`\u203A ${command}`, contentWidth);
1812
- if (targetRow < cmdWrapped.length) {
1813
- return 0;
1814
- }
1815
- currentRow += cmdWrapped.length;
1816
- }
1817
- for (let l = 0; l < logicalLines.length; l++) {
1818
- const lineText = logicalLines[l];
1819
- const lineLen = lineText.length;
1820
- let wrappedRows;
1821
- let prefixFirst = 0;
1822
- let prefixCont = 0;
1823
- if (role === "user") {
1824
- wrappedRows = wrapTranscriptLine(`\u2503 ${lineText}`, contentWidth, "\u2503 ");
1825
- prefixFirst = 3;
1826
- prefixCont = 3;
1827
- } else if (role === "local") {
1828
- wrappedRows = wrapTranscriptLine(` ${lineText}`, contentWidth, " ");
1829
- prefixFirst = 2;
1830
- prefixCont = 2;
1831
- } else {
1832
- if (l === 0 && !lineText.startsWith("[nolo]")) {
1833
- wrappedRows = wrapTranscriptLine(`\u25C8 ${lineText}`, contentWidth);
1834
- prefixFirst = 2;
1835
- prefixCont = 0;
1836
- } else {
1837
- wrappedRows = wrapTranscriptLine(lineText, contentWidth);
1838
- prefixFirst = 0;
1839
- prefixCont = 0;
1840
- }
1841
- }
1842
- const rowCount = Math.max(1, wrappedRows.length);
1843
- if (targetRow >= currentRow && targetRow < currentRow + rowCount) {
1844
- const subRow = targetRow - currentRow;
1845
- let offsetInLine = 0;
1846
- for (let s = 0; s < subRow; s++) {
1847
- const rowStr = stripAnsi(wrappedRows[s] ?? "");
1848
- const pLen2 = s === 0 ? prefixFirst : prefixCont;
1849
- const pureText = rowStr.slice(pLen2);
1850
- offsetInLine += pureText.length;
1851
- }
1852
- const targetRowStr = stripAnsi(wrappedRows[subRow] ?? "");
1853
- const pLen = subRow === 0 ? prefixFirst : prefixCont;
1854
- const pureTargetText = targetRowStr.slice(pLen);
1855
- const effectiveCol = Math.max(0, targetCol - pLen);
1856
- let colAccum = 0;
1857
- for (let c = 0; c < pureTargetText.length; c++) {
1858
- const char = pureTargetText[c];
1859
- const w = displayWidth(char);
1860
- if (colAccum + w > effectiveCol) {
1861
- break;
1768
+ return a.globalRow === b.globalRow && a.col === b.col;
1769
+ }
1770
+ function highlightLineByColumns(line, selStartCol, selEndCol) {
1771
+ if (selStartCol >= selEndCol) return line;
1772
+ const tokens = tokenizeAnsiLine(line);
1773
+ if (tokens.length === 0) return line;
1774
+ let lastNonSpaceCol = 0;
1775
+ let colScan = 0;
1776
+ for (const tok of tokens) {
1777
+ if (tok.kind === "char") {
1778
+ if (tok.value.trim().length > 0) {
1779
+ lastNonSpaceCol = colScan + tok.width;
1780
+ }
1781
+ colScan += tok.width;
1782
+ }
1783
+ }
1784
+ const effectiveEndCol = Math.min(selEndCol, lastNonSpaceCol);
1785
+ if (selStartCol >= effectiveEndCol) return line;
1786
+ let out = "";
1787
+ let currentCol = 0;
1788
+ let isHighlightActive = false;
1789
+ for (let i = 0; i < tokens.length; i++) {
1790
+ const token = tokens[i];
1791
+ if (token.kind === "sgr") {
1792
+ if (isHighlightActive) {
1793
+ if (/^\x1b\[0?m$/.test(token.value)) {
1794
+ out += `${token.value}\x1B[7m`;
1795
+ } else {
1796
+ out += token.value;
1862
1797
  }
1863
- colAccum += w;
1864
- offsetInLine += char.length;
1865
- }
1866
- let finalOffset = Math.min(normalized.length, charAccumulator + Math.min(lineLen, offsetInLine));
1867
- if (finalOffset > 0 && finalOffset < normalized.length && normalized.charCodeAt(finalOffset - 1) >= 55296 && normalized.charCodeAt(finalOffset - 1) <= 56319) {
1868
- finalOffset += 1;
1798
+ } else {
1799
+ out += token.value;
1869
1800
  }
1870
- return finalOffset;
1801
+ continue;
1871
1802
  }
1872
- currentRow += rowCount;
1873
- charAccumulator += lineLen + 1;
1803
+ const charStartCol = currentCol;
1804
+ const charEndCol = currentCol + token.width;
1805
+ const isSelected = charStartCol >= selStartCol && charStartCol < effectiveEndCol;
1806
+ if (isSelected && !isHighlightActive) {
1807
+ out += "\x1B[7m";
1808
+ isHighlightActive = true;
1809
+ } else if (!isSelected && isHighlightActive) {
1810
+ out += "\x1B[27m";
1811
+ isHighlightActive = false;
1812
+ }
1813
+ out += token.value;
1814
+ currentCol = charEndCol;
1815
+ }
1816
+ if (isHighlightActive) {
1817
+ out += "\x1B[27m";
1818
+ }
1819
+ return out;
1820
+ }
1821
+ function hitTestHistory(history, screenRow, screenCol, _contentWidth, scrollTop) {
1822
+ const globalRow = Math.max(0, scrollTop + screenRow);
1823
+ const col = Math.max(0, screenCol);
1824
+ return { globalRow, col };
1825
+ }
1826
+ function extractTextSliceByColumns(plain, startCol, endCol) {
1827
+ let text = plain;
1828
+ let textStartCol = 0;
1829
+ if (text.startsWith("\u25C8 ")) {
1830
+ text = text.slice(2);
1831
+ textStartCol = 2;
1832
+ } else if (text.startsWith("\u2503 ")) {
1833
+ text = text.slice(3);
1834
+ textStartCol = 3;
1835
+ } else if (text.startsWith("\u203A ")) {
1836
+ text = text.slice(2);
1837
+ textStartCol = 2;
1838
+ }
1839
+ const effectiveStart = Math.max(0, startCol - textStartCol);
1840
+ const effectiveEnd = Math.max(0, endCol - textStartCol);
1841
+ if (effectiveStart >= effectiveEnd) return "";
1842
+ let out = "";
1843
+ let col = 0;
1844
+ for (const char of text) {
1845
+ const w = displayWidth(char);
1846
+ if (col + w > effectiveStart && col < effectiveEnd) {
1847
+ out += char;
1848
+ }
1849
+ col += w;
1874
1850
  }
1875
- return normalized.length;
1851
+ return out;
1876
1852
  }
1877
- function extractSelectedText(history, anchor, head) {
1853
+ function extractSelectedText(history, anchor, head, contentWidth = 80) {
1878
1854
  if (!anchor || !head) return "";
1879
1855
  const cmp = compareSelectionPoints(anchor, head);
1880
1856
  if (cmp === 0) return "";
1881
1857
  const start = cmp < 0 ? anchor : head;
1882
1858
  const end = cmp < 0 ? head : anchor;
1883
- if (start.turnIndex === end.turnIndex) {
1884
- const turn = history.turns[start.turnIndex];
1885
- if (!turn) return "";
1886
- const raw = turn.content.slice(start.sourceOffset, end.sourceOffset);
1887
- return stripAnsi(raw);
1888
- }
1889
- const pieces = [];
1890
- const startTurn2 = history.turns[start.turnIndex];
1891
- if (startTurn2) {
1892
- pieces.push(startTurn2.content.slice(start.sourceOffset));
1893
- }
1894
- for (let i = start.turnIndex + 1; i < end.turnIndex; i++) {
1895
- const midTurn = history.turns[i];
1896
- if (midTurn) {
1897
- pieces.push(midTurn.content);
1859
+ const lines = buildHistoryLines(history, contentWidth);
1860
+ const selectedLines = [];
1861
+ for (let r = start.globalRow; r <= end.globalRow; r++) {
1862
+ const rawLine = lines[r];
1863
+ if (rawLine === void 0) continue;
1864
+ const plain = stripAnsi(rawLine);
1865
+ if (plain.trim().length === 0) {
1866
+ if (selectedLines.length > 0 && selectedLines[selectedLines.length - 1] !== "") {
1867
+ selectedLines.push("");
1868
+ }
1869
+ continue;
1870
+ }
1871
+ let lineStartCol = 0;
1872
+ let lineEndCol = Infinity;
1873
+ if (r === start.globalRow) {
1874
+ lineStartCol = start.col;
1875
+ }
1876
+ if (r === end.globalRow) {
1877
+ lineEndCol = end.col;
1878
+ }
1879
+ const extracted = extractTextSliceByColumns(plain, lineStartCol, lineEndCol);
1880
+ if (extracted.length > 0) {
1881
+ selectedLines.push(extracted);
1898
1882
  }
1899
1883
  }
1900
- const endTurn = history.turns[end.turnIndex];
1901
- if (endTurn) {
1902
- pieces.push(endTurn.content.slice(0, end.sourceOffset));
1903
- }
1904
- return stripAnsi(pieces.join("\n\n"));
1884
+ return selectedLines.join("\n");
1905
1885
  }
1906
- function applySelectionOverlay(visibleLines, history, contentWidth, scrollTop, selection) {
1907
- if (!selection.dragging || !selection.anchor || !selection.head) {
1886
+ function applySelectionOverlay(visibleLines, _history, _contentWidth, scrollTop, selection) {
1887
+ if (!selection.anchor || !selection.head) {
1908
1888
  return visibleLines;
1909
1889
  }
1910
1890
  const cmp = compareSelectionPoints(selection.anchor, selection.head);
1911
1891
  if (cmp === 0) return visibleLines;
1912
1892
  const start = cmp < 0 ? selection.anchor : selection.head;
1913
1893
  const end = cmp < 0 ? selection.head : selection.anchor;
1914
- const { entries } = buildTurnOffsets2(history, contentWidth);
1915
1894
  const result = [...visibleLines];
1916
1895
  for (let screenRow = 0; screenRow < visibleLines.length; screenRow++) {
1917
1896
  const globalRow = scrollTop + screenRow;
1918
- let isSelectedRow = false;
1919
- for (let i = start.turnIndex; i <= end.turnIndex; i++) {
1920
- const entry = entries[i];
1921
- if (!entry) continue;
1922
- const turnStart = entry.startRow;
1923
- const turnEnd = turnStart + entry.lineCount;
1924
- if (i === start.turnIndex && i === end.turnIndex) {
1925
- if (globalRow >= turnStart && globalRow < turnEnd) {
1926
- isSelectedRow = true;
1927
- }
1928
- } else if (i === start.turnIndex) {
1929
- if (globalRow >= turnStart) {
1930
- isSelectedRow = true;
1931
- }
1932
- } else if (i === end.turnIndex) {
1933
- if (globalRow < turnEnd) {
1934
- isSelectedRow = true;
1935
- }
1936
- } else {
1937
- if (globalRow >= turnStart && globalRow < turnEnd) {
1938
- isSelectedRow = true;
1939
- }
1940
- }
1897
+ if (globalRow < start.globalRow || globalRow > end.globalRow) {
1898
+ continue;
1941
1899
  }
1942
- if (isSelectedRow) {
1943
- const line = result[screenRow] ?? "";
1944
- if (line.length > 0) {
1945
- result[screenRow] = `\x1B[7m${line}\x1B[27m`;
1946
- }
1900
+ const currentLine = result[screenRow] ?? "";
1901
+ if (currentLine.length === 0) continue;
1902
+ let selStartCol = 0;
1903
+ let selEndCol = Infinity;
1904
+ if (globalRow === start.globalRow) {
1905
+ selStartCol = start.col;
1947
1906
  }
1907
+ if (globalRow === end.globalRow) {
1908
+ selEndCol = end.col;
1909
+ }
1910
+ result[screenRow] = highlightLineByColumns(
1911
+ currentLine,
1912
+ selStartCol,
1913
+ selEndCol
1914
+ );
1948
1915
  }
1949
1916
  return result;
1950
1917
  }
@@ -1959,7 +1926,6 @@ function resetHistoryFrameDiffCache(output) {
1959
1926
  frameBufferByOutput.delete(output);
1960
1927
  }
1961
1928
  }
1962
- var renderCacheMissCount = 0;
1963
1929
  function createTurnHistory() {
1964
1930
  return {
1965
1931
  turns: [],
@@ -1970,11 +1936,8 @@ function createTurnHistory() {
1970
1936
  };
1971
1937
  }
1972
1938
  function startTurn(history, role) {
1973
- if (history.currentRole !== null) {
1974
- history.turns.push({
1975
- role: history.currentRole,
1976
- content: history.currentContent
1977
- });
1939
+ if (history.currentRole !== null && history.currentContent) {
1940
+ finalizeCurrentTurn(history);
1978
1941
  }
1979
1942
  history.currentRole = role;
1980
1943
  history.currentContent = "";
@@ -2036,9 +1999,8 @@ function fillUserBubbleRow(row, surfaceSeq, contentWidth) {
2036
1999
  }
2037
2000
  return renderSurfaceLine({ text: row, surface: surfaceSeq, padTo: contentWidth });
2038
2001
  }
2039
- function renderTurnBlock(role, content, contentWidth, colorEnabled, command) {
2002
+ function layoutTurnRows(role, content, contentWidth, colorEnabled, command) {
2040
2003
  content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2041
- const lines = [];
2042
2004
  if (role === "user") {
2043
2005
  const logicalLines = content.split("\n");
2044
2006
  const accentSeq = colorEnabled ? themeColorSequence("accent") : "";
@@ -2046,37 +2008,140 @@ function renderTurnBlock(role, content, contentWidth, colorEnabled, command) {
2046
2008
  const multilinePrefix = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2047
2009
  const hangingIndent = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2048
2010
  const surfaceSeq = colorEnabled ? userSurfaceBackgroundSequence() : "";
2011
+ const prefixWidth = 3;
2012
+ let lineSourceStart = 0;
2013
+ const rows2 = [];
2049
2014
  for (let i = 0; i < logicalLines.length; i++) {
2050
2015
  const line = logicalLines[i];
2051
2016
  const prefix = i === 0 ? firstPrefix : multilinePrefix;
2052
2017
  const styledLine = `${prefix}${line}`;
2053
- const rows = wrapTranscriptLine(styledLine, contentWidth, hangingIndent);
2054
- lines.push(
2055
- ...surfaceSeq ? rows.map((row) => fillUserBubbleRow(row, surfaceSeq, contentWidth)) : rows
2018
+ const prefixCharCount = prefix.length;
2019
+ const wrappedRows = wrapTranscriptLineWithLayout(
2020
+ styledLine,
2021
+ contentWidth,
2022
+ hangingIndent,
2023
+ lineSourceStart,
2024
+ prefixWidth,
2025
+ prefixCharCount
2056
2026
  );
2027
+ for (const r of wrappedRows) {
2028
+ const rendered = surfaceSeq ? fillUserBubbleRow(r.rendered, surfaceSeq, contentWidth) : r.rendered;
2029
+ rows2.push({
2030
+ rendered,
2031
+ sourceStart: r.sourceStart,
2032
+ sourceEnd: r.sourceEnd,
2033
+ prefixWidth: r.prefixWidth,
2034
+ sourceMapping: r.sourceMapping
2035
+ });
2036
+ }
2037
+ lineSourceStart += line.length + 1;
2057
2038
  }
2058
- } else if (role === "local") {
2039
+ return rows2.length > 0 ? rows2 : [{ rendered: surfaceSeq ? fillUserBubbleRow(firstPrefix, surfaceSeq, contentWidth) : firstPrefix, sourceStart: 0, sourceEnd: 0, prefixWidth: 3 }];
2040
+ }
2041
+ if (role === "local") {
2059
2042
  const dimSeq = colorEnabled ? "\x1B[2m" : "";
2060
2043
  const resetSeq = colorEnabled ? "\x1B[0m" : "";
2044
+ const rows2 = [];
2061
2045
  if (command) {
2062
- lines.push(
2063
- ...wrapTranscriptLine(`${dimSeq}\u203A ${command}${resetSeq}`, contentWidth)
2046
+ const cmdPrefix = `${dimSeq}\u203A `;
2047
+ const cmdLine = `${cmdPrefix}${command}${resetSeq}`;
2048
+ const cmdRows = wrapTranscriptLineWithLayout(
2049
+ cmdLine,
2050
+ contentWidth,
2051
+ "",
2052
+ 0,
2053
+ 2,
2054
+ cmdPrefix.length
2064
2055
  );
2056
+ for (const r of cmdRows) {
2057
+ rows2.push({
2058
+ rendered: r.rendered,
2059
+ sourceStart: 0,
2060
+ sourceEnd: 0,
2061
+ prefixWidth: 2,
2062
+ sourceMapping: void 0
2063
+ });
2064
+ }
2065
2065
  }
2066
+ let lineSourceStart = 0;
2066
2067
  if (content) {
2067
2068
  for (const line of content.split("\n")) {
2068
- lines.push(
2069
- ...wrapTranscriptLine(`${dimSeq} ${line}${resetSeq}`, contentWidth)
2069
+ const prefix = `${dimSeq} `;
2070
+ const styledLine = `${prefix}${line}${resetSeq}`;
2071
+ const prefixCharCount = prefix.length;
2072
+ const wrappedRows = wrapTranscriptLineWithLayout(
2073
+ styledLine,
2074
+ contentWidth,
2075
+ " ",
2076
+ lineSourceStart,
2077
+ 2,
2078
+ prefixCharCount
2070
2079
  );
2071
- }
2072
- }
2073
- } else {
2074
- const styledEntry = styleAssistantTurn(content, colorEnabled);
2075
- for (const logicalLine of styledEntry.split("\n")) {
2076
- lines.push(...wrapTranscriptLine(logicalLine, contentWidth));
2080
+ rows2.push(...wrappedRows);
2081
+ lineSourceStart += line.length + 1;
2082
+ }
2083
+ }
2084
+ return rows2.length > 0 ? rows2 : [{ rendered: "", sourceStart: 0, sourceEnd: 0, prefixWidth: 2 }];
2085
+ }
2086
+ const styledEntry = styleAssistantTurn(content, colorEnabled);
2087
+ const styledLines = styledEntry.split("\n");
2088
+ const rawLines = content.split("\n");
2089
+ let rawIdx = 0;
2090
+ let rawOffset = 0;
2091
+ const rows = [];
2092
+ for (let i = 0; i < styledLines.length; i++) {
2093
+ const styledLine = styledLines[i];
2094
+ const isFirstLine = i === 0 && !styledLine.startsWith("[nolo]");
2095
+ const anchorPrefix = isFirstLine ? colorEnabled ? `${themeColorSequence("chrome")}\u25C8\x1B[39m ` : "\u25C8 " : "";
2096
+ const prefixWidth = isFirstLine ? 2 : 0;
2097
+ const prefixCharCount = anchorPrefix.length;
2098
+ if (styledLine === "" && rawIdx < rawLines.length && rawLines[rawIdx] !== "") {
2099
+ rows.push({
2100
+ rendered: "",
2101
+ sourceStart: rawOffset,
2102
+ sourceEnd: rawOffset,
2103
+ prefixWidth: 0
2104
+ });
2105
+ continue;
2077
2106
  }
2078
- }
2079
- return lines;
2107
+ const rawLine = rawIdx < rawLines.length ? rawLines[rawIdx] : "";
2108
+ const rawLineLen = rawLine.length;
2109
+ const sourceMapping = buildSourceMapping(rawLine, styledLine, prefixCharCount);
2110
+ const lineRows = wrapTranscriptLineWithLayout(
2111
+ styledLine,
2112
+ contentWidth,
2113
+ "",
2114
+ rawOffset,
2115
+ prefixWidth,
2116
+ prefixCharCount,
2117
+ sourceMapping,
2118
+ rawLineLen
2119
+ );
2120
+ rows.push(...lineRows);
2121
+ rawIdx += 1;
2122
+ rawOffset += rawLineLen + 1;
2123
+ }
2124
+ return rows.length > 0 ? rows : [{ rendered: "", sourceStart: 0, sourceEnd: 0, prefixWidth: 0 }];
2125
+ }
2126
+ function getTurnLayoutRows(turn, contentWidth, colorEnabled, density, surface) {
2127
+ const cached = turnLineCache.get(turn);
2128
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.surface === surface) {
2129
+ return cached.layoutRows;
2130
+ }
2131
+ const layoutRows = layoutTurnRows(turn.role, turn.content, contentWidth, colorEnabled, turn.command);
2132
+ const lines = layoutRows.map((r) => r.rendered);
2133
+ turnLineCache.set(turn, {
2134
+ width: contentWidth,
2135
+ color: colorEnabled,
2136
+ density,
2137
+ surface,
2138
+ lines,
2139
+ layoutRows
2140
+ });
2141
+ return layoutRows;
2142
+ }
2143
+ function renderTurnBlock(role, content, contentWidth, colorEnabled, command) {
2144
+ return layoutTurnRows(role, content, contentWidth, colorEnabled, command).map((r) => r.rendered);
2080
2145
  }
2081
2146
  var streamingTurnCache = null;
2082
2147
  function resetStreamingTurnCache() {
@@ -2104,12 +2169,12 @@ function renderPrefixTurnBlock(role, content, contentWidth, colorEnabled) {
2104
2169
  }
2105
2170
  function renderTailTurnBlock(role, content, contentWidth, colorEnabled) {
2106
2171
  content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2107
- const lines = [];
2108
2172
  if (role === "user") {
2109
2173
  const surfaceSeq = colorEnabled ? userSurfaceBackgroundSequence() : "";
2110
2174
  const accentSeq = colorEnabled ? themeColorSequence("accent") : "";
2111
2175
  const multilinePrefix = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2112
2176
  const hangingIndent = colorEnabled ? `${accentSeq}\x1B[1m\u2503 ` : "\u2503 ";
2177
+ const lines = [];
2113
2178
  for (const rawLine of content.split("\n")) {
2114
2179
  const styledLine = `${multilinePrefix}${rawLine}`;
2115
2180
  const rows = wrapTranscriptLine(styledLine, contentWidth, hangingIndent);
@@ -2117,17 +2182,19 @@ function renderTailTurnBlock(role, content, contentWidth, colorEnabled) {
2117
2182
  ...surfaceSeq ? rows.map((row) => fillUserBubbleRow(row, surfaceSeq, contentWidth)) : rows
2118
2183
  );
2119
2184
  }
2185
+ return lines;
2120
2186
  } else {
2121
- const highlighted = colorEnabled ? formatAssistantDisplay(content) : stripAnsi(formatAssistantDisplay(content));
2187
+ const highlighted = colorEnabled ? formatAssistantDisplay(content, { trimEdges: false }) : stripAnsi(formatAssistantDisplay(content, { trimEdges: false }));
2122
2188
  const rawLines = highlighted.split("\n");
2123
2189
  const styledLines = rawLines.map((line) => {
2124
2190
  return line.startsWith("[nolo]") && colorEnabled ? themeText(line, "chrome", true) : line;
2125
2191
  });
2192
+ const lines = [];
2126
2193
  for (const logicalLine of styledLines) {
2127
2194
  lines.push(...wrapTranscriptLine(logicalLine, contentWidth));
2128
2195
  }
2196
+ return lines;
2129
2197
  }
2130
- return lines;
2131
2198
  }
2132
2199
  function getStreamingTurnLines(role, content, contentWidth, colorEnabled, density, surface) {
2133
2200
  if (streamingTurnCache && (streamingTurnCache.role !== role || streamingTurnCache.contentWidth !== contentWidth || streamingTurnCache.colorEnabled !== colorEnabled || streamingTurnCache.density !== density || streamingTurnCache.surface !== surface || !content.startsWith(streamingTurnCache.fullContent.slice(0, streamingTurnCache.prefixLength)))) {
@@ -2185,51 +2252,10 @@ function getStreamingTurnLines(role, content, contentWidth, colorEnabled, densit
2185
2252
  const tailLines = renderTailTurnBlock(role, tailContent, contentWidth, colorEnabled);
2186
2253
  return [...streamingTurnCache.prefixLines, ...tailLines];
2187
2254
  }
2188
- var assistantPlainLinesCache = /* @__PURE__ */ new Map();
2189
- function getAssistantPlainLines(content) {
2190
- let plainLines = assistantPlainLinesCache.get(content);
2191
- if (plainLines !== void 0) {
2192
- return plainLines;
2193
- }
2194
- if (!/[\r\x00#|\-*_~`>+\[\]()•☐☑]|\b\d+\./.test(content)) {
2195
- plainLines = content.trim().split("\n");
2196
- } else {
2197
- plainLines = stripAnsi(formatAssistantDisplay(content)).split("\n");
2198
- }
2199
- if (assistantPlainLinesCache.size > 2e3) {
2200
- const firstKey = assistantPlainLinesCache.keys().next().value;
2201
- if (firstKey !== void 0) assistantPlainLinesCache.delete(firstKey);
2202
- }
2203
- assistantPlainLinesCache.set(content, plainLines);
2204
- return plainLines;
2205
- }
2206
2255
  function countTurnLines(role, content, contentWidth, command) {
2207
- content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2208
- let count = 0;
2209
- if (role === "user") {
2210
- for (const line of content.split("\n")) {
2211
- count += wrapTranscriptLine(`\u2503 ${line}`, contentWidth, "\u2503 ").length;
2212
- }
2213
- } else if (role === "local") {
2214
- if (command) {
2215
- count += wrapTranscriptLine(`\u203A ${command}`, contentWidth).length;
2216
- }
2217
- if (content) {
2218
- for (const line of content.split("\n")) {
2219
- count += wrapTranscriptLine(` ${line}`, contentWidth).length;
2220
- }
2221
- }
2222
- } else {
2223
- const plainLines = getAssistantPlainLines(content);
2224
- for (let i = 0; i < plainLines.length; i++) {
2225
- const line = plainLines[i];
2226
- const wrapped = i === 0 && !line.startsWith("[nolo]") ? wrapTranscriptLine(`\u25C8 ${line}`, contentWidth) : wrapTranscriptLine(line, contentWidth);
2227
- count += wrapped.length;
2228
- }
2229
- }
2230
- return count;
2256
+ return layoutTurnRows(role, content, contentWidth, false, command).length;
2231
2257
  }
2232
- function buildTurnOffsets2(history, contentWidth) {
2258
+ function buildTurnOffsets(history, contentWidth) {
2233
2259
  const density = getActiveDensity();
2234
2260
  const entries = [];
2235
2261
  let offset = 0;
@@ -2250,6 +2276,36 @@ function buildTurnOffsets2(history, contentWidth) {
2250
2276
  }
2251
2277
  return { entries, totalLines: offset };
2252
2278
  }
2279
+ function getAllTurnEntries(history, contentWidth) {
2280
+ const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
2281
+ const turns = [...history.turns];
2282
+ if (history.currentRole !== null && history.currentContent) {
2283
+ const density = getActiveDensity();
2284
+ const colorEnabled = resolveCliColorEnabled();
2285
+ const surface = colorEnabled ? userSurfaceBackgroundSequence() : "";
2286
+ const streamingLines = getStreamingTurnLines(
2287
+ history.currentRole,
2288
+ history.currentContent,
2289
+ contentWidth,
2290
+ colorEnabled,
2291
+ density,
2292
+ surface
2293
+ );
2294
+ const sep = currentTurnSeparator(history, density);
2295
+ const startRow = finalizedLines + sep;
2296
+ entries.push({
2297
+ startRow,
2298
+ lineCount: streamingLines.length,
2299
+ separatorAbove: sep
2300
+ });
2301
+ turns.push({
2302
+ role: history.currentRole,
2303
+ content: history.currentContent
2304
+ });
2305
+ return { entries, totalLines: startRow + streamingLines.length, turns };
2306
+ }
2307
+ return { entries, totalLines: finalizedLines, turns };
2308
+ }
2253
2309
  function currentTurnSeparator(history, density) {
2254
2310
  return density === "spacious" && (history.turns.length > 0 || history.currentRole === "user") ? 1 : 0;
2255
2311
  }
@@ -2273,6 +2329,37 @@ function buildCopyViewLines(history) {
2273
2329
  }
2274
2330
  return lines;
2275
2331
  }
2332
+ function buildHistoryLines(history, contentWidth) {
2333
+ const colorEnabled = resolveCliColorEnabled();
2334
+ const density = getActiveDensity();
2335
+ const surface = colorEnabled ? userSurfaceBackgroundSequence() : "";
2336
+ const wrapped = [];
2337
+ for (let i = 0; i < history.turns.length; i++) {
2338
+ const turn = history.turns[i];
2339
+ if (density === "spacious" && (i > 0 || turn.role === "user")) {
2340
+ wrapped.push("");
2341
+ }
2342
+ const layoutRows = getTurnLayoutRows(turn, contentWidth, colorEnabled, density, surface);
2343
+ wrapped.push(...layoutRows.map((r) => r.rendered));
2344
+ }
2345
+ if (history.currentRole !== null && history.currentContent) {
2346
+ const i = history.turns.length;
2347
+ if (density === "spacious" && (i > 0 || history.currentRole === "user")) {
2348
+ wrapped.push("");
2349
+ }
2350
+ const streamingLines = getStreamingTurnLines(
2351
+ history.currentRole,
2352
+ history.currentContent,
2353
+ contentWidth,
2354
+ colorEnabled,
2355
+ density,
2356
+ surface
2357
+ );
2358
+ wrapped.push(...streamingLines);
2359
+ }
2360
+ return wrapped;
2361
+ }
2362
+ var renderCacheMissCount = 0;
2276
2363
  function renderHistory(output, history, inputLines, selection) {
2277
2364
  const tty = output;
2278
2365
  if (!tty.isTTY) return;
@@ -2283,13 +2370,9 @@ function renderHistory(output, history, inputLines, selection) {
2283
2370
  const colorEnabled = resolveCliColorEnabled();
2284
2371
  const density = getActiveDensity();
2285
2372
  const surface = colorEnabled ? userSurfaceBackgroundSequence() : "";
2286
- const { entries, totalLines: finalizedLines } = buildTurnOffsets2(
2287
- history,
2288
- contentWidth
2289
- );
2290
- let totalLines = finalizedLines;
2291
- let currentStart = -1;
2373
+ const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
2292
2374
  let currentLines = [];
2375
+ let currentStart = -1;
2293
2376
  if (history.currentRole !== null && history.currentContent) {
2294
2377
  currentLines = getStreamingTurnLines(
2295
2378
  history.currentRole,
@@ -2299,16 +2382,17 @@ function renderHistory(output, history, inputLines, selection) {
2299
2382
  density,
2300
2383
  surface
2301
2384
  );
2302
- currentStart = finalizedLines + currentTurnSeparator(history, density);
2303
- totalLines = currentStart + currentLines.length;
2385
+ const sep = currentTurnSeparator(history, density);
2386
+ currentStart = finalizedLines + sep;
2304
2387
  }
2388
+ const totalLines = currentStart >= 0 ? currentStart + currentLines.length : finalizedLines;
2305
2389
  if (history.followBottom) {
2306
2390
  history.scrollTop = Math.max(0, totalLines - visibleHeight);
2307
2391
  } else {
2308
- history.scrollTop = Math.max(
2309
- 0,
2310
- Math.min(history.scrollTop, Math.max(0, totalLines - visibleHeight))
2311
- );
2392
+ const maxScroll = Math.max(0, totalLines - visibleHeight);
2393
+ if (history.scrollTop > maxScroll) {
2394
+ history.scrollTop = maxScroll;
2395
+ }
2312
2396
  }
2313
2397
  history.hasMoreAbove = history.scrollTop > 0;
2314
2398
  history.hasMoreBelow = history.scrollTop + visibleHeight < totalLines;
@@ -2328,20 +2412,14 @@ function renderHistory(output, history, inputLines, selection) {
2328
2412
  if (turnEnd <= winStart || turnStart >= winEnd) continue;
2329
2413
  const turn = history.turns[i];
2330
2414
  const cached = turnLineCache.get(turn);
2331
- let lines;
2415
+ let layoutRows;
2332
2416
  if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.surface === surface) {
2333
- lines = cached.lines;
2417
+ layoutRows = cached.layoutRows;
2334
2418
  } else {
2335
2419
  renderCacheMissCount += 1;
2336
- lines = renderTurnBlock(turn.role, turn.content, contentWidth, colorEnabled, turn.command);
2337
- turnLineCache.set(turn, {
2338
- width: contentWidth,
2339
- color: colorEnabled,
2340
- density,
2341
- surface,
2342
- lines
2343
- });
2420
+ layoutRows = getTurnLayoutRows(turn, contentWidth, colorEnabled, density, surface);
2344
2421
  }
2422
+ const lines = layoutRows.map((r) => r.rendered);
2345
2423
  const interStart = Math.max(turnStart, winStart);
2346
2424
  const interEnd = Math.min(turnEnd, winEnd);
2347
2425
  for (let r = interStart; r < interEnd; r++) {
@@ -2365,7 +2443,7 @@ function renderHistory(output, history, inputLines, selection) {
2365
2443
  }
2366
2444
  }
2367
2445
  }
2368
- if (selection && selection.dragging) {
2446
+ if (selection && selection.anchor && selection.head) {
2369
2447
  visibleLines = applySelectionOverlay(
2370
2448
  visibleLines,
2371
2449
  history,
@@ -2377,11 +2455,11 @@ function renderHistory(output, history, inputLines, selection) {
2377
2455
  const prevBuffer = typeof output === "object" && output !== null ? frameBufferByOutput.get(output) : void 0;
2378
2456
  const isGeometryCompatible = prevBuffer !== void 0 && prevBuffer.rows === rows && prevBuffer.columns === columns && prevBuffer.inputLines === inputLines && prevBuffer.lines.length === visibleHeight;
2379
2457
  const prevLines = isGeometryCompatible ? prevBuffer.lines : void 0;
2380
- const nextLines = new Array(visibleHeight);
2381
2458
  let frame = "";
2459
+ const nextLines = new Array(visibleHeight);
2382
2460
  for (let i = 0; i < visibleHeight; i++) {
2383
- const line = visibleLines[i] ?? "";
2384
- const padded = padOrTruncateToWidth(line, contentWidth);
2461
+ const content = visibleLines[i] ?? "";
2462
+ const padded = padOrTruncateToWidth(content, contentWidth);
2385
2463
  const thumb = renderScrollbarRow(i, visibleHeight, totalLines, history.scrollTop);
2386
2464
  const scrollbarPrefix = colorEnabled ? themeColorSequence("chrome") : "";
2387
2465
  const scrollbarSuffix = colorEnabled ? "\x1B[39m" : "";
@@ -2407,9 +2485,6 @@ function renderHistory(output, history, inputLines, selection) {
2407
2485
  }
2408
2486
  function createHistoryOutputStream(history, onUpdate) {
2409
2487
  return {
2410
- // Virtual TTY: Spinner uses \\r in-place updates. We honor that via
2411
- // applyTerminalOutputToText so frames collapse to one status line instead
2412
- // of spamming the transcript. Do not fall through to process.stdout here.
2413
2488
  isTTY: true,
2414
2489
  write(chunk) {
2415
2490
  const text = typeof chunk === "string" ? chunk : chunk.toString();
@@ -2426,7 +2501,7 @@ function applyScrollAction(history, action, output, inputLines) {
2426
2501
  const columns = tty.columns ?? 80;
2427
2502
  const visibleHeight = Math.max(1, rows - inputLines);
2428
2503
  const contentWidth = Math.max(1, columns - 1);
2429
- const { totalLines: finalizedLines } = buildTurnOffsets2(history, contentWidth);
2504
+ const { totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
2430
2505
  let totalLines = finalizedLines;
2431
2506
  if (history.currentRole !== null && history.currentContent) {
2432
2507
  totalLines += currentTurnSeparator(history, getActiveDensity()) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
@@ -7406,7 +7481,7 @@ ${err.message}` : ""}`
7406
7481
  stopAutoScroll();
7407
7482
  }
7408
7483
  } else {
7409
- const { totalLines } = buildTurnOffsets(history, contentWidth);
7484
+ const { totalLines } = getAllTurnEntries(history, contentWidth);
7410
7485
  const maxScroll = Math.max(0, totalLines - visibleHeight);
7411
7486
  if (history.scrollTop < maxScroll) {
7412
7487
  history.scrollTop = Math.min(maxScroll, history.scrollTop + 1);
@@ -7573,13 +7648,18 @@ ${merged}` : merged;
7573
7648
  import("./clipboardy-MMBYVRLF.js").then(({ default: clipboard }) => clipboard.write(textToCopy)).catch(() => {
7574
7649
  });
7575
7650
  }
7651
+ selectionState.dragging = false;
7652
+ } else {
7653
+ clearSelection();
7576
7654
  }
7577
- clearSelection();
7578
7655
  paintFrame(buffer);
7579
7656
  return;
7580
7657
  }
7581
7658
  return;
7582
7659
  }
7660
+ if (selectionState.anchor) {
7661
+ clearSelection();
7662
+ }
7583
7663
  const scrollAction = parseScrollAction(sequence);
7584
7664
  if (scrollAction) {
7585
7665
  if (fixedInput.isPaused()) return;