Skip to content

Commit ef97f56

Browse files
committed
refactor: unify dashboard config updates behind cached API
1 parent 712b722 commit ef97f56

File tree

3 files changed

+144
-131
lines changed

3 files changed

+144
-131
lines changed

packages/config/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export {
44
loadOdeConfig,
55
invalidateOdeConfigCache,
66
saveOdeConfig,
7+
updateOdeConfig,
8+
readDashboardConfig,
9+
writeDashboardConfig,
10+
updateDashboardConfig,
711
getWorkspaces,
812
getAgentsConfig,
913
getEnabledAgentProviders,

packages/config/local/ode.ts

Lines changed: 119 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import * as os from "os";
33
import * as path from "path";
44
import { z } from "zod";
55
import { normalizeCwd } from "../paths";
6+
import {
7+
sanitizeDashboardConfig,
8+
type DashboardConfig,
9+
} from "../dashboard-config";
610

711
const existsSync = fs.existsSync;
812
const 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 {
334316
export 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

359394
export function getWorkspaces(): WorkspaceConfig[] {
@@ -395,8 +430,7 @@ export function getOpenCodeModels(): string[] {
395430
}
396431

397432
export 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

411445
export function getCodexModels(): string[] {
412446
return getAgentsConfig().codex.models;
413447
}
414448

415449
export 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

429462
export function getKiloModels(): string[] {
430463
return getAgentsConfig().kilo.models ?? [];
431464
}
432465

433466
export 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

447479
export function getUpdateConfig(): UpdateConfig {
@@ -579,41 +611,41 @@ export function getUserGeneralSettings(): UserGeneralSettings {
579611
}
580612

581613
export 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

593624
export 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

611643
export 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

619651
export 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

Comments
 (0)