@oxecli/oxe 1.0.108 → 1.0.110
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 +3 -3
- package/dist/tools.js +7 -3
- package/dist/ui.js +82 -49
- package/package.json +1 -1
package/dist/engine.js
CHANGED
|
@@ -586,11 +586,11 @@ export class InferenceEngine {
|
|
|
586
586
|
this.queryCalledTool = true;
|
|
587
587
|
const started = toolCallLabel(c.name, c.arguments);
|
|
588
588
|
// Print the tool label the instant the command starts, then show a
|
|
589
|
-
// "╰─
|
|
589
|
+
// "╰─ Running..." dots line that swaps to the real result in place.
|
|
590
590
|
dockAppendContent(`${started}\n`);
|
|
591
591
|
dockTransientStart("flush");
|
|
592
592
|
const toolSpinner = new Spinner();
|
|
593
|
-
toolSpinner.startDots("\x1b[90m╰─\x1b[0m
|
|
593
|
+
toolSpinner.startDots("\x1b[90m╰─\x1b[0m Running");
|
|
594
594
|
const rawResult = await this.runTool(c.name, c.arguments, this.activeAbort?.signal);
|
|
595
595
|
// A tool call was made, which interrupts any current working phase,
|
|
596
596
|
// so the next agent iteration may commit a fresh "Worked for …" block.
|
|
@@ -609,7 +609,7 @@ export class InferenceEngine {
|
|
|
609
609
|
status: failed ? "failed" : "ok",
|
|
610
610
|
diff,
|
|
611
611
|
});
|
|
612
|
-
// Swap the "╰─
|
|
612
|
+
// Swap the "╰─ Running..." line for the real result. If the tool
|
|
613
613
|
// produced a diff it renders directly below the action line with no
|
|
614
614
|
// gap; otherwise the dock gap separates the action from what follows.
|
|
615
615
|
toolSpinner.stop();
|
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
|
|
@@ -148,6 +148,10 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
148
148
|
return { kind: k, body: line.slice(1), oldNum, newNum };
|
|
149
149
|
});
|
|
150
150
|
const termW = terminalWidth();
|
|
151
|
+
// The diff rows render outside a panel, so clamp their width to the scaled
|
|
152
|
+
// box width (minus margin) so they never overflow on narrow terminals.
|
|
153
|
+
const boxW = fitBoxWidth(termW - 4, termW);
|
|
154
|
+
const innerW = Math.max(boxW - 4, 1);
|
|
151
155
|
let maxNum = 1;
|
|
152
156
|
for (const v of visible) {
|
|
153
157
|
if (v.oldNum)
|
|
@@ -156,7 +160,7 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
156
160
|
maxNum = Math.max(maxNum, v.newNum);
|
|
157
161
|
}
|
|
158
162
|
const numW = String(maxNum).length;
|
|
159
|
-
const bodyMax = Math.max(
|
|
163
|
+
const bodyMax = Math.max(innerW - ind.length - numW - 4, 1);
|
|
160
164
|
const gap = " ".repeat(numW);
|
|
161
165
|
for (const v of visible) {
|
|
162
166
|
if (v.kind === "~") {
|
|
@@ -184,7 +188,7 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
184
188
|
const shown = isAdd
|
|
185
189
|
? body
|
|
186
190
|
: recolorBrackets(body, fg, "\x1b[97m");
|
|
187
|
-
const fill = Math.max(0,
|
|
191
|
+
const fill = Math.max(0, innerW - ind.length - numW - 4 - plainLen(body));
|
|
188
192
|
pendingDiffOutput.push(`${ind}${bg}${fg}${numStr} ${sign} \x1b[0m${bg}\x1b[97m${shown}${bg}\x1b[97m${" ".repeat(fill)}\x1b[0m`);
|
|
189
193
|
}
|
|
190
194
|
else {
|
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
|
}
|
|
@@ -198,55 +198,62 @@ function wrapStyledToWidth(line, width) {
|
|
|
198
198
|
pending = "";
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
-
const
|
|
202
|
-
|
|
201
|
+
const render = (atoms) => atoms.map((a) => a.pre + a.ch).join("");
|
|
202
|
+
const rows = [];
|
|
203
|
+
let cur = [];
|
|
203
204
|
let curLen = 0;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
// Index (in `cur`) of the whitespace atom that is a valid break point, or -1.
|
|
206
|
+
let breakAt = -1;
|
|
207
|
+
// The last SGR open code currently in effect, so a word split mid-way can
|
|
208
|
+
// re-apply the style on its continuation.
|
|
209
|
+
let activeStyle = "";
|
|
210
|
+
for (const a of units) {
|
|
211
|
+
// Track the active style from this glyph's ANSI prefix.
|
|
212
|
+
if (a.pre) {
|
|
213
|
+
const sgr = a.pre.match(/\x1b\[[0-9;]*m/g);
|
|
214
|
+
if (sgr && sgr.length) {
|
|
215
|
+
const last = sgr[sgr.length - 1];
|
|
216
|
+
activeStyle = last === "\x1b[0m" ? "" : last;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const isSpace = /\s/.test(a.ch);
|
|
220
|
+
if (isSpace) {
|
|
221
|
+
cur.push(a);
|
|
211
222
|
curLen++;
|
|
223
|
+
breakAt = cur.length - 1;
|
|
224
|
+
continue;
|
|
212
225
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (!emitChunk(chunk, ci === 0)) {
|
|
232
|
-
out.push(cur);
|
|
233
|
-
cur = "";
|
|
226
|
+
if (curLen + 1 > width) {
|
|
227
|
+
if (breakAt >= 0) {
|
|
228
|
+
// Break after the whitespace at breakAt: keep the whitespace's ANSI
|
|
229
|
+
// prefix (often a reset) so style never leaks onto the continuation.
|
|
230
|
+
const space = cur[breakAt];
|
|
231
|
+
const lead = space.pre; // e.g. \x1b[0m — applied at start of next row
|
|
232
|
+
const kept = cur.slice(breakAt + 1);
|
|
233
|
+
rows.push(cur.slice(0, breakAt));
|
|
234
|
+
cur = [{ ch: "", pre: lead }, ...kept];
|
|
235
|
+
curLen = kept.length;
|
|
236
|
+
breakAt = -1;
|
|
237
|
+
}
|
|
238
|
+
else if (cur.length) {
|
|
239
|
+
// No whitespace break point: split the word mid-way, re-applying the
|
|
240
|
+
// active style on the continuation so a styled word keeps its style.
|
|
241
|
+
rows.push(cur);
|
|
242
|
+
const lead = activeStyle ? activeStyle : "";
|
|
243
|
+
cur = lead ? [{ ch: "", pre: lead }] : [];
|
|
234
244
|
curLen = 0;
|
|
235
|
-
emitChunk(chunk, false);
|
|
236
245
|
}
|
|
237
246
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
for (const u of units) {
|
|
241
|
-
if (/\s/.test(u.ch))
|
|
242
|
-
flushWord();
|
|
243
|
-
else
|
|
244
|
-
word.push(u);
|
|
247
|
+
cur.push(a);
|
|
248
|
+
curLen++;
|
|
245
249
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return
|
|
250
|
+
if (cur.length)
|
|
251
|
+
rows.push(cur);
|
|
252
|
+
// Emit each row, ending with a reset so no style bleeds across rows.
|
|
253
|
+
return rows.map((r) => {
|
|
254
|
+
const rendered = render(r);
|
|
255
|
+
return rendered + (plainLen(rendered) ? "\x1b[0m" : "");
|
|
256
|
+
});
|
|
250
257
|
}
|
|
251
258
|
// ---------------------------------------------------------------------------
|
|
252
259
|
// Markup tag -> ANSI
|
|
@@ -356,13 +363,36 @@ function formatMarkdownTable(tableLines) {
|
|
|
356
363
|
}
|
|
357
364
|
colWidths.push(Math.max(mw, 3));
|
|
358
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
|
+
}
|
|
359
388
|
const out = [];
|
|
360
389
|
const topBorder = `\x1b[90m╭${colWidths.map((w) => "─".repeat(w + 2)).join("┬")}╮\x1b[0m`;
|
|
361
390
|
const midBorder = `\x1b[90m├${colWidths.map((w) => "─".repeat(w + 2)).join("┼")}┤\x1b[0m`;
|
|
362
391
|
const botBorder = `\x1b[90m╰${colWidths.map((w) => "─".repeat(w + 2)).join("┴")}╯\x1b[0m`;
|
|
363
392
|
out.push(topBorder);
|
|
364
393
|
const headCells = header.map((h, i) => {
|
|
365
|
-
const
|
|
394
|
+
const raw = `\x1b[1;37m${formatInlineMarkdown(h)}\x1b[0m`;
|
|
395
|
+
const formatted = truncateStyled(raw, colWidths[i]);
|
|
366
396
|
const pad = colWidths[i] - plainLen(formatted);
|
|
367
397
|
return ` ${formatted}${" ".repeat(Math.max(pad, 0))} `;
|
|
368
398
|
});
|
|
@@ -370,7 +400,7 @@ function formatMarkdownTable(tableLines) {
|
|
|
370
400
|
out.push(midBorder);
|
|
371
401
|
for (const r of rows) {
|
|
372
402
|
const rowCells = header.map((_, i) => {
|
|
373
|
-
const cell = r[i] ? formatInlineMarkdown(r[i]) : "";
|
|
403
|
+
const cell = r[i] ? truncateStyled(formatInlineMarkdown(r[i]), colWidths[i]) : "";
|
|
374
404
|
const pad = colWidths[i] - plainLen(cell);
|
|
375
405
|
return ` ${cell}${" ".repeat(Math.max(pad, 0))} `;
|
|
376
406
|
});
|
|
@@ -948,7 +978,7 @@ export function showCursor() {
|
|
|
948
978
|
}
|
|
949
979
|
}
|
|
950
980
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
951
|
-
const DOT_FRAMES = [".
|
|
981
|
+
const DOT_FRAMES = [".", "..", "..."];
|
|
952
982
|
export class Spinner {
|
|
953
983
|
timer = null;
|
|
954
984
|
frame = 0;
|
|
@@ -1008,12 +1038,15 @@ export class Spinner {
|
|
|
1008
1038
|
}
|
|
1009
1039
|
writeDots(initial) {
|
|
1010
1040
|
const dots = DOT_FRAMES[this.dotsFrame];
|
|
1041
|
+
// Always emit a fixed 3-cell field so packed dots (., .., ...) animate
|
|
1042
|
+
// without leaving residue when the frame shrinks (e.g. ... -> .).
|
|
1043
|
+
const padded = dots.padEnd(3, " ");
|
|
1011
1044
|
if (initial) {
|
|
1012
|
-
process.stdout.write("\r" + this.dotsText +
|
|
1045
|
+
process.stdout.write("\r" + this.dotsText + padded + "\r");
|
|
1013
1046
|
}
|
|
1014
1047
|
else {
|
|
1015
1048
|
// Jump to the dots column (1-based) and rewrite only the 3-cell field.
|
|
1016
|
-
process.stdout.write(`\x1b[${plainLen(this.dotsText) + 1}G` +
|
|
1049
|
+
process.stdout.write(`\x1b[${plainLen(this.dotsText) + 1}G` + padded + "\r");
|
|
1017
1050
|
}
|
|
1018
1051
|
}
|
|
1019
1052
|
/** Overwrite the dots line in place with `text` and advance to the next
|