agentacta 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,7 +108,8 @@ On first run, AgentActa creates a config file with sensible defaults at `~/.conf
108
108
  "port": 4003,
109
109
  "storage": "reference",
110
110
  "sessionsPath": null,
111
- "dbPath": "./agentacta.db"
111
+ "dbPath": "./agentacta.db",
112
+ "projectAliases": {}
112
113
  }
113
114
  ```
114
115
 
@@ -126,6 +127,7 @@ On first run, AgentActa creates a config file with sensible defaults at `~/.conf
126
127
  | `AGENTACTA_SESSIONS_PATH` | Auto-detected | Custom sessions directory |
127
128
  | `AGENTACTA_DB_PATH` | `./agentacta.db` | Database file location |
128
129
  | `AGENTACTA_STORAGE` | `reference` | Storage mode (`reference` or `archive`) |
130
+ | `AGENTACTA_PROJECT_ALIASES_JSON` | unset | JSON object mapping inferred project names (e.g. `{"old-name":"new-name"}`) |
129
131
 
130
132
  ## API
131
133
 
package/config.js CHANGED
@@ -18,7 +18,8 @@ const DEFAULTS = {
18
18
  port: 4003,
19
19
  storage: 'reference',
20
20
  sessionsPath: null,
21
- dbPath: './agentacta.db'
21
+ dbPath: './agentacta.db',
22
+ projectAliases: {}
22
23
  };
23
24
 
24
25
  function loadConfig() {
@@ -45,9 +46,17 @@ function loadConfig() {
45
46
  if (process.env.AGENTACTA_STORAGE) config.storage = process.env.AGENTACTA_STORAGE;
46
47
  if (process.env.AGENTACTA_SESSIONS_PATH) config.sessionsPath = process.env.AGENTACTA_SESSIONS_PATH;
47
48
  if (process.env.AGENTACTA_DB_PATH) config.dbPath = process.env.AGENTACTA_DB_PATH;
49
+ if (process.env.AGENTACTA_PROJECT_ALIASES_JSON) {
50
+ try {
51
+ config.projectAliases = JSON.parse(process.env.AGENTACTA_PROJECT_ALIASES_JSON);
52
+ } catch (err) {
53
+ console.error('Warning: Could not parse AGENTACTA_PROJECT_ALIASES_JSON:', err.message);
54
+ }
55
+ }
48
56
 
49
57
  // Resolve dbPath relative to cwd
50
58
  config.dbPath = path.resolve(config.dbPath);
59
+ if (!config.projectAliases || typeof config.projectAliases !== 'object') config.projectAliases = {};
51
60
 
52
61
  return config;
53
62
  }
package/index.js CHANGED
@@ -97,6 +97,31 @@ function getDbSize() {
97
97
  }
98
98
  }
99
99
 
100
+ function normalizeAgentLabel(agent) {
101
+ if (!agent) return agent;
102
+ if (agent === 'main') return 'openclaw-main';
103
+ if (agent.startsWith('claude-') || agent.startsWith('claude--')) return 'claude-code';
104
+ return agent;
105
+ }
106
+
107
+ function looksLikeSessionId(q) {
108
+ const s = (q || '').trim();
109
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
110
+ }
111
+
112
+ function toFtsQuery(q) {
113
+ const s = (q || '').trim();
114
+ if (!s) return '';
115
+ // Quote each token so dashes and punctuation don't break FTS parsing.
116
+ // Example: abc-def -> "abc-def"
117
+ const tokens = s.match(/"[^"]+"|\S+/g) || [];
118
+ return tokens
119
+ .map(t => t.replace(/^"|"$/g, '').replace(/"/g, '""'))
120
+ .filter(Boolean)
121
+ .map(t => `"${t}"`)
122
+ .join(' AND ');
123
+ }
124
+
100
125
  // Init DB and start watcher
101
126
  init();
102
127
  const db = open();
@@ -197,7 +222,11 @@ const server = http.createServer((req, res) => {
197
222
  const tools = db.prepare("SELECT DISTINCT tool_name FROM events WHERE tool_name IS NOT NULL").all().map(r => r.tool_name);
198
223
  const dateRange = db.prepare('SELECT MIN(start_time) as earliest, MAX(start_time) as latest FROM sessions').get();
199
224
  const costData = db.prepare('SELECT SUM(total_cost) as cost, SUM(total_tokens) as tokens FROM sessions').get();
200
- const agents = db.prepare('SELECT DISTINCT agent FROM sessions WHERE agent IS NOT NULL').all().map(r => r.agent);
225
+ const agents = [...new Set(
226
+ db.prepare('SELECT DISTINCT agent FROM sessions WHERE agent IS NOT NULL').all()
227
+ .map(r => normalizeAgentLabel(r.agent))
228
+ .filter(Boolean)
229
+ )];
201
230
  const dbSize = getDbSize();
202
231
  json(res, { sessions, events, messages, toolCalls, uniqueTools: tools.length, tools, dateRange, totalCost: costData.cost || 0, totalTokens: costData.tokens || 0, agents, storageMode: config.storage, dbSize, sessionDirs: sessionDirs.map(d => ({ path: d.path, agent: d.agent })) });
203
232
  }
@@ -256,7 +285,12 @@ const server = http.createServer((req, res) => {
256
285
  if (!q) { json(res, { error: 'No query' }, 400); return; }
257
286
  let results;
258
287
  try {
259
- results = db.prepare(`SELECT e.*, s.start_time as session_start, s.summary as session_summary FROM events_fts fts JOIN events e ON e.rowid = fts.rowid JOIN sessions s ON s.id = e.session_id WHERE events_fts MATCH ? ORDER BY e.timestamp DESC LIMIT 200`).all(q);
288
+ if (looksLikeSessionId(q)) {
289
+ results = db.prepare(`SELECT e.*, s.start_time as session_start, s.summary as session_summary FROM events e JOIN sessions s ON s.id = e.session_id WHERE e.session_id = ? ORDER BY e.timestamp DESC LIMIT 200`).all(q.trim());
290
+ } else {
291
+ const ftsQuery = toFtsQuery(q);
292
+ results = db.prepare(`SELECT e.*, s.start_time as session_start, s.summary as session_summary FROM events_fts fts JOIN events e ON e.rowid = fts.rowid JOIN sessions s ON s.id = e.session_id WHERE events_fts MATCH ? ORDER BY e.timestamp DESC LIMIT 200`).all(ftsQuery);
293
+ }
260
294
  } catch (err) { json(res, { error: 'Invalid search query' }, 400); return; }
261
295
  if (format === 'md') {
262
296
  let md = `# Search Results: "${q}"\n\n${results.length} results\n\n`;
