@oxecli/oxe 1.0.110 → 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 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 command starts, then show a
589
- // "╰─ Running..." dots line that swaps to the real result in place.
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("\x1b[90m╰─\x1b[0m Running");
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
@@ -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
- pendingDiffOutput.push(`\x1b[90m${ind}${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
80
- `(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m`);
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 });
@@ -164,7 +177,7 @@ function displayDiff(pathName, oldContent, newContent) {
164
177
  const gap = " ".repeat(numW);
165
178
  for (const v of visible) {
166
179
  if (v.kind === "~") {
167
- const body = truncateStyled(v.body, bodyMax);
180
+ const body = truncateStyledEllipsis(v.body, bodyMax);
168
181
  pendingDiffOutput.push(`\x1b[90m${ind}${gap} ${body}\x1b[0m`);
169
182
  continue;
170
183
  }
@@ -181,7 +194,7 @@ function displayDiff(pathName, oldContent, newContent) {
181
194
  // background + white base after each token reset so both hold across
182
195
  // the whole body.
183
196
  const raw = highlightCodeLine(v.body);
184
- const body = truncateStyled(raw.replace(/\x1b\[0m/g, `\x1b[0m${bg}\x1b[97m`), bodyMax);
197
+ const body = truncateStyledEllipsis(raw.replace(/\x1b\[0m/g, `\x1b[0m${bg}\x1b[97m`), bodyMax);
185
198
  // Deleted rows: recolor bracket syntax to the same red as the line
186
199
  // number (fg) so brackets match the row's deleted color. Added rows keep
187
200
  // their green highlighter as-is.
@@ -197,7 +210,7 @@ function displayDiff(pathName, oldContent, newContent) {
197
210
  // lines (only the number gutter stays dim). highlightCodeLine resets to
198
211
  // default after each token, so re-apply the base after every reset.
199
212
  const base = v.kind === "\\" ? "\x1b[90m" : "\x1b[97m";
200
- const body = truncateStyled(raw.replace(/\x1b\[0m/g, `\x1b[0m${base}`), bodyMax);
213
+ const body = truncateStyledEllipsis(raw.replace(/\x1b\[0m/g, `\x1b[0m${base}`), bodyMax);
201
214
  pendingDiffOutput.push(`\x1b[90m${ind}${numStr} \x1b[0m${base}${body}\x1b[0m`);
202
215
  }
203
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.110",
3
+ "version": "1.0.111",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },