refactor: remove legacy config and state aliases (PR5)

This commit is contained in:
ejclaw
2026-04-10 22:09:04 +09:00
parent 312d19c0d1
commit e5bd721ac4
18 changed files with 155 additions and 980 deletions

View File

@@ -1,9 +1,9 @@
import fs from 'fs';
import path from 'path';
import { Database } from 'bun:sqlite';
import { DATA_DIR, STORE_DIR } from '../src/config.js';
import { listUnexpectedDataStateFiles } from '../src/data-state-files.js';
import {
initializeDatabaseSchema,
openPersistentDatabase,
@@ -11,112 +11,16 @@ import {
import {
buildLegacyRoomMigrationPlan,
getPendingLegacyRegisteredGroupJids,
getStoredRoomSettingsRowFromDatabase,
insertStoredRoomSettingsFromMigration,
normalizeStoredAgentType,
upsertRoomRoleOverride,
} from '../src/db/room-registration.js';
import type { RegisteredGroup } from '../src/types.js';
import { isValidGroupFolder } from '../src/group-folder.js';
import { logger } from '../src/logger.js';
import { readJsonFile } from '../src/utils.js';
import { emitStatus } from './status.js';
interface MigrationReport {
migratedRooms: number;
migratedRoleOverrides: number;
skippedRooms: number;
migratedJsonRooms: number;
skippedJsonRooms: number;
backedUpLegacyRows: number;
renamedLegacyJson: boolean;
}
interface StoredOwnerRoleOverride {
agentType: string;
agentConfig?: RegisteredGroup['agentConfig'];
}
function normalizeAgentConfigValue(
agentConfig: RegisteredGroup['agentConfig'] | undefined,
): string {
return JSON.stringify(agentConfig ?? null);
}
function jsonEntryMatchesStoredRoom(
jid: string,
group: RegisteredGroup,
database: Database,
): boolean {
const existing = getStoredRoomSettingsRowFromDatabase(database, jid);
if (!existing) {
return false;
}
const ownerAgentType =
normalizeStoredAgentType(group.agentType) ?? 'claude-code';
return (
existing.roomMode === 'single' &&
existing.name === group.name &&
existing.folder === group.folder &&
existing.trigger === group.trigger &&
(existing.requiresTrigger ?? true) === (group.requiresTrigger ?? true) &&
(existing.isMain ?? false) === (group.isMain ?? false) &&
existing.ownerAgentType === ownerAgentType &&
(existing.workDir ?? null) === (group.workDir ?? null)
);
}
function getStoredOwnerRoleOverride(
database: Database,
jid: string,
): StoredOwnerRoleOverride | undefined {
try {
const row = database
.prepare(
`SELECT agent_type, agent_config_json
FROM room_role_overrides
WHERE chat_jid = ?
AND role = 'owner'`,
)
.get(jid) as
| {
agent_type: string | null;
agent_config_json: string | null;
}
| undefined;
const agentType = normalizeStoredAgentType(row?.agent_type);
if (!agentType) {
return undefined;
}
return {
agentType,
agentConfig: row?.agent_config_json
? JSON.parse(row.agent_config_json)
: undefined,
};
} catch {
return undefined;
}
}
function jsonEntryMatchesStoredOwnerOverride(
database: Database,
jid: string,
group: RegisteredGroup,
): boolean {
const existingOverride = getStoredOwnerRoleOverride(database, jid);
if (!existingOverride) {
return false;
}
const ownerAgentType =
normalizeStoredAgentType(group.agentType) ?? 'claude-code';
return (
existingOverride.agentType === ownerAgentType &&
normalizeAgentConfigValue(existingOverride.agentConfig) ===
normalizeAgentConfigValue(group.agentConfig)
);
}
function ensureLegacyBackupTable(database: Database): void {
@@ -217,125 +121,15 @@ function migrateLegacyRegisteredGroupsTable(
}
}
function loadValidatedLegacyRegisteredGroupsJson(
database: Database,
): Array<{ jid: string; group: RegisteredGroup }> {
const legacyJsonPath = path.join(DATA_DIR, 'registered_groups.json');
const legacyJson = readJsonFile(legacyJsonPath) as Record<
string,
RegisteredGroup
> | null;
if (!legacyJson) {
return [];
}
const entries = Object.entries(legacyJson);
for (const [jid, group] of entries) {
if (!isValidGroupFolder(group.folder)) {
throw new Error(
`Invalid legacy registered_groups.json folder for ${jid}: ${group.folder}`,
);
}
const existing = getStoredRoomSettingsRowFromDatabase(database, jid);
if (existing && !jsonEntryMatchesStoredRoom(jid, group, database)) {
throw new Error(
`Legacy registered_groups.json entry collides with existing room_settings row for ${jid}`,
);
}
}
return entries.map(([jid, group]) => ({ jid, group }));
}
function migrateLegacyRegisteredGroupsJson(
database: Database,
projectRoot: string,
report: MigrationReport,
): void {
const legacyJsonPath = path.join(DATA_DIR, 'registered_groups.json');
const legacyJsonEntries = loadValidatedLegacyRegisteredGroupsJson(database);
if (legacyJsonEntries.length === 0) {
return;
}
const insertRoom = database.prepare(
`INSERT INTO room_settings (
chat_jid,
room_mode,
mode_source,
name,
folder,
trigger_pattern,
requires_trigger,
is_main,
owner_agent_type,
work_dir,
created_at,
updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(chat_jid) DO NOTHING`,
);
for (const { jid, group } of legacyJsonEntries) {
const createdAt = group.added_at || new Date().toISOString();
const ownerAgentType =
normalizeStoredAgentType(group.agentType) ?? 'claude-code';
const existing = getStoredRoomSettingsRowFromDatabase(database, jid);
const existingOwnerOverride = getStoredOwnerRoleOverride(database, jid);
if (!existing) {
insertRoom.run(
jid,
'single',
'inferred',
group.name,
group.folder,
group.trigger,
(group.requiresTrigger ?? true) ? 1 : 0,
(group.isMain ?? false) ? 1 : 0,
ownerAgentType,
group.workDir ?? null,
createdAt,
createdAt,
);
report.migratedRooms += 1;
report.migratedJsonRooms += 1;
}
if (
existingOwnerOverride &&
!jsonEntryMatchesStoredOwnerOverride(database, jid, group)
) {
throw new Error(
`Legacy registered_groups.json owner override conflicts with existing owner override for ${jid}`,
);
}
if (!existingOwnerOverride) {
upsertRoomRoleOverride(database, jid, {
role: 'owner',
agentType: ownerAgentType,
agentConfig: group.agentConfig,
createdAt,
updatedAt: createdAt,
});
report.migratedRoleOverrides += 1;
}
}
fs.renameSync(legacyJsonPath, `${legacyJsonPath}.migrated`);
report.renamedLegacyJson = true;
logger.info(
{ legacyJsonPath, projectRoot },
'Renamed registered_groups.json after explicit migration',
);
}
export async function run(_args: string[]): Promise<void> {
fs.mkdirSync(STORE_DIR, { recursive: true });
const projectRoot = process.cwd();
const unexpectedDataStateFiles = listUnexpectedDataStateFiles(DATA_DIR);
if (unexpectedDataStateFiles.length > 0) {
throw new Error(
`Unexpected data state files detected before room migration (files=${unexpectedDataStateFiles.join(',')})`,
);
}
const database = openPersistentDatabase();
initializeDatabaseSchema(database);
@@ -343,16 +137,12 @@ export async function run(_args: string[]): Promise<void> {
migratedRooms: 0,
migratedRoleOverrides: 0,
skippedRooms: 0,
migratedJsonRooms: 0,
skippedJsonRooms: 0,
backedUpLegacyRows: 0,
renamedLegacyJson: false,
};
database.transaction(() => {
report.backedUpLegacyRows = backupPendingLegacyRows(database);
migrateLegacyRegisteredGroupsTable(database, report);
migrateLegacyRegisteredGroupsJson(database, projectRoot, report);
})();
database.close();
@@ -361,10 +151,7 @@ export async function run(_args: string[]): Promise<void> {
MIGRATED_ROOMS: report.migratedRooms,
MIGRATED_ROLE_OVERRIDES: report.migratedRoleOverrides,
SKIPPED_ROOMS: report.skippedRooms,
MIGRATED_JSON_ROOMS: report.migratedJsonRooms,
SKIPPED_JSON_ROOMS: report.skippedJsonRooms,
BACKED_UP_LEGACY_ROWS: report.backedUpLegacyRows,
RENAMED_LEGACY_JSON: report.renamedLegacyJson,
STATUS: 'success',
LOG: 'logs/setup.log',
});