@agent-inspect/index-sqlite 4.1.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/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/index.cjs +409 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.mjs +394 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentInspect contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @agent-inspect/index-sqlite
|
|
2
|
+
|
|
3
|
+
Optional, disposable **local SQLite index** for faster queries over [AgentInspect](https://github.com/rajudandigam/agent-inspect) JSONL traces.
|
|
4
|
+
|
|
5
|
+
- **Experimental** (added in v4.1).
|
|
6
|
+
- **Local-only:** no network, no upload.
|
|
7
|
+
- **JSONL stays the source of truth.** The index is a derived cache and is always safe to delete.
|
|
8
|
+
- **Never mutates trace files.**
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install agent-inspect @agent-inspect/index-sqlite
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`agent-inspect` is a peer dependency. `better-sqlite3` is bundled as a dependency (prebuilt binaries; Node >= 20).
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import {
|
|
22
|
+
buildIndex,
|
|
23
|
+
queryRuns,
|
|
24
|
+
indexStatus,
|
|
25
|
+
cleanIndex,
|
|
26
|
+
resolveIndexDbPath,
|
|
27
|
+
} from "@agent-inspect/index-sqlite";
|
|
28
|
+
|
|
29
|
+
// Build (or fully rebuild — idempotent) from a trace directory.
|
|
30
|
+
const result = await buildIndex({ traceDir: ".agent-inspect/runs" });
|
|
31
|
+
console.log(result.runs, result.steps, result.dbPath);
|
|
32
|
+
|
|
33
|
+
// Fast queries against the index (falls back to [] if missing/corrupt).
|
|
34
|
+
const failed = queryRuns(result.dbPath, { status: "error", limit: 20 });
|
|
35
|
+
const withTool = queryRuns(result.dbPath, { tool: "search", kind: "tool" });
|
|
36
|
+
|
|
37
|
+
// Status and cleanup.
|
|
38
|
+
console.log(indexStatus(result.dbPath));
|
|
39
|
+
await cleanIndex(resolveIndexDbPath(".agent-inspect/runs"));
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Behavior
|
|
43
|
+
|
|
44
|
+
- **Rebuild is idempotent:** building twice from identical inputs yields identical contents.
|
|
45
|
+
- **Corruption recovery:** a missing or `integrity_check`-failing database is treated as absent; queries return empty and a rebuild recreates it.
|
|
46
|
+
- **Staleness:** `isIndexStale(dbPath, newestTraceMtimeMs)` reports when traces are newer than the index.
|
|
47
|
+
- **Disposable:** deleting the database never affects traces.
|
|
48
|
+
|
|
49
|
+
## Boundaries
|
|
50
|
+
|
|
51
|
+
- No SQLite dependency in `agent-inspect` core or root — it lives only here.
|
|
52
|
+
- No vector/semantic search, no daemon, no remote service.
|
|
53
|
+
|
|
54
|
+
## Version ownership
|
|
55
|
+
|
|
56
|
+
Linked to the `agent-inspect` release line; published at the same version.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('fs/promises');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var Database = require('better-sqlite3');
|
|
6
|
+
var advanced = require('agent-inspect/advanced');
|
|
7
|
+
var fs = require('fs');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
12
|
+
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
13
|
+
|
|
14
|
+
// packages/index-sqlite/src/types.ts
|
|
15
|
+
var INDEX_SCHEMA_VERSION = "1";
|
|
16
|
+
var INDEX_DB_FILENAME = "trace-index.sqlite";
|
|
17
|
+
|
|
18
|
+
// packages/index-sqlite/src/schema.ts
|
|
19
|
+
var INDEX_SCHEMA_SQL = `
|
|
20
|
+
DROP TABLE IF EXISTS meta;
|
|
21
|
+
DROP TABLE IF EXISTS errors;
|
|
22
|
+
DROP TABLE IF EXISTS steps;
|
|
23
|
+
DROP TABLE IF EXISTS sessions;
|
|
24
|
+
DROP TABLE IF EXISTS runs;
|
|
25
|
+
|
|
26
|
+
CREATE TABLE meta (
|
|
27
|
+
key TEXT PRIMARY KEY,
|
|
28
|
+
value TEXT
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE runs (
|
|
32
|
+
run_id TEXT PRIMARY KEY,
|
|
33
|
+
file TEXT NOT NULL,
|
|
34
|
+
mtime_ms REAL NOT NULL,
|
|
35
|
+
name TEXT,
|
|
36
|
+
status TEXT,
|
|
37
|
+
started_at REAL,
|
|
38
|
+
ended_at REAL,
|
|
39
|
+
duration_ms REAL,
|
|
40
|
+
session_id TEXT,
|
|
41
|
+
group_id TEXT,
|
|
42
|
+
correlation_id TEXT
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE steps (
|
|
46
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
+
run_id TEXT NOT NULL,
|
|
48
|
+
step_id TEXT NOT NULL,
|
|
49
|
+
kind TEXT,
|
|
50
|
+
name TEXT,
|
|
51
|
+
status TEXT,
|
|
52
|
+
duration_ms REAL,
|
|
53
|
+
tool_name TEXT,
|
|
54
|
+
model TEXT,
|
|
55
|
+
parent_id TEXT
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CREATE TABLE errors (
|
|
59
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
|
+
run_id TEXT NOT NULL,
|
|
61
|
+
step_id TEXT,
|
|
62
|
+
message TEXT,
|
|
63
|
+
code TEXT
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE sessions (
|
|
67
|
+
session_id TEXT PRIMARY KEY,
|
|
68
|
+
run_count INTEGER NOT NULL,
|
|
69
|
+
first_started_at REAL,
|
|
70
|
+
last_ended_at REAL
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE INDEX idx_runs_status ON runs(status);
|
|
74
|
+
CREATE INDEX idx_runs_session ON runs(session_id);
|
|
75
|
+
CREATE INDEX idx_runs_started ON runs(started_at);
|
|
76
|
+
CREATE INDEX idx_steps_run ON steps(run_id);
|
|
77
|
+
CREATE INDEX idx_steps_kind ON steps(kind);
|
|
78
|
+
CREATE INDEX idx_steps_tool ON steps(tool_name);
|
|
79
|
+
`;
|
|
80
|
+
var DERIVE_SESSIONS_SQL = `
|
|
81
|
+
INSERT INTO sessions (session_id, run_count, first_started_at, last_ended_at)
|
|
82
|
+
SELECT session_id, COUNT(*), MIN(started_at), MAX(ended_at)
|
|
83
|
+
FROM runs
|
|
84
|
+
WHERE session_id IS NOT NULL
|
|
85
|
+
GROUP BY session_id;
|
|
86
|
+
`;
|
|
87
|
+
var META_KEYS = {
|
|
88
|
+
schemaVersion: "schemaVersion",
|
|
89
|
+
builtAt: "builtAt",
|
|
90
|
+
sourceDir: "sourceDir",
|
|
91
|
+
fileCount: "fileCount",
|
|
92
|
+
driver: "driver"
|
|
93
|
+
};
|
|
94
|
+
function metaDefaults(sourceDir, fileCount) {
|
|
95
|
+
return {
|
|
96
|
+
[META_KEYS.schemaVersion]: INDEX_SCHEMA_VERSION,
|
|
97
|
+
[META_KEYS.builtAt]: (/* @__PURE__ */ new Date()).toISOString(),
|
|
98
|
+
[META_KEYS.sourceDir]: sourceDir,
|
|
99
|
+
[META_KEYS.fileCount]: String(fileCount),
|
|
100
|
+
[META_KEYS.driver]: "better-sqlite3"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// packages/index-sqlite/src/builder.ts
|
|
105
|
+
var DEFAULT_MAX_RUNS = 1e4;
|
|
106
|
+
function resolveIndexDbPath(traceDir, dbPath) {
|
|
107
|
+
if (dbPath && dbPath.trim() !== "") return path__default.default.resolve(dbPath);
|
|
108
|
+
return path__default.default.join(path__default.default.resolve(traceDir), INDEX_DB_FILENAME);
|
|
109
|
+
}
|
|
110
|
+
function str(value) {
|
|
111
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
112
|
+
}
|
|
113
|
+
function num(value) {
|
|
114
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
115
|
+
}
|
|
116
|
+
function deriveRun(file, mtimeMs, events) {
|
|
117
|
+
const started = events.find((e) => e.event === "run_started");
|
|
118
|
+
if (!started || started.event !== "run_started") return null;
|
|
119
|
+
const completed = events.find((e) => e.event === "run_completed");
|
|
120
|
+
const metadata = started.metadata ?? {};
|
|
121
|
+
const run = {
|
|
122
|
+
runId: started.runId,
|
|
123
|
+
file,
|
|
124
|
+
mtimeMs,
|
|
125
|
+
name: str(started.name),
|
|
126
|
+
status: completed && completed.event === "run_completed" ? completed.status : null,
|
|
127
|
+
startedAt: num(started.startTime),
|
|
128
|
+
endedAt: completed && completed.event === "run_completed" ? num(completed.endTime) : null,
|
|
129
|
+
durationMs: completed && completed.event === "run_completed" ? num(completed.durationMs) : null,
|
|
130
|
+
sessionId: str(metadata.sessionId),
|
|
131
|
+
groupId: str(metadata.groupId),
|
|
132
|
+
correlationId: str(metadata.correlationId)
|
|
133
|
+
};
|
|
134
|
+
const stepStarts = /* @__PURE__ */ new Map();
|
|
135
|
+
const steps = [];
|
|
136
|
+
const errors = [];
|
|
137
|
+
if (completed && completed.event === "run_completed" && completed.error) {
|
|
138
|
+
errors.push({
|
|
139
|
+
stepId: null,
|
|
140
|
+
message: str(completed.error.message),
|
|
141
|
+
code: str(completed.error.code)
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
for (const event of events) {
|
|
145
|
+
if (event.event === "step_started") {
|
|
146
|
+
stepStarts.set(event.stepId, event);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const event of events) {
|
|
150
|
+
if (event.event !== "step_completed") continue;
|
|
151
|
+
const start = stepStarts.get(event.stepId);
|
|
152
|
+
const meta2 = start?.metadata ?? {};
|
|
153
|
+
steps.push({
|
|
154
|
+
runId: run.runId,
|
|
155
|
+
stepId: event.stepId,
|
|
156
|
+
kind: start ? str(start.type) : null,
|
|
157
|
+
name: start ? str(start.name) : null,
|
|
158
|
+
status: event.status,
|
|
159
|
+
durationMs: num(event.durationMs),
|
|
160
|
+
toolName: str(meta2.toolName),
|
|
161
|
+
model: str(meta2.model),
|
|
162
|
+
parentId: start ? str(start.parentId) : null
|
|
163
|
+
});
|
|
164
|
+
if (event.error) {
|
|
165
|
+
errors.push({
|
|
166
|
+
stepId: event.stepId,
|
|
167
|
+
message: str(event.error.message),
|
|
168
|
+
code: str(event.error.code)
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { run, steps, errors };
|
|
173
|
+
}
|
|
174
|
+
async function buildIndex(options = {}) {
|
|
175
|
+
const traceDir = advanced.resolveTraceDir({ dir: options.traceDir });
|
|
176
|
+
const dbPath = resolveIndexDbPath(traceDir, options.dbPath);
|
|
177
|
+
const maxRuns = options.maxRuns ?? DEFAULT_MAX_RUNS;
|
|
178
|
+
const warnings = [];
|
|
179
|
+
const td = new advanced.TraceDirectory({ dir: traceDir });
|
|
180
|
+
const files = await td.list();
|
|
181
|
+
if (files.length > maxRuns) {
|
|
182
|
+
warnings.push(
|
|
183
|
+
`index.truncated: ${files.length} trace files present; indexing first ${maxRuns}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const slice = files.slice(0, maxRuns);
|
|
187
|
+
const derived = [];
|
|
188
|
+
for (const file of slice) {
|
|
189
|
+
try {
|
|
190
|
+
const raw = await promises.readFile(td.getPath(file), "utf-8");
|
|
191
|
+
const parsed = advanced.parseTraceJsonl(raw, { validate: advanced.validateEvent });
|
|
192
|
+
const stats = await td.getFileStats(file);
|
|
193
|
+
const one = deriveRun(file, stats.mtimeMs, parsed.events);
|
|
194
|
+
if (one) derived.push(one);
|
|
195
|
+
else warnings.push(`index.skipped: ${file} has no run_started event`);
|
|
196
|
+
} catch {
|
|
197
|
+
warnings.push(`index.unreadable: ${file}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
await promises.mkdir(path__default.default.dirname(dbPath), { recursive: true });
|
|
201
|
+
await promises.rm(dbPath, { force: true });
|
|
202
|
+
const db = new Database__default.default(dbPath);
|
|
203
|
+
let runCount = 0;
|
|
204
|
+
let stepCount = 0;
|
|
205
|
+
let errorCount = 0;
|
|
206
|
+
try {
|
|
207
|
+
db.pragma("journal_mode = WAL");
|
|
208
|
+
db.exec(INDEX_SCHEMA_SQL);
|
|
209
|
+
const insertRun = db.prepare(
|
|
210
|
+
`INSERT INTO runs (run_id, file, mtime_ms, name, status, started_at, ended_at, duration_ms, session_id, group_id, correlation_id)
|
|
211
|
+
VALUES (@runId, @file, @mtimeMs, @name, @status, @startedAt, @endedAt, @durationMs, @sessionId, @groupId, @correlationId)`
|
|
212
|
+
);
|
|
213
|
+
const insertStep = db.prepare(
|
|
214
|
+
`INSERT INTO steps (run_id, step_id, kind, name, status, duration_ms, tool_name, model, parent_id)
|
|
215
|
+
VALUES (@runId, @stepId, @kind, @name, @status, @durationMs, @toolName, @model, @parentId)`
|
|
216
|
+
);
|
|
217
|
+
const insertError = db.prepare(
|
|
218
|
+
`INSERT INTO errors (run_id, step_id, message, code) VALUES (@runId, @stepId, @message, @code)`
|
|
219
|
+
);
|
|
220
|
+
const insertMeta = db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)`);
|
|
221
|
+
const write = db.transaction((items) => {
|
|
222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
223
|
+
for (const item of items) {
|
|
224
|
+
if (seen.has(item.run.runId)) continue;
|
|
225
|
+
seen.add(item.run.runId);
|
|
226
|
+
insertRun.run(item.run);
|
|
227
|
+
runCount += 1;
|
|
228
|
+
for (const step of item.steps) {
|
|
229
|
+
insertStep.run(step);
|
|
230
|
+
stepCount += 1;
|
|
231
|
+
}
|
|
232
|
+
for (const err of item.errors) {
|
|
233
|
+
insertError.run({ runId: item.run.runId, ...err });
|
|
234
|
+
errorCount += 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
db.exec(DERIVE_SESSIONS_SQL);
|
|
238
|
+
for (const [key, value] of Object.entries(metaDefaults(traceDir, slice.length))) {
|
|
239
|
+
insertMeta.run(key, value);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
write(derived);
|
|
243
|
+
} finally {
|
|
244
|
+
db.close();
|
|
245
|
+
}
|
|
246
|
+
const builtAtRow = readMetaValue(dbPath, META_KEYS.builtAt);
|
|
247
|
+
return {
|
|
248
|
+
dbPath,
|
|
249
|
+
traceDir,
|
|
250
|
+
runs: runCount,
|
|
251
|
+
steps: stepCount,
|
|
252
|
+
errors: errorCount,
|
|
253
|
+
builtAt: builtAtRow ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
254
|
+
warnings
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
var rebuildIndex = buildIndex;
|
|
258
|
+
function readMetaValue(dbPath, key) {
|
|
259
|
+
try {
|
|
260
|
+
const db = new Database__default.default(dbPath, { readonly: true, fileMustExist: true });
|
|
261
|
+
try {
|
|
262
|
+
const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key);
|
|
263
|
+
return row?.value ?? null;
|
|
264
|
+
} finally {
|
|
265
|
+
db.close();
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async function cleanIndex(dbPath) {
|
|
272
|
+
await promises.rm(dbPath, { force: true });
|
|
273
|
+
await promises.rm(`${dbPath}-wal`, { force: true });
|
|
274
|
+
await promises.rm(`${dbPath}-shm`, { force: true });
|
|
275
|
+
}
|
|
276
|
+
function openHealthy(dbPath) {
|
|
277
|
+
if (!fs.existsSync(dbPath)) return null;
|
|
278
|
+
try {
|
|
279
|
+
const db = new Database__default.default(dbPath, { readonly: true, fileMustExist: true });
|
|
280
|
+
try {
|
|
281
|
+
const result = db.pragma("integrity_check", { simple: true });
|
|
282
|
+
if (result !== "ok") {
|
|
283
|
+
db.close();
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
db.prepare(`SELECT 1 FROM meta LIMIT 1`).get();
|
|
287
|
+
db.prepare(`SELECT 1 FROM runs LIMIT 1`).get();
|
|
288
|
+
return { db, healthy: true };
|
|
289
|
+
} catch {
|
|
290
|
+
db.close();
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function meta(db, key) {
|
|
298
|
+
const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key);
|
|
299
|
+
return row?.value ?? null;
|
|
300
|
+
}
|
|
301
|
+
function indexStatus(dbPath) {
|
|
302
|
+
const opened = openHealthy(dbPath);
|
|
303
|
+
if (!opened) {
|
|
304
|
+
return {
|
|
305
|
+
dbPath,
|
|
306
|
+
exists: fs.existsSync(dbPath),
|
|
307
|
+
healthy: false,
|
|
308
|
+
builtAt: null,
|
|
309
|
+
sourceDir: null,
|
|
310
|
+
schemaVersion: null,
|
|
311
|
+
runs: 0,
|
|
312
|
+
steps: 0
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const { db } = opened;
|
|
316
|
+
try {
|
|
317
|
+
const runs = db.prepare(`SELECT COUNT(*) AS c FROM runs`).get().c;
|
|
318
|
+
const steps = db.prepare(`SELECT COUNT(*) AS c FROM steps`).get().c;
|
|
319
|
+
return {
|
|
320
|
+
dbPath,
|
|
321
|
+
exists: true,
|
|
322
|
+
healthy: true,
|
|
323
|
+
builtAt: meta(db, META_KEYS.builtAt),
|
|
324
|
+
sourceDir: meta(db, META_KEYS.sourceDir),
|
|
325
|
+
schemaVersion: meta(db, META_KEYS.schemaVersion),
|
|
326
|
+
runs,
|
|
327
|
+
steps
|
|
328
|
+
};
|
|
329
|
+
} finally {
|
|
330
|
+
db.close();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function isIndexStale(dbPath, newestTraceMtimeMs) {
|
|
334
|
+
const opened = openHealthy(dbPath);
|
|
335
|
+
if (!opened) return true;
|
|
336
|
+
try {
|
|
337
|
+
const builtAt = meta(opened.db, META_KEYS.builtAt);
|
|
338
|
+
if (!builtAt) return true;
|
|
339
|
+
const builtMs = Date.parse(builtAt);
|
|
340
|
+
if (Number.isNaN(builtMs)) return true;
|
|
341
|
+
return newestTraceMtimeMs > builtMs;
|
|
342
|
+
} finally {
|
|
343
|
+
opened.db.close();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function mapRow(row) {
|
|
347
|
+
return {
|
|
348
|
+
runId: row.run_id,
|
|
349
|
+
file: row.file,
|
|
350
|
+
mtimeMs: row.mtime_ms,
|
|
351
|
+
name: row.name ?? null,
|
|
352
|
+
status: row.status ?? null,
|
|
353
|
+
startedAt: row.started_at ?? null,
|
|
354
|
+
endedAt: row.ended_at ?? null,
|
|
355
|
+
durationMs: row.duration_ms ?? null,
|
|
356
|
+
sessionId: row.session_id ?? null,
|
|
357
|
+
groupId: row.group_id ?? null,
|
|
358
|
+
correlationId: row.correlation_id ?? null
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function queryRuns(dbPath, query = {}) {
|
|
362
|
+
const opened = openHealthy(dbPath);
|
|
363
|
+
if (!opened) return [];
|
|
364
|
+
const { db } = opened;
|
|
365
|
+
try {
|
|
366
|
+
const where = [];
|
|
367
|
+
const params = {};
|
|
368
|
+
if (query.status) {
|
|
369
|
+
where.push(`r.status = @status`);
|
|
370
|
+
params.status = query.status;
|
|
371
|
+
}
|
|
372
|
+
if (query.sessionId) {
|
|
373
|
+
where.push(`r.session_id = @sessionId`);
|
|
374
|
+
params.sessionId = query.sessionId;
|
|
375
|
+
}
|
|
376
|
+
if (query.name) {
|
|
377
|
+
where.push(`LOWER(r.name) LIKE @name`);
|
|
378
|
+
params.name = `%${query.name.toLowerCase()}%`;
|
|
379
|
+
}
|
|
380
|
+
if (query.kind) {
|
|
381
|
+
where.push(`EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND s.kind = @kind)`);
|
|
382
|
+
params.kind = query.kind;
|
|
383
|
+
}
|
|
384
|
+
if (query.tool) {
|
|
385
|
+
where.push(
|
|
386
|
+
`EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND LOWER(s.tool_name) LIKE @tool)`
|
|
387
|
+
);
|
|
388
|
+
params.tool = `%${query.tool.toLowerCase()}%`;
|
|
389
|
+
}
|
|
390
|
+
const limit = Number.isInteger(query.limit) && query.limit > 0 ? query.limit : 100;
|
|
391
|
+
const sql = `SELECT r.* FROM runs r ${where.length ? `WHERE ${where.join(" AND ")}` : ""} ORDER BY r.started_at DESC LIMIT ${limit}`;
|
|
392
|
+
const rows = db.prepare(sql).all(params);
|
|
393
|
+
return rows.map(mapRow);
|
|
394
|
+
} finally {
|
|
395
|
+
db.close();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
exports.INDEX_DB_FILENAME = INDEX_DB_FILENAME;
|
|
400
|
+
exports.INDEX_SCHEMA_VERSION = INDEX_SCHEMA_VERSION;
|
|
401
|
+
exports.buildIndex = buildIndex;
|
|
402
|
+
exports.cleanIndex = cleanIndex;
|
|
403
|
+
exports.indexStatus = indexStatus;
|
|
404
|
+
exports.isIndexStale = isIndexStale;
|
|
405
|
+
exports.queryRuns = queryRuns;
|
|
406
|
+
exports.rebuildIndex = rebuildIndex;
|
|
407
|
+
exports.resolveIndexDbPath = resolveIndexDbPath;
|
|
408
|
+
//# sourceMappingURL=index.cjs.map
|
|
409
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/schema.ts","../src/builder.ts","../src/query.ts"],"names":["path","meta","resolveTraceDir","TraceDirectory","readFile","parseTraceJsonl","validateEvent","mkdir","rm","Database","existsSync"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Public types for the optional local SQLite trace index (v4.1, experimental).\n *\n * @remarks\n * Local-only. The index is derived from JSONL traces and is always safe to\n * delete. Trace files are never mutated. No network access.\n */\n\n/** Current index schema version. Bumped when table layout changes. */\nexport const INDEX_SCHEMA_VERSION = \"1\" as const;\n\n/** Default index database filename. */\nexport const INDEX_DB_FILENAME = \"trace-index.sqlite\";\n\n/** A derived run row in the index. */\nexport interface IndexedRun {\n runId: string;\n file: string;\n mtimeMs: number;\n name: string | null;\n status: string | null;\n startedAt: number | null;\n endedAt: number | null;\n durationMs: number | null;\n sessionId: string | null;\n groupId: string | null;\n correlationId: string | null;\n}\n\n/** A derived step row in the index. */\nexport interface IndexedStep {\n runId: string;\n stepId: string;\n kind: string | null;\n name: string | null;\n status: string | null;\n durationMs: number | null;\n toolName: string | null;\n model: string | null;\n parentId: string | null;\n}\n\n/** Options for building/rebuilding the index. */\nexport interface BuildIndexOptions {\n /** Trace directory to index. Resolved via core `resolveTraceDir` when omitted. */\n traceDir?: string;\n /** Index database path. Defaults to `<traceDir>/<INDEX_DB_FILENAME>`. */\n dbPath?: string;\n /** Cap on the number of trace files indexed (default 10000). */\n maxRuns?: number;\n}\n\n/** Result of a build/rebuild. */\nexport interface BuildIndexResult {\n dbPath: string;\n traceDir: string;\n runs: number;\n steps: number;\n errors: number;\n builtAt: string;\n warnings: string[];\n}\n\n/** Status of an existing index. */\nexport interface IndexStatus {\n dbPath: string;\n exists: boolean;\n healthy: boolean;\n builtAt: string | null;\n sourceDir: string | null;\n schemaVersion: string | null;\n runs: number;\n steps: number;\n}\n\n/** Filter for querying indexed runs. */\nexport interface RunQuery {\n status?: string;\n sessionId?: string;\n /** Case-insensitive substring match on run name. */\n name?: string;\n /** Match runs that contain a step of this kind. */\n kind?: string;\n /** Match runs that contain a step with this tool name (substring). */\n tool?: string;\n limit?: number;\n}\n","import { INDEX_SCHEMA_VERSION } from \"./types.js\";\n\n/**\n * Idempotent DDL for the local trace index. A full rebuild drops and recreates\n * all tables, so building twice from the same inputs yields identical contents.\n */\nexport const INDEX_SCHEMA_SQL = `\nDROP TABLE IF EXISTS meta;\nDROP TABLE IF EXISTS errors;\nDROP TABLE IF EXISTS steps;\nDROP TABLE IF EXISTS sessions;\nDROP TABLE IF EXISTS runs;\n\nCREATE TABLE meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\nCREATE TABLE runs (\n run_id TEXT PRIMARY KEY,\n file TEXT NOT NULL,\n mtime_ms REAL NOT NULL,\n name TEXT,\n status TEXT,\n started_at REAL,\n ended_at REAL,\n duration_ms REAL,\n session_id TEXT,\n group_id TEXT,\n correlation_id TEXT\n);\n\nCREATE TABLE steps (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n step_id TEXT NOT NULL,\n kind TEXT,\n name TEXT,\n status TEXT,\n duration_ms REAL,\n tool_name TEXT,\n model TEXT,\n parent_id TEXT\n);\n\nCREATE TABLE errors (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n step_id TEXT,\n message TEXT,\n code TEXT\n);\n\nCREATE TABLE sessions (\n session_id TEXT PRIMARY KEY,\n run_count INTEGER NOT NULL,\n first_started_at REAL,\n last_ended_at REAL\n);\n\nCREATE INDEX idx_runs_status ON runs(status);\nCREATE INDEX idx_runs_session ON runs(session_id);\nCREATE INDEX idx_runs_started ON runs(started_at);\nCREATE INDEX idx_steps_run ON steps(run_id);\nCREATE INDEX idx_steps_kind ON steps(kind);\nCREATE INDEX idx_steps_tool ON steps(tool_name);\n`;\n\n/** SQL to derive the sessions table from indexed runs. */\nexport const DERIVE_SESSIONS_SQL = `\nINSERT INTO sessions (session_id, run_count, first_started_at, last_ended_at)\nSELECT session_id, COUNT(*), MIN(started_at), MAX(ended_at)\nFROM runs\nWHERE session_id IS NOT NULL\nGROUP BY session_id;\n`;\n\nexport const META_KEYS = {\n schemaVersion: \"schemaVersion\",\n builtAt: \"builtAt\",\n sourceDir: \"sourceDir\",\n fileCount: \"fileCount\",\n driver: \"driver\",\n} as const;\n\nexport function metaDefaults(sourceDir: string, fileCount: number) {\n return {\n [META_KEYS.schemaVersion]: INDEX_SCHEMA_VERSION,\n [META_KEYS.builtAt]: new Date().toISOString(),\n [META_KEYS.sourceDir]: sourceDir,\n [META_KEYS.fileCount]: String(fileCount),\n [META_KEYS.driver]: \"better-sqlite3\",\n } as Record<string, string>;\n}\n","import { mkdir, readFile, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport Database from \"better-sqlite3\";\nimport {\n TraceDirectory,\n parseTraceJsonl,\n resolveTraceDir,\n validateEvent,\n} from \"agent-inspect/advanced\";\nimport type { TraceEvent } from \"agent-inspect/advanced\";\n\nimport {\n DERIVE_SESSIONS_SQL,\n INDEX_SCHEMA_SQL,\n META_KEYS,\n metaDefaults,\n} from \"./schema.js\";\nimport {\n INDEX_DB_FILENAME,\n type BuildIndexOptions,\n type BuildIndexResult,\n type IndexedRun,\n type IndexedStep,\n} from \"./types.js\";\n\nconst DEFAULT_MAX_RUNS = 10_000;\n\n/** Resolves the index database path for a trace directory. */\nexport function resolveIndexDbPath(traceDir: string, dbPath?: string): string {\n if (dbPath && dbPath.trim() !== \"\") return path.resolve(dbPath);\n return path.join(path.resolve(traceDir), INDEX_DB_FILENAME);\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === \"string\" && value !== \"\" ? value : null;\n}\n\nfunction num(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\ninterface DerivedRun {\n run: IndexedRun;\n steps: IndexedStep[];\n errors: Array<{ stepId: string | null; message: string | null; code: string | null }>;\n}\n\nfunction deriveRun(file: string, mtimeMs: number, events: TraceEvent[]): DerivedRun | null {\n const started = events.find((e) => e.event === \"run_started\");\n if (!started || started.event !== \"run_started\") return null;\n\n const completed = events.find((e) => e.event === \"run_completed\");\n const metadata = (started.metadata ?? {}) as Record<string, unknown>;\n\n const run: IndexedRun = {\n runId: started.runId,\n file,\n mtimeMs,\n name: str(started.name),\n status: completed && completed.event === \"run_completed\" ? completed.status : null,\n startedAt: num(started.startTime),\n endedAt: completed && completed.event === \"run_completed\" ? num(completed.endTime) : null,\n durationMs:\n completed && completed.event === \"run_completed\" ? num(completed.durationMs) : null,\n sessionId: str(metadata.sessionId),\n groupId: str(metadata.groupId),\n correlationId: str(metadata.correlationId),\n };\n\n const stepStarts = new Map<string, Extract<TraceEvent, { event: \"step_started\" }>>();\n const steps: IndexedStep[] = [];\n const errors: DerivedRun[\"errors\"] = [];\n\n if (completed && completed.event === \"run_completed\" && completed.error) {\n errors.push({\n stepId: null,\n message: str(completed.error.message),\n code: str((completed.error as { code?: unknown }).code),\n });\n }\n\n for (const event of events) {\n if (event.event === \"step_started\") {\n stepStarts.set(event.stepId, event);\n }\n }\n\n for (const event of events) {\n if (event.event !== \"step_completed\") continue;\n const start = stepStarts.get(event.stepId);\n const meta = (start?.metadata ?? {}) as Record<string, unknown>;\n steps.push({\n runId: run.runId,\n stepId: event.stepId,\n kind: start ? str(start.type) : null,\n name: start ? str(start.name) : null,\n status: event.status,\n durationMs: num(event.durationMs),\n toolName: str(meta.toolName),\n model: str(meta.model),\n parentId: start ? str(start.parentId) : null,\n });\n if (event.error) {\n errors.push({\n stepId: event.stepId,\n message: str(event.error.message),\n code: str((event.error as { code?: unknown }).code),\n });\n }\n }\n\n return { run, steps, errors };\n}\n\n/**\n * Builds (or fully rebuilds) the local SQLite index from a trace directory.\n * Idempotent: rebuilding from identical inputs yields identical contents.\n * Never mutates trace files.\n */\nexport async function buildIndex(\n options: BuildIndexOptions = {},\n): Promise<BuildIndexResult> {\n const traceDir = resolveTraceDir({ dir: options.traceDir });\n const dbPath = resolveIndexDbPath(traceDir, options.dbPath);\n const maxRuns = options.maxRuns ?? DEFAULT_MAX_RUNS;\n const warnings: string[] = [];\n\n const td = new TraceDirectory({ dir: traceDir });\n const files = await td.list();\n if (files.length > maxRuns) {\n warnings.push(\n `index.truncated: ${files.length} trace files present; indexing first ${maxRuns}`,\n );\n }\n const slice = files.slice(0, maxRuns);\n\n const derived: DerivedRun[] = [];\n for (const file of slice) {\n try {\n const raw = await readFile(td.getPath(file), \"utf-8\");\n const parsed = parseTraceJsonl(raw, { validate: validateEvent });\n const stats = await td.getFileStats(file);\n const one = deriveRun(file, stats.mtimeMs, parsed.events);\n if (one) derived.push(one);\n else warnings.push(`index.skipped: ${file} has no run_started event`);\n } catch {\n warnings.push(`index.unreadable: ${file}`);\n }\n }\n\n await mkdir(path.dirname(dbPath), { recursive: true });\n // Remove any prior (possibly corrupt) file so the rebuild is deterministic.\n await rm(dbPath, { force: true });\n\n const db = new Database(dbPath);\n let runCount = 0;\n let stepCount = 0;\n let errorCount = 0;\n try {\n db.pragma(\"journal_mode = WAL\");\n db.exec(INDEX_SCHEMA_SQL);\n\n const insertRun = db.prepare(\n `INSERT INTO runs (run_id, file, mtime_ms, name, status, started_at, ended_at, duration_ms, session_id, group_id, correlation_id)\n VALUES (@runId, @file, @mtimeMs, @name, @status, @startedAt, @endedAt, @durationMs, @sessionId, @groupId, @correlationId)`,\n );\n const insertStep = db.prepare(\n `INSERT INTO steps (run_id, step_id, kind, name, status, duration_ms, tool_name, model, parent_id)\n VALUES (@runId, @stepId, @kind, @name, @status, @durationMs, @toolName, @model, @parentId)`,\n );\n const insertError = db.prepare(\n `INSERT INTO errors (run_id, step_id, message, code) VALUES (@runId, @stepId, @message, @code)`,\n );\n const insertMeta = db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)`);\n\n const write = db.transaction((items: DerivedRun[]) => {\n const seen = new Set<string>();\n for (const item of items) {\n if (seen.has(item.run.runId)) continue;\n seen.add(item.run.runId);\n insertRun.run(item.run);\n runCount += 1;\n for (const step of item.steps) {\n insertStep.run(step);\n stepCount += 1;\n }\n for (const err of item.errors) {\n insertError.run({ runId: item.run.runId, ...err });\n errorCount += 1;\n }\n }\n db.exec(DERIVE_SESSIONS_SQL);\n for (const [key, value] of Object.entries(metaDefaults(traceDir, slice.length))) {\n insertMeta.run(key, value);\n }\n });\n write(derived);\n } finally {\n db.close();\n }\n\n const builtAtRow = readMetaValue(dbPath, META_KEYS.builtAt);\n\n return {\n dbPath,\n traceDir,\n runs: runCount,\n steps: stepCount,\n errors: errorCount,\n builtAt: builtAtRow ?? new Date().toISOString(),\n warnings,\n };\n}\n\n/** Alias for {@link buildIndex}; a rebuild is a full, idempotent build. */\nexport const rebuildIndex = buildIndex;\n\nfunction readMetaValue(dbPath: string, key: string): string | null {\n try {\n const db = new Database(dbPath, { readonly: true, fileMustExist: true });\n try {\n const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key) as\n | { value: string }\n | undefined;\n return row?.value ?? null;\n } finally {\n db.close();\n }\n } catch {\n return null;\n }\n}\n\n/** Deletes the index database file. Always safe; traces are unaffected. */\nexport async function cleanIndex(dbPath: string): Promise<void> {\n await rm(dbPath, { force: true });\n await rm(`${dbPath}-wal`, { force: true });\n await rm(`${dbPath}-shm`, { force: true });\n}\n","import { existsSync } from \"node:fs\";\n\nimport Database from \"better-sqlite3\";\n\nimport { META_KEYS } from \"./schema.js\";\nimport type { IndexStatus, IndexedRun, RunQuery } from \"./types.js\";\n\ninterface OpenResult {\n db: Database.Database;\n healthy: boolean;\n}\n\n/**\n * Opens the index read-only and verifies integrity. Returns `null` when the\n * file is missing or fails `integrity_check` (corruption), so callers can fall\n * back to a full rebuild or a directory scan.\n */\nfunction openHealthy(dbPath: string): OpenResult | null {\n if (!existsSync(dbPath)) return null;\n try {\n const db = new Database(dbPath, { readonly: true, fileMustExist: true });\n try {\n const result = db.pragma(\"integrity_check\", { simple: true });\n if (result !== \"ok\") {\n db.close();\n return null;\n }\n // Confirm the expected schema is present.\n db.prepare(`SELECT 1 FROM meta LIMIT 1`).get();\n db.prepare(`SELECT 1 FROM runs LIMIT 1`).get();\n return { db, healthy: true };\n } catch {\n db.close();\n return null;\n }\n } catch {\n return null;\n }\n}\n\nfunction meta(db: Database.Database, key: string): string | null {\n const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key) as\n | { value: string }\n | undefined;\n return row?.value ?? null;\n}\n\n/** Reports index presence, health, and basic counts. Never throws. */\nexport function indexStatus(dbPath: string): IndexStatus {\n const opened = openHealthy(dbPath);\n if (!opened) {\n return {\n dbPath,\n exists: existsSync(dbPath),\n healthy: false,\n builtAt: null,\n sourceDir: null,\n schemaVersion: null,\n runs: 0,\n steps: 0,\n };\n }\n const { db } = opened;\n try {\n const runs = (db.prepare(`SELECT COUNT(*) AS c FROM runs`).get() as { c: number }).c;\n const steps = (db.prepare(`SELECT COUNT(*) AS c FROM steps`).get() as { c: number }).c;\n return {\n dbPath,\n exists: true,\n healthy: true,\n builtAt: meta(db, META_KEYS.builtAt),\n sourceDir: meta(db, META_KEYS.sourceDir),\n schemaVersion: meta(db, META_KEYS.schemaVersion),\n runs,\n steps,\n };\n } finally {\n db.close();\n }\n}\n\n/** Returns true when the index is missing, corrupt, or older than any trace. */\nexport function isIndexStale(dbPath: string, newestTraceMtimeMs: number): boolean {\n const opened = openHealthy(dbPath);\n if (!opened) return true;\n try {\n const builtAt = meta(opened.db, META_KEYS.builtAt);\n if (!builtAt) return true;\n const builtMs = Date.parse(builtAt);\n if (Number.isNaN(builtMs)) return true;\n return newestTraceMtimeMs > builtMs;\n } finally {\n opened.db.close();\n }\n}\n\nfunction mapRow(row: Record<string, unknown>): IndexedRun {\n return {\n runId: row.run_id as string,\n file: row.file as string,\n mtimeMs: row.mtime_ms as number,\n name: (row.name as string | null) ?? null,\n status: (row.status as string | null) ?? null,\n startedAt: (row.started_at as number | null) ?? null,\n endedAt: (row.ended_at as number | null) ?? null,\n durationMs: (row.duration_ms as number | null) ?? null,\n sessionId: (row.session_id as string | null) ?? null,\n groupId: (row.group_id as string | null) ?? null,\n correlationId: (row.correlation_id as string | null) ?? null,\n };\n}\n\n/**\n * Queries indexed runs. Returns an empty array when the index is missing or\n * corrupt (the caller should fall back to a directory scan).\n */\nexport function queryRuns(dbPath: string, query: RunQuery = {}): IndexedRun[] {\n const opened = openHealthy(dbPath);\n if (!opened) return [];\n const { db } = opened;\n try {\n const where: string[] = [];\n const params: Record<string, unknown> = {};\n\n if (query.status) {\n where.push(`r.status = @status`);\n params.status = query.status;\n }\n if (query.sessionId) {\n where.push(`r.session_id = @sessionId`);\n params.sessionId = query.sessionId;\n }\n if (query.name) {\n where.push(`LOWER(r.name) LIKE @name`);\n params.name = `%${query.name.toLowerCase()}%`;\n }\n if (query.kind) {\n where.push(`EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND s.kind = @kind)`);\n params.kind = query.kind;\n }\n if (query.tool) {\n where.push(\n `EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND LOWER(s.tool_name) LIKE @tool)`,\n );\n params.tool = `%${query.tool.toLowerCase()}%`;\n }\n\n const limit = Number.isInteger(query.limit) && query.limit! > 0 ? query.limit! : 100;\n const sql = `SELECT r.* FROM runs r ${\n where.length ? `WHERE ${where.join(\" AND \")}` : \"\"\n } ORDER BY r.started_at DESC LIMIT ${limit}`;\n\n const rows = db.prepare(sql).all(params) as Array<Record<string, unknown>>;\n return rows.map(mapRow);\n } finally {\n db.close();\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the optional local SQLite trace index (v4.1, experimental).
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Local-only. The index is derived from JSONL traces and is always safe to
|
|
6
|
+
* delete. Trace files are never mutated. No network access.
|
|
7
|
+
*/
|
|
8
|
+
/** Current index schema version. Bumped when table layout changes. */
|
|
9
|
+
declare const INDEX_SCHEMA_VERSION: "1";
|
|
10
|
+
/** Default index database filename. */
|
|
11
|
+
declare const INDEX_DB_FILENAME = "trace-index.sqlite";
|
|
12
|
+
/** A derived run row in the index. */
|
|
13
|
+
interface IndexedRun {
|
|
14
|
+
runId: string;
|
|
15
|
+
file: string;
|
|
16
|
+
mtimeMs: number;
|
|
17
|
+
name: string | null;
|
|
18
|
+
status: string | null;
|
|
19
|
+
startedAt: number | null;
|
|
20
|
+
endedAt: number | null;
|
|
21
|
+
durationMs: number | null;
|
|
22
|
+
sessionId: string | null;
|
|
23
|
+
groupId: string | null;
|
|
24
|
+
correlationId: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** A derived step row in the index. */
|
|
27
|
+
interface IndexedStep {
|
|
28
|
+
runId: string;
|
|
29
|
+
stepId: string;
|
|
30
|
+
kind: string | null;
|
|
31
|
+
name: string | null;
|
|
32
|
+
status: string | null;
|
|
33
|
+
durationMs: number | null;
|
|
34
|
+
toolName: string | null;
|
|
35
|
+
model: string | null;
|
|
36
|
+
parentId: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** Options for building/rebuilding the index. */
|
|
39
|
+
interface BuildIndexOptions {
|
|
40
|
+
/** Trace directory to index. Resolved via core `resolveTraceDir` when omitted. */
|
|
41
|
+
traceDir?: string;
|
|
42
|
+
/** Index database path. Defaults to `<traceDir>/<INDEX_DB_FILENAME>`. */
|
|
43
|
+
dbPath?: string;
|
|
44
|
+
/** Cap on the number of trace files indexed (default 10000). */
|
|
45
|
+
maxRuns?: number;
|
|
46
|
+
}
|
|
47
|
+
/** Result of a build/rebuild. */
|
|
48
|
+
interface BuildIndexResult {
|
|
49
|
+
dbPath: string;
|
|
50
|
+
traceDir: string;
|
|
51
|
+
runs: number;
|
|
52
|
+
steps: number;
|
|
53
|
+
errors: number;
|
|
54
|
+
builtAt: string;
|
|
55
|
+
warnings: string[];
|
|
56
|
+
}
|
|
57
|
+
/** Status of an existing index. */
|
|
58
|
+
interface IndexStatus {
|
|
59
|
+
dbPath: string;
|
|
60
|
+
exists: boolean;
|
|
61
|
+
healthy: boolean;
|
|
62
|
+
builtAt: string | null;
|
|
63
|
+
sourceDir: string | null;
|
|
64
|
+
schemaVersion: string | null;
|
|
65
|
+
runs: number;
|
|
66
|
+
steps: number;
|
|
67
|
+
}
|
|
68
|
+
/** Filter for querying indexed runs. */
|
|
69
|
+
interface RunQuery {
|
|
70
|
+
status?: string;
|
|
71
|
+
sessionId?: string;
|
|
72
|
+
/** Case-insensitive substring match on run name. */
|
|
73
|
+
name?: string;
|
|
74
|
+
/** Match runs that contain a step of this kind. */
|
|
75
|
+
kind?: string;
|
|
76
|
+
/** Match runs that contain a step with this tool name (substring). */
|
|
77
|
+
tool?: string;
|
|
78
|
+
limit?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Resolves the index database path for a trace directory. */
|
|
82
|
+
declare function resolveIndexDbPath(traceDir: string, dbPath?: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Builds (or fully rebuilds) the local SQLite index from a trace directory.
|
|
85
|
+
* Idempotent: rebuilding from identical inputs yields identical contents.
|
|
86
|
+
* Never mutates trace files.
|
|
87
|
+
*/
|
|
88
|
+
declare function buildIndex(options?: BuildIndexOptions): Promise<BuildIndexResult>;
|
|
89
|
+
/** Alias for {@link buildIndex}; a rebuild is a full, idempotent build. */
|
|
90
|
+
declare const rebuildIndex: typeof buildIndex;
|
|
91
|
+
/** Deletes the index database file. Always safe; traces are unaffected. */
|
|
92
|
+
declare function cleanIndex(dbPath: string): Promise<void>;
|
|
93
|
+
|
|
94
|
+
/** Reports index presence, health, and basic counts. Never throws. */
|
|
95
|
+
declare function indexStatus(dbPath: string): IndexStatus;
|
|
96
|
+
/** Returns true when the index is missing, corrupt, or older than any trace. */
|
|
97
|
+
declare function isIndexStale(dbPath: string, newestTraceMtimeMs: number): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Queries indexed runs. Returns an empty array when the index is missing or
|
|
100
|
+
* corrupt (the caller should fall back to a directory scan).
|
|
101
|
+
*/
|
|
102
|
+
declare function queryRuns(dbPath: string, query?: RunQuery): IndexedRun[];
|
|
103
|
+
|
|
104
|
+
export { type BuildIndexOptions, type BuildIndexResult, INDEX_DB_FILENAME, INDEX_SCHEMA_VERSION, type IndexStatus, type IndexedRun, type IndexedStep, type RunQuery, buildIndex, cleanIndex, indexStatus, isIndexStale, queryRuns, rebuildIndex, resolveIndexDbPath };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the optional local SQLite trace index (v4.1, experimental).
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Local-only. The index is derived from JSONL traces and is always safe to
|
|
6
|
+
* delete. Trace files are never mutated. No network access.
|
|
7
|
+
*/
|
|
8
|
+
/** Current index schema version. Bumped when table layout changes. */
|
|
9
|
+
declare const INDEX_SCHEMA_VERSION: "1";
|
|
10
|
+
/** Default index database filename. */
|
|
11
|
+
declare const INDEX_DB_FILENAME = "trace-index.sqlite";
|
|
12
|
+
/** A derived run row in the index. */
|
|
13
|
+
interface IndexedRun {
|
|
14
|
+
runId: string;
|
|
15
|
+
file: string;
|
|
16
|
+
mtimeMs: number;
|
|
17
|
+
name: string | null;
|
|
18
|
+
status: string | null;
|
|
19
|
+
startedAt: number | null;
|
|
20
|
+
endedAt: number | null;
|
|
21
|
+
durationMs: number | null;
|
|
22
|
+
sessionId: string | null;
|
|
23
|
+
groupId: string | null;
|
|
24
|
+
correlationId: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** A derived step row in the index. */
|
|
27
|
+
interface IndexedStep {
|
|
28
|
+
runId: string;
|
|
29
|
+
stepId: string;
|
|
30
|
+
kind: string | null;
|
|
31
|
+
name: string | null;
|
|
32
|
+
status: string | null;
|
|
33
|
+
durationMs: number | null;
|
|
34
|
+
toolName: string | null;
|
|
35
|
+
model: string | null;
|
|
36
|
+
parentId: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** Options for building/rebuilding the index. */
|
|
39
|
+
interface BuildIndexOptions {
|
|
40
|
+
/** Trace directory to index. Resolved via core `resolveTraceDir` when omitted. */
|
|
41
|
+
traceDir?: string;
|
|
42
|
+
/** Index database path. Defaults to `<traceDir>/<INDEX_DB_FILENAME>`. */
|
|
43
|
+
dbPath?: string;
|
|
44
|
+
/** Cap on the number of trace files indexed (default 10000). */
|
|
45
|
+
maxRuns?: number;
|
|
46
|
+
}
|
|
47
|
+
/** Result of a build/rebuild. */
|
|
48
|
+
interface BuildIndexResult {
|
|
49
|
+
dbPath: string;
|
|
50
|
+
traceDir: string;
|
|
51
|
+
runs: number;
|
|
52
|
+
steps: number;
|
|
53
|
+
errors: number;
|
|
54
|
+
builtAt: string;
|
|
55
|
+
warnings: string[];
|
|
56
|
+
}
|
|
57
|
+
/** Status of an existing index. */
|
|
58
|
+
interface IndexStatus {
|
|
59
|
+
dbPath: string;
|
|
60
|
+
exists: boolean;
|
|
61
|
+
healthy: boolean;
|
|
62
|
+
builtAt: string | null;
|
|
63
|
+
sourceDir: string | null;
|
|
64
|
+
schemaVersion: string | null;
|
|
65
|
+
runs: number;
|
|
66
|
+
steps: number;
|
|
67
|
+
}
|
|
68
|
+
/** Filter for querying indexed runs. */
|
|
69
|
+
interface RunQuery {
|
|
70
|
+
status?: string;
|
|
71
|
+
sessionId?: string;
|
|
72
|
+
/** Case-insensitive substring match on run name. */
|
|
73
|
+
name?: string;
|
|
74
|
+
/** Match runs that contain a step of this kind. */
|
|
75
|
+
kind?: string;
|
|
76
|
+
/** Match runs that contain a step with this tool name (substring). */
|
|
77
|
+
tool?: string;
|
|
78
|
+
limit?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Resolves the index database path for a trace directory. */
|
|
82
|
+
declare function resolveIndexDbPath(traceDir: string, dbPath?: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Builds (or fully rebuilds) the local SQLite index from a trace directory.
|
|
85
|
+
* Idempotent: rebuilding from identical inputs yields identical contents.
|
|
86
|
+
* Never mutates trace files.
|
|
87
|
+
*/
|
|
88
|
+
declare function buildIndex(options?: BuildIndexOptions): Promise<BuildIndexResult>;
|
|
89
|
+
/** Alias for {@link buildIndex}; a rebuild is a full, idempotent build. */
|
|
90
|
+
declare const rebuildIndex: typeof buildIndex;
|
|
91
|
+
/** Deletes the index database file. Always safe; traces are unaffected. */
|
|
92
|
+
declare function cleanIndex(dbPath: string): Promise<void>;
|
|
93
|
+
|
|
94
|
+
/** Reports index presence, health, and basic counts. Never throws. */
|
|
95
|
+
declare function indexStatus(dbPath: string): IndexStatus;
|
|
96
|
+
/** Returns true when the index is missing, corrupt, or older than any trace. */
|
|
97
|
+
declare function isIndexStale(dbPath: string, newestTraceMtimeMs: number): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Queries indexed runs. Returns an empty array when the index is missing or
|
|
100
|
+
* corrupt (the caller should fall back to a directory scan).
|
|
101
|
+
*/
|
|
102
|
+
declare function queryRuns(dbPath: string, query?: RunQuery): IndexedRun[];
|
|
103
|
+
|
|
104
|
+
export { type BuildIndexOptions, type BuildIndexResult, INDEX_DB_FILENAME, INDEX_SCHEMA_VERSION, type IndexStatus, type IndexedRun, type IndexedStep, type RunQuery, buildIndex, cleanIndex, indexStatus, isIndexStale, queryRuns, rebuildIndex, resolveIndexDbPath };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { readFile, mkdir, rm } from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import Database from 'better-sqlite3';
|
|
4
|
+
import { resolveTraceDir, TraceDirectory, parseTraceJsonl, validateEvent } from 'agent-inspect/advanced';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
|
|
7
|
+
// packages/index-sqlite/src/types.ts
|
|
8
|
+
var INDEX_SCHEMA_VERSION = "1";
|
|
9
|
+
var INDEX_DB_FILENAME = "trace-index.sqlite";
|
|
10
|
+
|
|
11
|
+
// packages/index-sqlite/src/schema.ts
|
|
12
|
+
var INDEX_SCHEMA_SQL = `
|
|
13
|
+
DROP TABLE IF EXISTS meta;
|
|
14
|
+
DROP TABLE IF EXISTS errors;
|
|
15
|
+
DROP TABLE IF EXISTS steps;
|
|
16
|
+
DROP TABLE IF EXISTS sessions;
|
|
17
|
+
DROP TABLE IF EXISTS runs;
|
|
18
|
+
|
|
19
|
+
CREATE TABLE meta (
|
|
20
|
+
key TEXT PRIMARY KEY,
|
|
21
|
+
value TEXT
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE TABLE runs (
|
|
25
|
+
run_id TEXT PRIMARY KEY,
|
|
26
|
+
file TEXT NOT NULL,
|
|
27
|
+
mtime_ms REAL NOT NULL,
|
|
28
|
+
name TEXT,
|
|
29
|
+
status TEXT,
|
|
30
|
+
started_at REAL,
|
|
31
|
+
ended_at REAL,
|
|
32
|
+
duration_ms REAL,
|
|
33
|
+
session_id TEXT,
|
|
34
|
+
group_id TEXT,
|
|
35
|
+
correlation_id TEXT
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE steps (
|
|
39
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
+
run_id TEXT NOT NULL,
|
|
41
|
+
step_id TEXT NOT NULL,
|
|
42
|
+
kind TEXT,
|
|
43
|
+
name TEXT,
|
|
44
|
+
status TEXT,
|
|
45
|
+
duration_ms REAL,
|
|
46
|
+
tool_name TEXT,
|
|
47
|
+
model TEXT,
|
|
48
|
+
parent_id TEXT
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE errors (
|
|
52
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
53
|
+
run_id TEXT NOT NULL,
|
|
54
|
+
step_id TEXT,
|
|
55
|
+
message TEXT,
|
|
56
|
+
code TEXT
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE sessions (
|
|
60
|
+
session_id TEXT PRIMARY KEY,
|
|
61
|
+
run_count INTEGER NOT NULL,
|
|
62
|
+
first_started_at REAL,
|
|
63
|
+
last_ended_at REAL
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
CREATE INDEX idx_runs_status ON runs(status);
|
|
67
|
+
CREATE INDEX idx_runs_session ON runs(session_id);
|
|
68
|
+
CREATE INDEX idx_runs_started ON runs(started_at);
|
|
69
|
+
CREATE INDEX idx_steps_run ON steps(run_id);
|
|
70
|
+
CREATE INDEX idx_steps_kind ON steps(kind);
|
|
71
|
+
CREATE INDEX idx_steps_tool ON steps(tool_name);
|
|
72
|
+
`;
|
|
73
|
+
var DERIVE_SESSIONS_SQL = `
|
|
74
|
+
INSERT INTO sessions (session_id, run_count, first_started_at, last_ended_at)
|
|
75
|
+
SELECT session_id, COUNT(*), MIN(started_at), MAX(ended_at)
|
|
76
|
+
FROM runs
|
|
77
|
+
WHERE session_id IS NOT NULL
|
|
78
|
+
GROUP BY session_id;
|
|
79
|
+
`;
|
|
80
|
+
var META_KEYS = {
|
|
81
|
+
schemaVersion: "schemaVersion",
|
|
82
|
+
builtAt: "builtAt",
|
|
83
|
+
sourceDir: "sourceDir",
|
|
84
|
+
fileCount: "fileCount",
|
|
85
|
+
driver: "driver"
|
|
86
|
+
};
|
|
87
|
+
function metaDefaults(sourceDir, fileCount) {
|
|
88
|
+
return {
|
|
89
|
+
[META_KEYS.schemaVersion]: INDEX_SCHEMA_VERSION,
|
|
90
|
+
[META_KEYS.builtAt]: (/* @__PURE__ */ new Date()).toISOString(),
|
|
91
|
+
[META_KEYS.sourceDir]: sourceDir,
|
|
92
|
+
[META_KEYS.fileCount]: String(fileCount),
|
|
93
|
+
[META_KEYS.driver]: "better-sqlite3"
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// packages/index-sqlite/src/builder.ts
|
|
98
|
+
var DEFAULT_MAX_RUNS = 1e4;
|
|
99
|
+
function resolveIndexDbPath(traceDir, dbPath) {
|
|
100
|
+
if (dbPath && dbPath.trim() !== "") return path.resolve(dbPath);
|
|
101
|
+
return path.join(path.resolve(traceDir), INDEX_DB_FILENAME);
|
|
102
|
+
}
|
|
103
|
+
function str(value) {
|
|
104
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
105
|
+
}
|
|
106
|
+
function num(value) {
|
|
107
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
108
|
+
}
|
|
109
|
+
function deriveRun(file, mtimeMs, events) {
|
|
110
|
+
const started = events.find((e) => e.event === "run_started");
|
|
111
|
+
if (!started || started.event !== "run_started") return null;
|
|
112
|
+
const completed = events.find((e) => e.event === "run_completed");
|
|
113
|
+
const metadata = started.metadata ?? {};
|
|
114
|
+
const run = {
|
|
115
|
+
runId: started.runId,
|
|
116
|
+
file,
|
|
117
|
+
mtimeMs,
|
|
118
|
+
name: str(started.name),
|
|
119
|
+
status: completed && completed.event === "run_completed" ? completed.status : null,
|
|
120
|
+
startedAt: num(started.startTime),
|
|
121
|
+
endedAt: completed && completed.event === "run_completed" ? num(completed.endTime) : null,
|
|
122
|
+
durationMs: completed && completed.event === "run_completed" ? num(completed.durationMs) : null,
|
|
123
|
+
sessionId: str(metadata.sessionId),
|
|
124
|
+
groupId: str(metadata.groupId),
|
|
125
|
+
correlationId: str(metadata.correlationId)
|
|
126
|
+
};
|
|
127
|
+
const stepStarts = /* @__PURE__ */ new Map();
|
|
128
|
+
const steps = [];
|
|
129
|
+
const errors = [];
|
|
130
|
+
if (completed && completed.event === "run_completed" && completed.error) {
|
|
131
|
+
errors.push({
|
|
132
|
+
stepId: null,
|
|
133
|
+
message: str(completed.error.message),
|
|
134
|
+
code: str(completed.error.code)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
for (const event of events) {
|
|
138
|
+
if (event.event === "step_started") {
|
|
139
|
+
stepStarts.set(event.stepId, event);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const event of events) {
|
|
143
|
+
if (event.event !== "step_completed") continue;
|
|
144
|
+
const start = stepStarts.get(event.stepId);
|
|
145
|
+
const meta2 = start?.metadata ?? {};
|
|
146
|
+
steps.push({
|
|
147
|
+
runId: run.runId,
|
|
148
|
+
stepId: event.stepId,
|
|
149
|
+
kind: start ? str(start.type) : null,
|
|
150
|
+
name: start ? str(start.name) : null,
|
|
151
|
+
status: event.status,
|
|
152
|
+
durationMs: num(event.durationMs),
|
|
153
|
+
toolName: str(meta2.toolName),
|
|
154
|
+
model: str(meta2.model),
|
|
155
|
+
parentId: start ? str(start.parentId) : null
|
|
156
|
+
});
|
|
157
|
+
if (event.error) {
|
|
158
|
+
errors.push({
|
|
159
|
+
stepId: event.stepId,
|
|
160
|
+
message: str(event.error.message),
|
|
161
|
+
code: str(event.error.code)
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { run, steps, errors };
|
|
166
|
+
}
|
|
167
|
+
async function buildIndex(options = {}) {
|
|
168
|
+
const traceDir = resolveTraceDir({ dir: options.traceDir });
|
|
169
|
+
const dbPath = resolveIndexDbPath(traceDir, options.dbPath);
|
|
170
|
+
const maxRuns = options.maxRuns ?? DEFAULT_MAX_RUNS;
|
|
171
|
+
const warnings = [];
|
|
172
|
+
const td = new TraceDirectory({ dir: traceDir });
|
|
173
|
+
const files = await td.list();
|
|
174
|
+
if (files.length > maxRuns) {
|
|
175
|
+
warnings.push(
|
|
176
|
+
`index.truncated: ${files.length} trace files present; indexing first ${maxRuns}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const slice = files.slice(0, maxRuns);
|
|
180
|
+
const derived = [];
|
|
181
|
+
for (const file of slice) {
|
|
182
|
+
try {
|
|
183
|
+
const raw = await readFile(td.getPath(file), "utf-8");
|
|
184
|
+
const parsed = parseTraceJsonl(raw, { validate: validateEvent });
|
|
185
|
+
const stats = await td.getFileStats(file);
|
|
186
|
+
const one = deriveRun(file, stats.mtimeMs, parsed.events);
|
|
187
|
+
if (one) derived.push(one);
|
|
188
|
+
else warnings.push(`index.skipped: ${file} has no run_started event`);
|
|
189
|
+
} catch {
|
|
190
|
+
warnings.push(`index.unreadable: ${file}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
await mkdir(path.dirname(dbPath), { recursive: true });
|
|
194
|
+
await rm(dbPath, { force: true });
|
|
195
|
+
const db = new Database(dbPath);
|
|
196
|
+
let runCount = 0;
|
|
197
|
+
let stepCount = 0;
|
|
198
|
+
let errorCount = 0;
|
|
199
|
+
try {
|
|
200
|
+
db.pragma("journal_mode = WAL");
|
|
201
|
+
db.exec(INDEX_SCHEMA_SQL);
|
|
202
|
+
const insertRun = db.prepare(
|
|
203
|
+
`INSERT INTO runs (run_id, file, mtime_ms, name, status, started_at, ended_at, duration_ms, session_id, group_id, correlation_id)
|
|
204
|
+
VALUES (@runId, @file, @mtimeMs, @name, @status, @startedAt, @endedAt, @durationMs, @sessionId, @groupId, @correlationId)`
|
|
205
|
+
);
|
|
206
|
+
const insertStep = db.prepare(
|
|
207
|
+
`INSERT INTO steps (run_id, step_id, kind, name, status, duration_ms, tool_name, model, parent_id)
|
|
208
|
+
VALUES (@runId, @stepId, @kind, @name, @status, @durationMs, @toolName, @model, @parentId)`
|
|
209
|
+
);
|
|
210
|
+
const insertError = db.prepare(
|
|
211
|
+
`INSERT INTO errors (run_id, step_id, message, code) VALUES (@runId, @stepId, @message, @code)`
|
|
212
|
+
);
|
|
213
|
+
const insertMeta = db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)`);
|
|
214
|
+
const write = db.transaction((items) => {
|
|
215
|
+
const seen = /* @__PURE__ */ new Set();
|
|
216
|
+
for (const item of items) {
|
|
217
|
+
if (seen.has(item.run.runId)) continue;
|
|
218
|
+
seen.add(item.run.runId);
|
|
219
|
+
insertRun.run(item.run);
|
|
220
|
+
runCount += 1;
|
|
221
|
+
for (const step of item.steps) {
|
|
222
|
+
insertStep.run(step);
|
|
223
|
+
stepCount += 1;
|
|
224
|
+
}
|
|
225
|
+
for (const err of item.errors) {
|
|
226
|
+
insertError.run({ runId: item.run.runId, ...err });
|
|
227
|
+
errorCount += 1;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
db.exec(DERIVE_SESSIONS_SQL);
|
|
231
|
+
for (const [key, value] of Object.entries(metaDefaults(traceDir, slice.length))) {
|
|
232
|
+
insertMeta.run(key, value);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
write(derived);
|
|
236
|
+
} finally {
|
|
237
|
+
db.close();
|
|
238
|
+
}
|
|
239
|
+
const builtAtRow = readMetaValue(dbPath, META_KEYS.builtAt);
|
|
240
|
+
return {
|
|
241
|
+
dbPath,
|
|
242
|
+
traceDir,
|
|
243
|
+
runs: runCount,
|
|
244
|
+
steps: stepCount,
|
|
245
|
+
errors: errorCount,
|
|
246
|
+
builtAt: builtAtRow ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
247
|
+
warnings
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
var rebuildIndex = buildIndex;
|
|
251
|
+
function readMetaValue(dbPath, key) {
|
|
252
|
+
try {
|
|
253
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
254
|
+
try {
|
|
255
|
+
const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key);
|
|
256
|
+
return row?.value ?? null;
|
|
257
|
+
} finally {
|
|
258
|
+
db.close();
|
|
259
|
+
}
|
|
260
|
+
} catch {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function cleanIndex(dbPath) {
|
|
265
|
+
await rm(dbPath, { force: true });
|
|
266
|
+
await rm(`${dbPath}-wal`, { force: true });
|
|
267
|
+
await rm(`${dbPath}-shm`, { force: true });
|
|
268
|
+
}
|
|
269
|
+
function openHealthy(dbPath) {
|
|
270
|
+
if (!existsSync(dbPath)) return null;
|
|
271
|
+
try {
|
|
272
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
273
|
+
try {
|
|
274
|
+
const result = db.pragma("integrity_check", { simple: true });
|
|
275
|
+
if (result !== "ok") {
|
|
276
|
+
db.close();
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
db.prepare(`SELECT 1 FROM meta LIMIT 1`).get();
|
|
280
|
+
db.prepare(`SELECT 1 FROM runs LIMIT 1`).get();
|
|
281
|
+
return { db, healthy: true };
|
|
282
|
+
} catch {
|
|
283
|
+
db.close();
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function meta(db, key) {
|
|
291
|
+
const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key);
|
|
292
|
+
return row?.value ?? null;
|
|
293
|
+
}
|
|
294
|
+
function indexStatus(dbPath) {
|
|
295
|
+
const opened = openHealthy(dbPath);
|
|
296
|
+
if (!opened) {
|
|
297
|
+
return {
|
|
298
|
+
dbPath,
|
|
299
|
+
exists: existsSync(dbPath),
|
|
300
|
+
healthy: false,
|
|
301
|
+
builtAt: null,
|
|
302
|
+
sourceDir: null,
|
|
303
|
+
schemaVersion: null,
|
|
304
|
+
runs: 0,
|
|
305
|
+
steps: 0
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const { db } = opened;
|
|
309
|
+
try {
|
|
310
|
+
const runs = db.prepare(`SELECT COUNT(*) AS c FROM runs`).get().c;
|
|
311
|
+
const steps = db.prepare(`SELECT COUNT(*) AS c FROM steps`).get().c;
|
|
312
|
+
return {
|
|
313
|
+
dbPath,
|
|
314
|
+
exists: true,
|
|
315
|
+
healthy: true,
|
|
316
|
+
builtAt: meta(db, META_KEYS.builtAt),
|
|
317
|
+
sourceDir: meta(db, META_KEYS.sourceDir),
|
|
318
|
+
schemaVersion: meta(db, META_KEYS.schemaVersion),
|
|
319
|
+
runs,
|
|
320
|
+
steps
|
|
321
|
+
};
|
|
322
|
+
} finally {
|
|
323
|
+
db.close();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function isIndexStale(dbPath, newestTraceMtimeMs) {
|
|
327
|
+
const opened = openHealthy(dbPath);
|
|
328
|
+
if (!opened) return true;
|
|
329
|
+
try {
|
|
330
|
+
const builtAt = meta(opened.db, META_KEYS.builtAt);
|
|
331
|
+
if (!builtAt) return true;
|
|
332
|
+
const builtMs = Date.parse(builtAt);
|
|
333
|
+
if (Number.isNaN(builtMs)) return true;
|
|
334
|
+
return newestTraceMtimeMs > builtMs;
|
|
335
|
+
} finally {
|
|
336
|
+
opened.db.close();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function mapRow(row) {
|
|
340
|
+
return {
|
|
341
|
+
runId: row.run_id,
|
|
342
|
+
file: row.file,
|
|
343
|
+
mtimeMs: row.mtime_ms,
|
|
344
|
+
name: row.name ?? null,
|
|
345
|
+
status: row.status ?? null,
|
|
346
|
+
startedAt: row.started_at ?? null,
|
|
347
|
+
endedAt: row.ended_at ?? null,
|
|
348
|
+
durationMs: row.duration_ms ?? null,
|
|
349
|
+
sessionId: row.session_id ?? null,
|
|
350
|
+
groupId: row.group_id ?? null,
|
|
351
|
+
correlationId: row.correlation_id ?? null
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function queryRuns(dbPath, query = {}) {
|
|
355
|
+
const opened = openHealthy(dbPath);
|
|
356
|
+
if (!opened) return [];
|
|
357
|
+
const { db } = opened;
|
|
358
|
+
try {
|
|
359
|
+
const where = [];
|
|
360
|
+
const params = {};
|
|
361
|
+
if (query.status) {
|
|
362
|
+
where.push(`r.status = @status`);
|
|
363
|
+
params.status = query.status;
|
|
364
|
+
}
|
|
365
|
+
if (query.sessionId) {
|
|
366
|
+
where.push(`r.session_id = @sessionId`);
|
|
367
|
+
params.sessionId = query.sessionId;
|
|
368
|
+
}
|
|
369
|
+
if (query.name) {
|
|
370
|
+
where.push(`LOWER(r.name) LIKE @name`);
|
|
371
|
+
params.name = `%${query.name.toLowerCase()}%`;
|
|
372
|
+
}
|
|
373
|
+
if (query.kind) {
|
|
374
|
+
where.push(`EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND s.kind = @kind)`);
|
|
375
|
+
params.kind = query.kind;
|
|
376
|
+
}
|
|
377
|
+
if (query.tool) {
|
|
378
|
+
where.push(
|
|
379
|
+
`EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND LOWER(s.tool_name) LIKE @tool)`
|
|
380
|
+
);
|
|
381
|
+
params.tool = `%${query.tool.toLowerCase()}%`;
|
|
382
|
+
}
|
|
383
|
+
const limit = Number.isInteger(query.limit) && query.limit > 0 ? query.limit : 100;
|
|
384
|
+
const sql = `SELECT r.* FROM runs r ${where.length ? `WHERE ${where.join(" AND ")}` : ""} ORDER BY r.started_at DESC LIMIT ${limit}`;
|
|
385
|
+
const rows = db.prepare(sql).all(params);
|
|
386
|
+
return rows.map(mapRow);
|
|
387
|
+
} finally {
|
|
388
|
+
db.close();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export { INDEX_DB_FILENAME, INDEX_SCHEMA_VERSION, buildIndex, cleanIndex, indexStatus, isIndexStale, queryRuns, rebuildIndex, resolveIndexDbPath };
|
|
393
|
+
//# sourceMappingURL=index.mjs.map
|
|
394
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/schema.ts","../src/builder.ts","../src/query.ts"],"names":["meta","Database"],"mappings":";;;;;;;AASO,IAAM,oBAAA,GAAuB;AAG7B,IAAM,iBAAA,GAAoB;;;ACN1B,IAAM,gBAAA,GAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA+DzB,IAAM,mBAAA,GAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAQ5B,IAAM,SAAA,GAAY;AAAA,EACvB,aAAA,EAAe,eAAA;AAAA,EACf,OAAA,EAAS,SAAA;AAAA,EACT,SAAA,EAAW,WAAA;AAAA,EACX,SAAA,EAAW,WAAA;AAAA,EACX,MAAA,EAAQ;AACV,CAAA;AAEO,SAAS,YAAA,CAAa,WAAmB,SAAA,EAAmB;AACjE,EAAA,OAAO;AAAA,IACL,CAAC,SAAA,CAAU,aAAa,GAAG,oBAAA;AAAA,IAC3B,CAAC,SAAA,CAAU,OAAO,oBAAG,IAAI,IAAA,IAAO,WAAA,EAAY;AAAA,IAC5C,CAAC,SAAA,CAAU,SAAS,GAAG,SAAA;AAAA,IACvB,CAAC,SAAA,CAAU,SAAS,GAAG,OAAO,SAAS,CAAA;AAAA,IACvC,CAAC,SAAA,CAAU,MAAM,GAAG;AAAA,GACtB;AACF;;;ACnEA,IAAM,gBAAA,GAAmB,GAAA;AAGlB,SAAS,kBAAA,CAAmB,UAAkB,MAAA,EAAyB;AAC5E,EAAA,IAAI,MAAA,IAAU,OAAO,IAAA,EAAK,KAAM,IAAI,OAAO,IAAA,CAAK,QAAQ,MAAM,CAAA;AAC9D,EAAA,OAAO,KAAK,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,QAAQ,GAAG,iBAAiB,CAAA;AAC5D;AAEA,SAAS,IAAI,KAAA,EAA+B;AAC1C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,KAAK,KAAA,GAAQ,IAAA;AAC7D;AAEA,SAAS,IAAI,KAAA,EAA+B;AAC1C,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,OAAO,QAAA,CAAS,KAAK,IAAI,KAAA,GAAQ,IAAA;AACvE;AAQA,SAAS,SAAA,CAAU,IAAA,EAAc,OAAA,EAAiB,MAAA,EAAyC;AACzF,EAAA,MAAM,UAAU,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,aAAa,CAAA;AAC5D,EAAA,IAAI,CAAC,OAAA,IAAW,OAAA,CAAQ,KAAA,KAAU,eAAe,OAAO,IAAA;AAExD,EAAA,MAAM,YAAY,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,eAAe,CAAA;AAChE,EAAA,MAAM,QAAA,GAAY,OAAA,CAAQ,QAAA,IAAY,EAAC;AAEvC,EAAA,MAAM,GAAA,GAAkB;AAAA,IACtB,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,IAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAA,EAAM,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AAAA,IACtB,QAAQ,SAAA,IAAa,SAAA,CAAU,KAAA,KAAU,eAAA,GAAkB,UAAU,MAAA,GAAS,IAAA;AAAA,IAC9E,SAAA,EAAW,GAAA,CAAI,OAAA,CAAQ,SAAS,CAAA;AAAA,IAChC,OAAA,EAAS,aAAa,SAAA,CAAU,KAAA,KAAU,kBAAkB,GAAA,CAAI,SAAA,CAAU,OAAO,CAAA,GAAI,IAAA;AAAA,IACrF,UAAA,EACE,aAAa,SAAA,CAAU,KAAA,KAAU,kBAAkB,GAAA,CAAI,SAAA,CAAU,UAAU,CAAA,GAAI,IAAA;AAAA,IACjF,SAAA,EAAW,GAAA,CAAI,QAAA,CAAS,SAAS,CAAA;AAAA,IACjC,OAAA,EAAS,GAAA,CAAI,QAAA,CAAS,OAAO,CAAA;AAAA,IAC7B,aAAA,EAAe,GAAA,CAAI,QAAA,CAAS,aAAa;AAAA,GAC3C;AAEA,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAA4D;AACnF,EAAA,MAAM,QAAuB,EAAC;AAC9B,EAAA,MAAM,SAA+B,EAAC;AAEtC,EAAA,IAAI,SAAA,IAAa,SAAA,CAAU,KAAA,KAAU,eAAA,IAAmB,UAAU,KAAA,EAAO;AACvE,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,MAAA,EAAQ,IAAA;AAAA,MACR,OAAA,EAAS,GAAA,CAAI,SAAA,CAAU,KAAA,CAAM,OAAO,CAAA;AAAA,MACpC,IAAA,EAAM,GAAA,CAAK,SAAA,CAAU,KAAA,CAA6B,IAAI;AAAA,KACvD,CAAA;AAAA,EACH;AAEA,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,KAAA,CAAM,UAAU,cAAA,EAAgB;AAClC,MAAA,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA;AAAA,IACpC;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,KAAA,CAAM,UAAU,gBAAA,EAAkB;AACtC,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,MAAM,CAAA;AACzC,IAAA,MAAMA,KAAAA,GAAQ,KAAA,EAAO,QAAA,IAAY,EAAC;AAClC,IAAA,KAAA,CAAM,IAAA,CAAK;AAAA,MACT,OAAO,GAAA,CAAI,KAAA;AAAA,MACX,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,IAAA,EAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,GAAI,IAAA;AAAA,MAChC,IAAA,EAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,GAAI,IAAA;AAAA,MAChC,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,UAAA,EAAY,GAAA,CAAI,KAAA,CAAM,UAAU,CAAA;AAAA,MAChC,QAAA,EAAU,GAAA,CAAIA,KAAAA,CAAK,QAAQ,CAAA;AAAA,MAC3B,KAAA,EAAO,GAAA,CAAIA,KAAAA,CAAK,KAAK,CAAA;AAAA,MACrB,QAAA,EAAU,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,QAAQ,CAAA,GAAI;AAAA,KACzC,CAAA;AACD,IAAA,IAAI,MAAM,KAAA,EAAO;AACf,MAAA,MAAA,CAAO,IAAA,CAAK;AAAA,QACV,QAAQ,KAAA,CAAM,MAAA;AAAA,QACd,OAAA,EAAS,GAAA,CAAI,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AAAA,QAChC,IAAA,EAAM,GAAA,CAAK,KAAA,CAAM,KAAA,CAA6B,IAAI;AAAA,OACnD,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,GAAA,EAAK,KAAA,EAAO,MAAA,EAAO;AAC9B;AAOA,eAAsB,UAAA,CACpB,OAAA,GAA6B,EAAC,EACH;AAC3B,EAAA,MAAM,WAAW,eAAA,CAAgB,EAAE,GAAA,EAAK,OAAA,CAAQ,UAAU,CAAA;AAC1D,EAAA,MAAM,MAAA,GAAS,kBAAA,CAAmB,QAAA,EAAU,OAAA,CAAQ,MAAM,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,gBAAA;AACnC,EAAA,MAAM,WAAqB,EAAC;AAE5B,EAAA,MAAM,KAAK,IAAI,cAAA,CAAe,EAAE,GAAA,EAAK,UAAU,CAAA;AAC/C,EAAA,MAAM,KAAA,GAAQ,MAAM,EAAA,CAAG,IAAA,EAAK;AAC5B,EAAA,IAAI,KAAA,CAAM,SAAS,OAAA,EAAS;AAC1B,IAAA,QAAA,CAAS,IAAA;AAAA,MACP,CAAA,iBAAA,EAAoB,KAAA,CAAM,MAAM,CAAA,qCAAA,EAAwC,OAAO,CAAA;AAAA,KACjF;AAAA,EACF;AACA,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA;AAEpC,EAAA,MAAM,UAAwB,EAAC;AAC/B,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,MAAM,QAAA,CAAS,GAAG,OAAA,CAAQ,IAAI,GAAG,OAAO,CAAA;AACpD,MAAA,MAAM,SAAS,eAAA,CAAgB,GAAA,EAAK,EAAE,QAAA,EAAU,eAAe,CAAA;AAC/D,MAAA,MAAM,KAAA,GAAQ,MAAM,EAAA,CAAG,YAAA,CAAa,IAAI,CAAA;AACxC,MAAA,MAAM,MAAM,SAAA,CAAU,IAAA,EAAM,KAAA,CAAM,OAAA,EAAS,OAAO,MAAM,CAAA;AACxD,MAAA,IAAI,GAAA,EAAK,OAAA,CAAQ,IAAA,CAAK,GAAG,CAAA;AAAA,WACpB,QAAA,CAAS,IAAA,CAAK,CAAA,eAAA,EAAkB,IAAI,CAAA,yBAAA,CAA2B,CAAA;AAAA,IACtE,CAAA,CAAA,MAAQ;AACN,MAAA,QAAA,CAAS,IAAA,CAAK,CAAA,kBAAA,EAAqB,IAAI,CAAA,CAAE,CAAA;AAAA,IAC3C;AAAA,EACF;AAEA,EAAA,MAAM,KAAA,CAAM,KAAK,OAAA,CAAQ,MAAM,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AAErD,EAAA,MAAM,EAAA,CAAG,MAAA,EAAQ,EAAE,KAAA,EAAO,MAAM,CAAA;AAEhC,EAAA,MAAM,EAAA,GAAK,IAAI,QAAA,CAAS,MAAM,CAAA;AAC9B,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,SAAA,GAAY,CAAA;AAChB,EAAA,IAAI,UAAA,GAAa,CAAA;AACjB,EAAA,IAAI;AACF,IAAA,EAAA,CAAG,OAAO,oBAAoB,CAAA;AAC9B,IAAA,EAAA,CAAG,KAAK,gBAAgB,CAAA;AAExB,IAAA,MAAM,YAAY,EAAA,CAAG,OAAA;AAAA,MACnB,CAAA;AAAA,gIAAA;AAAA,KAEF;AACA,IAAA,MAAM,aAAa,EAAA,CAAG,OAAA;AAAA,MACpB,CAAA;AAAA,iGAAA;AAAA,KAEF;AACA,IAAA,MAAM,cAAc,EAAA,CAAG,OAAA;AAAA,MACrB,CAAA,6FAAA;AAAA,KACF;AACA,IAAA,MAAM,UAAA,GAAa,EAAA,CAAG,OAAA,CAAQ,CAAA,2CAAA,CAA6C,CAAA;AAE3E,IAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,WAAA,CAAY,CAAC,KAAA,KAAwB;AACpD,MAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,MAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,QAAA,IAAI,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA,EAAG;AAC9B,QAAA,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA;AACvB,QAAA,SAAA,CAAU,GAAA,CAAI,KAAK,GAAG,CAAA;AACtB,QAAA,QAAA,IAAY,CAAA;AACZ,QAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,UAAA,UAAA,CAAW,IAAI,IAAI,CAAA;AACnB,UAAA,SAAA,IAAa,CAAA;AAAA,QACf;AACA,QAAA,KAAA,MAAW,GAAA,IAAO,KAAK,MAAA,EAAQ;AAC7B,UAAA,WAAA,CAAY,GAAA,CAAI,EAAE,KAAA,EAAO,IAAA,CAAK,IAAI,KAAA,EAAO,GAAG,KAAK,CAAA;AACjD,UAAA,UAAA,IAAc,CAAA;AAAA,QAChB;AAAA,MACF;AACA,MAAA,EAAA,CAAG,KAAK,mBAAmB,CAAA;AAC3B,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,MAAA,CAAO,OAAA,CAAQ,YAAA,CAAa,QAAA,EAAU,KAAA,CAAM,MAAM,CAAC,CAAA,EAAG;AAC/E,QAAA,UAAA,CAAW,GAAA,CAAI,KAAK,KAAK,CAAA;AAAA,MAC3B;AAAA,IACF,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,OAAO,CAAA;AAAA,EACf,CAAA,SAAE;AACA,IAAA,EAAA,CAAG,KAAA,EAAM;AAAA,EACX;AAEA,EAAA,MAAM,UAAA,GAAa,aAAA,CAAc,MAAA,EAAQ,SAAA,CAAU,OAAO,CAAA;AAE1D,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,QAAA;AAAA,IACA,IAAA,EAAM,QAAA;AAAA,IACN,KAAA,EAAO,SAAA;AAAA,IACP,MAAA,EAAQ,UAAA;AAAA,IACR,OAAA,EAAS,UAAA,IAAA,iBAAc,IAAI,IAAA,IAAO,WAAA,EAAY;AAAA,IAC9C;AAAA,GACF;AACF;AAGO,IAAM,YAAA,GAAe;AAE5B,SAAS,aAAA,CAAc,QAAgB,GAAA,EAA4B;AACjE,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,GAAK,IAAI,QAAA,CAAS,MAAA,EAAQ,EAAE,QAAA,EAAU,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,CAAA;AACvE,IAAA,IAAI;AACF,MAAA,MAAM,MAAM,EAAA,CAAG,OAAA,CAAQ,CAAA,oCAAA,CAAsC,CAAA,CAAE,IAAI,GAAG,CAAA;AAGtE,MAAA,OAAO,KAAK,KAAA,IAAS,IAAA;AAAA,IACvB,CAAA,SAAE;AACA,MAAA,EAAA,CAAG,KAAA,EAAM;AAAA,IACX;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAGA,eAAsB,WAAW,MAAA,EAA+B;AAC9D,EAAA,MAAM,EAAA,CAAG,MAAA,EAAQ,EAAE,KAAA,EAAO,MAAM,CAAA;AAChC,EAAA,MAAM,GAAG,CAAA,EAAG,MAAM,QAAQ,EAAE,KAAA,EAAO,MAAM,CAAA;AACzC,EAAA,MAAM,GAAG,CAAA,EAAG,MAAM,QAAQ,EAAE,KAAA,EAAO,MAAM,CAAA;AAC3C;AC9NA,SAAS,YAAY,MAAA,EAAmC;AACtD,EAAA,IAAI,CAAC,UAAA,CAAW,MAAM,CAAA,EAAG,OAAO,IAAA;AAChC,EAAA,IAAI;AACF,IAAA,MAAM,EAAA,GAAK,IAAIC,QAAAA,CAAS,MAAA,EAAQ,EAAE,QAAA,EAAU,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,CAAA;AACvE,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,EAAA,CAAG,MAAA,CAAO,mBAAmB,EAAE,MAAA,EAAQ,MAAM,CAAA;AAC5D,MAAA,IAAI,WAAW,IAAA,EAAM;AACnB,QAAA,EAAA,CAAG,KAAA,EAAM;AACT,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,EAAA,CAAG,OAAA,CAAQ,CAAA,0BAAA,CAA4B,CAAA,CAAE,GAAA,EAAI;AAC7C,MAAA,EAAA,CAAG,OAAA,CAAQ,CAAA,0BAAA,CAA4B,CAAA,CAAE,GAAA,EAAI;AAC7C,MAAA,OAAO,EAAE,EAAA,EAAI,OAAA,EAAS,IAAA,EAAK;AAAA,IAC7B,CAAA,CAAA,MAAQ;AACN,MAAA,EAAA,CAAG,KAAA,EAAM;AACT,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEA,SAAS,IAAA,CAAK,IAAuB,GAAA,EAA4B;AAC/D,EAAA,MAAM,MAAM,EAAA,CAAG,OAAA,CAAQ,CAAA,oCAAA,CAAsC,CAAA,CAAE,IAAI,GAAG,CAAA;AAGtE,EAAA,OAAO,KAAK,KAAA,IAAS,IAAA;AACvB;AAGO,SAAS,YAAY,MAAA,EAA6B;AACvD,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO;AAAA,MACL,MAAA;AAAA,MACA,MAAA,EAAQ,WAAW,MAAM,CAAA;AAAA,MACzB,OAAA,EAAS,KAAA;AAAA,MACT,OAAA,EAAS,IAAA;AAAA,MACT,SAAA,EAAW,IAAA;AAAA,MACX,aAAA,EAAe,IAAA;AAAA,MACf,IAAA,EAAM,CAAA;AAAA,MACN,KAAA,EAAO;AAAA,KACT;AAAA,EACF;AACA,EAAA,MAAM,EAAE,IAAG,GAAI,MAAA;AACf,EAAA,IAAI;AACF,IAAA,MAAM,OAAQ,EAAA,CAAG,OAAA,CAAQ,CAAA,8BAAA,CAAgC,CAAA,CAAE,KAAI,CAAoB,CAAA;AACnF,IAAA,MAAM,QAAS,EAAA,CAAG,OAAA,CAAQ,CAAA,+BAAA,CAAiC,CAAA,CAAE,KAAI,CAAoB,CAAA;AACrF,IAAA,OAAO;AAAA,MACL,MAAA;AAAA,MACA,MAAA,EAAQ,IAAA;AAAA,MACR,OAAA,EAAS,IAAA;AAAA,MACT,OAAA,EAAS,IAAA,CAAK,EAAA,EAAI,SAAA,CAAU,OAAO,CAAA;AAAA,MACnC,SAAA,EAAW,IAAA,CAAK,EAAA,EAAI,SAAA,CAAU,SAAS,CAAA;AAAA,MACvC,aAAA,EAAe,IAAA,CAAK,EAAA,EAAI,SAAA,CAAU,aAAa,CAAA;AAAA,MAC/C,IAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA,SAAE;AACA,IAAA,EAAA,CAAG,KAAA,EAAM;AAAA,EACX;AACF;AAGO,SAAS,YAAA,CAAa,QAAgB,kBAAA,EAAqC;AAChF,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AACpB,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAA,CAAO,EAAA,EAAI,UAAU,OAAO,CAAA;AACjD,IAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAClC,IAAA,IAAI,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA,EAAG,OAAO,IAAA;AAClC,IAAA,OAAO,kBAAA,GAAqB,OAAA;AAAA,EAC9B,CAAA,SAAE;AACA,IAAA,MAAA,CAAO,GAAG,KAAA,EAAM;AAAA,EAClB;AACF;AAEA,SAAS,OAAO,GAAA,EAA0C;AACxD,EAAA,OAAO;AAAA,IACL,OAAO,GAAA,CAAI,MAAA;AAAA,IACX,MAAM,GAAA,CAAI,IAAA;AAAA,IACV,SAAS,GAAA,CAAI,QAAA;AAAA,IACb,IAAA,EAAO,IAAI,IAAA,IAA0B,IAAA;AAAA,IACrC,MAAA,EAAS,IAAI,MAAA,IAA4B,IAAA;AAAA,IACzC,SAAA,EAAY,IAAI,UAAA,IAAgC,IAAA;AAAA,IAChD,OAAA,EAAU,IAAI,QAAA,IAA8B,IAAA;AAAA,IAC5C,UAAA,EAAa,IAAI,WAAA,IAAiC,IAAA;AAAA,IAClD,SAAA,EAAY,IAAI,UAAA,IAAgC,IAAA;AAAA,IAChD,OAAA,EAAU,IAAI,QAAA,IAA8B,IAAA;AAAA,IAC5C,aAAA,EAAgB,IAAI,cAAA,IAAoC;AAAA,GAC1D;AACF;AAMO,SAAS,SAAA,CAAU,MAAA,EAAgB,KAAA,GAAkB,EAAC,EAAiB;AAC5E,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,IAAI,CAAC,MAAA,EAAQ,OAAO,EAAC;AACrB,EAAA,MAAM,EAAE,IAAG,GAAI,MAAA;AACf,EAAA,IAAI;AACF,IAAA,MAAM,QAAkB,EAAC;AACzB,IAAA,MAAM,SAAkC,EAAC;AAEzC,IAAA,IAAI,MAAM,MAAA,EAAQ;AAChB,MAAA,KAAA,CAAM,KAAK,CAAA,kBAAA,CAAoB,CAAA;AAC/B,MAAA,MAAA,CAAO,SAAS,KAAA,CAAM,MAAA;AAAA,IACxB;AACA,IAAA,IAAI,MAAM,SAAA,EAAW;AACnB,MAAA,KAAA,CAAM,KAAK,CAAA,yBAAA,CAA2B,CAAA;AACtC,MAAA,MAAA,CAAO,YAAY,KAAA,CAAM,SAAA;AAAA,IAC3B;AACA,IAAA,IAAI,MAAM,IAAA,EAAM;AACd,MAAA,KAAA,CAAM,KAAK,CAAA,wBAAA,CAA0B,CAAA;AACrC,MAAA,MAAA,CAAO,IAAA,GAAO,CAAA,CAAA,EAAI,KAAA,CAAM,IAAA,CAAK,aAAa,CAAA,CAAA,CAAA;AAAA,IAC5C;AACA,IAAA,IAAI,MAAM,IAAA,EAAM;AACd,MAAA,KAAA,CAAM,KAAK,CAAA,2EAAA,CAA6E,CAAA;AACxF,MAAA,MAAA,CAAO,OAAO,KAAA,CAAM,IAAA;AAAA,IACtB;AACA,IAAA,IAAI,MAAM,IAAA,EAAM;AACd,MAAA,KAAA,CAAM,IAAA;AAAA,QACJ,CAAA,0FAAA;AAAA,OACF;AACA,MAAA,MAAA,CAAO,IAAA,GAAO,CAAA,CAAA,EAAI,KAAA,CAAM,IAAA,CAAK,aAAa,CAAA,CAAA,CAAA;AAAA,IAC5C;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,SAAA,CAAU,KAAA,CAAM,KAAK,KAAK,KAAA,CAAM,KAAA,GAAS,CAAA,GAAI,KAAA,CAAM,KAAA,GAAS,GAAA;AACjF,IAAA,MAAM,GAAA,GAAM,CAAA,uBAAA,EACV,KAAA,CAAM,MAAA,GAAS,CAAA,MAAA,EAAS,KAAA,CAAM,IAAA,CAAK,OAAO,CAAC,CAAA,CAAA,GAAK,EAClD,CAAA,kCAAA,EAAqC,KAAK,CAAA,CAAA;AAE1C,IAAA,MAAM,OAAO,EAAA,CAAG,OAAA,CAAQ,GAAG,CAAA,CAAE,IAAI,MAAM,CAAA;AACvC,IAAA,OAAO,IAAA,CAAK,IAAI,MAAM,CAAA;AAAA,EACxB,CAAA,SAAE;AACA,IAAA,EAAA,CAAG,KAAA,EAAM;AAAA,EACX;AACF","file":"index.mjs","sourcesContent":["/**\n * Public types for the optional local SQLite trace index (v4.1, experimental).\n *\n * @remarks\n * Local-only. The index is derived from JSONL traces and is always safe to\n * delete. Trace files are never mutated. No network access.\n */\n\n/** Current index schema version. Bumped when table layout changes. */\nexport const INDEX_SCHEMA_VERSION = \"1\" as const;\n\n/** Default index database filename. */\nexport const INDEX_DB_FILENAME = \"trace-index.sqlite\";\n\n/** A derived run row in the index. */\nexport interface IndexedRun {\n runId: string;\n file: string;\n mtimeMs: number;\n name: string | null;\n status: string | null;\n startedAt: number | null;\n endedAt: number | null;\n durationMs: number | null;\n sessionId: string | null;\n groupId: string | null;\n correlationId: string | null;\n}\n\n/** A derived step row in the index. */\nexport interface IndexedStep {\n runId: string;\n stepId: string;\n kind: string | null;\n name: string | null;\n status: string | null;\n durationMs: number | null;\n toolName: string | null;\n model: string | null;\n parentId: string | null;\n}\n\n/** Options for building/rebuilding the index. */\nexport interface BuildIndexOptions {\n /** Trace directory to index. Resolved via core `resolveTraceDir` when omitted. */\n traceDir?: string;\n /** Index database path. Defaults to `<traceDir>/<INDEX_DB_FILENAME>`. */\n dbPath?: string;\n /** Cap on the number of trace files indexed (default 10000). */\n maxRuns?: number;\n}\n\n/** Result of a build/rebuild. */\nexport interface BuildIndexResult {\n dbPath: string;\n traceDir: string;\n runs: number;\n steps: number;\n errors: number;\n builtAt: string;\n warnings: string[];\n}\n\n/** Status of an existing index. */\nexport interface IndexStatus {\n dbPath: string;\n exists: boolean;\n healthy: boolean;\n builtAt: string | null;\n sourceDir: string | null;\n schemaVersion: string | null;\n runs: number;\n steps: number;\n}\n\n/** Filter for querying indexed runs. */\nexport interface RunQuery {\n status?: string;\n sessionId?: string;\n /** Case-insensitive substring match on run name. */\n name?: string;\n /** Match runs that contain a step of this kind. */\n kind?: string;\n /** Match runs that contain a step with this tool name (substring). */\n tool?: string;\n limit?: number;\n}\n","import { INDEX_SCHEMA_VERSION } from \"./types.js\";\n\n/**\n * Idempotent DDL for the local trace index. A full rebuild drops and recreates\n * all tables, so building twice from the same inputs yields identical contents.\n */\nexport const INDEX_SCHEMA_SQL = `\nDROP TABLE IF EXISTS meta;\nDROP TABLE IF EXISTS errors;\nDROP TABLE IF EXISTS steps;\nDROP TABLE IF EXISTS sessions;\nDROP TABLE IF EXISTS runs;\n\nCREATE TABLE meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\nCREATE TABLE runs (\n run_id TEXT PRIMARY KEY,\n file TEXT NOT NULL,\n mtime_ms REAL NOT NULL,\n name TEXT,\n status TEXT,\n started_at REAL,\n ended_at REAL,\n duration_ms REAL,\n session_id TEXT,\n group_id TEXT,\n correlation_id TEXT\n);\n\nCREATE TABLE steps (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n step_id TEXT NOT NULL,\n kind TEXT,\n name TEXT,\n status TEXT,\n duration_ms REAL,\n tool_name TEXT,\n model TEXT,\n parent_id TEXT\n);\n\nCREATE TABLE errors (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n step_id TEXT,\n message TEXT,\n code TEXT\n);\n\nCREATE TABLE sessions (\n session_id TEXT PRIMARY KEY,\n run_count INTEGER NOT NULL,\n first_started_at REAL,\n last_ended_at REAL\n);\n\nCREATE INDEX idx_runs_status ON runs(status);\nCREATE INDEX idx_runs_session ON runs(session_id);\nCREATE INDEX idx_runs_started ON runs(started_at);\nCREATE INDEX idx_steps_run ON steps(run_id);\nCREATE INDEX idx_steps_kind ON steps(kind);\nCREATE INDEX idx_steps_tool ON steps(tool_name);\n`;\n\n/** SQL to derive the sessions table from indexed runs. */\nexport const DERIVE_SESSIONS_SQL = `\nINSERT INTO sessions (session_id, run_count, first_started_at, last_ended_at)\nSELECT session_id, COUNT(*), MIN(started_at), MAX(ended_at)\nFROM runs\nWHERE session_id IS NOT NULL\nGROUP BY session_id;\n`;\n\nexport const META_KEYS = {\n schemaVersion: \"schemaVersion\",\n builtAt: \"builtAt\",\n sourceDir: \"sourceDir\",\n fileCount: \"fileCount\",\n driver: \"driver\",\n} as const;\n\nexport function metaDefaults(sourceDir: string, fileCount: number) {\n return {\n [META_KEYS.schemaVersion]: INDEX_SCHEMA_VERSION,\n [META_KEYS.builtAt]: new Date().toISOString(),\n [META_KEYS.sourceDir]: sourceDir,\n [META_KEYS.fileCount]: String(fileCount),\n [META_KEYS.driver]: \"better-sqlite3\",\n } as Record<string, string>;\n}\n","import { mkdir, readFile, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport Database from \"better-sqlite3\";\nimport {\n TraceDirectory,\n parseTraceJsonl,\n resolveTraceDir,\n validateEvent,\n} from \"agent-inspect/advanced\";\nimport type { TraceEvent } from \"agent-inspect/advanced\";\n\nimport {\n DERIVE_SESSIONS_SQL,\n INDEX_SCHEMA_SQL,\n META_KEYS,\n metaDefaults,\n} from \"./schema.js\";\nimport {\n INDEX_DB_FILENAME,\n type BuildIndexOptions,\n type BuildIndexResult,\n type IndexedRun,\n type IndexedStep,\n} from \"./types.js\";\n\nconst DEFAULT_MAX_RUNS = 10_000;\n\n/** Resolves the index database path for a trace directory. */\nexport function resolveIndexDbPath(traceDir: string, dbPath?: string): string {\n if (dbPath && dbPath.trim() !== \"\") return path.resolve(dbPath);\n return path.join(path.resolve(traceDir), INDEX_DB_FILENAME);\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === \"string\" && value !== \"\" ? value : null;\n}\n\nfunction num(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\ninterface DerivedRun {\n run: IndexedRun;\n steps: IndexedStep[];\n errors: Array<{ stepId: string | null; message: string | null; code: string | null }>;\n}\n\nfunction deriveRun(file: string, mtimeMs: number, events: TraceEvent[]): DerivedRun | null {\n const started = events.find((e) => e.event === \"run_started\");\n if (!started || started.event !== \"run_started\") return null;\n\n const completed = events.find((e) => e.event === \"run_completed\");\n const metadata = (started.metadata ?? {}) as Record<string, unknown>;\n\n const run: IndexedRun = {\n runId: started.runId,\n file,\n mtimeMs,\n name: str(started.name),\n status: completed && completed.event === \"run_completed\" ? completed.status : null,\n startedAt: num(started.startTime),\n endedAt: completed && completed.event === \"run_completed\" ? num(completed.endTime) : null,\n durationMs:\n completed && completed.event === \"run_completed\" ? num(completed.durationMs) : null,\n sessionId: str(metadata.sessionId),\n groupId: str(metadata.groupId),\n correlationId: str(metadata.correlationId),\n };\n\n const stepStarts = new Map<string, Extract<TraceEvent, { event: \"step_started\" }>>();\n const steps: IndexedStep[] = [];\n const errors: DerivedRun[\"errors\"] = [];\n\n if (completed && completed.event === \"run_completed\" && completed.error) {\n errors.push({\n stepId: null,\n message: str(completed.error.message),\n code: str((completed.error as { code?: unknown }).code),\n });\n }\n\n for (const event of events) {\n if (event.event === \"step_started\") {\n stepStarts.set(event.stepId, event);\n }\n }\n\n for (const event of events) {\n if (event.event !== \"step_completed\") continue;\n const start = stepStarts.get(event.stepId);\n const meta = (start?.metadata ?? {}) as Record<string, unknown>;\n steps.push({\n runId: run.runId,\n stepId: event.stepId,\n kind: start ? str(start.type) : null,\n name: start ? str(start.name) : null,\n status: event.status,\n durationMs: num(event.durationMs),\n toolName: str(meta.toolName),\n model: str(meta.model),\n parentId: start ? str(start.parentId) : null,\n });\n if (event.error) {\n errors.push({\n stepId: event.stepId,\n message: str(event.error.message),\n code: str((event.error as { code?: unknown }).code),\n });\n }\n }\n\n return { run, steps, errors };\n}\n\n/**\n * Builds (or fully rebuilds) the local SQLite index from a trace directory.\n * Idempotent: rebuilding from identical inputs yields identical contents.\n * Never mutates trace files.\n */\nexport async function buildIndex(\n options: BuildIndexOptions = {},\n): Promise<BuildIndexResult> {\n const traceDir = resolveTraceDir({ dir: options.traceDir });\n const dbPath = resolveIndexDbPath(traceDir, options.dbPath);\n const maxRuns = options.maxRuns ?? DEFAULT_MAX_RUNS;\n const warnings: string[] = [];\n\n const td = new TraceDirectory({ dir: traceDir });\n const files = await td.list();\n if (files.length > maxRuns) {\n warnings.push(\n `index.truncated: ${files.length} trace files present; indexing first ${maxRuns}`,\n );\n }\n const slice = files.slice(0, maxRuns);\n\n const derived: DerivedRun[] = [];\n for (const file of slice) {\n try {\n const raw = await readFile(td.getPath(file), \"utf-8\");\n const parsed = parseTraceJsonl(raw, { validate: validateEvent });\n const stats = await td.getFileStats(file);\n const one = deriveRun(file, stats.mtimeMs, parsed.events);\n if (one) derived.push(one);\n else warnings.push(`index.skipped: ${file} has no run_started event`);\n } catch {\n warnings.push(`index.unreadable: ${file}`);\n }\n }\n\n await mkdir(path.dirname(dbPath), { recursive: true });\n // Remove any prior (possibly corrupt) file so the rebuild is deterministic.\n await rm(dbPath, { force: true });\n\n const db = new Database(dbPath);\n let runCount = 0;\n let stepCount = 0;\n let errorCount = 0;\n try {\n db.pragma(\"journal_mode = WAL\");\n db.exec(INDEX_SCHEMA_SQL);\n\n const insertRun = db.prepare(\n `INSERT INTO runs (run_id, file, mtime_ms, name, status, started_at, ended_at, duration_ms, session_id, group_id, correlation_id)\n VALUES (@runId, @file, @mtimeMs, @name, @status, @startedAt, @endedAt, @durationMs, @sessionId, @groupId, @correlationId)`,\n );\n const insertStep = db.prepare(\n `INSERT INTO steps (run_id, step_id, kind, name, status, duration_ms, tool_name, model, parent_id)\n VALUES (@runId, @stepId, @kind, @name, @status, @durationMs, @toolName, @model, @parentId)`,\n );\n const insertError = db.prepare(\n `INSERT INTO errors (run_id, step_id, message, code) VALUES (@runId, @stepId, @message, @code)`,\n );\n const insertMeta = db.prepare(`INSERT INTO meta (key, value) VALUES (?, ?)`);\n\n const write = db.transaction((items: DerivedRun[]) => {\n const seen = new Set<string>();\n for (const item of items) {\n if (seen.has(item.run.runId)) continue;\n seen.add(item.run.runId);\n insertRun.run(item.run);\n runCount += 1;\n for (const step of item.steps) {\n insertStep.run(step);\n stepCount += 1;\n }\n for (const err of item.errors) {\n insertError.run({ runId: item.run.runId, ...err });\n errorCount += 1;\n }\n }\n db.exec(DERIVE_SESSIONS_SQL);\n for (const [key, value] of Object.entries(metaDefaults(traceDir, slice.length))) {\n insertMeta.run(key, value);\n }\n });\n write(derived);\n } finally {\n db.close();\n }\n\n const builtAtRow = readMetaValue(dbPath, META_KEYS.builtAt);\n\n return {\n dbPath,\n traceDir,\n runs: runCount,\n steps: stepCount,\n errors: errorCount,\n builtAt: builtAtRow ?? new Date().toISOString(),\n warnings,\n };\n}\n\n/** Alias for {@link buildIndex}; a rebuild is a full, idempotent build. */\nexport const rebuildIndex = buildIndex;\n\nfunction readMetaValue(dbPath: string, key: string): string | null {\n try {\n const db = new Database(dbPath, { readonly: true, fileMustExist: true });\n try {\n const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key) as\n | { value: string }\n | undefined;\n return row?.value ?? null;\n } finally {\n db.close();\n }\n } catch {\n return null;\n }\n}\n\n/** Deletes the index database file. Always safe; traces are unaffected. */\nexport async function cleanIndex(dbPath: string): Promise<void> {\n await rm(dbPath, { force: true });\n await rm(`${dbPath}-wal`, { force: true });\n await rm(`${dbPath}-shm`, { force: true });\n}\n","import { existsSync } from \"node:fs\";\n\nimport Database from \"better-sqlite3\";\n\nimport { META_KEYS } from \"./schema.js\";\nimport type { IndexStatus, IndexedRun, RunQuery } from \"./types.js\";\n\ninterface OpenResult {\n db: Database.Database;\n healthy: boolean;\n}\n\n/**\n * Opens the index read-only and verifies integrity. Returns `null` when the\n * file is missing or fails `integrity_check` (corruption), so callers can fall\n * back to a full rebuild or a directory scan.\n */\nfunction openHealthy(dbPath: string): OpenResult | null {\n if (!existsSync(dbPath)) return null;\n try {\n const db = new Database(dbPath, { readonly: true, fileMustExist: true });\n try {\n const result = db.pragma(\"integrity_check\", { simple: true });\n if (result !== \"ok\") {\n db.close();\n return null;\n }\n // Confirm the expected schema is present.\n db.prepare(`SELECT 1 FROM meta LIMIT 1`).get();\n db.prepare(`SELECT 1 FROM runs LIMIT 1`).get();\n return { db, healthy: true };\n } catch {\n db.close();\n return null;\n }\n } catch {\n return null;\n }\n}\n\nfunction meta(db: Database.Database, key: string): string | null {\n const row = db.prepare(`SELECT value FROM meta WHERE key = ?`).get(key) as\n | { value: string }\n | undefined;\n return row?.value ?? null;\n}\n\n/** Reports index presence, health, and basic counts. Never throws. */\nexport function indexStatus(dbPath: string): IndexStatus {\n const opened = openHealthy(dbPath);\n if (!opened) {\n return {\n dbPath,\n exists: existsSync(dbPath),\n healthy: false,\n builtAt: null,\n sourceDir: null,\n schemaVersion: null,\n runs: 0,\n steps: 0,\n };\n }\n const { db } = opened;\n try {\n const runs = (db.prepare(`SELECT COUNT(*) AS c FROM runs`).get() as { c: number }).c;\n const steps = (db.prepare(`SELECT COUNT(*) AS c FROM steps`).get() as { c: number }).c;\n return {\n dbPath,\n exists: true,\n healthy: true,\n builtAt: meta(db, META_KEYS.builtAt),\n sourceDir: meta(db, META_KEYS.sourceDir),\n schemaVersion: meta(db, META_KEYS.schemaVersion),\n runs,\n steps,\n };\n } finally {\n db.close();\n }\n}\n\n/** Returns true when the index is missing, corrupt, or older than any trace. */\nexport function isIndexStale(dbPath: string, newestTraceMtimeMs: number): boolean {\n const opened = openHealthy(dbPath);\n if (!opened) return true;\n try {\n const builtAt = meta(opened.db, META_KEYS.builtAt);\n if (!builtAt) return true;\n const builtMs = Date.parse(builtAt);\n if (Number.isNaN(builtMs)) return true;\n return newestTraceMtimeMs > builtMs;\n } finally {\n opened.db.close();\n }\n}\n\nfunction mapRow(row: Record<string, unknown>): IndexedRun {\n return {\n runId: row.run_id as string,\n file: row.file as string,\n mtimeMs: row.mtime_ms as number,\n name: (row.name as string | null) ?? null,\n status: (row.status as string | null) ?? null,\n startedAt: (row.started_at as number | null) ?? null,\n endedAt: (row.ended_at as number | null) ?? null,\n durationMs: (row.duration_ms as number | null) ?? null,\n sessionId: (row.session_id as string | null) ?? null,\n groupId: (row.group_id as string | null) ?? null,\n correlationId: (row.correlation_id as string | null) ?? null,\n };\n}\n\n/**\n * Queries indexed runs. Returns an empty array when the index is missing or\n * corrupt (the caller should fall back to a directory scan).\n */\nexport function queryRuns(dbPath: string, query: RunQuery = {}): IndexedRun[] {\n const opened = openHealthy(dbPath);\n if (!opened) return [];\n const { db } = opened;\n try {\n const where: string[] = [];\n const params: Record<string, unknown> = {};\n\n if (query.status) {\n where.push(`r.status = @status`);\n params.status = query.status;\n }\n if (query.sessionId) {\n where.push(`r.session_id = @sessionId`);\n params.sessionId = query.sessionId;\n }\n if (query.name) {\n where.push(`LOWER(r.name) LIKE @name`);\n params.name = `%${query.name.toLowerCase()}%`;\n }\n if (query.kind) {\n where.push(`EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND s.kind = @kind)`);\n params.kind = query.kind;\n }\n if (query.tool) {\n where.push(\n `EXISTS (SELECT 1 FROM steps s WHERE s.run_id = r.run_id AND LOWER(s.tool_name) LIKE @tool)`,\n );\n params.tool = `%${query.tool.toLowerCase()}%`;\n }\n\n const limit = Number.isInteger(query.limit) && query.limit! > 0 ? query.limit! : 100;\n const sql = `SELECT r.* FROM runs r ${\n where.length ? `WHERE ${where.join(\" AND \")}` : \"\"\n } ORDER BY r.started_at DESC LIMIT ${limit}`;\n\n const rows = db.prepare(sql).all(params) as Array<Record<string, unknown>>;\n return rows.map(mapRow);\n } finally {\n db.close();\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-inspect/index-sqlite",
|
|
3
|
+
"version": "4.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Optional, disposable local SQLite index for faster queries over AgentInspect JSONL traces",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rajudandigam/agent-inspect.git",
|
|
10
|
+
"directory": "packages/index-sqlite"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/rajudandigam/agent-inspect/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/rajudandigam/agent-inspect/tree/main/packages/index-sqlite",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.mjs",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/index.d.cts",
|
|
28
|
+
"default": "./dist/index.cjs"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"README.md",
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"better-sqlite3": "^11.8.0",
|
|
41
|
+
"agent-inspect": "4.1.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/better-sqlite3": "^7.6.11"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "pnpm --workspace-root exec tsup --config tsup.index-sqlite.config.ts",
|
|
51
|
+
"test": "vitest run -c ../../vitest.config.ts"
|
|
52
|
+
}
|
|
53
|
+
}
|