@@ -25,6 +25,7 @@ import { STATUS_KEY_HANDOFF, WIDGET_KEY_WARNING, updateIndicators } from "./tui.
2525import {
2626 MANUAL_AGENTICODING_SETTINGS_INSTRUCTIONS ,
2727 buildAgenticodingSettingsModel ,
28+ createAgenticodingSettingsComponent ,
2829 getAgenticodingSettingsDisplayLines ,
2930 readHandoffSettingsState ,
3031 resolveHandoffResumeBehavior ,
@@ -625,6 +626,40 @@ test("agenticoding settings TUI warns when project override masks global setting
625626 } ) ;
626627} ) ;
627628
629+ test ( "agenticoding settings TUI editable control anchors and refreshes to global value when project override masks it" , async ( ) => {
630+ await withIsolatedSettings ( async ( { home, cwd } ) => {
631+ await writeSettingsFile ( join ( home , ".pi" , "agent" , "settings.json" ) , { handoff : { resumeBehavior : "proceed" } } ) ;
632+ await writeSettingsFile ( join ( cwd , ".pi" , "settings.json" ) , { handoff : { resumeBehavior : "wait" } } ) ;
633+ const ctx = { cwd, hasUI : true , ui : { notify : ( ) => { } } } as any ;
634+ const model = await buildAgenticodingSettingsModel ( ctx ) ;
635+ const component = createAgenticodingSettingsComponent ( model , ctx , { requestRender : ( ) => { } } , theme , ( ) => { } ) ;
636+
637+ const rendered = stripAnsi ( component . render ( 120 ) . join ( "\n" ) ) ;
638+ assert . match ( rendered , / R e s o l v e d h a n d o f f \. r e s u m e B e h a v i o r : w a i t \( p r o j e c t \) / ) ;
639+ assert . match ( rendered , / G l o b a l s e t t i n g s : .* " p r o c e e d " / ) ;
640+ assert . match ( rendered , / H a n d o f f r e s u m e b e h a v i o r \( g l o b a l s a v e \) \s + p r o c e e d / ) ;
641+ } ) ;
642+
643+ await withIsolatedSettings ( async ( { home, cwd } ) => {
644+ const globalPath = join ( home , ".pi" , "agent" , "settings.json" ) ;
645+ await writeSettingsFile ( globalPath , { handoff : { resumeBehavior : "wait" } } ) ;
646+ await writeSettingsFile ( join ( cwd , ".pi" , "settings.json" ) , { handoff : { resumeBehavior : "wait" } } ) ;
647+ const ctx = { cwd, hasUI : true , ui : { notify : ( ) => { } } } as any ;
648+ const model = await buildAgenticodingSettingsModel ( ctx ) ;
649+ const component = createAgenticodingSettingsComponent ( model , ctx , { requestRender : ( ) => { } } , theme , ( ) => { } ) ;
650+
651+ component . handleInput ( "\r" ) ;
652+ await new Promise ( resolve => setTimeout ( resolve , 50 ) ) ;
653+
654+ const saved = JSON . parse ( await readFile ( globalPath , "utf8" ) ) ;
655+ assert . equal ( saved . handoff . resumeBehavior , "proceed" ) ;
656+ const rendered = stripAnsi ( component . render ( 120 ) . join ( "\n" ) ) ;
657+ assert . match ( rendered , / R e s o l v e d h a n d o f f \. r e s u m e B e h a v i o r : w a i t \( p r o j e c t \) / ) ;
658+ assert . match ( rendered , / G l o b a l s e t t i n g s : .* " p r o c e e d " / ) ;
659+ assert . match ( rendered , / H a n d o f f r e s u m e b e h a v i o r \( g l o b a l s a v e \) \s + p r o c e e d / ) ;
660+ } ) ;
661+ } ) ;
662+
628663test ( "agenticoding settings TUI handles invalid JSON policies" , async ( ) => {
629664 await withIsolatedSettings ( async ( { home, cwd } ) => {
630665 const globalPath = join ( home , ".pi" , "agent" , "settings.json" ) ;
@@ -644,6 +679,28 @@ test("agenticoding settings TUI handles invalid JSON policies", async () => {
644679 assert . match ( notifications . at ( - 1 ) ?. message ?? "" , / I n v a l i d g l o b a l s e t t i n g s J S O N / ) ;
645680 } ) ;
646681
682+ for ( const nonObjectRoot of [ "[]" , "\"x\"" , "42" ] ) {
683+ await withIsolatedSettings ( async ( { home, cwd } ) => {
684+ const globalPath = join ( home , ".pi" , "agent" , "settings.json" ) ;
685+ await writeSettingsFile ( globalPath , nonObjectRoot ) ;
686+ const notifications : Array < { message : string ; level : string } > = [ ] ;
687+ const ctx = {
688+ cwd,
689+ hasUI : true ,
690+ ui : { notify : ( message : string , level : string ) => notifications . push ( { message, level } ) } ,
691+ } as any ;
692+
693+ const state = await readHandoffSettingsState ( cwd ) ;
694+ assert . equal ( state . global . invalid , true ) ;
695+ const invalidGlobal = await buildAgenticodingSettingsModel ( ctx ) ;
696+ assert . equal ( invalidGlobal . globalWriteBlocked , true ) ;
697+ assert . equal ( await invalidGlobal . save ( "proceed" , ctx ) , false ) ;
698+ assert . equal ( await readFile ( globalPath , "utf8" ) , nonObjectRoot ) ;
699+ assert . equal ( notifications . at ( - 1 ) ?. level , "error" ) ;
700+ assert . match ( notifications . at ( - 1 ) ?. message ?? "" , / r o o t m u s t b e a n o b j e c t / ) ;
701+ } ) ;
702+ }
703+
647704 await withIsolatedSettings ( async ( { home, cwd } ) => {
648705 const globalPath = join ( home , ".pi" , "agent" , "settings.json" ) ;
649706 await writeSettingsFile ( join ( cwd , ".pi" , "settings.json" ) , "{" ) ;
0 commit comments