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 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) dirs.push({ path: projDir, agent: 'claude-code' });
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
- dirs.push({ path: p, agent: 'codex-cli', recursive: true });
67
+ addDir({ path: p, agent: 'codex-cli', recursive: true });
61
68
  } else {
62
- dirs.push({ path: p, agent: path.basename(path.dirname(p)) });
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
- dirs.push({ path: sp, agent });
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
- dirs.push({ path: codexSessions, agent: 'codex-cli', recursive: true });
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)) dirs.push({ path: fallback, agent: 'main' });
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 = indexFile(db, f.path, f.agent, stmts, archiveMode, config);
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 = indexFile(db, filePath, dir.agent, stmts, archiveMode, config);
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 = indexFile(db, filePath, dir.agent, stmts, archiveMode, config);
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 = indexFile(db, filePath, dir.agent, stmts, archiveMode, config);
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.27",
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;