@@ -2191,6 +2191,121 @@ describe("OpenCodeAdapterV2", () => {
21912191 ) ,
21922192 ) ;
21932193
2194+ it . effect ( "installs Full access rules on a native session it resumes from Auto" , ( ) =>
2195+ Effect . gen ( function * ( ) {
2196+ const nativeSessions = new Map < string , Record < string , unknown > > ( ) ;
2197+ const updateInputs : Array < unknown > = [ ] ;
2198+ const fakeClient = {
2199+ event : {
2200+ subscribe : async ( _input ?: unknown , options ?: { readonly signal ?: AbortSignal } ) => ( {
2201+ stream : {
2202+ [ Symbol . asyncIterator ] : ( ) => ( {
2203+ next : ( ) =>
2204+ new Promise < IteratorResult < never > > ( ( resolve ) => {
2205+ const done = ( ) => resolve ( { done : true , value : undefined } ) ;
2206+ if ( options ?. signal ?. aborted ) return done ( ) ;
2207+ options ?. signal ?. addEventListener ( "abort" , done , { once : true } ) ;
2208+ } ) ,
2209+ } ) ,
2210+ } ,
2211+ } ) ,
2212+ } ,
2213+ session : {
2214+ create : async ( input : { readonly permission ?: unknown } ) => {
2215+ const nativeSession = {
2216+ id : "ses_native_auto" ,
2217+ permission : input . permission ,
2218+ time : { created : 1 , updated : 1 } ,
2219+ } ;
2220+ nativeSessions . set ( nativeSession . id , nativeSession ) ;
2221+ return { data : nativeSession } ;
2222+ } ,
2223+ get : async ( { sessionID } : { readonly sessionID : string } ) => ( {
2224+ data : nativeSessions . get ( sessionID ) ,
2225+ } ) ,
2226+ update : async ( input : { readonly sessionID : string ; readonly permission ?: unknown } ) => {
2227+ updateInputs . push ( input ) ;
2228+ const nativeSession = { ...nativeSessions . get ( input . sessionID ) , ...input } ;
2229+ nativeSessions . set ( input . sessionID , nativeSession ) ;
2230+ return { data : nativeSession } ;
2231+ } ,
2232+ } ,
2233+ } as unknown as OpencodeClient ;
2234+ const unused = ( operation : string ) => ( ) => Effect . die ( `${ operation } is not used` ) ;
2235+ const runtime : OpenCodeRuntime . OpenCodeRuntime [ "Service" ] = {
2236+ startOpenCodeServerProcess : unused ( "startOpenCodeServerProcess" ) ,
2237+ connectToOpenCodeServer : ( ) =>
2238+ Effect . succeed ( {
2239+ url : "test://opencode" ,
2240+ version : "test" ,
2241+ exitCode : null ,
2242+ external : true ,
2243+ } ) ,
2244+ runOpenCodeCommand : unused ( "runOpenCodeCommand" ) ,
2245+ createOpenCodeSdkClient : ( ) => fakeClient ,
2246+ loadOpenCodeInventory : unused ( "loadOpenCodeInventory" ) ,
2247+ loadInventoryFromCli : unused ( "loadInventoryFromCli" ) ,
2248+ loadOpenCodeSkills : unused ( "loadOpenCodeSkills" ) ,
2249+ loadSkillsFromCli : unused ( "loadSkillsFromCli" ) ,
2250+ } ;
2251+ const instanceId = ProviderInstanceId . make ( "opencode" ) ;
2252+ const threadId = ThreadId . make ( "thread-opencode-mode-switch" ) ;
2253+ const modelSelection = { instanceId, model : "default" } ;
2254+ const auto = runtimePolicy ( "auto" ) ;
2255+ const fullAccess = runtimePolicy ( "full-access" ) ;
2256+ const adapter = yield * makeOpenCodeAdapterV2 ( {
2257+ instanceId,
2258+ settings : OPENCODE_TEST_SETTINGS ,
2259+ environment : { } ,
2260+ } ) . pipe ( Effect . provideService ( OpenCodeRuntime . OpenCodeRuntime , runtime ) ) ;
2261+ const autoSession = yield * adapter . openSession ( {
2262+ threadId,
2263+ providerSessionId : ProviderSessionId . make ( "provider-session-opencode-auto" ) ,
2264+ modelSelection,
2265+ runtimePolicy : auto ,
2266+ } ) ;
2267+ const providerThread = yield * autoSession . ensureThread ( {
2268+ threadId,
2269+ modelSelection,
2270+ runtimePolicy : auto ,
2271+ } ) ;
2272+ assert . deepEqual (
2273+ nativeSessions . get ( "ses_native_auto" ) ?. permission ,
2274+ openCodePermissionRules ( auto ) ,
2275+ ) ;
2276+ assert . equal ( permissionAction ( openCodePermissionRules ( auto ) , "edit" ) , "ask" ) ;
2277+
2278+ // Switching the composer to Full access reloads the existing native
2279+ // session under the new policy; its creation-time Auto rules must not
2280+ // survive, or OpenCode keeps asking for edits and shell commands.
2281+ const fullAccessSession = yield * adapter . openSession ( {
2282+ threadId,
2283+ providerSessionId : ProviderSessionId . make ( "provider-session-opencode-full-access" ) ,
2284+ modelSelection,
2285+ runtimePolicy : fullAccess ,
2286+ } ) ;
2287+ yield * fullAccessSession . resumeThread ( {
2288+ providerThread,
2289+ threadId,
2290+ modelSelection,
2291+ runtimePolicy : fullAccess ,
2292+ } ) ;
2293+
2294+ assert . deepEqual ( updateInputs , [
2295+ { sessionID : "ses_native_auto" , permission : openCodePermissionRules ( fullAccess ) } ,
2296+ ] ) ;
2297+ } ) . pipe (
2298+ Effect . scoped ,
2299+ Effect . provide (
2300+ Layer . mergeAll (
2301+ IdAllocator . layer ,
2302+ McpProviderSessions . layer ,
2303+ TestProviderHost . layer ( ) . pipe ( Layer . provide ( NodeServices . layer ) ) ,
2304+ ) ,
2305+ ) ,
2306+ ) ,
2307+ ) ;
2308+
21942309 it ( "maps runtime modes to safe OpenCode permission rules" , ( ) => {
21952310 const approvalRequired = openCodePermissionRules ( runtimePolicy ( "approval-required" ) ) ;
21962311 assert . equal ( permissionAction ( approvalRequired , "read" ) , "allow" ) ;
0 commit comments