agentacta 2026.3.27 → 2026.4.8
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/indexer.js +122 -19
- package/package.json +2 -2
- package/public/style.css +6 -1
package/indexer.js
CHANGED
|
@@ -29,14 +29,21 @@ function discoverSessionDirs(config) {
|
|
|
29
29
|
const dirs = [];
|
|
30
30
|
const home = process.env.HOME;
|
|
31
31
|
const codexSessionsPath = path.join(home, '.codex/sessions');
|
|
32
|
+
const cronRunsPath = path.join(home, '.openclaw/cron/runs');
|
|
32
33
|
|
|
33
34
|
function normalizedPath(p) {
|
|
34
35
|
return path.resolve(p).replace(/[\\\/]+$/, '');
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
function hasDir(targetPath) {
|
|
38
|
+
function hasDir(targetPath, sourceType = 'transcript') {
|
|
38
39
|
const wanted = normalizedPath(targetPath);
|
|
39
|
-
return dirs.some(d => normalizedPath(d.path) === wanted);
|
|
40
|
+
return dirs.some(d => normalizedPath(d.path) === wanted && (d.sourceType || 'transcript') === sourceType);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function addDir(dir) {
|
|
44
|
+
if (!dir || !dir.path) return;
|
|
45
|
+
if (hasDir(dir.path, dir.sourceType || 'transcript')) return;
|
|
46
|
+
dirs.push(dir);
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
// Expand a single path into session dirs, handling Claude Code's per-project structure
|
|
@@ -52,14 +59,14 @@ function discoverSessionDirs(config) {
|
|
|
52
59
|
const projDir = path.join(p, proj);
|
|
53
60
|
if (fs.statSync(projDir).isDirectory()) {
|
|
54
61
|
const hasJsonl = fs.readdirSync(projDir).some(f => f.endsWith('.jsonl'));
|
|
55
|
-
if (hasJsonl)
|
|
62
|
+
if (hasJsonl) addDir({ path: projDir, agent: 'claude-code' });
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
} else if (normalized === normalizedCodex) {
|
|
59
66
|
// Codex CLI stores nested YYYY/MM/DD directories and must be recursive.
|
|
60
|
-
|
|
67
|
+
addDir({ path: p, agent: 'codex-cli', recursive: true });
|
|
61
68
|
} else {
|
|
62
|
-
|
|
69
|
+
addDir({ path: p, agent: path.basename(path.dirname(p)) });
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
|
|
@@ -70,11 +77,6 @@ function discoverSessionDirs(config) {
|
|
|
70
77
|
? sessionsOverride
|
|
71
78
|
: sessionsOverride.split(':');
|
|
72
79
|
overridePaths.forEach(expandPath);
|
|
73
|
-
// Keep direct Codex visibility even when custom overrides omit it.
|
|
74
|
-
if (fs.existsSync(codexSessionsPath) && fs.statSync(codexSessionsPath).isDirectory() && !hasDir(codexSessionsPath)) {
|
|
75
|
-
dirs.push({ path: codexSessionsPath, agent: 'codex-cli', recursive: true });
|
|
76
|
-
}
|
|
77
|
-
if (dirs.length) return dirs;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
// Auto-discover: ~/.openclaw/agents/*/sessions/
|
|
@@ -83,7 +85,7 @@ function discoverSessionDirs(config) {
|
|
|
83
85
|
for (const agent of fs.readdirSync(oclawAgents)) {
|
|
84
86
|
const sp = path.join(oclawAgents, agent, 'sessions');
|
|
85
87
|
if (fs.existsSync(sp) && fs.statSync(sp).isDirectory()) {
|
|
86
|
-
|
|
88
|
+
addDir({ path: sp, agent });
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
}
|
|
@@ -94,13 +96,18 @@ function discoverSessionDirs(config) {
|
|
|
94
96
|
// Scan ~/.codex/sessions recursively (Codex CLI stores nested YYYY/MM/DD/*.jsonl)
|
|
95
97
|
const codexSessions = codexSessionsPath;
|
|
96
98
|
if (fs.existsSync(codexSessions) && fs.statSync(codexSessions).isDirectory()) {
|
|
97
|
-
|
|
99
|
+
addDir({ path: codexSessions, agent: 'codex-cli', recursive: true });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Fallback synthetic source for cron-backed runs that have metadata but no transcript JSONL.
|
|
103
|
+
if (fs.existsSync(cronRunsPath) && fs.statSync(cronRunsPath).isDirectory()) {
|
|
104
|
+
addDir({ path: cronRunsPath, agent: 'cron', sourceType: 'cron-run' });
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
if (!dirs.length) {
|
|
101
108
|
// Fallback to hardcoded
|
|
102
109
|
const fallback = path.join(home, '.openclaw/agents/main/sessions');
|
|
103
|
-
if (fs.existsSync(fallback))
|
|
110
|
+
if (fs.existsSync(fallback)) addDir({ path: fallback, agent: 'main' });
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
return dirs;
|
|
@@ -261,6 +268,94 @@ function extractProjectFromPath(filePath, config) {
|
|
|
261
268
|
return null;
|
|
262
269
|
}
|
|
263
270
|
|
|
271
|
+
function indexCronRunFile(db, filePath, agentName, stmts) {
|
|
272
|
+
const stat = fs.statSync(filePath);
|
|
273
|
+
const mtime = stat.mtime.toISOString();
|
|
274
|
+
|
|
275
|
+
if (!REINDEX) {
|
|
276
|
+
const state = stmts.getState.get(filePath);
|
|
277
|
+
if (state && state.last_modified === mtime) return { skipped: true };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const raw = fs.readFileSync(filePath, 'utf8').trim();
|
|
281
|
+
if (!raw) return { skipped: true };
|
|
282
|
+
|
|
283
|
+
let meta;
|
|
284
|
+
try {
|
|
285
|
+
meta = JSON.parse(raw.split('\n').find(Boolean));
|
|
286
|
+
} catch {
|
|
287
|
+
return { skipped: true };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const sessionId = meta.sessionId;
|
|
291
|
+
if (!sessionId) return { skipped: true };
|
|
292
|
+
|
|
293
|
+
// Guard: don't overwrite a session that was already indexed from a real transcript.
|
|
294
|
+
// Check both event presence AND session_type — a transcript session with zero events
|
|
295
|
+
// (e.g. header-only file) should still win over synthetic cron metadata.
|
|
296
|
+
const existingSession = db.prepare('SELECT session_type FROM sessions WHERE id = ?').get(sessionId);
|
|
297
|
+
if (existingSession && existingSession.session_type !== 'cron') {
|
|
298
|
+
stmts.upsertState.run(filePath, 1, mtime);
|
|
299
|
+
return { skipped: true, preferredTranscript: true, sessionId };
|
|
300
|
+
}
|
|
301
|
+
const existingRealSession = db.prepare('SELECT EXISTS(SELECT 1 FROM events WHERE session_id = ?) AS has_events').get(sessionId);
|
|
302
|
+
if (existingRealSession && existingRealSession.has_events) {
|
|
303
|
+
stmts.upsertState.run(filePath, 1, mtime);
|
|
304
|
+
return { skipped: true, preferredTranscript: true, sessionId };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const ts = typeof meta.ts === 'number' ? new Date(meta.ts).toISOString() : new Date().toISOString();
|
|
308
|
+
const runAt = typeof meta.runAtMs === 'number' ? new Date(meta.runAtMs).toISOString() : ts;
|
|
309
|
+
const durationMs = typeof meta.durationMs === 'number' ? meta.durationMs : null;
|
|
310
|
+
const endTime = ts;
|
|
311
|
+
const startTime = durationMs ? new Date(new Date(endTime).getTime() - durationMs).toISOString() : runAt;
|
|
312
|
+
const summary = stripLeadingDatetimePrefix(meta.summary || 'Cron run');
|
|
313
|
+
const sessionKey = typeof meta.sessionKey === 'string' ? meta.sessionKey : '';
|
|
314
|
+
const sessionKeyParts = sessionKey.split(':');
|
|
315
|
+
const inferredAgent = sessionKeyParts[0] === 'agent' && sessionKeyParts[1] ? sessionKeyParts[1] : agentName;
|
|
316
|
+
const model = meta.model || meta.provider || null;
|
|
317
|
+
const totalInputTokens = meta.usage && typeof meta.usage.input_tokens === 'number' ? meta.usage.input_tokens : 0;
|
|
318
|
+
const totalOutputTokens = meta.usage && typeof meta.usage.output_tokens === 'number' ? meta.usage.output_tokens : 0;
|
|
319
|
+
const totalTokens = meta.usage && typeof meta.usage.total_tokens === 'number'
|
|
320
|
+
? meta.usage.total_tokens
|
|
321
|
+
: totalInputTokens + totalOutputTokens;
|
|
322
|
+
|
|
323
|
+
const commitIndex = db.transaction(() => {
|
|
324
|
+
if (stmts.deleteArchive) stmts.deleteArchive.run(sessionId);
|
|
325
|
+
stmts.deleteFileActivity.run(sessionId);
|
|
326
|
+
stmts.deleteEvents.run(sessionId);
|
|
327
|
+
stmts.deleteSession.run(sessionId);
|
|
328
|
+
|
|
329
|
+
stmts.upsertSession.run(
|
|
330
|
+
sessionId,
|
|
331
|
+
startTime,
|
|
332
|
+
endTime,
|
|
333
|
+
0,
|
|
334
|
+
0,
|
|
335
|
+
model,
|
|
336
|
+
summary,
|
|
337
|
+
inferredAgent,
|
|
338
|
+
'cron',
|
|
339
|
+
0,
|
|
340
|
+
totalTokens,
|
|
341
|
+
totalInputTokens,
|
|
342
|
+
totalOutputTokens,
|
|
343
|
+
0,
|
|
344
|
+
0,
|
|
345
|
+
null,
|
|
346
|
+
null,
|
|
347
|
+
null,
|
|
348
|
+
model ? JSON.stringify([model]) : null,
|
|
349
|
+
null
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
stmts.upsertState.run(filePath, 1, mtime);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
commitIndex();
|
|
356
|
+
return { sessionId, synthetic: true };
|
|
357
|
+
}
|
|
358
|
+
|
|
264
359
|
function indexFile(db, filePath, agentName, stmts, archiveMode, config) {
|
|
265
360
|
const stat = fs.statSync(filePath);
|
|
266
361
|
const mtime = stat.mtime.toISOString();
|
|
@@ -664,7 +759,7 @@ function run() {
|
|
|
664
759
|
let allFiles = [];
|
|
665
760
|
for (const dir of sessionDirs) {
|
|
666
761
|
const files = listJsonlFiles(dir.path, !!dir.recursive)
|
|
667
|
-
.map(filePath => ({ path: filePath, agent: dir.agent }));
|
|
762
|
+
.map(filePath => ({ path: filePath, agent: dir.agent, sourceType: dir.sourceType || 'transcript' }));
|
|
668
763
|
allFiles.push(...files);
|
|
669
764
|
}
|
|
670
765
|
|
|
@@ -673,7 +768,9 @@ function run() {
|
|
|
673
768
|
const indexMany = db.transaction(() => {
|
|
674
769
|
let indexed = 0;
|
|
675
770
|
for (const f of allFiles) {
|
|
676
|
-
const result =
|
|
771
|
+
const result = f.sourceType === 'cron-run'
|
|
772
|
+
? indexCronRunFile(db, f.path, f.agent, stmts)
|
|
773
|
+
: indexFile(db, f.path, f.agent, stmts, archiveMode, config);
|
|
677
774
|
if (!result.skipped) {
|
|
678
775
|
indexed++;
|
|
679
776
|
if (indexed % 10 === 0) process.stdout.write('.');
|
|
@@ -706,7 +803,9 @@ function run() {
|
|
|
706
803
|
const files = listJsonlFiles(dir.path, true);
|
|
707
804
|
let changed = 0;
|
|
708
805
|
for (const filePath of files) {
|
|
709
|
-
const result =
|
|
806
|
+
const result = dir.sourceType === 'cron-run'
|
|
807
|
+
? indexCronRunFile(db, filePath, dir.agent, stmts)
|
|
808
|
+
: indexFile(db, filePath, dir.agent, stmts, archiveMode, config);
|
|
710
809
|
if (!result.skipped) changed++;
|
|
711
810
|
}
|
|
712
811
|
if (changed > 0) console.log(`Re-indexed ${changed} files (${dir.agent})`);
|
|
@@ -723,7 +822,9 @@ function run() {
|
|
|
723
822
|
if (!fs.existsSync(filePath)) return;
|
|
724
823
|
setTimeout(() => {
|
|
725
824
|
try {
|
|
726
|
-
const result =
|
|
825
|
+
const result = dir.sourceType === 'cron-run'
|
|
826
|
+
? indexCronRunFile(db, filePath, dir.agent, stmts)
|
|
827
|
+
: indexFile(db, filePath, dir.agent, stmts, archiveMode, config);
|
|
727
828
|
if (!result.skipped) console.log(`Re-indexed: ${filename} (${dir.agent})`);
|
|
728
829
|
} catch (err) {
|
|
729
830
|
console.error(`Error re-indexing ${filename}:`, err.message);
|
|
@@ -745,7 +846,9 @@ function indexAll(db, config) {
|
|
|
745
846
|
const files = listJsonlFiles(dir.path, !!dir.recursive);
|
|
746
847
|
for (const filePath of files) {
|
|
747
848
|
try {
|
|
748
|
-
const result =
|
|
849
|
+
const result = dir.sourceType === 'cron-run'
|
|
850
|
+
? indexCronRunFile(db, filePath, dir.agent, stmts)
|
|
851
|
+
: indexFile(db, filePath, dir.agent, stmts, archiveMode, config);
|
|
749
852
|
if (!result.skipped) totalSessions++;
|
|
750
853
|
} catch (err) {
|
|
751
854
|
console.error(`Error indexing ${path.basename(filePath)}:`, err.message);
|
|
@@ -757,6 +860,6 @@ function indexAll(db, config) {
|
|
|
757
860
|
return { sessions: stats.sessions, events: evStats.events, newSessions: totalSessions };
|
|
758
861
|
}
|
|
759
862
|
|
|
760
|
-
module.exports = { discoverSessionDirs, listJsonlFiles, indexFile, indexAll };
|
|
863
|
+
module.exports = { discoverSessionDirs, listJsonlFiles, indexFile, indexCronRunFile, indexAll };
|
|
761
864
|
|
|
762
865
|
if (require.main === module) run();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentacta",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.4.8",
|
|
4
4
|
"description": "Audit trail and search engine for AI agent sessions",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -54,4 +54,4 @@
|
|
|
54
54
|
"tar-fs": "^3.0.6",
|
|
55
55
|
"npmlog": "^7.0.1"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
package/public/style.css
CHANGED
|
@@ -162,6 +162,8 @@ body {
|
|
|
162
162
|
-webkit-font-smoothing: antialiased;
|
|
163
163
|
-moz-osx-font-smoothing: grayscale;
|
|
164
164
|
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
|
|
165
|
+
overscroll-behavior-y: none;
|
|
166
|
+
overflow-x: hidden;
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
html.cmdk-open,
|
|
@@ -563,6 +565,8 @@ body.cmdk-open {
|
|
|
563
565
|
cursor: pointer;
|
|
564
566
|
transition: all var(--duration-normal) var(--ease-out);
|
|
565
567
|
position: relative;
|
|
568
|
+
overflow: hidden;
|
|
569
|
+
max-width: 100%;
|
|
566
570
|
}
|
|
567
571
|
|
|
568
572
|
.session-item:hover {
|
|
@@ -1550,7 +1554,8 @@ mark {
|
|
|
1550
1554
|
bottom: calc(24px + env(safe-area-inset-bottom, 0px));
|
|
1551
1555
|
left: 50%;
|
|
1552
1556
|
right: auto;
|
|
1553
|
-
transform: translateX(-50%);
|
|
1557
|
+
transform: translateX(-50%) translateZ(0);
|
|
1558
|
+
-webkit-transform: translateX(-50%) translateZ(0);
|
|
1554
1559
|
width: auto;
|
|
1555
1560
|
height: auto;
|
|
1556
1561
|
border-right: none;
|