aiden-runtime 3.16.2 → 3.18.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.
@@ -67,6 +67,7 @@ const mcpClient_1 = require("./mcpClient");
67
67
  const codeInterpreter_1 = require("./codeInterpreter");
68
68
  const sandboxRunner_1 = require("./sandboxRunner");
69
69
  const responseCache_1 = require("./responseCache");
70
+ const permissionSystem_1 = require("./permissionSystem");
70
71
  const youtubeTranscript_1 = require("./youtubeTranscript");
71
72
  const knowledgeBase_1 = require("./knowledgeBase");
72
73
  const calendarTool_1 = require("./tools/calendarTool");
@@ -182,6 +183,16 @@ const SHELL_ALLOWLIST = [
182
183
  // 13. Instant Actions: lock screen (rundll32) and volume one-liners (powershell -c)
183
184
  /^rundll32\b/i,
184
185
  /^powershell\s+-c\b/i,
186
+ // 14. Process / app control — close named apps by name (user-directed, not destructive)
187
+ /^taskkill\s+\/im\s+\S+/i, // taskkill /im chrome.exe
188
+ /^taskkill\s+\/f\s+\/im\s+\S+/i, // taskkill /f /im chrome.exe
189
+ /^Stop-Process\s+-Name\b/i, // Stop-Process -Name chrome
190
+ /^Stop-Process\s+-Id\b/i, // Stop-Process -Id 1234
191
+ /^kill\b/i, // kill <pid> (Unix/WSL)
192
+ // 15. Volume / brightness / display controls
193
+ /^nircmd\b/i,
194
+ // 16. Windows window management
195
+ /^(start|explorer)\b/i,
185
196
  ];
186
197
  function isCommandAllowed(cmd) {
187
198
  // Hard-block: denylist and dangerous patterns take priority
@@ -284,6 +295,7 @@ const TOOL_TIMEOUTS = {
284
295
  window_focus: 8000,
285
296
  app_launch: 10000,
286
297
  app_close: 8000,
298
+ system_volume: 8000,
287
299
  watch_folder: 10000,
288
300
  watch_folder_list: 5000,
289
301
  clarify: 300000, // up to 5 min for human response
@@ -331,9 +343,32 @@ exports.TOOLS = {
331
343
  const url = p.url || p.command || '';
332
344
  if (!url)
333
345
  return { success: false, output: '', error: 'No URL provided' };
346
+ // ── Permission system check ────────────────────────────────
347
+ const permBrowser = permissionSystem_1.permissionSystem.checkBrowserDomain(url);
348
+ if (permBrowser.verdict === 'deny') {
349
+ console.warn(`[Permissions] open_browser DENIED: ${url}`);
350
+ return { success: false, output: '', error: permBrowser.reason || 'Blocked by permission system.' };
351
+ }
352
+ if (permBrowser.verdict === 'ask') {
353
+ return { success: false, output: '', error: `PermissionGate: Navigation to this URL requires explicit user approval: ${url}` };
354
+ }
334
355
  const r = await (0, playwrightBridge_1.pwNavigate)(url);
335
- if (r.ok)
356
+ if (r.ok) {
357
+ // Auto-chain: if we landed on a YouTube search results page, immediately
358
+ // click the first video — so "play X on YouTube" works in a single step
359
+ // even when the planner forgets to emit the browser_click follow-up.
360
+ if (r.url.includes('youtube.com/results')) {
361
+ console.log('[open_browser] YouTube search detected — auto-clicking first result');
362
+ const click = await (0, playwrightBridge_1.pwClickFirstResult)();
363
+ if (click.ok) {
364
+ return { success: true, output: `Opened YouTube → playing first result → ${click.url ?? r.url}` };
365
+ }
366
+ console.warn(`[open_browser] YouTube auto-click failed: ${click.error}`);
367
+ // Navigation still succeeded; report it and let a browser_click retry handle it
368
+ return { success: true, output: `Opened browser: ${r.url} (auto-click failed: ${click.error})` };
369
+ }
336
370
  return { success: true, output: `Opened browser: ${r.url}` };
371
+ }
337
372
  // Playwright failed — fall back to system browser open
338
373
  // (Legacy path: activeBrowserPage = null; openBrowser(url))
339
374
  try {
@@ -485,14 +520,29 @@ exports.TOOLS = {
485
520
  const cmd = p.command || p.cmd || '';
486
521
  if (!cmd)
487
522
  return { success: false, output: '', error: 'No command' };
488
- const shellGate = isCommandAllowed(cmd);
489
- if (!shellGate.allowed) {
490
- if (shellGate.needsApproval) {
491
- console.warn(`[AllowList] shell_exec UNKNOWN — approval required: ${cmd.slice(0, 120)}`);
492
- return { success: false, output: '', error: `CommandGate: This command requires explicit user approval before running: ${cmd.slice(0, 80)}` };
523
+ // ── Permission system check (workspace/permissions.yaml) ──
524
+ const permCheck = permissionSystem_1.permissionSystem.checkShell(cmd);
525
+ if (permCheck.verdict === 'deny') {
526
+ console.warn(`[Permissions] shell_exec DENIED: ${cmd.slice(0, 120)}`);
527
+ return { success: false, output: '', error: permCheck.reason || 'Blocked by permission system.' };
528
+ }
529
+ if (permCheck.verdict === 'ask') {
530
+ console.warn(`[Permissions] shell_exec ASK — approval required: ${cmd.slice(0, 120)}`);
531
+ return { success: false, output: '', error: `PermissionGate: This command requires explicit user approval: ${cmd.slice(0, 80)}` };
532
+ }
533
+ // verdict === 'allow' skips the hardcoded gate below
534
+ // verdict === 'defer' falls through to the existing SHELL_ALLOWLIST gate
535
+ if (permCheck.verdict !== 'allow') {
536
+ // Existing hardcoded gate (DENIED_COMMANDS + SHELL_DANGEROUS + SHELL_ALLOWLIST)
537
+ const shellGate = isCommandAllowed(cmd);
538
+ if (!shellGate.allowed) {
539
+ if (shellGate.needsApproval) {
540
+ console.warn(`[AllowList] shell_exec UNKNOWN — approval required: ${cmd.slice(0, 120)}`);
541
+ return { success: false, output: '', error: `CommandGate: This command requires explicit user approval before running: ${cmd.slice(0, 80)}` };
542
+ }
543
+ console.warn(`[Security] shell_exec DENIED: ${cmd.slice(0, 120)}`);
544
+ return { success: false, output: '', error: 'Blocked: this command pattern is not allowed. Dangerous operations require explicit user approval.' };
493
545
  }
494
- console.warn(`[Security] shell_exec DENIED: ${cmd.slice(0, 120)}`);
495
- return { success: false, output: '', error: 'Blocked: this command pattern is not allowed. Dangerous operations require explicit user approval.' };
496
546
  }
497
547
  // ── N+34: Docker sandbox routing ───────────────────────────
498
548
  const _sandboxMode = process.env.AIDEN_SANDBOX_MODE || 'off';
@@ -552,14 +602,25 @@ exports.TOOLS = {
552
602
  const script = p.script || p.command || '';
553
603
  if (!script)
554
604
  return { success: false, output: '', error: 'No script' };
555
- const psGate = isCommandAllowed(script);
556
- if (!psGate.allowed) {
557
- if (psGate.needsApproval) {
558
- console.warn(`[AllowList] run_powershell UNKNOWN — approval required: ${script.slice(0, 120)}`);
559
- return { success: false, output: '', error: `CommandGate: This PowerShell command requires explicit user approval before running.` };
605
+ // ── Permission system check ────────────────────────────────
606
+ const permPs = permissionSystem_1.permissionSystem.checkShell(script);
607
+ if (permPs.verdict === 'deny') {
608
+ console.warn(`[Permissions] run_powershell DENIED: ${script.slice(0, 120)}`);
609
+ return { success: false, output: '', error: permPs.reason || 'Blocked by permission system.' };
610
+ }
611
+ if (permPs.verdict === 'ask') {
612
+ return { success: false, output: '', error: `PermissionGate: This PowerShell command requires explicit user approval: ${script.slice(0, 80)}` };
613
+ }
614
+ if (permPs.verdict !== 'allow') {
615
+ const psGate = isCommandAllowed(script);
616
+ if (!psGate.allowed) {
617
+ if (psGate.needsApproval) {
618
+ console.warn(`[AllowList] run_powershell UNKNOWN — approval required: ${script.slice(0, 120)}`);
619
+ return { success: false, output: '', error: `CommandGate: This PowerShell command requires explicit user approval before running.` };
620
+ }
621
+ console.warn(`[Security] run_powershell DENIED: ${script.slice(0, 120)}`);
622
+ return { success: false, output: '', error: 'Blocked: this command pattern is not allowed. Dangerous operations require explicit user approval.' };
560
623
  }
561
- console.warn(`[Security] run_powershell DENIED: ${script.slice(0, 120)}`);
562
- return { success: false, output: '', error: 'Blocked: this command pattern is not allowed. Dangerous operations require explicit user approval.' };
563
624
  }
564
625
  const tmpFile = path_1.default.join(process.cwd(), 'workspace', `tmp_${Date.now()}.ps1`);
565
626
  fs_1.default.mkdirSync(path_1.default.dirname(tmpFile), { recursive: true });
@@ -664,6 +725,12 @@ exports.TOOLS = {
664
725
  const content = p.content || '';
665
726
  if (!filePath)
666
727
  return { success: false, output: '', error: 'No path' };
728
+ // ── Permission system check ────────────────────────────────
729
+ const permWrite = permissionSystem_1.permissionSystem.checkFileWrite(filePath);
730
+ if (permWrite.verdict === 'deny') {
731
+ console.warn(`[Permissions] file_write DENIED: ${filePath}`);
732
+ return { success: false, output: '', error: permWrite.reason || 'Blocked by permission system.' };
733
+ }
667
734
  if (isProtectedFile(filePath)) {
668
735
  console.warn(`[Security] file_write BLOCKED (protected): ${filePath}`);
669
736
  return { success: false, output: '', error: `Protected file: ${filePath} cannot be modified by agents. Use 'devos config' or edit manually.` };
@@ -702,6 +769,12 @@ exports.TOOLS = {
702
769
  let filePath = p.path || p.file || '';
703
770
  if (!filePath)
704
771
  return { success: false, output: '', error: 'No path' };
772
+ // ── Permission system check ────────────────────────────────
773
+ const permRead = permissionSystem_1.permissionSystem.checkFileRead(filePath);
774
+ if (permRead.verdict === 'deny') {
775
+ console.warn(`[Permissions] file_read DENIED: ${filePath}`);
776
+ return { success: false, output: '', error: permRead.reason || 'Blocked by permission system.' };
777
+ }
705
778
  if (isPathDenied(filePath)) {
706
779
  console.warn(`[Security] file_read DENIED: ${filePath}`);
707
780
  return { success: false, output: '', error: 'Access denied: protected path. Aiden cannot read credentials, SSH keys, or env files.' };
@@ -1526,31 +1599,241 @@ exports.TOOLS = {
1526
1599
  }
1527
1600
  },
1528
1601
  app_launch: async (p) => {
1529
- const app = p.app || p.path || p.command || '';
1530
- if (!app)
1531
- return { success: false, output: '', error: 'No app specified' };
1532
- if (isShellDangerous(app)) {
1533
- return { success: false, output: '', error: 'CommandGate: Blocked potentially dangerous app launch.' };
1534
- }
1602
+ const appName = (p.app_name ?? p.appName ?? p.app ?? p.path ?? p.command ?? p.name ?? p.target ?? '').toString().toLowerCase().trim();
1603
+ if (!appName)
1604
+ return { success: false, output: '', error: 'No app_name provided. Pass app_name e.g. "chrome" or "spotify".' };
1605
+ // Map friendly display names → executable/URI names
1606
+ const exeMap = {
1607
+ 'chrome': 'chrome',
1608
+ 'google chrome': 'chrome',
1609
+ 'firefox': 'firefox',
1610
+ 'edge': 'msedge',
1611
+ 'microsoft edge': 'msedge',
1612
+ 'spotify': 'spotify',
1613
+ 'discord': 'discord',
1614
+ 'vscode': 'code',
1615
+ 'vs code': 'code',
1616
+ 'visual studio code': 'code',
1617
+ 'notepad': 'notepad',
1618
+ 'notepad++': 'notepad++',
1619
+ 'word': 'winword',
1620
+ 'excel': 'excel',
1621
+ 'powerpoint': 'powerpnt',
1622
+ 'slack': 'slack',
1623
+ 'zoom': 'zoom',
1624
+ 'teams': 'teams',
1625
+ 'microsoft teams': 'teams',
1626
+ 'explorer': 'explorer',
1627
+ 'file explorer': 'explorer',
1628
+ 'task manager': 'taskmgr',
1629
+ 'taskmgr': 'taskmgr',
1630
+ 'calculator': 'calc',
1631
+ 'calc': 'calc',
1632
+ 'paint': 'mspaint',
1633
+ 'terminal': 'wt',
1634
+ 'windows terminal': 'wt',
1635
+ 'cmd': 'cmd',
1636
+ 'powershell': 'powershell',
1637
+ };
1638
+ const exe = exeMap[appName] ?? appName;
1535
1639
  try {
1536
1640
  const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
1537
- const safe = app.replace(/'/g, "''");
1538
- execSync(`powershell.exe -Command "Start-Process '${safe}'"`, { timeout: 10000 });
1539
- return { success: true, output: `Launched: "${app}"` };
1641
+ // Use cmd /c start — cmd built-in, avoids Start-Process (blocked by permissions)
1642
+ execSync(`cmd /c start "" "${exe}"`, { timeout: 10000 });
1643
+ return { success: true, output: `Launched: "${appName}"` };
1540
1644
  }
1541
1645
  catch (e) {
1542
1646
  return { success: false, output: '', error: e.message };
1543
1647
  }
1544
1648
  },
1545
1649
  app_close: async (p) => {
1546
- const app = p.app || p.process || p.command || '';
1547
- if (!app)
1548
- return { success: false, output: '', error: 'No app/process name provided' };
1650
+ // Accept app_name (planner default), appName, name, app, process, command
1651
+ const appName = (p.app_name ?? p.appName ?? p.app ?? p.process ?? p.command ?? p.name ?? p.target ?? '').toString().toLowerCase().trim();
1652
+ if (!appName)
1653
+ return { success: false, output: '', error: 'No app name provided. Pass app_name e.g. "chrome" or "spotify".' };
1654
+ const exeMap = {
1655
+ 'chrome': 'chrome.exe',
1656
+ 'google chrome': 'chrome.exe',
1657
+ 'chrome browser': 'chrome.exe',
1658
+ 'firefox': 'firefox.exe',
1659
+ 'mozilla firefox': 'firefox.exe',
1660
+ 'edge': 'msedge.exe',
1661
+ 'microsoft edge': 'msedge.exe',
1662
+ 'spotify': 'Spotify.exe',
1663
+ 'notepad': 'notepad.exe',
1664
+ 'notepad++': 'notepad++.exe',
1665
+ 'word': 'WINWORD.EXE',
1666
+ 'microsoft word': 'WINWORD.EXE',
1667
+ 'excel': 'EXCEL.EXE',
1668
+ 'microsoft excel': 'EXCEL.EXE',
1669
+ 'powerpoint': 'POWERPNT.EXE',
1670
+ 'microsoft powerpoint': 'POWERPNT.EXE',
1671
+ 'vscode': 'Code.exe',
1672
+ 'vs code': 'Code.exe',
1673
+ 'visual studio code': 'Code.exe',
1674
+ 'discord': 'Discord.exe',
1675
+ 'slack': 'slack.exe',
1676
+ 'zoom': 'Zoom.exe',
1677
+ 'teams': 'Teams.exe',
1678
+ 'microsoft teams': 'Teams.exe',
1679
+ 'vlc': 'vlc.exe',
1680
+ 'steam': 'steam.exe',
1681
+ 'explorer': 'explorer.exe',
1682
+ 'file explorer': 'explorer.exe',
1683
+ 'windows explorer': 'explorer.exe',
1684
+ 'cmd': 'cmd.exe',
1685
+ 'command prompt': 'cmd.exe',
1686
+ 'terminal': 'wt.exe',
1687
+ 'windows terminal': 'wt.exe',
1688
+ 'paint': 'mspaint.exe',
1689
+ 'ms paint': 'mspaint.exe',
1690
+ 'calculator': 'Calculator.exe',
1691
+ 'task manager': 'Taskmgr.exe',
1692
+ 'taskmgr': 'Taskmgr.exe',
1693
+ 'whatsapp': 'WhatsApp.exe',
1694
+ 'telegram': 'Telegram.exe',
1695
+ 'obs': 'obs64.exe',
1696
+ 'obs studio': 'obs64.exe',
1697
+ 'brave': 'brave.exe',
1698
+ 'brave browser': 'brave.exe',
1699
+ 'opera': 'opera.exe',
1700
+ 'winamp': 'winamp.exe',
1701
+ 'itunes': 'iTunes.exe',
1702
+ };
1703
+ const exe = exeMap[appName] ?? (appName.endsWith('.exe') ? appName : appName + '.exe');
1704
+ try {
1705
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
1706
+ execSync(`taskkill /F /IM "${exe}"`, { timeout: 5000 });
1707
+ return { success: true, output: `Closed: "${appName}" (${exe})` };
1708
+ }
1709
+ catch (e) {
1710
+ const msg = (e.message || '').toLowerCase();
1711
+ if (msg.includes('not found') || msg.includes('no tasks')) {
1712
+ return { success: false, output: '', error: `Process not found: ${exe} — is "${appName}" running?` };
1713
+ }
1714
+ return { success: false, output: '', error: e.message };
1715
+ }
1716
+ },
1717
+ system_volume: async (p) => {
1718
+ // ── Natural input detection ──────────────────────────────────
1719
+ // Planner may send { volume: 20 }, { level: 50 }, { mute: true },
1720
+ // { action: "up", amount: 20 }, or a mix — normalise all forms here.
1721
+ let action = (p.action ?? '').toString().toLowerCase().trim();
1722
+ let amount = Number(p.amount ?? p.percent ?? p.by ?? 0);
1723
+ let target = p.level !== undefined ? Number(p.level)
1724
+ : p.set !== undefined ? Number(p.set) : -1;
1725
+ if (!action) {
1726
+ if (p.mute === true)
1727
+ action = 'mute';
1728
+ else if (p.unmute === true)
1729
+ action = 'unmute';
1730
+ else if (typeof p.volume === 'number') {
1731
+ action = (p.direction === 'down') ? 'down' : 'up';
1732
+ amount = p.volume;
1733
+ }
1734
+ else if (typeof p.level === 'number')
1735
+ action = 'set';
1736
+ // { amount: 20 } or { by: 20 } with no other hints → default up
1737
+ else if (amount > 0)
1738
+ action = (p.direction === 'down') ? 'down' : 'up';
1739
+ else
1740
+ action = 'get';
1741
+ }
1742
+ // Sensible default step when caller didn't supply an amount
1743
+ if (!amount && action !== 'get' && action !== 'mute' && action !== 'unmute') {
1744
+ amount = 20;
1745
+ }
1549
1746
  try {
1550
1747
  const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
1551
- const safe = app.replace(/'/g, "''");
1552
- execSync(`powershell.exe -Command "Stop-Process -Name '${safe}' -Force -ErrorAction SilentlyContinue"`, { timeout: 8000 });
1553
- return { success: true, output: `Closed process: "${app}"` };
1748
+ const { writeFileSync, unlinkSync } = await Promise.resolve().then(() => __importStar(require('fs')));
1749
+ const { tmpdir } = await Promise.resolve().then(() => __importStar(require('os')));
1750
+ const { join } = await Promise.resolve().then(() => __importStar(require('path')));
1751
+ // Helper: write + run + clean a temp .ps1 (avoids all quoting nightmares)
1752
+ const runPs = (script, label) => {
1753
+ const f = join(tmpdir(), `_aiden_${label}_${Date.now()}.ps1`);
1754
+ writeFileSync(f, script, 'utf8');
1755
+ try {
1756
+ return execSync(`powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${f}"`, { timeout: 6000, encoding: 'utf8' }).trim();
1757
+ }
1758
+ finally {
1759
+ try {
1760
+ unlinkSync(f);
1761
+ }
1762
+ catch { }
1763
+ }
1764
+ };
1765
+ // Shared keybd_event helper — works without a focused window unlike WScript.Shell
1766
+ const keybdScript = (vk) => `
1767
+ Add-Type -TypeDefinition @"
1768
+ using System; using System.Runtime.InteropServices;
1769
+ public class AidenKbd {
1770
+ [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
1771
+ }
1772
+ "@
1773
+ [AidenKbd]::keybd_event(${vk}, 0, 0, 0)
1774
+ Start-Sleep -Milliseconds 50
1775
+ [AidenKbd]::keybd_event(${vk}, 0, 2, 0)
1776
+ `;
1777
+ // ── Mute / Unmute — VK_VOLUME_MUTE = 0xAD = 173 ─────────
1778
+ if (action === 'mute' || action === 'unmute') {
1779
+ runPs(keybdScript(173), 'mute');
1780
+ return { success: true, output: action === 'mute' ? 'Muted' : 'Unmuted (toggle)' };
1781
+ }
1782
+ // ── Up / Down — fire VK_VOLUME_UP/DOWN N times ────────────
1783
+ // VK_VOLUME_DOWN = 0xAE = 174, VK_VOLUME_UP = 0xAF = 175
1784
+ if (action === 'up' || action === 'down') {
1785
+ const presses = Math.max(1, Math.round(amount / 2)); // each press ≈ 2%
1786
+ const vk = action === 'up' ? 175 : 174;
1787
+ const script = `
1788
+ Add-Type -TypeDefinition @"
1789
+ using System; using System.Runtime.InteropServices;
1790
+ public class AidenKbd2 {
1791
+ [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
1792
+ }
1793
+ "@
1794
+ for ($i = 0; $i -lt ${presses}; $i++) {
1795
+ [AidenKbd2]::keybd_event(${vk}, 0, 0, 0)
1796
+ Start-Sleep -Milliseconds 30
1797
+ [AidenKbd2]::keybd_event(${vk}, 0, 2, 0)
1798
+ Start-Sleep -Milliseconds 20
1799
+ }
1800
+ `;
1801
+ runPs(script, action);
1802
+ return { success: true, output: `Volume ${action === 'up' ? 'increased' : 'decreased'} by ~${presses * 2}%` };
1803
+ }
1804
+ // ── Get — read waveOut scalar ─────────────────────────────
1805
+ if (action === 'get') {
1806
+ const raw = runPs(`
1807
+ Add-Type -TypeDefinition @"
1808
+ using System; using System.Runtime.InteropServices;
1809
+ public class AidenVolGet {
1810
+ [DllImport("winmm.dll")] public static extern int waveOutGetVolume(IntPtr h, out uint v);
1811
+ }
1812
+ "@
1813
+ $v = [uint32]0
1814
+ [AidenVolGet]::waveOutGetVolume([IntPtr]::Zero, [ref]$v) | Out-Null
1815
+ [Math]::Round(($v -band 0xFFFF) / 65535.0 * 100)
1816
+ `, 'get');
1817
+ const vol = Number(raw);
1818
+ return { success: true, output: `Current volume: ${vol}%`, volume: vol };
1819
+ }
1820
+ // ── Set — write exact waveOut scalar ─────────────────────
1821
+ if (action === 'set' && target >= 0) {
1822
+ const clamped = Math.max(0, Math.min(100, target));
1823
+ const scalar = Math.round(clamped / 100 * 65535);
1824
+ const dword = (scalar << 16) | scalar;
1825
+ runPs(`
1826
+ Add-Type -TypeDefinition @"
1827
+ using System; using System.Runtime.InteropServices;
1828
+ public class AidenVolSet {
1829
+ [DllImport("winmm.dll")] public static extern int waveOutSetVolume(IntPtr h, uint v);
1830
+ }
1831
+ "@
1832
+ [AidenVolSet]::waveOutSetVolume([IntPtr]::Zero, [uint32]${dword}) | Out-Null
1833
+ `, 'set');
1834
+ return { success: true, output: `Volume set to ${clamped}%`, volume: clamped };
1835
+ }
1836
+ return { success: false, output: '', error: `Unknown action: "${action}". Use: get, up, down, mute, unmute, set` };
1554
1837
  }
1555
1838
  catch (e) {
1556
1839
  return { success: false, output: '', error: e.message };
@@ -2471,7 +2754,8 @@ exports.TOOL_DESCRIPTIONS = {
2471
2754
  window_list: 'List all open windows on the desktop',
2472
2755
  window_focus: 'Bring a specific window to the foreground by title',
2473
2756
  app_launch: 'Launch an application by name or executable path',
2474
- app_close: 'Close an application by window title',
2757
+ app_close: 'Close an application by window title or process name',
2758
+ system_volume: 'Get or set Windows speaker volume (get/up/down/mute/unmute/set)',
2475
2759
  watch_folder: 'Watch a folder and react automatically when new files appear',
2476
2760
  watch_folder_list: 'List all currently watched folder paths',
2477
2761
  get_briefing: 'Run the morning briefing: weather, markets, news, and daily summary',
@@ -2565,6 +2849,7 @@ const TOOL_TIERS = {
2565
2849
  window_focus: 3,
2566
2850
  app_launch: 3,
2567
2851
  app_close: 3,
2852
+ system_volume: 2,
2568
2853
  // Voice tools — Tier 2 (subprocess / local model)
2569
2854
  voice_speak: 2,
2570
2855
  voice_transcribe: 2,
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // AUTO-GENERATED by scripts/inject-version.js — do not edit by hand
5
- exports.VERSION = '3.16.2';
5
+ exports.VERSION = '3.18.0';
@@ -32,6 +32,7 @@ const nvidia_1 = require("./nvidia");
32
32
  const boa_1 = require("./boa");
33
33
  const custom_1 = require("./custom");
34
34
  const modelDiscovery_1 = require("../core/modelDiscovery");
35
+ const modelRegistry_1 = require("../core/modelRegistry");
35
36
  // Per-provider rate-limit windows — tuned to actual reset characteristics.
36
37
  // Previous flat 1-hour window was far too conservative for fast-reset APIs.
37
38
  const RATE_LIMIT_WINDOWS = {
@@ -424,7 +425,7 @@ function getSmartProvider() {
424
425
  // AUTO MODE: round-robin across available APIs
425
426
  const next = getNextAvailableAPI();
426
427
  if (next) {
427
- return { provider: next.provider, model: next.entry.model || 'llama-3.3-70b-versatile', userName, apiName: next.entry.name };
428
+ return { provider: next.provider, model: next.entry.model || (0, modelRegistry_1.getDefaultModel)(next.entry.provider) || 'llama-3.3-70b-versatile', userName, apiName: next.entry.name };
428
429
  }
429
430
  // FALLBACK: best discovered Ollama model
430
431
  if (config.routing?.fallbackToOllama !== false) {