@code-yeongyu/senpi-tui 2026.7.25-2 → 2026.7.28

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 (50) hide show
  1. package/dist/components/editor.d.ts +22 -6
  2. package/dist/components/editor.d.ts.map +1 -1
  3. package/dist/components/editor.js +56 -120
  4. package/dist/components/editor.js.map +1 -1
  5. package/dist/components/image.d.ts.map +1 -1
  6. package/dist/components/image.js +7 -1
  7. package/dist/components/image.js.map +1 -1
  8. package/dist/components/select-list.d.ts +32 -0
  9. package/dist/components/select-list.d.ts.map +1 -1
  10. package/dist/components/select-list.js +15 -7
  11. package/dist/components/select-list.js.map +1 -1
  12. package/dist/editor-component.d.ts +18 -0
  13. package/dist/editor-component.d.ts.map +1 -1
  14. package/dist/editor-component.js.map +1 -1
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +3 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/paste-markers.d.ts +29 -0
  20. package/dist/paste-markers.d.ts.map +1 -0
  21. package/dist/paste-markers.js +185 -0
  22. package/dist/paste-markers.js.map +1 -0
  23. package/dist/terminal-capabilities.d.ts +18 -0
  24. package/dist/terminal-capabilities.d.ts.map +1 -0
  25. package/dist/terminal-capabilities.js +62 -0
  26. package/dist/terminal-capabilities.js.map +1 -0
  27. package/dist/terminal-image.d.ts +31 -6
  28. package/dist/terminal-image.d.ts.map +1 -1
  29. package/dist/terminal-image.js +137 -92
  30. package/dist/terminal-image.js.map +1 -1
  31. package/dist/tmux-focus.d.ts +8 -0
  32. package/dist/tmux-focus.d.ts.map +1 -0
  33. package/dist/tmux-focus.js +14 -0
  34. package/dist/tmux-focus.js.map +1 -0
  35. package/dist/tmux-image-capability.d.ts +17 -0
  36. package/dist/tmux-image-capability.d.ts.map +1 -0
  37. package/dist/tmux-image-capability.js +61 -0
  38. package/dist/tmux-image-capability.js.map +1 -0
  39. package/dist/tmux-image-probe.d.ts +39 -0
  40. package/dist/tmux-image-probe.d.ts.map +1 -0
  41. package/dist/tmux-image-probe.js +121 -0
  42. package/dist/tmux-image-probe.js.map +1 -0
  43. package/dist/tui.d.ts +3 -0
  44. package/dist/tui.d.ts.map +1 -1
  45. package/dist/tui.js +63 -33
  46. package/dist/tui.js.map +1 -1
  47. package/dist/utils.d.ts.map +1 -1
  48. package/dist/utils.js +19 -0
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +2 -2
package/dist/tui.js CHANGED
@@ -8,7 +8,8 @@ import { performance } from "node:perf_hooks";
8
8
  import { isKeyRelease, matchesKey } from "./keys.js";
9
9
  import { isMultiplexerSession, useLegacyMuxRender, viewportRenderEnabled } from "./mux.js";
10
10
  import { isOsc11BackgroundColorResponse, parseOsc11BackgroundColor, parseTerminalColorSchemeReport, } from "./terminal-colors.js";
11
- import { deleteKittyImage, getCapabilities, isImageLine, setCellDimensions } from "./terminal-image.js";
11
+ import { deleteKittyImage, getCapabilities, isImageLine, resetCapabilitiesCache, setCellDimensions, } from "./terminal-image.js";
12
+ import { consumeTmuxFocusEvent, DISABLE_FOCUS_REPORTING, ENABLE_FOCUS_REPORTING } from "./tmux-focus.js";
12
13
  import { extractSegments, normalizeTerminalOutput, sliceByColumn, sliceWithWidth, visibleWidth } from "./utils.js";
13
14
  const KITTY_SEQUENCE_PREFIX = "\x1b_G";
