fix(dashboard): align droid config diagnostics with factory paths

- replace ~/.config/factory/config.json diagnostics with legacy ~/.factory/config.json

- keep ~/.factory/settings.json as the primary BYOK source in UI + API payload

- update droid dashboard tests and frontend typings for legacyConfig
This commit is contained in:
Tam Nhu Tran
2026-02-25 23:26:23 +07:00
parent bc079bc886
commit c6d2e71ec2
5 changed files with 21 additions and 32 deletions
@@ -48,7 +48,7 @@ export interface DroidDashboardDiagnostics {
binary: DroidBinaryDiagnostics; binary: DroidBinaryDiagnostics;
files: { files: {
settings: DroidConfigFileDiagnostics; settings: DroidConfigFileDiagnostics;
globalConfig: DroidConfigFileDiagnostics; legacyConfig: DroidConfigFileDiagnostics;
}; };
byok: DroidByokDiagnostics; byok: DroidByokDiagnostics;
warnings: string[]; warnings: string[];
@@ -14,8 +14,8 @@ import type {
interface DroidConfigPaths { interface DroidConfigPaths {
settingsPath: string; settingsPath: string;
settingsDisplayPath: string; settingsDisplayPath: string;
globalConfigPath: string; legacyConfigPath: string;
globalConfigDisplayPath: string; legacyConfigDisplayPath: string;
} }
interface JsonFileProbe { interface JsonFileProbe {
@@ -57,25 +57,18 @@ export function resolveDroidConfigPaths(
homeDir?: string; homeDir?: string;
} = {} } = {}
): DroidConfigPaths { ): DroidConfigPaths {
const platform = options.platform ?? process.platform;
const env = options.env ?? process.env; const env = options.env ?? process.env;
const homeDir = options.homeDir ?? os.homedir(); const homeDir = options.homeDir ?? os.homedir();
const byokBase = env.CCS_HOME || homeDir; const byokBase = env.CCS_HOME || homeDir;
const settingsPath = path.join(byokBase, '.factory', 'settings.json'); const settingsPath = path.join(byokBase, '.factory', 'settings.json');
const legacyConfigPath = path.join(byokBase, '.factory', 'config.json');
const globalConfigRoot =
platform === 'win32'
? env.APPDATA || path.join(homeDir, 'AppData', 'Roaming')
: env.XDG_CONFIG_HOME || path.join(homeDir, '.config');
const globalConfigPath = path.join(globalConfigRoot, 'factory', 'config.json');
return { return {
settingsPath, settingsPath,
settingsDisplayPath: '~/.factory/settings.json', settingsDisplayPath: '~/.factory/settings.json',
globalConfigPath, legacyConfigPath,
globalConfigDisplayPath: legacyConfigDisplayPath: '~/.factory/config.json',
platform === 'win32' ? '%APPDATA%/factory/config.json' : '~/.config/factory/config.json',
}; };
} }
@@ -225,10 +218,10 @@ export function getDroidDashboardDiagnostics(): DroidDashboardDiagnostics {
'BYOK settings', 'BYOK settings',
paths.settingsDisplayPath paths.settingsDisplayPath
); );
const globalConfigProbe = readJsonFileProbe( const legacyConfigProbe = readJsonFileProbe(
paths.globalConfigPath, paths.legacyConfigPath,
'Global config', 'Legacy config',
paths.globalConfigDisplayPath paths.legacyConfigDisplayPath
); );
const byok = summarizeDroidCustomModels(settingsProbe.json?.customModels); const byok = summarizeDroidCustomModels(settingsProbe.json?.customModels);
@@ -242,8 +235,8 @@ export function getDroidDashboardDiagnostics(): DroidDashboardDiagnostics {
if (byok.invalidModelEntryCount > 0) { if (byok.invalidModelEntryCount > 0) {
warnings.push(`${byok.invalidModelEntryCount} customModels entries are malformed.`); warnings.push(`${byok.invalidModelEntryCount} customModels entries are malformed.`);
} }
if (globalConfigProbe.diagnostics.parseError) { if (legacyConfigProbe.diagnostics.parseError) {
warnings.push('Global Droid config JSON is invalid.'); warnings.push('Legacy Droid config (~/.factory/config.json) JSON is invalid.');
} }
return { return {
@@ -257,7 +250,7 @@ export function getDroidDashboardDiagnostics(): DroidDashboardDiagnostics {
}, },
files: { files: {
settings: settingsProbe.diagnostics, settings: settingsProbe.diagnostics,
globalConfig: globalConfigProbe.diagnostics, legacyConfig: legacyConfigProbe.diagnostics,
}, },
byok, byok,
warnings, warnings,
@@ -271,6 +264,7 @@ export function getDroidDashboardDiagnostics(): DroidDashboardDiagnostics {
], ],
notes: [ notes: [
'BYOK custom models are read from ~/.factory/settings.json customModels[]', 'BYOK custom models are read from ~/.factory/settings.json customModels[]',
'Factory docs mention legacy support for ~/.factory/config.json',
'Interactive model selection uses settings.model (custom:<alias>)', 'Interactive model selection uses settings.model (custom:<alias>)',
'droid exec supports --model for one-off execution mode', 'droid exec supports --model for one-off execution mode',
], ],
@@ -29,31 +29,26 @@ describe('droid-dashboard-service', () => {
platform: 'darwin', platform: 'darwin',
env: { env: {
CCS_HOME: '/tmp/ccs-home', CCS_HOME: '/tmp/ccs-home',
XDG_CONFIG_HOME: '/tmp/xdg',
} as NodeJS.ProcessEnv, } as NodeJS.ProcessEnv,
homeDir: '/Users/tester', homeDir: '/Users/tester',
}); });
expect(resolved.settingsPath).toBe('/tmp/ccs-home/.factory/settings.json'); expect(resolved.settingsPath).toBe('/tmp/ccs-home/.factory/settings.json');
expect(resolved.globalConfigPath).toBe('/tmp/xdg/factory/config.json'); expect(resolved.legacyConfigPath).toBe('/tmp/ccs-home/.factory/config.json');
expect(resolved.settingsDisplayPath).toBe('~/.factory/settings.json'); expect(resolved.settingsDisplayPath).toBe('~/.factory/settings.json');
expect(resolved.globalConfigDisplayPath).toBe('~/.config/factory/config.json'); expect(resolved.legacyConfigDisplayPath).toBe('~/.factory/config.json');
}); });
it('resolves droid config paths on windows platforms', () => { it('resolves droid config paths on windows platforms', () => {
const resolved = resolveDroidConfigPaths({ const resolved = resolveDroidConfigPaths({
platform: 'win32', platform: 'win32',
env: { env: {} as NodeJS.ProcessEnv,
APPDATA: 'C:/Users/test/AppData/Roaming',
} as NodeJS.ProcessEnv,
homeDir: 'C:/Users/test', homeDir: 'C:/Users/test',
}); });
expect(resolved.settingsPath).toBe(path.join('C:/Users/test', '.factory', 'settings.json')); expect(resolved.settingsPath).toBe(path.join('C:/Users/test', '.factory', 'settings.json'));
expect(resolved.globalConfigPath).toBe( expect(resolved.legacyConfigPath).toBe(path.join('C:/Users/test', '.factory', 'config.json'));
path.join('C:/Users/test/AppData/Roaming', 'factory', 'config.json') expect(resolved.legacyConfigDisplayPath).toBe('~/.factory/config.json');
);
expect(resolved.globalConfigDisplayPath).toBe('%APPDATA%/factory/config.json');
}); });
it('masks api key preview with only suffix', () => { it('masks api key preview with only suffix', () => {
+1 -1
View File
@@ -40,7 +40,7 @@ export interface DroidDashboardDiagnostics {
binary: DroidBinaryDiagnostics; binary: DroidBinaryDiagnostics;
files: { files: {
settings: DroidConfigFileDiagnostics; settings: DroidConfigFileDiagnostics;
globalConfig: DroidConfigFileDiagnostics; legacyConfig: DroidConfigFileDiagnostics;
}; };
byok: { byok: {
activeModelSelector: string | null; activeModelSelector: string | null;
+1 -1
View File
@@ -147,7 +147,7 @@ export function DroidPage() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
{[diagnostics.files.settings, diagnostics.files.globalConfig].map((file) => ( {[diagnostics.files.settings, diagnostics.files.legacyConfig].map((file) => (
<div key={file.label} className="rounded-md border p-3 space-y-1.5"> <div key={file.label} className="rounded-md border p-3 space-y-1.5">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<span className="font-medium text-sm">{file.label}</span> <span className="font-medium text-sm">{file.label}</span>