open-agents-ai 0.15.9 → 0.16.1

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 +158 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1103,10 +1103,11 @@ var init_tool_executor = __esm({
1103
1103
 
1104
1104
  // packages/execution/dist/tools/shell.js
1105
1105
  import { spawn } from "node:child_process";
1106
- var ShellTool;
1106
+ var PERMISSION_ERROR_RE, ShellTool;
1107
1107
  var init_shell = __esm({
1108
1108
  "packages/execution/dist/tools/shell.js"() {
1109
1109
  "use strict";
1110
+ PERMISSION_ERROR_RE = /Permission denied|Operation not permitted|EACCES|EPERM|PermissionError|sudo:|not permitted|password is required|authentication failure/i;
1110
1111
  ShellTool = class {
1111
1112
  name = "shell";
1112
1113
  description = "Execute a shell command in the project working directory. Commands run non-interactively (CI=true). For commands that need input, use the stdin parameter or pass non-interactive flags (--yes, --no-input).";
@@ -1130,14 +1131,55 @@ var init_shell = __esm({
1130
1131
  };
1131
1132
  workingDir;
1132
1133
  defaultTimeout;
1134
+ _sudoPassword = null;
1133
1135
  constructor(workingDir, defaultTimeout = 3e4) {
1134
1136
  this.workingDir = workingDir;
1135
1137
  this.defaultTimeout = defaultTimeout;
1136
1138
  }
1139
+ /** Set a cached sudo password for the session. The shell tool will
1140
+ * automatically retry permission-denied commands with `sudo -S`. */
1141
+ setSudoPassword(password) {
1142
+ this._sudoPassword = password;
1143
+ }
1144
+ /** Check whether a sudo password is currently cached. */
1145
+ hasSudoPassword() {
1146
+ return this._sudoPassword !== null;
1147
+ }
1137
1148
  async execute(args) {
1138
1149
  const command = args["command"];
1139
1150
  const timeout = args["timeout"] ?? this.defaultTimeout;
1140
1151
  const stdinInput = args["stdin"];
1152
+ const result = await this.runCommand(command, timeout, stdinInput);
1153
+ if (!result.success && this._sudoPassword && this.isPermissionError(result)) {
1154
+ const sudoCmd = command.trimStart().startsWith("sudo ") ? command : `sudo -S ${command}`;
1155
+ const sudoResult = await this.runCommand(sudoCmd, timeout, `${this._sudoPassword}
1156
+ ${stdinInput ?? ""}`);
1157
+ if (sudoResult.success) {
1158
+ return sudoResult;
1159
+ }
1160
+ if (this.isPermissionError(sudoResult) || /incorrect password|Sorry, try again/i.test((sudoResult.error ?? "") + sudoResult.output)) {
1161
+ this._sudoPassword = null;
1162
+ return {
1163
+ ...sudoResult,
1164
+ error: (sudoResult.error ?? "") + "\n\n[SUDO_PASSWORD_INCORRECT] The cached sudo password was rejected. Ask the user for the correct password."
1165
+ };
1166
+ }
1167
+ return sudoResult;
1168
+ }
1169
+ if (!result.success && this.isPermissionError(result) && !this._sudoPassword) {
1170
+ return {
1171
+ ...result,
1172
+ error: (result.error ?? "") + "\n\n[PERMISSION_ERROR] This command requires elevated privileges. The user needs to provide their sudo password. Wait for the user to enter their password \u2014 the system is prompting them now."
1173
+ };
1174
+ }
1175
+ return result;
1176
+ }
1177
+ /** Detect if a tool result is a permission / privilege error */
1178
+ isPermissionError(result) {
1179
+ const combined = (result.error ?? "") + result.output;
1180
+ return PERMISSION_ERROR_RE.test(combined);
1181
+ }
1182
+ runCommand(command, timeout, stdinInput) {
1141
1183
  const start = performance.now();
1142
1184
  return new Promise((resolve16) => {
1143
1185
  const child = spawn("bash", ["-c", command], {
@@ -8014,19 +8056,22 @@ If you notice you're performing the SAME multi-step sequence for the 3rd time or
8014
8056
  handlers = [];
8015
8057
  pendingUserMessages = [];
8016
8058
  aborted = false;
8059
+ _sudoPassword = null;
8060
+ _sudoResolve = null;
8017
8061
  constructor(backend, options) {
8018
8062
  this.backend = backend;
8019
8063
  this.options = {
8020
- maxTurns: options?.maxTurns ?? 30,
8064
+ maxTurns: options?.maxTurns ?? 60,
8021
8065
  maxTokens: options?.maxTokens ?? 16384,
8022
8066
  temperature: options?.temperature ?? 0,
8023
8067
  requestTimeoutMs: options?.requestTimeoutMs ?? 3e5,
8024
- taskTimeoutMs: options?.taskTimeoutMs ?? 12e5,
8068
+ taskTimeoutMs: options?.taskTimeoutMs ?? 36e5,
8069
+ // 60 min
8025
8070
  compactionThreshold: options?.compactionThreshold ?? 4e4,
8026
8071
  dynamicContext: options?.dynamicContext ?? "",
8027
8072
  streamEnabled: options?.streamEnabled ?? false,
8028
- bruteForce: options?.bruteForce ?? false,
8029
- bruteForceMaxCycles: options?.bruteForceMaxCycles ?? 3
8073
+ bruteForce: options?.bruteForce ?? true,
8074
+ bruteForceMaxCycles: options?.bruteForceMaxCycles ?? 100
8030
8075
  };
8031
8076
  }
8032
8077
  /** Register a tool for the agent to use */
@@ -8264,6 +8309,21 @@ Integrate this guidance into your current approach. Continue working on the task
8264
8309
  result = { success: false, output: "", error: err instanceof Error ? err.message : String(err) };
8265
8310
  }
8266
8311
  }
8312
+ if (!result.success && tc.name === "shell" && /\[PERMISSION_ERROR\]/.test(result.error ?? "")) {
8313
+ this.emit({
8314
+ type: "sudo_request",
8315
+ content: `Command requires elevated privileges: ${tc.arguments.command?.slice(0, 100) ?? ""}`,
8316
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8317
+ });
8318
+ const pw = await this.waitForSudoPassword(12e4);
8319
+ if (pw) {
8320
+ try {
8321
+ result = await tool.execute(tc.arguments);
8322
+ } catch (err) {
8323
+ result = { success: false, output: "", error: err instanceof Error ? err.message : String(err) };
8324
+ }
8325
+ }
8326
+ }
8267
8327
  const maxLen = 8e3;
8268
8328
  const output = result.success ? result.output.length > maxLen ? result.output.slice(0, maxLen) + `
8269
8329
  ...(truncated)` : result.output : `Error: ${result.error || "unknown error"}
@@ -8337,27 +8397,28 @@ ${result.output}`;
8337
8397
  });
8338
8398
  }
8339
8399
  }
8340
- if (!completed && !this.aborted && this.options.bruteForce && bruteForceCycle < this.options.bruteForceMaxCycles && Date.now() < hardDeadline) {
8400
+ while (!completed && !this.aborted && this.options.bruteForce && bruteForceCycle < this.options.bruteForceMaxCycles && Date.now() < hardDeadline) {
8341
8401
  bruteForceCycle++;
8342
8402
  const totalTurns = messages.filter((m) => m.role === "assistant").length;
8343
8403
  this.emit({
8344
8404
  type: "compaction",
8345
- content: `Brute-force cycle ${bruteForceCycle}/${this.options.bruteForceMaxCycles} \u2014 re-engaging agent`,
8405
+ content: `Re-engaging \u2014 cycle ${bruteForceCycle} (${totalTurns} turns, ${toolCallCount} tool calls so far)`,
8346
8406
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
8347
8407
  });
8348
8408
  messages.push({
8349
8409
  role: "user",
8350
- content: `[BRUTE-FORCE RE-ENGAGEMENT \u2014 Cycle ${bruteForceCycle}/${this.options.bruteForceMaxCycles}]
8410
+ content: `[CONTINUATION \u2014 Cycle ${bruteForceCycle}]
8351
8411
 
8352
- You have used ${totalTurns} turns and ${toolCallCount} tool calls without completing the task. DO NOT give up. Reassess your approach:
8412
+ You have used ${totalTurns} turns and ${toolCallCount} tool calls so far. The task is NOT yet complete. DO NOT give up \u2014 re-evaluate and continue:
8353
8413
 
8354
- 1. DIAGNOSE: What exactly is blocking progress? Read error messages carefully.
8355
- 2. PIVOT: If the current approach isn't working, try a completely different strategy.
8356
- 3. INVESTIGATE: Use grep_search, find_files, and web_search to find solutions.
8357
- 4. SIMPLIFY: Break the problem into smaller steps. Fix one thing at a time.
8358
- 5. VERIFY: Run tests/build after EVERY change \u2014 don't batch multiple changes.
8414
+ 1. ASSESS: What have you accomplished so far? What specific part remains?
8415
+ 2. DIAGNOSE: If stuck, what exactly is blocking? Read error output carefully.
8416
+ 3. PIVOT: If the current approach failed, try a different strategy.
8417
+ 4. INVESTIGATE: Use grep_search, find_files, web_search to find answers.
8418
+ 5. SIMPLIFY: Break remaining work into the smallest possible steps.
8419
+ 6. VERIFY: Run tests/build after EVERY change.
8359
8420
 
8360
- You have ${this.options.maxTurns} more turns. Be creative and investigative. If a file is confusing, re-read it. If a test fails, read the FULL error. Call task_complete when done.`
8421
+ You have ${this.options.maxTurns} more turns. Continue making progress. Call task_complete when the task is fully done.`
8361
8422
  });
8362
8423
  const compacted = this.compactMessages(messages);
8363
8424
  if (compacted !== messages) {
@@ -8463,6 +8524,17 @@ Integrate this guidance into your current approach. Continue working on the task
8463
8524
  result = { success: false, output: "", error: err instanceof Error ? err.message : String(err) };
8464
8525
  }
8465
8526
  }
8527
+ if (!result.success && tc.name === "shell" && /\[PERMISSION_ERROR\]/.test(result.error ?? "")) {
8528
+ this.emit({ type: "sudo_request", content: `Command requires elevated privileges: ${tc.arguments.command?.slice(0, 100) ?? ""}`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
8529
+ const pw = await this.waitForSudoPassword(12e4);
8530
+ if (pw && tool) {
8531
+ try {
8532
+ result = await tool.execute(tc.arguments);
8533
+ } catch (err) {
8534
+ result = { success: false, output: "", error: err instanceof Error ? err.message : String(err) };
8535
+ }
8536
+ }
8537
+ }
8466
8538
  const maxLen = 8e3;
8467
8539
  const output = result.success ? result.output.length > maxLen ? result.output.slice(0, maxLen) + `
8468
8540
  ...(truncated)` : result.output : `Error: ${result.error || "unknown error"}
@@ -8501,6 +8573,37 @@ ${result.output}`;
8501
8573
  return { completed, turns: messages.filter((m) => m.role === "assistant").length, toolCalls: toolCallCount, totalTokens, promptTokens, completionTokens, estimatedTokens, summary, durationMs };
8502
8574
  }
8503
8575
  // -------------------------------------------------------------------------
8576
+ // Sudo / privilege escalation support
8577
+ // -------------------------------------------------------------------------
8578
+ /** Provide the sudo password — resolves any pending sudo_request wait. */
8579
+ setSudoPassword(password) {
8580
+ this._sudoPassword = password;
8581
+ for (const tool of this.tools.values()) {
8582
+ if (typeof tool.setSudoPassword === "function") {
8583
+ tool.setSudoPassword(password);
8584
+ }
8585
+ }
8586
+ if (this._sudoResolve) {
8587
+ this._sudoResolve(password);
8588
+ this._sudoResolve = null;
8589
+ }
8590
+ }
8591
+ /** Wait for the user to provide a sudo password (with timeout). */
8592
+ waitForSudoPassword(timeoutMs = 12e4) {
8593
+ if (this._sudoPassword)
8594
+ return Promise.resolve(this._sudoPassword);
8595
+ return new Promise((resolve16) => {
8596
+ const timer = setTimeout(() => {
8597
+ this._sudoResolve = null;
8598
+ resolve16(null);
8599
+ }, timeoutMs);
8600
+ this._sudoResolve = (pw) => {
8601
+ clearTimeout(timer);
8602
+ resolve16(pw);
8603
+ };
8604
+ });
8605
+ }
8606
+ // -------------------------------------------------------------------------
8504
8607
  // Image / multimodal support
8505
8608
  // -------------------------------------------------------------------------
8506
8609
  /** Inject an image into the running conversation (for drag-drop / paste) */
@@ -15200,20 +15303,23 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
15200
15303
  }
15201
15304
  };
15202
15305
  }
15203
- function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar) {
15306
+ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback) {
15204
15307
  const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
15205
15308
  const dynamicContext = formatContextForPrompt(projectCtx);
15206
15309
  const backend = new OllamaAgenticBackend(config.backendUrl.replace(/\/$/, ""), config.model);
15207
15310
  const runner = new AgenticRunner(backend, {
15208
- maxTurns: 30,
15311
+ maxTurns: 60,
15209
15312
  maxTokens: 16384,
15210
15313
  temperature: 0,
15211
15314
  requestTimeoutMs: config.timeoutMs,
15212
- taskTimeoutMs: config.timeoutMs * 4,
15315
+ taskTimeoutMs: 36e5,
15316
+ // 60 minutes — never give up prematurely
15213
15317
  compactionThreshold: 4e4,
15214
15318
  dynamicContext,
15215
15319
  streamEnabled: stream?.enabled ?? false,
15216
- bruteForce: bruteForce ?? false
15320
+ bruteForce: bruteForce ?? true,
15321
+ bruteForceMaxCycles: 100
15322
+ // effectively unlimited — hard timeout is the real bound
15217
15323
  });
15218
15324
  runner.registerTools(buildTools(repoRoot, config));
15219
15325
  const filesTouched = /* @__PURE__ */ new Set();
@@ -15306,6 +15412,12 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
15306
15412
  statusBar.updateMetrics(event.tokenUsage);
15307
15413
  }
15308
15414
  break;
15415
+ case "sudo_request":
15416
+ contentWrite(() => renderWarning(`Sudo required: ${event.content ?? "elevated privileges needed"}`));
15417
+ contentWrite(() => renderInfo("Enter your password at the prompt below to continue."));
15418
+ if (sudoCallback)
15419
+ sudoCallback(event.content ?? "");
15420
+ break;
15309
15421
  case "complete":
15310
15422
  break;
15311
15423
  }
@@ -15413,7 +15525,7 @@ async function startInteractive(config, repoPath) {
15413
15525
  if (savedSettings.dbPath)
15414
15526
  config = { ...config, dbPath: savedSettings.dbPath };
15415
15527
  let streamEnabled = savedSettings.stream ?? false;
15416
- let bruteForceEnabled = savedSettings.bruteforce ?? false;
15528
+ let bruteForceEnabled = savedSettings.bruteforce ?? true;
15417
15529
  if (savedSettings.emojis !== void 0)
15418
15530
  setEmojisEnabled(savedSettings.emojis);
15419
15531
  if (savedSettings.colors !== void 0)
@@ -15500,8 +15612,10 @@ async function startInteractive(config, repoPath) {
15500
15612
  let lastSubmittedPrompt = "";
15501
15613
  let sessionFilesTouched = [];
15502
15614
  let sessionToolCallCount = 0;
15503
- const idlePrompt = `${c2.bold(c2.blue("> "))}`;
15504
- const activePrompt = `${c2.dim(c2.cyan("+ "))}`;
15615
+ let sessionSudoPassword = null;
15616
+ let sudoPromptPending = false;
15617
+ const idlePrompt = `${c2.bold(c2.white("\u{1F896} "))}`;
15618
+ const activePrompt = `${c2.bold(c2.white("+ "))}`;
15505
15619
  const rl = readline2.createInterface({
15506
15620
  input: process.stdin,
15507
15621
  output: process.stdout,
@@ -15552,6 +15666,17 @@ async function startInteractive(config, repoPath) {
15552
15666
  fn();
15553
15667
  }
15554
15668
  }
15669
+ function handleSudoRequest(_command) {
15670
+ if (sudoPromptPending)
15671
+ return;
15672
+ if (sessionSudoPassword && activeTask) {
15673
+ activeTask.runner.setSudoPassword(sessionSudoPassword);
15674
+ return;
15675
+ }
15676
+ sudoPromptPending = true;
15677
+ const sudoPromptStr = ` ${c2.bold(c2.yellow("\u{1F511} Password:"))} `;
15678
+ process.stdout.write(sudoPromptStr);
15679
+ }
15555
15680
  const commandCtx = {
15556
15681
  get config() {
15557
15682
  return currentConfig;
@@ -15787,6 +15912,15 @@ async function startInteractive(config, repoPath) {
15787
15912
  }, 50);
15788
15913
  });
15789
15914
  async function processLine(input) {
15915
+ if (sudoPromptPending && activeTask) {
15916
+ sudoPromptPending = false;
15917
+ sessionSudoPassword = input;
15918
+ activeTask.runner.setSudoPassword(input);
15919
+ process.stdout.write(`\r\x1B[K ${c2.dim("\u{1F511} Password received")}
15920
+ `);
15921
+ showPrompt();
15922
+ return;
15923
+ }
15790
15924
  if (input.startsWith("/")) {
15791
15925
  if (statusBar.isActive)
15792
15926
  statusBar.beginContentWrite();
@@ -15836,7 +15970,7 @@ Execute this skill now. Follow the behavioral guidance above.`;
15836
15970
  taskMemoryStore: taskMemoryStore ?? void 0,
15837
15971
  failureStore: failureStore ?? void 0,
15838
15972
  toolPatternStore: toolPatternStore ?? void 0
15839
- }, bruteForceEnabled, statusBar);
15973
+ }, bruteForceEnabled, statusBar, handleSudoRequest);
15840
15974
  activeTask = task;
15841
15975
  showPrompt();
15842
15976
  await task.promise;
@@ -15934,7 +16068,7 @@ Summarize or analyze this transcription as appropriate.`;
15934
16068
  taskMemoryStore: taskMemoryStore ?? void 0,
15935
16069
  failureStore: failureStore ?? void 0,
15936
16070
  toolPatternStore: toolPatternStore ?? void 0
15937
- }, bruteForceEnabled, statusBar);
16071
+ }, bruteForceEnabled, statusBar, handleSudoRequest);
15938
16072
  activeTask = task;
15939
16073
  showPrompt();
15940
16074
  await task.promise;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.15.9",
3
+ "version": "0.16.1",
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",