osai-agent 4.2.11 → 4.2.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osai-agent",
3
- "version": "4.2.11",
3
+ "version": "4.2.13",
4
4
  "type": "module",
5
5
  "description": "OS AI Agent - YOUR AI AGENT",
6
6
  "main": "src/index.js",
@@ -235,6 +235,14 @@ export default {
235
235
  }
236
236
  }
237
237
 
238
+ // Sudo password prompt
239
+ if (tool === TOOLS.LOCAL_CMD && /^sudo\b/.test(toolCall.cmd?.trim()) && !this.isSubagent) {
240
+ if (!this.sudoPassword) {
241
+ this.onMarkdown('\n_This command requires sudo. Enter your password:_\n');
242
+ this.sudoPassword = await this._readPassword(' sudo password: ');
243
+ }
244
+ }
245
+
238
246
  const safety = checkSafety(toolCall, this.mode);
239
247
 
240
248
  if ((safety.tier === SAFETY_TIERS.READ && this.noConfirm) || !safety.requiresConfirmation) {
@@ -410,7 +418,7 @@ export default {
410
418
 
411
419
  case TOOLS.LOCAL_CMD:
412
420
  default:
413
- return await executeLocal(toolCall.cmd || '');
421
+ return await executeLocal(toolCall.cmd || '', this.sudoPassword || null);
414
422
  }
415
423
  } catch (error) {
416
424
  logger.error('Tool dispatch error', { tool, error: error.message });
@@ -428,6 +436,42 @@ export default {
428
436
  });
429
437
  },
430
438
 
439
+ _readPassword(prompt) {
440
+ return new Promise((resolve) => {
441
+ const input = this.readline?.input || process.stdin;
442
+ const output = this.readline?.output || process.stdout;
443
+
444
+ output.write(prompt);
445
+ let password = '';
446
+
447
+ const onData = (data) => {
448
+ const char = String(data);
449
+
450
+ if (char === '\r' || char === '\n') {
451
+ input.removeListener('data', onData);
452
+ output.write('\n');
453
+ resolve(password);
454
+ return;
455
+ }
456
+
457
+ if (char === '\x7f' || char === '\b') {
458
+ if (password.length > 0) {
459
+ password = password.slice(0, -1);
460
+ output.write('\b \b');
461
+ }
462
+ return;
463
+ }
464
+
465
+ if (char.charCodeAt(0) < 32 && char !== '\t') return;
466
+
467
+ password += char;
468
+ output.write('*');
469
+ };
470
+
471
+ input.on('data', onData);
472
+ });
473
+ },
474
+
431
475
  async _confirmExecution(toolCall, safety) {
432
476
  const tier = safety.tier || (safety.isDangerous ? SAFETY_TIERS.DANGEROUS : SAFETY_TIERS.WRITE);
433
477
 
@@ -216,6 +216,7 @@ export class AgentLoop {
216
216
 
217
217
  cancel() {
218
218
  this._cancelled = true;
219
+ this.sudoPassword = null;
219
220
  if (this.abortController) {
220
221
  try { this.abortController.abort(); } catch {}
221
222
  }
@@ -236,6 +237,7 @@ export class AgentLoop {
236
237
  }
237
238
 
238
239
  async cleanup() {
240
+ this.sudoPassword = null;
239
241
  if (this.ws) { try { this.ws.close(); } catch {} this.ws = null; }
240
242
  if (this.mode === MODES.NETWORK) closeAllConnections();
241
243
  }
@@ -194,7 +194,7 @@ export const getSystemInfo = () => ({
194
194
  /**
195
195
  * Execute a local shell command with timeout and safety checks.
196
196
  */
197
- export const executeLocal = async (command) => {
197
+ export const executeLocal = async (command, sudoPassword = null) => {
198
198
  const trimmedCmd = (command || '').trim();
199
199
  if (!trimmedCmd) {
200
200
  return { success: false, output: '', error: 'Empty command' };
@@ -214,7 +214,11 @@ export const executeLocal = async (command) => {
214
214
  logger.debug('Executing local command', { cmd: trimmedCmd, timeout: COMMAND_TIMEOUT });
215
215
 
216
216
  const isWindows = detectOS() === 'windows';
217
- const finalCommand = isWindows ? `chcp 65001 >nul 2>&1 && ${trimmedCmd}` : trimmedCmd;
217
+ let finalCommand = isWindows ? `chcp 65001 >nul 2>&1 && ${trimmedCmd}` : trimmedCmd;
218
+
219
+ if (sudoPassword && /^sudo\b/.test(finalCommand)) {
220
+ finalCommand = finalCommand.replace(/^sudo\b/, 'sudo -S');
221
+ }
218
222
 
219
223
  return await new Promise((resolve) => {
220
224
  let output = '';
@@ -235,6 +239,14 @@ export const executeLocal = async (command) => {
235
239
  env: { ...process.env, PYTHONIOENCODING: 'utf-8', LANG: 'en_US.UTF-8' },
236
240
  });
237
241
 
242
+ if (sudoPassword) {
243
+ const writePassword = () => {
244
+ child.stdin.write(sudoPassword + '\n');
245
+ child.stdin.end();
246
+ };
247
+ setTimeout(writePassword, 200);
248
+ }
249
+
238
250
  const timeoutId = setTimeout(() => {
239
251
  timedOut = true;
240
252
  try { child.kill('SIGTERM'); } catch {}