@@ -283,48 +283,18 @@ function pruneNode(
283283 return [ pruned ] ;
284284}
285285
286- function pruneMinimalNode ( node : CupNode ) : CupNode | null {
287- const children = node . children ?? [ ] ;
288- const keptChildren : CupNode [ ] = [ ] ;
289-
290- for ( const child of children ) {
291- const pruned = pruneMinimalNode ( child ) ;
292- if ( pruned ) keptChildren . push ( pruned ) ;
293- }
294-
295- if ( hasMeaningfulActions ( node ) || keptChildren . length > 0 ) {
296- const pruned : CupNode = { ...node } ;
297- delete pruned . children ;
298- if ( keptChildren . length > 0 ) {
299- pruned . children = keptChildren ;
300- }
301- return pruned ;
302- }
303-
304- return null ;
305- }
306-
307286export function pruneTree (
308287 tree : CupNode [ ] ,
309288 options ?: { detail ?: Detail ; screen ?: { w : number ; h : number } | null } ,
310289) : CupNode [ ] {
311- const detail = options ?. detail ?? "standard " ;
290+ const detail = options ?. detail ?? "compact " ;
312291 const screen = options ?. screen ;
313292
314293 if ( detail === "full" ) {
315294 return structuredClone ( tree ) ;
316295 }
317296
318- if ( detail === "minimal" ) {
319- const result : CupNode [ ] = [ ] ;
320- for ( const root of tree ) {
321- const pruned = pruneMinimalNode ( root ) ;
322- if ( pruned ) result . push ( pruned ) ;
323- }
324- return result ;
325- }
326-
327- // "standard"
297+ // "compact"
328298 let screenViewport : Rect | null = null ;
329299 if ( screen ) {
330300 screenViewport = { x : 0 , y : 0 , w : screen . w , h : screen . h } ;
@@ -342,8 +312,113 @@ export function pruneTree(
342312
343313const VALUE_ROLES = new Set ( [ "textbox" , "searchbox" , "combobox" , "spinbutton" , "slider" ] ) ;
344314
315+ // ---------------------------------------------------------------------------
316+ // Vocabulary short codes — compact aliases for roles, states, and actions.
317+ // These reduce per-node token cost by ~50% on role/state/action strings.
318+ // ---------------------------------------------------------------------------
319+
320+ export const ROLE_CODES : Record < string , string > = {
321+ alert : "alrt" ,
322+ alertdialog : "adlg" ,
323+ application : "app" ,
324+ banner : "bnr" ,
325+ button : "btn" ,
326+ cell : "cel" ,
327+ checkbox : "chk" ,
328+ columnheader : "colh" ,
329+ combobox : "cmb" ,
330+ complementary : "cmp" ,
331+ contentinfo : "ci" ,
332+ dialog : "dlg" ,
333+ document : "doc" ,
334+ form : "frm" ,
335+ generic : "gen" ,
336+ grid : "grd" ,
337+ group : "grp" ,
338+ heading : "hdg" ,
339+ img : "img" ,
340+ link : "lnk" ,
341+ list : "lst" ,
342+ listitem : "li" ,
343+ log : "log" ,
344+ main : "main" ,
345+ marquee : "mrq" ,
346+ menu : "mnu" ,
347+ menubar : "mnub" ,
348+ menuitem : "mi" ,
349+ menuitemcheckbox : "mic" ,
350+ menuitemradio : "mir" ,
351+ navigation : "nav" ,
352+ none : "none" ,
353+ option : "opt" ,
354+ progressbar : "pbar" ,
355+ radio : "rad" ,
356+ region : "rgn" ,
357+ row : "row" ,
358+ rowheader : "rowh" ,
359+ scrollbar : "sb" ,
360+ search : "srch" ,
361+ searchbox : "sbx" ,
362+ separator : "sep" ,
363+ slider : "sld" ,
364+ spinbutton : "spn" ,
365+ status : "sts" ,
366+ switch : "sw" ,
367+ tab : "tab" ,
368+ table : "tbl" ,
369+ tablist : "tabs" ,
370+ tabpanel : "tpnl" ,
371+ text : "txt" ,
372+ textbox : "tbx" ,
373+ timer : "tmr" ,
374+ titlebar : "ttlb" ,
375+ toolbar : "tlbr" ,
376+ tooltip : "ttp" ,
377+ tree : "tre" ,
378+ treeitem : "ti" ,
379+ window : "win" ,
380+ } ;
381+
382+ export const STATE_CODES : Record < string , string > = {
383+ busy : "bsy" ,
384+ checked : "chk" ,
385+ collapsed : "col" ,
386+ disabled : "dis" ,
387+ editable : "edt" ,
388+ expanded : "exp" ,
389+ focused : "foc" ,
390+ hidden : "hid" ,
391+ mixed : "mix" ,
392+ modal : "mod" ,
393+ multiselectable : "msel" ,
394+ offscreen : "off" ,
395+ pressed : "prs" ,
396+ readonly : "ro" ,
397+ required : "req" ,
398+ selected : "sel" ,
399+ } ;
400+
401+ export const ACTION_CODES : Record < string , string > = {
402+ click : "clk" ,
403+ collapse : "col" ,
404+ decrement : "dec" ,
405+ dismiss : "dsm" ,
406+ doubleclick : "dbl" ,
407+ expand : "exp" ,
408+ focus : "foc" ,
409+ increment : "inc" ,
410+ longpress : "lp" ,
411+ rightclick : "rclk" ,
412+ scroll : "scr" ,
413+ select : "sel" ,
414+ setvalue : "sv" ,
415+ toggle : "tog" ,
416+ type : "typ" ,
417+ } ;
418+
345419export function formatLine ( node : CupNode ) : string {
346- const parts = [ `[${ node . id } ]` , node . role ] ;
420+ const role = node . role ;
421+ const parts = [ `[${ node . id } ]` , ROLE_CODES [ role ] ?? role ] ;
347422
348423 const name = node . name || "" ;
349424 if ( name ) {
@@ -352,25 +427,29 @@ export function formatLine(node: CupNode): string {
352427 parts . push ( `"${ truncated } "` ) ;
353428 }
354429
430+ // Actions (drop "focus" — it's noise)
431+ const actions = ( node . actions ?? [ ] ) . filter ( ( a ) => a !== "focus" ) ;
432+
433+ // Only include bounds for interactable nodes (nodes with meaningful actions).
434+ // Non-interactable nodes are context-only — agents reference them by ID, not
435+ // by coordinates, so spatial info adds tokens without value.
355436 const bounds = node . bounds ;
356- if ( bounds ) {
357- parts . push ( `@ ${ bounds . x } ,${ bounds . y } ${ bounds . w } x${ bounds . h } ` ) ;
437+ if ( bounds && actions . length > 0 ) {
438+ parts . push ( `${ bounds . x } ,${ bounds . y } ${ bounds . w } x${ bounds . h } ` ) ;
358439 }
359440
360441 const states = node . states ?? [ ] ;
361442 if ( states . length > 0 ) {
362- parts . push ( "{" + states . join ( "," ) + "}" ) ;
443+ parts . push ( "{" + states . map ( ( s ) => STATE_CODES [ s ] ?? s ) . join ( "," ) + "}" ) ;
363444 }
364445
365- // Actions (drop "focus" — it's noise)
366- const actions = ( node . actions ?? [ ] ) . filter ( ( a ) => a !== "focus" ) ;
367446 if ( actions . length > 0 ) {
368- parts . push ( "[" + actions . join ( "," ) + "]" ) ;
447+ parts . push ( "[" + actions . map ( ( a ) => ACTION_CODES [ a ] ?? a ) . join ( "," ) + "]" ) ;
369448 }
370449
371450 // Value for input-type elements
372451 const value = node . value || "" ;
373- if ( value && VALUE_ROLES . has ( node . role ) ) {
452+ if ( value && VALUE_ROLES . has ( role ) ) {
374453 let truncatedVal = value . length > 120 ? value . slice ( 0 , 120 ) + "..." : value ;
375454 truncatedVal = truncatedVal . replace ( / " / g, '\\"' ) . replace ( / \n / g, " " ) ;
376455 parts . push ( `val="${ truncatedVal } "` ) ;
@@ -436,7 +515,7 @@ export function serializeCompact(
436515 maxChars ?: number ;
437516 } ,
438517) : string {
439- const detail = options ?. detail ?? "standard " ;
518+ const detail = options ?. detail ?? "compact " ;
440519 const maxChars = options ?. maxChars ?? MAX_OUTPUT_CHARS ;
441520 const windowList = options ?. windowList ?? null ;
442521
0 commit comments