Phase 2 / PR #3 of the module refactor. Moves the approval and interactive- question flows out of core and into src/modules/, wired through the response dispatcher and delivery action registries. New modules: - src/modules/interactive/ — registers a response handler that claims pending_questions rows, writes question_response to the session DB, wakes the container. createPendingQuestion call stays inline in delivery.ts (guarded by hasTable) per plan. - src/modules/approvals/ — registers 3 delivery actions (install_packages, request_rebuild, add_mcp_server), a response handler for pending_approvals (including OneCLI action fall-through), an adapter-ready hook that boots the OneCLI manual-approval handler, and a shutdown hook that stops it. OneCLI implementation (src/onecli-approvals.ts) moves into the module. Core lifecycle hooks added (narrow, not registries): - onDeliveryAdapterReady(cb) in delivery.ts — fires when setDeliveryAdapter runs (or immediately if already set). Used by approvals for OneCLI boot. - onShutdown(cb) in index.ts — fires on SIGTERM/SIGINT. Used by approvals for OneCLI teardown. - getDeliveryAdapter() getter in delivery.ts — for live-flow adapter access in registered delivery actions. Core shrinks: delivery.ts 911 → 665 lines, index.ts 405 → 224 lines. dispatchResponse now logs "Unclaimed response" instead of falling through to an inline handler — the inline fallback moved into the two modules. Migration files renamed to the module-<name>-<short>.ts convention: - 003-pending-approvals.ts → module-approvals-pending-approvals.ts - 007-pending-approvals-title-options.ts → module-approvals-title-options.ts Migration.name fields unchanged so existing DBs treat them as already-applied. Degradation verified: emptying the modules barrel builds clean and 137/137 tests pass. Actions would log "Unknown system action"; button clicks would log "Unclaimed response". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
import type Database from 'better-sqlite3';
|
|
|
|
import { log } from '../../log.js';
|
|
import { migration001 } from './001-initial.js';
|
|
import { migration002 } from './002-chat-sdk-state.js';
|
|
import { migration004 } from './004-agent-destinations.js';
|
|
import { migration008 } from './008-dropped-messages.js';
|
|
import { migration009 } from './009-drop-pending-credentials.js';
|
|
import { moduleApprovalsPendingApprovals } from './module-approvals-pending-approvals.js';
|
|
import { moduleApprovalsTitleOptions } from './module-approvals-title-options.js';
|
|
|
|
export interface Migration {
|
|
version: number;
|
|
name: string;
|
|
up: (db: Database.Database) => void;
|
|
}
|
|
|
|
const migrations: Migration[] = [
|
|
migration001,
|
|
migration002,
|
|
moduleApprovalsPendingApprovals,
|
|
migration004,
|
|
moduleApprovalsTitleOptions,
|
|
migration008,
|
|
migration009,
|
|
];
|
|
|
|
export function runMigrations(db: Database.Database): void {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
version INTEGER PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
applied TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_schema_version_name ON schema_version(name);
|
|
`);
|
|
|
|
// Uniqueness is keyed on `name`, not `version`. This lets module
|
|
// migrations (added later by install skills) pick arbitrary version
|
|
// numbers without coordinating across modules. `version` stays on
|
|
// the Migration object as an ordering hint within the barrel array;
|
|
// the stored `version` column is auto-assigned at insert time as an
|
|
// applied-order number.
|
|
const applied = new Set<string>(
|
|
(db.prepare('SELECT name FROM schema_version').all() as { name: string }[]).map((r) => r.name),
|
|
);
|
|
const pending = migrations.filter((m) => !applied.has(m.name));
|
|
if (pending.length === 0) return;
|
|
|
|
log.info('Running migrations', { count: pending.length });
|
|
|
|
for (const m of pending) {
|
|
db.transaction(() => {
|
|
m.up(db);
|
|
const next =
|
|
(db.prepare('SELECT COALESCE(MAX(version), 0) + 1 AS v FROM schema_version').get() as { v: number }).v;
|
|
db.prepare('INSERT INTO schema_version (version, name, applied) VALUES (?, ?, ?)').run(
|
|
next,
|
|
m.name,
|
|
new Date().toISOString(),
|
|
);
|
|
})();
|
|
log.info('Migration applied', { name: m.name });
|
|
}
|
|
}
|