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.
- package/dist/index.js +158 -24
- 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 ??
|
|
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 ??
|
|
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 ??
|
|
8029
|
-
bruteForceMaxCycles: options?.bruteForceMaxCycles ??
|
|
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
|
-
|
|
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: `
|
|
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: `[
|
|
8410
|
+
content: `[CONTINUATION \u2014 Cycle ${bruteForceCycle}]
|
|
8351
8411
|
|
|
8352
|
-
You have used ${totalTurns} turns and ${toolCallCount} tool calls
|
|
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.
|
|
8355
|
-
2.
|
|
8356
|
-
3.
|
|
8357
|
-
4.
|
|
8358
|
-
5.
|
|
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.
|
|
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:
|
|
15311
|
+
maxTurns: 60,
|
|
15209
15312
|
maxTokens: 16384,
|
|
15210
15313
|
temperature: 0,
|
|
15211
15314
|
requestTimeoutMs: config.timeoutMs,
|
|
15212
|
-
taskTimeoutMs:
|
|
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 ??
|
|
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 ??
|
|
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
|
-
|
|
15504
|
-
|
|
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