@oxecli/oxe 1.0.13 → 1.0.15

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
@@ -1,7 +1,7 @@
1
1
  import OpenAI from "openai";
2
2
  import { max_output_tokens, max_empty_retries, max_agent_steps, max_context_tokens, context_overhead_margin, compact_keep_recent_turns, max_summary_source_chars, max_read_file_stored_chars, } from "./config.js";
3
3
  import { SYSTEM_PROMPT } from "./system.js";
4
- import { buildTools, truncateToolOutput, TOOL_IMPLEMENTATIONS } from "./tools.js";
4
+ import { buildTools, truncateToolOutput, toolReadFile, toolWriteFile, toolEditFile, toolBash, toolGlob, toolGrep, toolLoadSkill, } from "./tools.js";
5
5
  import { mutedMarkdown, aiMarkdown, tickDuration, displayRows, safeCommitPoint, printAiChunk, formatToolAction, renderPanel, Spinner, hideCursor, } from "./ui.js";
6
6
  import { reportUsage } from "./api.js";
7
7
  import { stripOrphanCalls, persistCompactionSummary, toolOutputFailed, } from "./sessions.js";
@@ -83,10 +83,37 @@ export class InferenceEngine {
83
83
  args = {};
84
84
  }
85
85
  try {
86
- const impl = TOOL_IMPLEMENTATIONS[name];
87
- if (!impl)
88
- return `Error: unknown tool '${name}'`;
89
- const result = await impl(...Object.values(args));
86
+ // Dispatch by explicit named parameters. Do NOT use Object.values(args)
87
+ // (order-dependent): JSON object key order is not guaranteed, and when a
88
+ // model emits e.g. {content, path} for write_file the content would be
89
+ // mistaken for the path (turning JSX "/" into "\" on Windows). Binding
90
+ // each argument by name makes the tools robust to any key ordering.
91
+ let result;
92
+ switch (name) {
93
+ case "read_file":
94
+ result = toolReadFile(args.path, args.start_line, args.end_line);
95
+ break;
96
+ case "write_file":
97
+ result = toolWriteFile(args.path, args.content);
98
+ break;
99
+ case "edit_file":
100
+ result = toolEditFile(args.path, args.old_string, args.new_string, args.replace_all);
101
+ break;
102
+ case "bash":
103
+ result = await toolBash(args.command, args.timeout, args.cwd);
104
+ break;
105
+ case "glob":
106
+ result = toolGlob(args.pattern, args.path, args.limit);
107
+ break;
108
+ case "grep":
109
+ result = toolGrep(args.pattern, args.path, args.glob, args.limit);
110
+ break;
111
+ case "load_skill":
112
+ result = toolLoadSkill(args.skill_name);
113
+ break;
114
+ default:
115
+ return `Error: unknown tool '${name}'`;
116
+ }
90
117
  return typeof result === "string" ? result : String(result);
91
118
  }
92
119
  catch (err) {
package/dist/tools.js CHANGED
@@ -4,6 +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
+ export { toolLoadSkill };
7
8
  // ---------------------------------------------------------------------------
8
9
  // Natural sort + diff helpers
9
10
  // ---------------------------------------------------------------------------
package/dist/ui.js CHANGED
@@ -670,12 +670,37 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
670
670
  return;
671
671
  }
672
672
  };
673
- const insert = (text) => {
673
+ const shiftSpansAfterInsert = (pos, delta) => {
674
+ const newSpans = [];
675
+ for (const [s, e] of pasteSpans) {
676
+ if (pos <= s)
677
+ newSpans.push([s + delta, e + delta]);
678
+ else if (pos >= e)
679
+ newSpans.push([s, e]);
680
+ else {
681
+ newSpans.push([s, pos]);
682
+ newSpans.push([pos + delta, e + delta]);
683
+ }
684
+ }
685
+ return newSpans;
686
+ };
687
+ const insert = (text, isPaste = false) => {
674
688
  if (histIdx !== hist.length) {
675
689
  draft = { buffer, cursor, spans: pasteSpans.slice() };
676
690
  histIdx = hist.length;
677
691
  }
692
+ const pasteStart = cursor;
678
693
  buffer = buffer.slice(0, cursor) + text + buffer.slice(cursor);
694
+ // Keep existing paste spans valid across the insertion point.
695
+ pasteSpans = shiftSpansAfterInsert(cursor, text.length);
696
+ // A real paste records a span so the prompt box can collapse it (mirrors
697
+ // Python's handle_paste). splitBlocks only collapses segments inside a
698
+ // span that meet the threshold, so small pastes stay expanded but remain
699
+ // tagged for cursor/backspace handling.
700
+ if (isPaste) {
701
+ pasteSpans.push([pasteStart, pasteStart + text.length]);
702
+ pasteSpans.sort((a, b) => a[0] - b[0]);
703
+ }
679
704
  cursor += text.length;
680
705
  };
681
706
  const backspace = () => {
@@ -689,7 +714,25 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
689
714
  const before = Array.from(buffer.slice(0, cursor));
690
715
  before.pop();
691
716
  buffer = before.join("") + buffer.slice(cursor);
717
+ const deletedAt = cursor - 1;
692
718
  cursor -= 1;
719
+ // Adjust spans: shrink any span covering the deleted char, shift spans
720
+ // that start after it left by one.
721
+ const adjusted = [];
722
+ for (const [s, e] of pasteSpans) {
723
+ if (deletedAt >= s && deletedAt < e) {
724
+ const ne = e - 1;
725
+ if (s < ne)
726
+ adjusted.push([s, ne]);
727
+ }
728
+ else if (deletedAt < s) {
729
+ adjusted.push([s - 1, e - 1]);
730
+ }
731
+ else {
732
+ adjusted.push([s, e]);
733
+ }
734
+ }
735
+ pasteSpans = adjusted.filter(([s, e]) => s < e);
693
736
  };
694
737
  const moveLeft = () => {
695
738
  if (cursor > 0)
@@ -782,8 +825,9 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
782
825
  return;
783
826
  }
784
827
  if (str) {
785
- // paste / multi-char sequence
786
- insert(str);
828
+ // Multi-char sequence = a paste (single-char typed keys arrive one per
829
+ // event). Tag it so the prompt box collapses it when it meets the rules.
830
+ insert(str, str.length > 1);
787
831
  repaint();
788
832
  }
789
833
  };
@@ -795,6 +839,11 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
795
839
  // User display + message text
796
840
  // ---------------------------------------------------------------------------
797
841
  export function userDisplayText(payload, pasteSpans) {
842
+ // Mirror the prompt field exactly: collapse ONLY segments that fall inside a
843
+ // paste span and exceed the collapse threshold (via splitBlocks). Content that
844
+ // the user typed (no paste span) is never collapsed in the prompt, so it must
845
+ // not be collapsed here either — keeping the echoed message consistent with
846
+ // what the prompt box displayed.
798
847
  if (pasteSpans && pasteSpans.length) {
799
848
  const segs = splitBlocks(payload, pasteSpans);
800
849
  let out = "";
@@ -806,9 +855,6 @@ export function userDisplayText(payload, pasteSpans) {
806
855
  }
807
856
  return out;
808
857
  }
809
- if (shouldCollapsePaste(payload)) {
810
- return `\x1b[1m\x1b[36m[Pasted text, ${payload.split("\n").length} lines]\x1b[0m`;
811
- }
812
858
  return payload;
813
859
  }
814
860
  export function collapseLabelText(text, spans) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },