@oxecli/oxe 1.0.109 → 1.0.111
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 +17 -3
- package/dist/tools.js +25 -8
- package/dist/ui.js +27 -4
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -11,6 +11,10 @@ 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;
|
|
14
18
|
export function estimateTokens(items) {
|
|
15
19
|
let total = 0;
|
|
16
20
|
for (const item of items) {
|
|
@@ -585,12 +589,14 @@ export class InferenceEngine {
|
|
|
585
589
|
for (const c of calls) {
|
|
586
590
|
this.queryCalledTool = true;
|
|
587
591
|
const started = toolCallLabel(c.name, c.arguments);
|
|
588
|
-
// Print the tool label the instant the
|
|
589
|
-
// "╰─
|
|
592
|
+
// Print the tool label the instant the tool starts, then show a
|
|
593
|
+
// "╰─ Working..." (file tools) or "╰─ Running..." (bash) dots line
|
|
594
|
+
// that streams while the tool runs and swaps to the real result.
|
|
590
595
|
dockAppendContent(`${started}\n`);
|
|
591
596
|
dockTransientStart("flush");
|
|
592
597
|
const toolSpinner = new Spinner();
|
|
593
|
-
toolSpinner.startDots(
|
|
598
|
+
toolSpinner.startDots(`\x1b[90m╰─\x1b[0m ${c.name === "bash" ? "Running" : "Working"}`);
|
|
599
|
+
const t0 = Date.now();
|
|
594
600
|
const rawResult = await this.runTool(c.name, c.arguments, this.activeAbort?.signal);
|
|
595
601
|
// A tool call was made, which interrupts any current working phase,
|
|
596
602
|
// so the next agent iteration may commit a fresh "Worked for …" block.
|
|
@@ -599,6 +605,14 @@ export class InferenceEngine {
|
|
|
599
605
|
toolSpinner.stop();
|
|
600
606
|
throw new Error("interrupt");
|
|
601
607
|
}
|
|
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
|
+
}
|
|
602
616
|
const failed = toolOutputFailed(c.name, rawResult);
|
|
603
617
|
const action = formatToolResult(rawResult, failed);
|
|
604
618
|
const diff = takePendingDiffOutput();
|
package/dist/tools.js
CHANGED
|
@@ -4,7 +4,7 @@ import { execFile, spawn } from "node:child_process";
|
|
|
4
4
|
import { structuredPatch } from "diff";
|
|
5
5
|
import { max_diff_source_chars, max_diff_lines, max_diff_context_lines, max_diff_line_chars, max_read_line_chars, max_read_file_bytes, max_read_lines, max_output_chars, max_bash_timeout_seconds, max_grep_file_bytes, strict_max_properties, ignoredDirs, } from "./config.js";
|
|
6
6
|
import { toolLoadSkill } from "./skills.js";
|
|
7
|
-
import { truncateStyled, plainLen, terminalWidth, highlightCodeLine } from "./ui.js";
|
|
7
|
+
import { truncateStyled, plainLen, terminalWidth, highlightCodeLine, fitBoxWidth } from "./ui.js";
|
|
8
8
|
export { toolLoadSkill };
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Path sanitization helper
|
|
@@ -31,6 +31,18 @@ function truncateDiffLine(line) {
|
|
|
31
31
|
}
|
|
32
32
|
return line;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Truncate a styled line to at most `maxVisible` visible characters and, if
|
|
36
|
+
* truncation occurred, append a visible ellipsis "…". The diff viewer must
|
|
37
|
+
* NEVER wrap a line onto a new row (that breaks the +/- column alignment), so
|
|
38
|
+
* every row is hard-truncated here; the ellipsis makes the truncation obvious.
|
|
39
|
+
*/
|
|
40
|
+
function truncateStyledEllipsis(text, maxVisible) {
|
|
41
|
+
if (plainLen(text) <= maxVisible)
|
|
42
|
+
return text;
|
|
43
|
+
const dot = maxVisible > 1 ? "…" : "";
|
|
44
|
+
return truncateStyled(text, Math.max(1, maxVisible - (dot ? 1 : 0))) + dot;
|
|
45
|
+
}
|
|
34
46
|
/**
|
|
35
47
|
* Recolor bracket syntax characters (`( ) [ ] { }`) inside an already-ANSI
|
|
36
48
|
* rendered string to `fg`, restoring the row's white base after each bracket.
|
|
@@ -76,8 +88,9 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
76
88
|
// tool result text above it ("╰─ Wrote N chars").
|
|
77
89
|
const ind = " ";
|
|
78
90
|
if (oldContent.length + newContent.length > max_diff_source_chars) {
|
|
79
|
-
|
|
80
|
-
`(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)
|
|
91
|
+
const hiddenMsg = `${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
|
|
92
|
+
`(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)`;
|
|
93
|
+
pendingDiffOutput.push(`\x1b[90m${ind}${truncateStyledEllipsis(hiddenMsg, Math.max(terminalWidth() - 2, 1))}\x1b[0m`);
|
|
81
94
|
return null;
|
|
82
95
|
}
|
|
83
96
|
const patch = structuredPatch(pathName, pathName, oldContent, newContent, "", "", { context: max_diff_context_lines });
|
|
@@ -148,6 +161,10 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
148
161
|
return { kind: k, body: line.slice(1), oldNum, newNum };
|
|
149
162
|
});
|
|
150
163
|
const termW = terminalWidth();
|
|
164
|
+
// The diff rows render outside a panel, so clamp their width to the scaled
|
|
165
|
+
// box width (minus margin) so they never overflow on narrow terminals.
|
|
166
|
+
const boxW = fitBoxWidth(termW - 4, termW);
|
|
167
|
+
const innerW = Math.max(boxW - 4, 1);
|
|
151
168
|
let maxNum = 1;
|
|
152
169
|
for (const v of visible) {
|
|
153
170
|
if (v.oldNum)
|
|
@@ -156,11 +173,11 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
156
173
|
maxNum = Math.max(maxNum, v.newNum);
|
|
157
174
|
}
|
|
158
175
|
const numW = String(maxNum).length;
|
|
159
|
-
const bodyMax = Math.max(
|
|
176
|
+
const bodyMax = Math.max(innerW - ind.length - numW - 4, 1);
|
|
160
177
|
const gap = " ".repeat(numW);
|
|
161
178
|
for (const v of visible) {
|
|
162
179
|
if (v.kind === "~") {
|
|
163
|
-
const body =
|
|
180
|
+
const body = truncateStyledEllipsis(v.body, bodyMax);
|
|
164
181
|
pendingDiffOutput.push(`\x1b[90m${ind}${gap} ${body}\x1b[0m`);
|
|
165
182
|
continue;
|
|
166
183
|
}
|
|
@@ -177,14 +194,14 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
177
194
|
// background + white base after each token reset so both hold across
|
|
178
195
|
// the whole body.
|
|
179
196
|
const raw = highlightCodeLine(v.body);
|
|
180
|
-
const body =
|
|
197
|
+
const body = truncateStyledEllipsis(raw.replace(/\x1b\[0m/g, `\x1b[0m${bg}\x1b[97m`), bodyMax);
|
|
181
198
|
// Deleted rows: recolor bracket syntax to the same red as the line
|
|
182
199
|
// number (fg) so brackets match the row's deleted color. Added rows keep
|
|
183
200
|
// their green highlighter as-is.
|
|
184
201
|
const shown = isAdd
|
|
185
202
|
? body
|
|
186
203
|
: recolorBrackets(body, fg, "\x1b[97m");
|
|
187
|
-
const fill = Math.max(0,
|
|
204
|
+
const fill = Math.max(0, innerW - ind.length - numW - 4 - plainLen(body));
|
|
188
205
|
pendingDiffOutput.push(`${ind}${bg}${fg}${numStr} ${sign} \x1b[0m${bg}\x1b[97m${shown}${bg}\x1b[97m${" ".repeat(fill)}\x1b[0m`);
|
|
189
206
|
}
|
|
190
207
|
else {
|
|
@@ -193,7 +210,7 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
193
210
|
// lines (only the number gutter stays dim). highlightCodeLine resets to
|
|
194
211
|
// default after each token, so re-apply the base after every reset.
|
|
195
212
|
const base = v.kind === "\\" ? "\x1b[90m" : "\x1b[97m";
|
|
196
|
-
const body =
|
|
213
|
+
const body = truncateStyledEllipsis(raw.replace(/\x1b\[0m/g, `\x1b[0m${base}`), bodyMax);
|
|
197
214
|
pendingDiffOutput.push(`\x1b[90m${ind}${numStr} \x1b[0m${base}${body}\x1b[0m`);
|
|
198
215
|
}
|
|
199
216
|
}
|
package/dist/ui.js
CHANGED
|
@@ -117,7 +117,7 @@ export function terminalWidth() {
|
|
|
117
117
|
return process.stdout.columns || 80;
|
|
118
118
|
}
|
|
119
119
|
/** Minimum width for boxes/cards so they don't collapse to nothing. */
|
|
120
|
-
const MIN_BOX_WIDTH =
|
|
120
|
+
const MIN_BOX_WIDTH = 52;
|
|
121
121
|
/** Horizontal margin reserved on each side of a box/card from the terminal edge. */
|
|
122
122
|
const BOX_MARGIN = 4;
|
|
123
123
|
/**
|
|
@@ -127,7 +127,7 @@ const BOX_MARGIN = 4;
|
|
|
127
127
|
* terminal is narrower than min+margin, so cards size down smoothly instead of
|
|
128
128
|
* overflowing.
|
|
129
129
|
*/
|
|
130
|
-
function fitBoxWidth(pref, termW) {
|
|
130
|
+
export function fitBoxWidth(pref, termW) {
|
|
131
131
|
const maxW = Math.max(termW - BOX_MARGIN, 1);
|
|
132
132
|
return Math.min(Math.max(Math.min(pref, maxW), MIN_BOX_WIDTH), maxW);
|
|
133
133
|
}
|
|
@@ -363,13 +363,36 @@ function formatMarkdownTable(tableLines) {
|
|
|
363
363
|
}
|
|
364
364
|
colWidths.push(Math.max(mw, 3));
|
|
365
365
|
}
|
|
366
|
+
// Scale the table to fit the terminal: shrink columns (each toward a minimum
|
|
367
|
+
// of 3) so the whole table stays inside the box width instead of overflowing
|
|
368
|
+
// or wrapping its borders on a narrow/scaled terminal. Cell content that no
|
|
369
|
+
// longer fits is truncated to the column width when rendered.
|
|
370
|
+
{
|
|
371
|
+
const termW = terminalWidth();
|
|
372
|
+
const boxW = fitBoxWidth(termW - 4, termW);
|
|
373
|
+
const availW = Math.max(boxW - 4, 1);
|
|
374
|
+
// total = Σ(colWidth+2) + (ncols-1) separators + 2 outer borders.
|
|
375
|
+
const totalW = colWidths.reduce((a, w) => a + w + 2, 0) + (colWidths.length - 1) + 2;
|
|
376
|
+
let extra = totalW - availW;
|
|
377
|
+
if (extra > 0) {
|
|
378
|
+
for (let c = 0; c < colWidths.length && extra > 0; c++) {
|
|
379
|
+
const room = colWidths[c] - 3;
|
|
380
|
+
if (room > 0) {
|
|
381
|
+
const cut = Math.min(room, extra);
|
|
382
|
+
colWidths[c] -= cut;
|
|
383
|
+
extra -= cut;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
366
388
|
const out = [];
|
|
367
389
|
const topBorder = `\x1b[90m╭${colWidths.map((w) => "─".repeat(w + 2)).join("┬")}╮\x1b[0m`;
|
|
368
390
|
const midBorder = `\x1b[90m├${colWidths.map((w) => "─".repeat(w + 2)).join("┼")}┤\x1b[0m`;
|
|
369
391
|
const botBorder = `\x1b[90m╰${colWidths.map((w) => "─".repeat(w + 2)).join("┴")}╯\x1b[0m`;
|
|
370
392
|
out.push(topBorder);
|
|
371
393
|
const headCells = header.map((h, i) => {
|
|
372
|
-
const
|
|
394
|
+
const raw = `\x1b[1;37m${formatInlineMarkdown(h)}\x1b[0m`;
|
|
395
|
+
const formatted = truncateStyled(raw, colWidths[i]);
|
|
373
396
|
const pad = colWidths[i] - plainLen(formatted);
|
|
374
397
|
return ` ${formatted}${" ".repeat(Math.max(pad, 0))} `;
|
|
375
398
|
});
|
|
@@ -377,7 +400,7 @@ function formatMarkdownTable(tableLines) {
|
|
|
377
400
|
out.push(midBorder);
|
|
378
401
|
for (const r of rows) {
|
|
379
402
|
const rowCells = header.map((_, i) => {
|
|
380
|
-
const cell = r[i] ? formatInlineMarkdown(r[i]) : "";
|
|
403
|
+
const cell = r[i] ? truncateStyled(formatInlineMarkdown(r[i]), colWidths[i]) : "";
|
|
381
404
|
const pad = colWidths[i] - plainLen(cell);
|
|
382
405
|
return ` ${cell}${" ".repeat(Math.max(pad, 0))} `;
|
|
383
406
|
});
|