@stackmemoryai/stackmemory 0.5.36 → 0.5.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/context/frame-database.js +40 -9
- package/dist/core/context/frame-database.js.map +2 -2
- package/dist/core/context/frame-recovery.js +302 -0
- package/dist/core/context/frame-recovery.js.map +7 -0
- package/dist/core/context/index.js +7 -1
- package/dist/core/context/index.js.map +2 -2
- package/dist/core/context/refactored-frame-manager.js +37 -1
- package/dist/core/context/refactored-frame-manager.js.map +2 -2
- package/package.json +1 -1
|
@@ -4,6 +4,9 @@ const __filename = __fileURLToPath(import.meta.url);
|
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { logger } from "../monitoring/logger.js";
|
|
6
6
|
import { DatabaseError, ErrorCode } from "../errors/index.js";
|
|
7
|
+
const DEFAULT_FRAME_LIMIT = 1e3;
|
|
8
|
+
const DEFAULT_EVENT_LIMIT = 500;
|
|
9
|
+
const DEFAULT_ANCHOR_LIMIT = 200;
|
|
7
10
|
function safeJsonParse(json, fallback) {
|
|
8
11
|
if (!json) return fallback;
|
|
9
12
|
try {
|
|
@@ -221,10 +224,10 @@ class FrameDatabase {
|
|
|
221
224
|
/**
|
|
222
225
|
* Get frames by project and state
|
|
223
226
|
*/
|
|
224
|
-
getFramesByProject(projectId, state) {
|
|
227
|
+
getFramesByProject(projectId, state, limit = DEFAULT_FRAME_LIMIT) {
|
|
225
228
|
try {
|
|
226
|
-
const query = state ? "SELECT * FROM frames WHERE project_id = ? AND state = ? ORDER BY created_at" : "SELECT * FROM frames WHERE project_id = ? ORDER BY created_at";
|
|
227
|
-
const params = state ? [projectId, state] : [projectId];
|
|
229
|
+
const query = state ? "SELECT * FROM frames WHERE project_id = ? AND state = ? ORDER BY created_at LIMIT ?" : "SELECT * FROM frames WHERE project_id = ? ORDER BY created_at LIMIT ?";
|
|
230
|
+
const params = state ? [projectId, state, limit] : [projectId, limit];
|
|
228
231
|
const rows = this.db.prepare(query).all(...params);
|
|
229
232
|
return rows.map((row) => ({
|
|
230
233
|
...row,
|
|
@@ -297,10 +300,10 @@ class FrameDatabase {
|
|
|
297
300
|
/**
|
|
298
301
|
* Get events for a frame
|
|
299
302
|
*/
|
|
300
|
-
getFrameEvents(frameId, limit) {
|
|
303
|
+
getFrameEvents(frameId, limit = DEFAULT_EVENT_LIMIT) {
|
|
301
304
|
try {
|
|
302
|
-
const query =
|
|
303
|
-
const params =
|
|
305
|
+
const query = "SELECT * FROM events WHERE frame_id = ? ORDER BY seq DESC LIMIT ?";
|
|
306
|
+
const params = [frameId, limit];
|
|
304
307
|
const rows = this.db.prepare(query).all(...params);
|
|
305
308
|
return rows.map((row) => ({
|
|
306
309
|
...row,
|
|
@@ -383,11 +386,11 @@ class FrameDatabase {
|
|
|
383
386
|
/**
|
|
384
387
|
* Get anchors for a frame
|
|
385
388
|
*/
|
|
386
|
-
getFrameAnchors(frameId) {
|
|
389
|
+
getFrameAnchors(frameId, limit = DEFAULT_ANCHOR_LIMIT) {
|
|
387
390
|
try {
|
|
388
391
|
const rows = this.db.prepare(
|
|
389
|
-
"SELECT * FROM anchors WHERE frame_id = ? ORDER BY priority DESC, created_at ASC"
|
|
390
|
-
).all(frameId);
|
|
392
|
+
"SELECT * FROM anchors WHERE frame_id = ? ORDER BY priority DESC, created_at ASC LIMIT ?"
|
|
393
|
+
).all(frameId, limit);
|
|
391
394
|
return rows.map((row) => ({
|
|
392
395
|
...row,
|
|
393
396
|
metadata: safeJsonParse(row.metadata, {})
|
|
@@ -419,6 +422,31 @@ class FrameDatabase {
|
|
|
419
422
|
);
|
|
420
423
|
}
|
|
421
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Count frames by project and state (without loading all data)
|
|
427
|
+
*/
|
|
428
|
+
countFrames(projectId, state) {
|
|
429
|
+
try {
|
|
430
|
+
let query = "SELECT COUNT(*) as count FROM frames";
|
|
431
|
+
const params = [];
|
|
432
|
+
if (projectId) {
|
|
433
|
+
query += " WHERE project_id = ?";
|
|
434
|
+
params.push(projectId);
|
|
435
|
+
if (state) {
|
|
436
|
+
query += " AND state = ?";
|
|
437
|
+
params.push(state);
|
|
438
|
+
}
|
|
439
|
+
} else if (state) {
|
|
440
|
+
query += " WHERE state = ?";
|
|
441
|
+
params.push(state);
|
|
442
|
+
}
|
|
443
|
+
const result = this.db.prepare(query).get(...params);
|
|
444
|
+
return result.count;
|
|
445
|
+
} catch (error) {
|
|
446
|
+
logger.warn("Failed to count frames", { error, projectId, state });
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
422
450
|
/**
|
|
423
451
|
* Get database statistics
|
|
424
452
|
*/
|
|
@@ -443,6 +471,9 @@ class FrameDatabase {
|
|
|
443
471
|
}
|
|
444
472
|
}
|
|
445
473
|
export {
|
|
474
|
+
DEFAULT_ANCHOR_LIMIT,
|
|
475
|
+
DEFAULT_EVENT_LIMIT,
|
|
476
|
+
DEFAULT_FRAME_LIMIT,
|
|
446
477
|
FrameDatabase
|
|
447
478
|
};
|
|
448
479
|
//# sourceMappingURL=frame-database.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/context/frame-database.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Frame Database Operations\n * Handles all database interactions for frames, events, and anchors\n */\n\nimport Database from 'better-sqlite3';\nimport { Frame, Event, Anchor } from './frame-types.js';\nimport { logger } from '../monitoring/logger.js';\nimport { DatabaseError, ErrorCode } from '../errors/index.js';\n\n// Database row types for type-safe queries\ninterface FrameRow {\n frame_id: string;\n run_id: string;\n project_id: string;\n parent_frame_id: string | null;\n depth: number;\n type: string;\n name: string;\n state: string;\n inputs: string;\n outputs: string;\n digest_text: string | null;\n digest_json: string;\n created_at: number;\n closed_at: number | null;\n}\n\ninterface EventRow {\n event_id: string;\n frame_id: string;\n run_id: string;\n seq: number;\n event_type: string;\n payload: string;\n ts: number;\n}\n\ninterface AnchorRow {\n anchor_id: string;\n frame_id: string;\n type: string;\n text: string;\n priority: number;\n metadata: string;\n created_at: number;\n}\n\ninterface CountRow {\n count: number;\n}\n\ninterface MaxSeqRow {\n max_seq: number | null;\n}\n\n// Safe JSON parse with fallback\nfunction safeJsonParse<T>(json: string | null | undefined, fallback: T): T {\n if (!json) return fallback;\n try {\n return JSON.parse(json) as T;\n } catch {\n logger.warn('Failed to parse JSON, using fallback', {\n json: json.substring(0, 100),\n });\n return fallback;\n }\n}\n\nexport class FrameDatabase {\n constructor(private db: Database.Database) {}\n\n /**\n * Initialize database schema\n */\n initSchema(): void {\n try {\n // Enable WAL mode for better concurrency\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('synchronous = NORMAL');\n // Enforce referential integrity\n this.db.pragma('foreign_keys = ON');\n\n // Create frames table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS frames (\n frame_id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL,\n project_id TEXT NOT NULL,\n parent_frame_id TEXT,\n depth INTEGER NOT NULL DEFAULT 0,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n state TEXT NOT NULL DEFAULT 'active',\n inputs TEXT DEFAULT '{}',\n outputs TEXT DEFAULT '{}',\n digest_text TEXT,\n digest_json TEXT DEFAULT '{}',\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\n closed_at INTEGER,\n FOREIGN KEY (parent_frame_id) REFERENCES frames(frame_id)\n );\n `);\n\n // Create events table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n event_id TEXT PRIMARY KEY,\n frame_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n payload TEXT NOT NULL DEFAULT '{}',\n ts INTEGER NOT NULL DEFAULT (unixepoch() * 1000),\n FOREIGN KEY (frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE\n );\n `);\n\n // Create anchors table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS anchors (\n anchor_id TEXT PRIMARY KEY,\n frame_id TEXT NOT NULL,\n type TEXT NOT NULL,\n text TEXT NOT NULL,\n priority INTEGER NOT NULL DEFAULT 5,\n metadata TEXT DEFAULT '{}',\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\n FOREIGN KEY (frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE\n );\n `);\n\n // Create indexes for performance\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_frames_project_state ON frames(project_id, state);\n CREATE INDEX IF NOT EXISTS idx_frames_parent ON frames(parent_frame_id);\n CREATE INDEX IF NOT EXISTS idx_events_frame_seq ON events(frame_id, seq);\n CREATE INDEX IF NOT EXISTS idx_anchors_frame_priority ON anchors(frame_id, priority DESC);\n `);\n\n logger.info('Frame database schema initialized');\n } catch (error: unknown) {\n throw new DatabaseError(\n 'Failed to initialize frame database schema',\n ErrorCode.DB_SCHEMA_ERROR,\n { operation: 'initSchema' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Insert new frame\n */\n insertFrame(frame: Omit<Frame, 'created_at' | 'closed_at'>): Frame {\n try {\n const stmt = this.db.prepare(`\n INSERT INTO frames (frame_id, run_id, project_id, parent_frame_id, depth, type, name, state, inputs, outputs, digest_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const result = stmt.run(\n frame.frame_id,\n frame.run_id,\n frame.project_id,\n frame.parent_frame_id || null,\n frame.depth,\n frame.type,\n frame.name,\n frame.state,\n JSON.stringify(frame.inputs),\n JSON.stringify(frame.outputs),\n JSON.stringify(frame.digest_json)\n );\n\n if (result.changes === 0) {\n throw new DatabaseError(\n 'Frame insertion failed - no rows affected',\n ErrorCode.DB_INSERT_FAILED,\n { frameId: frame.frame_id, operation: 'insertFrame' }\n );\n }\n\n // Return the created frame with timestamp\n const createdFrame = this.getFrame(frame.frame_id);\n if (!createdFrame) {\n throw new DatabaseError(\n 'Failed to retrieve created frame',\n ErrorCode.DB_QUERY_FAILED,\n { frameId: frame.frame_id, operation: 'insertFrame' }\n );\n }\n\n return createdFrame;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to insert frame: ${frame.frame_id}`,\n ErrorCode.DB_INSERT_FAILED,\n {\n frameId: frame.frame_id,\n frameName: frame.name,\n operation: 'insertFrame',\n },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get frame by ID\n */\n getFrame(frameId: string): Frame | undefined {\n try {\n const row = this.db\n .prepare('SELECT * FROM frames WHERE frame_id = ?')\n .get(frameId) as FrameRow | undefined;\n\n if (!row) return undefined;\n\n return {\n ...row,\n parent_frame_id: row.parent_frame_id ?? undefined,\n inputs: safeJsonParse<Record<string, unknown>>(row.inputs, {}),\n outputs: safeJsonParse<Record<string, unknown>>(row.outputs, {}),\n digest_json: safeJsonParse<Record<string, unknown>>(\n row.digest_json,\n {}\n ),\n } as Frame;\n } catch (error: unknown) {\n logger.warn(`Failed to get frame: ${frameId}`, { error });\n return undefined;\n }\n }\n\n /**\n * Update frame state and outputs\n */\n updateFrame(frameId: string, updates: Partial<Frame>): void {\n try {\n const setClauses: string[] = [];\n const values: (string | number | null)[] = [];\n\n if (updates.state !== undefined) {\n setClauses.push('state = ?');\n values.push(updates.state);\n }\n\n if (updates.outputs !== undefined) {\n setClauses.push('outputs = ?');\n values.push(JSON.stringify(updates.outputs));\n }\n\n if (updates.digest_text !== undefined) {\n setClauses.push('digest_text = ?');\n values.push(updates.digest_text);\n }\n\n if (updates.digest_json !== undefined) {\n setClauses.push('digest_json = ?');\n values.push(JSON.stringify(updates.digest_json));\n }\n\n if (updates.closed_at !== undefined) {\n setClauses.push('closed_at = ?');\n values.push(updates.closed_at);\n }\n\n if (updates.parent_frame_id !== undefined) {\n setClauses.push('parent_frame_id = ?');\n values.push(updates.parent_frame_id);\n }\n\n if (updates.depth !== undefined) {\n setClauses.push('depth = ?');\n values.push(updates.depth);\n }\n\n if (setClauses.length === 0) {\n return; // No updates to apply\n }\n\n values.push(frameId);\n\n const stmt = this.db.prepare(`\n UPDATE frames SET ${setClauses.join(', ')} WHERE frame_id = ?\n `);\n\n const result = stmt.run(...values);\n\n if (result.changes === 0) {\n throw new DatabaseError(\n `Frame not found: ${frameId}`,\n ErrorCode.DB_UPDATE_FAILED,\n { frameId, operation: 'updateFrame' }\n );\n }\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to update frame: ${frameId}`,\n ErrorCode.DB_UPDATE_FAILED,\n { frameId, updates, operation: 'updateFrame' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get frames by project and state\n */\n getFramesByProject(projectId: string, state?: 'active' | 'closed'): Frame[] {\n try {\n const query = state\n ? 'SELECT * FROM frames WHERE project_id = ? AND state = ? ORDER BY created_at'\n : 'SELECT * FROM frames WHERE project_id = ? ORDER BY created_at';\n\n const params = state ? [projectId, state] : [projectId];\n const rows = this.db.prepare(query).all(...params) as FrameRow[];\n\n return rows.map((row) => ({\n ...row,\n parent_frame_id: row.parent_frame_id ?? undefined,\n inputs: safeJsonParse<Record<string, unknown>>(row.inputs, {}),\n outputs: safeJsonParse<Record<string, unknown>>(row.outputs, {}),\n digest_json: safeJsonParse<Record<string, unknown>>(\n row.digest_json,\n {}\n ),\n })) as Frame[];\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get frames for project: ${projectId}`,\n ErrorCode.DB_QUERY_FAILED,\n { projectId, state, operation: 'getFramesByProject' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Insert event\n */\n insertEvent(event: Omit<Event, 'ts'>): Event {\n try {\n const stmt = this.db.prepare(`\n INSERT INTO events (event_id, frame_id, run_id, seq, event_type, payload)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n const result = stmt.run(\n event.event_id,\n event.frame_id,\n event.run_id,\n event.seq,\n event.event_type,\n JSON.stringify(event.payload)\n );\n\n if (result.changes === 0) {\n throw new DatabaseError(\n 'Event insertion failed - no rows affected',\n ErrorCode.DB_INSERT_FAILED,\n {\n eventId: event.event_id,\n frameId: event.frame_id,\n operation: 'insertEvent',\n }\n );\n }\n\n // Return the created event with timestamp\n const createdEvent = this.db\n .prepare('SELECT * FROM events WHERE event_id = ?')\n .get(event.event_id) as EventRow;\n\n return {\n ...createdEvent,\n payload: safeJsonParse<Record<string, unknown>>(\n createdEvent.payload,\n {}\n ),\n } as Event;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to insert event: ${event.event_id}`,\n ErrorCode.DB_INSERT_FAILED,\n {\n eventId: event.event_id,\n frameId: event.frame_id,\n operation: 'insertEvent',\n },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get events for a frame\n */\n getFrameEvents(frameId: string, limit?: number): Event[] {\n try {\n const query = limit\n ? 'SELECT * FROM events WHERE frame_id = ? ORDER BY seq DESC LIMIT ?'\n : 'SELECT * FROM events WHERE frame_id = ? ORDER BY seq ASC';\n\n const params = limit ? [frameId, limit] : [frameId];\n const rows = this.db.prepare(query).all(...params) as EventRow[];\n\n return rows.map((row) => ({\n ...row,\n payload: safeJsonParse<Record<string, unknown>>(row.payload, {}),\n })) as Event[];\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get events for frame: ${frameId}`,\n ErrorCode.DB_QUERY_FAILED,\n { frameId, limit, operation: 'getFrameEvents' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get next event sequence number\n */\n getNextEventSequence(frameId: string): number {\n try {\n const result = this.db\n .prepare('SELECT MAX(seq) as max_seq FROM events WHERE frame_id = ?')\n .get(frameId) as MaxSeqRow;\n\n return (result.max_seq || 0) + 1;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get next event sequence for frame: ${frameId}`,\n ErrorCode.DB_QUERY_FAILED,\n { frameId, operation: 'getNextEventSequence' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Insert anchor\n */\n insertAnchor(anchor: Omit<Anchor, 'created_at'>): Anchor {\n try {\n const stmt = this.db.prepare(`\n INSERT INTO anchors (anchor_id, frame_id, type, text, priority, metadata)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n const result = stmt.run(\n anchor.anchor_id,\n anchor.frame_id,\n anchor.type,\n anchor.text,\n anchor.priority,\n JSON.stringify(anchor.metadata)\n );\n\n if (result.changes === 0) {\n throw new DatabaseError(\n 'Anchor insertion failed - no rows affected',\n ErrorCode.DB_INSERT_FAILED,\n {\n anchorId: anchor.anchor_id,\n frameId: anchor.frame_id,\n operation: 'insertAnchor',\n }\n );\n }\n\n // Return the created anchor with timestamp\n const createdAnchor = this.db\n .prepare('SELECT * FROM anchors WHERE anchor_id = ?')\n .get(anchor.anchor_id) as AnchorRow;\n\n return {\n ...createdAnchor,\n metadata: safeJsonParse<Record<string, unknown>>(\n createdAnchor.metadata,\n {}\n ),\n } as Anchor;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to insert anchor: ${anchor.anchor_id}`,\n ErrorCode.DB_INSERT_FAILED,\n {\n anchorId: anchor.anchor_id,\n frameId: anchor.frame_id,\n operation: 'insertAnchor',\n },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get anchors for a frame\n */\n getFrameAnchors(frameId: string): Anchor[] {\n try {\n const rows = this.db\n .prepare(\n 'SELECT * FROM anchors WHERE frame_id = ? ORDER BY priority DESC, created_at ASC'\n )\n .all(frameId) as AnchorRow[];\n\n return rows.map((row) => ({\n ...row,\n metadata: safeJsonParse<Record<string, unknown>>(row.metadata, {}),\n })) as Anchor[];\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get anchors for frame: ${frameId}`,\n ErrorCode.DB_QUERY_FAILED,\n { frameId, operation: 'getFrameAnchors' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Delete frame and all related data\n */\n deleteFrame(frameId: string): void {\n try {\n // Delete in order due to foreign keys\n this.db.prepare('DELETE FROM anchors WHERE frame_id = ?').run(frameId);\n this.db.prepare('DELETE FROM events WHERE frame_id = ?').run(frameId);\n this.db.prepare('DELETE FROM frames WHERE frame_id = ?').run(frameId);\n\n logger.info('Frame deleted', { frameId });\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to delete frame: ${frameId}`,\n ErrorCode.DB_DELETE_FAILED,\n { frameId, operation: 'deleteFrame' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get database statistics\n */\n getStatistics(): Record<string, number> {\n try {\n const frameCount = this.db\n .prepare('SELECT COUNT(*) as count FROM frames')\n .get() as CountRow;\n const eventCount = this.db\n .prepare('SELECT COUNT(*) as count FROM events')\n .get() as CountRow;\n const anchorCount = this.db\n .prepare('SELECT COUNT(*) as count FROM anchors')\n .get() as CountRow;\n const activeFrames = this.db\n .prepare(\"SELECT COUNT(*) as count FROM frames WHERE state = 'active'\")\n .get() as CountRow;\n\n return {\n totalFrames: frameCount.count,\n totalEvents: eventCount.count,\n totalAnchors: anchorCount.count,\n activeFrames: activeFrames.count,\n };\n } catch (error: unknown) {\n logger.warn('Failed to get database statistics', {\n error: error instanceof Error ? error.message : String(error),\n });\n return {};\n }\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;AAOA,SAAS,cAAc;AACvB,SAAS,eAAe,iBAAiB;
|
|
4
|
+
"sourcesContent": ["/**\n * Frame Database Operations\n * Handles all database interactions for frames, events, and anchors\n */\n\nimport Database from 'better-sqlite3';\nimport { Frame, Event, Anchor } from './frame-types.js';\nimport { logger } from '../monitoring/logger.js';\nimport { DatabaseError, ErrorCode } from '../errors/index.js';\n\n// Database row types for type-safe queries\ninterface FrameRow {\n frame_id: string;\n run_id: string;\n project_id: string;\n parent_frame_id: string | null;\n depth: number;\n type: string;\n name: string;\n state: string;\n inputs: string;\n outputs: string;\n digest_text: string | null;\n digest_json: string;\n created_at: number;\n closed_at: number | null;\n}\n\ninterface EventRow {\n event_id: string;\n frame_id: string;\n run_id: string;\n seq: number;\n event_type: string;\n payload: string;\n ts: number;\n}\n\ninterface AnchorRow {\n anchor_id: string;\n frame_id: string;\n type: string;\n text: string;\n priority: number;\n metadata: string;\n created_at: number;\n}\n\ninterface CountRow {\n count: number;\n}\n\ninterface MaxSeqRow {\n max_seq: number | null;\n}\n\n// Default limits to prevent unbounded queries\nexport const DEFAULT_FRAME_LIMIT = 1000;\nexport const DEFAULT_EVENT_LIMIT = 500;\nexport const DEFAULT_ANCHOR_LIMIT = 200;\n\n// Safe JSON parse with fallback\nfunction safeJsonParse<T>(json: string | null | undefined, fallback: T): T {\n if (!json) return fallback;\n try {\n return JSON.parse(json) as T;\n } catch {\n logger.warn('Failed to parse JSON, using fallback', {\n json: json.substring(0, 100),\n });\n return fallback;\n }\n}\n\nexport class FrameDatabase {\n constructor(private db: Database.Database) {}\n\n /**\n * Initialize database schema\n */\n initSchema(): void {\n try {\n // Enable WAL mode for better concurrency\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('synchronous = NORMAL');\n // Enforce referential integrity\n this.db.pragma('foreign_keys = ON');\n\n // Create frames table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS frames (\n frame_id TEXT PRIMARY KEY,\n run_id TEXT NOT NULL,\n project_id TEXT NOT NULL,\n parent_frame_id TEXT,\n depth INTEGER NOT NULL DEFAULT 0,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n state TEXT NOT NULL DEFAULT 'active',\n inputs TEXT DEFAULT '{}',\n outputs TEXT DEFAULT '{}',\n digest_text TEXT,\n digest_json TEXT DEFAULT '{}',\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\n closed_at INTEGER,\n FOREIGN KEY (parent_frame_id) REFERENCES frames(frame_id)\n );\n `);\n\n // Create events table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n event_id TEXT PRIMARY KEY,\n frame_id TEXT NOT NULL,\n run_id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n payload TEXT NOT NULL DEFAULT '{}',\n ts INTEGER NOT NULL DEFAULT (unixepoch() * 1000),\n FOREIGN KEY (frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE\n );\n `);\n\n // Create anchors table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS anchors (\n anchor_id TEXT PRIMARY KEY,\n frame_id TEXT NOT NULL,\n type TEXT NOT NULL,\n text TEXT NOT NULL,\n priority INTEGER NOT NULL DEFAULT 5,\n metadata TEXT DEFAULT '{}',\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\n FOREIGN KEY (frame_id) REFERENCES frames(frame_id) ON DELETE CASCADE\n );\n `);\n\n // Create indexes for performance\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_frames_project_state ON frames(project_id, state);\n CREATE INDEX IF NOT EXISTS idx_frames_parent ON frames(parent_frame_id);\n CREATE INDEX IF NOT EXISTS idx_events_frame_seq ON events(frame_id, seq);\n CREATE INDEX IF NOT EXISTS idx_anchors_frame_priority ON anchors(frame_id, priority DESC);\n `);\n\n logger.info('Frame database schema initialized');\n } catch (error: unknown) {\n throw new DatabaseError(\n 'Failed to initialize frame database schema',\n ErrorCode.DB_SCHEMA_ERROR,\n { operation: 'initSchema' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Insert new frame\n */\n insertFrame(frame: Omit<Frame, 'created_at' | 'closed_at'>): Frame {\n try {\n const stmt = this.db.prepare(`\n INSERT INTO frames (frame_id, run_id, project_id, parent_frame_id, depth, type, name, state, inputs, outputs, digest_json)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const result = stmt.run(\n frame.frame_id,\n frame.run_id,\n frame.project_id,\n frame.parent_frame_id || null,\n frame.depth,\n frame.type,\n frame.name,\n frame.state,\n JSON.stringify(frame.inputs),\n JSON.stringify(frame.outputs),\n JSON.stringify(frame.digest_json)\n );\n\n if (result.changes === 0) {\n throw new DatabaseError(\n 'Frame insertion failed - no rows affected',\n ErrorCode.DB_INSERT_FAILED,\n { frameId: frame.frame_id, operation: 'insertFrame' }\n );\n }\n\n // Return the created frame with timestamp\n const createdFrame = this.getFrame(frame.frame_id);\n if (!createdFrame) {\n throw new DatabaseError(\n 'Failed to retrieve created frame',\n ErrorCode.DB_QUERY_FAILED,\n { frameId: frame.frame_id, operation: 'insertFrame' }\n );\n }\n\n return createdFrame;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to insert frame: ${frame.frame_id}`,\n ErrorCode.DB_INSERT_FAILED,\n {\n frameId: frame.frame_id,\n frameName: frame.name,\n operation: 'insertFrame',\n },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get frame by ID\n */\n getFrame(frameId: string): Frame | undefined {\n try {\n const row = this.db\n .prepare('SELECT * FROM frames WHERE frame_id = ?')\n .get(frameId) as FrameRow | undefined;\n\n if (!row) return undefined;\n\n return {\n ...row,\n parent_frame_id: row.parent_frame_id ?? undefined,\n inputs: safeJsonParse<Record<string, unknown>>(row.inputs, {}),\n outputs: safeJsonParse<Record<string, unknown>>(row.outputs, {}),\n digest_json: safeJsonParse<Record<string, unknown>>(\n row.digest_json,\n {}\n ),\n } as Frame;\n } catch (error: unknown) {\n logger.warn(`Failed to get frame: ${frameId}`, { error });\n return undefined;\n }\n }\n\n /**\n * Update frame state and outputs\n */\n updateFrame(frameId: string, updates: Partial<Frame>): void {\n try {\n const setClauses: string[] = [];\n const values: (string | number | null)[] = [];\n\n if (updates.state !== undefined) {\n setClauses.push('state = ?');\n values.push(updates.state);\n }\n\n if (updates.outputs !== undefined) {\n setClauses.push('outputs = ?');\n values.push(JSON.stringify(updates.outputs));\n }\n\n if (updates.digest_text !== undefined) {\n setClauses.push('digest_text = ?');\n values.push(updates.digest_text);\n }\n\n if (updates.digest_json !== undefined) {\n setClauses.push('digest_json = ?');\n values.push(JSON.stringify(updates.digest_json));\n }\n\n if (updates.closed_at !== undefined) {\n setClauses.push('closed_at = ?');\n values.push(updates.closed_at);\n }\n\n if (updates.parent_frame_id !== undefined) {\n setClauses.push('parent_frame_id = ?');\n values.push(updates.parent_frame_id);\n }\n\n if (updates.depth !== undefined) {\n setClauses.push('depth = ?');\n values.push(updates.depth);\n }\n\n if (setClauses.length === 0) {\n return; // No updates to apply\n }\n\n values.push(frameId);\n\n const stmt = this.db.prepare(`\n UPDATE frames SET ${setClauses.join(', ')} WHERE frame_id = ?\n `);\n\n const result = stmt.run(...values);\n\n if (result.changes === 0) {\n throw new DatabaseError(\n `Frame not found: ${frameId}`,\n ErrorCode.DB_UPDATE_FAILED,\n { frameId, operation: 'updateFrame' }\n );\n }\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to update frame: ${frameId}`,\n ErrorCode.DB_UPDATE_FAILED,\n { frameId, updates, operation: 'updateFrame' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get frames by project and state\n */\n getFramesByProject(\n projectId: string,\n state?: 'active' | 'closed',\n limit: number = DEFAULT_FRAME_LIMIT\n ): Frame[] {\n try {\n const query = state\n ? 'SELECT * FROM frames WHERE project_id = ? AND state = ? ORDER BY created_at LIMIT ?'\n : 'SELECT * FROM frames WHERE project_id = ? ORDER BY created_at LIMIT ?';\n\n const params = state ? [projectId, state, limit] : [projectId, limit];\n const rows = this.db.prepare(query).all(...params) as FrameRow[];\n\n return rows.map((row) => ({\n ...row,\n parent_frame_id: row.parent_frame_id ?? undefined,\n inputs: safeJsonParse<Record<string, unknown>>(row.inputs, {}),\n outputs: safeJsonParse<Record<string, unknown>>(row.outputs, {}),\n digest_json: safeJsonParse<Record<string, unknown>>(\n row.digest_json,\n {}\n ),\n })) as Frame[];\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get frames for project: ${projectId}`,\n ErrorCode.DB_QUERY_FAILED,\n { projectId, state, operation: 'getFramesByProject' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Insert event\n */\n insertEvent(event: Omit<Event, 'ts'>): Event {\n try {\n const stmt = this.db.prepare(`\n INSERT INTO events (event_id, frame_id, run_id, seq, event_type, payload)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n const result = stmt.run(\n event.event_id,\n event.frame_id,\n event.run_id,\n event.seq,\n event.event_type,\n JSON.stringify(event.payload)\n );\n\n if (result.changes === 0) {\n throw new DatabaseError(\n 'Event insertion failed - no rows affected',\n ErrorCode.DB_INSERT_FAILED,\n {\n eventId: event.event_id,\n frameId: event.frame_id,\n operation: 'insertEvent',\n }\n );\n }\n\n // Return the created event with timestamp\n const createdEvent = this.db\n .prepare('SELECT * FROM events WHERE event_id = ?')\n .get(event.event_id) as EventRow;\n\n return {\n ...createdEvent,\n payload: safeJsonParse<Record<string, unknown>>(\n createdEvent.payload,\n {}\n ),\n } as Event;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to insert event: ${event.event_id}`,\n ErrorCode.DB_INSERT_FAILED,\n {\n eventId: event.event_id,\n frameId: event.frame_id,\n operation: 'insertEvent',\n },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get events for a frame\n */\n getFrameEvents(\n frameId: string,\n limit: number = DEFAULT_EVENT_LIMIT\n ): Event[] {\n try {\n const query =\n 'SELECT * FROM events WHERE frame_id = ? ORDER BY seq DESC LIMIT ?';\n const params = [frameId, limit];\n const rows = this.db.prepare(query).all(...params) as EventRow[];\n\n return rows.map((row) => ({\n ...row,\n payload: safeJsonParse<Record<string, unknown>>(row.payload, {}),\n })) as Event[];\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get events for frame: ${frameId}`,\n ErrorCode.DB_QUERY_FAILED,\n { frameId, limit, operation: 'getFrameEvents' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get next event sequence number\n */\n getNextEventSequence(frameId: string): number {\n try {\n const result = this.db\n .prepare('SELECT MAX(seq) as max_seq FROM events WHERE frame_id = ?')\n .get(frameId) as MaxSeqRow;\n\n return (result.max_seq || 0) + 1;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get next event sequence for frame: ${frameId}`,\n ErrorCode.DB_QUERY_FAILED,\n { frameId, operation: 'getNextEventSequence' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Insert anchor\n */\n insertAnchor(anchor: Omit<Anchor, 'created_at'>): Anchor {\n try {\n const stmt = this.db.prepare(`\n INSERT INTO anchors (anchor_id, frame_id, type, text, priority, metadata)\n VALUES (?, ?, ?, ?, ?, ?)\n `);\n\n const result = stmt.run(\n anchor.anchor_id,\n anchor.frame_id,\n anchor.type,\n anchor.text,\n anchor.priority,\n JSON.stringify(anchor.metadata)\n );\n\n if (result.changes === 0) {\n throw new DatabaseError(\n 'Anchor insertion failed - no rows affected',\n ErrorCode.DB_INSERT_FAILED,\n {\n anchorId: anchor.anchor_id,\n frameId: anchor.frame_id,\n operation: 'insertAnchor',\n }\n );\n }\n\n // Return the created anchor with timestamp\n const createdAnchor = this.db\n .prepare('SELECT * FROM anchors WHERE anchor_id = ?')\n .get(anchor.anchor_id) as AnchorRow;\n\n return {\n ...createdAnchor,\n metadata: safeJsonParse<Record<string, unknown>>(\n createdAnchor.metadata,\n {}\n ),\n } as Anchor;\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to insert anchor: ${anchor.anchor_id}`,\n ErrorCode.DB_INSERT_FAILED,\n {\n anchorId: anchor.anchor_id,\n frameId: anchor.frame_id,\n operation: 'insertAnchor',\n },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get anchors for a frame\n */\n getFrameAnchors(\n frameId: string,\n limit: number = DEFAULT_ANCHOR_LIMIT\n ): Anchor[] {\n try {\n const rows = this.db\n .prepare(\n 'SELECT * FROM anchors WHERE frame_id = ? ORDER BY priority DESC, created_at ASC LIMIT ?'\n )\n .all(frameId, limit) as AnchorRow[];\n\n return rows.map((row) => ({\n ...row,\n metadata: safeJsonParse<Record<string, unknown>>(row.metadata, {}),\n })) as Anchor[];\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to get anchors for frame: ${frameId}`,\n ErrorCode.DB_QUERY_FAILED,\n { frameId, operation: 'getFrameAnchors' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Delete frame and all related data\n */\n deleteFrame(frameId: string): void {\n try {\n // Delete in order due to foreign keys\n this.db.prepare('DELETE FROM anchors WHERE frame_id = ?').run(frameId);\n this.db.prepare('DELETE FROM events WHERE frame_id = ?').run(frameId);\n this.db.prepare('DELETE FROM frames WHERE frame_id = ?').run(frameId);\n\n logger.info('Frame deleted', { frameId });\n } catch (error: unknown) {\n throw new DatabaseError(\n `Failed to delete frame: ${frameId}`,\n ErrorCode.DB_DELETE_FAILED,\n { frameId, operation: 'deleteFrame' },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Count frames by project and state (without loading all data)\n */\n countFrames(projectId?: string, state?: 'active' | 'closed'): number {\n try {\n let query = 'SELECT COUNT(*) as count FROM frames';\n const params: (string | undefined)[] = [];\n\n if (projectId) {\n query += ' WHERE project_id = ?';\n params.push(projectId);\n if (state) {\n query += ' AND state = ?';\n params.push(state);\n }\n } else if (state) {\n query += ' WHERE state = ?';\n params.push(state);\n }\n\n const result = this.db.prepare(query).get(...params) as CountRow;\n return result.count;\n } catch (error: unknown) {\n logger.warn('Failed to count frames', { error, projectId, state });\n return 0;\n }\n }\n\n /**\n * Get database statistics\n */\n getStatistics(): Record<string, number> {\n try {\n const frameCount = this.db\n .prepare('SELECT COUNT(*) as count FROM frames')\n .get() as CountRow;\n const eventCount = this.db\n .prepare('SELECT COUNT(*) as count FROM events')\n .get() as CountRow;\n const anchorCount = this.db\n .prepare('SELECT COUNT(*) as count FROM anchors')\n .get() as CountRow;\n const activeFrames = this.db\n .prepare(\"SELECT COUNT(*) as count FROM frames WHERE state = 'active'\")\n .get() as CountRow;\n\n return {\n totalFrames: frameCount.count,\n totalEvents: eventCount.count,\n totalAnchors: anchorCount.count,\n activeFrames: activeFrames.count,\n };\n } catch (error: unknown) {\n logger.warn('Failed to get database statistics', {\n error: error instanceof Error ? error.message : String(error),\n });\n return {};\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { logger } from "../monitoring/logger.js";
|
|
6
|
+
class FrameRecovery {
|
|
7
|
+
constructor(db, options = {}) {
|
|
8
|
+
this.db = db;
|
|
9
|
+
this.orphanThresholdMs = (options.orphanThresholdHours ?? 24) * 60 * 60 * 1e3;
|
|
10
|
+
}
|
|
11
|
+
// Sessions older than this are considered orphaned (default: 24 hours)
|
|
12
|
+
orphanThresholdMs;
|
|
13
|
+
// Current session/run ID to exclude from orphan detection
|
|
14
|
+
currentRunId = null;
|
|
15
|
+
/**
|
|
16
|
+
* Set the current run ID to exclude from orphan detection
|
|
17
|
+
*/
|
|
18
|
+
setCurrentRunId(runId) {
|
|
19
|
+
this.currentRunId = runId;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Main recovery entry point - call on startup
|
|
23
|
+
*/
|
|
24
|
+
async recoverOnStartup() {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
27
|
+
logger.info("Starting crash recovery check");
|
|
28
|
+
const walStatus = this.checkWalStatus();
|
|
29
|
+
if (walStatus.checkpointNeeded) {
|
|
30
|
+
try {
|
|
31
|
+
this.checkpointWal();
|
|
32
|
+
logger.info("WAL checkpoint completed");
|
|
33
|
+
} catch (err) {
|
|
34
|
+
const msg = `WAL checkpoint failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
35
|
+
errors.push(msg);
|
|
36
|
+
logger.warn(msg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const integrityCheck = this.runIntegrityCheck();
|
|
40
|
+
if (!integrityCheck.passed) {
|
|
41
|
+
logger.error("Database integrity check failed", {
|
|
42
|
+
violations: integrityCheck.foreignKeyViolations,
|
|
43
|
+
corrupted: integrityCheck.corruptedRows
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const orphanedFrames = this.recoverOrphanedFrames();
|
|
47
|
+
if (orphanedFrames.detected > 0) {
|
|
48
|
+
logger.info("Orphaned frames processed", {
|
|
49
|
+
detected: orphanedFrames.detected,
|
|
50
|
+
recovered: orphanedFrames.recovered,
|
|
51
|
+
closed: orphanedFrames.closed
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const report = {
|
|
55
|
+
timestamp,
|
|
56
|
+
integrityCheck,
|
|
57
|
+
orphanedFrames,
|
|
58
|
+
walStatus,
|
|
59
|
+
recovered: integrityCheck.passed && orphanedFrames.detected === orphanedFrames.closed,
|
|
60
|
+
errors
|
|
61
|
+
};
|
|
62
|
+
logger.info("Crash recovery completed", {
|
|
63
|
+
recovered: report.recovered,
|
|
64
|
+
orphansFound: orphanedFrames.detected,
|
|
65
|
+
integrityPassed: integrityCheck.passed
|
|
66
|
+
});
|
|
67
|
+
return report;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check WAL mode status
|
|
71
|
+
*/
|
|
72
|
+
checkWalStatus() {
|
|
73
|
+
try {
|
|
74
|
+
const journalMode = this.db.pragma("journal_mode");
|
|
75
|
+
const isWal = journalMode[0]?.journal_mode === "wal";
|
|
76
|
+
if (!isWal) {
|
|
77
|
+
return { enabled: false, checkpointNeeded: false, walSize: 0 };
|
|
78
|
+
}
|
|
79
|
+
const walInfo = this.db.pragma("wal_checkpoint(PASSIVE)");
|
|
80
|
+
const walSize = walInfo[0]?.log ?? 0;
|
|
81
|
+
const checkpointed = walInfo[0]?.checkpointed ?? 0;
|
|
82
|
+
return {
|
|
83
|
+
enabled: true,
|
|
84
|
+
checkpointNeeded: walSize > 1e3,
|
|
85
|
+
// Checkpoint if WAL has > 1000 pages
|
|
86
|
+
walSize: walSize - checkpointed
|
|
87
|
+
};
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.warn("Failed to check WAL status", { error: err });
|
|
90
|
+
return { enabled: false, checkpointNeeded: false, walSize: 0 };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Force WAL checkpoint
|
|
95
|
+
*/
|
|
96
|
+
checkpointWal() {
|
|
97
|
+
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Run database integrity checks
|
|
101
|
+
*/
|
|
102
|
+
runIntegrityCheck() {
|
|
103
|
+
const errors = [];
|
|
104
|
+
let foreignKeyViolations = 0;
|
|
105
|
+
let corruptedRows = 0;
|
|
106
|
+
try {
|
|
107
|
+
const fkViolations = this.db.pragma(
|
|
108
|
+
"foreign_key_check"
|
|
109
|
+
);
|
|
110
|
+
foreignKeyViolations = fkViolations.length;
|
|
111
|
+
if (foreignKeyViolations > 0) {
|
|
112
|
+
errors.push(`Found ${foreignKeyViolations} foreign key violations`);
|
|
113
|
+
logger.warn("Foreign key violations detected", {
|
|
114
|
+
count: foreignKeyViolations,
|
|
115
|
+
samples: fkViolations.slice(0, 5)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const integrity = this.db.pragma("integrity_check");
|
|
119
|
+
const integrityResult = integrity[0]?.integrity_check;
|
|
120
|
+
if (integrityResult !== "ok") {
|
|
121
|
+
corruptedRows = integrity.length;
|
|
122
|
+
errors.push(`Integrity check failed: ${integrityResult}`);
|
|
123
|
+
logger.error("Database corruption detected", {
|
|
124
|
+
result: integrityResult
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
errors.push(
|
|
129
|
+
`Integrity check error: ${err instanceof Error ? err.message : String(err)}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
passed: foreignKeyViolations === 0 && corruptedRows === 0,
|
|
134
|
+
foreignKeyViolations,
|
|
135
|
+
corruptedRows,
|
|
136
|
+
errors
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Detect and recover orphaned frames
|
|
141
|
+
* Orphaned = active frames from sessions that are no longer running
|
|
142
|
+
*/
|
|
143
|
+
recoverOrphanedFrames() {
|
|
144
|
+
const orphanThreshold = Date.now() - this.orphanThresholdMs;
|
|
145
|
+
const thresholdUnix = Math.floor(orphanThreshold / 1e3);
|
|
146
|
+
const query = `
|
|
147
|
+
SELECT frame_id, run_id, project_id, name, type, created_at, depth
|
|
148
|
+
FROM frames
|
|
149
|
+
WHERE state = 'active'
|
|
150
|
+
AND created_at < ?
|
|
151
|
+
${this.currentRunId ? "AND run_id != ?" : ""}
|
|
152
|
+
ORDER BY created_at ASC
|
|
153
|
+
`;
|
|
154
|
+
const params = this.currentRunId ? [thresholdUnix, this.currentRunId] : [thresholdUnix];
|
|
155
|
+
const orphaned = this.db.prepare(query).all(...params);
|
|
156
|
+
if (orphaned.length === 0) {
|
|
157
|
+
return { detected: 0, recovered: 0, closed: 0, frameIds: [] };
|
|
158
|
+
}
|
|
159
|
+
const frameIds = orphaned.map((f) => f.frame_id);
|
|
160
|
+
let closed = 0;
|
|
161
|
+
const closeStmt = this.db.prepare(`
|
|
162
|
+
UPDATE frames
|
|
163
|
+
SET state = 'closed',
|
|
164
|
+
closed_at = unixepoch(),
|
|
165
|
+
outputs = json_set(COALESCE(outputs, '{}'), '$.recovered', true, '$.recoveryReason', 'orphan_cleanup')
|
|
166
|
+
WHERE frame_id = ?
|
|
167
|
+
`);
|
|
168
|
+
const transaction = this.db.transaction(() => {
|
|
169
|
+
for (const frame of orphaned) {
|
|
170
|
+
try {
|
|
171
|
+
closeStmt.run(frame.frame_id);
|
|
172
|
+
closed++;
|
|
173
|
+
logger.debug("Closed orphaned frame", {
|
|
174
|
+
frameId: frame.frame_id,
|
|
175
|
+
runId: frame.run_id,
|
|
176
|
+
name: frame.name,
|
|
177
|
+
age: Math.round((Date.now() / 1e3 - frame.created_at) / 3600) + "h"
|
|
178
|
+
});
|
|
179
|
+
} catch (err) {
|
|
180
|
+
logger.warn("Failed to close orphaned frame", {
|
|
181
|
+
frameId: frame.frame_id,
|
|
182
|
+
error: err
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
transaction();
|
|
188
|
+
return {
|
|
189
|
+
detected: orphaned.length,
|
|
190
|
+
recovered: 0,
|
|
191
|
+
// Future: could attempt to resume some frames
|
|
192
|
+
closed,
|
|
193
|
+
frameIds
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Validate data integrity for a specific project
|
|
198
|
+
*/
|
|
199
|
+
validateProjectIntegrity(projectId) {
|
|
200
|
+
const issues = [];
|
|
201
|
+
const invalidParents = this.db.prepare(
|
|
202
|
+
`
|
|
203
|
+
SELECT f1.frame_id, f1.parent_frame_id
|
|
204
|
+
FROM frames f1
|
|
205
|
+
LEFT JOIN frames f2 ON f1.parent_frame_id = f2.frame_id
|
|
206
|
+
WHERE f1.project_id = ?
|
|
207
|
+
AND f1.parent_frame_id IS NOT NULL
|
|
208
|
+
AND f2.frame_id IS NULL
|
|
209
|
+
`
|
|
210
|
+
).all(projectId);
|
|
211
|
+
if (invalidParents.length > 0) {
|
|
212
|
+
issues.push(
|
|
213
|
+
`${invalidParents.length} frames with invalid parent references`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const depthIssues = this.db.prepare(
|
|
217
|
+
`
|
|
218
|
+
SELECT f1.frame_id, f1.depth as child_depth, f2.depth as parent_depth
|
|
219
|
+
FROM frames f1
|
|
220
|
+
JOIN frames f2 ON f1.parent_frame_id = f2.frame_id
|
|
221
|
+
WHERE f1.project_id = ?
|
|
222
|
+
AND f1.depth != f2.depth + 1
|
|
223
|
+
`
|
|
224
|
+
).all(projectId);
|
|
225
|
+
if (depthIssues.length > 0) {
|
|
226
|
+
issues.push(`${depthIssues.length} frames with incorrect depth values`);
|
|
227
|
+
}
|
|
228
|
+
const orphanEvents = this.db.prepare(
|
|
229
|
+
`
|
|
230
|
+
SELECT COUNT(*) as count
|
|
231
|
+
FROM events e
|
|
232
|
+
LEFT JOIN frames f ON e.frame_id = f.frame_id
|
|
233
|
+
WHERE f.frame_id IS NULL
|
|
234
|
+
`
|
|
235
|
+
).get();
|
|
236
|
+
if (orphanEvents.count > 0) {
|
|
237
|
+
issues.push(`${orphanEvents.count} orphaned events without valid frames`);
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
valid: issues.length === 0,
|
|
241
|
+
issues
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Clean up orphaned events (events without valid frames)
|
|
246
|
+
*/
|
|
247
|
+
cleanupOrphanedEvents() {
|
|
248
|
+
const result = this.db.prepare(
|
|
249
|
+
`
|
|
250
|
+
DELETE FROM events
|
|
251
|
+
WHERE frame_id NOT IN (SELECT frame_id FROM frames)
|
|
252
|
+
`
|
|
253
|
+
).run();
|
|
254
|
+
if (result.changes > 0) {
|
|
255
|
+
logger.info("Cleaned up orphaned events", { count: result.changes });
|
|
256
|
+
}
|
|
257
|
+
return result.changes;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get recovery statistics
|
|
261
|
+
*/
|
|
262
|
+
getRecoveryStats() {
|
|
263
|
+
const stats = this.db.prepare(
|
|
264
|
+
`
|
|
265
|
+
SELECT
|
|
266
|
+
COUNT(*) as total,
|
|
267
|
+
SUM(CASE WHEN state = 'active' THEN 1 ELSE 0 END) as active,
|
|
268
|
+
SUM(CASE WHEN state = 'closed' THEN 1 ELSE 0 END) as closed,
|
|
269
|
+
SUM(CASE WHEN json_extract(outputs, '$.recovered') = true THEN 1 ELSE 0 END) as recovered
|
|
270
|
+
FROM frames
|
|
271
|
+
`
|
|
272
|
+
).get();
|
|
273
|
+
const oldest = this.db.prepare(
|
|
274
|
+
`
|
|
275
|
+
SELECT datetime(created_at, 'unixepoch') as created
|
|
276
|
+
FROM frames
|
|
277
|
+
WHERE state = 'active'
|
|
278
|
+
ORDER BY created_at ASC
|
|
279
|
+
LIMIT 1
|
|
280
|
+
`
|
|
281
|
+
).get();
|
|
282
|
+
return {
|
|
283
|
+
totalFrames: stats.total,
|
|
284
|
+
activeFrames: stats.active,
|
|
285
|
+
closedFrames: stats.closed,
|
|
286
|
+
recoveredFrames: stats.recovered,
|
|
287
|
+
oldestActiveFrame: oldest?.created ?? null
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function recoverDatabase(db, currentRunId) {
|
|
292
|
+
const recovery = new FrameRecovery(db);
|
|
293
|
+
if (currentRunId) {
|
|
294
|
+
recovery.setCurrentRunId(currentRunId);
|
|
295
|
+
}
|
|
296
|
+
return recovery.recoverOnStartup();
|
|
297
|
+
}
|
|
298
|
+
export {
|
|
299
|
+
FrameRecovery,
|
|
300
|
+
recoverDatabase
|
|
301
|
+
};
|
|
302
|
+
//# sourceMappingURL=frame-recovery.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/core/context/frame-recovery.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Frame Recovery System\n * Handles crash recovery, integrity checks, and orphan cleanup\n *\n * Key responsibilities:\n * 1. Verify database integrity on startup\n * 2. Detect orphaned frames from crashed sessions\n * 3. Recover or close orphaned frames\n * 4. Provide data integrity validation\n */\n\nimport Database from 'better-sqlite3';\nimport { logger } from '../monitoring/logger.js';\n\nexport interface RecoveryReport {\n timestamp: string;\n integrityCheck: IntegrityCheckResult;\n orphanedFrames: OrphanedFrameResult;\n walStatus: WalStatus;\n recovered: boolean;\n errors: string[];\n}\n\nexport interface IntegrityCheckResult {\n passed: boolean;\n foreignKeyViolations: number;\n corruptedRows: number;\n errors: string[];\n}\n\nexport interface OrphanedFrameResult {\n detected: number;\n recovered: number;\n closed: number;\n frameIds: string[];\n}\n\nexport interface WalStatus {\n enabled: boolean;\n checkpointNeeded: boolean;\n walSize: number;\n}\n\ninterface OrphanedFrameRow {\n frame_id: string;\n run_id: string;\n project_id: string;\n name: string;\n type: string;\n created_at: number;\n depth: number;\n}\n\ninterface ForeignKeyViolation {\n table: string;\n rowid: number;\n parent: string;\n fkid: number;\n}\n\ninterface IntegrityRow {\n integrity_check: string;\n}\n\ninterface WalRow {\n busy: number;\n log: number;\n checkpointed: number;\n}\n\ninterface JournalModeRow {\n journal_mode: string;\n}\n\n/**\n * Frame Recovery Manager\n * Call recoverOnStartup() when initializing StackMemory\n */\nexport class FrameRecovery {\n // Sessions older than this are considered orphaned (default: 24 hours)\n private orphanThresholdMs: number;\n // Current session/run ID to exclude from orphan detection\n private currentRunId: string | null = null;\n\n constructor(\n private db: Database.Database,\n options: { orphanThresholdHours?: number } = {}\n ) {\n this.orphanThresholdMs =\n (options.orphanThresholdHours ?? 24) * 60 * 60 * 1000;\n }\n\n /**\n * Set the current run ID to exclude from orphan detection\n */\n setCurrentRunId(runId: string): void {\n this.currentRunId = runId;\n }\n\n /**\n * Main recovery entry point - call on startup\n */\n async recoverOnStartup(): Promise<RecoveryReport> {\n const errors: string[] = [];\n const timestamp = new Date().toISOString();\n\n logger.info('Starting crash recovery check');\n\n // 1. Check WAL status and checkpoint if needed\n const walStatus = this.checkWalStatus();\n if (walStatus.checkpointNeeded) {\n try {\n this.checkpointWal();\n logger.info('WAL checkpoint completed');\n } catch (err) {\n const msg = `WAL checkpoint failed: ${err instanceof Error ? err.message : String(err)}`;\n errors.push(msg);\n logger.warn(msg);\n }\n }\n\n // 2. Run integrity check\n const integrityCheck = this.runIntegrityCheck();\n if (!integrityCheck.passed) {\n logger.error('Database integrity check failed', {\n violations: integrityCheck.foreignKeyViolations,\n corrupted: integrityCheck.corruptedRows,\n });\n }\n\n // 3. Detect and handle orphaned frames\n const orphanedFrames = this.recoverOrphanedFrames();\n if (orphanedFrames.detected > 0) {\n logger.info('Orphaned frames processed', {\n detected: orphanedFrames.detected,\n recovered: orphanedFrames.recovered,\n closed: orphanedFrames.closed,\n });\n }\n\n const report: RecoveryReport = {\n timestamp,\n integrityCheck,\n orphanedFrames,\n walStatus,\n recovered:\n integrityCheck.passed &&\n orphanedFrames.detected === orphanedFrames.closed,\n errors,\n };\n\n logger.info('Crash recovery completed', {\n recovered: report.recovered,\n orphansFound: orphanedFrames.detected,\n integrityPassed: integrityCheck.passed,\n });\n\n return report;\n }\n\n /**\n * Check WAL mode status\n */\n checkWalStatus(): WalStatus {\n try {\n const journalMode = this.db.pragma('journal_mode') as JournalModeRow[];\n const isWal = journalMode[0]?.journal_mode === 'wal';\n\n if (!isWal) {\n return { enabled: false, checkpointNeeded: false, walSize: 0 };\n }\n\n // Check WAL size\n const walInfo = this.db.pragma('wal_checkpoint(PASSIVE)') as WalRow[];\n const walSize = walInfo[0]?.log ?? 0;\n const checkpointed = walInfo[0]?.checkpointed ?? 0;\n\n return {\n enabled: true,\n checkpointNeeded: walSize > 1000, // Checkpoint if WAL has > 1000 pages\n walSize: walSize - checkpointed,\n };\n } catch (err) {\n logger.warn('Failed to check WAL status', { error: err });\n return { enabled: false, checkpointNeeded: false, walSize: 0 };\n }\n }\n\n /**\n * Force WAL checkpoint\n */\n checkpointWal(): void {\n this.db.pragma('wal_checkpoint(TRUNCATE)');\n }\n\n /**\n * Run database integrity checks\n */\n runIntegrityCheck(): IntegrityCheckResult {\n const errors: string[] = [];\n let foreignKeyViolations = 0;\n let corruptedRows = 0;\n\n try {\n // Check foreign key constraints\n const fkViolations = this.db.pragma(\n 'foreign_key_check'\n ) as ForeignKeyViolation[];\n foreignKeyViolations = fkViolations.length;\n\n if (foreignKeyViolations > 0) {\n errors.push(`Found ${foreignKeyViolations} foreign key violations`);\n logger.warn('Foreign key violations detected', {\n count: foreignKeyViolations,\n samples: fkViolations.slice(0, 5),\n });\n }\n\n // Run integrity check\n const integrity = this.db.pragma('integrity_check') as IntegrityRow[];\n const integrityResult = integrity[0]?.integrity_check;\n\n if (integrityResult !== 'ok') {\n corruptedRows = integrity.length;\n errors.push(`Integrity check failed: ${integrityResult}`);\n logger.error('Database corruption detected', {\n result: integrityResult,\n });\n }\n } catch (err) {\n errors.push(\n `Integrity check error: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n return {\n passed: foreignKeyViolations === 0 && corruptedRows === 0,\n foreignKeyViolations,\n corruptedRows,\n errors,\n };\n }\n\n /**\n * Detect and recover orphaned frames\n * Orphaned = active frames from sessions that are no longer running\n */\n recoverOrphanedFrames(): OrphanedFrameResult {\n const orphanThreshold = Date.now() - this.orphanThresholdMs;\n const thresholdUnix = Math.floor(orphanThreshold / 1000);\n\n // Find active frames older than threshold (likely from crashed sessions)\n const query = `\n SELECT frame_id, run_id, project_id, name, type, created_at, depth\n FROM frames\n WHERE state = 'active'\n AND created_at < ?\n ${this.currentRunId ? 'AND run_id != ?' : ''}\n ORDER BY created_at ASC\n `;\n\n const params = this.currentRunId\n ? [thresholdUnix, this.currentRunId]\n : [thresholdUnix];\n\n const orphaned = this.db\n .prepare(query)\n .all(...params) as OrphanedFrameRow[];\n\n if (orphaned.length === 0) {\n return { detected: 0, recovered: 0, closed: 0, frameIds: [] };\n }\n\n const frameIds = orphaned.map((f) => f.frame_id);\n let closed = 0;\n\n // Close orphaned frames with a \"recovered\" state marker\n const closeStmt = this.db.prepare(`\n UPDATE frames\n SET state = 'closed',\n closed_at = unixepoch(),\n outputs = json_set(COALESCE(outputs, '{}'), '$.recovered', true, '$.recoveryReason', 'orphan_cleanup')\n WHERE frame_id = ?\n `);\n\n const transaction = this.db.transaction(() => {\n for (const frame of orphaned) {\n try {\n closeStmt.run(frame.frame_id);\n closed++;\n logger.debug('Closed orphaned frame', {\n frameId: frame.frame_id,\n runId: frame.run_id,\n name: frame.name,\n age:\n Math.round((Date.now() / 1000 - frame.created_at) / 3600) + 'h',\n });\n } catch (err) {\n logger.warn('Failed to close orphaned frame', {\n frameId: frame.frame_id,\n error: err,\n });\n }\n }\n });\n\n transaction();\n\n return {\n detected: orphaned.length,\n recovered: 0, // Future: could attempt to resume some frames\n closed,\n frameIds,\n };\n }\n\n /**\n * Validate data integrity for a specific project\n */\n validateProjectIntegrity(projectId: string): {\n valid: boolean;\n issues: string[];\n } {\n const issues: string[] = [];\n\n // Check for frames with invalid parent references\n const invalidParents = this.db\n .prepare(\n `\n SELECT f1.frame_id, f1.parent_frame_id\n FROM frames f1\n LEFT JOIN frames f2 ON f1.parent_frame_id = f2.frame_id\n WHERE f1.project_id = ?\n AND f1.parent_frame_id IS NOT NULL\n AND f2.frame_id IS NULL\n `\n )\n .all(projectId) as Array<{ frame_id: string; parent_frame_id: string }>;\n\n if (invalidParents.length > 0) {\n issues.push(\n `${invalidParents.length} frames with invalid parent references`\n );\n }\n\n // Check for depth inconsistencies\n const depthIssues = this.db\n .prepare(\n `\n SELECT f1.frame_id, f1.depth as child_depth, f2.depth as parent_depth\n FROM frames f1\n JOIN frames f2 ON f1.parent_frame_id = f2.frame_id\n WHERE f1.project_id = ?\n AND f1.depth != f2.depth + 1\n `\n )\n .all(projectId) as Array<{\n frame_id: string;\n child_depth: number;\n parent_depth: number;\n }>;\n\n if (depthIssues.length > 0) {\n issues.push(`${depthIssues.length} frames with incorrect depth values`);\n }\n\n // Check for events without valid frames\n const orphanEvents = this.db\n .prepare(\n `\n SELECT COUNT(*) as count\n FROM events e\n LEFT JOIN frames f ON e.frame_id = f.frame_id\n WHERE f.frame_id IS NULL\n `\n )\n .get() as { count: number };\n\n if (orphanEvents.count > 0) {\n issues.push(`${orphanEvents.count} orphaned events without valid frames`);\n }\n\n return {\n valid: issues.length === 0,\n issues,\n };\n }\n\n /**\n * Clean up orphaned events (events without valid frames)\n */\n cleanupOrphanedEvents(): number {\n const result = this.db\n .prepare(\n `\n DELETE FROM events\n WHERE frame_id NOT IN (SELECT frame_id FROM frames)\n `\n )\n .run();\n\n if (result.changes > 0) {\n logger.info('Cleaned up orphaned events', { count: result.changes });\n }\n\n return result.changes;\n }\n\n /**\n * Get recovery statistics\n */\n getRecoveryStats(): {\n totalFrames: number;\n activeFrames: number;\n closedFrames: number;\n recoveredFrames: number;\n oldestActiveFrame: string | null;\n } {\n const stats = this.db\n .prepare(\n `\n SELECT\n COUNT(*) as total,\n SUM(CASE WHEN state = 'active' THEN 1 ELSE 0 END) as active,\n SUM(CASE WHEN state = 'closed' THEN 1 ELSE 0 END) as closed,\n SUM(CASE WHEN json_extract(outputs, '$.recovered') = true THEN 1 ELSE 0 END) as recovered\n FROM frames\n `\n )\n .get() as {\n total: number;\n active: number;\n closed: number;\n recovered: number;\n };\n\n const oldest = this.db\n .prepare(\n `\n SELECT datetime(created_at, 'unixepoch') as created\n FROM frames\n WHERE state = 'active'\n ORDER BY created_at ASC\n LIMIT 1\n `\n )\n .get() as { created: string } | undefined;\n\n return {\n totalFrames: stats.total,\n activeFrames: stats.active,\n closedFrames: stats.closed,\n recoveredFrames: stats.recovered,\n oldestActiveFrame: oldest?.created ?? null,\n };\n }\n}\n\n/**\n * Convenience function to run recovery on a database\n */\nexport async function recoverDatabase(\n db: Database.Database,\n currentRunId?: string\n): Promise<RecoveryReport> {\n const recovery = new FrameRecovery(db);\n if (currentRunId) {\n recovery.setCurrentRunId(currentRunId);\n }\n return recovery.recoverOnStartup();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -9,11 +9,17 @@ import { FrameDigestGenerator } from "./frame-digest.js";
|
|
|
9
9
|
import {
|
|
10
10
|
frameLifecycleHooks
|
|
11
11
|
} from "./frame-lifecycle-hooks.js";
|
|
12
|
+
import {
|
|
13
|
+
FrameRecovery,
|
|
14
|
+
recoverDatabase
|
|
15
|
+
} from "./frame-recovery.js";
|
|
12
16
|
export {
|
|
13
17
|
FrameDatabase,
|
|
14
18
|
FrameDigestGenerator,
|
|
15
19
|
RefactoredFrameManager as FrameManager,
|
|
20
|
+
FrameRecovery,
|
|
16
21
|
FrameStack,
|
|
17
|
-
frameLifecycleHooks
|
|
22
|
+
frameLifecycleHooks,
|
|
23
|
+
recoverDatabase
|
|
18
24
|
};
|
|
19
25
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/context/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Context Module Exports\n * Maintains compatibility while providing access to refactored components\n */\n\n// Export refactored components as primary\nexport { RefactoredFrameManager as FrameManager } from './refactored-frame-manager.js';\n\n// Export types (type-only, no runtime value)\nexport type {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\n\n// Export focused modules for direct access\nexport { FrameDatabase } from './frame-database.js';\nexport { FrameStack } from './frame-stack.js';\nexport { FrameDigestGenerator } from './frame-digest.js';\n\n// Export lifecycle hooks for external integrations\nexport {\n frameLifecycleHooks,\n type FrameCloseData,\n type FrameCloseHook,\n type FrameCreateHook,\n} from './frame-lifecycle-hooks.js';\n"],
|
|
5
|
-
"mappings": ";;;;AAMA,SAAmC,8BAAoB;AAgBvD,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AAGrC;AAAA,EACE;AAAA,OAIK;",
|
|
4
|
+
"sourcesContent": ["/**\n * Context Module Exports\n * Maintains compatibility while providing access to refactored components\n */\n\n// Export refactored components as primary\nexport { RefactoredFrameManager as FrameManager } from './refactored-frame-manager.js';\n\n// Export types (type-only, no runtime value)\nexport type {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\n\n// Export focused modules for direct access\nexport { FrameDatabase } from './frame-database.js';\nexport { FrameStack } from './frame-stack.js';\nexport { FrameDigestGenerator } from './frame-digest.js';\n\n// Export lifecycle hooks for external integrations\nexport {\n frameLifecycleHooks,\n type FrameCloseData,\n type FrameCloseHook,\n type FrameCreateHook,\n} from './frame-lifecycle-hooks.js';\n\n// Export recovery system\nexport {\n FrameRecovery,\n recoverDatabase,\n type RecoveryReport,\n type IntegrityCheckResult,\n type OrphanedFrameResult,\n} from './frame-recovery.js';\n"],
|
|
5
|
+
"mappings": ";;;;AAMA,SAAmC,8BAAoB;AAgBvD,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,4BAA4B;AAGrC;AAAA,EACE;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,18 +17,23 @@ const DEFAULT_MAX_DEPTH = 100;
|
|
|
17
17
|
import { FrameDatabase } from "./frame-database.js";
|
|
18
18
|
import { FrameStack } from "./frame-stack.js";
|
|
19
19
|
import { FrameDigestGenerator } from "./frame-digest.js";
|
|
20
|
+
import { FrameRecovery } from "./frame-recovery.js";
|
|
20
21
|
class RefactoredFrameManager {
|
|
21
22
|
frameDb;
|
|
22
23
|
frameStack;
|
|
23
24
|
digestGenerator;
|
|
25
|
+
frameRecovery;
|
|
26
|
+
db;
|
|
24
27
|
currentRunId;
|
|
25
28
|
sessionId;
|
|
26
29
|
projectId;
|
|
27
30
|
queryMode = FrameQueryMode.PROJECT_ACTIVE;
|
|
28
31
|
config;
|
|
29
32
|
maxFrameDepth = DEFAULT_MAX_DEPTH;
|
|
33
|
+
lastRecoveryReport = null;
|
|
30
34
|
constructor(db, projectId, config) {
|
|
31
35
|
this.projectId = projectId;
|
|
36
|
+
this.db = db;
|
|
32
37
|
this.config = {
|
|
33
38
|
projectId,
|
|
34
39
|
runId: config?.runId || uuidv4(),
|
|
@@ -45,6 +50,8 @@ class RefactoredFrameManager {
|
|
|
45
50
|
this.currentRunId
|
|
46
51
|
);
|
|
47
52
|
this.digestGenerator = new FrameDigestGenerator(this.frameDb);
|
|
53
|
+
this.frameRecovery = new FrameRecovery(db);
|
|
54
|
+
this.frameRecovery.setCurrentRunId(this.currentRunId);
|
|
48
55
|
this.frameDb.initSchema();
|
|
49
56
|
logger.info("RefactoredFrameManager initialized", {
|
|
50
57
|
projectId: this.projectId,
|
|
@@ -57,9 +64,19 @@ class RefactoredFrameManager {
|
|
|
57
64
|
*/
|
|
58
65
|
async initialize() {
|
|
59
66
|
try {
|
|
67
|
+
this.lastRecoveryReport = await this.frameRecovery.recoverOnStartup();
|
|
68
|
+
if (!this.lastRecoveryReport.recovered) {
|
|
69
|
+
logger.warn("Recovery completed with issues", {
|
|
70
|
+
errors: this.lastRecoveryReport.errors,
|
|
71
|
+
orphansFound: this.lastRecoveryReport.orphanedFrames.detected,
|
|
72
|
+
integrityPassed: this.lastRecoveryReport.integrityCheck.passed
|
|
73
|
+
});
|
|
74
|
+
}
|
|
60
75
|
await this.frameStack.initialize();
|
|
61
76
|
logger.info("Frame manager initialization completed", {
|
|
62
|
-
stackDepth: this.frameStack.getDepth()
|
|
77
|
+
stackDepth: this.frameStack.getDepth(),
|
|
78
|
+
recoveryRan: true,
|
|
79
|
+
orphansClosed: this.lastRecoveryReport.orphanedFrames.closed
|
|
63
80
|
});
|
|
64
81
|
} catch (error) {
|
|
65
82
|
throw new SystemError(
|
|
@@ -368,6 +385,25 @@ class RefactoredFrameManager {
|
|
|
368
385
|
getStatistics() {
|
|
369
386
|
return this.frameDb.getStatistics();
|
|
370
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Get the last recovery report from initialization
|
|
390
|
+
*/
|
|
391
|
+
getRecoveryReport() {
|
|
392
|
+
return this.lastRecoveryReport;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Manually trigger recovery (e.g., after detecting issues)
|
|
396
|
+
*/
|
|
397
|
+
async runRecovery() {
|
|
398
|
+
this.lastRecoveryReport = await this.frameRecovery.recoverOnStartup();
|
|
399
|
+
return this.lastRecoveryReport;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Validate project data integrity
|
|
403
|
+
*/
|
|
404
|
+
validateProjectIntegrity() {
|
|
405
|
+
return this.frameRecovery.validateProjectIntegrity(this.projectId);
|
|
406
|
+
}
|
|
371
407
|
/**
|
|
372
408
|
* Close all child frames recursively with depth limit to prevent stack overflow
|
|
373
409
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/context/refactored-frame-manager.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Refactored Frame Manager - Modular Implementation\n * Main orchestrator that uses focused modules for frame management\n */\n\nimport Database from 'better-sqlite3';\nimport { v4 as uuidv4 } from 'uuid';\nimport { logger } from '../monitoring/logger.js';\nimport { trace } from '../trace/index.js';\nimport {\n FrameError,\n SystemError,\n ErrorCode,\n wrapError,\n createErrorHandler,\n} from '../errors/index.js';\nimport { retry, withTimeout } from '../errors/recovery.js';\nimport { sessionManager, FrameQueryMode } from '../session/index.js';\nimport { frameLifecycleHooks } from './frame-lifecycle-hooks.js';\n\n// Constants for frame validation\nconst MAX_FRAME_DEPTH = 100; // Maximum allowed frame depth\nconst DEFAULT_MAX_DEPTH = 100; // Default if not configured\n\n// Import refactored modules\nimport {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\nimport { FrameDatabase } from './frame-database.js';\nimport { FrameStack } from './frame-stack.js';\nimport { FrameDigestGenerator } from './frame-digest.js';\n\nexport class RefactoredFrameManager {\n private frameDb: FrameDatabase;\n private frameStack: FrameStack;\n private digestGenerator: FrameDigestGenerator;\n\n private currentRunId: string;\n private sessionId: string;\n private projectId: string;\n private queryMode: FrameQueryMode = FrameQueryMode.PROJECT_ACTIVE;\n private config: FrameManagerConfig;\n private maxFrameDepth: number = DEFAULT_MAX_DEPTH;\n\n constructor(\n db: Database.Database,\n projectId: string,\n config?: Partial<FrameManagerConfig>\n ) {\n this.projectId = projectId;\n this.config = {\n projectId,\n runId: config?.runId || uuidv4(),\n sessionId: config?.sessionId || uuidv4(),\n maxStackDepth: config?.maxStackDepth || 50,\n };\n\n // Set max frame depth from config if provided\n this.maxFrameDepth = config?.maxStackDepth || DEFAULT_MAX_DEPTH;\n\n this.currentRunId = this.config.runId!;\n this.sessionId = this.config.sessionId!;\n\n // Initialize modules\n this.frameDb = new FrameDatabase(db);\n this.frameStack = new FrameStack(\n this.frameDb,\n projectId,\n this.currentRunId\n );\n this.digestGenerator = new FrameDigestGenerator(this.frameDb);\n\n // Initialize database schema\n this.frameDb.initSchema();\n\n logger.info('RefactoredFrameManager initialized', {\n projectId: this.projectId,\n runId: this.currentRunId,\n sessionId: this.sessionId,\n });\n }\n\n /**\n * Initialize the frame manager\n */\n async initialize(): Promise<void> {\n try {\n await this.frameStack.initialize();\n\n logger.info('Frame manager initialization completed', {\n stackDepth: this.frameStack.getDepth(),\n });\n } catch (error: unknown) {\n throw new SystemError(\n 'Failed to initialize frame manager',\n ErrorCode.SYSTEM_INIT_FAILED,\n { projectId: this.projectId },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Create a new frame\n */\n createFrame(options: FrameCreationOptions): string;\n createFrame(\n type: FrameType,\n name: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string;\n createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.createFrame',\n { typeOrOptions, name },\n () => this._createFrame(typeOrOptions, name, inputs, parentFrameId)\n );\n }\n\n private _createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n let frameOptions: FrameCreationOptions;\n\n // Handle both function signatures\n if (typeof typeOrOptions === 'string') {\n frameOptions = {\n type: typeOrOptions,\n name: name!,\n inputs: inputs || {},\n parentFrameId,\n };\n } else {\n frameOptions = typeOrOptions;\n }\n\n // Validate inputs\n if (!frameOptions.name || frameOptions.name.trim().length === 0) {\n throw new FrameError(\n 'Frame name is required',\n ErrorCode.FRAME_INVALID_INPUT,\n { frameOptions }\n );\n }\n\n // Check stack depth limit\n if (this.frameStack.getDepth() >= this.config.maxStackDepth!) {\n throw new FrameError(\n `Maximum stack depth reached: ${this.config.maxStackDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n { currentDepth: this.frameStack.getDepth() }\n );\n }\n\n // Determine parent frame\n const resolvedParentId =\n frameOptions.parentFrameId || this.frameStack.getCurrentFrameId();\n\n // Get depth from parent frame, not from stack position\n let depth = 0;\n if (resolvedParentId) {\n const parentFrame = this.frameDb.getFrame(resolvedParentId);\n depth = parentFrame ? parentFrame.depth + 1 : 0;\n }\n\n // Check for depth limit\n if (depth > this.maxFrameDepth) {\n throw new FrameError(\n `Maximum frame depth exceeded: ${depth} > ${this.maxFrameDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n currentDepth: depth,\n maxDepth: this.maxFrameDepth,\n frameName: frameOptions.name,\n parentFrameId: resolvedParentId,\n }\n );\n }\n\n // Check for circular reference before creating frame\n if (resolvedParentId) {\n const cycle = this.detectCycle(uuidv4(), resolvedParentId);\n if (cycle) {\n throw new FrameError(\n `Circular reference detected in frame hierarchy`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n parentFrameId: resolvedParentId,\n cycle,\n frameName: frameOptions.name,\n }\n );\n }\n }\n\n // Create frame data\n const frameId = uuidv4();\n const frame: Omit<Frame, 'created_at' | 'closed_at'> = {\n frame_id: frameId,\n run_id: this.currentRunId,\n project_id: this.projectId,\n parent_frame_id: resolvedParentId,\n depth,\n type: frameOptions.type,\n name: frameOptions.name,\n state: 'active',\n inputs: frameOptions.inputs || {},\n outputs: {},\n digest_json: {},\n };\n\n // Insert into database\n const createdFrame = this.frameDb.insertFrame(frame);\n\n // Add to stack\n this.frameStack.pushFrame(frameId);\n\n logger.info('Created frame', {\n frameId,\n name: frameOptions.name,\n type: frameOptions.type,\n parentFrameId: resolvedParentId,\n stackDepth: this.frameStack.getDepth(),\n });\n\n return frameId;\n }\n\n /**\n * Close a frame and generate digest\n */\n closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n trace.traceSync(\n 'function',\n 'FrameManager.closeFrame',\n { frameId, outputs },\n () => this._closeFrame(frameId, outputs)\n );\n }\n\n private _closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame to close',\n ErrorCode.FRAME_INVALID_STATE,\n {\n operation: 'closeFrame',\n stackDepth: this.frameStack.getDepth(),\n }\n );\n }\n\n // Get frame details\n const frame = this.frameDb.getFrame(targetFrameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${targetFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n {\n frameId: targetFrameId,\n operation: 'closeFrame',\n runId: this.currentRunId,\n }\n );\n }\n\n if (frame.state === 'closed') {\n logger.warn('Attempted to close already closed frame', {\n frameId: targetFrameId,\n });\n return;\n }\n\n // Generate digest before closing\n const digest = this.digestGenerator.generateDigest(targetFrameId);\n const finalOutputs = { ...outputs, ...digest.structured };\n\n // Update frame to closed state\n this.frameDb.updateFrame(targetFrameId, {\n state: 'closed',\n outputs: finalOutputs,\n digest_text: digest.text,\n digest_json: digest.structured,\n closed_at: Math.floor(Date.now() / 1000),\n });\n\n // Remove from stack (this will also remove any child frames)\n this.frameStack.popFrame(targetFrameId);\n\n // Close all child frames recursively\n this.closeChildFrames(targetFrameId);\n\n // Trigger lifecycle hooks (fire and forget)\n const events = this.frameDb.getFrameEvents(targetFrameId);\n const anchors = this.frameDb.getFrameAnchors(targetFrameId);\n frameLifecycleHooks\n .triggerClose({ frame: { ...frame, state: 'closed' }, events, anchors })\n .catch(() => {\n // Silently ignore errors - hooks are non-critical\n });\n\n logger.info('Closed frame', {\n frameId: targetFrameId,\n name: frame.name,\n duration: Math.floor(Date.now() / 1000) - frame.created_at,\n digestLength: digest.text.length,\n stackDepth: this.frameStack.getDepth(),\n });\n }\n\n /**\n * Add an event to the current frame\n */\n addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addEvent',\n { eventType, frameId },\n () => this._addEvent(eventType, payload, frameId)\n );\n }\n\n private _addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for event',\n ErrorCode.FRAME_INVALID_STATE,\n {\n eventType,\n operation: 'addEvent',\n }\n );\n }\n\n const eventId = uuidv4();\n const sequence = this.frameDb.getNextEventSequence(targetFrameId);\n\n const event: Omit<Event, 'ts'> = {\n event_id: eventId,\n frame_id: targetFrameId,\n run_id: this.currentRunId,\n seq: sequence,\n event_type: eventType,\n payload,\n };\n\n const createdEvent = this.frameDb.insertEvent(event);\n\n logger.debug('Added event', {\n eventId,\n frameId: targetFrameId,\n eventType,\n sequence,\n });\n\n return eventId;\n }\n\n /**\n * Add an anchor (important fact) to current frame\n */\n addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number = 5,\n metadata: Record<string, any> = {},\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addAnchor',\n { type, frameId },\n () => this._addAnchor(type, text, priority, metadata, frameId)\n );\n }\n\n private _addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number,\n metadata: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for anchor',\n ErrorCode.FRAME_INVALID_STATE,\n {\n anchorType: type,\n operation: 'addAnchor',\n }\n );\n }\n\n const anchorId = uuidv4();\n const anchor: Omit<Anchor, 'created_at'> = {\n anchor_id: anchorId,\n frame_id: targetFrameId,\n type,\n text,\n priority,\n metadata,\n };\n\n const createdAnchor = this.frameDb.insertAnchor(anchor);\n\n logger.debug('Added anchor', {\n anchorId,\n frameId: targetFrameId,\n type,\n priority,\n });\n\n return anchorId;\n }\n\n /**\n * Get hot stack context\n */\n getHotStackContext(maxEvents: number = 20): FrameContext[] {\n return this.frameStack.getHotStackContext(maxEvents);\n }\n\n /**\n * Get active frame path (root to current)\n */\n getActiveFramePath(): Frame[] {\n return this.frameStack.getStackFrames();\n }\n\n /**\n * Get current frame ID\n */\n getCurrentFrameId(): string | undefined {\n return this.frameStack.getCurrentFrameId();\n }\n\n /**\n * Get stack depth\n */\n getStackDepth(): number {\n return this.frameStack.getDepth();\n }\n\n /**\n * Get frame by ID\n */\n getFrame(frameId: string): Frame | undefined {\n return this.frameDb.getFrame(frameId);\n }\n\n /**\n * Get frame events\n */\n getFrameEvents(frameId: string, limit?: number): Event[] {\n return this.frameDb.getFrameEvents(frameId, limit);\n }\n\n /**\n * Get frame anchors\n */\n getFrameAnchors(frameId: string): Anchor[] {\n return this.frameDb.getFrameAnchors(frameId);\n }\n\n /**\n * Generate digest for a frame\n */\n generateDigest(frameId: string): DigestResult {\n return this.digestGenerator.generateDigest(frameId);\n }\n\n /**\n * Validate stack consistency\n */\n validateStack(): { isValid: boolean; errors: string[] } {\n return this.frameStack.validateStack();\n }\n\n /**\n * Get database statistics\n */\n getStatistics(): Record<string, number> {\n return this.frameDb.getStatistics();\n }\n\n /**\n * Close all child frames recursively with depth limit to prevent stack overflow\n */\n private closeChildFrames(parentFrameId: string, depth: number = 0): void {\n // Prevent stack overflow with depth limit\n if (depth > this.maxFrameDepth) {\n logger.warn('closeChildFrames depth limit exceeded', {\n parentFrameId,\n depth,\n maxDepth: this.maxFrameDepth,\n });\n return;\n }\n\n try {\n const activeFrames = this.frameDb.getFramesByProject(\n this.projectId,\n 'active'\n );\n const childFrames = activeFrames.filter(\n (f) => f.parent_frame_id === parentFrameId\n );\n\n for (const childFrame of childFrames) {\n if (this.frameStack.isFrameActive(childFrame.frame_id)) {\n // Close child's children first (depth-first)\n this.closeChildFrames(childFrame.frame_id, depth + 1);\n // Then close the child frame directly without recursing through closeFrame\n this.closeFrameDirectly(childFrame.frame_id);\n }\n }\n } catch (error: unknown) {\n logger.warn('Failed to close child frames', { parentFrameId, error });\n }\n }\n\n /**\n * Close a frame directly without triggering child frame closure\n * Used by closeChildFrames to avoid double recursion\n */\n private closeFrameDirectly(frameId: string): void {\n const frame = this.frameDb.getFrame(frameId);\n if (!frame || frame.state === 'closed') return;\n\n const digest = this.digestGenerator.generateDigest(frameId);\n\n this.frameDb.updateFrame(frameId, {\n state: 'closed',\n outputs: digest.structured,\n digest_text: digest.text,\n digest_json: digest.structured,\n closed_at: Math.floor(Date.now() / 1000),\n });\n\n this.frameStack.popFrame(frameId);\n\n logger.debug('Closed child frame directly', { frameId });\n }\n\n /**\n * Extract active artifacts from frame events\n */\n getActiveArtifacts(frameId: string): string[] {\n const events = this.frameDb.getFrameEvents(frameId);\n const artifacts: string[] = [];\n\n for (const event of events) {\n if (event.event_type === 'artifact' && event.payload.path) {\n artifacts.push(event.payload.path);\n }\n }\n\n return [...new Set(artifacts)];\n }\n\n /**\n * Extract constraints from frame inputs\n */\n extractConstraints(inputs: Record<string, unknown>): string[] {\n const constraints: string[] = [];\n\n if (inputs.constraints && Array.isArray(inputs.constraints)) {\n constraints.push(...(inputs.constraints as string[]));\n }\n\n if (inputs.requirements && Array.isArray(inputs.requirements)) {\n constraints.push(...(inputs.requirements as string[]));\n }\n\n if (inputs.limitations && Array.isArray(inputs.limitations)) {\n constraints.push(...(inputs.limitations as string[]));\n }\n\n return constraints;\n }\n\n /**\n * Detect if setting a parent frame would create a cycle in the frame hierarchy.\n * Returns the cycle path if detected, or null if no cycle.\n * @param childFrameId - The frame that would be the child\n * @param parentFrameId - The proposed parent frame\n * @returns Array of frame IDs forming the cycle, or null if no cycle\n */\n private detectCycle(\n childFrameId: string,\n parentFrameId: string\n ): string[] | null {\n const visited = new Set<string>();\n const path: string[] = [];\n\n // Start from the proposed parent and traverse up the hierarchy\n let currentId: string | undefined = parentFrameId;\n\n while (currentId) {\n // If we've seen this frame before, we have a cycle\n if (visited.has(currentId)) {\n // Build the cycle path\n const cycleStart = path.indexOf(currentId);\n return path.slice(cycleStart).concat(currentId);\n }\n\n // If the current frame is the child we're trying to add, it's a cycle\n if (currentId === childFrameId) {\n return path.concat([currentId, childFrameId]);\n }\n\n visited.add(currentId);\n path.push(currentId);\n\n // Move to the parent of current frame\n const frame = this.frameDb.getFrame(currentId);\n if (!frame) {\n // Frame not found, no cycle possible through this path\n break;\n }\n currentId = frame.parent_frame_id;\n\n // Safety check: if we've traversed too many levels, something is wrong\n if (path.length > this.maxFrameDepth) {\n throw new FrameError(\n `Frame hierarchy traversal exceeded maximum depth during cycle detection`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n depth: path.length,\n maxDepth: this.maxFrameDepth,\n path,\n }\n );\n }\n }\n\n return null; // No cycle detected\n }\n\n /**\n * Update parent frame of an existing frame (with cycle detection)\n * @param frameId - The frame to update\n * @param newParentFrameId - The new parent frame ID (null to make it a root frame)\n */\n public updateParentFrame(\n frameId: string,\n newParentFrameId: string | null\n ): void {\n // Check if frame exists\n const frame = this.frameDb.getFrame(frameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${frameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId }\n );\n }\n\n // If setting a parent, validate and check for cycles\n if (newParentFrameId) {\n // Verify the new parent exists\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (!newParentFrame) {\n throw new FrameError(\n `Parent frame not found: ${newParentFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId, newParentFrameId }\n );\n }\n\n const cycle = this.detectCycle(frameId, newParentFrameId);\n if (cycle) {\n throw new FrameError(\n `Cannot set parent: would create circular reference`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n frameId,\n newParentFrameId,\n cycle,\n currentParentId: frame.parent_frame_id,\n }\n );\n }\n\n // Check depth after parent change\n const newDepth = newParentFrame.depth + 1;\n if (newDepth > this.maxFrameDepth) {\n throw new FrameError(\n `Cannot set parent: would exceed maximum frame depth`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n frameId,\n newParentFrameId,\n newDepth,\n maxDepth: this.maxFrameDepth,\n }\n );\n }\n }\n\n // Calculate new depth based on parent\n let newDepth = 0;\n if (newParentFrameId) {\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (newParentFrame) {\n newDepth = newParentFrame.depth + 1;\n }\n }\n\n // Update the frame's parent and depth\n this.frameDb.updateFrame(frameId, {\n parent_frame_id: newParentFrameId,\n depth: newDepth,\n });\n\n logger.info('Updated parent frame', {\n frameId,\n oldParentId: frame.parent_frame_id,\n newParentId: newParentFrameId,\n });\n }\n\n /**\n * Validate the entire frame hierarchy for cycles and depth violations\n * @returns Validation result with any detected issues\n */\n public validateFrameHierarchy(): {\n isValid: boolean;\n errors: string[];\n warnings: string[];\n } {\n const errors: string[] = [];\n const warnings: string[] = [];\n const allFrames = this.frameDb.getFramesByProject(this.projectId);\n\n // Check each frame for depth violations\n for (const frame of allFrames) {\n if (frame.depth > this.maxFrameDepth) {\n errors.push(\n `Frame ${frame.frame_id} exceeds max depth: ${frame.depth} > ${this.maxFrameDepth}`\n );\n }\n\n // Warn about deep frames approaching the limit\n if (frame.depth > this.maxFrameDepth * 0.8) {\n warnings.push(\n `Frame ${frame.frame_id} is deep in hierarchy: ${frame.depth}/${this.maxFrameDepth}`\n );\n }\n }\n\n // Check for cycles by traversing from each root\n const rootFrames = allFrames.filter((f) => !f.parent_frame_id);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n const checkForCycle = (frameId: string): boolean => {\n if (visiting.has(frameId)) {\n errors.push(`Cycle detected involving frame ${frameId}`);\n return true;\n }\n\n if (visited.has(frameId)) {\n return false;\n }\n\n visiting.add(frameId);\n\n // Check all children\n const children = allFrames.filter((f) => f.parent_frame_id === frameId);\n for (const child of children) {\n if (checkForCycle(child.frame_id)) {\n return true;\n }\n }\n\n visiting.delete(frameId);\n visited.add(frameId);\n return false;\n };\n\n // Check from each root\n for (const root of rootFrames) {\n checkForCycle(root.frame_id);\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n };\n }\n\n /**\n * Set query mode for frame retrieval\n */\n setQueryMode(mode: FrameQueryMode): void {\n this.queryMode = mode;\n // Reinitialize stack with new query mode\n this.frameStack.setQueryMode(mode);\n }\n\n /**\n * Get recent frames for context sharing\n */\n async getRecentFrames(limit: number = 100): Promise<Frame[]> {\n try {\n const frames = this.frameDb.getFramesByProject(this.projectId);\n\n // Sort by created_at descending and limit\n return frames\n .sort((a, b) => (b.created_at || 0) - (a.created_at || 0))\n .slice(0, limit)\n .map((frame) => ({\n ...frame,\n // Add compatibility fields\n frameId: frame.frame_id,\n runId: frame.run_id,\n projectId: frame.project_id,\n parentFrameId: frame.parent_frame_id,\n title: frame.name,\n timestamp: frame.created_at,\n metadata: {\n tags: this.extractTagsFromFrame(frame),\n importance: this.calculateFrameImportance(frame),\n },\n data: {\n inputs: frame.inputs,\n outputs: frame.outputs,\n digest: frame.digest_json,\n },\n }));\n } catch (error: unknown) {\n logger.error('Failed to get recent frames', error as Error);\n return [];\n }\n }\n\n /**\n * Add context metadata to the current frame\n */\n async addContext(key: string, value: any): Promise<void> {\n const currentFrameId = this.frameStack.getCurrentFrameId();\n if (!currentFrameId) return;\n\n try {\n const frame = this.frameDb.getFrame(currentFrameId);\n if (!frame) return;\n\n const metadata = frame.outputs || {};\n metadata[key] = value;\n\n this.frameDb.updateFrame(currentFrameId, {\n outputs: metadata,\n });\n } catch (error: unknown) {\n logger.warn('Failed to add context to frame', { error, key });\n }\n }\n\n /**\n * Delete a frame completely from the database (used in handoffs)\n */\n deleteFrame(frameId: string): void {\n try {\n // Remove from active stack if present\n this.frameStack.removeFrame(frameId);\n\n // Delete the frame and related data (cascades via FrameDatabase)\n this.frameDb.deleteFrame(frameId);\n\n logger.debug('Deleted frame completely', { frameId });\n } catch (error: unknown) {\n logger.error('Failed to delete frame', { frameId, error });\n throw error;\n }\n }\n\n /**\n * Extract tags from frame for categorization\n */\n private extractTagsFromFrame(frame: Frame): string[] {\n const tags: string[] = [];\n\n if (frame.type) tags.push(frame.type);\n\n if (frame.name) {\n const nameLower = frame.name.toLowerCase();\n if (nameLower.includes('error')) tags.push('error');\n if (nameLower.includes('fix')) tags.push('resolution');\n if (nameLower.includes('decision')) tags.push('decision');\n if (nameLower.includes('milestone')) tags.push('milestone');\n }\n\n try {\n if (frame.digest_json && typeof frame.digest_json === 'object') {\n const digest = frame.digest_json as Record<string, unknown>;\n if (Array.isArray(digest.tags)) {\n tags.push(...(digest.tags as string[]));\n }\n }\n } catch {\n // Ignore parse errors\n }\n\n return [...new Set(tags)];\n }\n\n /**\n * Calculate frame importance for prioritization\n */\n private calculateFrameImportance(frame: Frame): 'high' | 'medium' | 'low' {\n if (frame.type === 'milestone' || frame.name?.includes('decision')) {\n return 'high';\n }\n\n if (frame.type === 'error' || frame.type === 'resolution') {\n return 'medium';\n }\n\n if (frame.closed_at && frame.created_at) {\n const duration = frame.closed_at - frame.created_at;\n if (duration > 300) return 'medium';\n }\n\n return 'low';\n }\n}\n\n// Re-export types for compatibility (type-only, no runtime value)\nexport type {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Refactored Frame Manager - Modular Implementation\n * Main orchestrator that uses focused modules for frame management\n */\n\nimport Database from 'better-sqlite3';\nimport { v4 as uuidv4 } from 'uuid';\nimport { logger } from '../monitoring/logger.js';\nimport { trace } from '../trace/index.js';\nimport {\n FrameError,\n SystemError,\n ErrorCode,\n wrapError,\n createErrorHandler,\n} from '../errors/index.js';\nimport { retry, withTimeout } from '../errors/recovery.js';\nimport { sessionManager, FrameQueryMode } from '../session/index.js';\nimport { frameLifecycleHooks } from './frame-lifecycle-hooks.js';\n\n// Constants for frame validation\nconst MAX_FRAME_DEPTH = 100; // Maximum allowed frame depth\nconst DEFAULT_MAX_DEPTH = 100; // Default if not configured\n\n// Import refactored modules\nimport {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\nimport { FrameDatabase } from './frame-database.js';\nimport { FrameStack } from './frame-stack.js';\nimport { FrameDigestGenerator } from './frame-digest.js';\nimport { FrameRecovery, type RecoveryReport } from './frame-recovery.js';\n\nexport class RefactoredFrameManager {\n private frameDb: FrameDatabase;\n private frameStack: FrameStack;\n private digestGenerator: FrameDigestGenerator;\n private frameRecovery: FrameRecovery;\n private db: Database.Database;\n\n private currentRunId: string;\n private sessionId: string;\n private projectId: string;\n private queryMode: FrameQueryMode = FrameQueryMode.PROJECT_ACTIVE;\n private config: FrameManagerConfig;\n private maxFrameDepth: number = DEFAULT_MAX_DEPTH;\n private lastRecoveryReport: RecoveryReport | null = null;\n\n constructor(\n db: Database.Database,\n projectId: string,\n config?: Partial<FrameManagerConfig>\n ) {\n this.projectId = projectId;\n this.db = db;\n this.config = {\n projectId,\n runId: config?.runId || uuidv4(),\n sessionId: config?.sessionId || uuidv4(),\n maxStackDepth: config?.maxStackDepth || 50,\n };\n\n // Set max frame depth from config if provided\n this.maxFrameDepth = config?.maxStackDepth || DEFAULT_MAX_DEPTH;\n\n this.currentRunId = this.config.runId!;\n this.sessionId = this.config.sessionId!;\n\n // Initialize modules\n this.frameDb = new FrameDatabase(db);\n this.frameStack = new FrameStack(\n this.frameDb,\n projectId,\n this.currentRunId\n );\n this.digestGenerator = new FrameDigestGenerator(this.frameDb);\n this.frameRecovery = new FrameRecovery(db);\n this.frameRecovery.setCurrentRunId(this.currentRunId);\n\n // Initialize database schema\n this.frameDb.initSchema();\n\n logger.info('RefactoredFrameManager initialized', {\n projectId: this.projectId,\n runId: this.currentRunId,\n sessionId: this.sessionId,\n });\n }\n\n /**\n * Initialize the frame manager\n */\n async initialize(): Promise<void> {\n try {\n // Run crash recovery first\n this.lastRecoveryReport = await this.frameRecovery.recoverOnStartup();\n\n if (!this.lastRecoveryReport.recovered) {\n logger.warn('Recovery completed with issues', {\n errors: this.lastRecoveryReport.errors,\n orphansFound: this.lastRecoveryReport.orphanedFrames.detected,\n integrityPassed: this.lastRecoveryReport.integrityCheck.passed,\n });\n }\n\n await this.frameStack.initialize();\n\n logger.info('Frame manager initialization completed', {\n stackDepth: this.frameStack.getDepth(),\n recoveryRan: true,\n orphansClosed: this.lastRecoveryReport.orphanedFrames.closed,\n });\n } catch (error: unknown) {\n throw new SystemError(\n 'Failed to initialize frame manager',\n ErrorCode.SYSTEM_INIT_FAILED,\n { projectId: this.projectId },\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Create a new frame\n */\n createFrame(options: FrameCreationOptions): string;\n createFrame(\n type: FrameType,\n name: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string;\n createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.createFrame',\n { typeOrOptions, name },\n () => this._createFrame(typeOrOptions, name, inputs, parentFrameId)\n );\n }\n\n private _createFrame(\n typeOrOptions: FrameType | FrameCreationOptions,\n name?: string,\n inputs?: Record<string, any>,\n parentFrameId?: string\n ): string {\n let frameOptions: FrameCreationOptions;\n\n // Handle both function signatures\n if (typeof typeOrOptions === 'string') {\n frameOptions = {\n type: typeOrOptions,\n name: name!,\n inputs: inputs || {},\n parentFrameId,\n };\n } else {\n frameOptions = typeOrOptions;\n }\n\n // Validate inputs\n if (!frameOptions.name || frameOptions.name.trim().length === 0) {\n throw new FrameError(\n 'Frame name is required',\n ErrorCode.FRAME_INVALID_INPUT,\n { frameOptions }\n );\n }\n\n // Check stack depth limit\n if (this.frameStack.getDepth() >= this.config.maxStackDepth!) {\n throw new FrameError(\n `Maximum stack depth reached: ${this.config.maxStackDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n { currentDepth: this.frameStack.getDepth() }\n );\n }\n\n // Determine parent frame\n const resolvedParentId =\n frameOptions.parentFrameId || this.frameStack.getCurrentFrameId();\n\n // Get depth from parent frame, not from stack position\n let depth = 0;\n if (resolvedParentId) {\n const parentFrame = this.frameDb.getFrame(resolvedParentId);\n depth = parentFrame ? parentFrame.depth + 1 : 0;\n }\n\n // Check for depth limit\n if (depth > this.maxFrameDepth) {\n throw new FrameError(\n `Maximum frame depth exceeded: ${depth} > ${this.maxFrameDepth}`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n currentDepth: depth,\n maxDepth: this.maxFrameDepth,\n frameName: frameOptions.name,\n parentFrameId: resolvedParentId,\n }\n );\n }\n\n // Check for circular reference before creating frame\n if (resolvedParentId) {\n const cycle = this.detectCycle(uuidv4(), resolvedParentId);\n if (cycle) {\n throw new FrameError(\n `Circular reference detected in frame hierarchy`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n parentFrameId: resolvedParentId,\n cycle,\n frameName: frameOptions.name,\n }\n );\n }\n }\n\n // Create frame data\n const frameId = uuidv4();\n const frame: Omit<Frame, 'created_at' | 'closed_at'> = {\n frame_id: frameId,\n run_id: this.currentRunId,\n project_id: this.projectId,\n parent_frame_id: resolvedParentId,\n depth,\n type: frameOptions.type,\n name: frameOptions.name,\n state: 'active',\n inputs: frameOptions.inputs || {},\n outputs: {},\n digest_json: {},\n };\n\n // Insert into database\n const createdFrame = this.frameDb.insertFrame(frame);\n\n // Add to stack\n this.frameStack.pushFrame(frameId);\n\n logger.info('Created frame', {\n frameId,\n name: frameOptions.name,\n type: frameOptions.type,\n parentFrameId: resolvedParentId,\n stackDepth: this.frameStack.getDepth(),\n });\n\n return frameId;\n }\n\n /**\n * Close a frame and generate digest\n */\n closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n trace.traceSync(\n 'function',\n 'FrameManager.closeFrame',\n { frameId, outputs },\n () => this._closeFrame(frameId, outputs)\n );\n }\n\n private _closeFrame(frameId?: string, outputs?: Record<string, any>): void {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame to close',\n ErrorCode.FRAME_INVALID_STATE,\n {\n operation: 'closeFrame',\n stackDepth: this.frameStack.getDepth(),\n }\n );\n }\n\n // Get frame details\n const frame = this.frameDb.getFrame(targetFrameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${targetFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n {\n frameId: targetFrameId,\n operation: 'closeFrame',\n runId: this.currentRunId,\n }\n );\n }\n\n if (frame.state === 'closed') {\n logger.warn('Attempted to close already closed frame', {\n frameId: targetFrameId,\n });\n return;\n }\n\n // Generate digest before closing\n const digest = this.digestGenerator.generateDigest(targetFrameId);\n const finalOutputs = { ...outputs, ...digest.structured };\n\n // Update frame to closed state\n this.frameDb.updateFrame(targetFrameId, {\n state: 'closed',\n outputs: finalOutputs,\n digest_text: digest.text,\n digest_json: digest.structured,\n closed_at: Math.floor(Date.now() / 1000),\n });\n\n // Remove from stack (this will also remove any child frames)\n this.frameStack.popFrame(targetFrameId);\n\n // Close all child frames recursively\n this.closeChildFrames(targetFrameId);\n\n // Trigger lifecycle hooks (fire and forget)\n const events = this.frameDb.getFrameEvents(targetFrameId);\n const anchors = this.frameDb.getFrameAnchors(targetFrameId);\n frameLifecycleHooks\n .triggerClose({ frame: { ...frame, state: 'closed' }, events, anchors })\n .catch(() => {\n // Silently ignore errors - hooks are non-critical\n });\n\n logger.info('Closed frame', {\n frameId: targetFrameId,\n name: frame.name,\n duration: Math.floor(Date.now() / 1000) - frame.created_at,\n digestLength: digest.text.length,\n stackDepth: this.frameStack.getDepth(),\n });\n }\n\n /**\n * Add an event to the current frame\n */\n addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addEvent',\n { eventType, frameId },\n () => this._addEvent(eventType, payload, frameId)\n );\n }\n\n private _addEvent(\n eventType: Event['event_type'],\n payload: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for event',\n ErrorCode.FRAME_INVALID_STATE,\n {\n eventType,\n operation: 'addEvent',\n }\n );\n }\n\n const eventId = uuidv4();\n const sequence = this.frameDb.getNextEventSequence(targetFrameId);\n\n const event: Omit<Event, 'ts'> = {\n event_id: eventId,\n frame_id: targetFrameId,\n run_id: this.currentRunId,\n seq: sequence,\n event_type: eventType,\n payload,\n };\n\n const createdEvent = this.frameDb.insertEvent(event);\n\n logger.debug('Added event', {\n eventId,\n frameId: targetFrameId,\n eventType,\n sequence,\n });\n\n return eventId;\n }\n\n /**\n * Add an anchor (important fact) to current frame\n */\n addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number = 5,\n metadata: Record<string, any> = {},\n frameId?: string\n ): string {\n return trace.traceSync(\n 'function',\n 'FrameManager.addAnchor',\n { type, frameId },\n () => this._addAnchor(type, text, priority, metadata, frameId)\n );\n }\n\n private _addAnchor(\n type: Anchor['type'],\n text: string,\n priority: number,\n metadata: Record<string, any>,\n frameId?: string\n ): string {\n const targetFrameId = frameId || this.frameStack.getCurrentFrameId();\n if (!targetFrameId) {\n throw new FrameError(\n 'No active frame for anchor',\n ErrorCode.FRAME_INVALID_STATE,\n {\n anchorType: type,\n operation: 'addAnchor',\n }\n );\n }\n\n const anchorId = uuidv4();\n const anchor: Omit<Anchor, 'created_at'> = {\n anchor_id: anchorId,\n frame_id: targetFrameId,\n type,\n text,\n priority,\n metadata,\n };\n\n const createdAnchor = this.frameDb.insertAnchor(anchor);\n\n logger.debug('Added anchor', {\n anchorId,\n frameId: targetFrameId,\n type,\n priority,\n });\n\n return anchorId;\n }\n\n /**\n * Get hot stack context\n */\n getHotStackContext(maxEvents: number = 20): FrameContext[] {\n return this.frameStack.getHotStackContext(maxEvents);\n }\n\n /**\n * Get active frame path (root to current)\n */\n getActiveFramePath(): Frame[] {\n return this.frameStack.getStackFrames();\n }\n\n /**\n * Get current frame ID\n */\n getCurrentFrameId(): string | undefined {\n return this.frameStack.getCurrentFrameId();\n }\n\n /**\n * Get stack depth\n */\n getStackDepth(): number {\n return this.frameStack.getDepth();\n }\n\n /**\n * Get frame by ID\n */\n getFrame(frameId: string): Frame | undefined {\n return this.frameDb.getFrame(frameId);\n }\n\n /**\n * Get frame events\n */\n getFrameEvents(frameId: string, limit?: number): Event[] {\n return this.frameDb.getFrameEvents(frameId, limit);\n }\n\n /**\n * Get frame anchors\n */\n getFrameAnchors(frameId: string): Anchor[] {\n return this.frameDb.getFrameAnchors(frameId);\n }\n\n /**\n * Generate digest for a frame\n */\n generateDigest(frameId: string): DigestResult {\n return this.digestGenerator.generateDigest(frameId);\n }\n\n /**\n * Validate stack consistency\n */\n validateStack(): { isValid: boolean; errors: string[] } {\n return this.frameStack.validateStack();\n }\n\n /**\n * Get database statistics\n */\n getStatistics(): Record<string, number> {\n return this.frameDb.getStatistics();\n }\n\n /**\n * Get the last recovery report from initialization\n */\n getRecoveryReport(): RecoveryReport | null {\n return this.lastRecoveryReport;\n }\n\n /**\n * Manually trigger recovery (e.g., after detecting issues)\n */\n async runRecovery(): Promise<RecoveryReport> {\n this.lastRecoveryReport = await this.frameRecovery.recoverOnStartup();\n return this.lastRecoveryReport;\n }\n\n /**\n * Validate project data integrity\n */\n validateProjectIntegrity(): { valid: boolean; issues: string[] } {\n return this.frameRecovery.validateProjectIntegrity(this.projectId);\n }\n\n /**\n * Close all child frames recursively with depth limit to prevent stack overflow\n */\n private closeChildFrames(parentFrameId: string, depth: number = 0): void {\n // Prevent stack overflow with depth limit\n if (depth > this.maxFrameDepth) {\n logger.warn('closeChildFrames depth limit exceeded', {\n parentFrameId,\n depth,\n maxDepth: this.maxFrameDepth,\n });\n return;\n }\n\n try {\n const activeFrames = this.frameDb.getFramesByProject(\n this.projectId,\n 'active'\n );\n const childFrames = activeFrames.filter(\n (f) => f.parent_frame_id === parentFrameId\n );\n\n for (const childFrame of childFrames) {\n if (this.frameStack.isFrameActive(childFrame.frame_id)) {\n // Close child's children first (depth-first)\n this.closeChildFrames(childFrame.frame_id, depth + 1);\n // Then close the child frame directly without recursing through closeFrame\n this.closeFrameDirectly(childFrame.frame_id);\n }\n }\n } catch (error: unknown) {\n logger.warn('Failed to close child frames', { parentFrameId, error });\n }\n }\n\n /**\n * Close a frame directly without triggering child frame closure\n * Used by closeChildFrames to avoid double recursion\n */\n private closeFrameDirectly(frameId: string): void {\n const frame = this.frameDb.getFrame(frameId);\n if (!frame || frame.state === 'closed') return;\n\n const digest = this.digestGenerator.generateDigest(frameId);\n\n this.frameDb.updateFrame(frameId, {\n state: 'closed',\n outputs: digest.structured,\n digest_text: digest.text,\n digest_json: digest.structured,\n closed_at: Math.floor(Date.now() / 1000),\n });\n\n this.frameStack.popFrame(frameId);\n\n logger.debug('Closed child frame directly', { frameId });\n }\n\n /**\n * Extract active artifacts from frame events\n */\n getActiveArtifacts(frameId: string): string[] {\n const events = this.frameDb.getFrameEvents(frameId);\n const artifacts: string[] = [];\n\n for (const event of events) {\n if (event.event_type === 'artifact' && event.payload.path) {\n artifacts.push(event.payload.path);\n }\n }\n\n return [...new Set(artifacts)];\n }\n\n /**\n * Extract constraints from frame inputs\n */\n extractConstraints(inputs: Record<string, unknown>): string[] {\n const constraints: string[] = [];\n\n if (inputs.constraints && Array.isArray(inputs.constraints)) {\n constraints.push(...(inputs.constraints as string[]));\n }\n\n if (inputs.requirements && Array.isArray(inputs.requirements)) {\n constraints.push(...(inputs.requirements as string[]));\n }\n\n if (inputs.limitations && Array.isArray(inputs.limitations)) {\n constraints.push(...(inputs.limitations as string[]));\n }\n\n return constraints;\n }\n\n /**\n * Detect if setting a parent frame would create a cycle in the frame hierarchy.\n * Returns the cycle path if detected, or null if no cycle.\n * @param childFrameId - The frame that would be the child\n * @param parentFrameId - The proposed parent frame\n * @returns Array of frame IDs forming the cycle, or null if no cycle\n */\n private detectCycle(\n childFrameId: string,\n parentFrameId: string\n ): string[] | null {\n const visited = new Set<string>();\n const path: string[] = [];\n\n // Start from the proposed parent and traverse up the hierarchy\n let currentId: string | undefined = parentFrameId;\n\n while (currentId) {\n // If we've seen this frame before, we have a cycle\n if (visited.has(currentId)) {\n // Build the cycle path\n const cycleStart = path.indexOf(currentId);\n return path.slice(cycleStart).concat(currentId);\n }\n\n // If the current frame is the child we're trying to add, it's a cycle\n if (currentId === childFrameId) {\n return path.concat([currentId, childFrameId]);\n }\n\n visited.add(currentId);\n path.push(currentId);\n\n // Move to the parent of current frame\n const frame = this.frameDb.getFrame(currentId);\n if (!frame) {\n // Frame not found, no cycle possible through this path\n break;\n }\n currentId = frame.parent_frame_id;\n\n // Safety check: if we've traversed too many levels, something is wrong\n if (path.length > this.maxFrameDepth) {\n throw new FrameError(\n `Frame hierarchy traversal exceeded maximum depth during cycle detection`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n depth: path.length,\n maxDepth: this.maxFrameDepth,\n path,\n }\n );\n }\n }\n\n return null; // No cycle detected\n }\n\n /**\n * Update parent frame of an existing frame (with cycle detection)\n * @param frameId - The frame to update\n * @param newParentFrameId - The new parent frame ID (null to make it a root frame)\n */\n public updateParentFrame(\n frameId: string,\n newParentFrameId: string | null\n ): void {\n // Check if frame exists\n const frame = this.frameDb.getFrame(frameId);\n if (!frame) {\n throw new FrameError(\n `Frame not found: ${frameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId }\n );\n }\n\n // If setting a parent, validate and check for cycles\n if (newParentFrameId) {\n // Verify the new parent exists\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (!newParentFrame) {\n throw new FrameError(\n `Parent frame not found: ${newParentFrameId}`,\n ErrorCode.FRAME_NOT_FOUND,\n { frameId, newParentFrameId }\n );\n }\n\n const cycle = this.detectCycle(frameId, newParentFrameId);\n if (cycle) {\n throw new FrameError(\n `Cannot set parent: would create circular reference`,\n ErrorCode.FRAME_CYCLE_DETECTED,\n {\n frameId,\n newParentFrameId,\n cycle,\n currentParentId: frame.parent_frame_id,\n }\n );\n }\n\n // Check depth after parent change\n const newDepth = newParentFrame.depth + 1;\n if (newDepth > this.maxFrameDepth) {\n throw new FrameError(\n `Cannot set parent: would exceed maximum frame depth`,\n ErrorCode.FRAME_STACK_OVERFLOW,\n {\n frameId,\n newParentFrameId,\n newDepth,\n maxDepth: this.maxFrameDepth,\n }\n );\n }\n }\n\n // Calculate new depth based on parent\n let newDepth = 0;\n if (newParentFrameId) {\n const newParentFrame = this.frameDb.getFrame(newParentFrameId);\n if (newParentFrame) {\n newDepth = newParentFrame.depth + 1;\n }\n }\n\n // Update the frame's parent and depth\n this.frameDb.updateFrame(frameId, {\n parent_frame_id: newParentFrameId,\n depth: newDepth,\n });\n\n logger.info('Updated parent frame', {\n frameId,\n oldParentId: frame.parent_frame_id,\n newParentId: newParentFrameId,\n });\n }\n\n /**\n * Validate the entire frame hierarchy for cycles and depth violations\n * @returns Validation result with any detected issues\n */\n public validateFrameHierarchy(): {\n isValid: boolean;\n errors: string[];\n warnings: string[];\n } {\n const errors: string[] = [];\n const warnings: string[] = [];\n const allFrames = this.frameDb.getFramesByProject(this.projectId);\n\n // Check each frame for depth violations\n for (const frame of allFrames) {\n if (frame.depth > this.maxFrameDepth) {\n errors.push(\n `Frame ${frame.frame_id} exceeds max depth: ${frame.depth} > ${this.maxFrameDepth}`\n );\n }\n\n // Warn about deep frames approaching the limit\n if (frame.depth > this.maxFrameDepth * 0.8) {\n warnings.push(\n `Frame ${frame.frame_id} is deep in hierarchy: ${frame.depth}/${this.maxFrameDepth}`\n );\n }\n }\n\n // Check for cycles by traversing from each root\n const rootFrames = allFrames.filter((f) => !f.parent_frame_id);\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n const checkForCycle = (frameId: string): boolean => {\n if (visiting.has(frameId)) {\n errors.push(`Cycle detected involving frame ${frameId}`);\n return true;\n }\n\n if (visited.has(frameId)) {\n return false;\n }\n\n visiting.add(frameId);\n\n // Check all children\n const children = allFrames.filter((f) => f.parent_frame_id === frameId);\n for (const child of children) {\n if (checkForCycle(child.frame_id)) {\n return true;\n }\n }\n\n visiting.delete(frameId);\n visited.add(frameId);\n return false;\n };\n\n // Check from each root\n for (const root of rootFrames) {\n checkForCycle(root.frame_id);\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n };\n }\n\n /**\n * Set query mode for frame retrieval\n */\n setQueryMode(mode: FrameQueryMode): void {\n this.queryMode = mode;\n // Reinitialize stack with new query mode\n this.frameStack.setQueryMode(mode);\n }\n\n /**\n * Get recent frames for context sharing\n */\n async getRecentFrames(limit: number = 100): Promise<Frame[]> {\n try {\n const frames = this.frameDb.getFramesByProject(this.projectId);\n\n // Sort by created_at descending and limit\n return frames\n .sort((a, b) => (b.created_at || 0) - (a.created_at || 0))\n .slice(0, limit)\n .map((frame) => ({\n ...frame,\n // Add compatibility fields\n frameId: frame.frame_id,\n runId: frame.run_id,\n projectId: frame.project_id,\n parentFrameId: frame.parent_frame_id,\n title: frame.name,\n timestamp: frame.created_at,\n metadata: {\n tags: this.extractTagsFromFrame(frame),\n importance: this.calculateFrameImportance(frame),\n },\n data: {\n inputs: frame.inputs,\n outputs: frame.outputs,\n digest: frame.digest_json,\n },\n }));\n } catch (error: unknown) {\n logger.error('Failed to get recent frames', error as Error);\n return [];\n }\n }\n\n /**\n * Add context metadata to the current frame\n */\n async addContext(key: string, value: any): Promise<void> {\n const currentFrameId = this.frameStack.getCurrentFrameId();\n if (!currentFrameId) return;\n\n try {\n const frame = this.frameDb.getFrame(currentFrameId);\n if (!frame) return;\n\n const metadata = frame.outputs || {};\n metadata[key] = value;\n\n this.frameDb.updateFrame(currentFrameId, {\n outputs: metadata,\n });\n } catch (error: unknown) {\n logger.warn('Failed to add context to frame', { error, key });\n }\n }\n\n /**\n * Delete a frame completely from the database (used in handoffs)\n */\n deleteFrame(frameId: string): void {\n try {\n // Remove from active stack if present\n this.frameStack.removeFrame(frameId);\n\n // Delete the frame and related data (cascades via FrameDatabase)\n this.frameDb.deleteFrame(frameId);\n\n logger.debug('Deleted frame completely', { frameId });\n } catch (error: unknown) {\n logger.error('Failed to delete frame', { frameId, error });\n throw error;\n }\n }\n\n /**\n * Extract tags from frame for categorization\n */\n private extractTagsFromFrame(frame: Frame): string[] {\n const tags: string[] = [];\n\n if (frame.type) tags.push(frame.type);\n\n if (frame.name) {\n const nameLower = frame.name.toLowerCase();\n if (nameLower.includes('error')) tags.push('error');\n if (nameLower.includes('fix')) tags.push('resolution');\n if (nameLower.includes('decision')) tags.push('decision');\n if (nameLower.includes('milestone')) tags.push('milestone');\n }\n\n try {\n if (frame.digest_json && typeof frame.digest_json === 'object') {\n const digest = frame.digest_json as Record<string, unknown>;\n if (Array.isArray(digest.tags)) {\n tags.push(...(digest.tags as string[]));\n }\n }\n } catch {\n // Ignore parse errors\n }\n\n return [...new Set(tags)];\n }\n\n /**\n * Calculate frame importance for prioritization\n */\n private calculateFrameImportance(frame: Frame): 'high' | 'medium' | 'low' {\n if (frame.type === 'milestone' || frame.name?.includes('decision')) {\n return 'high';\n }\n\n if (frame.type === 'error' || frame.type === 'resolution') {\n return 'medium';\n }\n\n if (frame.closed_at && frame.created_at) {\n const duration = frame.closed_at - frame.created_at;\n if (duration > 300) return 'medium';\n }\n\n return 'low';\n }\n}\n\n// Re-export types for compatibility (type-only, no runtime value)\nexport type {\n Frame,\n FrameContext,\n Anchor,\n Event,\n FrameType,\n FrameState,\n FrameCreationOptions,\n FrameManagerConfig,\n DigestResult,\n} from './frame-types.js';\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["newDepth"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackmemoryai/stackmemory",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.38",
|
|
4
4
|
"description": "Lossless memory runtime for AI coding tools - organizes context as a call stack instead of linear chat logs, with team collaboration and infinite retention",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0",
|