@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/.turbo/turbo-build.log +1 -1
- package/dist/conversation-manager.js +1 -1
- package/dist/conversation-manager.js.map +1 -1
- package/dist/database.d.ts +30 -31
- package/dist/database.js +262 -404
- package/dist/database.js.map +1 -1
- package/dist/project-docs-manager.js +5 -4
- package/dist/project-docs-manager.js.map +1 -1
- package/dist/state-machine-loader.d.ts +2 -1
- package/dist/state-machine-loader.js +45 -7
- package/dist/state-machine-loader.js.map +1 -1
- package/dist/template-manager.js +9 -7
- package/dist/template-manager.js.map +1 -1
- package/dist/workflow-manager.js +9 -7
- package/dist/workflow-manager.js.map +1 -1
- package/package.json +3 -2
- package/src/conversation-manager.ts +1 -4
- package/src/database.ts +309 -567
- package/src/project-docs-manager.ts +5 -4
- package/src/state-machine-loader.ts +71 -14
- package/src/template-manager.ts +15 -8
- package/src/workflow-manager.ts +12 -7
package/dist/database.js
CHANGED
@@ -1,233 +1,169 @@
|
|
1
1
|
/**
|
2
|
-
* Database
|
2
|
+
* Database Manager
|
3
3
|
*
|
4
|
-
*
|
5
|
-
*
|
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
|
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
|
-
|
15
|
-
|
16
|
-
|
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(
|
50
|
-
|
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
|
-
//
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
70
|
-
this.
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
*
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
}
|
150
|
-
this.
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
-
*
|
88
|
+
* Save database content to file
|
181
89
|
*/
|
182
|
-
async
|
183
|
-
|
90
|
+
async saveToFile() {
|
91
|
+
if (!this.db || !this.dbPath || this.dbPath === ':memory:') {
|
92
|
+
return;
|
93
|
+
}
|
184
94
|
try {
|
185
|
-
const
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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.
|
211
|
-
|
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
|
-
*
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
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
|
-
*
|
187
|
+
* Get conversation state by ID
|
256
188
|
*/
|
257
|
-
async
|
258
|
-
|
259
|
-
|
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:
|
264
|
-
projectPath:
|
265
|
-
gitBranch:
|
266
|
-
currentPhase:
|
267
|
-
planFilePath:
|
268
|
-
workflowName:
|
269
|
-
gitCommitConfig:
|
270
|
-
requireReviewsBeforePhaseTransition: Boolean(row
|
271
|
-
createdAt:
|
272
|
-
updatedAt:
|
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
|
-
|
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
|
259
|
+
* Log interaction
|
283
260
|
*/
|
284
261
|
async logInteraction(log) {
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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
|
286
|
+
* Get interaction logs for a conversation
|
316
287
|
*/
|
317
|
-
async
|
318
|
-
|
319
|
-
|
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
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
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
|
-
*
|
311
|
+
* Get interaction logs for a conversation (alias for compatibility)
|
345
312
|
*/
|
346
|
-
async
|
347
|
-
|
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
|
317
|
+
* Soft delete interaction logs (for compatibility - actually deletes them)
|
394
318
|
*/
|
395
|
-
async softDeleteInteractionLogs(conversationId
|
396
|
-
|
397
|
-
|
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
|
-
*
|
332
|
+
* Reset conversation state (for testing)
|
415
333
|
*/
|
416
|
-
async
|
417
|
-
|
418
|
-
|
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
|
-
|
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
|
-
|
450
|
-
|
451
|
-
|
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
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
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
|