@@ -150,6 +150,39 @@ describe('switchPresetOnDisk', () => {
150150 } ) ;
151151 } ) ;
152152
153+ test ( 'persists preset name when the user config has a UTF-8 BOM' , ( ) => {
154+ const configDir = path . join ( tempDir , 'opencode-config' ) ;
155+ fs . mkdirSync ( configDir , { recursive : true } ) ;
156+ process . env . OPENCODE_CONFIG_DIR = configDir ;
157+
158+ const configPath = path . join ( configDir , 'oh-my-opencode-slim.json' ) ;
159+ fs . writeFileSync (
160+ configPath ,
161+ `\uFEFF${ JSON . stringify ( {
162+ preset : 'old' ,
163+ agents : { oracle : { model : 'old-model' } } ,
164+ } ) } `,
165+ ) ;
166+
167+ const config : PluginConfig = {
168+ presets : {
169+ cheap : { orchestrator : { model : 'anthropic/claude-3.5-haiku' } } ,
170+ } ,
171+ } ;
172+
173+ const result = switchPresetOnDisk ( tempDir , 'cheap' , config ) ;
174+ expect ( result . ok ) . toBe ( true ) ;
175+
176+ // The BOM is stripped on read, so the preset name is persisted; the
177+ // rewritten file is plain JSON that parses cleanly.
178+ const persisted = JSON . parse ( fs . readFileSync ( configPath , 'utf-8' ) ) as {
179+ preset ?: string ;
180+ agents ?: Record < string , unknown > ;
181+ } ;
182+ expect ( persisted . preset ) . toBe ( 'cheap' ) ;
183+ expect ( persisted . agents ) . toEqual ( { oracle : { model : 'old-model' } } ) ;
184+ } ) ;
185+
153186 test ( 'resolves legacy alias keys (explore → explorer)' , ( ) => {
154187 const config : PluginConfig = {
155188 presets : {
@@ -287,6 +320,36 @@ describe('writePreset', () => {
287320 expect ( persisted . preset ) . toBe ( 'old' ) ;
288321 } ) ;
289322
323+ test ( 'reads a user config with a UTF-8 BOM before writing' , ( ) => {
324+ const configDir = path . join ( tempDir , 'opencode-config' ) ;
325+ fs . mkdirSync ( configDir , { recursive : true } ) ;
326+ process . env . OPENCODE_CONFIG_DIR = configDir ;
327+ const configPath = path . join ( configDir , 'oh-my-opencode-slim.json' ) ;
328+ fs . writeFileSync (
329+ configPath ,
330+ `\uFEFF${ JSON . stringify ( {
331+ preset : 'old' ,
332+ presets : { existing : { oracle : { model : 'a' } } } ,
333+ } ) } `,
334+ ) ;
335+
336+ const ok = writePreset ( tempDir , 'scout' , {
337+ explorer : { model : 'openai/gpt-5.6-luna' } ,
338+ } ) ;
339+
340+ expect ( ok ) . toBe ( true ) ;
341+ const persisted = JSON . parse ( fs . readFileSync ( configPath , 'utf-8' ) ) as {
342+ preset ?: string ;
343+ presets ?: Record < string , unknown > ;
344+ } ;
345+ // Existing fields survived, proving the BOM-prefixed file was parsed
346+ expect ( persisted . preset ) . toBe ( 'old' ) ;
347+ expect ( persisted . presets ?. existing ) . toEqual ( { oracle : { model : 'a' } } ) ;
348+ expect ( persisted . presets ?. scout ) . toEqual ( {
349+ explorer : { model : 'openai/gpt-5.6-luna' } ,
350+ } ) ;
351+ } ) ;
352+
290353 test ( 'overwrites an existing preset of the same name' , ( ) => {
291354 const configDir = path . join ( tempDir , 'opencode-config' ) ;
292355 fs . mkdirSync ( configDir , { recursive : true } ) ;
0 commit comments