@@ -3,6 +3,10 @@ import * as os from "os";
33import * as path from "path" ;
44import { z } from "zod" ;
55import { normalizeCwd } from "../paths" ;
6+ import {
7+ sanitizeDashboardConfig ,
8+ type DashboardConfig ,
9+ } from "../dashboard-config" ;
610
711const existsSync = fs . existsSync ;
812const mkdirSync = fs . mkdirSync ;
@@ -297,29 +301,7 @@ export function loadOdeConfig(): OdeConfig {
297301 const parsedJson = JSON . parse ( raw ) as Record < string , unknown > ;
298302 const parsed = odeConfigSchema . safeParse ( parsedJson ) ;
299303 const base = parsed . success ? parsed . data : EMPTY_TEMPLATE ;
300- const hasExplicitModels = ( base . agents ?. opencode ?. models ?. length ?? 0 ) > 0 ;
301- const legacyModels = Array . isArray ( parsedJson . devServers )
302- ? Array . from (
303- new Set (
304- parsedJson . devServers
305- . filter ( ( entry ) : entry is { models ?: unknown } => Boolean ( entry && typeof entry === "object" ) )
306- . flatMap ( ( entry ) => ( Array . isArray ( entry . models ) ? entry . models : [ ] ) )
307- . filter ( ( model ) : model is string => typeof model === "string" )
308- . map ( ( model ) => model . trim ( ) )
309- . filter ( Boolean )
310- )
311- )
312- : [ ] ;
313- cachedConfig = normalizeConfig ( {
314- ...base ,
315- agents : {
316- ...base . agents ,
317- opencode : {
318- ...base . agents . opencode ,
319- models : hasExplicitModels ? base . agents . opencode . models : legacyModels ,
320- } ,
321- } ,
322- } ) ;
304+ cachedConfig = normalizeConfig ( base ) ;
323305 return cachedConfig ;
324306 } catch {
325307 cachedConfig = normalizeConfig ( EMPTY_TEMPLATE ) ;
@@ -334,26 +316,79 @@ export function invalidateOdeConfigCache(): void {
334316export function saveOdeConfig ( config : OdeConfig ) : void {
335317 ensureConfigDir ( ) ;
336318 cachedConfig = normalizeConfig ( config ) ;
337- let existingRaw : Record < string , unknown > | null = null ;
338- try {
339- const raw = readFileSync ( ODE_CONFIG_FILE , "utf-8" ) ;
340- const parsed = JSON . parse ( raw ) ;
341- if ( parsed && typeof parsed === "object" ) {
342- existingRaw = parsed as Record < string , unknown > ;
343- }
344- } catch {
345- existingRaw = null ;
346- }
319+ writeFileSync ( ODE_CONFIG_FILE , JSON . stringify ( cachedConfig , null , 2 ) ) ;
320+ }
347321
348- const persisted : Record < string , unknown > = { ...cachedConfig } ;
349- if ( existingRaw && Object . prototype . hasOwnProperty . call ( existingRaw , "devServer" ) ) {
350- persisted . devServer = existingRaw . devServer ;
351- }
352- if ( existingRaw && Object . prototype . hasOwnProperty . call ( existingRaw , "devServers" ) ) {
353- persisted . devServers = existingRaw . devServers ;
354- }
322+ export function updateOdeConfig ( updater : ( config : OdeConfig ) => OdeConfig ) : OdeConfig {
323+ const next = updater ( structuredClone ( loadOdeConfig ( ) ) ) ;
324+ saveOdeConfig ( next ) ;
325+ return loadOdeConfig ( ) ;
326+ }
327+
328+ function toDashboardConfig ( config : OdeConfig ) : DashboardConfig {
329+ const defaultStatusMessageFormat =
330+ config . user . defaultStatusMessageFormat === "aggressive" || config . user . defaultStatusMessageFormat === "minimum"
331+ ? config . user . defaultStatusMessageFormat
332+ : "medium" ;
333+
334+ return {
335+ completeOnboarding : config . completeOnboarding ,
336+ user : {
337+ name : config . user . name ,
338+ email : config . user . email ,
339+ initials : config . user . initials ,
340+ avatar : config . user . avatar ,
341+ gitStrategy : config . user . gitStrategy ,
342+ defaultStatusMessageFormat,
343+ } ,
344+ agents : structuredClone ( config . agents ) ,
345+ workspaces : structuredClone ( config . workspaces ) ,
346+ } ;
347+ }
348+
349+ function mergeDashboardConfig ( config : OdeConfig , dashboardConfig : DashboardConfig ) : OdeConfig {
350+ const workspaces : WorkspaceConfig [ ] = dashboardConfig . workspaces . map ( ( workspace ) => ( {
351+ ...workspace ,
352+ slackAppToken : workspace . slackAppToken ?? "" ,
353+ slackBotToken : workspace . slackBotToken ?? "" ,
354+ discordBotToken : workspace . discordBotToken ?? "" ,
355+ larkAppKey : workspace . larkAppKey ?? workspace . larkAppId ?? "" ,
356+ larkAppId : workspace . larkAppId ?? workspace . larkAppKey ?? "" ,
357+ larkAppSecret : workspace . larkAppSecret ?? "" ,
358+ channelDetails : workspace . channelDetails . map ( ( channel ) => ( {
359+ ...channel ,
360+ agentProvider : channel . agentProvider ?? "opencode" ,
361+ channelSystemMessage : channel . channelSystemMessage ?? "" ,
362+ } ) ) ,
363+ } ) ) ;
364+
365+ return {
366+ ...config ,
367+ completeOnboarding : dashboardConfig . completeOnboarding ,
368+ user : {
369+ ...config . user ,
370+ ...dashboardConfig . user ,
371+ } ,
372+ agents : structuredClone ( dashboardConfig . agents ) ,
373+ workspaces,
374+ } ;
375+ }
376+
377+ export function readDashboardConfig ( ) : DashboardConfig {
378+ return sanitizeDashboardConfig ( toDashboardConfig ( loadOdeConfig ( ) ) ) ;
379+ }
380+
381+ export function writeDashboardConfig ( config : DashboardConfig ) : DashboardConfig {
382+ const sanitized = sanitizeDashboardConfig ( config ) ;
383+ updateOdeConfig ( ( current ) => mergeDashboardConfig ( current , sanitized ) ) ;
384+ return readDashboardConfig ( ) ;
385+ }
355386
356- writeFileSync ( ODE_CONFIG_FILE , JSON . stringify ( persisted , null , 2 ) ) ;
387+ export function updateDashboardConfig (
388+ updater : ( config : DashboardConfig ) => DashboardConfig
389+ ) : DashboardConfig {
390+ const next = updater ( readDashboardConfig ( ) ) ;
391+ return writeDashboardConfig ( next ) ;
357392}
358393
359394export function getWorkspaces ( ) : WorkspaceConfig [ ] {
@@ -395,8 +430,7 @@ export function getOpenCodeModels(): string[] {
395430}
396431
397432export function setOpenCodeModels ( models : string [ ] ) : void {
398- const config = loadOdeConfig ( ) ;
399- saveOdeConfig ( {
433+ updateOdeConfig ( ( config ) => ( {
400434 ...config ,
401435 agents : {
402436 ...config . agents ,
@@ -405,16 +439,15 @@ export function setOpenCodeModels(models: string[]): void {
405439 models,
406440 } ,
407441 } ,
408- } ) ;
442+ } ) ) ;
409443}
410444
411445export function getCodexModels ( ) : string [ ] {
412446 return getAgentsConfig ( ) . codex . models ;
413447}
414448
415449export function setCodexModels ( models : string [ ] ) : void {
416- const config = loadOdeConfig ( ) ;
417- saveOdeConfig ( {
450+ updateOdeConfig ( ( config ) => ( {
418451 ...config ,
419452 agents : {
420453 ...config . agents ,
@@ -423,16 +456,15 @@ export function setCodexModels(models: string[]): void {
423456 models,
424457 } ,
425458 } ,
426- } ) ;
459+ } ) ) ;
427460}
428461
429462export function getKiloModels ( ) : string [ ] {
430463 return getAgentsConfig ( ) . kilo . models ?? [ ] ;
431464}
432465
433466export function setKiloModels ( models : string [ ] ) : void {
434- const config = loadOdeConfig ( ) ;
435- saveOdeConfig ( {
467+ updateOdeConfig ( ( config ) => ( {
436468 ...config ,
437469 agents : {
438470 ...config . agents ,
@@ -441,7 +473,7 @@ export function setKiloModels(models: string[]): void {
441473 models,
442474 } ,
443475 } ,
444- } ) ;
476+ } ) ) ;
445477}
446478
447479export function getUpdateConfig ( ) : UpdateConfig {
@@ -579,41 +611,41 @@ export function getUserGeneralSettings(): UserGeneralSettings {
579611}
580612
581613export function setUserGeneralSettings ( settings : UserGeneralSettings ) : void {
582- const config = loadOdeConfig ( ) ;
583- saveOdeConfig ( {
614+ updateOdeConfig ( ( config ) => ( {
584615 ...config ,
585616 user : {
586617 ...config . user ,
587618 defaultStatusMessageFormat : settings . defaultStatusMessageFormat ,
588619 gitStrategy : settings . gitStrategy ,
589620 } ,
590- } ) ;
621+ } ) ) ;
591622}
592623
593624export function setGitHubInfoForUser ( userId : string , info : GitHubInfo ) : void {
594- const config = loadOdeConfig ( ) ;
595- const githubInfos = { ...( config . githubInfos ?? { } ) } ;
596- const token = info . token ?. trim ( ) || "" ;
597- const gitName = info . gitName ?. trim ( ) || "" ;
598- const gitEmail = info . gitEmail ?. trim ( ) || "" ;
599- if ( ! token && ! gitName && ! gitEmail ) {
600- delete githubInfos [ userId ] ;
601- } else {
602- githubInfos [ userId ] = {
603- token,
604- gitName,
605- gitEmail,
606- } ;
607- }
608- saveOdeConfig ( { ...config , githubInfos } ) ;
625+ updateOdeConfig ( ( config ) => {
626+ const githubInfos = { ...( config . githubInfos ?? { } ) } ;
627+ const token = info . token ?. trim ( ) || "" ;
628+ const gitName = info . gitName ?. trim ( ) || "" ;
629+ const gitEmail = info . gitEmail ?. trim ( ) || "" ;
630+ if ( ! token && ! gitName && ! gitEmail ) {
631+ delete githubInfos [ userId ] ;
632+ } else {
633+ githubInfos [ userId ] = {
634+ token,
635+ gitName,
636+ gitEmail,
637+ } ;
638+ }
639+ return { ...config , githubInfos } ;
640+ } ) ;
609641}
610642
611643export function clearGitHubInfoForUser ( userId : string ) : void {
612- const config = loadOdeConfig ( ) ;
613- const githubInfos = { ...( config . githubInfos ?? { } ) } ;
614- if ( ! ( userId in githubInfos ) ) return ;
615- delete githubInfos [ userId ] ;
616- saveOdeConfig ( { ... config , githubInfos } ) ;
644+ updateOdeConfig ( ( config ) => {
645+ const githubInfos = { ...( config . githubInfos ?? { } ) } ;
646+ delete githubInfos [ userId ] ;
647+ return { ... config , githubInfos } ;
648+ } ) ;
617649}
618650
619651export type ChannelCwdInfo = {
@@ -701,20 +733,21 @@ function updateChannel(
701733 channelId : string ,
702734 updater : ( channel : ChannelDetail ) => ChannelDetail
703735) : void {
704- const config = loadOdeConfig ( ) ;
705- let found = false ;
706- const workspaces = config . workspaces . map ( ( workspace ) => {
707- const channelDetails = workspace . channelDetails . map ( ( channel ) => {
708- if ( channel . id !== channelId ) return channel ;
709- found = true ;
710- return updater ( channel ) ;
736+ let updated = false ;
737+ updateOdeConfig ( ( config ) => {
738+ const workspaces = config . workspaces . map ( ( workspace ) => {
739+ const channelDetails = workspace . channelDetails . map ( ( channel ) => {
740+ if ( channel . id !== channelId ) return channel ;
741+ updated = true ;
742+ return updater ( channel ) ;
743+ } ) ;
744+ return { ...workspace , channelDetails } ;
711745 } ) ;
712- return { ...workspace , channelDetails } ;
713- } ) ;
714746
715- if ( ! found ) {
716- throw new Error ( "Channel not found in ~/.config/ode/ode.json" ) ;
717- }
747+ if ( ! updated ) {
748+ throw new Error ( "Channel not found in ~/.config/ode/ode.json" ) ;
749+ }
718750
719- saveOdeConfig ( { ...config , workspaces } ) ;
751+ return { ...config , workspaces } ;
752+ } ) ;
720753}
0 commit comments