feat(coding-agent): migrated settings and models to models.yml

- Settings now stored in ~/.omp/agent/config.yaml instead of agent.db
- Models config now supports models.yaml (preferred) alongside models.json
- Auto-migration from agent.db settings and models.json on first run
- AgentStorage.saveSettings() deprecated (getSettings() kept for migration)
- storage-migration.ts now only handles auth.json migration
This commit is contained in:
can1357
2026-01-23 13:46:06 +01:00
parent 6e5fa5b24d
commit fb68e72d78
6 changed files with 186 additions and 133 deletions
@@ -116,8 +116,6 @@ export class AgentStorage {
private static instances = new Map<string, AgentStorage>();
private listSettingsStmt: Statement;
private insertSettingStmt: Statement;
private deleteSettingsStmt: Statement;
private getCacheStmt: Statement;
private upsertCacheStmt: Statement;
private deleteExpiredCacheStmt: Statement;
@@ -137,10 +135,6 @@ export class AgentStorage {
this.hardenPermissions(dbPath);
this.listSettingsStmt = this.db.prepare("SELECT key, value FROM settings");
this.insertSettingStmt = this.db.prepare(
"INSERT INTO settings (key, value, updated_at) VALUES (?, ?, unixepoch())",
);
this.deleteSettingsStmt = this.db.prepare("DELETE FROM settings");
this.getCacheStmt = this.db.prepare("SELECT value FROM cache WHERE key = ? AND expires_at > unixepoch()");
this.upsertCacheStmt = this.db.prepare(
@@ -276,8 +270,11 @@ CREATE TABLE settings (
}
/**
* Retrieves all settings from storage.
* Retrieves all settings from storage (legacy, for migration only).
* Settings are now stored in config.yml. This method is only used
* during migration from agent.db to config.yml.
* @returns Settings object, or null if no settings are stored
* @deprecated Use config.yml instead. This is only for migration.
*/
getSettings(): Settings | null {
const rows = (this.listSettingsStmt.all() as SettingsRow[]) ?? [];
@@ -297,26 +294,13 @@ CREATE TABLE settings (
}
/**
* Atomically replaces all settings in storage.
* Uses delete-then-insert within a transaction for consistency.
* @param settings - Settings object to persist
* @deprecated Settings are now stored in config.yml, not agent.db.
* This method is kept for backward compatibility but does nothing.
*/
saveSettings(settings: Settings): void {
const entries = Object.entries(settings).filter(([, value]) => value !== undefined);
const replace = this.db.transaction((rows: Array<[string, unknown]>) => {
this.deleteSettingsStmt.run();
for (const [key, value] of rows) {
const serialized = JSON.stringify(value);
if (serialized === undefined) continue;
this.insertSettingStmt.run(key, serialized);
}
logger.warn("AgentStorage.saveSettings is deprecated - settings are now stored in config.yml", {
keys: Object.keys(settings),
});
try {
replace(entries);
} catch (error) {
logger.error("AgentStorage failed to save settings", { error: String(error) });
}
}
/**
@@ -1,12 +1,14 @@
/**
* Migrates legacy JSON storage (settings.json, auth.json) to SQLite-based agent.db.
* Settings migrate only when the DB has no settings; auth merges per-provider when missing.
* Migrates legacy auth.json to SQLite-based agent.db.
* Auth credentials merge per-provider when missing.
* Original JSON files are backed up to .bak and removed after successful migration.
*
* NOTE: Settings migration is now handled by SettingsManager.migrateToYaml(),
* which migrates from both settings.json and agent.db to config.yaml.
*/
import { logger } from "@oh-my-pi/pi-utils";
import { getAgentDbPath } from "$c/config";
import type { Settings } from "$c/config/settings-manager";
import { AgentStorage } from "./agent-storage";
import type { AuthCredential, AuthCredentialEntry, AuthStorageData } from "./auth-storage";
@@ -14,7 +16,7 @@ import type { AuthCredential, AuthCredentialEntry, AuthStorageData } from "./aut
type MigrationPaths = {
/** Directory containing agent.db */
agentDir: string;
/** Path to legacy settings.json file */
/** Path to legacy settings.json file (kept for API compatibility, no longer used) */
settingsPath: string;
/** Candidate paths to search for auth.json (checked in order) */
authPaths: string[];
@@ -22,7 +24,7 @@ type MigrationPaths = {
/** Result of the JSON-to-SQLite storage migration. */
export interface StorageMigrationResult {
/** Whether settings.json was migrated to agent.db */
/** Whether settings.json was migrated (always false - settings migration is handled elsewhere) */
migratedSettings: boolean;
/** Whether auth.json was migrated to agent.db */
migratedAuth: boolean;
@@ -39,20 +41,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}
/**
* Transforms legacy settings to current schema (e.g., queueMode -> steeringMode).
* @param settings - Settings object potentially containing deprecated keys
* @returns Settings with deprecated keys renamed to current equivalents
*/
function migrateLegacySettings(settings: Settings): Settings {
const migrated = { ...settings } as Record<string, unknown>;
if ("queueMode" in migrated && !("steeringMode" in migrated)) {
migrated.steeringMode = migrated.queueMode;
delete migrated.queueMode;
}
return migrated as Settings;
}
/**
* Normalizes credential entries to array format (legacy stored single credentials).
* @param entry - Single credential or array of credentials
@@ -99,32 +87,6 @@ async function backupJson(path: string): Promise<void> {
}
}
/**
* Migrates settings.json to SQLite storage if DB is empty.
* @param storage - AgentStorage instance to migrate into
* @param settingsPath - Path to legacy settings.json
* @param warnings - Array to collect non-fatal warnings
* @returns True if migration was performed
*/
async function migrateSettings(storage: AgentStorage, settingsPath: string, warnings: string[]): Promise<boolean> {
const settingsFile = Bun.file(settingsPath);
const settingsExists = await settingsFile.exists();
const hasDbSettings = storage.getSettings() !== null;
if (!settingsExists) return false;
if (hasDbSettings) {
warnings.push(`settings.json exists but agent.db is authoritative: ${settingsPath}`);
return false;
}
const settingsJson = await readJsonFile<Settings>(settingsPath);
if (!settingsJson) return false;
storage.saveSettings(migrateLegacySettings(settingsJson));
await backupJson(settingsPath);
return true;
}
/**
* Finds the first valid auth.json from candidate paths (checked in priority order).
* @param authPaths - Candidate paths to search (e.g., project-local before global)
@@ -191,8 +153,8 @@ async function migrateAuth(storage: AgentStorage, authPaths: string[], warnings:
}
/**
* Migrates legacy JSON files (settings.json, auth.json) to SQLite-based agent.db.
* Settings migrate only when the DB has no settings; auth merges per-provider when missing.
* Migrates legacy auth.json to SQLite-based agent.db.
* Settings migration is handled separately by SettingsManager.migrateToYaml().
* @param paths - Configuration specifying locations of legacy files and target DB
* @returns Result indicating what was migrated and any warnings encountered
*/
@@ -200,10 +162,7 @@ export async function migrateJsonStorage(paths: MigrationPaths): Promise<Storage
const storage = AgentStorage.open(getAgentDbPath(paths.agentDir));
const warnings: string[] = [];
const [migratedSettings, migratedAuth] = await Promise.all([
migrateSettings(storage, paths.settingsPath, warnings),
migrateAuth(storage, paths.authPaths, warnings),
]);
const migratedAuth = await migrateAuth(storage, paths.authPaths, warnings);
if (warnings.length > 0) {
for (const warning of warnings) {
@@ -211,5 +170,5 @@ export async function migrateJsonStorage(paths: MigrationPaths): Promise<Storage
}
}
return { migratedSettings, migratedAuth, warnings };
return { migratedSettings: false, migratedAuth, warnings };
}