fix(failures): use is_error field instead of content pattern matching
Index the is_error boolean from JSONL tool_result blocks into a new column, replacing the old ERROR_PATS text-matching approach that produced ~90% false positives. Bash exit code pattern kept as fallback.
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
plans/
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ All list-returning functions accept a common filter opts object: `{ project, aft
|
|||||||
- `workflowTree(runId)` -- workflow + its agents + all their messages
|
- `workflowTree(runId)` -- workflow + its agents + all their messages
|
||||||
- `fileHistory(filePath, opts?)` -- every Edit/Write/Read on a file. opts: `{ after, before, limit }`
|
- `fileHistory(filePath, opts?)` -- every Edit/Write/Read on a file. opts: `{ after, before, limit }`
|
||||||
- `failures(opts?)` -- tool calls that returned errors, with surrounding context. opts: `{ sessionId, project, after, before, limit }`
|
- `failures(opts?)` -- tool calls that returned errors, with surrounding context. opts: `{ sessionId, project, after, before, limit }`
|
||||||
- `summaries(opts?)` -- session summaries (away recaps, compaction summaries). opts: `{ sessionId, project, after, before, limit, sessions }`
|
- `summaries(opts?)` -- session summaries (away recaps, compaction summaries). opts: `{ sessionId, project, after, before, limit, sessions }`. Returns: `[{ id, session_id, timestamp, source, content, session_title, project }]`. Use `source` for values like `away_summary`; use `content` for the summary text.
|
||||||
- `raw(uuid, opts?)` -- windowed access to the original JSONL line (bypasses index truncation)
|
- `raw(uuid, opts?)` -- windowed access to the original JSONL line (bypasses index truncation)
|
||||||
|
|
||||||
### Retrieval strategy
|
### Retrieval strategy
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ CREATE TABLE IF NOT EXISTS tool_calls (
|
|||||||
name TEXT, input_json TEXT, file_path TEXT);
|
name TEXT, input_json TEXT, file_path TEXT);
|
||||||
CREATE TABLE IF NOT EXISTS tool_results (
|
CREATE TABLE IF NOT EXISTS tool_results (
|
||||||
tool_use_id TEXT PRIMARY KEY, message_uuid TEXT, session_id TEXT,
|
tool_use_id TEXT PRIMARY KEY, message_uuid TEXT, session_id TEXT,
|
||||||
content TEXT, file_path TEXT);
|
content TEXT, file_path TEXT, is_error INTEGER DEFAULT 0);
|
||||||
CREATE TABLE IF NOT EXISTS subagents (
|
CREATE TABLE IF NOT EXISTS subagents (
|
||||||
agent_id TEXT PRIMARY KEY, session_id TEXT, parent_tool_use_id TEXT,
|
agent_id TEXT PRIMARY KEY, session_id TEXT, parent_tool_use_id TEXT,
|
||||||
agent_type TEXT, description TEXT, duration_ms INTEGER, total_tokens INTEGER);
|
agent_type TEXT, description TEXT, duration_ms INTEGER, total_tokens INTEGER);
|
||||||
|
|||||||
+2
-2
@@ -61,7 +61,7 @@ function indexJsonl(db, fi) {
|
|||||||
ses: db.prepare('INSERT OR REPLACE INTO sessions (id,title,project,project_path,started_at,ended_at,git_branch,version,message_count,jsonl_path) VALUES (?,?,?,?,?,?,?,?,?,?)'),
|
ses: db.prepare('INSERT OR REPLACE INTO sessions (id,title,project,project_path,started_at,ended_at,git_branch,version,message_count,jsonl_path) VALUES (?,?,?,?,?,?,?,?,?,?)'),
|
||||||
msg: db.prepare('INSERT OR REPLACE INTO messages (uuid,session_id,type,parent_uuid,timestamp,role,text,model,is_sidechain,agent_id,input_tokens,output_tokens) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)'),
|
msg: db.prepare('INSERT OR REPLACE INTO messages (uuid,session_id,type,parent_uuid,timestamp,role,text,model,is_sidechain,agent_id,input_tokens,output_tokens) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)'),
|
||||||
tc: db.prepare('INSERT OR REPLACE INTO tool_calls (id,message_uuid,session_id,name,input_json,file_path) VALUES (?,?,?,?,?,?)'),
|
tc: db.prepare('INSERT OR REPLACE INTO tool_calls (id,message_uuid,session_id,name,input_json,file_path) VALUES (?,?,?,?,?,?)'),
|
||||||
tr: db.prepare('INSERT OR REPLACE INTO tool_results (tool_use_id,message_uuid,session_id,content,file_path) VALUES (?,?,?,?,?)'),
|
tr: db.prepare('INSERT OR REPLACE INTO tool_results (tool_use_id,message_uuid,session_id,content,file_path,is_error) VALUES (?,?,?,?,?,?)'),
|
||||||
sum: db.prepare('INSERT OR REPLACE INTO summaries (id,session_id,timestamp,source,content) VALUES (?,?,?,?,?)'),
|
sum: db.prepare('INSERT OR REPLACE INTO summaries (id,session_id,timestamp,source,content) VALUES (?,?,?,?,?)'),
|
||||||
idx: db.prepare('INSERT OR REPLACE INTO index_state (jsonl_path,mtime,lines_processed) VALUES (?,?,?)'),
|
idx: db.prepare('INSERT OR REPLACE INTO index_state (jsonl_path,mtime,lines_processed) VALUES (?,?,?)'),
|
||||||
};
|
};
|
||||||
@@ -121,7 +121,7 @@ function indexJsonl(db, fi) {
|
|||||||
if (b.type !== 'tool_result' || !b.tool_use_id) continue;
|
if (b.type !== 'tool_result' || !b.tool_use_id) continue;
|
||||||
const rt = typeof b.content === 'string' ? b.content
|
const rt = typeof b.content === 'string' ? b.content
|
||||||
: Array.isArray(b.content) ? b.content.map(c => c.text || '').join('\n') : '';
|
: Array.isArray(b.content) ? b.content.map(c => c.text || '').join('\n') : '';
|
||||||
ins.tr.run(b.tool_use_id, obj.uuid, sid, trunc(rt), obj.toolUseResult?.filePath || null);
|
ins.tr.run(b.tool_use_id, obj.uuid, sid, trunc(rt), obj.toolUseResult?.filePath || null, b.is_error ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-6
@@ -1,7 +1,5 @@
|
|||||||
import { openDb, readLines, fs, path } from './db.mjs';
|
import { openDb, readLines, fs, path } from './db.mjs';
|
||||||
|
|
||||||
const ERROR_PATS = ['error','Error','ENOENT','failed','Failed','FAILED','permission denied','Permission denied','EPERM','EACCES','command not found','No such file','Exit code'];
|
|
||||||
|
|
||||||
function normalizeOpts(optsOrScalar, scalarKey = 'sessionId') {
|
function normalizeOpts(optsOrScalar, scalarKey = 'sessionId') {
|
||||||
if (optsOrScalar == null) return {};
|
if (optsOrScalar == null) return {};
|
||||||
if (typeof optsOrScalar === 'string') return { [scalarKey]: optsOrScalar };
|
if (typeof optsOrScalar === 'string') return { [scalarKey]: optsOrScalar };
|
||||||
@@ -24,6 +22,8 @@ function buildWhere(opts, aliases) {
|
|||||||
return { where: clauses.length ? clauses.join(' AND ') : '1=1', params };
|
return { where: clauses.length ? clauses.join(' AND ') : '1=1', params };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BASH_EXIT_PAT = 'Exit code [1-9]%';
|
||||||
|
|
||||||
function createQueryApi(db) {
|
function createQueryApi(db) {
|
||||||
const q = (sql, ...p) => db.prepare(sql).all(...p);
|
const q = (sql, ...p) => db.prepare(sql).all(...p);
|
||||||
|
|
||||||
@@ -129,13 +129,12 @@ function createQueryApi(db) {
|
|||||||
const failures = (optsOrSid) => {
|
const failures = (optsOrSid) => {
|
||||||
const opts = normalizeOpts(optsOrSid);
|
const opts = normalizeOpts(optsOrSid);
|
||||||
const { limit = 50 } = opts;
|
const { limit = 50 } = opts;
|
||||||
const likeClauses = ERROR_PATS.map(() => 'tr.content LIKE ?').join(' OR ');
|
|
||||||
const likeParams = ERROR_PATS.map(p => `%${p}%`);
|
|
||||||
const needsJoin = opts.project || opts.branch;
|
const needsJoin = opts.project || opts.branch;
|
||||||
const { where, params: filterParams } = buildWhere(opts, { sessionId: 'tr.session_id', project: 's.project', timestamp: 'rm.timestamp', branch: 's.git_branch' });
|
const { where, params: filterParams } = buildWhere(opts, { sessionId: 'tr.session_id', project: 's.project', timestamp: 'rm.timestamp', branch: 's.git_branch' });
|
||||||
const join = needsJoin ? 'LEFT JOIN sessions s ON s.id=tr.session_id' : '';
|
const join = needsJoin ? 'LEFT JOIN sessions s ON s.id=tr.session_id' : '';
|
||||||
const allParams = [...likeParams, ...filterParams, limit];
|
const errorCond = `(tr.is_error = 1 OR tr.content LIKE '${BASH_EXIT_PAT}')`;
|
||||||
const rows = db.prepare(`SELECT tr.* FROM tool_results tr ${join} LEFT JOIN messages rm ON rm.uuid=tr.message_uuid WHERE (${likeClauses}) AND ${where} LIMIT ?`).all(...allParams);
|
const allParams = [...filterParams, limit];
|
||||||
|
const rows = db.prepare(`SELECT tr.* FROM tool_results tr ${join} LEFT JOIN messages rm ON rm.uuid=tr.message_uuid WHERE ${errorCond} AND ${where} LIMIT ?`).all(...allParams);
|
||||||
return rows.map(r => {
|
return rows.map(r => {
|
||||||
const tc = db.prepare('SELECT * FROM tool_calls WHERE id=?').get(r.tool_use_id);
|
const tc = db.prepare('SELECT * FROM tool_calls WHERE id=?').get(r.tool_use_id);
|
||||||
const session = db.prepare('SELECT * FROM sessions WHERE id=?').get(r.session_id);
|
const session = db.prepare('SELECT * FROM sessions WHERE id=?').get(r.session_id);
|
||||||
|
|||||||
Reference in New Issue
Block a user