feat(query): unified filter opts across all list-returning APIs
Add sessions() as first-class entry point for session discovery with project/time/branch filters. Extend summaries, workflows, failures, subagents, fileHistory with the same opts vocabulary that search() already had — no more pulling full datasets to filter client-side.
This commit is contained in:
@@ -44,6 +44,17 @@ Returns: `[{ message: {uuid, text, role, timestamp, model}, session: {id, title,
|
|||||||
|
|
||||||
opts: `{ limit, sessionId, project, after, before }`
|
opts: `{ limit, sessionId, project, after, before }`
|
||||||
|
|
||||||
|
### sessions(opts?)
|
||||||
|
|
||||||
|
Query sessions with filters. Returns session rows ordered by `ended_at` descending.
|
||||||
|
|
||||||
|
opts: `{ project, after, before, limit, branch, sessionId, sessions }`
|
||||||
|
|
||||||
|
```js
|
||||||
|
sessions({ project: '%quiet-zero%' })
|
||||||
|
sessions({ after: '2026-06-01', branch: 'main', limit: 5 })
|
||||||
|
```
|
||||||
|
|
||||||
### context(uuid)
|
### context(uuid)
|
||||||
|
|
||||||
Full story around a message: the message itself, parent chain, session info, subagent/workflow metadata.
|
Full story around a message: the message itself, parent chain, session info, subagent/workflow metadata.
|
||||||
@@ -52,7 +63,7 @@ Returns: `{ message, parentChain, session, subagent, workflow }`
|
|||||||
|
|
||||||
### recent(n?)
|
### recent(n?)
|
||||||
|
|
||||||
Latest n sessions (default 10). Returns session rows with title, project, started_at, ended_at.
|
Shorthand for `sessions({ limit: n })`. Latest n sessions (default 10).
|
||||||
|
|
||||||
### sql(query, ...params)
|
### sql(query, ...params)
|
||||||
|
|
||||||
@@ -64,22 +75,25 @@ Tables: `sessions`, `messages`, `tool_calls`, `tool_results`, `subagents`, `work
|
|||||||
|
|
||||||
### Other APIs
|
### Other APIs
|
||||||
|
|
||||||
|
All list-returning functions accept a common filter opts object: `{ project, after, before, limit, sessionId, sessions }`. For backward compatibility, passing a string is treated as `sessionId`, a number as `limit`.
|
||||||
|
|
||||||
- `trace(uuid)` -- full parent chain from root to message
|
- `trace(uuid)` -- full parent chain from root to message
|
||||||
- `thread(sessionId)` -- all messages in a session, ordered by time
|
- `thread(sessionId)` -- all messages in a session, ordered by time
|
||||||
- `subagents(sessionId)` -- subagent metadata + message counts
|
- `subagents(opts?)` -- subagent metadata + message counts. opts: `{ sessionId, project, limit }`
|
||||||
- `workflows(sessionId?)` -- workflow runs (all if no sessionId)
|
- `workflows(opts?)` -- workflow runs. opts: `{ sessionId, project, after, before, limit }`
|
||||||
- `workflowTree(runId)` -- workflow + its agents + all their messages
|
- `workflowTree(runId)` -- workflow + its agents + all their messages
|
||||||
- `fileHistory(filePath)` -- every Edit/Write/Read on a file across sessions
|
- `fileHistory(filePath, opts?)` -- every Edit/Write/Read on a file. opts: `{ after, before, limit }`
|
||||||
- `failures(sessionId?)` -- tool calls that returned errors, with surrounding context
|
- `failures(opts?)` -- tool calls that returned errors, with surrounding context. opts: `{ sessionId, project, after, before, limit }`
|
||||||
- `summaries(sessionId?)` -- session summaries (away recaps, compaction summaries when available)
|
- `summaries(opts?)` -- session summaries (away recaps, compaction summaries). opts: `{ sessionId, project, after, before, limit, sessions }`
|
||||||
- `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
|
||||||
|
|
||||||
**Never pull an entire session.** Navigate incrementally:
|
**Never pull an entire session.** Navigate incrementally:
|
||||||
|
|
||||||
1. `summaries()` — read session summaries to judge relevance (cheapest)
|
1. `sessions({ project: '...' })` or `recent()` — find relevant sessions
|
||||||
2. `search()` — find specific messages matching a query
|
2. `summaries({ project: '...' })` — read session summaries to judge relevance (cheapest)
|
||||||
|
3. `search()` — find specific messages matching a query
|
||||||
3. When you find a relevant message and want more context, expand from that point:
|
3. When you find a relevant message and want more context, expand from that point:
|
||||||
- **Horizontally**: use `sql()` to fetch neighboring messages by timestamp
|
- **Horizontally**: use `sql()` to fetch neighboring messages by timestamp
|
||||||
```js
|
```js
|
||||||
@@ -124,7 +138,7 @@ return hits.slice(0, 5).map(h => ({
|
|||||||
### "最近在做什么"
|
### "最近在做什么"
|
||||||
|
|
||||||
```js
|
```js
|
||||||
return recent(10).map(s => ({ title: s.title, project: s.project, date: s.started_at }))
|
return sessions({ limit: 10 }).map(s => ({ title: s.title, project: s.project, date: s.started_at }))
|
||||||
```
|
```
|
||||||
|
|
||||||
### "哪些文件被反复修改"
|
### "哪些文件被反复修改"
|
||||||
@@ -137,15 +151,12 @@ return sql(`
|
|||||||
`)
|
`)
|
||||||
```
|
```
|
||||||
|
|
||||||
### "那个 review workflow 的结果是什么"
|
### "这个项目的 workflow 跑过几次"
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const wfs = workflows()
|
return workflows({ project: '%quiet-zero%' }).map(w => ({
|
||||||
const review = wfs.find(w =>
|
run: w.run_id, agents: w.agent_count, time: w.timestamp
|
||||||
w.run_id.includes('review') ||
|
}))
|
||||||
JSON.parse(w.result_json || '{}').synthesis
|
|
||||||
)
|
|
||||||
return review ? JSON.parse(review.result_json) : 'No review workflow found'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### "上次跑 experiment 用了多少 token"
|
### "上次跑 experiment 用了多少 token"
|
||||||
|
|||||||
+70
-27
@@ -262,26 +262,40 @@ const msgs = thread('session-uuid');
|
|||||||
return { count: msgs.length, first: msgs[0]?.text?.slice(0, 100) };
|
return { count: msgs.length, first: msgs[0]?.text?.slice(0, 100) };
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `subagents(sessionId)`
|
#### `subagents(opts?)`
|
||||||
|
|
||||||
All subagent spawns for a session, with message counts.
|
All subagent spawns, with message counts. For backward compatibility, passing a string is treated as `sessionId`.
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `opts.sessionId` | `string` | Restrict to one session |
|
||||||
|
| `opts.project` | `string` | Filter by project slug (LIKE) |
|
||||||
|
| `opts.limit` | `number` | Max results (default 100) |
|
||||||
|
|
||||||
**Returns:** `Array<{ ...subagent_row, messageCount }>`.
|
**Returns:** `Array<{ ...subagent_row, messageCount }>`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const subs = subagents('session-uuid');
|
const subs = subagents({ project: '%quiet-zero%' });
|
||||||
return subs.map(s => ({ type: s.agent_type, desc: s.description, msgs: s.messageCount, tokens: s.total_tokens }));
|
return subs.map(s => ({ type: s.agent_type, desc: s.description, msgs: s.messageCount, tokens: s.total_tokens }));
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `workflows(sessionId?)`
|
#### `workflows(opts?)`
|
||||||
|
|
||||||
Workflow executions. Pass a session ID to filter, or omit for all workflows (newest first).
|
Workflow executions. For backward compatibility, passing a string is treated as `sessionId`.
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `opts.sessionId` | `string` | Restrict to one session |
|
||||||
|
| `opts.project` | `string` | Filter by project slug (LIKE) |
|
||||||
|
| `opts.after` | `string` | ISO 8601 lower bound on timestamp |
|
||||||
|
| `opts.before` | `string` | ISO 8601 upper bound on timestamp |
|
||||||
|
| `opts.limit` | `number` | Max results (default 100) |
|
||||||
|
|
||||||
**Returns:** `Array<workflow_row>`.
|
**Returns:** `Array<workflow_row>`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const wfs = workflows();
|
const wfs = workflows({ project: '%quiet-zero%' });
|
||||||
return wfs.slice(0, 5).map(w => ({ run: w.run_id, agents: w.agent_count, time: w.timestamp }));
|
return wfs.map(w => ({ run: w.run_id, agents: w.agent_count, time: w.timestamp }));
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `workflowTree(runId)`
|
#### `workflowTree(runId)`
|
||||||
@@ -295,31 +309,46 @@ const tree = workflowTree('run-uuid');
|
|||||||
return tree?.agents.map(a => ({ type: a.agent_type, msgs: a.messages.length }));
|
return tree?.agents.map(a => ({ type: a.agent_type, msgs: a.messages.length }));
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `fileHistory(filePath)`
|
#### `fileHistory(filePath, opts?)`
|
||||||
|
|
||||||
All tool calls that touched a specific file, across every session.
|
All tool calls that touched a specific file, across every session.
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `filePath` | `string` | Absolute file path (required) |
|
||||||
|
| `opts.after` | `string` | ISO 8601 lower bound |
|
||||||
|
| `opts.before` | `string` | ISO 8601 upper bound |
|
||||||
|
| `opts.limit` | `number` | Max results (default 200) |
|
||||||
|
|
||||||
**Returns:** `Array<{ toolCall, session, timestamp }>`.
|
**Returns:** `Array<{ toolCall, session, timestamp }>`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const edits = fileHistory('/Users/tomiya/Code/quiet-zero/src/mcts.ts');
|
const edits = fileHistory('/Users/tomiya/Code/quiet-zero/src/mcts.ts', { after: '2026-05-28' });
|
||||||
return edits.map(e => ({ tool: e.toolCall.name, session: e.session.title, time: e.timestamp }));
|
return edits.map(e => ({ tool: e.toolCall.name, session: e.session.title, time: e.timestamp }));
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `failures(sessionId?)`
|
#### `failures(opts?)`
|
||||||
|
|
||||||
Tool calls whose results contain error patterns (`Error`, `ENOENT`, `failed`, `permission denied`, etc.). Includes the 3 messages immediately after each failure for retry context.
|
Tool calls whose results contain error patterns (`Error`, `ENOENT`, `failed`, `permission denied`, etc.). Includes the 3 messages immediately after each failure for retry context. For backward compatibility, passing a string is treated as `sessionId`.
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `opts.sessionId` | `string` | Restrict to one session |
|
||||||
|
| `opts.project` | `string` | Filter by project slug (LIKE) |
|
||||||
|
| `opts.after` | `string` | ISO 8601 lower bound |
|
||||||
|
| `opts.before` | `string` | ISO 8601 upper bound |
|
||||||
|
| `opts.limit` | `number` | Max results (default 50) |
|
||||||
|
|
||||||
**Returns:** `Array<{ toolCall, result, session, nextMessages }>`.
|
**Returns:** `Array<{ toolCall, result, session, nextMessages }>`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const fails = failures('session-uuid');
|
const fails = failures({ project: '%quiet-zero%', limit: 10 });
|
||||||
return fails.map(f => ({ tool: f.toolCall?.name, error: f.result.content?.slice(0, 200) }));
|
return fails.map(f => ({ tool: f.toolCall?.name, error: f.result.content?.slice(0, 200) }));
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `recent(n?)`
|
#### `recent(n?)`
|
||||||
|
|
||||||
Last `n` sessions (default 10), ordered by `ended_at` descending.
|
Shorthand for `sessions({ limit: n })`. Last `n` sessions (default 10), ordered by `ended_at` descending.
|
||||||
|
|
||||||
**Returns:** `Array<session_row>`.
|
**Returns:** `Array<session_row>`.
|
||||||
|
|
||||||
@@ -328,6 +357,27 @@ const last5 = recent(5);
|
|||||||
return last5.map(s => ({ title: s.title, project: s.project_path, ended: s.ended_at }));
|
return last5.map(s => ({ title: s.title, project: s.project_path, ended: s.ended_at }));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `sessions(opts?)`
|
||||||
|
|
||||||
|
Query sessions with filters. For backward compatibility, passing a number is treated as `limit`.
|
||||||
|
|
||||||
|
| Param | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `opts.project` | `string` | Filter by project slug (supports LIKE, e.g. `'%quiet-zero%'`) |
|
||||||
|
| `opts.after` | `string` | ISO 8601 lower bound on `started_at` |
|
||||||
|
| `opts.before` | `string` | ISO 8601 upper bound on `started_at` |
|
||||||
|
| `opts.limit` | `number` | Max results (default 50) |
|
||||||
|
| `opts.branch` | `string` | Filter by git branch (exact match) |
|
||||||
|
| `opts.sessionId` | `string` | Restrict to one session |
|
||||||
|
| `opts.sessions` | `string[]` | Restrict to a set of session IDs |
|
||||||
|
|
||||||
|
**Returns:** `Array<session_row>` ordered by `ended_at` descending.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const qz = sessions({ project: '%quiet-zero%', limit: 5 });
|
||||||
|
return qz.map(s => ({ title: s.title, branch: s.git_branch, ended: s.ended_at }));
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Common Query Patterns
|
## 3. Common Query Patterns
|
||||||
@@ -436,13 +486,9 @@ return rows;
|
|||||||
### Find sessions by time range
|
### Find sessions by time range
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const rows = sql(`
|
return sessions({ after: '2026-05-28T00:00:00Z', before: '2026-05-30T00:00:00Z' }).map(s => ({
|
||||||
SELECT id, title, project_path, started_at, ended_at, message_count
|
title: s.title, project: s.project_path, started: s.started_at, messages: s.message_count,
|
||||||
FROM sessions
|
}));
|
||||||
WHERE started_at >= ? AND started_at < ?
|
|
||||||
ORDER BY started_at DESC
|
|
||||||
`, '2026-05-28T00:00:00Z', '2026-05-30T00:00:00Z');
|
|
||||||
return rows;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cross-reference subagent findings
|
### Cross-reference subagent findings
|
||||||
@@ -460,13 +506,10 @@ return details;
|
|||||||
### Find all sessions for a project
|
### Find all sessions for a project
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const rows = sql(`
|
return sessions({ project: '%quiet-zero%' }).map(s => ({
|
||||||
SELECT id, title, started_at, ended_at, message_count, git_branch
|
title: s.title, started: s.started_at, ended: s.ended_at,
|
||||||
FROM sessions
|
messages: s.message_count, branch: s.git_branch,
|
||||||
WHERE project_path = ?
|
}));
|
||||||
ORDER BY started_at DESC
|
|
||||||
`, '/Users/tomiya/Code/quiet-zero');
|
|
||||||
return rows;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reconstruct what happened in a session
|
### Reconstruct what happened in a session
|
||||||
|
|||||||
+75
-20
@@ -2,6 +2,28 @@ 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'];
|
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') {
|
||||||
|
if (optsOrScalar == null) return {};
|
||||||
|
if (typeof optsOrScalar === 'string') return { [scalarKey]: optsOrScalar };
|
||||||
|
if (typeof optsOrScalar === 'number') return { limit: optsOrScalar };
|
||||||
|
return optsOrScalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWhere(opts, aliases) {
|
||||||
|
const clauses = [];
|
||||||
|
const params = [];
|
||||||
|
if (opts.sessionId) { clauses.push(`${aliases.sessionId} = ?`); params.push(opts.sessionId); }
|
||||||
|
if (opts.sessions?.length) {
|
||||||
|
clauses.push(`${aliases.sessionId} IN (${opts.sessions.map(() => '?').join(',')})`);
|
||||||
|
params.push(...opts.sessions);
|
||||||
|
}
|
||||||
|
if (opts.project) { clauses.push(`${aliases.project} LIKE ?`); params.push(opts.project); }
|
||||||
|
if (opts.after) { clauses.push(`${aliases.timestamp} > ?`); params.push(opts.after); }
|
||||||
|
if (opts.before) { clauses.push(`${aliases.timestamp} < ?`); params.push(opts.before); }
|
||||||
|
if (opts.branch) { clauses.push(`${aliases.branch} = ?`); params.push(opts.branch); }
|
||||||
|
return { where: clauses.length ? clauses.join(' AND ') : '1=1', params };
|
||||||
|
}
|
||||||
|
|
||||||
function createQueryApi(db) {
|
function createQueryApi(db) {
|
||||||
const q = (sql, ...p) => db.prepare(sql).all(...p);
|
const q = (sql, ...p) => db.prepare(sql).all(...p);
|
||||||
|
|
||||||
@@ -56,16 +78,28 @@ function createQueryApi(db) {
|
|||||||
|
|
||||||
const thread = (sid) => db.prepare('SELECT * FROM messages WHERE session_id=? ORDER BY timestamp').all(sid);
|
const thread = (sid) => db.prepare('SELECT * FROM messages WHERE session_id=? ORDER BY timestamp').all(sid);
|
||||||
|
|
||||||
const subagents = (sid) => {
|
const subagents = (optsOrSid) => {
|
||||||
return db.prepare('SELECT * FROM subagents WHERE session_id=?').all(sid).map(r => {
|
const opts = normalizeOpts(optsOrSid);
|
||||||
|
const { limit = 100 } = opts;
|
||||||
|
const needsJoin = opts.project || opts.branch;
|
||||||
|
const { where, params } = buildWhere(opts, { sessionId: 'sa.session_id', project: 's.project', timestamp: 'sa.session_id', branch: 's.git_branch' });
|
||||||
|
params.push(limit);
|
||||||
|
const join = needsJoin ? 'LEFT JOIN sessions s ON s.id=sa.session_id' : '';
|
||||||
|
return db.prepare(`SELECT sa.* FROM subagents sa ${join} WHERE ${where} LIMIT ?`).all(...params).map(r => {
|
||||||
const c = db.prepare('SELECT COUNT(*) as c FROM messages WHERE agent_id=?').get(r.agent_id);
|
const c = db.prepare('SELECT COUNT(*) as c FROM messages WHERE agent_id=?').get(r.agent_id);
|
||||||
return { ...r, messageCount: c?.c || 0 };
|
return { ...r, messageCount: c?.c || 0 };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const workflows = (sid) => sid
|
const workflows = (optsOrSid) => {
|
||||||
? db.prepare('SELECT * FROM workflows WHERE session_id=? ORDER BY timestamp').all(sid)
|
const opts = normalizeOpts(optsOrSid);
|
||||||
: db.prepare('SELECT * FROM workflows ORDER BY timestamp DESC').all();
|
const { limit = 100 } = opts;
|
||||||
|
const needsJoin = opts.project || opts.branch;
|
||||||
|
const { where, params } = buildWhere(opts, { sessionId: 'w.session_id', project: 's.project', timestamp: 'w.timestamp', branch: 's.git_branch' });
|
||||||
|
params.push(limit);
|
||||||
|
const join = needsJoin ? 'LEFT JOIN sessions s ON s.id=w.session_id' : '';
|
||||||
|
return db.prepare(`SELECT w.* FROM workflows w ${join} WHERE ${where} ORDER BY w.timestamp DESC LIMIT ?`).all(...params);
|
||||||
|
};
|
||||||
|
|
||||||
const workflowTree = (runId) => {
|
const workflowTree = (runId) => {
|
||||||
const wf = db.prepare('SELECT * FROM workflows WHERE run_id=?').get(runId);
|
const wf = db.prepare('SELECT * FROM workflows WHERE run_id=?').get(runId);
|
||||||
@@ -76,23 +110,32 @@ function createQueryApi(db) {
|
|||||||
return { ...wf, agents };
|
return { ...wf, agents };
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileHistory = (fp) => {
|
const fileHistory = (fp, opts = {}) => {
|
||||||
|
const { limit = 200, after, before } = opts;
|
||||||
|
let where = 'tc.file_path=?';
|
||||||
|
const params = [fp];
|
||||||
|
if (after) { where += ' AND m.timestamp > ?'; params.push(after); }
|
||||||
|
if (before) { where += ' AND m.timestamp < ?'; params.push(before); }
|
||||||
|
params.push(limit);
|
||||||
return db.prepare(
|
return db.prepare(
|
||||||
'SELECT tc.*,s.title as s_title,s.project as s_project FROM tool_calls tc LEFT JOIN sessions s ON s.id=tc.session_id WHERE tc.file_path=? ORDER BY tc.id'
|
`SELECT tc.*,s.title as s_title,s.project as s_project,m.timestamp as ts FROM tool_calls tc LEFT JOIN sessions s ON s.id=tc.session_id LEFT JOIN messages m ON m.uuid=tc.message_uuid WHERE ${where} ORDER BY m.timestamp LIMIT ?`
|
||||||
).all(fp).map(r => ({
|
).all(...params).map(r => ({
|
||||||
toolCall: { id: r.id, message_uuid: r.message_uuid, name: r.name, input_json: r.input_json },
|
toolCall: { id: r.id, message_uuid: r.message_uuid, name: r.name, input_json: r.input_json },
|
||||||
session: { id: r.session_id, title: r.s_title, project: r.s_project },
|
session: { id: r.session_id, title: r.s_title, project: r.s_project },
|
||||||
timestamp: db.prepare('SELECT timestamp FROM messages WHERE uuid=?').get(r.message_uuid)?.timestamp,
|
timestamp: r.ts,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const failures = (sid) => {
|
const failures = (optsOrSid) => {
|
||||||
const likeClauses = ERROR_PATS.map(() => 'content LIKE ?').join(' OR ');
|
const opts = normalizeOpts(optsOrSid);
|
||||||
|
const { limit = 50 } = opts;
|
||||||
|
const likeClauses = ERROR_PATS.map(() => 'tr.content LIKE ?').join(' OR ');
|
||||||
const likeParams = ERROR_PATS.map(p => `%${p}%`);
|
const likeParams = ERROR_PATS.map(p => `%${p}%`);
|
||||||
let query = `SELECT * FROM tool_results WHERE (${likeClauses})`;
|
const needsJoin = opts.project || opts.branch;
|
||||||
const params = [...likeParams];
|
const { where, params: filterParams } = buildWhere(opts, { sessionId: 'tr.session_id', project: 's.project', timestamp: 'rm.timestamp', branch: 's.git_branch' });
|
||||||
if (sid) { query += ' AND session_id=?'; params.push(sid); }
|
const join = needsJoin ? 'LEFT JOIN sessions s ON s.id=tr.session_id' : '';
|
||||||
const rows = db.prepare(query).all(...params);
|
const allParams = [...likeParams, ...filterParams, limit];
|
||||||
|
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);
|
||||||
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);
|
||||||
@@ -102,11 +145,23 @@ function createQueryApi(db) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const recent = (n = 10) => db.prepare('SELECT * FROM sessions ORDER BY ended_at DESC LIMIT ?').all(n);
|
const sessions = (optsOrN) => {
|
||||||
|
const opts = normalizeOpts(optsOrN, 'sessionId');
|
||||||
|
const { limit = 50 } = opts;
|
||||||
|
const { where, params } = buildWhere(opts, { sessionId: 's.id', project: 's.project', timestamp: 's.started_at', branch: 's.git_branch' });
|
||||||
|
params.push(limit);
|
||||||
|
return db.prepare(`SELECT * FROM sessions s WHERE ${where} ORDER BY ended_at DESC LIMIT ?`).all(...params);
|
||||||
|
};
|
||||||
|
|
||||||
const summaries = (sessionId) => sessionId
|
const recent = (n = 10) => sessions({ limit: n });
|
||||||
? db.prepare('SELECT * FROM summaries WHERE session_id=? ORDER BY timestamp').all(sessionId)
|
|
||||||
: db.prepare('SELECT su.*, s.title as session_title, s.project FROM summaries su LEFT JOIN sessions s ON s.id=su.session_id ORDER BY su.timestamp DESC').all();
|
const summaries = (optsOrSid) => {
|
||||||
|
const opts = normalizeOpts(optsOrSid);
|
||||||
|
const { limit = 100 } = opts;
|
||||||
|
const { where, params } = buildWhere(opts, { sessionId: 'su.session_id', project: 's.project', timestamp: 'su.timestamp', branch: 's.git_branch' });
|
||||||
|
params.push(limit);
|
||||||
|
return db.prepare(`SELECT su.*, s.title as session_title, s.project FROM summaries su LEFT JOIN sessions s ON s.id=su.session_id WHERE ${where} ORDER BY su.timestamp DESC LIMIT ?`).all(...params);
|
||||||
|
};
|
||||||
|
|
||||||
const resolveJsonlPath = (messageUuid) => {
|
const resolveJsonlPath = (messageUuid) => {
|
||||||
const msg = db.prepare('SELECT session_id, agent_id FROM messages WHERE uuid=?').get(messageUuid);
|
const msg = db.prepare('SELECT session_id, agent_id FROM messages WHERE uuid=?').get(messageUuid);
|
||||||
@@ -153,7 +208,7 @@ function createQueryApi(db) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return { sql: q, search, context, trace, thread, subagents, workflows, workflowTree, fileHistory, failures, recent, summaries, raw };
|
return { sql: q, search, context, trace, thread, subagents, workflows, workflowTree, fileHistory, failures, sessions, recent, summaries, raw };
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createQueryApi };
|
export { createQueryApi };
|
||||||
|
|||||||
Reference in New Issue
Block a user