@remcp/runtime 0.2.149 → 0.2.151

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.
@@ -1,3 +1,5 @@
1
+ import { macWindowTargets, MAC_WINDOW_TARGET_TTL_MS, rememberMacWindowTarget, macWindowId, normalizeMacWindowTarget } from './desktop-macos-windows.mjs';
2
+ export { macWindowId, normalizeMacWindowTarget };
1
3
  import path from 'node:path';
2
4
 
3
5
  import { image, multi, text } from '../util.mjs';
@@ -23,72 +25,8 @@ function parseTsv(value, fields) {
23
25
  });
24
26
  }
25
27
 
26
- const macWindowTargets = new Map();
27
- const MAC_WINDOW_TARGET_TTL_MS = 5 * 60 * 1000;
28
28
  let macWindowGeneration = 0;
29
29
 
30
- function rememberMacWindowTarget(row) {
31
- macWindowTargets.set(row.id, {
32
- pid:row.pid,
33
- app:row.app,
34
- title:row.title,
35
- x:row.x,
36
- y:row.y,
37
- width:row.width,
38
- height:row.height,
39
- at:Date.now(),
40
- });
41
- }
42
-
43
- function cachedMacWindowTarget(id) {
44
- const cached = macWindowTargets.get(id);
45
- if (!cached) return null;
46
- if ((Date.now() - cached.at) > MAC_WINDOW_TARGET_TTL_MS) {
47
- macWindowTargets.delete(id);
48
- return null;
49
- }
50
- return cached;
51
- }
52
-
53
- export function macWindowId(pid, windowIndex, generation) {
54
- const numericPid = Number(pid);
55
- const numericIndex = Number(windowIndex);
56
- const numericGeneration = Number(generation);
57
- if (!Number.isInteger(numericPid) || numericPid <= 0
58
- || !Number.isInteger(numericIndex) || numericIndex < 0
59
- || !Number.isInteger(numericGeneration) || numericGeneration <= 0) {
60
- throw new Error('Could not construct a macOS window id from invalid PID/index/generation');
61
- }
62
- return `mac:${numericPid}:w${numericIndex}:g${numericGeneration}`;
63
- }
64
-
65
- export function normalizeMacWindowTarget(args = {}, { useWindowId = false } = {}) {
66
- const explicitPid = Number.isInteger(Number(args.pid)) && Number(args.pid) > 0 ? Number(args.pid) : 0;
67
- let idPid = 0;
68
- let windowIndex = null;
69
- const rawId = useWindowId ? optionalString(args.id) : '';
70
- let cached = null;
71
- if (rawId) {
72
- const match = /^mac:(\d+):w(\d+):g(\d+)$/.exec(rawId);
73
- if (!match) throw new Error('macOS window id is stale or unsupported; call list_windows again');
74
- idPid = Number(match[1]);
75
- windowIndex = Number(match[2]);
76
- if (explicitPid && explicitPid !== idPid) throw new Error('macOS window id and pid refer to different processes');
77
- cached = cachedMacWindowTarget(rawId);
78
- }
79
- return {
80
- id:rawId,
81
- idCached:Boolean(cached),
82
- pid:idPid || explicitPid,
83
- windowIndex,
84
- app:optionalString(args.app) || '',
85
- title:optionalString(args.window_title || args.windowTitle || args.title) || '',
86
- expectedApp:cached?.app || '',
87
- expectedTitle:cached?.title || '',
88
- expectedBounds:cached ? [cached.x,cached.y,cached.width,cached.height] : null,
89
- };
90
- }
91
-
92
30
  export async function listWindows() {
93
31
  const script = `
94
32
  set output to ""
@@ -0,0 +1,104 @@
1
+ import { clamp, runPowerShell, escapePowerShellSingle, optionalString } from './common.mjs';
2
+
3
+ export async function msaaBrowserSnapshot(args = {}) {
4
+ const maxNodes = clamp(args.max_nodes, 1200, 1, 5000);
5
+ const maxDepth = clamp(args.max_depth, 64, 1, 128);
6
+ const script = String.raw`
7
+ Add-Type -AssemblyName UIAutomationClient;Add-Type -AssemblyName UIAutomationTypes;Add-Type -AssemblyName Accessibility
8
+ ${windowsUiRootScript(args)}
9
+ Add-Type @'
10
+ using System;
11
+ using System.Runtime.InteropServices;
12
+ public static class RMAcc {
13
+ [DllImport("oleacc.dll")]
14
+ public static extern int AccessibleObjectFromWindow(IntPtr hwnd,uint dwId,ref Guid riid,[MarshalAs(UnmanagedType.Interface)] out object ppvObject);
15
+ public static object FromWindow(IntPtr hwnd) {
16
+ Guid iid=new Guid("618736E0-3C3D-11CF-810C-00AA00389B71");
17
+ object value=null;
18
+ int hr=AccessibleObjectFromWindow(hwnd,0xFFFFFFFCu,ref iid,out value);
19
+ if(hr<0) Marshal.ThrowExceptionForHR(hr);
20
+ return value;
21
+ }
22
+ }
23
+ '@
24
+ $rootAcc=[Accessibility.IAccessible][RMAcc]::FromWindow($h)
25
+ $nodes=New-Object System.Collections.ArrayList
26
+ $seen=0
27
+ $truncated=$false
28
+ $interactive=@(30,33,34,36,37,43,44,45,46,47,51,56,60,62)
29
+ $informative=@(15,20,22,24,31,32,40,41,42)
30
+ function Get-RoleName([int]$role){
31
+ switch($role){
32
+ 15{'Document'};20{'Group'};22{'Toolbar'};24{'Table'};30{'Link'};33{'List'};34{'ListItem'};35{'Tree'};36{'TreeItem'};37{'TabItem'};40{'Graphic'};41{'Text'};42{'Edit'};43{'Button'};44{'CheckBox'};45{'RadioButton'};46{'ComboBox'};47{'DropList'};51{'Slider'};56{'Button'};60{'TabList'};62{'Button'};default{'Accessible'}
33
+ }
34
+ }
35
+ function Walk-Acc($acc,[int]$depth,[string]$path,[bool]$inDocument,[string]$parentId){
36
+ if($null -eq $acc -or $depth -gt ${maxDepth} -or $script:seen -ge ${maxNodes}){if($script:seen -ge ${maxNodes}){$script:truncated=$true};return}
37
+ $script:seen++
38
+ try{$roleRaw=$acc.get_accRole(0);$role=[int]$roleRaw}catch{$role=-1}
39
+ try{$state=[int]$acc.get_accState(0)}catch{$state=0}
40
+ $invisible=(($state -band 0x8000) -ne 0) -or (($state -band 0x10000) -ne 0)
41
+ if($depth -gt 0 -and $invisible){return}
42
+ $isDocument=$role -eq 15
43
+ $inside=$inDocument -or $isDocument
44
+ $l=0;$t=0;$w=0;$hh=0;$hasRect=$true
45
+ try{$acc.accLocation([ref]$l,[ref]$t,[ref]$w,[ref]$hh,0)}catch{$hasRect=$false}
46
+ try{$name=[string]$acc.get_accName(0)}catch{$name=''}
47
+ try{$value=[string]$acc.get_accValue(0)}catch{$value=''}
48
+ $visible=$hasRect -and $w -gt 0 -and $hh -gt 0 -and -not $invisible
49
+ if($inside -and $visible -and (($interactive -contains $role) -or ($informative -contains $role) -or $name -or $value)){
50
+ $id='msaa:'+$rootPid+':'+$path
51
+ $patterns=New-Object System.Collections.ArrayList
52
+ if($role -in @(30,43,56,62)){[void]$patterns.Add('invoke')}
53
+ if($role -in @(42,46,47)){[void]$patterns.Add('value')}
54
+ if($role -in @(34,36,37,45)){[void]$patterns.Add('selection_item')}
55
+ if($role -eq 44){[void]$patterns.Add('toggle')}
56
+ if($role -eq 51){[void]$patterns.Add('range_value')}
57
+ [void]$nodes.Add([pscustomobject]@{
58
+ id=$id;parent_id=$parentId;depth=$depth;active=($depth -eq 0);pid=$rootPid;app=$rootApp;window=$windowName;
59
+ name=$name;role=(Get-RoleName $role);localized_role=(Get-RoleName $role);automationId='';className='';frameworkId='MSAA';
60
+ enabled=$true;offscreen=$false;focused=(($state -band 0x4) -ne 0);focusable=(($state -band 0x100000) -ne 0);password=$false;
61
+ value=$value;patterns=@($patterns);x=[int]$l;y=[int]$t;width=[int]$w;height=[int]$hh
62
+ })
63
+ $parentId=$id
64
+ }
65
+ try{$count=[int]$acc.accChildCount}catch{$count=0}
66
+ for($i=1;$i -le $count;$i++){
67
+ if($script:seen -ge ${maxNodes}){$script:truncated=$true;break}
68
+ try{$child=$acc.get_accChild($i)}catch{continue}
69
+ if($child -is [Accessibility.IAccessible]){
70
+ Walk-Acc $child ($depth+1) ($path+'.'+$i) $inside $parentId
71
+ }
72
+ }
73
+ }
74
+ Walk-Acc $rootAcc 0 'r' $false $null
75
+ [pscustomobject]@{platform='win32';count=$nodes.Count;nodes=@($nodes);truncated=$truncated;max_nodes=${maxNodes};browser_dom=[pscustomobject]@{requested=$true;available=($nodes.Count -gt 0);provider='msaa-iaccessible'}}|ConvertTo-Json -Compress -Depth 7`;
76
+ const { stdout } = await runPowerShell(script, { label:'MSAA browser accessibility snapshot', timeout:30_000 });
77
+ return stdout.trim() ? JSON.parse(stdout) : { platform:'win32', count:0, nodes:[], browser_dom:{ requested:true, available:false, provider:'msaa-iaccessible' } };
78
+ }
79
+
80
+ export function windowsUiRootScript(args = {}) {
81
+ const pid = Number.isInteger(Number(args.pid)) ? Number(args.pid) : 0;
82
+ const app = escapePowerShellSingle(optionalString(args.app) || '');
83
+ const title = escapePowerShellSingle(optionalString(args.window_title || args.windowTitle) || '');
84
+ const scoped = pid > 0 || app || title;
85
+ const selector = scoped
86
+ ? `$p=Get-Process|Where-Object{$_.MainWindowHandle -ne 0 -and (${pid} -eq 0 -or $_.Id -eq ${pid}) -and ('${app}' -eq '' -or $_.ProcessName -like '*${app}*') -and ('${title}' -eq '' -or $_.MainWindowTitle -like '*${title}*')}|Select-Object -First 1;if(-not $p){throw 'No matching UI window found'};$h=$p.MainWindowHandle`
87
+ : '$h=[RMFG]::GetForegroundWindow()';
88
+ return String.raw`
89
+ Add-Type @'
90
+ using System;using System.Runtime.InteropServices;
91
+ public static class RMFG{
92
+ [DllImport("user32.dll")]public static extern IntPtr GetForegroundWindow();
93
+ }
94
+ '@
95
+ $h=[IntPtr]::Zero
96
+ ${selector}
97
+ if($h -eq [IntPtr]::Zero){throw 'No foreground UI window is available'}
98
+ $root=[System.Windows.Automation.AutomationElement]::FromHandle($h)
99
+ if(-not $root){throw 'Could not create UI Automation root'}
100
+ $windowName=$root.Current.Name
101
+ $rootPid=$root.Current.ProcessId
102
+ try{$rootApp=(Get-Process -Id $rootPid -ErrorAction Stop).ProcessName}catch{$rootApp=''}
103
+ `;
104
+ }
@@ -1,3 +1,4 @@
1
+ import { msaaBrowserSnapshot, windowsUiRootScript } from './desktop-windows-msaa.mjs';
1
2
  import path from 'node:path';
2
3
  import process from 'node:process';
3
4
 
@@ -100,109 +101,6 @@ switch('${action}'){
100
101
  return text(`Window action ${action} completed.`);
101
102
  }
102
103
 
103
- async function msaaBrowserSnapshot(args = {}) {
104
- const maxNodes = clamp(args.max_nodes, 1200, 1, 5000);
105
- const maxDepth = clamp(args.max_depth, 64, 1, 128);
106
- const script = String.raw`
107
- Add-Type -AssemblyName UIAutomationClient;Add-Type -AssemblyName UIAutomationTypes;Add-Type -AssemblyName Accessibility
108
- ${windowsUiRootScript(args)}
109
- Add-Type @'
110
- using System;
111
- using System.Runtime.InteropServices;
112
- public static class RMAcc {
113
- [DllImport("oleacc.dll")]
114
- public static extern int AccessibleObjectFromWindow(IntPtr hwnd,uint dwId,ref Guid riid,[MarshalAs(UnmanagedType.Interface)] out object ppvObject);
115
- public static object FromWindow(IntPtr hwnd) {
116
- Guid iid=new Guid("618736E0-3C3D-11CF-810C-00AA00389B71");
117
- object value=null;
118
- int hr=AccessibleObjectFromWindow(hwnd,0xFFFFFFFCu,ref iid,out value);
119
- if(hr<0) Marshal.ThrowExceptionForHR(hr);
120
- return value;
121
- }
122
- }
123
- '@
124
- $rootAcc=[Accessibility.IAccessible][RMAcc]::FromWindow($h)
125
- $nodes=New-Object System.Collections.ArrayList
126
- $seen=0
127
- $truncated=$false
128
- $interactive=@(30,33,34,36,37,43,44,45,46,47,51,56,60,62)
129
- $informative=@(15,20,22,24,31,32,40,41,42)
130
- function Get-RoleName([int]$role){
131
- switch($role){
132
- 15{'Document'};20{'Group'};22{'Toolbar'};24{'Table'};30{'Link'};33{'List'};34{'ListItem'};35{'Tree'};36{'TreeItem'};37{'TabItem'};40{'Graphic'};41{'Text'};42{'Edit'};43{'Button'};44{'CheckBox'};45{'RadioButton'};46{'ComboBox'};47{'DropList'};51{'Slider'};56{'Button'};60{'TabList'};62{'Button'};default{'Accessible'}
133
- }
134
- }
135
- function Walk-Acc($acc,[int]$depth,[string]$path,[bool]$inDocument,[string]$parentId){
136
- if($null -eq $acc -or $depth -gt ${maxDepth} -or $script:seen -ge ${maxNodes}){if($script:seen -ge ${maxNodes}){$script:truncated=$true};return}
137
- $script:seen++
138
- try{$roleRaw=$acc.get_accRole(0);$role=[int]$roleRaw}catch{$role=-1}
139
- try{$state=[int]$acc.get_accState(0)}catch{$state=0}
140
- $invisible=(($state -band 0x8000) -ne 0) -or (($state -band 0x10000) -ne 0)
141
- if($depth -gt 0 -and $invisible){return}
142
- $isDocument=$role -eq 15
143
- $inside=$inDocument -or $isDocument
144
- $l=0;$t=0;$w=0;$hh=0;$hasRect=$true
145
- try{$acc.accLocation([ref]$l,[ref]$t,[ref]$w,[ref]$hh,0)}catch{$hasRect=$false}
146
- try{$name=[string]$acc.get_accName(0)}catch{$name=''}
147
- try{$value=[string]$acc.get_accValue(0)}catch{$value=''}
148
- $visible=$hasRect -and $w -gt 0 -and $hh -gt 0 -and -not $invisible
149
- if($inside -and $visible -and (($interactive -contains $role) -or ($informative -contains $role) -or $name -or $value)){
150
- $id='msaa:'+$rootPid+':'+$path
151
- $patterns=New-Object System.Collections.ArrayList
152
- if($role -in @(30,43,56,62)){[void]$patterns.Add('invoke')}
153
- if($role -in @(42,46,47)){[void]$patterns.Add('value')}
154
- if($role -in @(34,36,37,45)){[void]$patterns.Add('selection_item')}
155
- if($role -eq 44){[void]$patterns.Add('toggle')}
156
- if($role -eq 51){[void]$patterns.Add('range_value')}
157
- [void]$nodes.Add([pscustomobject]@{
158
- id=$id;parent_id=$parentId;depth=$depth;active=($depth -eq 0);pid=$rootPid;app=$rootApp;window=$windowName;
159
- name=$name;role=(Get-RoleName $role);localized_role=(Get-RoleName $role);automationId='';className='';frameworkId='MSAA';
160
- enabled=$true;offscreen=$false;focused=(($state -band 0x4) -ne 0);focusable=(($state -band 0x100000) -ne 0);password=$false;
161
- value=$value;patterns=@($patterns);x=[int]$l;y=[int]$t;width=[int]$w;height=[int]$hh
162
- })
163
- $parentId=$id
164
- }
165
- try{$count=[int]$acc.accChildCount}catch{$count=0}
166
- for($i=1;$i -le $count;$i++){
167
- if($script:seen -ge ${maxNodes}){$script:truncated=$true;break}
168
- try{$child=$acc.get_accChild($i)}catch{continue}
169
- if($child -is [Accessibility.IAccessible]){
170
- Walk-Acc $child ($depth+1) ($path+'.'+$i) $inside $parentId
171
- }
172
- }
173
- }
174
- Walk-Acc $rootAcc 0 'r' $false $null
175
- [pscustomobject]@{platform='win32';count=$nodes.Count;nodes=@($nodes);truncated=$truncated;max_nodes=${maxNodes};browser_dom=[pscustomobject]@{requested=$true;available=($nodes.Count -gt 0);provider='msaa-iaccessible'}}|ConvertTo-Json -Compress -Depth 7`;
176
- const { stdout } = await runPowerShell(script, { label:'MSAA browser accessibility snapshot', timeout:30_000 });
177
- return stdout.trim() ? JSON.parse(stdout) : { platform:'win32', count:0, nodes:[], browser_dom:{ requested:true, available:false, provider:'msaa-iaccessible' } };
178
- }
179
-
180
- function windowsUiRootScript(args = {}) {
181
- const pid = Number.isInteger(Number(args.pid)) ? Number(args.pid) : 0;
182
- const app = escapePowerShellSingle(optionalString(args.app) || '');
183
- const title = escapePowerShellSingle(optionalString(args.window_title || args.windowTitle) || '');
184
- const scoped = pid > 0 || app || title;
185
- const selector = scoped
186
- ? `$p=Get-Process|Where-Object{$_.MainWindowHandle -ne 0 -and (${pid} -eq 0 -or $_.Id -eq ${pid}) -and ('${app}' -eq '' -or $_.ProcessName -like '*${app}*') -and ('${title}' -eq '' -or $_.MainWindowTitle -like '*${title}*')}|Select-Object -First 1;if(-not $p){throw 'No matching UI window found'};$h=$p.MainWindowHandle`
187
- : '$h=[RMFG]::GetForegroundWindow()';
188
- return String.raw`
189
- Add-Type @'
190
- using System;using System.Runtime.InteropServices;
191
- public static class RMFG{
192
- [DllImport("user32.dll")]public static extern IntPtr GetForegroundWindow();
193
- }
194
- '@
195
- $h=[IntPtr]::Zero
196
- ${selector}
197
- if($h -eq [IntPtr]::Zero){throw 'No foreground UI window is available'}
198
- $root=[System.Windows.Automation.AutomationElement]::FromHandle($h)
199
- if(-not $root){throw 'Could not create UI Automation root'}
200
- $windowName=$root.Current.Name
201
- $rootPid=$root.Current.ProcessId
202
- try{$rootApp=(Get-Process -Id $rootPid -ErrorAction Stop).ProcessName}catch{$rootApp=''}
203
- `;
204
- }
205
-
206
104
  export async function uiSnapshot(args = {}) {
207
105
  const maxNodes = clamp(args.max_nodes, 500, 1, 5000);
208
106
  const maxDepth = clamp(args.max_depth, 8, 1, 32);