@openqa/cli 1.3.4 → 2.0.0
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/README.md +1 -1
- package/dist/agent/brain/diff-analyzer.js +140 -0
- package/dist/agent/brain/diff-analyzer.js.map +1 -0
- package/dist/agent/brain/llm-cache.js +47 -0
- package/dist/agent/brain/llm-cache.js.map +1 -0
- package/dist/agent/brain/llm-resilience.js +252 -0
- package/dist/agent/brain/llm-resilience.js.map +1 -0
- package/dist/agent/config/index.js +588 -0
- package/dist/agent/config/index.js.map +1 -0
- package/dist/agent/coverage/index.js +74 -0
- package/dist/agent/coverage/index.js.map +1 -0
- package/dist/agent/export/index.js +158 -0
- package/dist/agent/export/index.js.map +1 -0
- package/dist/agent/index-v2.js +2795 -0
- package/dist/agent/index-v2.js.map +1 -0
- package/dist/agent/index.js +369 -105
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/logger.js +41 -0
- package/dist/agent/logger.js.map +1 -0
- package/dist/agent/metrics.js +39 -0
- package/dist/agent/metrics.js.map +1 -0
- package/dist/agent/notifications/index.js +106 -0
- package/dist/agent/notifications/index.js.map +1 -0
- package/dist/agent/openapi/spec.js +338 -0
- package/dist/agent/openapi/spec.js.map +1 -0
- package/dist/agent/tools/project-runner.js +481 -0
- package/dist/agent/tools/project-runner.js.map +1 -0
- package/dist/cli/config.html.js +454 -0
- package/dist/cli/daemon.js +7572 -0
- package/dist/cli/dashboard.html.js +1619 -0
- package/dist/cli/index.js +3492 -1622
- package/dist/cli/kanban.html.js +577 -0
- package/dist/cli/routes.js +895 -0
- package/dist/cli/routes.js.map +1 -0
- package/dist/cli/server.js +3469 -1630
- package/dist/database/index.js +485 -60
- package/dist/database/index.js.map +1 -1
- package/dist/database/sqlite.js +281 -0
- package/dist/database/sqlite.js.map +1 -0
- package/package.json +18 -5
package/dist/database/index.js
CHANGED
|
@@ -1,11 +1,328 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var init_esm_shims = __esm({
|
|
15
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// database/sqlite.ts
|
|
21
|
+
var sqlite_exports = {};
|
|
22
|
+
__export(sqlite_exports, {
|
|
23
|
+
OpenQASQLiteDatabase: () => OpenQASQLiteDatabase
|
|
24
|
+
});
|
|
25
|
+
import Database from "better-sqlite3";
|
|
26
|
+
import { mkdirSync } from "fs";
|
|
27
|
+
import { dirname } from "path";
|
|
28
|
+
var OpenQASQLiteDatabase;
|
|
29
|
+
var init_sqlite = __esm({
|
|
30
|
+
"database/sqlite.ts"() {
|
|
31
|
+
"use strict";
|
|
32
|
+
init_esm_shims();
|
|
33
|
+
OpenQASQLiteDatabase = class {
|
|
34
|
+
db;
|
|
35
|
+
constructor(dbPath = "./data/openqa.db") {
|
|
36
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
37
|
+
this.db = new Database(dbPath);
|
|
38
|
+
this.db.pragma("journal_mode = WAL");
|
|
39
|
+
this.db.pragma("foreign_keys = ON");
|
|
40
|
+
this.migrate();
|
|
41
|
+
}
|
|
42
|
+
migrate() {
|
|
43
|
+
this.db.exec(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
45
|
+
key TEXT PRIMARY KEY,
|
|
46
|
+
value TEXT NOT NULL
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS test_sessions (
|
|
50
|
+
id TEXT PRIMARY KEY,
|
|
51
|
+
started_at TEXT NOT NULL,
|
|
52
|
+
ended_at TEXT,
|
|
53
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
54
|
+
total_actions INTEGER NOT NULL DEFAULT 0,
|
|
55
|
+
bugs_found INTEGER NOT NULL DEFAULT 0,
|
|
56
|
+
metadata TEXT
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS actions (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
session_id TEXT NOT NULL,
|
|
62
|
+
timestamp TEXT NOT NULL,
|
|
63
|
+
type TEXT NOT NULL,
|
|
64
|
+
description TEXT NOT NULL,
|
|
65
|
+
input TEXT,
|
|
66
|
+
output TEXT,
|
|
67
|
+
screenshot_path TEXT,
|
|
68
|
+
FOREIGN KEY (session_id) REFERENCES test_sessions(id)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE IF NOT EXISTS bugs (
|
|
72
|
+
id TEXT PRIMARY KEY,
|
|
73
|
+
session_id TEXT NOT NULL,
|
|
74
|
+
title TEXT NOT NULL,
|
|
75
|
+
description TEXT NOT NULL,
|
|
76
|
+
severity TEXT NOT NULL DEFAULT 'medium',
|
|
77
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
78
|
+
github_issue_url TEXT,
|
|
79
|
+
screenshot_path TEXT,
|
|
80
|
+
created_at TEXT NOT NULL,
|
|
81
|
+
updated_at TEXT NOT NULL,
|
|
82
|
+
FOREIGN KEY (session_id) REFERENCES test_sessions(id)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
CREATE TABLE IF NOT EXISTS kanban_tickets (
|
|
86
|
+
id TEXT PRIMARY KEY,
|
|
87
|
+
bug_id TEXT,
|
|
88
|
+
title TEXT NOT NULL,
|
|
89
|
+
description TEXT NOT NULL,
|
|
90
|
+
priority TEXT NOT NULL DEFAULT 'medium',
|
|
91
|
+
column TEXT NOT NULL DEFAULT 'backlog',
|
|
92
|
+
tags TEXT,
|
|
93
|
+
screenshot_url TEXT,
|
|
94
|
+
created_at TEXT NOT NULL,
|
|
95
|
+
updated_at TEXT NOT NULL
|
|
96
|
+
);
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
// ── Config ──
|
|
100
|
+
async getConfig(key) {
|
|
101
|
+
const row = this.db.prepare("SELECT value FROM config WHERE key = ?").get(key);
|
|
102
|
+
return row?.value ?? null;
|
|
103
|
+
}
|
|
104
|
+
async setConfig(key, value) {
|
|
105
|
+
this.db.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)").run(key, value);
|
|
106
|
+
}
|
|
107
|
+
async getAllConfig() {
|
|
108
|
+
const rows = this.db.prepare("SELECT key, value FROM config").all();
|
|
109
|
+
return Object.fromEntries(rows.map((r) => [r.key, r.value]));
|
|
110
|
+
}
|
|
111
|
+
async clearAllConfig() {
|
|
112
|
+
this.db.prepare("DELETE FROM config").run();
|
|
113
|
+
}
|
|
114
|
+
// ── Sessions ──
|
|
115
|
+
async createSession(id, metadata) {
|
|
116
|
+
const session = {
|
|
117
|
+
id,
|
|
118
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
119
|
+
status: "running",
|
|
120
|
+
total_actions: 0,
|
|
121
|
+
bugs_found: 0,
|
|
122
|
+
metadata: metadata ? JSON.stringify(metadata) : void 0
|
|
123
|
+
};
|
|
124
|
+
this.db.prepare(`
|
|
125
|
+
INSERT INTO test_sessions (id, started_at, status, total_actions, bugs_found, metadata)
|
|
126
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
127
|
+
`).run(session.id, session.started_at, session.status, session.total_actions, session.bugs_found, session.metadata ?? null);
|
|
128
|
+
return session;
|
|
129
|
+
}
|
|
130
|
+
async getSession(id) {
|
|
131
|
+
return this.db.prepare("SELECT * FROM test_sessions WHERE id = ?").get(id) ?? null;
|
|
132
|
+
}
|
|
133
|
+
async updateSession(id, updates) {
|
|
134
|
+
const fields = Object.keys(updates).map((k) => `${k} = ?`).join(", ");
|
|
135
|
+
const values = [...Object.values(updates), id];
|
|
136
|
+
this.db.prepare(`UPDATE test_sessions SET ${fields} WHERE id = ?`).run(...values);
|
|
137
|
+
}
|
|
138
|
+
async getRecentSessions(limit = 10) {
|
|
139
|
+
return this.db.prepare("SELECT * FROM test_sessions ORDER BY started_at DESC LIMIT ?").all(limit);
|
|
140
|
+
}
|
|
141
|
+
// ── Actions ──
|
|
142
|
+
async createAction(action) {
|
|
143
|
+
const newAction = {
|
|
144
|
+
id: `action_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
145
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
146
|
+
...action
|
|
147
|
+
};
|
|
148
|
+
this.db.prepare(`
|
|
149
|
+
INSERT INTO actions (id, session_id, timestamp, type, description, input, output, screenshot_path)
|
|
150
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
151
|
+
`).run(
|
|
152
|
+
newAction.id,
|
|
153
|
+
newAction.session_id,
|
|
154
|
+
newAction.timestamp,
|
|
155
|
+
newAction.type,
|
|
156
|
+
newAction.description,
|
|
157
|
+
newAction.input ?? null,
|
|
158
|
+
newAction.output ?? null,
|
|
159
|
+
newAction.screenshot_path ?? null
|
|
160
|
+
);
|
|
161
|
+
return newAction;
|
|
162
|
+
}
|
|
163
|
+
async getSessionActions(sessionId) {
|
|
164
|
+
return this.db.prepare("SELECT * FROM actions WHERE session_id = ? ORDER BY timestamp DESC").all(sessionId);
|
|
165
|
+
}
|
|
166
|
+
// ── Bugs ──
|
|
167
|
+
async createBug(bug) {
|
|
168
|
+
const newBug = {
|
|
169
|
+
id: `bug_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
170
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
171
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
172
|
+
...bug
|
|
173
|
+
};
|
|
174
|
+
this.db.prepare(`
|
|
175
|
+
INSERT INTO bugs (id, session_id, title, description, severity, status, github_issue_url, screenshot_path, created_at, updated_at)
|
|
176
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
177
|
+
`).run(
|
|
178
|
+
newBug.id,
|
|
179
|
+
newBug.session_id,
|
|
180
|
+
newBug.title,
|
|
181
|
+
newBug.description,
|
|
182
|
+
newBug.severity,
|
|
183
|
+
newBug.status,
|
|
184
|
+
newBug.github_issue_url ?? null,
|
|
185
|
+
newBug.screenshot_path ?? null,
|
|
186
|
+
newBug.created_at,
|
|
187
|
+
newBug.updated_at
|
|
188
|
+
);
|
|
189
|
+
return newBug;
|
|
190
|
+
}
|
|
191
|
+
async updateBug(id, updates) {
|
|
192
|
+
const patch = { ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
193
|
+
const fields = Object.keys(patch).map((k) => `${k} = ?`).join(", ");
|
|
194
|
+
const values = [...Object.values(patch), id];
|
|
195
|
+
this.db.prepare(`UPDATE bugs SET ${fields} WHERE id = ?`).run(...values);
|
|
196
|
+
}
|
|
197
|
+
async getAllBugs() {
|
|
198
|
+
return this.db.prepare("SELECT * FROM bugs ORDER BY created_at DESC").all();
|
|
199
|
+
}
|
|
200
|
+
async getBugsByStatus(status) {
|
|
201
|
+
return this.db.prepare("SELECT * FROM bugs WHERE status = ? ORDER BY created_at DESC").all(status);
|
|
202
|
+
}
|
|
203
|
+
// ── Kanban ──
|
|
204
|
+
async createKanbanTicket(ticket) {
|
|
205
|
+
const newTicket = {
|
|
206
|
+
id: `ticket_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
207
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
208
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
209
|
+
...ticket
|
|
210
|
+
};
|
|
211
|
+
this.db.prepare(`
|
|
212
|
+
INSERT INTO kanban_tickets (id, bug_id, title, description, priority, column, tags, screenshot_url, created_at, updated_at)
|
|
213
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
214
|
+
`).run(
|
|
215
|
+
newTicket.id,
|
|
216
|
+
newTicket.bug_id ?? null,
|
|
217
|
+
newTicket.title,
|
|
218
|
+
newTicket.description,
|
|
219
|
+
newTicket.priority,
|
|
220
|
+
newTicket.column,
|
|
221
|
+
newTicket.tags ?? null,
|
|
222
|
+
newTicket.screenshot_url ?? null,
|
|
223
|
+
newTicket.created_at,
|
|
224
|
+
newTicket.updated_at
|
|
225
|
+
);
|
|
226
|
+
return newTicket;
|
|
227
|
+
}
|
|
228
|
+
async updateKanbanTicket(id, updates) {
|
|
229
|
+
const patch = { ...updates, updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
230
|
+
const fields = Object.keys(patch).map((k) => `"${k}" = ?`).join(", ");
|
|
231
|
+
const values = [...Object.values(patch), id];
|
|
232
|
+
this.db.prepare(`UPDATE kanban_tickets SET ${fields} WHERE id = ?`).run(...values);
|
|
233
|
+
}
|
|
234
|
+
async getKanbanTickets() {
|
|
235
|
+
return this.db.prepare("SELECT * FROM kanban_tickets ORDER BY created_at DESC").all();
|
|
236
|
+
}
|
|
237
|
+
async getKanbanTicketsByColumn(column) {
|
|
238
|
+
return this.db.prepare('SELECT * FROM kanban_tickets WHERE "column" = ? ORDER BY created_at DESC').all(column);
|
|
239
|
+
}
|
|
240
|
+
async deleteKanbanTicket(id) {
|
|
241
|
+
this.db.prepare("DELETE FROM kanban_tickets WHERE id = ?").run(id);
|
|
242
|
+
}
|
|
243
|
+
// ── Storage / Cleanup ──
|
|
244
|
+
async pruneOldSessions(maxAgeDays) {
|
|
245
|
+
const cutoff = new Date(Date.now() - maxAgeDays * 864e5).toISOString();
|
|
246
|
+
const oldSessions = this.db.prepare("SELECT id FROM test_sessions WHERE started_at < ?").all(cutoff);
|
|
247
|
+
const ids = oldSessions.map((s) => s.id);
|
|
248
|
+
if (ids.length === 0) return { sessionsRemoved: 0, actionsRemoved: 0 };
|
|
249
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
250
|
+
const actionsResult = this.db.prepare(`DELETE FROM actions WHERE session_id IN (${placeholders})`).run(...ids);
|
|
251
|
+
this.db.prepare(`DELETE FROM test_sessions WHERE id IN (${placeholders})`).run(...ids);
|
|
252
|
+
return { sessionsRemoved: ids.length, actionsRemoved: actionsResult.changes };
|
|
253
|
+
}
|
|
254
|
+
async getStorageStats() {
|
|
255
|
+
const count = (table) => this.db.prepare(`SELECT COUNT(*) as n FROM ${table}`).get().n;
|
|
256
|
+
return {
|
|
257
|
+
sessions: count("test_sessions"),
|
|
258
|
+
actions: count("actions"),
|
|
259
|
+
bugs: count("bugs"),
|
|
260
|
+
tickets: count("kanban_tickets")
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// ── Compat helpers (used by daemon.ts) ──
|
|
264
|
+
async getActiveAgents() {
|
|
265
|
+
const sessions = await this.getRecentSessions(1);
|
|
266
|
+
const current = sessions[0];
|
|
267
|
+
const isRunning = current?.status === "running";
|
|
268
|
+
return [{
|
|
269
|
+
name: "Main Agent",
|
|
270
|
+
status: isRunning ? "running" : "idle",
|
|
271
|
+
purpose: "Autonomous QA orchestration",
|
|
272
|
+
performance: current ? Math.min(100, Math.round(current.total_actions / 100 * 100)) : 0,
|
|
273
|
+
tasks: current?.total_actions ?? 0
|
|
274
|
+
}];
|
|
275
|
+
}
|
|
276
|
+
async getCurrentTasks() {
|
|
277
|
+
const sessions = await this.getRecentSessions(1);
|
|
278
|
+
if (!sessions[0]) return [];
|
|
279
|
+
const actions = await this.getSessionActions(sessions[0].id);
|
|
280
|
+
return actions.slice(0, 10).map((a, i) => ({
|
|
281
|
+
id: a.id,
|
|
282
|
+
name: a.type,
|
|
283
|
+
status: i === 0 && sessions[0].status === "running" ? "running" : "completed",
|
|
284
|
+
progress: i === 0 && sessions[0].status === "running" ? "65%" : "100%",
|
|
285
|
+
agent: "Main Agent",
|
|
286
|
+
started_at: a.timestamp,
|
|
287
|
+
result: a.output || a.description
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
async getCurrentIssues() {
|
|
291
|
+
const bugs = await this.getAllBugs();
|
|
292
|
+
return bugs.slice(0, 10).map((b) => ({
|
|
293
|
+
id: b.id,
|
|
294
|
+
title: b.title,
|
|
295
|
+
description: b.description,
|
|
296
|
+
severity: b.severity,
|
|
297
|
+
status: b.status,
|
|
298
|
+
discovered_at: b.created_at,
|
|
299
|
+
agent: "Main Agent"
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
async close() {
|
|
303
|
+
this.db.close();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
1
309
|
// database/index.ts
|
|
310
|
+
init_esm_shims();
|
|
311
|
+
init_sqlite();
|
|
2
312
|
import { Low } from "lowdb";
|
|
3
313
|
import { JSONFile } from "lowdb/node";
|
|
4
|
-
import { dirname } from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import { mkdirSync } from "fs";
|
|
7
|
-
|
|
8
|
-
|
|
314
|
+
import { dirname as dirname2 } from "path";
|
|
315
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
316
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
317
|
+
async function createDatabase(path2) {
|
|
318
|
+
if (path2.endsWith(".db")) {
|
|
319
|
+
const { OpenQASQLiteDatabase: OpenQASQLiteDatabase2 } = await Promise.resolve().then(() => (init_sqlite(), sqlite_exports));
|
|
320
|
+
return new OpenQASQLiteDatabase2(path2);
|
|
321
|
+
}
|
|
322
|
+
return new OpenQADatabase(path2);
|
|
323
|
+
}
|
|
324
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
325
|
+
var __dirname2 = dirname2(__filename2);
|
|
9
326
|
var OpenQADatabase = class {
|
|
10
327
|
constructor(dbPath = "./data/openqa.json") {
|
|
11
328
|
this.dbPath = dbPath;
|
|
@@ -13,15 +330,16 @@ var OpenQADatabase = class {
|
|
|
13
330
|
}
|
|
14
331
|
db = null;
|
|
15
332
|
initialize() {
|
|
16
|
-
const dir =
|
|
17
|
-
|
|
333
|
+
const dir = dirname2(this.dbPath);
|
|
334
|
+
mkdirSync2(dir, { recursive: true });
|
|
18
335
|
const adapter = new JSONFile(this.dbPath);
|
|
19
336
|
this.db = new Low(adapter, {
|
|
20
337
|
config: {},
|
|
21
338
|
test_sessions: [],
|
|
22
339
|
actions: [],
|
|
23
340
|
bugs: [],
|
|
24
|
-
kanban_tickets: []
|
|
341
|
+
kanban_tickets: [],
|
|
342
|
+
users: []
|
|
25
343
|
});
|
|
26
344
|
this.db.read();
|
|
27
345
|
if (!this.db.data) {
|
|
@@ -30,7 +348,8 @@ var OpenQADatabase = class {
|
|
|
30
348
|
test_sessions: [],
|
|
31
349
|
actions: [],
|
|
32
350
|
bugs: [],
|
|
33
|
-
kanban_tickets: []
|
|
351
|
+
kanban_tickets: [],
|
|
352
|
+
users: []
|
|
34
353
|
};
|
|
35
354
|
this.db.write();
|
|
36
355
|
}
|
|
@@ -40,6 +359,12 @@ var OpenQADatabase = class {
|
|
|
40
359
|
this.initialize();
|
|
41
360
|
}
|
|
42
361
|
await this.db.read();
|
|
362
|
+
let migrated = false;
|
|
363
|
+
if (!this.db.data.users) {
|
|
364
|
+
this.db.data.users = [];
|
|
365
|
+
migrated = true;
|
|
366
|
+
}
|
|
367
|
+
if (migrated) await this.db.write();
|
|
43
368
|
}
|
|
44
369
|
async getConfig(key) {
|
|
45
370
|
await this.ensureInitialized();
|
|
@@ -163,83 +488,183 @@ var OpenQADatabase = class {
|
|
|
163
488
|
await this.ensureInitialized();
|
|
164
489
|
return this.db.data.kanban_tickets.filter((t) => t.column === column).sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
165
490
|
}
|
|
491
|
+
async deleteKanbanTicket(id) {
|
|
492
|
+
await this.ensureInitialized();
|
|
493
|
+
const index = this.db.data.kanban_tickets.findIndex((t) => t.id === id);
|
|
494
|
+
if (index !== -1) {
|
|
495
|
+
this.db.data.kanban_tickets.splice(index, 1);
|
|
496
|
+
await this.db.write();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
166
499
|
async clearAllConfig() {
|
|
167
500
|
await this.ensureInitialized();
|
|
168
501
|
this.db.data.config = {};
|
|
169
502
|
await this.db.write();
|
|
170
503
|
}
|
|
171
|
-
// Get real data methods
|
|
504
|
+
// Get real data methods - connected to actual database records
|
|
172
505
|
async getActiveAgents() {
|
|
173
506
|
await this.ensureInitialized();
|
|
174
507
|
const sessions = await this.getRecentSessions(1);
|
|
175
508
|
const currentSession = sessions[0];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
509
|
+
const isRunning = currentSession?.status === "running";
|
|
510
|
+
const totalActions = currentSession?.total_actions || 0;
|
|
511
|
+
const agents = [
|
|
512
|
+
{
|
|
513
|
+
name: "Main Agent",
|
|
514
|
+
status: isRunning ? "running" : "idle",
|
|
515
|
+
purpose: "Autonomous QA orchestration",
|
|
516
|
+
performance: totalActions > 0 ? Math.min(100, Math.round(totalActions / 100 * 100)) : 0,
|
|
517
|
+
tasks: totalActions
|
|
518
|
+
}
|
|
186
519
|
];
|
|
520
|
+
if (currentSession && totalActions > 0) {
|
|
521
|
+
const actions = await this.getSessionActions(currentSession.id);
|
|
522
|
+
const actionTypes = actions.reduce((acc, action) => {
|
|
523
|
+
const type = action.type || "unknown";
|
|
524
|
+
acc[type] = (acc[type] || 0) + 1;
|
|
525
|
+
return acc;
|
|
526
|
+
}, {});
|
|
527
|
+
if (actionTypes["navigate"] || actionTypes["click"] || actionTypes["screenshot"]) {
|
|
528
|
+
agents.push({
|
|
529
|
+
name: "Browser Specialist",
|
|
530
|
+
status: isRunning ? "running" : "idle",
|
|
531
|
+
purpose: "UI navigation and interaction",
|
|
532
|
+
performance: Math.round(((actionTypes["navigate"] || 0) + (actionTypes["click"] || 0)) / totalActions * 100),
|
|
533
|
+
tasks: (actionTypes["navigate"] || 0) + (actionTypes["click"] || 0)
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (actionTypes["api_call"] || actionTypes["request"]) {
|
|
537
|
+
agents.push({
|
|
538
|
+
name: "API Tester",
|
|
539
|
+
status: isRunning ? "running" : "idle",
|
|
540
|
+
purpose: "API endpoint testing",
|
|
541
|
+
performance: Math.round((actionTypes["api_call"] || actionTypes["request"] || 0) / totalActions * 100),
|
|
542
|
+
tasks: actionTypes["api_call"] || actionTypes["request"] || 0
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (actionTypes["auth"] || actionTypes["login"]) {
|
|
546
|
+
agents.push({
|
|
547
|
+
name: "Auth Specialist",
|
|
548
|
+
status: isRunning ? "running" : "idle",
|
|
549
|
+
purpose: "Authentication testing",
|
|
550
|
+
performance: Math.round((actionTypes["auth"] || actionTypes["login"] || 0) / totalActions * 100),
|
|
551
|
+
tasks: actionTypes["auth"] || actionTypes["login"] || 0
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return agents;
|
|
187
556
|
}
|
|
188
557
|
async getCurrentTasks() {
|
|
189
558
|
await this.ensureInitialized();
|
|
190
559
|
const sessions = await this.getRecentSessions(1);
|
|
191
560
|
const currentSession = sessions[0];
|
|
192
|
-
if (!currentSession
|
|
561
|
+
if (!currentSession) {
|
|
193
562
|
return [];
|
|
194
563
|
}
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
agent: ["Main Agent", "Browser Specialist", "API Tester", "UI Tester"][i % 4],
|
|
207
|
-
started_at: new Date(Date.now() - i * 10 * 60 * 1e3).toISOString(),
|
|
208
|
-
result: status === "completed" ? "Successfully completed task execution" : null
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
return tasks;
|
|
564
|
+
const actions = await this.getSessionActions(currentSession.id);
|
|
565
|
+
const recentActions = actions.slice(-10).reverse();
|
|
566
|
+
return recentActions.map((action, index) => ({
|
|
567
|
+
id: action.id,
|
|
568
|
+
name: action.type || "Unknown Action",
|
|
569
|
+
status: index === 0 && currentSession.status === "running" ? "running" : "completed",
|
|
570
|
+
progress: index === 0 && currentSession.status === "running" ? "65%" : "100%",
|
|
571
|
+
agent: "Main Agent",
|
|
572
|
+
started_at: action.timestamp,
|
|
573
|
+
result: action.output || action.description || "Completed"
|
|
574
|
+
}));
|
|
212
575
|
}
|
|
213
576
|
async getCurrentIssues() {
|
|
214
577
|
await this.ensureInitialized();
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
578
|
+
const bugs = await this.getAllBugs();
|
|
579
|
+
return bugs.slice(0, 10).map((bug) => ({
|
|
580
|
+
id: bug.id,
|
|
581
|
+
title: bug.title,
|
|
582
|
+
description: bug.description,
|
|
583
|
+
severity: bug.severity || "medium",
|
|
584
|
+
status: bug.status || "open",
|
|
585
|
+
discovered_at: bug.created_at,
|
|
586
|
+
agent: "Main Agent"
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
async pruneOldSessions(maxAgeDays) {
|
|
590
|
+
await this.ensureInitialized();
|
|
591
|
+
const cutoff = new Date(Date.now() - maxAgeDays * 864e5).toISOString();
|
|
592
|
+
const oldSessions = this.db.data.test_sessions.filter((s) => s.started_at < cutoff);
|
|
593
|
+
const oldSessionIds = new Set(oldSessions.map((s) => s.id));
|
|
594
|
+
const actionsBefore = this.db.data.actions.length;
|
|
595
|
+
this.db.data.actions = this.db.data.actions.filter((a) => !oldSessionIds.has(a.session_id));
|
|
596
|
+
const actionsRemoved = actionsBefore - this.db.data.actions.length;
|
|
597
|
+
this.db.data.test_sessions = this.db.data.test_sessions.filter((s) => s.started_at >= cutoff);
|
|
598
|
+
await this.db.write();
|
|
599
|
+
return { sessionsRemoved: oldSessions.length, actionsRemoved };
|
|
600
|
+
}
|
|
601
|
+
async getStorageStats() {
|
|
602
|
+
await this.ensureInitialized();
|
|
603
|
+
return {
|
|
604
|
+
sessions: this.db.data.test_sessions.length,
|
|
605
|
+
actions: this.db.data.actions.length,
|
|
606
|
+
bugs: this.db.data.bugs.length,
|
|
607
|
+
tickets: this.db.data.kanban_tickets.length
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
// ── User management ──────────────────────────────────────────────────────────
|
|
611
|
+
async countUsers() {
|
|
612
|
+
await this.ensureInitialized();
|
|
613
|
+
return this.db.data.users.length;
|
|
614
|
+
}
|
|
615
|
+
async findUserByUsername(username) {
|
|
616
|
+
await this.ensureInitialized();
|
|
617
|
+
return this.db.data.users.find((u) => u.username === username) ?? null;
|
|
618
|
+
}
|
|
619
|
+
async getUserById(id) {
|
|
620
|
+
await this.ensureInitialized();
|
|
621
|
+
return this.db.data.users.find((u) => u.id === id) ?? null;
|
|
622
|
+
}
|
|
623
|
+
async getAllUsers() {
|
|
624
|
+
await this.ensureInitialized();
|
|
625
|
+
return [...this.db.data.users];
|
|
626
|
+
}
|
|
627
|
+
async createUser(data) {
|
|
628
|
+
await this.ensureInitialized();
|
|
629
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
630
|
+
const user = {
|
|
631
|
+
id: `user_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
|
632
|
+
username: data.username,
|
|
633
|
+
passwordHash: data.passwordHash,
|
|
634
|
+
role: data.role,
|
|
635
|
+
createdAt: now,
|
|
636
|
+
updatedAt: now
|
|
637
|
+
};
|
|
638
|
+
this.db.data.users.push(user);
|
|
639
|
+
await this.db.write();
|
|
640
|
+
return user;
|
|
641
|
+
}
|
|
642
|
+
async updateUser(id, updates) {
|
|
643
|
+
await this.ensureInitialized();
|
|
644
|
+
const idx = this.db.data.users.findIndex((u) => u.id === id);
|
|
645
|
+
if (idx !== -1) {
|
|
646
|
+
this.db.data.users[idx] = {
|
|
647
|
+
...this.db.data.users[idx],
|
|
648
|
+
...updates,
|
|
649
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
650
|
+
};
|
|
651
|
+
await this.db.write();
|
|
219
652
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
id: `issue_${i + 1}`,
|
|
228
|
-
title: issueTypes[i % issueTypes.length],
|
|
229
|
-
description: `Issue detected during automated testing session`,
|
|
230
|
-
severity: severities[i % severities.length],
|
|
231
|
-
status: "open",
|
|
232
|
-
discovered_at: new Date(Date.now() - i * 30 * 60 * 1e3).toISOString(),
|
|
233
|
-
agent: ["Main Agent", "Browser Specialist", "API Tester"][i % 3]
|
|
234
|
-
});
|
|
235
|
-
}
|
|
653
|
+
}
|
|
654
|
+
async deleteUser(id) {
|
|
655
|
+
await this.ensureInitialized();
|
|
656
|
+
const idx = this.db.data.users.findIndex((u) => u.id === id);
|
|
657
|
+
if (idx !== -1) {
|
|
658
|
+
this.db.data.users.splice(idx, 1);
|
|
659
|
+
await this.db.write();
|
|
236
660
|
}
|
|
237
|
-
return issues;
|
|
238
661
|
}
|
|
239
662
|
async close() {
|
|
240
663
|
}
|
|
241
664
|
};
|
|
242
665
|
export {
|
|
243
|
-
OpenQADatabase
|
|
666
|
+
OpenQADatabase,
|
|
667
|
+
OpenQASQLiteDatabase,
|
|
668
|
+
createDatabase
|
|
244
669
|
};
|
|
245
670
|
//# sourceMappingURL=index.js.map
|