@oxecli/oxe 1.0.112 → 1.0.114

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 (2) hide show
  1. package/dist/ui.js +68 -9
  2. package/package.json +1 -1
package/dist/ui.js CHANGED
@@ -1249,6 +1249,17 @@ export const PROMPT_PLACEHOLDER = "Describe a coding task, or type /help for com
1249
1249
  const MAX_PROMPT_DISPLAY_LINES = 12;
1250
1250
  const MAX_PROMPT_PASTE_CHARS = 400;
1251
1251
  const MAX_PROMPT_CHARS = 500;
1252
+ /**
1253
+ * Watchdog for bracketed paste. Some terminals (notably Windows Terminal /
1254
+ * conpty) occasionally drop the paste-END marker, which would leave the field
1255
+ * permanently capturing keystrokes (a "locked" prompt). If a paste-start
1256
+ * arrives but no paste-end AND no further paste data shows up within this
1257
+ * window, we treat the paste as aborted, release capture, and keep whatever
1258
+ * text already arrived so input is never lost. The window resets on every
1259
+ * received paste chunk, so a genuine (continuously-streaming) paste is never
1260
+ * cut short.
1261
+ */
1262
+ const PASTE_GUARD_MS = 3000;
1252
1263
  function shouldCollapsePaste(text) {
1253
1264
  return (text.split("\n").length > MAX_PROMPT_DISPLAY_LINES ||
1254
1265
  text.length > MAX_PROMPT_PASTE_CHARS);
@@ -1492,18 +1503,29 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
1492
1503
  let totalRows = body.length + 2;
1493
1504
  if (hints && (hints.left || hints.right)) {
1494
1505
  let left = hints.left ?? "";
1495
- const right = hints.right ?? "";
1506
+ let right = hints.right ?? "";
1496
1507
  // Inset the hint row inside the box (box spans columns 0..boxW-1): a
1497
1508
  // leading space gives left-edge padding, and the right hint ends one
1498
- // column before the box's right border for the same padding. Truncate the
1499
- // left hint so the row never exceeds the box width on narrow terminals
1500
- // (otherwise it overflows past the right border instead of scaling down).
1509
+ // column before the box's right border for the same padding. Truncate so
1510
+ // the row NEVER exceeds the box width on narrow/scaled terminals: the left
1511
+ // hint is cut first to keep the (priority) right hint intact, then the
1512
+ // right hint is cut as a last resort. Otherwise a long right hint would
1513
+ // overflow past the border instead of scaling down.
1501
1514
  const hintMax = boxW - 2;
1502
- if (plainLen(left) + plainLen(right) + 1 > hintMax) {
1503
- const room = Math.max(0, hintMax - plainLen(right) - 1);
1504
- left = truncateStyled(left, room);
1515
+ const maxText = Math.max(0, hintMax - 1); // reserve a >=1 gap between hints
1516
+ let leftW = plainLen(left);
1517
+ let rightW = plainLen(right);
1518
+ if (leftW + rightW > maxText) {
1519
+ const roomL = Math.max(0, maxText - rightW);
1520
+ left = truncateStyled(left, roomL);
1521
+ leftW = plainLen(left);
1522
+ }
1523
+ const roomR = Math.max(0, maxText - leftW);
1524
+ if (rightW > roomR) {
1525
+ right = truncateStyled(right, roomR);
1526
+ rightW = plainLen(right);
1505
1527
  }
1506
- const gap = Math.max(1, boxW - 2 - plainLen(left) - plainLen(right));
1528
+ const gap = Math.max(1, hintMax - leftW - rightW);
1507
1529
  lines.push(" " + left + " ".repeat(gap) + right);
1508
1530
  totalRows += 1;
1509
1531
  }
@@ -1555,6 +1577,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1555
1577
  let draft = null;
1556
1578
  let bracketedPaste = false;
1557
1579
  let bracketedPasteBuffer = "";
1580
+ let pasteGuardTimer = null;
1558
1581
  let pasteBurst = false;
1559
1582
  let pasteBurstTimer = null;
1560
1583
  readline.emitKeypressEvents(process.stdin);
@@ -1593,6 +1616,10 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1593
1616
  bracketedPasteBuffer = "";
1594
1617
  if (pasteBurstTimer)
1595
1618
  clearTimeout(pasteBurstTimer);
1619
+ if (pasteGuardTimer) {
1620
+ clearTimeout(pasteGuardTimer);
1621
+ pasteGuardTimer = null;
1622
+ }
1596
1623
  if (isErr) {
1597
1624
  clearBox();
1598
1625
  dockSetInactive();
@@ -1898,17 +1925,46 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1898
1925
  const end = "\x1b[201~";
1899
1926
  const sequence = key?.sequence || "";
1900
1927
  const value = str || "";
1928
+ // A paste-end marker must close the capture. If it never arrives and no
1929
+ // more paste data follows, the guard below auto-releases the field so it
1930
+ // can't lock. (definitions hoisted below)
1931
+ const armPasteGuard = () => {
1932
+ if (pasteGuardTimer)
1933
+ clearTimeout(pasteGuardTimer);
1934
+ pasteGuardTimer = setTimeout(() => {
1935
+ pasteGuardTimer = null;
1936
+ if (!bracketedPaste)
1937
+ return;
1938
+ // The terminal dropped the paste-end (a Windows/conpty failure). Bail
1939
+ // out of capture mode and insert whatever already arrived so nothing
1940
+ // typed afterward is swallowed.
1941
+ bracketedPaste = false;
1942
+ const leftover = bracketedPasteBuffer;
1943
+ bracketedPasteBuffer = "";
1944
+ if (leftover)
1945
+ insert(leftover, true);
1946
+ repaint();
1947
+ }, PASTE_GUARD_MS);
1948
+ };
1949
+ const disarmPasteGuard = () => {
1950
+ if (pasteGuardTimer) {
1951
+ clearTimeout(pasteGuardTimer);
1952
+ pasteGuardTimer = null;
1953
+ }
1954
+ };
1901
1955
  // A new paste-start while already inside a paste means the previous paste
1902
1956
  // was aborted (its end marker never arrived). Reset and start over so we
1903
1957
  // never swallow subsequent typing.
1904
1958
  if ((key?.name === "paste-start" || sequence === start || value === start)) {
1905
1959
  bracketedPaste = true;
1906
1960
  bracketedPasteBuffer = "";
1961
+ armPasteGuard();
1907
1962
  const inline = value === start ? "" : value.startsWith(start) ? value.slice(start.length) : "";
1908
1963
  if (inline)
1909
1964
  bracketedPasteBuffer = inline;
1910
1965
  if (inline.includes(end)) {
1911
1966
  const pasted = inline.slice(0, inline.indexOf(end)).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1967
+ disarmPasteGuard();
1912
1968
  bracketedPaste = false;
1913
1969
  bracketedPasteBuffer = "";
1914
1970
  if (pasted)
@@ -1926,6 +1982,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1926
1982
  if (key?.name === "paste-end" || sequence === end || value === end || endAt >= 0) {
1927
1983
  bracketedPasteBuffer += endAt >= 0 ? value.slice(0, endAt) : "";
1928
1984
  const pasted = bracketedPasteBuffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1985
+ disarmPasteGuard();
1929
1986
  bracketedPaste = false;
1930
1987
  bracketedPasteBuffer = "";
1931
1988
  if (pasted)
@@ -1934,8 +1991,10 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1934
1991
  return true;
1935
1992
  }
1936
1993
  const chunk = value || (sequence && !sequence.startsWith("\x1b[") ? sequence : "");
1937
- if (chunk)
1994
+ if (chunk) {
1938
1995
  bracketedPasteBuffer += chunk;
1996
+ armPasteGuard();
1997
+ }
1939
1998
  return true;
1940
1999
  };
1941
2000
  const markPasteBurst = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.112",
3
+ "version": "1.0.114",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },