ft-scout 9.0.3 → 9.0.5
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/.firebase/hosting.d2Vi.cache +7 -7
- package/README.md +76 -83
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +31 -228
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -1
- package/bin/src/commands/plan.js +45 -18
- package/bin/src/commands/plan.js.map +1 -1
- package/bin/src/engine/actionVerifier.d.ts.map +1 -1
- package/bin/src/engine/actionVerifier.js +61 -9
- package/bin/src/engine/actionVerifier.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +701 -553
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/agentState.d.ts.map +1 -1
- package/bin/src/engine/agentState.js +0 -64
- package/bin/src/engine/agentState.js.map +1 -1
- package/bin/src/engine/browser/browserController.d.ts.map +1 -1
- package/bin/src/engine/browser/browserController.js +8 -0
- package/bin/src/engine/browser/browserController.js.map +1 -1
- package/bin/src/engine/computer/computerController.d.ts.map +1 -1
- package/bin/src/engine/computer/computerController.js +686 -240
- package/bin/src/engine/computer/computerController.js.map +1 -1
- package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
- package/bin/src/engine/computer/computerDiagnostic.js +71 -0
- package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
- package/bin/src/engine/computer/index.d.ts.map +1 -1
- package/bin/src/engine/computer/index.js +2 -0
- package/bin/src/engine/computer/index.js.map +1 -1
- package/bin/src/engine/computer/mockComputer.d.ts.map +1 -0
- package/bin/src/engine/computer/mockComputer.js +260 -0
- package/bin/src/engine/computer/mockComputer.js.map +1 -0
- package/bin/src/engine/computer/scoutDriver.cs +873 -0
- package/bin/src/engine/computer/scout_driver.exe +0 -0
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -0
- package/bin/src/engine/runtime/agentRuntime.js +1160 -0
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -0
- package/bin/src/engine/runtime/index.d.ts.map +1 -0
- package/bin/src/engine/runtime/index.js +2 -0
- package/bin/src/engine/runtime/index.js.map +1 -0
- package/bin/src/engine/safety.d.ts.map +1 -1
- package/bin/src/engine/safety.js +59 -25
- package/bin/src/engine/safety.js.map +1 -1
- package/bin/src/engine/toolRegistry.d.ts.map +1 -1
- package/bin/src/engine/toolRegistry.js +293 -68
- package/bin/src/engine/toolRegistry.js.map +1 -1
- package/bin/src/engine/voiceEngine.d.ts.map +1 -1
- package/bin/src/engine/voiceEngine.js +0 -1
- package/bin/src/engine/voiceEngine.js.map +1 -1
- package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -0
- package/bin/src/engine/workspace/agentWorkspace.js +505 -0
- package/bin/src/engine/workspace/agentWorkspace.js.map +1 -0
- package/bin/src/engine/workspace/index.d.ts.map +1 -0
- package/bin/src/engine/workspace/index.js +2 -0
- package/bin/src/engine/workspace/index.js.map +1 -0
- package/bin/src/index.js +4 -5
- package/bin/src/index.js.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +25 -4
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js +81 -50
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/payment.d.ts.map +1 -1
- package/bin/src/utils/payment.js +15 -1
- package/bin/src/utils/payment.js.map +1 -1
- package/bin/src/utils/pricing.js +1 -1
- package/bin/src/utils/pricing.js.map +1 -1
- package/bin/src/utils/razorpay.d.ts.map +1 -1
- package/bin/src/utils/razorpay.js +10 -2
- package/bin/src/utils/razorpay.js.map +1 -1
- package/downloads/invoice-2026-03.pdf +1 -0
- package/package.json +1 -1
- package/web/app.js +54 -135
- package/web/styles.css +2 -2
- package/bin/src/engine/appControl.d.ts.map +0 -1
- package/bin/src/engine/appControl.js +0 -2413
- package/bin/src/engine/appControl.js.map +0 -1
- package/bin/src/engine/liveScreenEngine.d.ts.map +0 -1
- package/bin/src/engine/liveScreenEngine.js +0 -421
- package/bin/src/engine/liveScreenEngine.js.map +0 -1
- package/bin/src/engine/scripts/winCapture.ps1 +0 -248
- package/downloads/INVOICE_2026_ACME.pdf +0 -1
|
@@ -1,295 +1,741 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { exec, execFile, spawn } from 'child_process';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
export class SystemComputerController {
|
|
11
|
+
workspaceDir;
|
|
12
|
+
screenshotsDir;
|
|
13
|
+
expectedApplication = null;
|
|
14
|
+
activeWindow = null;
|
|
15
|
+
driverExePath = null;
|
|
16
|
+
driverInitAttempted = false;
|
|
17
|
+
constructor(workspaceDir = process.cwd()) {
|
|
18
|
+
this.workspaceDir = workspaceDir;
|
|
19
|
+
this.screenshotsDir = path.join(this.workspaceDir, '.ft', 'screenshots');
|
|
8
20
|
try {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
output: `Captured screen to ${shotPath}`,
|
|
13
|
-
details: { path: shotPath },
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
catch (err) {
|
|
17
|
-
return {
|
|
18
|
-
success: false,
|
|
19
|
-
output: `Failed to capture screen: ${err.message}`,
|
|
20
|
-
};
|
|
21
|
+
if (!fs.existsSync(this.screenshotsDir)) {
|
|
22
|
+
fs.mkdirSync(this.screenshotsDir, { recursive: true });
|
|
23
|
+
}
|
|
21
24
|
}
|
|
25
|
+
catch { }
|
|
22
26
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return {
|
|
26
|
-
success: res.success,
|
|
27
|
-
output: res.output,
|
|
28
|
-
details: res.details,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async double_click(x, y) {
|
|
32
|
-
const res = await executeInApp('desktop', 'click_app', { x, y, double: true });
|
|
33
|
-
return {
|
|
34
|
-
success: res.success,
|
|
35
|
-
output: res.output,
|
|
36
|
-
details: res.details,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
async type(text) {
|
|
40
|
-
const res = await executeInApp('desktop', 'type_text', { text });
|
|
41
|
-
return {
|
|
42
|
-
success: res.success,
|
|
43
|
-
output: res.output,
|
|
44
|
-
details: res.details,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
async key_press(key) {
|
|
48
|
-
const res = await executeInApp('desktop', 'send_keys', { key });
|
|
49
|
-
return {
|
|
50
|
-
success: res.success,
|
|
51
|
-
output: res.output,
|
|
52
|
-
details: res.details,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
async hotkey(keys) {
|
|
56
|
-
const keyCombo = Array.isArray(keys) ? keys.join('+') : keys;
|
|
57
|
-
const res = await executeInApp('desktop', 'key_combo', { keyCombo });
|
|
58
|
-
return {
|
|
59
|
-
success: res.success,
|
|
60
|
-
output: res.output,
|
|
61
|
-
details: res.details,
|
|
62
|
-
};
|
|
27
|
+
setExpectedApplication(app) {
|
|
28
|
+
this.expectedApplication = app;
|
|
63
29
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
success: res.success,
|
|
68
|
-
output: res.output,
|
|
69
|
-
details: res.details,
|
|
70
|
-
};
|
|
30
|
+
getExpectedApplication() {
|
|
31
|
+
return this.expectedApplication;
|
|
71
32
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolves or builds the native scout_driver.exe for Windows.
|
|
35
|
+
*/
|
|
36
|
+
async ensureDriver() {
|
|
37
|
+
if (this.driverExePath && fs.existsSync(this.driverExePath)) {
|
|
38
|
+
return this.driverExePath;
|
|
39
|
+
}
|
|
40
|
+
if (process.platform !== 'win32') {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Candidate locations
|
|
44
|
+
const candidates = [
|
|
45
|
+
path.join(__dirname, 'scout_driver.exe'),
|
|
46
|
+
path.join(this.workspaceDir, 'bin', 'src', 'engine', 'computer', 'scout_driver.exe'),
|
|
47
|
+
path.join(this.workspaceDir, '.ft', 'bin', 'scout_driver.exe'),
|
|
48
|
+
];
|
|
49
|
+
for (const c of candidates) {
|
|
50
|
+
if (fs.existsSync(c)) {
|
|
51
|
+
this.driverExePath = c;
|
|
52
|
+
return c;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (this.driverInitAttempted) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
this.driverInitAttempted = true;
|
|
59
|
+
// Try compiling using csc.exe
|
|
60
|
+
const csSourceCandidates = [
|
|
61
|
+
path.join(__dirname, 'scoutDriver.cs'),
|
|
62
|
+
path.join(this.workspaceDir, 'bin', 'src', 'engine', 'computer', 'scoutDriver.cs'),
|
|
63
|
+
];
|
|
64
|
+
const csSource = csSourceCandidates.find((p) => fs.existsSync(p));
|
|
65
|
+
const cscExe = 'C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe';
|
|
66
|
+
if (csSource && fs.existsSync(cscExe)) {
|
|
67
|
+
const outDir = path.join(this.workspaceDir, '.ft', 'bin');
|
|
68
|
+
try {
|
|
69
|
+
if (!fs.existsSync(outDir))
|
|
70
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
71
|
+
const targetExe = path.join(outDir, 'scout_driver.exe');
|
|
72
|
+
await execAsync(`"${cscExe}" /nologo /optimize /t:exe /out:"${targetExe}" /r:System.Windows.Forms.dll,System.Drawing.dll "${csSource}"`);
|
|
73
|
+
if (fs.existsSync(targetExe)) {
|
|
74
|
+
this.driverExePath = targetExe;
|
|
75
|
+
return targetExe;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Runs an action using native driver or fallback.
|
|
84
|
+
*/
|
|
85
|
+
async runDriverAction(action, args = []) {
|
|
86
|
+
const driver = await this.ensureDriver();
|
|
87
|
+
const strArgs = args.map((a) => String(a));
|
|
88
|
+
if (driver) {
|
|
89
|
+
try {
|
|
90
|
+
const { stdout } = await execFileAsync(driver, [action, ...strArgs], {
|
|
91
|
+
windowsHide: true,
|
|
92
|
+
timeout: 10000,
|
|
93
|
+
});
|
|
94
|
+
const trimmed = (stdout || '').trim();
|
|
95
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(trimmed);
|
|
98
|
+
}
|
|
99
|
+
catch { }
|
|
100
|
+
}
|
|
101
|
+
return { success: true, output: trimmed };
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
output: `Driver execution error: ${err?.message || String(err)}`,
|
|
107
|
+
error: err?.message || String(err),
|
|
108
|
+
recoverable: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// PowerShell fallback with input desktop attachment
|
|
113
|
+
return await this.runPowerShellFallback(action, strArgs);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Reliable PowerShell fallback with thread input desktop attachment
|
|
117
|
+
*/
|
|
118
|
+
async runPowerShellFallback(action, args) {
|
|
119
|
+
const script = `
|
|
120
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
121
|
+
Add-Type -AssemblyName System.Drawing
|
|
122
|
+
|
|
123
|
+
$src = @"
|
|
124
|
+
using System;
|
|
125
|
+
using System.Drawing;
|
|
126
|
+
using System.Drawing.Imaging;
|
|
127
|
+
using System.Threading;
|
|
128
|
+
using System.Runtime.InteropServices;
|
|
129
|
+
public class WinDriverFallback {
|
|
130
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
131
|
+
public static extern IntPtr OpenWindowStation(string lpszWinSta, bool fInherit, uint dwDesiredAccess);
|
|
132
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
133
|
+
public static extern bool SetProcessWindowStation(IntPtr hWinSta);
|
|
134
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
135
|
+
public static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess);
|
|
136
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
137
|
+
public static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess);
|
|
138
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
139
|
+
public static extern bool SetThreadDesktop(IntPtr hDesktop);
|
|
140
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
141
|
+
public static extern bool GetCursorPos(out POINT lpPoint);
|
|
142
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
143
|
+
public static extern bool SetCursorPos(int X, int Y);
|
|
144
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
145
|
+
public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);
|
|
146
|
+
[DllImport("user32.dll")]
|
|
147
|
+
public static extern int GetSystemMetrics(int nIndex);
|
|
148
|
+
[DllImport("user32.dll")]
|
|
149
|
+
public static extern bool SetProcessDPIAware();
|
|
150
|
+
|
|
151
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
152
|
+
public struct POINT { public int X; public int Y; }
|
|
153
|
+
|
|
154
|
+
public static void Attach() {
|
|
155
|
+
try {
|
|
156
|
+
SetProcessDPIAware();
|
|
157
|
+
IntPtr hw = OpenWindowStation("WinSta0", false, 0x10000000);
|
|
158
|
+
if (hw != IntPtr.Zero) SetProcessWindowStation(hw);
|
|
159
|
+
IntPtr hd = OpenInputDesktop(0, false, 0x10000000);
|
|
160
|
+
if (hd == IntPtr.Zero) hd = OpenDesktop("Default", 0, false, 0x10000000);
|
|
161
|
+
if (hd != IntPtr.Zero) SetThreadDesktop(hd);
|
|
162
|
+
} catch {}
|
|
79
163
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
164
|
+
}
|
|
165
|
+
"@
|
|
166
|
+
Add-Type -TypeDefinition $src -ReferencedAssemblies System.Drawing
|
|
167
|
+
|
|
168
|
+
[WinDriverFallback]::Attach()
|
|
169
|
+
$act = "${action.replace(/"/g, '`"')}".ToLower()
|
|
170
|
+
|
|
171
|
+
if ($act -eq "move") {
|
|
172
|
+
$x = [int]${Number(args[0]) || 0}
|
|
173
|
+
$y = [int]${Number(args[1]) || 0}
|
|
174
|
+
[WinDriverFallback]::SetCursorPos($x, $y)
|
|
175
|
+
Write-Output "{\`"success\`":true,\`"action\`":\`"move\`",\`"coordinates\`":{\`"x\`":$x,\`"y\`":$y}}"
|
|
176
|
+
} elseif ($act -eq "click" -or $act -eq "left_click") {
|
|
177
|
+
$x = [int]${Number(args[0]) || 0}
|
|
178
|
+
$y = [int]${Number(args[1]) || 0}
|
|
179
|
+
[WinDriverFallback]::SetCursorPos($x, $y)
|
|
180
|
+
Start-Sleep -Milliseconds 30
|
|
181
|
+
[WinDriverFallback]::mouse_event(0x0002, 0, 0, 0, 0)
|
|
182
|
+
Start-Sleep -Milliseconds 40
|
|
183
|
+
[WinDriverFallback]::mouse_event(0x0004, 0, 0, 0, 0)
|
|
184
|
+
Write-Output "{\`"success\`":true,\`"action\`":\`"click\`",\`"button\`":\`"left\`",\`"coordinates\`":{\`"x\`":$x,\`"y\`":$y}}"
|
|
185
|
+
} elseif ($act -eq "right_click") {
|
|
186
|
+
$x = [int]${Number(args[0]) || 0}
|
|
187
|
+
$y = [int]${Number(args[1]) || 0}
|
|
188
|
+
[WinDriverFallback]::SetCursorPos($x, $y)
|
|
189
|
+
Start-Sleep -Milliseconds 30
|
|
190
|
+
[WinDriverFallback]::mouse_event(0x0008, 0, 0, 0, 0)
|
|
191
|
+
Start-Sleep -Milliseconds 40
|
|
192
|
+
[WinDriverFallback]::mouse_event(0x0010, 0, 0, 0, 0)
|
|
193
|
+
Write-Output "{\`"success\`":true,\`"action\`":\`"right_click\`",\`"button\`":\`"right\`",\`"coordinates\`":{\`"x\`":$x,\`"y\`":$y}}"
|
|
194
|
+
} elseif ($act -eq "double_click") {
|
|
195
|
+
$x = [int]${Number(args[0]) || 0}
|
|
196
|
+
$y = [int]${Number(args[1]) || 0}
|
|
197
|
+
[WinDriverFallback]::SetCursorPos($x, $y)
|
|
198
|
+
Start-Sleep -Milliseconds 30
|
|
199
|
+
[WinDriverFallback]::mouse_event(0x0002, 0, 0, 0, 0)
|
|
200
|
+
Start-Sleep -Milliseconds 40
|
|
201
|
+
[WinDriverFallback]::mouse_event(0x0004, 0, 0, 0, 0)
|
|
202
|
+
Start-Sleep -Milliseconds 80
|
|
203
|
+
[WinDriverFallback]::mouse_event(0x0002, 0, 0, 0, 0)
|
|
204
|
+
Start-Sleep -Milliseconds 40
|
|
205
|
+
[WinDriverFallback]::mouse_event(0x0004, 0, 0, 0, 0)
|
|
206
|
+
Write-Output "{\`"success\`":true,\`"action\`":\`"double_click\`",\`"coordinates\`":{\`"x\`":$x,\`"y\`":$y}}"
|
|
207
|
+
} elseif ($act -eq "screenshot") {
|
|
208
|
+
$w = [WinDriverFallback]::GetSystemMetrics(0)
|
|
209
|
+
$h = [WinDriverFallback]::GetSystemMetrics(1)
|
|
210
|
+
if ($w -le 0) { $w = 1920 }
|
|
211
|
+
if ($h -le 0) { $h = 1080 }
|
|
212
|
+
$p = "${(args[0] || '').replace(/\\/g, '\\\\').replace(/"/g, '`"')}"
|
|
213
|
+
$bmp = New-Object System.Drawing.Bitmap $w, $h
|
|
214
|
+
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
215
|
+
$g.CopyFromScreen(0, 0, 0, 0, (New-Object System.Drawing.Size($w, $h)), [System.Drawing.CopyPixelOperation]::SourceCopy)
|
|
216
|
+
$bmp.Save($p, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
217
|
+
$g.Dispose()
|
|
218
|
+
$bmp.Dispose()
|
|
219
|
+
Write-Output "{\`"success\`":true,\`"action\`":\`"screenshot\`",\`"width\`":$w,\`"height\`":$h,\`"path\`":\`"$p\`"}"
|
|
220
|
+
} else {
|
|
221
|
+
Write-Output "{\`"success\`":true,\`"action\`":\`"$act\`"}"
|
|
222
|
+
}
|
|
223
|
+
`;
|
|
224
|
+
try {
|
|
225
|
+
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
|
226
|
+
const { stdout } = await execAsync(`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`);
|
|
227
|
+
const trimmed = (stdout || '').trim();
|
|
228
|
+
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
229
|
+
return JSON.parse(trimmed);
|
|
230
|
+
}
|
|
231
|
+
return { success: true, output: trimmed };
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
return { success: false, output: `PowerShell fallback failed: ${err?.message || String(err)}`, error: err?.message, recoverable: true };
|
|
235
|
+
}
|
|
87
236
|
}
|
|
88
|
-
async
|
|
89
|
-
|
|
237
|
+
async screenshot() {
|
|
238
|
+
const timestamp = new Date().toISOString();
|
|
239
|
+
const filename = `screen_${Date.now()}.png`;
|
|
240
|
+
const filePath = path.join(this.screenshotsDir, filename);
|
|
241
|
+
if (process.platform === 'win32') {
|
|
242
|
+
const res = await this.runDriverAction('screenshot', [filePath]);
|
|
243
|
+
if (res && res.success) {
|
|
244
|
+
const activeWin = res.activeWindow || (await this.inspectUI()).activeWindow;
|
|
245
|
+
this.activeWindow = activeWin;
|
|
246
|
+
return {
|
|
247
|
+
path: filePath,
|
|
248
|
+
width: Number(res.width) || 1920,
|
|
249
|
+
height: Number(res.height) || 1080,
|
|
250
|
+
activeWindow: activeWin,
|
|
251
|
+
timestamp,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (process.platform === 'darwin') {
|
|
256
|
+
try {
|
|
257
|
+
await execAsync(`screencapture -x "${filePath}"`);
|
|
258
|
+
return { path: filePath, width: 1920, height: 1080, activeWindow: 'Desktop', timestamp };
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return { width: 1920, height: 1080, timestamp };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
try {
|
|
266
|
+
await execAsync(`scrot "${filePath}"`);
|
|
267
|
+
return { path: filePath, width: 1920, height: 1080, activeWindow: 'Desktop', timestamp };
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return { width: 1920, height: 1080, timestamp };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { width: 1920, height: 1080, activeWindow: 'Desktop', timestamp };
|
|
274
|
+
}
|
|
275
|
+
async click(x, y, options) {
|
|
276
|
+
const btn = options?.button || 'left';
|
|
277
|
+
const count = options?.clicks || 1;
|
|
278
|
+
if (process.platform === 'win32') {
|
|
279
|
+
const act = count === 2 ? 'double_click' : btn === 'right' ? 'right_click' : 'click';
|
|
280
|
+
const res = await this.runDriverAction(act, [x, y, btn]);
|
|
281
|
+
const success = Boolean(res?.success);
|
|
282
|
+
const coords = res?.coordinates || { x, y };
|
|
283
|
+
const activeWin = res?.activeWindow || this.activeWindow || undefined;
|
|
284
|
+
return {
|
|
285
|
+
success,
|
|
286
|
+
output: success
|
|
287
|
+
? `Clicked ${btn} button at (${coords.x}, ${coords.y})`
|
|
288
|
+
: `Click failed at (${x}, ${y}): ${res?.error || 'Unknown error'}`,
|
|
289
|
+
action: 'click',
|
|
290
|
+
coordinates: coords,
|
|
291
|
+
button: btn,
|
|
292
|
+
error: res?.error,
|
|
293
|
+
recoverable: res?.recoverable ?? true,
|
|
294
|
+
activeWindow: activeWin,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
90
297
|
return {
|
|
91
298
|
success: true,
|
|
92
|
-
output: `
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
async open_application(name, target) {
|
|
97
|
-
const res = await openApp(name, target);
|
|
98
|
-
return {
|
|
99
|
-
success: res.success,
|
|
100
|
-
output: res.output,
|
|
101
|
-
details: res.details,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
async close_application(name) {
|
|
105
|
-
const res = await executeInApp(name, 'close_app');
|
|
106
|
-
return {
|
|
107
|
-
success: res.success,
|
|
108
|
-
output: res.output,
|
|
109
|
-
details: res.details,
|
|
299
|
+
output: `Simulated click ${btn} at (${x}, ${y})`,
|
|
300
|
+
action: 'click',
|
|
301
|
+
coordinates: { x, y },
|
|
302
|
+
button: btn,
|
|
110
303
|
};
|
|
111
304
|
}
|
|
112
|
-
async
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
success: res.success,
|
|
116
|
-
output: res.output,
|
|
117
|
-
details: res.details,
|
|
118
|
-
};
|
|
305
|
+
async leftClick(x, y) {
|
|
306
|
+
return this.click(x, y, { button: 'left', clicks: 1 });
|
|
119
307
|
}
|
|
120
|
-
async
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
success: res.success,
|
|
124
|
-
output: res.output,
|
|
125
|
-
details: res.details,
|
|
126
|
-
};
|
|
308
|
+
async rightClick(x, y) {
|
|
309
|
+
return this.click(x, y, { button: 'right', clicks: 1 });
|
|
127
310
|
}
|
|
128
|
-
async
|
|
129
|
-
|
|
311
|
+
async doubleClick(x, y) {
|
|
312
|
+
if (process.platform === 'win32') {
|
|
313
|
+
const res = await this.runDriverAction('double_click', [x, y]);
|
|
314
|
+
const success = Boolean(res?.success);
|
|
315
|
+
const coords = res?.coordinates || { x, y };
|
|
316
|
+
return {
|
|
317
|
+
success,
|
|
318
|
+
output: success
|
|
319
|
+
? `Double-clicked at (${coords.x}, ${coords.y})`
|
|
320
|
+
: `Double-click failed at (${x}, ${y}): ${res?.error || 'Unknown error'}`,
|
|
321
|
+
action: 'double_click',
|
|
322
|
+
coordinates: coords,
|
|
323
|
+
button: 'left',
|
|
324
|
+
error: res?.error,
|
|
325
|
+
recoverable: res?.recoverable ?? true,
|
|
326
|
+
activeWindow: res?.activeWindow || this.activeWindow || undefined,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
130
329
|
return {
|
|
131
|
-
success:
|
|
132
|
-
output:
|
|
133
|
-
|
|
330
|
+
success: true,
|
|
331
|
+
output: `Simulated double-click at (${x}, ${y})`,
|
|
332
|
+
action: 'double_click',
|
|
333
|
+
coordinates: { x, y },
|
|
334
|
+
button: 'left',
|
|
134
335
|
};
|
|
135
336
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
337
|
+
async mouseDown(x, y, button) {
|
|
338
|
+
const btn = button || 'left';
|
|
339
|
+
if (process.platform === 'win32') {
|
|
340
|
+
const res = await this.runDriverAction('mouse_down', [x ?? -1, y ?? -1, btn]);
|
|
341
|
+
const coords = res?.coordinates || (x !== undefined && y !== undefined ? { x, y } : undefined);
|
|
342
|
+
return {
|
|
343
|
+
success: Boolean(res?.success),
|
|
344
|
+
output: `Mouse down (${btn}) at (${coords?.x ?? 'current'}, ${coords?.y ?? 'current'})`,
|
|
345
|
+
action: 'mouse_down',
|
|
346
|
+
button: btn,
|
|
347
|
+
coordinates: coords,
|
|
348
|
+
error: res?.error,
|
|
349
|
+
recoverable: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
149
352
|
return {
|
|
150
353
|
success: true,
|
|
151
|
-
output:
|
|
152
|
-
|
|
354
|
+
output: `Simulated mouse down (${btn})`,
|
|
355
|
+
action: 'mouse_down',
|
|
356
|
+
button: btn,
|
|
357
|
+
coordinates: x !== undefined && y !== undefined ? { x, y } : undefined,
|
|
153
358
|
};
|
|
154
359
|
}
|
|
155
|
-
async
|
|
156
|
-
const btn =
|
|
157
|
-
|
|
158
|
-
|
|
360
|
+
async mouseUp(x, y, button) {
|
|
361
|
+
const btn = button || 'left';
|
|
362
|
+
if (process.platform === 'win32') {
|
|
363
|
+
const res = await this.runDriverAction('mouse_up', [x ?? -1, y ?? -1, btn]);
|
|
364
|
+
const coords = res?.coordinates || (x !== undefined && y !== undefined ? { x, y } : undefined);
|
|
365
|
+
return {
|
|
366
|
+
success: Boolean(res?.success),
|
|
367
|
+
output: `Mouse up (${btn}) at (${coords?.x ?? 'current'}, ${coords?.y ?? 'current'})`,
|
|
368
|
+
action: 'mouse_up',
|
|
369
|
+
button: btn,
|
|
370
|
+
coordinates: coords,
|
|
371
|
+
error: res?.error,
|
|
372
|
+
recoverable: true,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
159
375
|
return {
|
|
160
376
|
success: true,
|
|
161
|
-
output: `
|
|
162
|
-
|
|
377
|
+
output: `Simulated mouse up (${btn})`,
|
|
378
|
+
action: 'mouse_up',
|
|
379
|
+
button: btn,
|
|
380
|
+
coordinates: x !== undefined && y !== undefined ? { x, y } : undefined,
|
|
163
381
|
};
|
|
164
382
|
}
|
|
165
|
-
async
|
|
166
|
-
return
|
|
167
|
-
}
|
|
168
|
-
async typeText(text) {
|
|
169
|
-
return this.type(text);
|
|
170
|
-
}
|
|
171
|
-
async takeScreenshot() {
|
|
172
|
-
return {
|
|
173
|
-
format: 'base64',
|
|
174
|
-
width: 1920,
|
|
175
|
-
height: 1080,
|
|
176
|
-
data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
|
177
|
-
};
|
|
383
|
+
async mouseMove(x, y) {
|
|
384
|
+
return this.move(x, y);
|
|
178
385
|
}
|
|
179
|
-
async
|
|
180
|
-
|
|
181
|
-
|
|
386
|
+
async move(x, y) {
|
|
387
|
+
if (process.platform === 'win32') {
|
|
388
|
+
const res = await this.runDriverAction('move', [x, y]);
|
|
389
|
+
const success = Boolean(res?.success);
|
|
390
|
+
const coords = res?.coordinates || { x, y };
|
|
391
|
+
return {
|
|
392
|
+
success,
|
|
393
|
+
output: success
|
|
394
|
+
? `Moved mouse cursor to (${coords.x}, ${coords.y})`
|
|
395
|
+
: `MouseMove failed to (${x}, ${y}): ${res?.error || 'Unknown error'}`,
|
|
396
|
+
action: 'move',
|
|
397
|
+
coordinates: coords,
|
|
398
|
+
error: res?.error,
|
|
399
|
+
recoverable: res?.recoverable ?? true,
|
|
400
|
+
activeWindow: res?.activeWindow || this.activeWindow || undefined,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
182
403
|
return {
|
|
183
404
|
success: true,
|
|
184
|
-
output: `
|
|
185
|
-
|
|
405
|
+
output: `Simulated mouse move to (${x}, ${y})`,
|
|
406
|
+
action: 'move',
|
|
407
|
+
coordinates: { x, y },
|
|
186
408
|
};
|
|
187
409
|
}
|
|
188
|
-
async
|
|
189
|
-
|
|
190
|
-
|
|
410
|
+
async drag(startX, startY, endX, endY) {
|
|
411
|
+
if (process.platform === 'win32') {
|
|
412
|
+
const res = await this.runDriverAction('drag', [startX, startY, endX, endY]);
|
|
413
|
+
const success = Boolean(res?.success);
|
|
414
|
+
return {
|
|
415
|
+
success,
|
|
416
|
+
output: success
|
|
417
|
+
? `Dragged from (${startX}, ${startY}) to (${endX}, ${endY})`
|
|
418
|
+
: `Drag failed: ${res?.error || 'Unknown error'}`,
|
|
419
|
+
action: 'drag',
|
|
420
|
+
from: { x: startX, y: startY },
|
|
421
|
+
to: { x: endX, y: endY },
|
|
422
|
+
coordinates: res?.coordinates || { x: endX, y: endY },
|
|
423
|
+
error: res?.error,
|
|
424
|
+
recoverable: res?.recoverable ?? true,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
191
427
|
return {
|
|
192
428
|
success: true,
|
|
193
|
-
output: `
|
|
194
|
-
|
|
429
|
+
output: `Simulated drag from (${startX}, ${startY}) to (${endX}, ${endY})`,
|
|
430
|
+
action: 'drag',
|
|
431
|
+
from: { x: startX, y: startY },
|
|
432
|
+
to: { x: endX, y: endY },
|
|
433
|
+
coordinates: { x: endX, y: endY },
|
|
195
434
|
};
|
|
196
435
|
}
|
|
197
|
-
async
|
|
198
|
-
|
|
436
|
+
async scroll(deltaX, deltaY) {
|
|
437
|
+
if (process.platform === 'win32') {
|
|
438
|
+
const res = await this.runDriverAction('scroll', [deltaX, deltaY]);
|
|
439
|
+
return {
|
|
440
|
+
success: Boolean(res?.success),
|
|
441
|
+
output: `Scrolled deltaY=${deltaY}, deltaX=${deltaX}`,
|
|
442
|
+
action: 'scroll',
|
|
443
|
+
details: { deltaX, deltaY },
|
|
444
|
+
};
|
|
445
|
+
}
|
|
199
446
|
return {
|
|
200
447
|
success: true,
|
|
201
|
-
output: `
|
|
202
|
-
|
|
448
|
+
output: `Simulated scroll deltaY=${deltaY}, deltaX=${deltaX}`,
|
|
449
|
+
action: 'scroll',
|
|
450
|
+
details: { deltaX, deltaY },
|
|
203
451
|
};
|
|
204
452
|
}
|
|
205
|
-
async
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
};
|
|
453
|
+
async getMousePosition() {
|
|
454
|
+
if (process.platform === 'win32') {
|
|
455
|
+
const res = await this.runDriverAction('position', []);
|
|
456
|
+
if (res && res.coordinates) {
|
|
457
|
+
return res.coordinates;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return { x: 0, y: 0 };
|
|
213
461
|
}
|
|
214
|
-
async
|
|
215
|
-
|
|
462
|
+
async runDiagnostic() {
|
|
463
|
+
if (process.platform === 'win32') {
|
|
464
|
+
const res = await this.runDriverAction('diagnostic', []);
|
|
465
|
+
return {
|
|
466
|
+
success: Boolean(res?.success),
|
|
467
|
+
output: `Diagnostic: Screen ${res?.width}x${res?.height}, Mouse (${res?.mouse?.x}, ${res?.mouse?.y}), Active: "${res?.activeWindow}", Windows: ${res?.windowCount}`,
|
|
468
|
+
action: 'diagnostic',
|
|
469
|
+
coordinates: res?.mouse,
|
|
470
|
+
activeWindow: res?.activeWindow,
|
|
471
|
+
details: res,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
216
474
|
return {
|
|
217
475
|
success: true,
|
|
218
|
-
output: `
|
|
219
|
-
|
|
476
|
+
output: `Diagnostic on ${process.platform}: Screen 1920x1080, Mouse (0, 0), Active: "Desktop"`,
|
|
477
|
+
action: 'diagnostic',
|
|
478
|
+
coordinates: { x: 0, y: 0 },
|
|
479
|
+
activeWindow: 'Desktop',
|
|
220
480
|
};
|
|
221
481
|
}
|
|
222
|
-
async
|
|
223
|
-
|
|
224
|
-
|
|
482
|
+
async type(text) {
|
|
483
|
+
if (!text)
|
|
484
|
+
return { success: true, output: 'Typed empty string', action: 'type' };
|
|
485
|
+
if (process.platform === 'win32') {
|
|
486
|
+
const targetApp = this.expectedApplication || '';
|
|
487
|
+
if (targetApp) {
|
|
488
|
+
await this.switchWindow(targetApp);
|
|
489
|
+
}
|
|
490
|
+
const res = await this.runDriverAction('type', [text]);
|
|
491
|
+
const success = Boolean(res?.success);
|
|
492
|
+
const activeWin = res?.activeWindow || targetApp || 'foreground';
|
|
493
|
+
this.activeWindow = activeWin;
|
|
494
|
+
return {
|
|
495
|
+
success,
|
|
496
|
+
output: success
|
|
497
|
+
? `Typed text (${text.length} chars) into "${activeWin}"`
|
|
498
|
+
: `Type failed: ${res?.error || 'Unknown error'}`,
|
|
499
|
+
action: 'type',
|
|
500
|
+
error: res?.error,
|
|
501
|
+
recoverable: true,
|
|
502
|
+
activeWindow: activeWin,
|
|
503
|
+
expectedWindow: this.expectedApplication || undefined,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
225
506
|
return {
|
|
226
507
|
success: true,
|
|
227
|
-
output: `
|
|
228
|
-
|
|
508
|
+
output: `Simulated type (${text.length} chars)`,
|
|
509
|
+
action: 'type',
|
|
510
|
+
activeWindow: this.expectedApplication || 'Simulated Window',
|
|
229
511
|
};
|
|
230
512
|
}
|
|
231
|
-
async
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
513
|
+
async keyPress(key) {
|
|
514
|
+
if (process.platform === 'win32') {
|
|
515
|
+
const targetApp = this.expectedApplication || '';
|
|
516
|
+
if (targetApp) {
|
|
517
|
+
await this.switchWindow(targetApp);
|
|
518
|
+
}
|
|
519
|
+
const res = await this.runDriverAction('key', [key]);
|
|
520
|
+
const success = Boolean(res?.success);
|
|
521
|
+
return {
|
|
522
|
+
success,
|
|
523
|
+
output: success ? `Pressed key: ${key}` : `KeyPress failed: ${res?.error || 'Unknown error'}`,
|
|
524
|
+
action: 'key_press',
|
|
525
|
+
error: res?.error,
|
|
526
|
+
recoverable: true,
|
|
527
|
+
activeWindow: res?.activeWindow || targetApp || undefined,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
return { success: true, output: `Simulated key press: ${key}`, action: 'key_press' };
|
|
239
531
|
}
|
|
240
|
-
async
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
532
|
+
async hotkey(keys) {
|
|
533
|
+
if (!keys || keys.length === 0) {
|
|
534
|
+
return { success: false, output: 'No keys provided for hotkey', action: 'hotkey', error: 'No keys provided' };
|
|
535
|
+
}
|
|
536
|
+
if (process.platform === 'win32') {
|
|
537
|
+
const targetApp = this.expectedApplication || '';
|
|
538
|
+
if (targetApp) {
|
|
539
|
+
await this.switchWindow(targetApp);
|
|
540
|
+
}
|
|
541
|
+
const res = await this.runDriverAction('hotkey', [keys.join('+')]);
|
|
542
|
+
const success = Boolean(res?.success);
|
|
543
|
+
return {
|
|
544
|
+
success,
|
|
545
|
+
output: success ? `Executed hotkey: ${keys.join('+')}` : `Hotkey failed: ${res?.error || 'Unknown error'}`,
|
|
546
|
+
action: 'hotkey',
|
|
547
|
+
error: res?.error,
|
|
548
|
+
recoverable: true,
|
|
549
|
+
activeWindow: res?.activeWindow || targetApp || undefined,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return { success: true, output: `Simulated hotkey: ${keys.join('+')}`, action: 'hotkey' };
|
|
247
553
|
}
|
|
248
|
-
async
|
|
249
|
-
|
|
250
|
-
this.activeApp = name;
|
|
251
|
-
this.actionLog.push({ action: 'open_application', params: { name, target } });
|
|
252
|
-
return {
|
|
253
|
-
success: true,
|
|
254
|
-
output: `Mock opened application "${name}" with target "${target || 'default'}".`,
|
|
255
|
-
details: { name, target, active: true },
|
|
256
|
-
};
|
|
554
|
+
async wait(ms) {
|
|
555
|
+
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
257
556
|
}
|
|
258
|
-
async
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
557
|
+
async openApplication(appName, args = []) {
|
|
558
|
+
const appLower = appName.toLowerCase().trim();
|
|
559
|
+
const argsStr = args.join(' ');
|
|
560
|
+
try {
|
|
561
|
+
if (process.platform === 'win32') {
|
|
562
|
+
const driverRes = await this.runDriverAction('launch', [appName]);
|
|
563
|
+
if (!driverRes || !driverRes.success) {
|
|
564
|
+
if (appLower === 'code' || appLower === 'vscode') {
|
|
565
|
+
await execAsync(`start code ${argsStr}`);
|
|
566
|
+
}
|
|
567
|
+
else if (appLower === 'explorer' || appLower === 'file explorer') {
|
|
568
|
+
await execAsync(`start explorer ${argsStr || '.'}`);
|
|
569
|
+
}
|
|
570
|
+
else if (appLower === 'notepad') {
|
|
571
|
+
await execAsync(`start notepad ${argsStr}`);
|
|
572
|
+
}
|
|
573
|
+
else if (appLower === 'terminal' || appLower === 'cmd' || appLower === 'powershell') {
|
|
574
|
+
await execAsync(`start powershell ${argsStr}`);
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
await execAsync(`start "" "${appName}" ${argsStr}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
this.expectedApplication = appName;
|
|
581
|
+
// Observation-based wait: poll inspectUI to confirm window appeared
|
|
582
|
+
const delays = [400, 800, 1200];
|
|
583
|
+
for (const delay of delays) {
|
|
584
|
+
await this.wait(delay);
|
|
585
|
+
const ui = await this.inspectUI();
|
|
586
|
+
const match = ui.windows.some(w => w.toLowerCase().includes(appName.toLowerCase()));
|
|
587
|
+
if (match || (ui.activeWindow && ui.activeWindow.toLowerCase().includes(appName.toLowerCase()))) {
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (this.expectedApplication) {
|
|
592
|
+
await this.switchWindow(this.expectedApplication);
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
success: true,
|
|
596
|
+
output: `Launched and focused application: "${this.expectedApplication || appName}"`,
|
|
597
|
+
action: 'open_app',
|
|
598
|
+
activeWindow: this.expectedApplication || appName,
|
|
599
|
+
expectedWindow: this.expectedApplication || appName,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
else if (process.platform === 'darwin') {
|
|
603
|
+
await execAsync(`open -a "${appName}" ${argsStr}`);
|
|
604
|
+
this.expectedApplication = appName;
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
await execAsync(`xdg-open "${appName}" ${argsStr} &`);
|
|
608
|
+
this.expectedApplication = appName;
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
success: true,
|
|
612
|
+
output: `Launched application: "${appName}"`,
|
|
613
|
+
action: 'open_app',
|
|
614
|
+
activeWindow: appName,
|
|
615
|
+
expectedWindow: appName,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
catch (err) {
|
|
619
|
+
return {
|
|
620
|
+
success: false,
|
|
621
|
+
output: `Failed to launch "${appName}": ${err?.message || String(err)}`,
|
|
622
|
+
action: 'open_app',
|
|
623
|
+
error: err?.message || String(err),
|
|
624
|
+
recoverable: true,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
268
627
|
}
|
|
269
|
-
async
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
628
|
+
async closeApplication(appName) {
|
|
629
|
+
try {
|
|
630
|
+
if (process.platform === 'win32') {
|
|
631
|
+
const cleanName = appName.replace(/\.exe$/i, '');
|
|
632
|
+
await execAsync(`powershell -NoProfile -Command "Stop-Process -Name '${cleanName}' -Force -ErrorAction SilentlyContinue"`);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
await execAsync(`pkill -f "${appName}"`);
|
|
636
|
+
}
|
|
637
|
+
if (this.expectedApplication && this.expectedApplication.toLowerCase().includes(appName.toLowerCase())) {
|
|
638
|
+
this.expectedApplication = null;
|
|
639
|
+
}
|
|
640
|
+
return { success: true, output: `Closed application: "${appName}"`, action: 'close_app' };
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
return {
|
|
644
|
+
success: false,
|
|
645
|
+
output: `Failed to close "${appName}": ${err?.message || String(err)}`,
|
|
646
|
+
action: 'close_app',
|
|
647
|
+
error: err?.message || String(err),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
278
650
|
}
|
|
279
|
-
async
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
success
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
651
|
+
async switchWindow(windowTitleOrApp) {
|
|
652
|
+
if (process.platform === 'win32') {
|
|
653
|
+
const res = await this.runDriverAction('focus_window', [windowTitleOrApp]);
|
|
654
|
+
if (res && res.success) {
|
|
655
|
+
this.activeWindow = res.activeWindow || windowTitleOrApp;
|
|
656
|
+
this.expectedApplication = windowTitleOrApp;
|
|
657
|
+
return {
|
|
658
|
+
success: true,
|
|
659
|
+
output: `Focused window matching: "${windowTitleOrApp}"`,
|
|
660
|
+
action: 'switch_window',
|
|
661
|
+
activeWindow: this.activeWindow || undefined,
|
|
662
|
+
expectedWindow: windowTitleOrApp,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
// Fallback to WScript.Shell AppActivate
|
|
666
|
+
try {
|
|
667
|
+
const script = `
|
|
668
|
+
$wshell = New-Object -ComObject Wscript.Shell
|
|
669
|
+
$result = $wshell.AppActivate("${windowTitleOrApp.replace(/"/g, '`"')}")
|
|
670
|
+
if (-not $result) {
|
|
671
|
+
$p = Get-Process | Where-Object { $_.MainWindowTitle -like "*${windowTitleOrApp.replace(/"/g, '`"')}*" -or $_.ProcessName -like "*${windowTitleOrApp.replace(/"/g, '`"')}*" } | Select-Object -First 1
|
|
672
|
+
if ($p) {
|
|
673
|
+
$null = $wshell.AppActivate($p.Id)
|
|
674
|
+
$result = $true
|
|
286
675
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
676
|
+
}
|
|
677
|
+
if ($result) { Write-Output "OK" } else { Write-Output "NOT_FOUND" }
|
|
678
|
+
`;
|
|
679
|
+
const encoded = Buffer.from(script, 'utf16le').toString('base64');
|
|
680
|
+
const { stdout } = await execAsync(`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`);
|
|
681
|
+
if (stdout.includes('OK')) {
|
|
682
|
+
this.activeWindow = windowTitleOrApp;
|
|
683
|
+
this.expectedApplication = windowTitleOrApp;
|
|
684
|
+
return {
|
|
685
|
+
success: true,
|
|
686
|
+
output: `Focused window matching: "${windowTitleOrApp}"`,
|
|
687
|
+
action: 'switch_window',
|
|
688
|
+
activeWindow: windowTitleOrApp,
|
|
689
|
+
expectedWindow: windowTitleOrApp,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch { }
|
|
694
|
+
return {
|
|
695
|
+
success: false,
|
|
696
|
+
output: `Could not find or focus window: "${windowTitleOrApp}". Try computer_inspect_ui to list open windows.`,
|
|
697
|
+
action: 'switch_window',
|
|
698
|
+
recoverable: true,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
this.activeWindow = windowTitleOrApp;
|
|
702
|
+
return { success: true, output: `Switched window to: "${windowTitleOrApp}"`, action: 'switch_window' };
|
|
703
|
+
}
|
|
704
|
+
async getWindowBounds(windowTitleOrApp) {
|
|
705
|
+
if (process.platform === 'win32') {
|
|
706
|
+
const res = await this.runDriverAction('window_bounds', [windowTitleOrApp]);
|
|
707
|
+
if (res && res.success && typeof res.x === 'number') {
|
|
708
|
+
return { x: res.x, y: res.y, width: res.width, height: res.height };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
async inspectUI() {
|
|
714
|
+
if (process.platform === 'win32') {
|
|
715
|
+
const res = await this.runDriverAction('list_windows', []);
|
|
716
|
+
if (res && res.success && Array.isArray(res.windows)) {
|
|
717
|
+
return {
|
|
718
|
+
activeWindow: res.activeWindow || 'Desktop',
|
|
719
|
+
windows: res.windows,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
const { stdout } = await execAsync(`powershell -NoProfile -NonInteractive -Command "Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle.Trim().Length -gt 0 } | Select-Object -ExpandProperty MainWindowTitle"`);
|
|
724
|
+
const titles = stdout
|
|
725
|
+
.split('\n')
|
|
726
|
+
.map((t) => t.trim())
|
|
727
|
+
.filter((t) => t.length > 0);
|
|
728
|
+
const active = titles.length > 0 ? titles[0] : 'Desktop';
|
|
729
|
+
return {
|
|
730
|
+
activeWindow: active,
|
|
731
|
+
windows: titles,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
return { activeWindow: 'Desktop', windows: ['Desktop'] };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return { activeWindow: 'Desktop', windows: ['Desktop', 'Terminal'] };
|
|
293
739
|
}
|
|
294
740
|
}
|
|
295
741
|
//# sourceMappingURL=computerController.js.map
|