@codemcp/workflows-core 3.1.20 → 3.1.21-fix-build-after-monorepo.1

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/database.js CHANGED
@@ -1,233 +1,169 @@
1
1
  /**
2
- * Database module for persistent state storage
2
+ * Database Manager
3
3
  *
4
- * Manages SQLite database for conversation state persistence.
5
- * Stores minimal state information to survive server restarts.
6
- * Also stores interaction logs for auditing and debugging.
4
+ * Handles SQLite database operations for conversation state persistence.
5
+ * Uses @sqlite.org/sqlite-wasm for reliable cross-platform WebAssembly bindings.
7
6
  */
8
- import sqlite3 from 'sqlite3';
7
+ import sqlite3InitModule from '@sqlite.org/sqlite-wasm';
9
8
  import { mkdir } from 'node:fs/promises';
10
9
  import { dirname } from 'node:path';
11
- import { join } from 'node:path';
12
10
  import { createLogger } from './logger.js';
13
11
  const logger = createLogger('Database');
14
- // Database row validation utilities
15
- function validateString(value, fieldName) {
16
- if (typeof value === 'string') {
17
- return value;
18
- }
19
- throw new Error(`Database field '${fieldName}' expected string but got ${typeof value}: ${value}`);
20
- }
21
- function parseJsonSafely(value, fieldName) {
22
- if (!value) {
23
- return undefined;
24
- }
25
- const stringValue = validateString(value, fieldName);
26
- try {
27
- return JSON.parse(stringValue);
28
- }
29
- catch (error) {
30
- throw new Error(`Failed to parse JSON in field '${fieldName}': ${error instanceof Error ? error.message : String(error)}`);
31
- }
32
- }
33
- function mapRowToInteractionLog(row) {
34
- return {
35
- id: typeof row.id === 'number' ? row.id : undefined,
36
- conversationId: validateString(row.conversationId, 'conversationId'),
37
- toolName: validateString(row.toolName, 'toolName'),
38
- inputParams: validateString(row.inputParams, 'inputParams'),
39
- responseData: validateString(row.responseData, 'responseData'),
40
- currentPhase: validateString(row.currentPhase, 'currentPhase'),
41
- timestamp: validateString(row.timestamp, 'timestamp'),
42
- isReset: typeof row.isReset === 'number' ? Boolean(row.isReset) : undefined,
43
- resetAt: row.resetAt ? validateString(row.resetAt, 'resetAt') : undefined,
44
- };
45
- }
12
+ /**
13
+ * Database connection and operations manager
14
+ */
46
15
  export class Database {
47
16
  db = null;
17
+ sqlite3 = null;
48
18
  dbPath;
49
- constructor(projectPath) {
50
- // Store database in .vibe subfolder of the project
51
- const vibeDir = join(projectPath, '.vibe');
52
- this.dbPath = join(vibeDir, 'conversation-state.sqlite');
53
- logger.debug('Database path configured', {
54
- projectPath,
55
- dbPath: this.dbPath,
56
- });
19
+ constructor(dbPath) {
20
+ this.dbPath = dbPath;
57
21
  }
58
22
  /**
59
23
  * Initialize database connection and create tables
60
24
  */
61
25
  async initialize() {
62
- logger.debug('Initializing database', { dbPath: this.dbPath });
63
26
  try {
64
- // Ensure directory exists
65
- await mkdir(dirname(this.dbPath), { recursive: true });
66
- logger.debug('Database directory ensured', {
67
- directory: dirname(this.dbPath),
27
+ // Initialize SQLite WASM
28
+ this.sqlite3 = await sqlite3InitModule();
29
+ // Always use in-memory database (sqlite-wasm Node.js limitation)
30
+ this.db = new this.sqlite3.oo1.DB();
31
+ logger.debug('Database connection established (in-memory)', {
32
+ originalPath: this.dbPath,
68
33
  });
69
- // Create database connection
70
- this.db = new sqlite3.Database(this.dbPath);
71
- logger.debug('Database connection established');
72
- // Create conversation_states table
73
- await this.runQuery(`
74
- CREATE TABLE IF NOT EXISTS conversation_states (
75
- conversation_id TEXT PRIMARY KEY,
76
- project_path TEXT NOT NULL,
77
- git_branch TEXT NOT NULL,
78
- current_phase TEXT NOT NULL,
79
- plan_file_path TEXT NOT NULL,
80
- workflow_name TEXT DEFAULT 'waterfall',
81
- git_commit_config TEXT, -- JSON string for GitCommitConfig
82
- created_at TEXT NOT NULL,
83
- updated_at TEXT NOT NULL
84
- )
85
- `);
86
- // Create index for efficient lookups
87
- await this.runQuery(`
88
- CREATE INDEX IF NOT EXISTS idx_project_branch
89
- ON conversation_states(project_path, git_branch)
90
- `);
91
- // Create interaction_logs table
92
- await this.runQuery(`
93
- CREATE TABLE IF NOT EXISTS interaction_logs (
94
- id INTEGER PRIMARY KEY AUTOINCREMENT,
95
- conversation_id TEXT NOT NULL,
96
- tool_name TEXT NOT NULL,
97
- input_params TEXT NOT NULL,
98
- response_data TEXT NOT NULL,
99
- current_phase TEXT NOT NULL,
100
- timestamp TEXT NOT NULL,
101
- is_reset BOOLEAN DEFAULT FALSE,
102
- reset_at TEXT,
103
- FOREIGN KEY (conversation_id) REFERENCES conversation_states(conversation_id)
104
- )
105
- `);
106
- // Create index for efficient lookups of interaction logs
107
- await this.runQuery(`
108
- CREATE INDEX IF NOT EXISTS idx_interaction_conversation_id
109
- ON interaction_logs(conversation_id)
110
- `);
111
- // Run migrations to add any missing columns
112
- await this.runMigrations();
34
+ // Create tables
35
+ await this.createTables();
36
+ // Load existing data from file if it exists
37
+ if (this.dbPath !== ':memory:' && this.dbPath) {
38
+ await this.loadFromFile();
39
+ }
113
40
  logger.info('Database initialized successfully', { dbPath: this.dbPath });
114
41
  }
115
42
  catch (error) {
116
- logger.error('Failed to initialize database', error, {
117
- dbPath: this.dbPath,
118
- });
43
+ logger.error('Failed to initialize database', error);
119
44
  throw error;
120
45
  }
121
46
  }
122
47
  /**
123
- * Helper method to run queries with promises
124
- */
125
- runQuery(sql, params = []) {
126
- return new Promise((resolve, reject) => {
127
- if (!this.db) {
128
- reject(new Error('Database not initialized'));
129
- return;
130
- }
131
- this.db.run(sql, params, function (err) {
132
- if (err) {
133
- reject(err);
134
- }
135
- else {
136
- resolve();
137
- }
138
- });
139
- });
140
- }
141
- /**
142
- * Helper method to get single row with promises
48
+ * Load database content from file
143
49
  */
144
- getRow(sql, params = []) {
145
- return new Promise((resolve, reject) => {
146
- if (!this.db) {
147
- reject(new Error('Database not initialized'));
148
- return;
149
- }
150
- this.db.get(sql, params, (err, row) => {
151
- if (err) {
152
- reject(err);
153
- }
154
- else {
155
- resolve(row);
50
+ async loadFromFile() {
51
+ if (!this.db || !this.dbPath || this.dbPath === ':memory:') {
52
+ return;
53
+ }
54
+ try {
55
+ const { readFile, access } = await import('node:fs/promises');
56
+ await access(this.dbPath);
57
+ const data = await readFile(this.dbPath);
58
+ if (data.length > 0) {
59
+ // Close current in-memory DB and create new one from file data
60
+ this.db.close();
61
+ // Create new DB and deserialize data into it
62
+ //eslint-disable-next-line @typescript-eslint/no-non-null-assertion
63
+ this.db = new this.sqlite3.oo1.DB();
64
+ if (!this.db.pointer) {
65
+ throw new Error('Failed to create database');
156
66
  }
157
- });
158
- });
159
- }
160
- /**
161
- * Helper method to get multiple rows with promises
162
- */
163
- getAllRows(sql, params = []) {
164
- return new Promise((resolve, reject) => {
165
- if (!this.db) {
166
- reject(new Error('Database not initialized'));
167
- return;
67
+ // Convert Buffer to Uint8Array
68
+ const uint8Data = new Uint8Array(data);
69
+ //eslint-disable-next-line @typescript-eslint/no-non-null-assertion
70
+ const wasmPtr = this.sqlite3.wasm.allocFromTypedArray(uint8Data);
71
+ //eslint-disable-next-line @typescript-eslint/no-non-null-assertion
72
+ this.sqlite3.capi.sqlite3_deserialize(this.db.pointer, 'main', wasmPtr, data.length, data.length, 0x01 // SQLITE_DESERIALIZE_FREEONCLOSE
73
+ );
74
+ logger.debug('Loaded database from file', {
75
+ dbPath: this.dbPath,
76
+ size: data.length,
77
+ });
168
78
  }
169
- this.db.all(sql, params, (err, rows) => {
170
- if (err) {
171
- reject(err);
172
- }
173
- else {
174
- resolve(rows);
175
- }
79
+ }
80
+ catch {
81
+ // File doesn't exist - that's OK for new databases
82
+ logger.debug('No existing database file to load', {
83
+ dbPath: this.dbPath,
176
84
  });
177
- });
85
+ }
178
86
  }
179
87
  /**
180
- * Get conversation state by ID
88
+ * Save database content to file
181
89
  */
182
- async getConversationState(conversationId) {
183
- logger.debug('Retrieving conversation state', { conversationId });
90
+ async saveToFile() {
91
+ if (!this.db || !this.dbPath || this.dbPath === ':memory:') {
92
+ return;
93
+ }
184
94
  try {
185
- const row = await this.getRow('SELECT * FROM conversation_states WHERE conversation_id = ?', [conversationId]);
186
- if (!row) {
187
- logger.debug('Conversation state not found', { conversationId });
188
- return null;
95
+ const { writeFile } = await import('node:fs/promises');
96
+ const dbDir = dirname(this.dbPath);
97
+ await mkdir(dbDir, { recursive: true });
98
+ // Export database to Uint8Array and save to file
99
+ if (!this.db.pointer) {
100
+ throw new Error('Database pointer is invalid');
189
101
  }
190
- const state = {
191
- conversationId: validateString(row.conversation_id, 'conversation_id'),
192
- projectPath: validateString(row.project_path, 'project_path'),
193
- gitBranch: validateString(row.git_branch, 'git_branch'),
194
- currentPhase: validateString(row.current_phase, 'current_phase'),
195
- planFilePath: validateString(row.plan_file_path, 'plan_file_path'),
196
- workflowName: validateString(row.workflow_name, 'workflow_name'),
197
- gitCommitConfig: parseJsonSafely(row.git_commit_config, 'git_commit_config'),
198
- requireReviewsBeforePhaseTransition: Boolean(row.require_reviews_before_phase_transition),
199
- createdAt: validateString(row.created_at, 'created_at'),
200
- updatedAt: validateString(row.updated_at, 'updated_at'),
201
- };
202
- logger.debug('Conversation state retrieved', {
203
- conversationId,
204
- currentPhase: state.currentPhase,
205
- projectPath: state.projectPath,
102
+ //eslint-disable-next-line @typescript-eslint/no-non-null-assertion
103
+ const data = this.sqlite3.capi.sqlite3_js_db_export(this.db.pointer);
104
+ await writeFile(this.dbPath, data);
105
+ logger.debug('Saved database to file', {
106
+ dbPath: this.dbPath,
107
+ size: data.length,
206
108
  });
207
- return state;
208
109
  }
209
110
  catch (error) {
210
- logger.error('Failed to retrieve conversation state', error, {
211
- conversationId,
111
+ logger.warn('Failed to save database to file', {
112
+ error: error,
113
+ dbPath: this.dbPath,
212
114
  });
213
- throw error;
214
115
  }
215
116
  }
216
117
  /**
217
- * Save or update conversation state
118
+ * Create database tables if they don't exist
119
+ */
120
+ async createTables() {
121
+ if (!this.db) {
122
+ throw new Error('Database not initialized');
123
+ }
124
+ const createConversationStateTable = `
125
+ CREATE TABLE IF NOT EXISTS conversation_state (
126
+ conversationId TEXT PRIMARY KEY,
127
+ projectPath TEXT NOT NULL,
128
+ gitBranch TEXT NOT NULL,
129
+ currentPhase TEXT NOT NULL,
130
+ planFilePath TEXT NOT NULL,
131
+ workflowName TEXT NOT NULL,
132
+ gitCommitConfig TEXT,
133
+ requireReviewsBeforePhaseTransition INTEGER NOT NULL DEFAULT 0,
134
+ createdAt TEXT NOT NULL,
135
+ updatedAt TEXT NOT NULL
136
+ )
137
+ `;
138
+ const createInteractionLogTable = `
139
+ CREATE TABLE IF NOT EXISTS interaction_log (
140
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
141
+ conversationId TEXT NOT NULL,
142
+ toolName TEXT NOT NULL,
143
+ inputParams TEXT NOT NULL,
144
+ responseData TEXT NOT NULL,
145
+ currentPhase TEXT NOT NULL,
146
+ timestamp TEXT NOT NULL,
147
+ FOREIGN KEY (conversationId) REFERENCES conversation_state(conversationId)
148
+ )
149
+ `;
150
+ this.db.exec(createConversationStateTable);
151
+ this.db.exec(createInteractionLogTable);
152
+ logger.debug('Database tables created');
153
+ }
154
+ /**
155
+ * Save conversation state to database
218
156
  */
219
157
  async saveConversationState(state) {
220
- logger.debug('Saving conversation state', {
221
- conversationId: state.conversationId,
222
- currentPhase: state.currentPhase,
223
- projectPath: state.projectPath,
224
- workflowName: state.workflowName,
225
- });
226
- try {
227
- await this.runQuery(`INSERT OR REPLACE INTO conversation_states (
228
- conversation_id, project_path, git_branch, current_phase,
229
- plan_file_path, workflow_name, git_commit_config, require_reviews_before_phase_transition, created_at, updated_at
230
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
158
+ if (!this.db) {
159
+ throw new Error('Database not initialized');
160
+ }
161
+ this.db.exec({
162
+ sql: `INSERT OR REPLACE INTO conversation_state
163
+ (conversationId, projectPath, gitBranch, currentPhase, planFilePath, workflowName,
164
+ gitCommitConfig, requireReviewsBeforePhaseTransition, createdAt, updatedAt)
165
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
166
+ bind: [
231
167
  state.conversationId,
232
168
  state.projectPath,
233
169
  state.gitBranch,
@@ -235,266 +171,188 @@ export class Database {
235
171
  state.planFilePath,
236
172
  state.workflowName,
237
173
  state.gitCommitConfig ? JSON.stringify(state.gitCommitConfig) : null,
238
- state.requireReviewsBeforePhaseTransition,
174
+ state.requireReviewsBeforePhaseTransition ? 1 : 0,
239
175
  state.createdAt,
240
176
  state.updatedAt,
241
- ]);
242
- logger.info('Conversation state saved successfully', {
243
- conversationId: state.conversationId,
244
- currentPhase: state.currentPhase,
245
- });
246
- }
247
- catch (error) {
248
- logger.error('Failed to save conversation state', error, {
249
- conversationId: state.conversationId,
250
- });
251
- throw error;
252
- }
177
+ ],
178
+ });
179
+ // Persist to file
180
+ await this.saveToFile();
181
+ logger.debug('Conversation state saved', {
182
+ conversationId: state.conversationId,
183
+ currentPhase: state.currentPhase,
184
+ });
253
185
  }
254
186
  /**
255
- * Find conversation by project path and git branch
187
+ * Get conversation state by ID
256
188
  */
257
- async findConversationByProject(projectPath, gitBranch) {
258
- const row = await this.getRow('SELECT * FROM conversation_states WHERE project_path = ? AND git_branch = ?', [projectPath, gitBranch]);
259
- if (!row) {
189
+ async getConversationState(conversationId) {
190
+ if (!this.db) {
191
+ throw new Error('Database not initialized');
192
+ }
193
+ const result = this.db.exec({
194
+ sql: 'SELECT * FROM conversation_state WHERE conversationId = ?',
195
+ bind: [conversationId],
196
+ returnValue: 'resultRows',
197
+ });
198
+ if (!result || result.length === 0) {
260
199
  return null;
261
200
  }
201
+ const row = result[0];
262
202
  return {
263
- conversationId: validateString(row.conversation_id, 'conversation_id'),
264
- projectPath: validateString(row.project_path, 'project_path'),
265
- gitBranch: validateString(row.git_branch, 'git_branch'),
266
- currentPhase: validateString(row.current_phase, 'current_phase'),
267
- planFilePath: validateString(row.plan_file_path, 'plan_file_path'),
268
- workflowName: validateString(row.workflow_name, 'workflow_name'),
269
- gitCommitConfig: parseJsonSafely(row.git_commit_config, 'git_commit_config'),
270
- requireReviewsBeforePhaseTransition: Boolean(row.require_reviews_before_phase_transition),
271
- createdAt: validateString(row.created_at, 'created_at'),
272
- updatedAt: validateString(row.updated_at, 'updated_at'),
203
+ conversationId: row[0],
204
+ projectPath: row[1],
205
+ gitBranch: row[2],
206
+ currentPhase: row[3],
207
+ planFilePath: row[4],
208
+ workflowName: row[5],
209
+ gitCommitConfig: row[6] ? JSON.parse(row[6]) : null,
210
+ requireReviewsBeforePhaseTransition: Boolean(row[7]),
211
+ createdAt: row[8],
212
+ updatedAt: row[9],
273
213
  };
274
214
  }
215
+ /**
216
+ * Get all conversation states
217
+ */
218
+ async getAllConversationStates() {
219
+ if (!this.db) {
220
+ throw new Error('Database not initialized');
221
+ }
222
+ const result = this.db.exec({
223
+ sql: 'SELECT * FROM conversation_state ORDER BY updatedAt DESC',
224
+ returnValue: 'resultRows',
225
+ });
226
+ if (!result) {
227
+ return [];
228
+ }
229
+ return result.map(row => ({
230
+ conversationId: row[0],
231
+ projectPath: row[1],
232
+ gitBranch: row[2],
233
+ currentPhase: row[3],
234
+ planFilePath: row[4],
235
+ workflowName: row[5],
236
+ gitCommitConfig: row[6] ? JSON.parse(row[6]) : null,
237
+ requireReviewsBeforePhaseTransition: Boolean(row[7]),
238
+ createdAt: row[8],
239
+ updatedAt: row[9],
240
+ }));
241
+ }
275
242
  /**
276
243
  * Delete conversation state
277
244
  */
278
245
  async deleteConversationState(conversationId) {
279
- await this.runQuery('DELETE FROM conversation_states WHERE conversation_id = ?', [conversationId]);
246
+ if (!this.db) {
247
+ throw new Error('Database not initialized');
248
+ }
249
+ this.db.exec({
250
+ sql: 'DELETE FROM conversation_state WHERE conversationId = ?',
251
+ bind: [conversationId],
252
+ });
253
+ // Persist to file
254
+ await this.saveToFile();
255
+ logger.debug('Conversation state deleted', { conversationId });
256
+ return true;
280
257
  }
281
258
  /**
282
- * Log an interaction to the database
259
+ * Log interaction
283
260
  */
284
261
  async logInteraction(log) {
285
- logger.debug('Logging interaction to database', {
286
- conversationId: log.conversationId,
287
- toolName: log.toolName,
288
- });
289
- try {
290
- await this.runQuery(`INSERT INTO interaction_logs (
291
- conversation_id, tool_name, input_params, response_data,
292
- current_phase, timestamp
293
- ) VALUES (?, ?, ?, ?, ?, ?)`, [
262
+ if (!this.db) {
263
+ throw new Error('Database not initialized');
264
+ }
265
+ this.db.exec({
266
+ sql: `INSERT INTO interaction_log
267
+ (conversationId, toolName, inputParams, responseData, currentPhase, timestamp)
268
+ VALUES (?, ?, ?, ?, ?, ?)`,
269
+ bind: [
294
270
  log.conversationId,
295
271
  log.toolName,
296
- log.inputParams,
297
- log.responseData,
272
+ JSON.stringify(log.inputParams),
273
+ JSON.stringify(log.responseData),
298
274
  log.currentPhase,
299
275
  log.timestamp,
300
- ]);
301
- logger.debug('Interaction logged successfully', {
302
- conversationId: log.conversationId,
303
- toolName: log.toolName,
304
- timestamp: log.timestamp,
305
- });
306
- }
307
- catch (error) {
308
- logger.error('Failed to log interaction', error, {
309
- conversationId: log.conversationId,
310
- });
311
- throw error;
312
- }
276
+ ],
277
+ });
278
+ // Persist to file
279
+ await this.saveToFile();
280
+ logger.debug('Interaction logged', {
281
+ conversationId: log.conversationId,
282
+ toolName: log.toolName,
283
+ });
313
284
  }
314
285
  /**
315
- * Get all interactions for a specific conversation
286
+ * Get interaction logs for a conversation
316
287
  */
317
- async getInteractionsByConversationId(conversationId) {
318
- logger.debug('Getting interactions by conversation ID', { conversationId });
319
- try {
320
- const rows = await this.getAllRows('SELECT * FROM interaction_logs WHERE conversation_id = ? ORDER BY timestamp ASC', [conversationId]);
321
- const logs = rows.map(row => ({
322
- id: typeof row.id === 'number' ? row.id : undefined,
323
- conversationId: validateString(row.conversation_id, 'conversation_id'),
324
- toolName: validateString(row.tool_name, 'tool_name'),
325
- inputParams: validateString(row.input_params, 'input_params'),
326
- responseData: validateString(row.response_data, 'response_data'),
327
- currentPhase: validateString(row.current_phase, 'current_phase'),
328
- timestamp: validateString(row.timestamp, 'timestamp'),
329
- }));
330
- logger.debug('Retrieved interaction logs', {
331
- conversationId,
332
- count: logs.length,
333
- });
334
- return logs;
288
+ async getInteractionLogs(conversationId) {
289
+ if (!this.db) {
290
+ throw new Error('Database not initialized');
335
291
  }
336
- catch (error) {
337
- logger.error('Failed to get interaction logs', error, {
338
- conversationId,
339
- });
340
- throw error;
292
+ const result = this.db.exec({
293
+ sql: 'SELECT * FROM interaction_log WHERE conversationId = ? ORDER BY timestamp ASC',
294
+ bind: [conversationId],
295
+ returnValue: 'resultRows',
296
+ });
297
+ if (!result) {
298
+ return [];
341
299
  }
300
+ return result.map(row => ({
301
+ id: row[0],
302
+ conversationId: row[1],
303
+ toolName: row[2],
304
+ inputParams: JSON.parse(row[3]),
305
+ responseData: JSON.parse(row[4]),
306
+ currentPhase: row[5],
307
+ timestamp: row[6],
308
+ }));
342
309
  }
343
310
  /**
344
- * Run database migrations to add new columns
311
+ * Get interaction logs for a conversation (alias for compatibility)
345
312
  */
346
- async runMigrations() {
347
- logger.debug('Running database migrations');
348
- try {
349
- // Check if interaction_logs table exists first
350
- const tables = await this.getAllRows("SELECT name FROM sqlite_master WHERE type='table' AND name='interaction_logs'");
351
- if (tables.length > 0) {
352
- // Table exists, check for missing columns
353
- const tableInfo = await this.getAllRows('PRAGMA table_info(interaction_logs)');
354
- const hasIsReset = tableInfo.some((col) => col.name === 'is_reset');
355
- const hasResetAt = tableInfo.some((col) => col.name === 'reset_at');
356
- if (!hasIsReset) {
357
- logger.info('Adding is_reset column to interaction_logs table');
358
- await this.runQuery('ALTER TABLE interaction_logs ADD COLUMN is_reset BOOLEAN DEFAULT FALSE');
359
- }
360
- if (!hasResetAt) {
361
- logger.info('Adding reset_at column to interaction_logs table');
362
- await this.runQuery('ALTER TABLE interaction_logs ADD COLUMN reset_at TEXT');
363
- }
364
- }
365
- // Check if conversation_states table exists and has workflow_name column
366
- const conversationTables = await this.getAllRows("SELECT name FROM sqlite_master WHERE type='table' AND name='conversation_states'");
367
- if (conversationTables.length > 0) {
368
- const conversationTableInfo = (await this.getAllRows('PRAGMA table_info(conversation_states)'));
369
- const hasWorkflowName = conversationTableInfo.some((col) => col.name === 'workflow_name');
370
- const hasGitCommitConfig = conversationTableInfo.some((col) => col.name === 'git_commit_config');
371
- const hasRequireReviews = conversationTableInfo.some((col) => col.name === 'require_reviews_before_phase_transition');
372
- if (!hasWorkflowName) {
373
- logger.info('Adding workflow_name column to conversation_states table');
374
- await this.runQuery("ALTER TABLE conversation_states ADD COLUMN workflow_name TEXT DEFAULT 'waterfall'");
375
- }
376
- if (!hasGitCommitConfig) {
377
- logger.info('Adding git_commit_config column to conversation_states table');
378
- await this.runQuery('ALTER TABLE conversation_states ADD COLUMN git_commit_config TEXT');
379
- }
380
- if (!hasRequireReviews) {
381
- logger.info('Adding require_reviews_before_phase_transition column to conversation_states table');
382
- await this.runQuery('ALTER TABLE conversation_states ADD COLUMN require_reviews_before_phase_transition BOOLEAN DEFAULT FALSE');
383
- }
384
- }
385
- logger.debug('Database migrations completed successfully');
386
- }
387
- catch (error) {
388
- logger.error('Failed to run database migrations', error);
389
- throw error;
390
- }
313
+ async getInteractionsByConversationId(conversationId) {
314
+ return this.getInteractionLogs(conversationId);
391
315
  }
392
316
  /**
393
- * Soft delete interaction logs for a conversation
317
+ * Soft delete interaction logs (for compatibility - actually deletes them)
394
318
  */
395
- async softDeleteInteractionLogs(conversationId, reason) {
396
- logger.debug('Soft deleting interaction logs', { conversationId, reason });
397
- try {
398
- const resetAt = new Date().toISOString();
399
- await this.runQuery('UPDATE interaction_logs SET is_reset = TRUE, reset_at = ? WHERE conversation_id = ? AND is_reset = FALSE', [resetAt, conversationId]);
400
- logger.info('Interaction logs soft deleted successfully', {
401
- conversationId,
402
- reason,
403
- resetAt,
404
- });
405
- }
406
- catch (error) {
407
- logger.error('Failed to soft delete interaction logs', error, {
408
- conversationId,
409
- });
410
- throw error;
319
+ async softDeleteInteractionLogs(conversationId) {
320
+ if (!this.db) {
321
+ throw new Error('Database not initialized');
411
322
  }
323
+ this.db.exec({
324
+ sql: 'DELETE FROM interaction_log WHERE conversationId = ?',
325
+ bind: [conversationId],
326
+ });
327
+ // Persist to file
328
+ await this.saveToFile();
329
+ logger.debug('Interaction logs deleted', { conversationId });
412
330
  }
413
331
  /**
414
- * Get active (non-reset) interaction logs for a conversation
332
+ * Reset conversation state (for testing)
415
333
  */
416
- async getActiveInteractionLogs(conversationId) {
417
- logger.debug('Getting active interaction logs', { conversationId });
418
- try {
419
- const rows = await this.getAllRows('SELECT * FROM interaction_logs WHERE conversation_id = ? AND (is_reset = FALSE OR is_reset IS NULL) ORDER BY timestamp ASC', [conversationId]);
420
- const logs = rows.map(row => mapRowToInteractionLog({
421
- id: row.id,
422
- conversationId: row.conversation_id,
423
- toolName: row.tool_name,
424
- inputParams: row.input_params,
425
- responseData: row.response_data,
426
- currentPhase: row.current_phase,
427
- timestamp: row.timestamp,
428
- }));
429
- logger.debug('Retrieved active interaction logs', {
430
- conversationId,
431
- count: logs.length,
432
- });
433
- return logs;
434
- }
435
- catch (error) {
436
- logger.error('Failed to get active interaction logs', error, {
437
- conversationId,
438
- });
439
- throw error;
334
+ async resetConversationState(conversationId) {
335
+ if (!this.db) {
336
+ throw new Error('Database not initialized');
440
337
  }
441
- }
442
- /**
443
- * Get all interaction logs including reset ones for a conversation
444
- */
445
- async getAllInteractionLogsIncludingReset(conversationId) {
446
- logger.debug('Getting all interaction logs including reset', {
447
- conversationId,
338
+ const resetAt = new Date().toISOString();
339
+ this.db.exec({
340
+ sql: 'UPDATE conversation_state SET updatedAt = ? WHERE conversationId = ?',
341
+ bind: [resetAt, conversationId],
448
342
  });
449
- try {
450
- const rows = await this.getAllRows('SELECT * FROM interaction_logs WHERE conversation_id = ? ORDER BY timestamp ASC', [conversationId]);
451
- const logs = rows.map(row => mapRowToInteractionLog({
452
- id: row.id,
453
- conversationId: row.conversation_id,
454
- toolName: row.tool_name,
455
- inputParams: row.input_params,
456
- responseData: row.response_data,
457
- currentPhase: row.current_phase,
458
- timestamp: row.timestamp,
459
- isReset: row.is_reset,
460
- resetAt: row.reset_at,
461
- }));
462
- logger.debug('Retrieved all interaction logs including reset', {
463
- conversationId,
464
- count: logs.length,
465
- resetCount: logs.filter(log => log.isReset).length,
466
- });
467
- return logs;
468
- }
469
- catch (error) {
470
- logger.error('Failed to get all interaction logs including reset', error, { conversationId });
471
- throw error;
472
- }
343
+ // Persist to file
344
+ await this.saveToFile();
345
+ logger.debug('Conversation state reset', { conversationId, resetAt });
473
346
  }
474
347
  /**
475
348
  * Close database connection
476
349
  */
477
350
  async close() {
478
- logger.debug('Closing database connection');
479
- return new Promise((resolve, reject) => {
480
- if (this.db) {
481
- this.db.close(err => {
482
- if (err) {
483
- logger.error('Failed to close database connection', err);
484
- reject(err);
485
- }
486
- else {
487
- this.db = null;
488
- logger.info('Database connection closed successfully');
489
- resolve();
490
- }
491
- });
492
- }
493
- else {
494
- logger.debug('Database connection already closed');
495
- resolve();
496
- }
497
- });
351
+ if (this.db) {
352
+ this.db.close();
353
+ this.db = null;
354
+ logger.debug('Database connection closed');
355
+ }
498
356
  }
499
357
  }
500
358
  //# sourceMappingURL=database.js.map