open-agents-ai 0.15.2 → 0.15.4

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.
Files changed (2) hide show
  1. package/dist/index.js +198 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8870,13 +8870,6 @@ ${newerSummary}` : newerSummary;
8870
8870
  acc.id = chunk.toolCallId;
8871
8871
  if (chunk.toolCallArgs) {
8872
8872
  acc.args += chunk.toolCallArgs;
8873
- this.emit({
8874
- type: "stream_token",
8875
- content: chunk.toolCallArgs,
8876
- streamKind: "tool_args",
8877
- turn,
8878
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
8879
- });
8880
8873
  }
8881
8874
  }
8882
8875
  }
@@ -9660,23 +9653,137 @@ function renderToolCallStart(toolName, args) {
9660
9653
  `);
9661
9654
  }
9662
9655
  function renderToolResult(toolName, success, output) {
9663
- const icon = success ? _emojisEnabled ? c2.green("\u2714") : c2.green("+") : _emojisEnabled ? c2.red("\u2716") : c2.red("x");
9664
- const maxLines = 8;
9656
+ const maxW = getTermWidth() - 10;
9657
+ const prefix = ` ${c2.dim("\u23BF")} `;
9658
+ switch (toolName) {
9659
+ case "file_write": {
9660
+ const summary = extractFirstLine(output, maxW);
9661
+ if (success) {
9662
+ process.stdout.write(`${prefix}${c2.dim(summary)}
9663
+ `);
9664
+ } else {
9665
+ process.stdout.write(`${prefix}${c2.red(summary)}
9666
+ `);
9667
+ }
9668
+ return;
9669
+ }
9670
+ case "file_edit": {
9671
+ const summary = extractFirstLine(output, maxW);
9672
+ if (success) {
9673
+ process.stdout.write(`${prefix}${c2.dim(summary)}
9674
+ `);
9675
+ } else {
9676
+ process.stdout.write(`${prefix}${c2.red(summary)}
9677
+ `);
9678
+ }
9679
+ return;
9680
+ }
9681
+ case "file_read": {
9682
+ if (!success) {
9683
+ process.stdout.write(`${prefix}${c2.red(extractFirstLine(output, maxW))}
9684
+ `);
9685
+ return;
9686
+ }
9687
+ renderCodePreview(output, prefix, maxW, 6);
9688
+ return;
9689
+ }
9690
+ case "shell":
9691
+ case "background_run": {
9692
+ renderShellOutput(output, success, prefix, maxW, 8);
9693
+ return;
9694
+ }
9695
+ case "grep_search": {
9696
+ renderShellOutput(output, success, prefix, maxW, 6);
9697
+ return;
9698
+ }
9699
+ case "task_complete": {
9700
+ process.stdout.write(`${prefix}${c2.green("\u2714")} ${c2.dim("Done")}
9701
+ `);
9702
+ return;
9703
+ }
9704
+ default:
9705
+ break;
9706
+ }
9665
9707
  const lines = output.split("\n").filter((l) => l.trim());
9666
9708
  if (lines.length === 0) {
9667
- process.stdout.write(` ${c2.dim("\u23BF")} ${icon} ${success ? c2.dim("Done") : c2.red("Failed")}
9709
+ const icon = success ? _emojisEnabled ? c2.green("\u2714") : c2.green("+") : _emojisEnabled ? c2.red("\u2716") : c2.red("x");
9710
+ process.stdout.write(`${prefix}${icon} ${success ? c2.dim("Done") : c2.red("Failed")}
9668
9711
  `);
9669
9712
  return;
9670
9713
  }
9714
+ const maxLines = 6;
9671
9715
  const shown = lines.slice(0, maxLines);
9672
9716
  for (const line of shown) {
9673
- const maxW = getTermWidth() - 10;
9717
+ if (isRawJsonDump(line)) {
9718
+ process.stdout.write(`${prefix}${c2.dim("(content omitted)")}
9719
+ `);
9720
+ return;
9721
+ }
9674
9722
  const trimmed = line.length > maxW ? line.slice(0, maxW - 3) + "..." : line;
9675
- process.stdout.write(` ${c2.dim("\u23BF")} ${highlightToolOutput(trimmed)}
9723
+ process.stdout.write(`${prefix}${highlightToolOutput(trimmed)}
9724
+ `);
9725
+ }
9726
+ if (lines.length > maxLines) {
9727
+ process.stdout.write(`${prefix}${c2.dim(`... ${lines.length - maxLines} more lines`)}
9728
+ `);
9729
+ }
9730
+ }
9731
+ function extractFirstLine(output, maxW) {
9732
+ const line = output.split("\n").find((l) => l.trim()) ?? output;
9733
+ return line.length > maxW ? line.slice(0, maxW - 3) + "..." : line;
9734
+ }
9735
+ function isRawJsonDump(line) {
9736
+ if (/\\u[0-9a-fA-F]{4}/.test(line) && line.length > 200)
9737
+ return true;
9738
+ if (/^\s*\{"content"\s*:/.test(line) && line.length > 100)
9739
+ return true;
9740
+ if (/\[38;5;\d+m/.test(line) && line.length > 100)
9741
+ return true;
9742
+ return false;
9743
+ }
9744
+ function renderCodePreview(output, prefix, maxW, maxLines) {
9745
+ const lines = output.split("\n");
9746
+ let start = 0;
9747
+ while (start < lines.length && !lines[start].trim())
9748
+ start++;
9749
+ const shown = lines.slice(start, start + maxLines);
9750
+ if (shown.length === 0) {
9751
+ process.stdout.write(`${prefix}${c2.dim("(empty file)")}
9752
+ `);
9753
+ return;
9754
+ }
9755
+ for (const line of shown) {
9756
+ const cropped = line.length > maxW ? line.slice(0, maxW - 3) + "..." : line;
9757
+ process.stdout.write(`${prefix}${c2.dim(cropped)}
9758
+ `);
9759
+ }
9760
+ const remaining = lines.length - start - shown.length;
9761
+ if (remaining > 0) {
9762
+ process.stdout.write(`${prefix}${c2.dim(`... ${remaining} more lines`)}
9763
+ `);
9764
+ }
9765
+ }
9766
+ function renderShellOutput(output, success, prefix, maxW, maxLines) {
9767
+ const lines = output.split("\n").filter((l) => l.trim());
9768
+ if (lines.length === 0) {
9769
+ const icon = success ? c2.green("\u2714") : c2.red("\u2716");
9770
+ process.stdout.write(`${prefix}${icon} ${success ? c2.dim("Done") : c2.red("Failed")}
9771
+ `);
9772
+ return;
9773
+ }
9774
+ const shown = lines.slice(0, maxLines);
9775
+ for (const line of shown) {
9776
+ if (isRawJsonDump(line)) {
9777
+ process.stdout.write(`${prefix}${c2.dim("(output omitted)")}
9778
+ `);
9779
+ return;
9780
+ }
9781
+ const cropped = line.length > maxW ? line.slice(0, maxW - 3) + "..." : line;
9782
+ process.stdout.write(`${prefix}${highlightToolOutput(cropped)}
9676
9783
  `);
9677
9784
  }
9678
9785
  if (lines.length > maxLines) {
9679
- process.stdout.write(` ${c2.dim("\u23BF")} ${c2.dim(`... ${lines.length - maxLines} more lines`)}
9786
+ process.stdout.write(`${prefix}${c2.dim(`... ${lines.length - maxLines} more lines`)}
9680
9787
  `);
9681
9788
  }
9682
9789
  }
@@ -11084,7 +11191,7 @@ async function handleSlashCommand(input, ctx) {
11084
11191
  return "handled";
11085
11192
  case "update":
11086
11193
  case "upgrade":
11087
- await handleUpdate(arg, ctx.repoRoot);
11194
+ await handleUpdate(arg, ctx);
11088
11195
  return "handled";
11089
11196
  case "voice": {
11090
11197
  const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
@@ -11374,7 +11481,8 @@ async function handleEndpoint(arg, ctx, local = false) {
11374
11481
  }
11375
11482
  process.stdout.write("\n");
11376
11483
  }
11377
- async function handleUpdate(subcommand, repoRoot) {
11484
+ async function handleUpdate(subcommand, ctx) {
11485
+ const repoRoot = ctx.repoRoot;
11378
11486
  if (subcommand === "auto") {
11379
11487
  const settings = { updateMode: "auto" };
11380
11488
  saveProjectSettings(repoRoot, settings);
@@ -11425,17 +11533,33 @@ async function handleUpdate(subcommand, repoRoot) {
11425
11533
  }
11426
11534
  process.stdout.write(` ${c2.yellow("\u26A0")} Update available: v${info.currentVersion} \u2192 v${c2.bold(c2.green(info.latestVersion))}
11427
11535
  `);
11428
- process.stdout.write(` ${c2.cyan("\u25CF")} Installing in background...
11536
+ process.stdout.write(` ${c2.cyan("\u25CF")} Installing update...
11429
11537
 
11430
11538
  `);
11431
- const { exec } = await import("node:child_process");
11432
- exec(`npm cache clean --force open-agents-ai 2>/dev/null; npm install -g open-agents-ai@latest --force`, { timeout: 18e4 }, (err) => {
11433
- if (err) {
11434
- renderWarning("Update install failed. Try manually: npm i -g open-agents-ai");
11435
- } else {
11436
- renderInfo(`${c2.green("\u2714")} Updated to v${info.latestVersion}. Takes effect next session.`);
11437
- }
11438
- });
11539
+ const { execSync: execSync14 } = await import("node:child_process");
11540
+ try {
11541
+ execSync14(`npm cache clean --force open-agents-ai 2>/dev/null; npm install -g open-agents-ai@latest --force`, { stdio: "pipe", timeout: 18e4 });
11542
+ } catch {
11543
+ renderWarning("Update install failed. Try manually: npm i -g open-agents-ai");
11544
+ return;
11545
+ }
11546
+ process.stdout.write(` ${c2.green("\u2714")} Installed v${info.latestVersion}. Reloading...
11547
+
11548
+ `);
11549
+ if (ctx.savePendingTaskState) {
11550
+ ctx.savePendingTaskState();
11551
+ }
11552
+ const { execPath, argv } = process;
11553
+ try {
11554
+ const { execFileSync } = await import("node:child_process");
11555
+ process.env.__OA_RESUMED = "1";
11556
+ execFileSync(execPath, argv.slice(1), {
11557
+ stdio: "inherit",
11558
+ env: { ...process.env, __OA_RESUMED: "1" }
11559
+ });
11560
+ } catch {
11561
+ renderWarning("Reload failed. Restart oa manually to use the new version.");
11562
+ }
11439
11563
  }
11440
11564
  async function switchModel(query, ctx, local = false) {
11441
11565
  try {
@@ -13253,6 +13377,12 @@ var init_stream_renderer = __esm({
13253
13377
  startTime = 0;
13254
13378
  /** Track if we're mid-tool-arg display */
13255
13379
  inToolArgs = false;
13380
+ /**
13381
+ * Track accumulated content size for JSON blob detection.
13382
+ * When a non-newline JSON blob exceeds this threshold, suppress rendering.
13383
+ */
13384
+ jsonBlobSize = 0;
13385
+ jsonBlobSuppressed = false;
13256
13386
  /** Called when a new model response starts streaming */
13257
13387
  onStreamStart() {
13258
13388
  this.lineBuffer = "";
@@ -13261,6 +13391,8 @@ var init_stream_renderer = __esm({
13261
13391
  this.codeLang = "";
13262
13392
  this.lineStarted = false;
13263
13393
  this.inToolArgs = false;
13394
+ this.jsonBlobSize = 0;
13395
+ this.jsonBlobSuppressed = false;
13264
13396
  this.enabled = true;
13265
13397
  this.tokenCount = 0;
13266
13398
  this.startTime = Date.now();
@@ -13371,8 +13503,26 @@ var init_stream_renderer = __esm({
13371
13503
  const raw = text.replace(/\n$/, "");
13372
13504
  if (!raw)
13373
13505
  return;
13374
- const prefix = this.lineStarted ? "" : " \u23BF ";
13375
13506
  const hasNewline = text.endsWith("\n");
13507
+ if (kind === "tool_args" || kind === "content" && this.looksLikeJsonBlob(raw)) {
13508
+ this.jsonBlobSize += raw.length;
13509
+ if (this.jsonBlobSize > 300) {
13510
+ if (!this.jsonBlobSuppressed) {
13511
+ this.jsonBlobSuppressed = true;
13512
+ }
13513
+ if (hasNewline) {
13514
+ this.jsonBlobSize = 0;
13515
+ this.jsonBlobSuppressed = false;
13516
+ }
13517
+ return;
13518
+ }
13519
+ } else {
13520
+ if (this.jsonBlobSuppressed || this.jsonBlobSize > 0) {
13521
+ this.jsonBlobSize = 0;
13522
+ this.jsonBlobSuppressed = false;
13523
+ }
13524
+ }
13525
+ const prefix = this.lineStarted ? "" : " \u23BF ";
13376
13526
  let rendered;
13377
13527
  switch (kind) {
13378
13528
  case "thinking":
@@ -13381,19 +13531,21 @@ var init_stream_renderer = __esm({
13381
13531
  case "tool_args":
13382
13532
  rendered = this.highlightJson(raw, true);
13383
13533
  break;
13384
- case "content":
13534
+ case "content": {
13535
+ const maxW = (process.stdout.columns ?? 80) - 6;
13385
13536
  if (this.inCodeBlock) {
13537
+ const cropped = raw.length > maxW ? raw.slice(0, maxW - 3) + "..." : raw;
13386
13538
  if (this.codeLang === "diff" || this.codeLang === "patch") {
13387
- rendered = this.highlightDiff(raw);
13539
+ rendered = this.highlightDiff(cropped);
13388
13540
  } else if (this.codeLang === "bash" || this.codeLang === "sh" || this.codeLang === "shell" || this.codeLang === "zsh") {
13389
- rendered = this.highlightShell(raw);
13541
+ rendered = this.highlightShell(cropped);
13390
13542
  } else {
13391
- rendered = this.highlightCode(raw);
13543
+ rendered = this.highlightCode(cropped);
13392
13544
  }
13393
13545
  } else if (this.looksLikeJson(raw)) {
13394
- rendered = this.highlightJson(raw, false);
13546
+ const cropped = raw.length > maxW ? raw.slice(0, maxW - 3) + "..." : raw;
13547
+ rendered = this.highlightJson(cropped, false);
13395
13548
  } else {
13396
- const maxW = (process.stdout.columns ?? 80) - 6;
13397
13549
  if (raw.length > maxW) {
13398
13550
  const wrapped = this.wordWrap(raw, maxW);
13399
13551
  for (let i = 0; i < wrapped.length; i++) {
@@ -13407,6 +13559,7 @@ var init_stream_renderer = __esm({
13407
13559
  rendered = this.highlightMarkdown(raw);
13408
13560
  }
13409
13561
  break;
13562
+ }
13410
13563
  }
13411
13564
  this.writeRaw(dimText(prefix) + rendered + (hasNewline ? "\n" : ""));
13412
13565
  this.lineStarted = !hasNewline;
@@ -13446,6 +13599,20 @@ var init_stream_renderer = __esm({
13446
13599
  const trimmed = text.trimStart();
13447
13600
  return trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("}") || trimmed.startsWith("]") || /^\s*"[^"]+"\s*:/.test(trimmed);
13448
13601
  }
13602
+ /**
13603
+ * Detect a JSON blob that likely contains serialized file content.
13604
+ * These are tool call args that leaked into the content stream —
13605
+ * they contain escaped unicode (\u003e), literal \n, or "content": keys.
13606
+ */
13607
+ looksLikeJsonBlob(text) {
13608
+ if (/\\u[0-9a-fA-F]{4}/.test(text))
13609
+ return true;
13610
+ if (/^\s*\{?"content"\s*:/.test(text))
13611
+ return true;
13612
+ if (text.includes("\\n") && this.looksLikeJson(text) && text.length > 100)
13613
+ return true;
13614
+ return false;
13615
+ }
13449
13616
  /**
13450
13617
  * Highlight a JSON line with pastel colors.
13451
13618
  * @param dim If true, apply dimmer colors (for tool args)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",