@swestash/agent-lens 0.9.4 → 0.9.6
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/dist/agent-lens.js +248 -227
- package/dist/agent-lens.js.map +1 -1
- package/package.json +1 -1
package/dist/agent-lens.js
CHANGED
|
@@ -4314,6 +4314,9 @@ function tableExists(db, name) {
|
|
|
4314
4314
|
function queryAll(db, sql, ...params) {
|
|
4315
4315
|
return db.prepare(sql).all(...params);
|
|
4316
4316
|
}
|
|
4317
|
+
function queryGet(db, sql, ...params) {
|
|
4318
|
+
return db.prepare(sql).get(...params);
|
|
4319
|
+
}
|
|
4317
4320
|
function pushDateRange(clauses, params, col, from, to) {
|
|
4318
4321
|
if (from) {
|
|
4319
4322
|
clauses.push(`date(${col}) >= date(?)`);
|
|
@@ -4419,22 +4422,17 @@ function extractParts(rawJson) {
|
|
|
4419
4422
|
return { text: text || null, thinking: thinking || null };
|
|
4420
4423
|
}
|
|
4421
4424
|
function listSources(db) {
|
|
4422
|
-
return db.
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
// many subagent sidechains each task spawns.
|
|
4426
|
-
`SELECT s.id, s.label, s.agent_id, s.config_dir,
|
|
4427
|
-
(SELECT COUNT(*) FROM sessions x WHERE x.source_id = s.id AND x.is_sidechain = 0) AS session_count
|
|
4428
|
-
FROM sources s ORDER BY s.label`
|
|
4429
|
-
).all();
|
|
4425
|
+
return queryAll(db, `SELECT s.id, s.label,
|
|
4426
|
+
(SELECT COUNT(*) FROM sessions x WHERE x.source_id = s.id AND x.is_sidechain = 0) AS session_count
|
|
4427
|
+
FROM sources s ORDER BY s.label`);
|
|
4430
4428
|
}
|
|
4431
4429
|
function listProjects(db) {
|
|
4432
|
-
return db
|
|
4433
|
-
|
|
4434
|
-
|
|
4430
|
+
return queryAll(db, `SELECT p.id, p.path,
|
|
4431
|
+
(SELECT COUNT(*) FROM sessions x WHERE x.project_id = p.id AND x.is_sidechain = 0) AS session_count
|
|
4432
|
+
FROM projects p ORDER BY session_count DESC, p.path`);
|
|
4435
4433
|
}
|
|
4436
4434
|
function listModels(db) {
|
|
4437
|
-
return db
|
|
4435
|
+
return queryAll(db, `SELECT DISTINCT model FROM token_usage WHERE model IS NOT NULL ORDER BY model`).map((r) => r.model);
|
|
4438
4436
|
}
|
|
4439
4437
|
function toFtsQuery(raw) {
|
|
4440
4438
|
return raw.trim().split(/\s+/).filter(Boolean).map((t) => `"${t.replace(/"/g, '""')}"`).join(" ");
|
|
@@ -4471,8 +4469,8 @@ function listSessions(db, f) {
|
|
|
4471
4469
|
params.push(toFtsQuery(f.q), like, like, like);
|
|
4472
4470
|
}
|
|
4473
4471
|
const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
4474
|
-
const total = db
|
|
4475
|
-
const baseSelect = `SELECT s.id, s.ai_title, s.slug, s.source_id, s.is_sidechain, s.started_at,
|
|
4472
|
+
const total = queryGet(db, `SELECT COUNT(*) n FROM sessions s ${whereSql}`, ...params).n;
|
|
4473
|
+
const baseSelect = `SELECT s.id, s.ai_title, s.slug, s.source_id, s.is_sidechain, s.started_at,
|
|
4476
4474
|
s.duration_ms, s.event_count, s.turn_count, p.path AS project_path,
|
|
4477
4475
|
(SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models,
|
|
4478
4476
|
(SELECT COUNT(*) FROM tool_calls tc WHERE tc.session_id = s.id) AS tool_call_count,
|
|
@@ -4491,10 +4489,9 @@ function listSessions(db, f) {
|
|
|
4491
4489
|
};
|
|
4492
4490
|
const sortKey = f.sort ?? "started";
|
|
4493
4491
|
const JS_SORT = /* @__PURE__ */ new Set(["tokens", "cost", "errors", "security"]);
|
|
4494
|
-
let
|
|
4492
|
+
let sessions;
|
|
4495
4493
|
if (JS_SORT.has(sortKey)) {
|
|
4496
|
-
const all = db
|
|
4497
|
-
attachSessionCost(db, all);
|
|
4494
|
+
const all = attachSessionCost(db, queryAll(db, baseSelect, ...params));
|
|
4498
4495
|
const sign = f.dir === "asc" ? 1 : -1;
|
|
4499
4496
|
const metric = (r) => {
|
|
4500
4497
|
switch (sortKey) {
|
|
@@ -4515,12 +4512,11 @@ function listSessions(db, f) {
|
|
|
4515
4512
|
const c = (metric(a) - metric(b)) * sign;
|
|
4516
4513
|
return c !== 0 ? c : String(a.id).localeCompare(String(b.id));
|
|
4517
4514
|
});
|
|
4518
|
-
|
|
4515
|
+
sessions = all.slice(f.offset, f.offset + f.limit);
|
|
4519
4516
|
} else {
|
|
4520
|
-
|
|
4521
|
-
attachSessionCost(db, rows);
|
|
4517
|
+
sessions = attachSessionCost(db, queryAll(db, `${baseSelect} ORDER BY ${orderBy(NATIVE_ORDER, sortKey, "started", f.dir)}, s.id ASC LIMIT ? OFFSET ?`, ...params, f.limit, f.offset));
|
|
4522
4518
|
}
|
|
4523
|
-
return { total, sessions
|
|
4519
|
+
return { total, sessions };
|
|
4524
4520
|
}
|
|
4525
4521
|
function attachSessionCost(db, rows) {
|
|
4526
4522
|
const usageBySession = /* @__PURE__ */ new Map();
|
|
@@ -4536,21 +4532,18 @@ function attachSessionCost(db, rows) {
|
|
|
4536
4532
|
usageBySession.set(u.session_id, list);
|
|
4537
4533
|
}
|
|
4538
4534
|
}
|
|
4539
|
-
|
|
4535
|
+
return rows.map(({ ai_title, slug, ...r }) => {
|
|
4540
4536
|
const u = usageBySession.get(r.id) ?? [];
|
|
4541
4537
|
const split = splitOf(u);
|
|
4542
|
-
r
|
|
4543
|
-
|
|
4544
|
-
r.cost = costOf(u);
|
|
4545
|
-
r.title = r.ai_title || r.slug || null;
|
|
4546
|
-
}
|
|
4538
|
+
return { ...r, tokens: tokensOf(split), token_split: split, cost: costOf(u), title: ai_title || slug || null };
|
|
4539
|
+
});
|
|
4547
4540
|
}
|
|
4548
4541
|
function loadToolCalls(db, id) {
|
|
4549
|
-
const toolRows = db
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4542
|
+
const toolRows = queryAll(db, `SELECT id, event_uuid, tool_name, skill_name, skill_id, agent_type, spawned_session_id,
|
|
4543
|
+
workflow_run_id, workflow_name, status, error_type,
|
|
4544
|
+
total_duration_ms, input_json, result_summary,
|
|
4545
|
+
(SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tool_calls.workflow_run_id) AS workflow_agent_count
|
|
4546
|
+
FROM tool_calls WHERE session_id = ?`, id);
|
|
4554
4547
|
if (tableExists(db, "tool_results")) {
|
|
4555
4548
|
const getFull = db.prepare("SELECT text, bytes FROM tool_results WHERE session_id = ? AND name = ?");
|
|
4556
4549
|
for (const t of toolRows) {
|
|
@@ -4568,8 +4561,8 @@ function loadFindings(db, id, toolRows) {
|
|
|
4568
4561
|
if (!tableExists(db, "findings"))
|
|
4569
4562
|
return [];
|
|
4570
4563
|
const sessionFindings = [];
|
|
4571
|
-
const findingRows = db
|
|
4572
|
-
|
|
4564
|
+
const findingRows = queryAll(db, `SELECT id, tool_call_id, event_uuid, turn_id, rule_id, category, framework_ref, severity, title, evidence, signals_json
|
|
4565
|
+
FROM findings WHERE session_id = ?`, id);
|
|
4573
4566
|
const byToolCall = /* @__PURE__ */ new Map();
|
|
4574
4567
|
for (const fr of findingRows) {
|
|
4575
4568
|
const f = {
|
|
@@ -4583,6 +4576,7 @@ function loadFindings(db, id, toolRows) {
|
|
|
4583
4576
|
severity: fr.severity,
|
|
4584
4577
|
title: fr.title,
|
|
4585
4578
|
evidence: fr.evidence,
|
|
4579
|
+
// Asserted, not validated: safeJson returns unknown and this is the assertion (ADR-026).
|
|
4586
4580
|
signals: safeJson(fr.signals_json)
|
|
4587
4581
|
};
|
|
4588
4582
|
sessionFindings.push(f);
|
|
@@ -4590,13 +4584,16 @@ function loadFindings(db, id, toolRows) {
|
|
|
4590
4584
|
pushGrouped(byToolCall, fr.tool_call_id, f);
|
|
4591
4585
|
}
|
|
4592
4586
|
for (const t of toolRows)
|
|
4593
|
-
t.findings = byToolCall.get(t.id) ?? [];
|
|
4587
|
+
t.findings = t.id ? byToolCall.get(t.id) ?? [] : [];
|
|
4594
4588
|
sessionFindings.sort((a, b) => severityRank(b.severity) - severityRank(a.severity));
|
|
4595
4589
|
return sessionFindings;
|
|
4596
4590
|
}
|
|
4591
|
+
function toResponseToolCall({ event_uuid: _e, error_type: _t, ...call }) {
|
|
4592
|
+
return call;
|
|
4593
|
+
}
|
|
4597
4594
|
function loadEvents(db, id, toolRows) {
|
|
4598
|
-
const eventRows = db
|
|
4599
|
-
|
|
4595
|
+
const eventRows = queryAll(db, `SELECT uuid, type, role, timestamp, model, is_sidechain, turn_id, raw_json
|
|
4596
|
+
FROM events WHERE session_id = ? ORDER BY timestamp, seq`, id);
|
|
4600
4597
|
const toolsByEvent = /* @__PURE__ */ new Map();
|
|
4601
4598
|
for (const t of toolRows) {
|
|
4602
4599
|
if (!t.event_uuid)
|
|
@@ -4615,13 +4612,13 @@ function loadEvents(db, id, toolRows) {
|
|
|
4615
4612
|
turn_id: e.turn_id,
|
|
4616
4613
|
text,
|
|
4617
4614
|
thinking,
|
|
4618
|
-
toolCalls: toolsByEvent.get(e.uuid) ?? []
|
|
4615
|
+
toolCalls: (toolsByEvent.get(e.uuid) ?? []).map(toResponseToolCall)
|
|
4619
4616
|
};
|
|
4620
4617
|
});
|
|
4621
4618
|
}
|
|
4622
4619
|
function loadClassification(db, id) {
|
|
4623
|
-
const cls = db
|
|
4624
|
-
|
|
4620
|
+
const cls = queryGet(db, `SELECT category, complexity_score, complexity_band, signals_json, classifier_version
|
|
4621
|
+
FROM classifications WHERE scope = 'session' AND target_id = ?`, id);
|
|
4625
4622
|
if (!cls)
|
|
4626
4623
|
return null;
|
|
4627
4624
|
let signals = null;
|
|
@@ -4640,66 +4637,67 @@ function loadClassification(db, id) {
|
|
|
4640
4637
|
function loadParent(db, session) {
|
|
4641
4638
|
if (!session.parent_session_id)
|
|
4642
4639
|
return null;
|
|
4643
|
-
const p = db
|
|
4644
|
-
|
|
4645
|
-
|
|
4640
|
+
const p = queryGet(db, `SELECT ps.id, ps.ai_title, ps.slug, t.seq AS turn_seq
|
|
4641
|
+
FROM sessions ps LEFT JOIN turns t ON t.id = ?
|
|
4642
|
+
WHERE ps.id = ?`, session.parent_turn_id, session.parent_session_id);
|
|
4646
4643
|
return p ? { id: p.id, title: p.ai_title || p.slug || null, turn_seq: p.turn_seq ?? null } : null;
|
|
4647
4644
|
}
|
|
4648
4645
|
function loadChildren(db, id) {
|
|
4649
4646
|
const hasMeta = tableExists(db, "session_meta");
|
|
4650
|
-
const children = db
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
ch.title = ch.ai_title || ch.slug || null;
|
|
4647
|
+
const children = queryAll(db, `SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.workflow_run_id,
|
|
4648
|
+
(SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models
|
|
4649
|
+
${metaProjection(hasMeta)}
|
|
4650
|
+
FROM sessions s ${metaJoin(hasMeta)}
|
|
4651
|
+
WHERE s.parent_session_id = ? ORDER BY s.started_at`, id);
|
|
4652
|
+
return children.map(({ ai_title, slug, ...ch }) => {
|
|
4657
4653
|
const u = sessionUsage(db, ch.id);
|
|
4658
|
-
ch
|
|
4659
|
-
|
|
4660
|
-
}
|
|
4661
|
-
return children;
|
|
4654
|
+
return { ...ch, title: ai_title || slug || null, tokens: tokensOf(splitOf(u)), cost: costOf(u) };
|
|
4655
|
+
});
|
|
4662
4656
|
}
|
|
4663
4657
|
function loadWorkflowRuns(db, id) {
|
|
4664
4658
|
const hasWR = tableExists(db, "workflow_results");
|
|
4665
|
-
return db
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4659
|
+
return queryAll(db, `SELECT tc.workflow_run_id AS run_id, tc.workflow_name AS name, t.seq AS turn_seq,
|
|
4660
|
+
(SELECT COUNT(*) FROM sessions s WHERE s.workflow_run_id = tc.workflow_run_id) AS agent_count
|
|
4661
|
+
${hasWR ? ", (SELECT status FROM workflow_results wr WHERE wr.run_id = tc.workflow_run_id) AS status" : ", NULL AS status"}
|
|
4662
|
+
FROM tool_calls tc LEFT JOIN turns t ON t.id = tc.turn_id
|
|
4663
|
+
WHERE tc.session_id = ? AND tc.workflow_run_id IS NOT NULL
|
|
4664
|
+
ORDER BY t.seq`, id);
|
|
4671
4665
|
}
|
|
4672
4666
|
function loadFileChanges(db, id) {
|
|
4673
4667
|
if (!tableExists(db, "file_changes"))
|
|
4674
4668
|
return [];
|
|
4675
|
-
return db
|
|
4676
|
-
|
|
4677
|
-
|
|
4669
|
+
return queryAll(db, `SELECT fc.id, fc.tool_call_id, fc.turn_id, fc.event_uuid, fc.file_path, fc.tool_name,
|
|
4670
|
+
fc.lines_added, fc.lines_removed, fc.timestamp
|
|
4671
|
+
FROM file_changes fc WHERE fc.session_id = ? ORDER BY fc.timestamp`, id);
|
|
4678
4672
|
}
|
|
4679
|
-
function
|
|
4673
|
+
function withSessionTotals(db, session, id, toolRows) {
|
|
4680
4674
|
const usage = sessionUsage(db, id);
|
|
4681
4675
|
const split = splitOf(usage);
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4676
|
+
const tool_error_count = toolRows.filter((t) => t.status === "error").length;
|
|
4677
|
+
const tool_rejection_count = toolRows.filter((t) => t.status === "error" && t.error_type && errorKind(t.error_type) === "rejection").length;
|
|
4678
|
+
return {
|
|
4679
|
+
...session,
|
|
4680
|
+
tokens: tokensOf(split),
|
|
4681
|
+
token_split: split,
|
|
4682
|
+
cost: costOf(usage),
|
|
4683
|
+
title: session.ai_title || session.slug || null,
|
|
4684
|
+
tool_call_count: toolRows.length,
|
|
4685
|
+
tool_error_count,
|
|
4686
|
+
tool_rejection_count,
|
|
4687
|
+
tool_failure_count: tool_error_count - tool_rejection_count
|
|
4688
|
+
};
|
|
4690
4689
|
}
|
|
4691
4690
|
function getSession(db, id) {
|
|
4692
|
-
const session = db
|
|
4693
|
-
|
|
4691
|
+
const session = queryGet(db, `SELECT s.*, p.path AS project_path FROM sessions s
|
|
4692
|
+
LEFT JOIN projects p ON p.id = s.project_id WHERE s.id = ?`, id);
|
|
4694
4693
|
if (!session)
|
|
4695
4694
|
return null;
|
|
4696
|
-
const turns = db
|
|
4695
|
+
const turns = queryAll(db, "SELECT * FROM turns WHERE session_id = ? ORDER BY seq", id);
|
|
4697
4696
|
const toolRows = loadToolCalls(db, id);
|
|
4698
4697
|
const findings = loadFindings(db, id, toolRows);
|
|
4699
4698
|
const events = loadEvents(db, id, toolRows);
|
|
4700
|
-
attachSessionTotals(db, session, id, toolRows);
|
|
4701
4699
|
return {
|
|
4702
|
-
session,
|
|
4700
|
+
session: withSessionTotals(db, session, id, toolRows),
|
|
4703
4701
|
turns,
|
|
4704
4702
|
events,
|
|
4705
4703
|
classification: loadClassification(db, id),
|
|
@@ -4724,27 +4722,26 @@ function safeJson(s) {
|
|
|
4724
4722
|
}
|
|
4725
4723
|
function loadAgents(db, runId) {
|
|
4726
4724
|
const hasMeta = tableExists(db, "session_meta");
|
|
4727
|
-
const
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4725
|
+
const agentRows = queryAll(db, `SELECT s.id, s.ai_title, s.slug, s.turn_count, s.started_at, s.ended_at, s.duration_ms,
|
|
4726
|
+
(SELECT GROUP_CONCAT(DISTINCT model) FROM token_usage t WHERE t.session_id = s.id) AS models
|
|
4727
|
+
${metaProjection(hasMeta)}
|
|
4728
|
+
FROM sessions s ${metaJoin(hasMeta)}
|
|
4729
|
+
WHERE s.workflow_run_id = ? ORDER BY s.started_at`, runId);
|
|
4732
4730
|
let total_tokens = 0;
|
|
4733
4731
|
let total_cost = 0;
|
|
4734
4732
|
let started_at = null;
|
|
4735
4733
|
let ended_at = null;
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
const
|
|
4739
|
-
a.tokens = tokensOf(splitOf(u));
|
|
4740
|
-
a.cost = costOf(u);
|
|
4734
|
+
const agents = agentRows.map(({ ai_title, slug, ...r }) => {
|
|
4735
|
+
const u = sessionUsage(db, r.id);
|
|
4736
|
+
const a = { ...r, title: ai_title || slug || null, tokens: tokensOf(splitOf(u)), cost: costOf(u) };
|
|
4741
4737
|
total_tokens += a.tokens;
|
|
4742
4738
|
total_cost += a.cost;
|
|
4743
4739
|
if (a.started_at && (!started_at || a.started_at < started_at))
|
|
4744
4740
|
started_at = a.started_at;
|
|
4745
4741
|
if (a.ended_at && (!ended_at || a.ended_at > ended_at))
|
|
4746
4742
|
ended_at = a.ended_at;
|
|
4747
|
-
|
|
4743
|
+
return a;
|
|
4744
|
+
});
|
|
4748
4745
|
const duration_ms = started_at && ended_at ? new Date(ended_at).getTime() - new Date(started_at).getTime() : null;
|
|
4749
4746
|
return {
|
|
4750
4747
|
agents,
|
|
@@ -4754,9 +4751,9 @@ function loadAgents(db, runId) {
|
|
|
4754
4751
|
function parseCompletion(db, wf) {
|
|
4755
4752
|
if (!wf.tool_use_id || !wf.parent_session_id)
|
|
4756
4753
|
return null;
|
|
4757
|
-
const ev = db
|
|
4758
|
-
|
|
4759
|
-
|
|
4754
|
+
const ev = queryGet(db, `SELECT raw_json FROM events
|
|
4755
|
+
WHERE session_id = ? AND text LIKE '%<task-notification>%' AND text LIKE ?
|
|
4756
|
+
ORDER BY timestamp DESC LIMIT 1`, wf.parent_session_id, `%${wf.tool_use_id}%`);
|
|
4760
4757
|
if (!ev)
|
|
4761
4758
|
return null;
|
|
4762
4759
|
const { text } = extractParts(ev.raw_json);
|
|
@@ -4764,9 +4761,9 @@ function parseCompletion(db, wf) {
|
|
|
4764
4761
|
return { status: xmlTag(t, "status"), summary: xmlTag(t, "summary"), result: xmlTag(t, "result"), failures: xmlTag(t, "failures") };
|
|
4765
4762
|
}
|
|
4766
4763
|
function loadRunSidecar(db, runId, completion) {
|
|
4767
|
-
const wr = tableExists(db, "workflow_results") ? db
|
|
4768
|
-
|
|
4769
|
-
|
|
4764
|
+
const wr = tableExists(db, "workflow_results") ? queryGet(db, `SELECT status, summary, default_model, result_json, phases_json, logs_json, progress_json,
|
|
4765
|
+
agent_count, total_tokens, total_tool_calls, duration_ms, started_at, ended_at
|
|
4766
|
+
FROM workflow_results WHERE run_id = ?`, runId) ?? null : null;
|
|
4770
4767
|
if (!wr)
|
|
4771
4768
|
return { wr: null, run: null, completion };
|
|
4772
4769
|
const run = {
|
|
@@ -4779,6 +4776,8 @@ function loadRunSidecar(db, runId, completion) {
|
|
|
4779
4776
|
duration_ms: wr.duration_ms ?? null,
|
|
4780
4777
|
started_at: wr.started_at ?? null,
|
|
4781
4778
|
ended_at: wr.ended_at ?? null,
|
|
4779
|
+
// The three JSON blobs are asserted, not validated (ADR-026): safeJson yields null for malformed
|
|
4780
|
+
// stored text, and these casts are what claims the shape of everything else.
|
|
4782
4781
|
phases: safeJson(wr.phases_json),
|
|
4783
4782
|
logs: safeJson(wr.logs_json),
|
|
4784
4783
|
// The per-phase/per-agent event timeline (workflowProgress) — lets the page render a phase graph
|
|
@@ -4796,16 +4795,16 @@ function loadRunSidecar(db, runId, completion) {
|
|
|
4796
4795
|
return { wr, run, completion };
|
|
4797
4796
|
}
|
|
4798
4797
|
function getWorkflow(db, runId) {
|
|
4799
|
-
const wf = db
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4798
|
+
const wf = queryGet(db, `SELECT tc.id AS tool_use_id, tc.workflow_run_id AS run_id, tc.workflow_name AS name, tc.status, tc.result_summary,
|
|
4799
|
+
tc.input_json AS input_json,
|
|
4800
|
+
tc.session_id AS parent_session_id, t.seq AS turn_seq,
|
|
4801
|
+
ps.ai_title AS parent_ai_title, ps.slug AS parent_slug
|
|
4802
|
+
FROM tool_calls tc
|
|
4803
|
+
LEFT JOIN turns t ON t.id = tc.turn_id
|
|
4804
|
+
LEFT JOIN sessions ps ON ps.id = tc.session_id
|
|
4805
|
+
WHERE tc.workflow_run_id = ? AND tc.tool_name = 'Workflow'
|
|
4806
|
+
ORDER BY t.seq
|
|
4807
|
+
LIMIT 1`, runId);
|
|
4809
4808
|
if (!wf)
|
|
4810
4809
|
return null;
|
|
4811
4810
|
const { agents, stats } = loadAgents(db, runId);
|
|
@@ -4835,20 +4834,18 @@ function listSkills(db, f = {}) {
|
|
|
4835
4834
|
where.push("s.source_id = ?"), params.push(f.source);
|
|
4836
4835
|
if (f.project)
|
|
4837
4836
|
where.push("s.project_id = ?"), params.push(f.project);
|
|
4838
|
-
const rows = db
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
r.sources = r.sources ? String(r.sources).split(",").filter(Boolean) : [];
|
|
4851
|
-
return rows;
|
|
4837
|
+
const rows = queryAll(db, `SELECT tc.skill_name AS name,
|
|
4838
|
+
COUNT(*) AS call_count,
|
|
4839
|
+
COUNT(DISTINCT tc.skill_id) AS version_count,
|
|
4840
|
+
MAX(e.timestamp) AS last_fired,
|
|
4841
|
+
GROUP_CONCAT(DISTINCT s.source_id) AS sources
|
|
4842
|
+
FROM tool_calls tc
|
|
4843
|
+
JOIN sessions s ON s.id = tc.session_id
|
|
4844
|
+
LEFT JOIN events e ON e.uuid = tc.event_uuid
|
|
4845
|
+
WHERE ${where.join(" AND ")}
|
|
4846
|
+
GROUP BY tc.skill_name
|
|
4847
|
+
ORDER BY call_count DESC, name`, ...params);
|
|
4848
|
+
return rows.map((r) => ({ ...r, sources: r.sources ? String(r.sources).split(",").filter(Boolean) : [] }));
|
|
4852
4849
|
}
|
|
4853
4850
|
function listFiles(db, f = {}) {
|
|
4854
4851
|
if (!tableExists(db, "file_changes"))
|
|
@@ -4871,7 +4868,7 @@ function listFiles(db, f = {}) {
|
|
|
4871
4868
|
MAX(fc.timestamp) AS last_ts
|
|
4872
4869
|
FROM file_changes fc${whereSql}
|
|
4873
4870
|
GROUP BY fc.project_id, fc.file_path`;
|
|
4874
|
-
const total = db
|
|
4871
|
+
const total = queryGet(db, `SELECT COUNT(*) n FROM (${grouped})`, ...params).n;
|
|
4875
4872
|
const ORDER = {
|
|
4876
4873
|
last_ts: "last_ts",
|
|
4877
4874
|
first_ts: "first_ts",
|
|
@@ -4881,9 +4878,9 @@ function listFiles(db, f = {}) {
|
|
|
4881
4878
|
};
|
|
4882
4879
|
const limit = Math.min(f.limit ?? 50, 200);
|
|
4883
4880
|
const offset = f.offset ?? 0;
|
|
4884
|
-
const files = db
|
|
4885
|
-
|
|
4886
|
-
|
|
4881
|
+
const files = queryAll(db, `SELECT g.*, p.path AS project_path FROM (${grouped}) g
|
|
4882
|
+
LEFT JOIN projects p ON p.id = g.project_id
|
|
4883
|
+
ORDER BY ${orderBy(ORDER, f.sort, "last_ts", f.dir)}, g.file_path ASC LIMIT ? OFFSET ?`, ...params, limit, offset);
|
|
4887
4884
|
return { total, files };
|
|
4888
4885
|
}
|
|
4889
4886
|
function getFileTimeline(db, filePath, projectId2) {
|
|
@@ -4893,15 +4890,15 @@ function getFileTimeline(db, filePath, projectId2) {
|
|
|
4893
4890
|
let scope = "fc.file_path = ?";
|
|
4894
4891
|
if (projectId2)
|
|
4895
4892
|
scope += " AND fc.project_id = ?", params.push(projectId2);
|
|
4896
|
-
const rows = db
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4893
|
+
const rows = queryAll(db, `SELECT fc.id, fc.session_id, fc.turn_id, fc.event_uuid, fc.tool_name, fc.lines_added,
|
|
4894
|
+
fc.lines_removed, fc.timestamp, fc.project_id,
|
|
4895
|
+
s.ai_title, s.slug, s.source_id, s.started_at, t.seq AS turn_seq, t.prompt_preview,
|
|
4896
|
+
p.path AS project_path
|
|
4897
|
+
FROM file_changes fc
|
|
4898
|
+
JOIN sessions s ON s.id = fc.session_id
|
|
4899
|
+
LEFT JOIN turns t ON t.id = fc.turn_id
|
|
4900
|
+
LEFT JOIN projects p ON p.id = fc.project_id
|
|
4901
|
+
WHERE ${scope} ORDER BY fc.timestamp`, ...params);
|
|
4905
4902
|
if (!rows.length)
|
|
4906
4903
|
return null;
|
|
4907
4904
|
const cat = tableExists(db, "classifications") ? db.prepare("SELECT category FROM classifications WHERE scope = 'session' AND target_id = ?") : null;
|
|
@@ -4950,22 +4947,21 @@ function getFileTimeline(db, filePath, projectId2) {
|
|
|
4950
4947
|
};
|
|
4951
4948
|
}
|
|
4952
4949
|
function getSkill(db, name) {
|
|
4953
|
-
const versions = db
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
const
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
if (!versions.length && !
|
|
4950
|
+
const versions = queryAll(db, `SELECT sk.id, sk.base_dir, sk.summary, sk.body, sk.body_bytes, sk.first_seen, sk.last_seen,
|
|
4951
|
+
(SELECT COUNT(*) FROM tool_calls tc WHERE tc.skill_id = sk.id) AS call_count
|
|
4952
|
+
FROM skills sk WHERE sk.name = ? ORDER BY sk.last_seen DESC, sk.id`, name);
|
|
4953
|
+
const sessionRows = queryAll(db, `SELECT s.id, s.ai_title, s.slug, s.source_id, s.started_at, p.path AS project_path,
|
|
4954
|
+
tc.skill_id AS version_id, MIN(e.timestamp) AS fired_at, COUNT(*) AS fire_count
|
|
4955
|
+
FROM tool_calls tc
|
|
4956
|
+
JOIN sessions s ON s.id = tc.session_id
|
|
4957
|
+
LEFT JOIN projects p ON p.id = s.project_id
|
|
4958
|
+
LEFT JOIN events e ON e.uuid = tc.event_uuid
|
|
4959
|
+
WHERE tc.tool_name = 'Skill' AND tc.skill_name = ?
|
|
4960
|
+
GROUP BY s.id, tc.skill_id
|
|
4961
|
+
ORDER BY fired_at DESC`, name);
|
|
4962
|
+
if (!versions.length && !sessionRows.length)
|
|
4966
4963
|
return null;
|
|
4967
|
-
|
|
4968
|
-
s.title = s.ai_title || s.slug || null;
|
|
4964
|
+
const sessions = sessionRows.map(({ ai_title, slug, ...s }) => ({ ...s, title: ai_title || slug || null }));
|
|
4969
4965
|
const call_count = sessions.reduce((a, s) => a + s.fire_count, 0);
|
|
4970
4966
|
return { name, versions, sessions, call_count };
|
|
4971
4967
|
}
|
|
@@ -5017,7 +5013,7 @@ function listFindings(db, f) {
|
|
|
5017
5013
|
const whereSql = where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
5018
5014
|
const dismissJoin = attached ? "LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id" : "";
|
|
5019
5015
|
const triageCols = attached ? `, (d.finding_id IS NOT NULL) AS dismissed, d.note AS dismiss_note, d.dismissed_at, ${MUTED_SQL} AS muted` : `, 0 AS dismissed, NULL AS dismiss_note, NULL AS dismissed_at, 0 AS muted`;
|
|
5020
|
-
const total = db
|
|
5016
|
+
const total = queryGet(db, `SELECT COUNT(*) n FROM findings f JOIN sessions s ON s.id = f.session_id ${dismissJoin} ${whereSql}`, ...w.params).n;
|
|
5021
5017
|
const ORDER = {
|
|
5022
5018
|
severity: SEVERITY_RANK_SQL,
|
|
5023
5019
|
session: "f.session_id",
|
|
@@ -5025,20 +5021,33 @@ function listFindings(db, f) {
|
|
|
5025
5021
|
category: "f.category",
|
|
5026
5022
|
time: "s.started_at"
|
|
5027
5023
|
};
|
|
5028
|
-
const findings = db
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5024
|
+
const findings = queryAll(db, `SELECT f.id, f.session_id, f.tool_call_id, f.event_uuid, f.turn_id, f.rule_id, f.category,
|
|
5025
|
+
f.framework_ref, f.severity, f.title, f.evidence, tc.tool_name,
|
|
5026
|
+
COALESCE(s.ai_title, s.slug) AS session_title, s.source_id, s.is_sidechain,
|
|
5027
|
+
s.started_at, p.path AS project_path, s.project_id ${triageCols}
|
|
5028
|
+
FROM findings f
|
|
5029
|
+
JOIN sessions s ON s.id = f.session_id
|
|
5030
|
+
LEFT JOIN projects p ON p.id = s.project_id
|
|
5031
|
+
LEFT JOIN tool_calls tc ON tc.id = f.tool_call_id
|
|
5032
|
+
${dismissJoin}
|
|
5033
|
+
${whereSql}
|
|
5034
|
+
ORDER BY ${orderBy(ORDER, f.sort, "severity", f.dir)}, ${SEVERITY_RANK_SQL} DESC, f.id ASC
|
|
5035
|
+
LIMIT ? OFFSET ?`, ...w.params, f.limit, f.offset);
|
|
5040
5036
|
return { total, findings };
|
|
5041
5037
|
}
|
|
5038
|
+
function openFindingIds(db, f) {
|
|
5039
|
+
if (!tableExists(db, "findings"))
|
|
5040
|
+
return [];
|
|
5041
|
+
const attached = triageAttached(db);
|
|
5042
|
+
const w = findingWhere(f);
|
|
5043
|
+
const st = statusWhere("open", attached);
|
|
5044
|
+
const where = [...w.sql, ...st ? [st] : []];
|
|
5045
|
+
return queryAll(db, `SELECT f.id
|
|
5046
|
+
FROM findings f
|
|
5047
|
+
JOIN sessions s ON s.id = f.session_id
|
|
5048
|
+
${attached ? "LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id" : ""}
|
|
5049
|
+
${where.length ? `WHERE ${where.join(" AND ")}` : ""}`, ...w.params).map((r) => r.id);
|
|
5050
|
+
}
|
|
5042
5051
|
function securitySummary(db) {
|
|
5043
5052
|
const categories = SECURITY_CATEGORIES;
|
|
5044
5053
|
if (!tableExists(db, "findings")) {
|
|
@@ -5048,15 +5057,15 @@ function securitySummary(db) {
|
|
|
5048
5057
|
const dismissJoin = attached ? "LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id" : "";
|
|
5049
5058
|
const base = `FROM findings f JOIN sessions s ON s.id = f.session_id ${dismissJoin}`;
|
|
5050
5059
|
const openWhere = attached ? `WHERE d.finding_id IS NULL AND NOT ${MUTED_SQL}` : "";
|
|
5051
|
-
const scalar = (sql) => db
|
|
5060
|
+
const scalar = (sql) => queryGet(db, sql).n;
|
|
5052
5061
|
const total = scalar(`SELECT COUNT(*) n ${base} ${openWhere}`);
|
|
5053
5062
|
const sessions_flagged = scalar(`SELECT COUNT(DISTINCT f.session_id) n ${base} ${openWhere}`);
|
|
5054
5063
|
const dismissed = attached ? scalar(`SELECT COUNT(*) n FROM findings f LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id WHERE d.finding_id IS NOT NULL`) : 0;
|
|
5055
5064
|
const muted = attached ? scalar(`SELECT COUNT(*) n ${base} WHERE d.finding_id IS NULL AND ${MUTED_SQL}`) : 0;
|
|
5056
|
-
const by_severity = db
|
|
5057
|
-
const by_category = db
|
|
5058
|
-
const by_rule = db
|
|
5059
|
-
|
|
5065
|
+
const by_severity = queryAll(db, `SELECT f.severity AS severity, COUNT(*) n ${base} ${openWhere} GROUP BY f.severity ORDER BY ${SEVERITY_RANK_SQL} DESC`);
|
|
5066
|
+
const by_category = queryAll(db, `SELECT f.category AS category, COUNT(*) n ${base} ${openWhere} GROUP BY f.category ORDER BY n DESC`);
|
|
5067
|
+
const by_rule = queryAll(db, `SELECT f.rule_id AS rule_id, f.category AS category, f.title AS title, COUNT(*) n, MAX(${SEVERITY_RANK_SQL}) AS rank
|
|
5068
|
+
${base} ${openWhere} GROUP BY f.rule_id ORDER BY rank DESC, n DESC`);
|
|
5060
5069
|
return { total, sessions_flagged, dismissed, muted, by_severity, by_category, by_rule, categories };
|
|
5061
5070
|
}
|
|
5062
5071
|
|
|
@@ -5113,6 +5122,12 @@ function addUsage(acc, r) {
|
|
|
5113
5122
|
acc.cache_creation += r.cw ?? 0;
|
|
5114
5123
|
acc.cache_read += r.cr ?? 0;
|
|
5115
5124
|
}
|
|
5125
|
+
var usageForCost = (u) => ({
|
|
5126
|
+
input_tokens: u.i ?? 0,
|
|
5127
|
+
output_tokens: u.o ?? 0,
|
|
5128
|
+
cache_creation_input_tokens: u.cw ?? 0,
|
|
5129
|
+
cache_read_input_tokens: u.cr ?? 0
|
|
5130
|
+
});
|
|
5116
5131
|
function pct(sorted, p) {
|
|
5117
5132
|
if (sorted.length === 0)
|
|
5118
5133
|
return 0;
|
|
@@ -5132,11 +5147,11 @@ function workflowAgg(db, f) {
|
|
|
5132
5147
|
if (!tableExists(db, "workflow_results"))
|
|
5133
5148
|
return empty;
|
|
5134
5149
|
const ww = workflowWhere(f);
|
|
5135
|
-
const rows = db
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5150
|
+
const rows = queryAll(db, `SELECT COALESCE(status, '(unknown)') status, COUNT(*) n,
|
|
5151
|
+
SUM(COALESCE(total_tokens, 0)) tokens,
|
|
5152
|
+
SUM(COALESCE(duration_ms, 0)) dur,
|
|
5153
|
+
SUM(CASE WHEN duration_ms IS NOT NULL THEN 1 ELSE 0 END) dur_n
|
|
5154
|
+
FROM workflow_results ${ww.sql} GROUP BY status ORDER BY n DESC`, ...ww.params);
|
|
5140
5155
|
let total = 0, completed = 0, failed = 0, tokens = 0, durSum = 0, durN = 0;
|
|
5141
5156
|
const by_status = rows.map((r) => {
|
|
5142
5157
|
total += r.n;
|
|
@@ -5162,32 +5177,32 @@ function workflowAgg(db, f) {
|
|
|
5162
5177
|
}
|
|
5163
5178
|
function dashboardOverview(db, f) {
|
|
5164
5179
|
const w = sessionWhere(f);
|
|
5165
|
-
const counts = db
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
const toolCount = db
|
|
5173
|
-
const usage = db
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5180
|
+
const counts = queryGet(db, `SELECT
|
|
5181
|
+
COUNT(*) sessions,
|
|
5182
|
+
SUM(CASE WHEN is_sidechain=0 THEN 1 ELSE 0 END) main,
|
|
5183
|
+
SUM(CASE WHEN is_sidechain=1 THEN 1 ELSE 0 END) subagent,
|
|
5184
|
+
SUM(turn_count) turns,
|
|
5185
|
+
COUNT(DISTINCT project_id) projects
|
|
5186
|
+
FROM sessions s ${w.sql}`, ...w.params);
|
|
5187
|
+
const toolCount = queryGet(db, `SELECT COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id ${w.sql}`, ...w.params).n;
|
|
5188
|
+
const usage = queryAll(db, `SELECT t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,
|
|
5189
|
+
SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr
|
|
5190
|
+
FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}
|
|
5191
|
+
GROUP BY t.model`, ...w.params);
|
|
5177
5192
|
const tokens = zeroSplit();
|
|
5178
5193
|
let cost = 0;
|
|
5179
5194
|
const unpriced = [];
|
|
5180
5195
|
for (const u of usage) {
|
|
5181
5196
|
addUsage(tokens, u);
|
|
5182
|
-
cost += costForUsage(u.model,
|
|
5197
|
+
cost += costForUsage(u.model, usageForCost(u));
|
|
5183
5198
|
if (u.model && !rateForModel(u.model) && (u.i || u.o || u.cw || u.cr))
|
|
5184
5199
|
unpriced.push(u.model);
|
|
5185
5200
|
}
|
|
5186
5201
|
const totalTokens = tokens.input + tokens.output + tokens.cache_creation + tokens.cache_read;
|
|
5187
|
-
const durs = db
|
|
5188
|
-
|
|
5189
|
-
const sessDurs = db
|
|
5190
|
-
|
|
5202
|
+
const durs = queryAll(db, `SELECT tn.duration_ms d FROM turns tn JOIN sessions s ON s.id = tn.session_id
|
|
5203
|
+
${w.sql ? w.sql + " AND" : "WHERE"} tn.duration_ms IS NOT NULL ORDER BY tn.duration_ms`, ...w.params).map((r) => r.d);
|
|
5204
|
+
const sessDurs = queryAll(db, `SELECT s.duration_ms d FROM sessions s
|
|
5205
|
+
${w.sql ? w.sql + " AND" : "WHERE"} s.is_sidechain = 0 AND s.duration_ms IS NOT NULL ORDER BY s.duration_ms`, ...w.params).map((r) => r.d);
|
|
5191
5206
|
return {
|
|
5192
5207
|
range: { from: f.from ?? null, to: f.to ?? null, source: f.source ?? null },
|
|
5193
5208
|
sessions: counts.sessions ?? 0,
|
|
@@ -5209,7 +5224,7 @@ function dashboardOverview(db, f) {
|
|
|
5209
5224
|
}
|
|
5210
5225
|
function chooseBucket(db, f) {
|
|
5211
5226
|
const w = sessionWhere(f);
|
|
5212
|
-
const r = db
|
|
5227
|
+
const r = queryGet(db, `SELECT MIN(started_at) mn, MAX(started_at) mx FROM sessions s ${w.sql}`, ...w.params);
|
|
5213
5228
|
if (!r?.mn || !r?.mx)
|
|
5214
5229
|
return "day";
|
|
5215
5230
|
const spanDays = (Date.parse(r.mx) - Date.parse(r.mn)) / 864e5;
|
|
@@ -5237,26 +5252,26 @@ function dashboardTimeseries(db, f, bucketParam) {
|
|
|
5237
5252
|
}
|
|
5238
5253
|
return r;
|
|
5239
5254
|
};
|
|
5240
|
-
for (const u of db
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5255
|
+
for (const u of queryAll(db, `SELECT ${expr} b, t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,
|
|
5256
|
+
SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr
|
|
5257
|
+
FROM token_usage t JOIN sessions s ON s.id = t.session_id
|
|
5258
|
+
${w.sql} GROUP BY b, t.model`, ...w.params)) {
|
|
5244
5259
|
if (!u.b)
|
|
5245
5260
|
continue;
|
|
5246
5261
|
const r = get(u.b);
|
|
5247
5262
|
addUsage(r, u);
|
|
5248
|
-
r.cost += costForUsage(u.model,
|
|
5263
|
+
r.cost += costForUsage(u.model, usageForCost(u));
|
|
5249
5264
|
}
|
|
5250
|
-
for (const s of db
|
|
5265
|
+
for (const s of queryAll(db, `SELECT ${expr} b, COUNT(*) n FROM sessions s ${w.sql} GROUP BY b`, ...w.params)) {
|
|
5251
5266
|
if (s.b)
|
|
5252
5267
|
get(s.b).sessions = s.n;
|
|
5253
5268
|
}
|
|
5254
|
-
for (const t of db
|
|
5269
|
+
for (const t of queryAll(db, `SELECT ${expr} b, COUNT(*) n FROM turns tn JOIN sessions s ON s.id = tn.session_id ${w.sql} GROUP BY b`, ...w.params)) {
|
|
5255
5270
|
if (t.b)
|
|
5256
5271
|
get(t.b).turns = t.n;
|
|
5257
5272
|
}
|
|
5258
|
-
for (const e of db
|
|
5259
|
-
|
|
5273
|
+
for (const e of queryAll(db, `SELECT ${expr} b, tc.error_type et, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5274
|
+
${w.sql ? w.sql + " AND" : "WHERE"} tc.status = 'error' GROUP BY b, tc.error_type`, ...w.params)) {
|
|
5260
5275
|
if (!e.b)
|
|
5261
5276
|
continue;
|
|
5262
5277
|
const r = get(e.b);
|
|
@@ -5272,53 +5287,60 @@ function dashboardTimeseries(db, f, bucketParam) {
|
|
|
5272
5287
|
}
|
|
5273
5288
|
var andWhere = (w) => w.sql ? w.sql + " AND" : "WHERE";
|
|
5274
5289
|
function modelBreakdown(db, w) {
|
|
5275
|
-
const modelRows =
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5290
|
+
const modelRows = queryAll(
|
|
5291
|
+
db,
|
|
5292
|
+
// COALESCE the bucket label, exactly as by_source does for a null source_id: token_usage.model is
|
|
5293
|
+
// nullable and this GROUP BY — unlike the model FILTER list — does not exclude nulls, so a
|
|
5294
|
+
// null-model bucket reaches the chart. It used to render as a nameless bar.
|
|
5295
|
+
`SELECT COALESCE(t.model, '(unknown)') model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,
|
|
5296
|
+
SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr,
|
|
5297
|
+
COUNT(DISTINCT t.session_id) sessions
|
|
5298
|
+
FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}
|
|
5299
|
+
GROUP BY t.model ORDER BY (SUM(t.input_tokens)+SUM(t.output_tokens)+SUM(t.cache_creation_input_tokens)+SUM(t.cache_read_input_tokens)) DESC`,
|
|
5300
|
+
...w.params
|
|
5301
|
+
);
|
|
5280
5302
|
return modelRows.map((u) => ({
|
|
5281
5303
|
model: u.model,
|
|
5282
5304
|
tokens: { input: u.i ?? 0, output: u.o ?? 0, cache_creation: u.cw ?? 0, cache_read: u.cr ?? 0 },
|
|
5283
5305
|
total_tokens: (u.i ?? 0) + (u.o ?? 0) + (u.cw ?? 0) + (u.cr ?? 0),
|
|
5284
|
-
cost: Number(costForUsage(u.model,
|
|
5306
|
+
cost: Number(costForUsage(u.model, usageForCost(u)).toFixed(4)),
|
|
5285
5307
|
sessions: u.sessions ?? 0,
|
|
5286
5308
|
priced: !!rateForModel(u.model)
|
|
5287
5309
|
}));
|
|
5288
5310
|
}
|
|
5289
5311
|
function sourceBreakdown(db, w) {
|
|
5290
|
-
return db
|
|
5291
|
-
|
|
5312
|
+
return queryAll(db, `SELECT COALESCE(s.source_id, '(none)') source, COUNT(*) sessions, SUM(s.turn_count) turns
|
|
5313
|
+
FROM sessions s ${w.sql} GROUP BY s.source_id ORDER BY sessions DESC`, ...w.params);
|
|
5292
5314
|
}
|
|
5293
5315
|
function classificationBreakdowns(db, w) {
|
|
5294
5316
|
const mainW = { sql: w.sql ? w.sql + " AND s.is_sidechain = 0" : "WHERE s.is_sidechain = 0", params: w.params };
|
|
5295
|
-
const byCategory = db
|
|
5296
|
-
|
|
5297
|
-
const byComplexity = db
|
|
5298
|
-
|
|
5317
|
+
const byCategory = queryAll(db, `SELECT c.category, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id
|
|
5318
|
+
${mainW.sql} GROUP BY c.category ORDER BY n DESC`, ...mainW.params);
|
|
5319
|
+
const byComplexity = queryAll(db, `SELECT c.complexity_band band, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id
|
|
5320
|
+
${mainW.sql} GROUP BY c.complexity_band ORDER BY n DESC`, ...mainW.params);
|
|
5299
5321
|
return { byCategory, byComplexity };
|
|
5300
5322
|
}
|
|
5301
5323
|
function toolBreakdown(db, w) {
|
|
5302
|
-
return db
|
|
5303
|
-
|
|
5324
|
+
return queryAll(db, `SELECT tc.tool_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5325
|
+
${w.sql} GROUP BY tc.tool_name ORDER BY n DESC LIMIT 20`, ...w.params);
|
|
5304
5326
|
}
|
|
5305
5327
|
function skillBreakdowns(db, w) {
|
|
5306
|
-
const skills = db
|
|
5307
|
-
|
|
5308
|
-
const skillVersions = db
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5328
|
+
const skills = queryAll(db, `SELECT tc.skill_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5329
|
+
${andWhere(w)} tc.skill_name IS NOT NULL GROUP BY tc.skill_name ORDER BY n DESC LIMIT 20`, ...w.params);
|
|
5330
|
+
const skillVersions = queryAll(db, `SELECT tc.skill_name name, sk.id version_id, sk.summary, sk.last_seen, COUNT(*) n
|
|
5331
|
+
FROM tool_calls tc
|
|
5332
|
+
JOIN skills sk ON sk.id = tc.skill_id
|
|
5333
|
+
JOIN sessions s ON s.id = tc.session_id
|
|
5334
|
+
${andWhere(w)} tc.skill_id IS NOT NULL
|
|
5335
|
+
GROUP BY tc.skill_name, sk.id ORDER BY name, n DESC`, ...w.params);
|
|
5314
5336
|
return { skills, skillVersions };
|
|
5315
5337
|
}
|
|
5316
5338
|
function subagentFanoutBreakdown(db, w) {
|
|
5317
|
-
const subagentByType = db
|
|
5318
|
-
|
|
5319
|
-
const perSession = db
|
|
5320
|
-
|
|
5321
|
-
|
|
5339
|
+
const subagentByType = queryAll(db, `SELECT tc.agent_type type, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5340
|
+
${andWhere(w)} tc.agent_type IS NOT NULL GROUP BY tc.agent_type ORDER BY n DESC`, ...w.params);
|
|
5341
|
+
const perSession = queryAll(db, `SELECT COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5342
|
+
${andWhere(w)} s.is_sidechain = 0 AND tc.tool_name IN ('Agent','Task')
|
|
5343
|
+
GROUP BY tc.session_id`, ...w.params);
|
|
5322
5344
|
const counts = perSession.map((r) => r.n);
|
|
5323
5345
|
return {
|
|
5324
5346
|
by_type: subagentByType,
|
|
@@ -5329,8 +5351,8 @@ function subagentFanoutBreakdown(db, w) {
|
|
|
5329
5351
|
};
|
|
5330
5352
|
}
|
|
5331
5353
|
function errorTypeBreakdown(db, w) {
|
|
5332
|
-
const errorRows = db
|
|
5333
|
-
|
|
5354
|
+
const errorRows = queryAll(db, `SELECT COALESCE(tc.error_type, 'other') type, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5355
|
+
${andWhere(w)} tc.status = 'error' GROUP BY tc.error_type ORDER BY n DESC`, ...w.params);
|
|
5334
5356
|
let failures = 0;
|
|
5335
5357
|
let rejections = 0;
|
|
5336
5358
|
const byType = errorRows.map((r) => {
|
|
@@ -5650,8 +5672,7 @@ async function createApp(db, opts = {}) {
|
|
|
5650
5672
|
if (!guardWrite(req, reply))
|
|
5651
5673
|
return reply;
|
|
5652
5674
|
const b = req.body ?? {};
|
|
5653
|
-
const
|
|
5654
|
-
const ids = page.findings.map((r) => r.id);
|
|
5675
|
+
const ids = openFindingIds(db, findingFilters(b.filter ?? {}));
|
|
5655
5676
|
const n = dismiss(triageDb, ids, b.note ?? null);
|
|
5656
5677
|
return { ok: true, dismissed: n };
|
|
5657
5678
|
});
|