open-agents-ai 0.89.0 → 0.91.0

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 +84 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -321,13 +321,20 @@ async function checkForUpdate(currentVersion, forceCheck = false) {
321
321
  };
322
322
  }
323
323
  function performSilentUpdate() {
324
+ const installCmd = `npm install -g ${PACKAGE_NAME}@latest --prefer-online`;
324
325
  try {
325
- execSync(`npm install -g ${PACKAGE_NAME}@latest --prefer-online`, {
326
- stdio: "pipe",
327
- timeout: 12e4
328
- });
326
+ execSync(installCmd, { stdio: "pipe", timeout: 12e4 });
329
327
  return true;
330
- } catch {
328
+ } catch (err) {
329
+ const msg = err instanceof Error ? err.message : String(err);
330
+ if (/EACCES|permission denied/i.test(msg)) {
331
+ try {
332
+ execSync(`sudo -n ${installCmd}`, { stdio: "pipe", timeout: 12e4 });
333
+ return true;
334
+ } catch {
335
+ return false;
336
+ }
337
+ }
331
338
  return false;
332
339
  }
333
340
  }
@@ -16487,6 +16494,8 @@ TASK: ${task}` : task }
16487
16494
  let completed = false;
16488
16495
  let summary = "";
16489
16496
  let bruteForceCycle = 0;
16497
+ let consecutiveTextOnly = 0;
16498
+ const MAX_CONSECUTIVE_TEXT_ONLY = 3;
16490
16499
  for (let turn = 0; turn < this.options.maxTurns; turn++) {
16491
16500
  if (this._paused) {
16492
16501
  const shouldContinue = await this.waitIfPaused();
@@ -16600,6 +16609,7 @@ Integrate this guidance into your current approach. Continue working on the task
16600
16609
  break;
16601
16610
  const msg = choice.message;
16602
16611
  if (msg.toolCalls && msg.toolCalls.length > 0) {
16612
+ consecutiveTextOnly = 0;
16603
16613
  messages.push({
16604
16614
  role: "assistant",
16605
16615
  content: msg.content || null,
@@ -16775,6 +16785,7 @@ Do NOT continue walking up parent directories.`);
16775
16785
  } else {
16776
16786
  const content = msg.content || "";
16777
16787
  messages.push({ role: "assistant", content });
16788
+ consecutiveTextOnly++;
16778
16789
  this.emit({
16779
16790
  type: "model_response",
16780
16791
  content: content.slice(0, 200),
@@ -16786,15 +16797,34 @@ Do NOT continue walking up parent directories.`);
16786
16797
  summary = content;
16787
16798
  break;
16788
16799
  }
16800
+ if (consecutiveTextOnly >= MAX_CONSECUTIVE_TEXT_ONLY) {
16801
+ this.emit({
16802
+ type: "status",
16803
+ content: `Model returned ${consecutiveTextOnly} consecutive text-only responses without using tools \u2014 breaking turn loop to avoid runaway`,
16804
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16805
+ });
16806
+ break;
16807
+ }
16789
16808
  messages.push({
16790
16809
  role: "user",
16791
16810
  content: "Continue working. Use tools to read files, make changes, and run validation. Call task_complete when done."
16792
16811
  });
16793
16812
  }
16794
16813
  }
16814
+ let prevCycleToolCalls = toolCallCount;
16795
16815
  while (!completed && !this.aborted && this.options.bruteForce && bruteForceCycle < this.options.bruteForceMaxCycles) {
16796
16816
  bruteForceCycle++;
16797
16817
  const totalTurns = messages.filter((m) => m.role === "assistant").length;
16818
+ if (bruteForceCycle > 1 && toolCallCount === prevCycleToolCalls) {
16819
+ this.emit({
16820
+ type: "status",
16821
+ content: `Stopping re-engagement \u2014 no tool call progress in cycle ${bruteForceCycle - 1}. Model may not support tool calling for this task.`,
16822
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
16823
+ });
16824
+ break;
16825
+ }
16826
+ prevCycleToolCalls = toolCallCount;
16827
+ consecutiveTextOnly = 0;
16798
16828
  this.emit({
16799
16829
  type: "status",
16800
16830
  content: `Re-engaging \u2014 cycle ${bruteForceCycle} (${totalTurns} turns, ${toolCallCount} tool calls so far)`,
@@ -16918,6 +16948,7 @@ Integrate this guidance into your current approach. Continue working on the task
16918
16948
  break;
16919
16949
  const msg = choice.message;
16920
16950
  if (msg.toolCalls && msg.toolCalls.length > 0) {
16951
+ consecutiveTextOnly = 0;
16921
16952
  messages.push({ role: "assistant", content: msg.content || null, tool_calls: msg.toolCalls.map((tc) => ({ id: tc.id, type: "function", function: { name: tc.name, arguments: JSON.stringify(tc.arguments) } })) });
16922
16953
  for (const tc of msg.toolCalls) {
16923
16954
  if (this.aborted)
@@ -16978,12 +17009,21 @@ Do NOT continue walking up parent directories.`);
16978
17009
  } else {
16979
17010
  const content = msg.content || "";
16980
17011
  messages.push({ role: "assistant", content });
17012
+ consecutiveTextOnly++;
16981
17013
  this.emit({ type: "model_response", content: content.slice(0, 200), turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
16982
17014
  if (/task.?complete|all tests pass/i.test(content)) {
16983
17015
  completed = true;
16984
17016
  summary = content;
16985
17017
  break;
16986
17018
  }
17019
+ if (consecutiveTextOnly >= MAX_CONSECUTIVE_TEXT_ONLY) {
17020
+ this.emit({
17021
+ type: "status",
17022
+ content: `Model returned ${consecutiveTextOnly} consecutive text-only responses \u2014 breaking cycle ${bruteForceCycle}`,
17023
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17024
+ });
17025
+ break;
17026
+ }
16987
17027
  messages.push({ role: "user", content: "Continue working. Use tools to read files, make changes, and run validation. Call task_complete when done." });
16988
17028
  }
16989
17029
  }
@@ -29992,22 +30032,55 @@ async function handleUpdate(subcommand, ctx) {
29992
30032
  return;
29993
30033
  }
29994
30034
  checkSpinner.stop(`Update available: v${info.currentVersion} \u2192 v${c2.bold(c2.green(info.latestVersion))}`);
30035
+ const { exec, execSync: es2 } = await import("node:child_process");
30036
+ let needsSudo = false;
30037
+ try {
30038
+ const prefix = es2("npm prefix -g", { encoding: "utf8", timeout: 5e3 }).trim();
30039
+ const { accessSync, constants } = await import("node:fs");
30040
+ try {
30041
+ accessSync(prefix, constants.W_OK);
30042
+ } catch {
30043
+ needsSudo = true;
30044
+ }
30045
+ } catch {
30046
+ }
30047
+ if (needsSudo) {
30048
+ renderInfo("Global npm directory requires elevated permissions.");
30049
+ renderInfo("You'll be asked for your password once \u2014 it covers all update steps.");
30050
+ process.stdout.write("\n");
30051
+ try {
30052
+ es2("sudo -v", { stdio: "inherit", timeout: 6e4 });
30053
+ } catch {
30054
+ renderWarning("Could not acquire sudo credentials. Try: sudo npm i -g open-agents-ai");
30055
+ return;
30056
+ }
30057
+ }
30058
+ const sudoPrefix = needsSudo ? "sudo " : "";
29995
30059
  const installSpinner = startInlineSpinner("Installing update");
29996
- const { exec } = await import("node:child_process");
29997
- const installOk = await new Promise((resolve31) => {
29998
- const child = exec(`npm cache clean --force open-agents-ai 2>/dev/null; npm install -g open-agents-ai@latest --force`, { timeout: 18e4 }, (err) => resolve31(!err));
30060
+ const installCmd = `${sudoPrefix}npm install -g open-agents-ai@latest --prefer-online`;
30061
+ let installOk = false;
30062
+ let installError = "";
30063
+ installOk = await new Promise((resolve31) => {
30064
+ const child = exec(installCmd, { timeout: 18e4 }, (err, _stdout, stderr) => {
30065
+ if (err)
30066
+ installError = (stderr || err.message || "").trim();
30067
+ resolve31(!err);
30068
+ });
29999
30069
  child.stdout?.resume();
30000
- child.stderr?.resume();
30001
30070
  });
30002
30071
  if (!installOk) {
30003
30072
  installSpinner.stop("Update install failed.");
30004
- renderWarning("Try manually: npm i -g open-agents-ai");
30073
+ const errPreview = installError.split("\n").filter((l) => l.trim()).slice(-3).join("\n ");
30074
+ if (errPreview)
30075
+ renderWarning(`Error:
30076
+ ${errPreview}`);
30077
+ renderWarning(`Try manually: ${sudoPrefix}npm i -g open-agents-ai`);
30005
30078
  return;
30006
30079
  }
30007
30080
  installSpinner.stop(`Update installed (v${info.latestVersion}).`);
30008
30081
  const rebuildSpinner = startInlineSpinner("Rebuilding native modules");
30009
30082
  const rebuildOk = await new Promise((resolve31) => {
30010
- const child = exec("npm rebuild -g open-agents-ai 2>/dev/null || true", { timeout: 12e4 }, () => resolve31(true));
30083
+ const child = exec(`${sudoPrefix}npm rebuild -g open-agents-ai 2>/dev/null || true`, { timeout: 12e4 }, () => resolve31(true));
30011
30084
  child.stdout?.resume();
30012
30085
  child.stderr?.resume();
30013
30086
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.89.0",
3
+ "version": "0.91.0",
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",