ft-scout 9.0.1 → 9.0.3
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 +93 -13
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +46 -3
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/engine/actionVerifier.d.ts.map +1 -0
- package/bin/src/engine/actionVerifier.js +223 -0
- package/bin/src/engine/actionVerifier.js.map +1 -0
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +237 -1
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/agentState.d.ts.map +1 -0
- package/bin/src/engine/agentState.js +445 -0
- package/bin/src/engine/agentState.js.map +1 -0
- package/bin/src/engine/appControl.d.ts.map +1 -1
- package/bin/src/engine/appControl.js +165 -59
- package/bin/src/engine/appControl.js.map +1 -1
- package/bin/src/engine/browser/browserController.d.ts.map +1 -0
- package/bin/src/engine/browser/browserController.js +689 -0
- package/bin/src/engine/browser/browserController.js.map +1 -0
- package/bin/src/engine/browser/index.d.ts.map +1 -0
- package/bin/src/engine/browser/index.js +2 -0
- package/bin/src/engine/browser/index.js.map +1 -0
- package/bin/src/engine/computer/computerController.d.ts.map +1 -0
- package/bin/src/engine/computer/computerController.js +295 -0
- package/bin/src/engine/computer/computerController.js.map +1 -0
- package/bin/src/engine/computer/index.d.ts.map +1 -0
- package/bin/src/engine/computer/index.js +2 -0
- package/bin/src/engine/computer/index.js.map +1 -0
- package/bin/src/engine/liveScreenEngine.js +2 -2
- package/bin/src/engine/liveScreenEngine.js.map +1 -1
- package/bin/src/engine/planner.d.ts.map +1 -0
- package/bin/src/engine/planner.js +242 -0
- package/bin/src/engine/planner.js.map +1 -0
- package/bin/src/engine/safety.d.ts.map +1 -0
- package/bin/src/engine/safety.js +281 -0
- package/bin/src/engine/safety.js.map +1 -0
- package/bin/src/engine/toolRegistry.d.ts.map +1 -0
- package/bin/src/engine/toolRegistry.js +218 -0
- package/bin/src/engine/toolRegistry.js.map +1 -0
- package/bin/src/index.js +6 -4
- package/bin/src/index.js.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +116 -0
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/downloads/INVOICE_2026_ACME.pdf +1 -0
- package/package.json +1 -1
- package/web/app.js +277 -46
- package/web/styles.css +79 -2
|
@@ -62,7 +62,7 @@ export function buildSocialUrl(platformOrAppName, target, text) {
|
|
|
62
62
|
const isLinkedIn = pLower.includes('linkedin') || cleanTarget.includes('linkedin.com');
|
|
63
63
|
if (isInstagram) {
|
|
64
64
|
if (handle && handle !== 'instagram' && !cleanTarget.includes('http')) {
|
|
65
|
-
return `https://www.instagram.com
|
|
65
|
+
return `https://www.instagram.com/${handle}/`;
|
|
66
66
|
}
|
|
67
67
|
return `https://www.instagram.com/direct/inbox/`;
|
|
68
68
|
}
|
|
@@ -653,9 +653,23 @@ export async function executeInApp(appName, action, payload) {
|
|
|
653
653
|
if (!msgText && typeof payload === 'string' && payload.length > 0 && !payload.startsWith('@') && !payload.startsWith('http') && !payload.includes('@')) {
|
|
654
654
|
msgText = payload;
|
|
655
655
|
}
|
|
656
|
-
const
|
|
657
|
-
const
|
|
658
|
-
|
|
656
|
+
const cleanRecipient = String(recipient || '').trim();
|
|
657
|
+
const cleanMsg = String(msgText || '').trim();
|
|
658
|
+
if (!cleanRecipient && !cleanMsg) {
|
|
659
|
+
const targetPlatform = name || 'browser';
|
|
660
|
+
return {
|
|
661
|
+
success: false,
|
|
662
|
+
app: targetPlatform,
|
|
663
|
+
action: actLower,
|
|
664
|
+
output: `Missing recipient username/handle and message text for ${targetPlatform}. You MUST call the ask_user tool to ask: "Who would you like to message on ${targetPlatform}, and what message should I send?" before completing the task.`,
|
|
665
|
+
details: { platform: targetPlatform, recipient: '', msgText: '' },
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
const isEmail = ['send_mail', 'send_email', 'mail', 'email'].includes(actLower) || cleanRecipient.includes('@');
|
|
669
|
+
const targetUrl = buildSocialUrl(isEmail ? 'gmail' : name, cleanRecipient, cleanMsg);
|
|
670
|
+
await openApp('browser', targetUrl);
|
|
671
|
+
// Display aura HUD directly over the browser window
|
|
672
|
+
showScreenAuraOverlay(4500, `Scout is messaging on ${isEmail ? 'Gmail' : (name || 'browser')}...`, false, 'browser').catch(() => { });
|
|
659
673
|
let keystrokeOutput = '';
|
|
660
674
|
if (isEmail) {
|
|
661
675
|
// For Gmail direct compose: wait 2.5s for page & compose dialog to render, then press Ctrl+Enter to send!
|
|
@@ -664,19 +678,38 @@ export async function executeInApp(appName, action, payload) {
|
|
|
664
678
|
const ksRes = await sendKeystrokes('browser', '', false, 'ctrl+enter');
|
|
665
679
|
keystrokeOutput = `\nAuto-Send Keystroke Dispatch (Ctrl+Enter): ${ksRes.output}`;
|
|
666
680
|
}
|
|
667
|
-
else if (
|
|
681
|
+
else if (cleanRecipient && (name.toLowerCase().includes('instagram') || targetUrl.includes('instagram.com'))) {
|
|
682
|
+
// Real Instagram Web UI Automation:
|
|
683
|
+
// 1. Wait for profile / direct page to fully render
|
|
684
|
+
await new Promise((r) => setTimeout(r, 3500));
|
|
685
|
+
// 2. Focus browser window
|
|
686
|
+
await focusWindow('chrome');
|
|
687
|
+
await focusWindow('browser');
|
|
688
|
+
// 3. If message text is provided, click "Message" on profile and type text
|
|
689
|
+
if (cleanMsg) {
|
|
690
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
691
|
+
await findAndClickElement('Message', 'browser');
|
|
692
|
+
await new Promise((r) => setTimeout(r, 2200));
|
|
693
|
+
const ksRes = await sendKeystrokes('browser', cleanMsg, true);
|
|
694
|
+
keystrokeOutput = `\nInstagram Message Dispatched to ${cleanRecipient}: ${ksRes.output}`;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
keystrokeOutput = `\nNavigated to Instagram profile for ${cleanRecipient}. Message text was not specified. Call ask_user to get message content.`;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else if (cleanMsg) {
|
|
668
701
|
// Lock user interruption during scratchpad keystroke dispatch so user clicks don't interrupt
|
|
669
702
|
await lockAppInput(name || 'browser', 3000);
|
|
670
|
-
await new Promise((r) => setTimeout(r,
|
|
671
|
-
const ksRes = await sendKeystrokes(name || 'browser',
|
|
703
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
704
|
+
const ksRes = await sendKeystrokes(name || 'browser', cleanMsg, true);
|
|
672
705
|
keystrokeOutput = `\nAuto-Keystroke Automated Dispatch: ${ksRes.output}`;
|
|
673
706
|
}
|
|
674
707
|
return {
|
|
675
708
|
success: true,
|
|
676
709
|
app: name,
|
|
677
710
|
action: actLower,
|
|
678
|
-
output: `Executed ${isEmail ? 'Gmail / Email Dispatch' : `${name} Direct Message`} for target recipient "${
|
|
679
|
-
details: { platform: isEmail ? 'gmail' : name, recipient, msgText, targetUrl },
|
|
711
|
+
output: `Executed ${isEmail ? 'Gmail / Email Dispatch' : `${name} Direct Message`} for target recipient "${cleanRecipient}" at ${targetUrl}.${keystrokeOutput}`,
|
|
712
|
+
details: { platform: isEmail ? 'gmail' : name, recipient: cleanRecipient, msgText: cleanMsg, targetUrl },
|
|
680
713
|
};
|
|
681
714
|
}
|
|
682
715
|
}
|
|
@@ -1069,13 +1102,87 @@ export async function getScreenInfo(appName) {
|
|
|
1069
1102
|
};
|
|
1070
1103
|
}
|
|
1071
1104
|
}
|
|
1105
|
+
function escapeXaml(text) {
|
|
1106
|
+
return text
|
|
1107
|
+
.replace(/&/g, '&')
|
|
1108
|
+
.replace(/</g, '<')
|
|
1109
|
+
.replace(/>/g, '>')
|
|
1110
|
+
.replace(/"/g, '"')
|
|
1111
|
+
.replace(/'/g, ''')
|
|
1112
|
+
.replace(/•/g, '•');
|
|
1113
|
+
}
|
|
1072
1114
|
let persistentAuraProcess = null;
|
|
1073
1115
|
let persistentAuraFlagPath = null;
|
|
1116
|
+
function buildTargetWindowRectSnippet(targetApp) {
|
|
1117
|
+
const clean = (targetApp || '').trim().replace(/["'`$\\]/g, '');
|
|
1118
|
+
if (!clean || clean.toLowerCase() === 'desktop' || clean.toLowerCase() === 'system') {
|
|
1119
|
+
return `
|
|
1120
|
+
$window.Left = [System.Windows.SystemParameters]::VirtualScreenLeft
|
|
1121
|
+
$window.Top = [System.Windows.SystemParameters]::VirtualScreenTop
|
|
1122
|
+
$window.Width = [System.Windows.SystemParameters]::VirtualScreenWidth
|
|
1123
|
+
$window.Height = [System.Windows.SystemParameters]::VirtualScreenHeight
|
|
1124
|
+
`;
|
|
1125
|
+
}
|
|
1126
|
+
return `
|
|
1127
|
+
$targetApp = "${clean}"
|
|
1128
|
+
$winLeft = [System.Windows.SystemParameters]::VirtualScreenLeft
|
|
1129
|
+
$winTop = [System.Windows.SystemParameters]::VirtualScreenTop
|
|
1130
|
+
$winWidth = [System.Windows.SystemParameters]::VirtualScreenWidth
|
|
1131
|
+
$winHeight = [System.Windows.SystemParameters]::VirtualScreenHeight
|
|
1132
|
+
|
|
1133
|
+
$procNames = @($targetApp)
|
|
1134
|
+
if ($targetApp -match "browser|chrome|edge|web") {
|
|
1135
|
+
$procNames = @("chrome", "msedge", "brave", "firefox")
|
|
1136
|
+
} elseif ($targetApp -match "editor|code|vscode") {
|
|
1137
|
+
$procNames = @("Code", "cursor", "notepad")
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
$proc = Get-Process -Name $procNames -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
1141
|
+
if (-not $proc) {
|
|
1142
|
+
$proc = Get-Process | Where-Object { ($_.MainWindowTitle -like "*$targetApp*" -or $_.ProcessName -like "*$targetApp*") -and $_.MainWindowHandle -ne 0 } | Select-Object -First 1
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if ($proc) {
|
|
1146
|
+
try {
|
|
1147
|
+
$sigRect = '[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); public struct RECT { public int Left; public int Top; public int Right; public int Bottom; }'
|
|
1148
|
+
$wType = Add-Type -MemberDefinition $sigRect -Name ('WinRect_' + [Guid]::NewGuid().ToString('N').Substring(0,8)) -Namespace 'Win32' -PassThru
|
|
1149
|
+
$rc = [Activator]::CreateInstance($wType.GetNestedType('RECT'))
|
|
1150
|
+
[void]$wType::GetWindowRect($proc.MainWindowHandle, [ref]$rc)
|
|
1151
|
+
$calcW = $rc.Right - $rc.Left
|
|
1152
|
+
$calcH = $rc.Bottom - $rc.Top
|
|
1153
|
+
if ($calcW -gt 120 -and $calcH -gt 120) {
|
|
1154
|
+
$winLeft = [double]$rc.Left
|
|
1155
|
+
$winTop = [double]$rc.Top
|
|
1156
|
+
$winWidth = [double]$calcW
|
|
1157
|
+
$winHeight = [double]$calcH
|
|
1158
|
+
}
|
|
1159
|
+
} catch {
|
|
1160
|
+
try {
|
|
1161
|
+
Add-Type -AssemblyName UIAutomationClient
|
|
1162
|
+
$el = [System.Windows.Automation.AutomationElement]::FromHandle($proc.MainWindowHandle)
|
|
1163
|
+
$b = $el.Current.BoundingRectangle
|
|
1164
|
+
if ($b.Width -gt 120 -and $b.Height -gt 120) {
|
|
1165
|
+
$winLeft = [double]$b.X
|
|
1166
|
+
$winTop = [double]$b.Y
|
|
1167
|
+
$winWidth = [double]$b.Width
|
|
1168
|
+
$winHeight = [double]$b.Height
|
|
1169
|
+
}
|
|
1170
|
+
} catch {}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
$window.Left = $winLeft
|
|
1175
|
+
$window.Top = $winTop
|
|
1176
|
+
$window.Width = $winWidth
|
|
1177
|
+
$window.Height = $winHeight
|
|
1178
|
+
`;
|
|
1179
|
+
}
|
|
1074
1180
|
/**
|
|
1075
1181
|
* Start persistent futuristic sky-blue aura overlay on screen borders and corners.
|
|
1076
1182
|
* Stays continuously active and animating as long as live screen sharing is running.
|
|
1183
|
+
* When targetApp is specified, bounds strictly to that application window.
|
|
1077
1184
|
*/
|
|
1078
|
-
export async function startScreenAuraOverlay(message = "Live Screen Share Active • Streaming to Model") {
|
|
1185
|
+
export async function startScreenAuraOverlay(message = "Live Screen Share Active • Streaming to Model", targetApp) {
|
|
1079
1186
|
const platform = process.platform;
|
|
1080
1187
|
if (platform !== 'win32')
|
|
1081
1188
|
return;
|
|
@@ -1090,7 +1197,9 @@ export async function startScreenAuraOverlay(message = "Live Screen Share Active
|
|
|
1090
1197
|
const flagFile = path.join(scriptsDir, 'aura_active.flag');
|
|
1091
1198
|
fs.writeFileSync(flagFile, String(Date.now()), 'utf8');
|
|
1092
1199
|
persistentAuraFlagPath = flagFile;
|
|
1093
|
-
const subtitleText = " Perception Active";
|
|
1200
|
+
const subtitleText = targetApp ? ` • Focused: ${targetApp}` : " Perception Active";
|
|
1201
|
+
const safeMessage = escapeXaml(message);
|
|
1202
|
+
const safeSubtitle = escapeXaml(subtitleText);
|
|
1094
1203
|
const parentPid = process.pid;
|
|
1095
1204
|
const overlayPs = `
|
|
1096
1205
|
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
|
|
@@ -1138,55 +1247,55 @@ Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
|
|
|
1138
1247
|
|
|
1139
1248
|
<!-- Sky Blue Corner Brackets with Aura Glow -->
|
|
1140
1249
|
<!-- Top Left Corner -->
|
|
1141
|
-
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Width="
|
|
1142
|
-
<Path x:Name="CornerTL" Data="M 0,
|
|
1250
|
+
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Height="100" Margin="4,4,0,0">
|
|
1251
|
+
<Path x:Name="CornerTL" Data="M 0,70 L 0,0 L 70,0" Stroke="#00e5ff" StrokeThickness="5">
|
|
1143
1252
|
<Path.Effect>
|
|
1144
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1253
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1145
1254
|
</Path.Effect>
|
|
1146
1255
|
</Path>
|
|
1147
1256
|
</Canvas>
|
|
1148
1257
|
|
|
1149
1258
|
<!-- Top Right Corner -->
|
|
1150
|
-
<Canvas HorizontalAlignment="Right" VerticalAlignment="Top" Width="
|
|
1151
|
-
<Path x:Name="CornerTR" Data="M 30,0 L
|
|
1259
|
+
<Canvas HorizontalAlignment="Right" VerticalAlignment="Top" Width="100" Height="100" Margin="0,4,4,0">
|
|
1260
|
+
<Path x:Name="CornerTR" Data="M 30,0 L 100,0 L 100,70" Stroke="#00e5ff" StrokeThickness="5">
|
|
1152
1261
|
<Path.Effect>
|
|
1153
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1262
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1154
1263
|
</Path.Effect>
|
|
1155
1264
|
</Path>
|
|
1156
1265
|
</Canvas>
|
|
1157
1266
|
|
|
1158
1267
|
<!-- Bottom Left Corner -->
|
|
1159
|
-
<Canvas HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="
|
|
1160
|
-
<Path x:Name="CornerBL" Data="M 0,30 L 0,
|
|
1268
|
+
<Canvas HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" Height="100" Margin="4,0,0,4">
|
|
1269
|
+
<Path x:Name="CornerBL" Data="M 0,30 L 0,100 L 70,100" Stroke="#00e5ff" StrokeThickness="5">
|
|
1161
1270
|
<Path.Effect>
|
|
1162
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1271
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1163
1272
|
</Path.Effect>
|
|
1164
1273
|
</Path>
|
|
1165
1274
|
</Canvas>
|
|
1166
1275
|
|
|
1167
1276
|
<!-- Bottom Right Corner -->
|
|
1168
|
-
<Canvas HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="
|
|
1169
|
-
<Path x:Name="CornerBR" Data="M 30,
|
|
1277
|
+
<Canvas HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="100" Height="100" Margin="0,0,4,4">
|
|
1278
|
+
<Path x:Name="CornerBR" Data="M 30,100 L 100,100 L 100,30" Stroke="#00e5ff" StrokeThickness="5">
|
|
1170
1279
|
<Path.Effect>
|
|
1171
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1280
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1172
1281
|
</Path.Effect>
|
|
1173
1282
|
</Path>
|
|
1174
1283
|
</Canvas>
|
|
1175
1284
|
|
|
1176
1285
|
<!-- Top Floating Status Badge -->
|
|
1177
|
-
<Border HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,
|
|
1178
|
-
Background="#EE03121F" BorderBrush="#00e5ff" BorderThickness="1.8" CornerRadius="24" Padding="
|
|
1286
|
+
<Border HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,16,0,0"
|
|
1287
|
+
Background="#EE03121F" BorderBrush="#00e5ff" BorderThickness="1.8" CornerRadius="24" Padding="22,8">
|
|
1179
1288
|
<Border.Effect>
|
|
1180
1289
|
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="0.95"/>
|
|
1181
1290
|
</Border.Effect>
|
|
1182
1291
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
|
1183
|
-
<Ellipse Width="
|
|
1292
|
+
<Ellipse Width="10" Height="10" Fill="#00e5ff" Margin="0,0,10,0">
|
|
1184
1293
|
<Ellipse.Effect>
|
|
1185
1294
|
<DropShadowEffect Color="#00e5ff" BlurRadius="12" ShadowDepth="0"/>
|
|
1186
1295
|
</Ellipse.Effect>
|
|
1187
1296
|
</Ellipse>
|
|
1188
|
-
<TextBlock Text="${
|
|
1189
|
-
<TextBlock Text="${
|
|
1297
|
+
<TextBlock Text="${safeMessage}" Foreground="#ffffff" FontSize="15" FontWeight="Bold" VerticalAlignment="Center"/>
|
|
1298
|
+
<TextBlock Text="${safeSubtitle}" Foreground="#38bdf8" FontSize="13" FontWeight="SemiBold" Margin="8,0,0,0" VerticalAlignment="Center"/>
|
|
1190
1299
|
</StackPanel>
|
|
1191
1300
|
</Border>
|
|
1192
1301
|
</Grid>
|
|
@@ -1196,10 +1305,7 @@ Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
|
|
|
1196
1305
|
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
|
|
1197
1306
|
$window = [System.Windows.Markup.XamlReader]::Load($reader)
|
|
1198
1307
|
|
|
1199
|
-
$
|
|
1200
|
-
$window.Top = [System.Windows.SystemParameters]::VirtualScreenTop
|
|
1201
|
-
$window.Width = [System.Windows.SystemParameters]::VirtualScreenWidth
|
|
1202
|
-
$window.Height = [System.Windows.SystemParameters]::VirtualScreenHeight
|
|
1308
|
+
${buildTargetWindowRectSnippet(targetApp)}
|
|
1203
1309
|
$window.Topmost = $true
|
|
1204
1310
|
$window.ShowActivated = $false
|
|
1205
1311
|
|
|
@@ -1233,7 +1339,7 @@ $timer.Start()
|
|
|
1233
1339
|
|
|
1234
1340
|
[void]$window.ShowDialog()
|
|
1235
1341
|
`;
|
|
1236
|
-
fs.writeFileSync(psFile, overlayPs, 'utf8');
|
|
1342
|
+
fs.writeFileSync(psFile, '\uFEFF' + overlayPs, 'utf8');
|
|
1237
1343
|
const child = spawn('powershell', ['-WindowStyle', 'Hidden', '-NoProfile', '-Sta', '-ExecutionPolicy', 'Bypass', '-File', psFile], {
|
|
1238
1344
|
detached: false,
|
|
1239
1345
|
stdio: 'ignore',
|
|
@@ -1265,11 +1371,12 @@ export function stopScreenAuraOverlay() {
|
|
|
1265
1371
|
/**
|
|
1266
1372
|
* Display a futuristic sky-blue aura animation on screen borders and corners,
|
|
1267
1373
|
* show the warning "Scout is on the screen.", and optionally block external inputs.
|
|
1374
|
+
* When targetApp is specified, bounds strictly to that application window.
|
|
1268
1375
|
* If durationMs <= 0, aura stays on continuously until stopScreenAuraOverlay() is called.
|
|
1269
1376
|
*/
|
|
1270
|
-
export async function showScreenAuraOverlay(durationMs = 2200, message = "Scout is viewing the screen.", blockInput = false) {
|
|
1377
|
+
export async function showScreenAuraOverlay(durationMs = 2200, message = "Scout is viewing the screen.", blockInput = false, targetApp) {
|
|
1271
1378
|
if (durationMs <= 0) {
|
|
1272
|
-
return await startScreenAuraOverlay(message);
|
|
1379
|
+
return await startScreenAuraOverlay(message, targetApp);
|
|
1273
1380
|
}
|
|
1274
1381
|
const platform = process.platform;
|
|
1275
1382
|
if (platform !== 'win32')
|
|
@@ -1288,7 +1395,9 @@ try { [void]$b::BlockInput($true) } catch {}
|
|
|
1288
1395
|
const unblockSnippet = blockInput ? `
|
|
1289
1396
|
try { [void]$b::BlockInput($false) } catch {}
|
|
1290
1397
|
` : '';
|
|
1291
|
-
const subtitleText = blockInput ? " • External Input Locked" : " Perception Active";
|
|
1398
|
+
const subtitleText = blockInput ? (targetApp ? ` • Controlling ${targetApp} (Input Locked)` : " • External Input Locked") : (targetApp ? ` • Focused: ${targetApp}` : " Perception Active");
|
|
1399
|
+
const safeMessage = escapeXaml(message);
|
|
1400
|
+
const safeSubtitle = escapeXaml(subtitleText);
|
|
1292
1401
|
const overlayPs = `
|
|
1293
1402
|
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
|
|
1294
1403
|
${blockInputSnippet}
|
|
@@ -1336,55 +1445,55 @@ ${blockInputSnippet}
|
|
|
1336
1445
|
|
|
1337
1446
|
<!-- Sky Blue Corner Brackets with Aura Glow -->
|
|
1338
1447
|
<!-- Top Left Corner -->
|
|
1339
|
-
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Width="
|
|
1340
|
-
<Path x:Name="CornerTL" Data="M 0,
|
|
1448
|
+
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Height="100" Margin="4,4,0,0">
|
|
1449
|
+
<Path x:Name="CornerTL" Data="M 0,70 L 0,0 L 70,0" Stroke="#00e5ff" StrokeThickness="5">
|
|
1341
1450
|
<Path.Effect>
|
|
1342
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1451
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1343
1452
|
</Path.Effect>
|
|
1344
1453
|
</Path>
|
|
1345
1454
|
</Canvas>
|
|
1346
1455
|
|
|
1347
1456
|
<!-- Top Right Corner -->
|
|
1348
|
-
<Canvas HorizontalAlignment="Right" VerticalAlignment="Top" Width="
|
|
1349
|
-
<Path x:Name="CornerTR" Data="M 30,0 L
|
|
1457
|
+
<Canvas HorizontalAlignment="Right" VerticalAlignment="Top" Width="100" Height="100" Margin="0,4,4,0">
|
|
1458
|
+
<Path x:Name="CornerTR" Data="M 30,0 L 100,0 L 100,70" Stroke="#00e5ff" StrokeThickness="5">
|
|
1350
1459
|
<Path.Effect>
|
|
1351
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1460
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1352
1461
|
</Path.Effect>
|
|
1353
1462
|
</Path>
|
|
1354
1463
|
</Canvas>
|
|
1355
1464
|
|
|
1356
1465
|
<!-- Bottom Left Corner -->
|
|
1357
|
-
<Canvas HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="
|
|
1358
|
-
<Path x:Name="CornerBL" Data="M 0,30 L 0,
|
|
1466
|
+
<Canvas HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" Height="100" Margin="4,0,0,4">
|
|
1467
|
+
<Path x:Name="CornerBL" Data="M 0,30 L 0,100 L 70,100" Stroke="#00e5ff" StrokeThickness="5">
|
|
1359
1468
|
<Path.Effect>
|
|
1360
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1469
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1361
1470
|
</Path.Effect>
|
|
1362
1471
|
</Path>
|
|
1363
1472
|
</Canvas>
|
|
1364
1473
|
|
|
1365
1474
|
<!-- Bottom Right Corner -->
|
|
1366
|
-
<Canvas HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="
|
|
1367
|
-
<Path x:Name="CornerBR" Data="M 30,
|
|
1475
|
+
<Canvas HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="100" Height="100" Margin="0,0,4,4">
|
|
1476
|
+
<Path x:Name="CornerBR" Data="M 30,100 L 100,100 L 100,30" Stroke="#00e5ff" StrokeThickness="5">
|
|
1368
1477
|
<Path.Effect>
|
|
1369
|
-
<DropShadowEffect Color="#00e5ff" BlurRadius="
|
|
1478
|
+
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="1"/>
|
|
1370
1479
|
</Path.Effect>
|
|
1371
1480
|
</Path>
|
|
1372
1481
|
</Canvas>
|
|
1373
1482
|
|
|
1374
1483
|
<!-- Top Floating Status Badge -->
|
|
1375
|
-
<Border HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,
|
|
1376
|
-
Background="#EE03121F" BorderBrush="#00e5ff" BorderThickness="1.8" CornerRadius="24" Padding="
|
|
1484
|
+
<Border HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,16,0,0"
|
|
1485
|
+
Background="#EE03121F" BorderBrush="#00e5ff" BorderThickness="1.8" CornerRadius="24" Padding="22,8">
|
|
1377
1486
|
<Border.Effect>
|
|
1378
1487
|
<DropShadowEffect Color="#00e5ff" BlurRadius="25" ShadowDepth="0" Opacity="0.95"/>
|
|
1379
1488
|
</Border.Effect>
|
|
1380
1489
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
|
1381
|
-
<Ellipse Width="
|
|
1490
|
+
<Ellipse Width="10" Height="10" Fill="#00e5ff" Margin="0,0,10,0">
|
|
1382
1491
|
<Ellipse.Effect>
|
|
1383
1492
|
<DropShadowEffect Color="#00e5ff" BlurRadius="12" ShadowDepth="0"/>
|
|
1384
1493
|
</Ellipse.Effect>
|
|
1385
1494
|
</Ellipse>
|
|
1386
|
-
<TextBlock Text="${
|
|
1387
|
-
<TextBlock Text="${
|
|
1495
|
+
<TextBlock Text="${safeMessage}" Foreground="#ffffff" FontSize="15" FontWeight="Bold" VerticalAlignment="Center"/>
|
|
1496
|
+
<TextBlock Text="${safeSubtitle}" Foreground="#38bdf8" FontSize="13" FontWeight="SemiBold" Margin="8,0,0,0" VerticalAlignment="Center"/>
|
|
1388
1497
|
</StackPanel>
|
|
1389
1498
|
</Border>
|
|
1390
1499
|
</Grid>
|
|
@@ -1394,10 +1503,7 @@ ${blockInputSnippet}
|
|
|
1394
1503
|
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
|
|
1395
1504
|
$window = [System.Windows.Markup.XamlReader]::Load($reader)
|
|
1396
1505
|
|
|
1397
|
-
$
|
|
1398
|
-
$window.Top = [System.Windows.SystemParameters]::VirtualScreenTop
|
|
1399
|
-
$window.Width = [System.Windows.SystemParameters]::VirtualScreenWidth
|
|
1400
|
-
$window.Height = [System.Windows.SystemParameters]::VirtualScreenHeight
|
|
1506
|
+
${buildTargetWindowRectSnippet(targetApp)}
|
|
1401
1507
|
$window.Topmost = $true
|
|
1402
1508
|
$window.ShowActivated = $false
|
|
1403
1509
|
|
|
@@ -1419,7 +1525,7 @@ $timer.Start()
|
|
|
1419
1525
|
[void]$window.ShowDialog()
|
|
1420
1526
|
${unblockSnippet}
|
|
1421
1527
|
`;
|
|
1422
|
-
fs.writeFileSync(psFile, overlayPs, 'utf8');
|
|
1528
|
+
fs.writeFileSync(psFile, '\uFEFF' + overlayPs, 'utf8');
|
|
1423
1529
|
await execAsync(`powershell -WindowStyle Hidden -NoProfile -Sta -ExecutionPolicy Bypass -File "${psFile}"`, { timeout: durationMs + 3000 });
|
|
1424
1530
|
}
|
|
1425
1531
|
catch {
|
|
@@ -1702,7 +1808,7 @@ export async function takeoverControl(appName, durationMs = 3000, x, y) {
|
|
|
1702
1808
|
// Trigger visual sky-blue aura overlay on screen borders & corners with warning badge & input block
|
|
1703
1809
|
try {
|
|
1704
1810
|
const auraLabel = targetApp ? `Scout is controlling ${targetApp}...` : "Scout is on the screen.";
|
|
1705
|
-
await showScreenAuraOverlay(Math.min(5000, Math.max(800, durationMs)), auraLabel, true);
|
|
1811
|
+
await showScreenAuraOverlay(Math.min(5000, Math.max(800, durationMs)), auraLabel, true, targetApp);
|
|
1706
1812
|
startLiveScreenShare({ targetApp }).catch(() => { });
|
|
1707
1813
|
}
|
|
1708
1814
|
catch { }
|