@oxecli/oxe 1.0.111 → 1.0.113
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.
- package/dist/engine.js +0 -13
- package/dist/ui.js +70 -5
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -11,10 +11,6 @@ import { takePendingDiffOutput } from "./tools.js";
|
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
const tokenEstCache = new Map();
|
|
13
13
|
const tokenEstCacheMax = 4096;
|
|
14
|
-
/** Minimum time (ms) a tool's "Working"/"Running" status stays visible after
|
|
15
|
-
* the tool returns, so instant tools (write_file/edit_file) are still seen to
|
|
16
|
-
* stream when called before the result replaces the status line. */
|
|
17
|
-
const MIN_TOOL_STATUS_MS = 350;
|
|
18
14
|
export function estimateTokens(items) {
|
|
19
15
|
let total = 0;
|
|
20
16
|
for (const item of items) {
|
|
@@ -596,7 +592,6 @@ export class InferenceEngine {
|
|
|
596
592
|
dockTransientStart("flush");
|
|
597
593
|
const toolSpinner = new Spinner();
|
|
598
594
|
toolSpinner.startDots(`\x1b[90m╰─\x1b[0m ${c.name === "bash" ? "Running" : "Working"}`);
|
|
599
|
-
const t0 = Date.now();
|
|
600
595
|
const rawResult = await this.runTool(c.name, c.arguments, this.activeAbort?.signal);
|
|
601
596
|
// A tool call was made, which interrupts any current working phase,
|
|
602
597
|
// so the next agent iteration may commit a fresh "Worked for …" block.
|
|
@@ -605,14 +600,6 @@ export class InferenceEngine {
|
|
|
605
600
|
toolSpinner.stop();
|
|
606
601
|
throw new Error("interrupt");
|
|
607
602
|
}
|
|
608
|
-
// Keep the status line streaming for at least MIN_TOOL_STATUS_MS so
|
|
609
|
-
// even instant tools (write_file/edit_file) visibly show their
|
|
610
|
-
// "Working" indicator when called, then swap to the result. The
|
|
611
|
-
// spinner's own timer animates the dots during this window.
|
|
612
|
-
const elapsed = Date.now() - t0;
|
|
613
|
-
if (elapsed < MIN_TOOL_STATUS_MS) {
|
|
614
|
-
await new Promise((res) => setTimeout(res, MIN_TOOL_STATUS_MS - elapsed));
|
|
615
|
-
}
|
|
616
603
|
const failed = toolOutputFailed(c.name, rawResult);
|
|
617
604
|
const action = formatToolResult(rawResult, failed);
|
|
618
605
|
const diff = takePendingDiffOutput();
|
package/dist/ui.js
CHANGED
|
@@ -173,6 +173,19 @@ export function truncateStyled(text, maxVisible) {
|
|
|
173
173
|
}
|
|
174
174
|
return out;
|
|
175
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Truncate styled text to at most `maxVisible` visible characters and, if
|
|
178
|
+
* truncated, append a visible ellipsis "…" so the cut is obvious. Used by the
|
|
179
|
+
* panel frame so long titles/subtitles shrink to the box instead of overflowing.
|
|
180
|
+
*/
|
|
181
|
+
export function truncateStyledEllipsis(text, maxVisible) {
|
|
182
|
+
if (maxVisible <= 0)
|
|
183
|
+
return "";
|
|
184
|
+
if (plainLen(text) <= maxVisible)
|
|
185
|
+
return text;
|
|
186
|
+
const dot = maxVisible > 1 ? "…" : "";
|
|
187
|
+
return truncateStyled(text, Math.max(1, maxVisible - (dot ? 1 : 0))) + dot;
|
|
188
|
+
}
|
|
176
189
|
/**
|
|
177
190
|
* Wrap a styled (ANSI) string into lines of at most `width` visible characters,
|
|
178
191
|
* wrapping whole words onto new lines and splitting any single word longer than
|
|
@@ -563,13 +576,17 @@ function panelString(content, title = "", subtitle = "", expand = true, borderSt
|
|
|
563
576
|
const embed = (text, align) => {
|
|
564
577
|
if (!text)
|
|
565
578
|
return "─".repeat(Math.max(boxW - 2, 0));
|
|
566
|
-
|
|
579
|
+
// Truncate the title/subtitle so the frame always scales down to the box
|
|
580
|
+
// width (matching the prompt field): a long subtitle used to overflow the
|
|
581
|
+
// bottom border on narrow/scaled terminals. Ellipsis marks the cut.
|
|
582
|
+
const prefix = align === "left" ? "───" : "";
|
|
583
|
+
const avail = Math.max(boxW - 2 - plainLen(prefix), 0);
|
|
584
|
+
const inner = ` ${truncateStyledEllipsis(text, Math.max(avail - 2, 0))} `;
|
|
567
585
|
if (align === "left") {
|
|
568
|
-
const
|
|
569
|
-
const fill = Math.max(boxW - 2 - plainLen(prefix) - plainLen(inner), 0);
|
|
586
|
+
const fill = Math.max(avail - plainLen(inner), 0);
|
|
570
587
|
return `${prefix}${inner}${"─".repeat(fill)}`;
|
|
571
588
|
}
|
|
572
|
-
const fill = Math.max(
|
|
589
|
+
const fill = Math.max(avail - plainLen(inner), 0);
|
|
573
590
|
const left = Math.floor(fill / 2);
|
|
574
591
|
const right = fill - left;
|
|
575
592
|
return `${"─".repeat(left)}${inner}${"─".repeat(right)}`;
|
|
@@ -1232,6 +1249,17 @@ export const PROMPT_PLACEHOLDER = "Describe a coding task, or type /help for com
|
|
|
1232
1249
|
const MAX_PROMPT_DISPLAY_LINES = 12;
|
|
1233
1250
|
const MAX_PROMPT_PASTE_CHARS = 400;
|
|
1234
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;
|
|
1235
1263
|
function shouldCollapsePaste(text) {
|
|
1236
1264
|
return (text.split("\n").length > MAX_PROMPT_DISPLAY_LINES ||
|
|
1237
1265
|
text.length > MAX_PROMPT_PASTE_CHARS);
|
|
@@ -1538,6 +1566,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1538
1566
|
let draft = null;
|
|
1539
1567
|
let bracketedPaste = false;
|
|
1540
1568
|
let bracketedPasteBuffer = "";
|
|
1569
|
+
let pasteGuardTimer = null;
|
|
1541
1570
|
let pasteBurst = false;
|
|
1542
1571
|
let pasteBurstTimer = null;
|
|
1543
1572
|
readline.emitKeypressEvents(process.stdin);
|
|
@@ -1576,6 +1605,10 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1576
1605
|
bracketedPasteBuffer = "";
|
|
1577
1606
|
if (pasteBurstTimer)
|
|
1578
1607
|
clearTimeout(pasteBurstTimer);
|
|
1608
|
+
if (pasteGuardTimer) {
|
|
1609
|
+
clearTimeout(pasteGuardTimer);
|
|
1610
|
+
pasteGuardTimer = null;
|
|
1611
|
+
}
|
|
1579
1612
|
if (isErr) {
|
|
1580
1613
|
clearBox();
|
|
1581
1614
|
dockSetInactive();
|
|
@@ -1881,17 +1914,46 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1881
1914
|
const end = "\x1b[201~";
|
|
1882
1915
|
const sequence = key?.sequence || "";
|
|
1883
1916
|
const value = str || "";
|
|
1917
|
+
// A paste-end marker must close the capture. If it never arrives and no
|
|
1918
|
+
// more paste data follows, the guard below auto-releases the field so it
|
|
1919
|
+
// can't lock. (definitions hoisted below)
|
|
1920
|
+
const armPasteGuard = () => {
|
|
1921
|
+
if (pasteGuardTimer)
|
|
1922
|
+
clearTimeout(pasteGuardTimer);
|
|
1923
|
+
pasteGuardTimer = setTimeout(() => {
|
|
1924
|
+
pasteGuardTimer = null;
|
|
1925
|
+
if (!bracketedPaste)
|
|
1926
|
+
return;
|
|
1927
|
+
// The terminal dropped the paste-end (a Windows/conpty failure). Bail
|
|
1928
|
+
// out of capture mode and insert whatever already arrived so nothing
|
|
1929
|
+
// typed afterward is swallowed.
|
|
1930
|
+
bracketedPaste = false;
|
|
1931
|
+
const leftover = bracketedPasteBuffer;
|
|
1932
|
+
bracketedPasteBuffer = "";
|
|
1933
|
+
if (leftover)
|
|
1934
|
+
insert(leftover, true);
|
|
1935
|
+
repaint();
|
|
1936
|
+
}, PASTE_GUARD_MS);
|
|
1937
|
+
};
|
|
1938
|
+
const disarmPasteGuard = () => {
|
|
1939
|
+
if (pasteGuardTimer) {
|
|
1940
|
+
clearTimeout(pasteGuardTimer);
|
|
1941
|
+
pasteGuardTimer = null;
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1884
1944
|
// A new paste-start while already inside a paste means the previous paste
|
|
1885
1945
|
// was aborted (its end marker never arrived). Reset and start over so we
|
|
1886
1946
|
// never swallow subsequent typing.
|
|
1887
1947
|
if ((key?.name === "paste-start" || sequence === start || value === start)) {
|
|
1888
1948
|
bracketedPaste = true;
|
|
1889
1949
|
bracketedPasteBuffer = "";
|
|
1950
|
+
armPasteGuard();
|
|
1890
1951
|
const inline = value === start ? "" : value.startsWith(start) ? value.slice(start.length) : "";
|
|
1891
1952
|
if (inline)
|
|
1892
1953
|
bracketedPasteBuffer = inline;
|
|
1893
1954
|
if (inline.includes(end)) {
|
|
1894
1955
|
const pasted = inline.slice(0, inline.indexOf(end)).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1956
|
+
disarmPasteGuard();
|
|
1895
1957
|
bracketedPaste = false;
|
|
1896
1958
|
bracketedPasteBuffer = "";
|
|
1897
1959
|
if (pasted)
|
|
@@ -1909,6 +1971,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1909
1971
|
if (key?.name === "paste-end" || sequence === end || value === end || endAt >= 0) {
|
|
1910
1972
|
bracketedPasteBuffer += endAt >= 0 ? value.slice(0, endAt) : "";
|
|
1911
1973
|
const pasted = bracketedPasteBuffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
1974
|
+
disarmPasteGuard();
|
|
1912
1975
|
bracketedPaste = false;
|
|
1913
1976
|
bracketedPasteBuffer = "";
|
|
1914
1977
|
if (pasted)
|
|
@@ -1917,8 +1980,10 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1917
1980
|
return true;
|
|
1918
1981
|
}
|
|
1919
1982
|
const chunk = value || (sequence && !sequence.startsWith("\x1b[") ? sequence : "");
|
|
1920
|
-
if (chunk)
|
|
1983
|
+
if (chunk) {
|
|
1921
1984
|
bracketedPasteBuffer += chunk;
|
|
1985
|
+
armPasteGuard();
|
|
1986
|
+
}
|
|
1922
1987
|
return true;
|
|
1923
1988
|
};
|
|
1924
1989
|
const markPasteBurst = () => {
|