@@ -24,13 +24,38 @@ function jsonBytes(value: unknown): number {
2424 return encoder . encode ( JSON . stringify ( value ) ) . length
2525}
2626
27+ type SnapshotNode = ComputerUseSnapshot [ 'nodes' ] [ number ]
28+
29+ /** Layout wrappers that carry no text, state (including disabled) or action; their children are shown under the nearest meaningful ancestor. */
30+ const WRAPPER_ROLES = new Set ( [
31+ 'AXGroup' ,
32+ 'AXSplitGroup' ,
33+ 'AXLayoutArea' ,
34+ 'AXLayoutItem' ,
35+ 'AXUnknown' ,
36+ ] )
37+
38+ function isWrapper ( node : SnapshotNode ) : boolean {
39+ return (
40+ WRAPPER_ROLES . has ( node . role ) &&
41+ ! node . label &&
42+ ! node . value &&
43+ ! node . placeholder &&
44+ ! node . focused &&
45+ ! node . editable &&
46+ node . enabled !== false &&
47+ node . actions . length === 0
48+ )
49+ }
50+
2751function nodeRow (
28- node : ComputerUseSnapshot [ 'nodes' ] [ number ] ,
52+ node : SnapshotNode ,
53+ parentId : string | undefined ,
54+ selectedWindowId : string ,
2955 selected ?: ReadonlySet < string >
3056) : string {
3157 const fields = [ node . elementId , node . role ]
32- if ( node . parentId && ( ! selected || selected . has ( node . parentId ) ) )
33- fields . push ( `parent=${ node . parentId } ` )
58+ if ( parentId && ( ! selected || selected . has ( parentId ) ) ) fields . push ( `parent=${ parentId } ` )
3459 if ( node . label ) fields . push ( `label=${ JSON . stringify ( truncateAtCodePoint ( node . label , 512 , '' ) ) } ` )
3560 if ( node . value ) fields . push ( `value=${ JSON . stringify ( truncateAtCodePoint ( node . value , 512 , '' ) ) } ` )
3661 if ( node . placeholder )
@@ -39,14 +64,16 @@ function nodeRow(
3964 if ( node . editable ) fields . push ( 'editable' )
4065 if ( node . enabled === false ) fields . push ( 'disabled' )
4166 if ( node . actions . length ) fields . push ( `actions=${ JSON . stringify ( node . actions . slice ( 0 , 16 ) ) } ` )
42- if ( node . windowId ) fields . push ( `window=${ node . windowId } ` )
67+ if ( node . windowId && node . windowId !== selectedWindowId ) fields . push ( `window=${ node . windowId } ` )
4368 if (
4469 node . x !== undefined &&
4570 node . y !== undefined &&
4671 node . width !== undefined &&
4772 node . height !== undefined
4873 )
49- fields . push ( `screenRect=${ JSON . stringify ( [ node . x , node . y , node . width , node . height ] ) } ` )
74+ fields . push (
75+ `screenRect=${ [ node . x , node . y , node . width , node . height ] . map ( ( value ) => Math . round ( value ) ) . join ( ',' ) } `
76+ )
5077 return fields . join ( ' ' )
5178}
5279
@@ -70,8 +97,10 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) {
7097 : { } ) ,
7198 windows,
7299 omittedWindowCount : allWindows . length - windows . length ,
100+ treeFormat :
101+ 'Rows: elementId role [parent=id] [label|value|placeholder] [focused] [editable] [disabled] [actions=JSON array of exact names] [window=id, omitted for windowId] [screenRect=x,y,w,h in global points]. Empty layout groups are folded into their parent.' ,
73102 accessibilityTree : '' ,
74- omittedNodeCount : nodes . length ,
103+ omittedNodeCount : nodes . filter ( ( node ) => ! isWrapper ( node ) ) . length ,
75104 nodeTextLimit : 512 ,
76105 ...( image
77106 ? {
@@ -87,8 +116,25 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) {
87116 model . omittedWindowCount += 1
88117 }
89118 const byId = new Map ( nodes . map ( ( node ) => [ node . elementId , node ] ) )
90- const rows = new Map ( nodes . map ( ( node ) => [ node . elementId , nodeRow ( node ) ] ) )
91- const priority = ( node : ComputerUseSnapshot [ 'nodes' ] [ number ] ) => {
119+ const shown = nodes . filter ( ( node ) => ! isWrapper ( node ) )
120+ /** Each shown node's nearest shown ancestor, skipping wrappers (and stopping on a parent cycle). */
121+ const shownParent = new Map < string , string | undefined > ( )
122+ for ( const node of shown ) {
123+ const seen = new Set ( [ node . elementId ] )
124+ let parent = node . parentId ? byId . get ( node . parentId ) : undefined
125+ while ( parent && isWrapper ( parent ) && ! seen . has ( parent . elementId ) ) {
126+ seen . add ( parent . elementId )
127+ parent = parent . parentId ? byId . get ( parent . parentId ) : undefined
128+ }
129+ shownParent . set ( node . elementId , parent && ! isWrapper ( parent ) ? parent . elementId : undefined )
130+ }
131+ const rows = new Map (
132+ shown . map ( ( node ) => [
133+ node . elementId ,
134+ nodeRow ( node , shownParent . get ( node . elementId ) , state . windowId ) ,
135+ ] )
136+ )
137+ const priority = ( node : SnapshotNode ) => {
92138 const visible =
93139 selectedWindow &&
94140 node . windowId === selectedWindow . windowId &&
@@ -133,14 +179,14 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) {
133179 bytes += cost
134180 return true
135181 }
136- for ( const node of [ ...nodes ] . sort ( ( left , right ) => priority ( right ) - priority ( left ) ) ) {
182+ for ( const node of [ ...shown ] . sort ( ( left , right ) => priority ( right ) - priority ( left ) ) ) {
137183 const ancestry = [ node . elementId ]
138184 const seen = new Set ( ancestry )
139- let parent = node . parentId
140- while ( parent && byId . has ( parent ) && ! seen . has ( parent ) ) {
185+ let parent = shownParent . get ( node . elementId )
186+ while ( parent && ! seen . has ( parent ) ) {
141187 ancestry . push ( parent )
142188 seen . add ( parent )
143- parent = byId . get ( parent ) ?. parentId
189+ parent = shownParent . get ( parent )
144190 }
145191 /** Keep the editor itself even if an unusually deep ancestry cannot fit. */
146192 if ( ! add ( ancestry . reverse ( ) ) ) add ( [ node . elementId ] )
@@ -155,14 +201,14 @@ function snapshotForModel(result: ComputerUseSnapshot, envelopeBytes = 0) {
155201 ) || allWindows . some ( ( window ) => window . title . length > 256 )
156202 return {
157203 ...model ,
158- accessibilityTree : nodes
204+ accessibilityTree : shown
159205 . filter ( ( node ) => selected . has ( node . elementId ) )
160- . map ( ( node ) => nodeRow ( node , selected ) )
206+ . map ( ( node ) => nodeRow ( node , shownParent . get ( node . elementId ) , state . windowId , selected ) )
161207 . join ( '\n' ) ,
162- omittedNodeCount : nodes . length - selected . size ,
208+ omittedNodeCount : shown . length - selected . size ,
163209 truncated :
164210 state . truncated ||
165- selected . size < nodes . length ||
211+ selected . size < shown . length ||
166212 windows . length < allWindows . length ||
167213 clipped ||
168214 screenshotOmitted ,
0 commit comments