@@ -97,7 +97,7 @@ const FUNCTIONS = [
9797 'refreshCloudToken' , 'renewSession' , 'storedToken' , 'catchUpWithStoredSession' , 'withSessionLock' , 'sameSession' , 'sessionExpired' , 'postSessionExpired' ,
9898 'sessionExpiredPending' , 'isEndedSessionError' , 'clearSessionExpired' , 'replaySessionExpired' , 'checkCloudSession' ,
9999 'refreshGatewayToken' , 'prepProviderRequest' , 'handleSend' , 'currentAccount' , 'postAccount' , 'accountSignOut' ,
100- 'storeSession' , 'newChat' , 'resumeSession' , 'restoreCheckpoint' , 'onConfigChanged'
100+ 'storeSession' , 'newChat' , 'resumeSession' , 'restoreCheckpoint' , 'onConfigChanged' , 'readApprovalMode'
101101] ;
102102// eslint-disable-next-line no-new-func
103103const makeHost = new Function ( 'env' , [
@@ -112,6 +112,8 @@ const makeHost = new Function('env', [
112112 'let agentMessages = [], currentCheckpoint = null, lastAgentGoal = null;' ,
113113 // resumeSession starts the Ask When Risky reviewer's record of the person's goals over (agentReview.js).
114114 'let humanGoals = [], humanAnswers = [];' ,
115+ // onConfigChanged re-reads the approvals mode (Settings, another window).
116+ "let approvalMode = 'ask';" , decl ( 'APPROVAL_MODES' ) ,
115117 // resumeSession also hands a session's stored diagrams back to the diagram service
116118 // (docs/RICH-DIAGRAMS.md). These sessions have none; test/diagramHost.test.js runs it with the real one.
117119 'let diagramsStubbed = false; const diagrams = { load: () => {}, stubsFor: () => [] };' ,
@@ -121,7 +123,7 @@ const makeHost = new Function('env', [
121123 'return { ' + FUNCTIONS . join ( ', ' ) + ',' ,
122124 ' KEY: { token: ACCOUNT_TOKEN_KEY, refresh: ACCOUNT_REFRESH_KEY, profile: ACCOUNT_PROFILE_KEY, expired: ACCOUNT_EXPIRED_KEY },' ,
123125 ' signedIn: () => cloudSignedIn, conversation: () => conversation, abort: () => abort,' ,
124- ' openWebview: (w) => { activeWebview = w; }, setAgentMessages: (m) => { agentMessages = m; } };'
126+ ' openWebview: (w) => { activeWebview = w; }, setAgentMessages: (m) => { agentMessages = m; }, approvalMode: () => approvalMode };'
125127] . join ( '\n' ) ) ;
126128
127129// The defaults a fresh install runs with come from package.json, not from this file: the whole
@@ -185,7 +187,8 @@ function boot(over) {
185187
186188 const secrets = new Map ( ) , state = new Map ( ) , posted = [ ] , ops = [ ] ;
187189 const calls = { refresh : [ ] , stream : [ ] , keyPrompts : 0 , flows : [ ] } ;
188- const vscode = { workspace : { getConfiguration : ( section ) => ( { get : ( k , d ) => ( settings [ section + '.' + k ] === undefined ? d : settings [ section + '.' + k ] ) } ) } } ;
190+ // inspect() as the user scope sees it: these settings are the user's (a folder cannot set them).
191+ const vscode = { workspace : { getConfiguration : ( section ) => ( { get : ( k , d ) => ( settings [ section + '.' + k ] === undefined ? d : settings [ section + '.' + k ] ) , inspect : ( k ) => ( { globalValue : settings [ section + '.' + k ] } ) } ) } } ;
189192 const webview = { postMessage : ( m ) => { posted . push ( m ) ; } } ;
190193 const t = {
191194 posted, calls, ops, secrets, state, webview, settings,
@@ -1099,6 +1102,27 @@ async function test(name, fn) { await fn(); n++; console.log(' ok - ' + name);
10991102 assert . match ( src , / c a s e ' c o n t i n u e A g e n t ' : [ ^ \n ] * a w a i t a g e n t F l o w \( ' C o n t i n u e f r o m w h e r e y o u l e f t o f f [ ^ \n ] * ' \) ; / ) ;
11001103 } ) ;
11011104
1105+ await test ( 'SETTINGS: the approvals mode changed in Settings or another window takes effect at once, and the pill follows' , async ( ) => {
1106+ const t = boot ( { settings : { 'levelcode.ai.agent.approvals' : 'autopilot' } } ) ;
1107+ assert . strictEqual ( t . host . approvalMode ( ) , 'ask' , 'the window started before the change' ) ;
1108+ t . host . onConfigChanged ( settingChanged ( 'levelcode.ai' , 'levelcode.ai.agent.approvals' ) ) ;
1109+ assert . strictEqual ( t . host . approvalMode ( ) , 'autopilot' ) ;
1110+ assert . deepStrictEqual ( t . posted . filter ( ( m ) => m . type === 'approvals' ) , [ { type : 'approvals' , mode : 'autopilot' } ] ) ;
1111+ // Back to Default Approvals the same way — the case that matters: Autopilot must not linger.
1112+ const t2 = boot ( { settings : { 'levelcode.ai.agent.approvals' : 'ask' , 'levelcode.ai.agent.autopilot' : true } } ) ;
1113+ t2 . host . onConfigChanged ( settingChanged ( 'levelcode.ai' , 'levelcode.ai.agent.approvals' ) ) ;
1114+ assert . strictEqual ( t2 . host . approvalMode ( ) , 'ask' , 'an explicit approvals value wins over the old boolean' ) ;
1115+ // The old boolean alone still means Autopilot, and changing it is picked up too.
1116+ const t3 = boot ( { settings : { 'levelcode.ai.agent.autopilot' : true } } ) ;
1117+ t3 . host . onConfigChanged ( settingChanged ( 'levelcode.ai' , 'levelcode.ai.agent.autopilot' ) ) ;
1118+ assert . strictEqual ( t3 . host . approvalMode ( ) , 'autopilot' ) ;
1119+ // An unrelated change leaves it alone.
1120+ const t4 = boot ( { settings : { 'levelcode.ai.agent.approvals' : 'review' } } ) ;
1121+ t4 . host . onConfigChanged ( settingChanged ( 'levelcode.ai' , 'levelcode.ai.chat.fontSize' ) ) ;
1122+ assert . strictEqual ( t4 . host . approvalMode ( ) , 'ask' ) ;
1123+ assert . deepStrictEqual ( t4 . posted . filter ( ( m ) => m . type === 'approvals' ) , [ ] ) ;
1124+ } ) ;
1125+
11021126 await test ( '`ready` does not say it twice: the handler has just read the same token, and the card is replayed last' , async ( ) => {
11031127 const t = boot ( { expired : true , profile : { name : 'Ada' } } ) ;
11041128 await t . host . checkCloudSession ( 'ready' ) ;
0 commit comments