@@ -280,18 +314,32 @@ const server = http.createServer((req, res) => {
280
314
 
281
315
  if (!q) { json(res, { results: [], total: 0 }); }
282
316
  else {
283
- let sql = `SELECT e.*, s.start_time as session_start, s.summary as session_summary
284
- FROM events_fts fts
285
- JOIN events e ON e.rowid = fts.rowid
286
- JOIN sessions s ON s.id = e.session_id
287
- WHERE events_fts MATCH ?`;
288
- const params = [q];
317
+ const isSessionLookup = looksLikeSessionId(q);
318
+ let sql;
319
+ const params = [];
320
+
321
+ if (isSessionLookup) {
322
+ sql = `SELECT e.*, s.start_time as session_start, s.summary as session_summary
323
+ FROM events e
324
+ JOIN sessions s ON s.id = e.session_id
325
+ WHERE e.session_id = ?`;
326
+ params.push(q.trim());
327
+ } else {
328
+ sql = `SELECT e.*, s.start_time as session_start, s.summary as session_summary
329
+ FROM events_fts fts
330
+ JOIN events e ON e.rowid = fts.rowid
331
+ JOIN sessions s ON s.id = e.session_id
332
+ WHERE events_fts MATCH ?`;
333
+ params.push(toFtsQuery(q));
334
+ }
335
+
289
336
  if (type) { sql += ` AND e.type = ?`; params.push(type); }
290
337
  if (role) { sql += ` AND e.role = ?`; params.push(role); }
291
338
  if (from) { sql += ` AND e.timestamp >= ?`; params.push(from); }
292
339
  if (to) { sql += ` AND e.timestamp <= ?`; params.push(to); }
293
340
  sql += ` ORDER BY e.timestamp DESC LIMIT ?`;
294
341
  params.push(limit);
342
+
295
343
  try {
296
344
  const results = db.prepare(sql).all(...params);
297
345
  json(res, { results, total: results.length });
package/indexer.js CHANGED
@@ -38,12 +38,12 @@ function discoverSessionDirs(config) {
38
38
  // Claude Code: JSONL files directly in project dir
39
39
  if (fs.existsSync(projDir) && fs.statSync(projDir).isDirectory()) {
40
40
  const hasJsonl = fs.readdirSync(projDir).some(f => f.endsWith('.jsonl'));
41
- if (hasJsonl) dirs.push({ path: projDir, agent: `claude-${proj}` });
41
+ if (hasJsonl) dirs.push({ path: projDir, agent: 'claude-code' });
42
42
  }
43
43
  // Also check sessions/ subdirectory (future-proofing)
44
44
  const sp = path.join(projDir, 'sessions');
45
45
  if (fs.existsSync(sp) && fs.statSync(sp).isDirectory()) {
46
- dirs.push({ path: sp, agent: `claude-${proj}` });
46
+ dirs.push({ path: sp, agent: 'claude-code' });
47
47
  }
48
48
  }
49
49
  }
@@ -107,13 +107,19 @@ function extractFilePaths(toolName, toolArgs) {
107
107
  return paths;
108
108
  }
109
109
 
110
- function extractProjectFromPath(filePath) {
110
+ function aliasProject(project, config) {
111
+ if (!project) return project;
112
+ const aliases = (config && config.projectAliases && typeof config.projectAliases === 'object') ? config.projectAliases : {};
113
+ return aliases[project] || project;
114
+ }
115
+
116
+ function extractProjectFromPath(filePath, config) {
111
117
  if (!filePath || typeof filePath !== 'string') return null;
112
118
 
113
119
  const normalized = filePath.replace(/\\/g, '/');
114
120
 
115
121
  // Relative paths are usually from workspace cwd -> treat as workspace activity
116
- if (!normalized.startsWith('/') && !normalized.startsWith('~')) return 'workspace';
122
+ if (!normalized.startsWith('/') && !normalized.startsWith('~')) return aliasProject('workspace', config);
117
123
 
118
124
  let rel = normalized
119
125
  .replace(/^\/home\/[^/]+\//, '')
@@ -124,22 +130,22 @@ function extractProjectFromPath(filePath) {
124
130
  if (!parts.length) return null;
125
131
 
126
132
  // Common repo location: ~/Developer/<repo>/...
127
- if (parts[0] === 'Developer' && parts[1]) return parts[1];
133
+ if (parts[0] === 'Developer' && parts[1]) return aliasProject(parts[1], config);
128
134
 
129
135
  // OpenClaw workspace and agent stores
130
- if (parts[0] === '.openclaw' && parts[1] === 'workspace') return 'workspace';
131
- if (parts[0] === '.openclaw' && parts[1] === 'agents' && parts[2]) return `agent:${parts[2]}`;
136
+ if (parts[0] === '.openclaw' && parts[1] === 'workspace') return aliasProject('workspace', config);
137
+ if (parts[0] === '.openclaw' && parts[1] === 'agents' && parts[2]) return aliasProject(`agent:${parts[2]}`, config);
132
138
 
133
139
  // Claude Code projects
134
- if (parts[0] === '.claude' && parts[1] === 'projects' && parts[2]) return `claude:${parts[2]}`;
140
+ if (parts[0] === '.claude' && parts[1] === 'projects' && parts[2]) return aliasProject(`claude:${parts[2]}`, config);
135
141
 
136
142
  // Shared files area
137
- if (parts[0] === 'Shared') return 'shared';
143
+ if (parts[0] === 'Shared') return aliasProject('shared', config);
138
144
 
139
145
  return null;
140
146
  }
141
147
 
142
- function indexFile(db, filePath, agentName, stmts, archiveMode) {
148
+ function indexFile(db, filePath, agentName, stmts, archiveMode, config) {
143
149
  const stat = fs.statSync(filePath);
144
150
  const mtime = stat.mtime.toISOString();
145
151
 
@@ -312,7 +318,7 @@ function indexFile(db, filePath, agentName, stmts, archiveMode) {
312
318
  : 'read';
313
319
  fileActivities.push([sessionId, fp, op, ts]);
314
320
 
315
- const project = extractProjectFromPath(fp);
321
+ const project = extractProjectFromPath(fp, config);
316
322
  if (project) projectCounts.set(project, (projectCounts.get(project) || 0) + 1);
317
323
  }
318
324
  }
@@ -391,7 +397,7 @@ function run() {
391
397
  const indexMany = db.transaction(() => {
392
398
  let indexed = 0;
393
399
  for (const f of allFiles) {
394
- const result = indexFile(db, f.path, f.agent, stmts, archiveMode);
400
+ const result = indexFile(db, f.path, f.agent, stmts, archiveMode, config);
395
401
  if (!result.skipped) {
396
402
  indexed++;
397
403
  if (indexed % 10 === 0) process.stdout.write('.');
@@ -416,7 +422,7 @@ function run() {
416
422
  if (!fs.existsSync(filePath)) return;
417
423
  setTimeout(() => {
418
424
  try {
419
- const result = indexFile(db, filePath, dir.agent, stmts, archiveMode);
425
+ const result = indexFile(db, filePath, dir.agent, stmts, archiveMode, config);
420
426
  if (!result.skipped) console.log(`Re-indexed: ${filename} (${dir.agent})`);
421
427
  } catch (err) {
422
428
  console.error(`Error re-indexing ${filename}:`, err.message);
@@ -437,7 +443,7 @@ function indexAll(db, config) {
437
443
  for (const dir of sessionDirs) {
438
444
  const files = fs.readdirSync(dir.path).filter(f => f.endsWith('.jsonl'));
439
445
  for (const file of files) {
440
- const result = indexFile(db, path.join(dir.path, file), dir.agent, stmts, archiveMode);
446
+ const result = indexFile(db, path.join(dir.path, file), dir.agent, stmts, archiveMode, config);
441
447
  if (!result.skipped) totalSessions++;
442
448
  }
443
449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentacta",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "Audit trail and search engine for AI agent sessions",
5
5
  "main": "index.js",
6
6
  "bin": {