14
15
  function parseKittyImageHeader(line) {
@@ -58,6 +59,7 @@ export const CURSOR_MARKER = "\x1b_pi:c\x07";
58
59
  export { visibleWidth };
59
60
  const renderErrorLoggedClasses = new Set();
60
61
  let renderErrorLogWrites = 0;
62
+ let renderDiagnosticLineScans = 0;
61
63
  const DIAGNOSTIC_LOG_MODE = 0o600;
62
64
  const VIEWPORT_RENDER_OVERSCAN = 16;
63
65
  // Keep scroll-region wins cheap when a few visible rows mutate during append streaming.
@@ -77,6 +79,12 @@ export function __renderErrorLogStats() {
77
79
  }
78
80
  return { writes: renderErrorLogWrites };
79
81
  }
82
+ export function __renderDiagnosticStats() {
83
+ if (process.env.PI_TUI_TEST_SEAMS !== "1") {
84
+ return undefined;
85
+ }
86
+ return { linesScanned: renderDiagnosticLineScans };
87
+ }
80
88
  export function __viewportRenderStats() {
81
89
  if (process.env.PI_TUI_TEST_SEAMS !== "1") {
82
90
  return undefined;
@@ -142,6 +150,18 @@ function writeRenderDiagnosticBestEffort(logPath, data) {
142
150
  throw error;
143
151
  }
144
152
  }
153
+ function formatOverWideRenderDiagnostic(lines, terminalWidth, lineIndex, lineWidth) {
154
+ renderDiagnosticLineScans += lines.length;
155
+ return [
156
+ `Crash at ${new Date().toISOString()}`,
157
+ `Terminal width: ${terminalWidth}`,
158
+ `Line ${lineIndex} visible width: ${lineWidth}`,
159
+ "",
160
+ "=== All rendered lines ===",
161
+ ...lines.map((line, index) => `[${index}] (w=${visibleWidth(line)}) ${line}`),
162
+ "",
163
+ ].join("\n");
164
+ }
145
165
  function chmodDiagnosticLogBestEffort(logPath) {
146
166
  try {
147
167
  fs.chmodSync(logPath, DIAGNOSTIC_LOG_MODE);
@@ -576,6 +596,8 @@ export class TUI extends Container {
576
596
  this.inputRenderPending = false;
577
597
  this.#lastCursorVisibility = undefined;
578
598
  this.terminal.start((data) => this.handleInput(data), () => this.requestRender());
599
+ if (process.env.TMUX)
600
+ this.terminal.write(ENABLE_FOCUS_REPORTING);
579
601
  this.#setCursorVisibility(false);
580
602
  if (this.terminalColorSchemeNotificationsEnabled) {
581
603
  this.terminal.write("\x1b[?2031h");
@@ -643,6 +665,8 @@ export class TUI extends Container {
643
665
  }
644
666
  this.#lastCursorVisibility = undefined;
645
667
  this.#setCursorVisibility(true);
668
+ if (process.env.TMUX)
669
+ this.terminal.write(DISABLE_FOCUS_REPORTING);
646
670
  this.terminal.stop();
647
671
  this.#lastCursorVisibility = undefined;
648
672
  this.previousLines = [];
@@ -749,6 +773,15 @@ export class TUI extends Container {
749
773
  this.doRender();
750
774
  }
751
775
  handleInput(data) {
776
+ const focus = consumeTmuxFocusEvent(data);
777
+ if (focus.event !== null) {
778
+ resetCapabilitiesCache();
779
+ this.invalidate();
780
+ this.requestRender(true);
781
+ if (focus.data.length === 0)
782
+ return;
783
+ data = focus.data;
784
+ }
752
785
  if (this.consumeOsc11BackgroundResponse(data)) {
753
786
  return;
754
787
  }
@@ -1350,8 +1383,11 @@ export class TUI extends Container {
1350
1383
  }
1351
1384
  /** Splice overlay content into a base line at a specific column. Single-pass optimized. */
1352
1385
  compositeLineAt(baseLine, overlayLine, startCol, overlayWidth, totalWidth) {
1353
- if (isImageLine(baseLine))
1386
+ if (isImageLine(baseLine) && visibleWidth(baseLine) === 0)
1354
1387
  return baseLine;
1388
+ const placeholderIndex = baseLine.indexOf("\u{10eeee}");
1389
+ const protocolEnd = placeholderIndex === -1 ? -1 : baseLine.lastIndexOf("\x1b\\", placeholderIndex);
1390
+ const protocolPrefix = protocolEnd === -1 ? "" : baseLine.slice(0, protocolEnd + 2);
1355
1391
  // Single pass through baseLine extracts both before and after segments
1356
1392
  const afterStart = startCol + overlayWidth;
1357
1393
  const base = extractSegments(baseLine, startCol, afterStart, totalWidth - afterStart, true);
@@ -1382,10 +1418,10 @@ export class TUI extends Container {
1382
1418
  // - Edge cases in segment extraction
1383
1419
  const resultWidth = visibleWidth(result);
1384
1420
  if (resultWidth <= totalWidth) {
1385
- return result;
1421
+ return protocolPrefix + result;
1386
1422
  }
1387
1423
  // Truncate with strict=true to ensure we don't exceed totalWidth
1388
- return sliceByColumn(result, 0, totalWidth, true);
1424
+ return protocolPrefix + sliceByColumn(result, 0, totalWidth, true);
1389
1425
  }
1390
1426
  /**
1391
1427
  * Find and extract cursor position from rendered lines.
@@ -1758,36 +1794,30 @@ export class TUI extends Container {
1758
1794
  buffer += `\x1b[2K${TUI.SEGMENT_RESET}`; // Clear current line
1759
1795
  const lineWidth = visibleWidth(line);
1760
1796
  if (!isImage && lineWidth > width) {
1761
- // Log all lines to crash file for debugging
1762
1797
  const crashLogPath = path.join(os.homedir(), ".senpi", "agent", "senpi-crash.log");
1763
- const crashData = [
1764
- `Crash at ${new Date().toISOString()}`,
1765
- `Terminal width: ${width}`,
1766
- `Line ${i} visible width: ${lineWidth}`,
1767
- "",
1768
- "=== All rendered lines ===",
1769
- ...newLines.map((l, idx) => `[${idx}] (w=${visibleWidth(l)}) ${l}`),
1770
- "",
1771
- ].join("\n");
1772
- if (process.env.PI_TUI_STRICT_RENDER === "1") {
1773
- const crashDumpWritten = writeRenderDiagnosticBestEffort(crashLogPath, crashData);
1774
- // Clean up terminal state before throwing
1775
- this.stop();
1776
- const errorMsg = [
1777
- `Rendered line ${i} exceeds terminal width (${lineWidth} > ${width}).`,
1778
- "",
1779
- "This is likely caused by a custom TUI component not truncating its output.",
1780
- "Use visibleWidth() to measure and truncateToWidth() to truncate lines.",
1781
- "",
1782
- crashDumpWritten
1783
- ? `Debug log written to: ${crashLogPath}`
1784
- : `Debug log could not be written to: ${crashLogPath}`,
1785
- ].join("\n");
1786
- throw new Error(errorMsg);
1787
- }
1788
- if (!this.overWideCrashDumpWritten) {
1789
- writeRenderDiagnosticBestEffort(crashLogPath, crashData);
1790
- this.overWideCrashDumpWritten = true;
1798
+ const strictRender = process.env.PI_TUI_STRICT_RENDER === "1";
1799
+ if (strictRender || !this.overWideCrashDumpWritten) {
1800
+ const crashData = formatOverWideRenderDiagnostic(newLines, width, i, lineWidth);
1801
+ if (!strictRender) {
1802
+ writeRenderDiagnosticBestEffort(crashLogPath, crashData);
1803
+ this.overWideCrashDumpWritten = true;
1804
+ }
1805
+ else {
1806
+ const crashDumpWritten = writeRenderDiagnosticBestEffort(crashLogPath, crashData);
1807
+ // Clean up terminal state before throwing
1808
+ this.stop();
1809
+ const errorMsg = [
1810
+ `Rendered line ${i} exceeds terminal width (${lineWidth} > ${width}).`,
1811
+ "",
1812
+ "This is likely caused by a custom TUI component not truncating its output.",
1813
+ "Use visibleWidth() to measure and truncateToWidth() to truncate lines.",
1814
+ "",
1815
+ crashDumpWritten
1816
+ ? `Debug log written to: ${crashLogPath}`
1817
+ : `Debug log could not be written to: ${crashLogPath}`,
1818
+ ].join("\n");
1819
+ throw new Error(errorMsg);
1820
+ }
1791
1821
  }
1792
1822
  const truncatedLine = sliceByColumn(line, 0, width, true) + TUI.SEGMENT_RESET;
1793
1823
  newLines[i] = truncatedLine;