feat(coding-agent): migrated settings and auth storage from JSON files to SQLite database

- Migrated settings and auth credential storage from JSON files (settings.json, auth.json) to SQLite database (agent.db).
- Added AgentStorage class implementing unified SQLite storage with WAL mode, schema versioning, and prepared statement caching.
- Added storage-migration module to handle one-time migration from legacy JSON files to SQLite with backup creation.
- Updated credential migration message to reference agent.db instead of auth.json.
- Removed file locking mechanism from AuthStorage since SQLite handles concurrency natively.
- Added getAgentDbPath() config function returning path to SQLite database file.
This commit is contained in:
can1357
2026-01-10 23:24:35 +01:00
parent 7e10c29792
commit 09427086d9
12 changed files with 1001 additions and 311 deletions
+14 -16
View File
@@ -3,9 +3,11 @@
*/
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { join } from "node:path";
import chalk from "chalk";
import { getAgentDir, getBinDir } from "./config";
import { getAgentDbPath, getAgentDir, getBinDir } from "./config";
import { AgentStorage } from "./core/agent-storage";
import type { AuthCredential } from "./core/auth-storage";
/**
* Migrate PI_* environment variables to OMP_* equivalents.
@@ -29,28 +31,26 @@ export function migrateEnvVars(): string[] {
}
/**
* Migrate legacy oauth.json and settings.json apiKeys to auth.json.
* Migrate legacy oauth.json and settings.json apiKeys to agent.db.
*
* @returns Array of provider names that were migrated
*/
export function migrateAuthToAuthJson(): string[] {
export function migrateAuthToAgentDb(): string[] {
const agentDir = getAgentDir();
const authPath = join(agentDir, "auth.json");
const oauthPath = join(agentDir, "oauth.json");
const settingsPath = join(agentDir, "settings.json");
const storage = AgentStorage.open(getAgentDbPath(agentDir));
// Skip if auth.json already exists
if (existsSync(authPath)) return [];
if (storage.hasAuthCredentials()) return [];
const migrated: Record<string, unknown> = {};
const migrated: Record<string, AuthCredential[]> = {};
const providers: string[] = [];
// Migrate oauth.json
if (existsSync(oauthPath)) {
try {
const oauth = JSON.parse(readFileSync(oauthPath, "utf-8"));
for (const [provider, cred] of Object.entries(oauth)) {
migrated[provider] = { type: "oauth", ...(cred as object) };
migrated[provider] = [{ type: "oauth", ...(cred as object) } as AuthCredential];
providers.push(provider);
}
renameSync(oauthPath, `${oauthPath}.migrated`);
@@ -59,7 +59,6 @@ export function migrateAuthToAuthJson(): string[] {
}
}
// Migrate settings.json apiKeys
if (existsSync(settingsPath)) {
try {
const content = readFileSync(settingsPath, "utf-8");
@@ -67,7 +66,7 @@ export function migrateAuthToAuthJson(): string[] {
if (settings.apiKeys && typeof settings.apiKeys === "object") {
for (const [provider, key] of Object.entries(settings.apiKeys)) {
if (!migrated[provider] && typeof key === "string") {
migrated[provider] = { type: "api_key", key };
migrated[provider] = [{ type: "api_key", key }];
providers.push(provider);
}
}
@@ -79,9 +78,8 @@ export function migrateAuthToAuthJson(): string[] {
}
}
if (Object.keys(migrated).length > 0) {
mkdirSync(dirname(authPath), { recursive: true });
writeFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });
for (const [provider, credentials] of Object.entries(migrated)) {
storage.replaceAuthCredentialsForProvider(provider, credentials);
}
return providers;
@@ -201,7 +199,7 @@ export async function runMigrations(_cwd: string): Promise<{
const migratedEnvVars = migrateEnvVars();
// Then: run data migrations
const migratedAuthProviders = migrateAuthToAuthJson();
const migratedAuthProviders = migrateAuthToAgentDb();
migrateSessionsFromAgentRoot();
migrateToolsToBin();