@swestash/agent-lens 0.9.4 → 0.9.5
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 +234 -225
- 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,18 +5021,18 @@ 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
|
}
|
|
5042
5038
|
function securitySummary(db) {
|
|
@@ -5048,15 +5044,15 @@ function securitySummary(db) {
|
|
|
5048
5044
|
const dismissJoin = attached ? "LEFT JOIN triage.dismissed_findings d ON d.finding_id = f.id" : "";
|
|
5049
5045
|
const base = `FROM findings f JOIN sessions s ON s.id = f.session_id ${dismissJoin}`;
|
|
5050
5046
|
const openWhere = attached ? `WHERE d.finding_id IS NULL AND NOT ${MUTED_SQL}` : "";
|
|
5051
|
-
const scalar = (sql) => db
|
|
5047
|
+
const scalar = (sql) => queryGet(db, sql).n;
|
|
5052
5048
|
const total = scalar(`SELECT COUNT(*) n ${base} ${openWhere}`);
|
|
5053
5049
|
const sessions_flagged = scalar(`SELECT COUNT(DISTINCT f.session_id) n ${base} ${openWhere}`);
|
|
5054
5050
|
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
5051
|
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
|
-
|
|
5052
|
+
const by_severity = queryAll(db, `SELECT f.severity AS severity, COUNT(*) n ${base} ${openWhere} GROUP BY f.severity ORDER BY ${SEVERITY_RANK_SQL} DESC`);
|
|
5053
|
+
const by_category = queryAll(db, `SELECT f.category AS category, COUNT(*) n ${base} ${openWhere} GROUP BY f.category ORDER BY n DESC`);
|
|
5054
|
+
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
|
|
5055
|
+
${base} ${openWhere} GROUP BY f.rule_id ORDER BY rank DESC, n DESC`);
|
|
5060
5056
|
return { total, sessions_flagged, dismissed, muted, by_severity, by_category, by_rule, categories };
|
|
5061
5057
|
}
|
|
5062
5058
|
|
|
@@ -5113,6 +5109,12 @@ function addUsage(acc, r) {
|
|
|
5113
5109
|
acc.cache_creation += r.cw ?? 0;
|
|
5114
5110
|
acc.cache_read += r.cr ?? 0;
|
|
5115
5111
|
}
|
|
5112
|
+
var usageForCost = (u) => ({
|
|
5113
|
+
input_tokens: u.i ?? 0,
|
|
5114
|
+
output_tokens: u.o ?? 0,
|
|
5115
|
+
cache_creation_input_tokens: u.cw ?? 0,
|
|
5116
|
+
cache_read_input_tokens: u.cr ?? 0
|
|
5117
|
+
});
|
|
5116
5118
|
function pct(sorted, p) {
|
|
5117
5119
|
if (sorted.length === 0)
|
|
5118
5120
|
return 0;
|
|
@@ -5132,11 +5134,11 @@ function workflowAgg(db, f) {
|
|
|
5132
5134
|
if (!tableExists(db, "workflow_results"))
|
|
5133
5135
|
return empty;
|
|
5134
5136
|
const ww = workflowWhere(f);
|
|
5135
|
-
const rows = db
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5137
|
+
const rows = queryAll(db, `SELECT COALESCE(status, '(unknown)') status, COUNT(*) n,
|
|
5138
|
+
SUM(COALESCE(total_tokens, 0)) tokens,
|
|
5139
|
+
SUM(COALESCE(duration_ms, 0)) dur,
|
|
5140
|
+
SUM(CASE WHEN duration_ms IS NOT NULL THEN 1 ELSE 0 END) dur_n
|
|
5141
|
+
FROM workflow_results ${ww.sql} GROUP BY status ORDER BY n DESC`, ...ww.params);
|
|
5140
5142
|
let total = 0, completed = 0, failed = 0, tokens = 0, durSum = 0, durN = 0;
|
|
5141
5143
|
const by_status = rows.map((r) => {
|
|
5142
5144
|
total += r.n;
|
|
@@ -5162,32 +5164,32 @@ function workflowAgg(db, f) {
|
|
|
5162
5164
|
}
|
|
5163
5165
|
function dashboardOverview(db, f) {
|
|
5164
5166
|
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
|
-
|
|
5167
|
+
const counts = queryGet(db, `SELECT
|
|
5168
|
+
COUNT(*) sessions,
|
|
5169
|
+
SUM(CASE WHEN is_sidechain=0 THEN 1 ELSE 0 END) main,
|
|
5170
|
+
SUM(CASE WHEN is_sidechain=1 THEN 1 ELSE 0 END) subagent,
|
|
5171
|
+
SUM(turn_count) turns,
|
|
5172
|
+
COUNT(DISTINCT project_id) projects
|
|
5173
|
+
FROM sessions s ${w.sql}`, ...w.params);
|
|
5174
|
+
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;
|
|
5175
|
+
const usage = queryAll(db, `SELECT t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,
|
|
5176
|
+
SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr
|
|
5177
|
+
FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}
|
|
5178
|
+
GROUP BY t.model`, ...w.params);
|
|
5177
5179
|
const tokens = zeroSplit();
|
|
5178
5180
|
let cost = 0;
|
|
5179
5181
|
const unpriced = [];
|
|
5180
5182
|
for (const u of usage) {
|
|
5181
5183
|
addUsage(tokens, u);
|
|
5182
|
-
cost += costForUsage(u.model,
|
|
5184
|
+
cost += costForUsage(u.model, usageForCost(u));
|
|
5183
5185
|
if (u.model && !rateForModel(u.model) && (u.i || u.o || u.cw || u.cr))
|
|
5184
5186
|
unpriced.push(u.model);
|
|
5185
5187
|
}
|
|
5186
5188
|
const totalTokens = tokens.input + tokens.output + tokens.cache_creation + tokens.cache_read;
|
|
5187
|
-
const durs = db
|
|
5188
|
-
|
|
5189
|
-
const sessDurs = db
|
|
5190
|
-
|
|
5189
|
+
const durs = queryAll(db, `SELECT tn.duration_ms d FROM turns tn JOIN sessions s ON s.id = tn.session_id
|
|
5190
|
+
${w.sql ? w.sql + " AND" : "WHERE"} tn.duration_ms IS NOT NULL ORDER BY tn.duration_ms`, ...w.params).map((r) => r.d);
|
|
5191
|
+
const sessDurs = queryAll(db, `SELECT s.duration_ms d FROM sessions s
|
|
5192
|
+
${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
5193
|
return {
|
|
5192
5194
|
range: { from: f.from ?? null, to: f.to ?? null, source: f.source ?? null },
|
|
5193
5195
|
sessions: counts.sessions ?? 0,
|
|
@@ -5209,7 +5211,7 @@ function dashboardOverview(db, f) {
|
|
|
5209
5211
|
}
|
|
5210
5212
|
function chooseBucket(db, f) {
|
|
5211
5213
|
const w = sessionWhere(f);
|
|
5212
|
-
const r = db
|
|
5214
|
+
const r = queryGet(db, `SELECT MIN(started_at) mn, MAX(started_at) mx FROM sessions s ${w.sql}`, ...w.params);
|
|
5213
5215
|
if (!r?.mn || !r?.mx)
|
|
5214
5216
|
return "day";
|
|
5215
5217
|
const spanDays = (Date.parse(r.mx) - Date.parse(r.mn)) / 864e5;
|
|
@@ -5237,26 +5239,26 @@ function dashboardTimeseries(db, f, bucketParam) {
|
|
|
5237
5239
|
}
|
|
5238
5240
|
return r;
|
|
5239
5241
|
};
|
|
5240
|
-
for (const u of db
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5242
|
+
for (const u of queryAll(db, `SELECT ${expr} b, t.model model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,
|
|
5243
|
+
SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr
|
|
5244
|
+
FROM token_usage t JOIN sessions s ON s.id = t.session_id
|
|
5245
|
+
${w.sql} GROUP BY b, t.model`, ...w.params)) {
|
|
5244
5246
|
if (!u.b)
|
|
5245
5247
|
continue;
|
|
5246
5248
|
const r = get(u.b);
|
|
5247
5249
|
addUsage(r, u);
|
|
5248
|
-
r.cost += costForUsage(u.model,
|
|
5250
|
+
r.cost += costForUsage(u.model, usageForCost(u));
|
|
5249
5251
|
}
|
|
5250
|
-
for (const s of db
|
|
5252
|
+
for (const s of queryAll(db, `SELECT ${expr} b, COUNT(*) n FROM sessions s ${w.sql} GROUP BY b`, ...w.params)) {
|
|
5251
5253
|
if (s.b)
|
|
5252
5254
|
get(s.b).sessions = s.n;
|
|
5253
5255
|
}
|
|
5254
|
-
for (const t of db
|
|
5256
|
+
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
5257
|
if (t.b)
|
|
5256
5258
|
get(t.b).turns = t.n;
|
|
5257
5259
|
}
|
|
5258
|
-
for (const e of db
|
|
5259
|
-
|
|
5260
|
+
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
|
|
5261
|
+
${w.sql ? w.sql + " AND" : "WHERE"} tc.status = 'error' GROUP BY b, tc.error_type`, ...w.params)) {
|
|
5260
5262
|
if (!e.b)
|
|
5261
5263
|
continue;
|
|
5262
5264
|
const r = get(e.b);
|
|
@@ -5272,53 +5274,60 @@ function dashboardTimeseries(db, f, bucketParam) {
|
|
|
5272
5274
|
}
|
|
5273
5275
|
var andWhere = (w) => w.sql ? w.sql + " AND" : "WHERE";
|
|
5274
5276
|
function modelBreakdown(db, w) {
|
|
5275
|
-
const modelRows =
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5277
|
+
const modelRows = queryAll(
|
|
5278
|
+
db,
|
|
5279
|
+
// COALESCE the bucket label, exactly as by_source does for a null source_id: token_usage.model is
|
|
5280
|
+
// nullable and this GROUP BY — unlike the model FILTER list — does not exclude nulls, so a
|
|
5281
|
+
// null-model bucket reaches the chart. It used to render as a nameless bar.
|
|
5282
|
+
`SELECT COALESCE(t.model, '(unknown)') model, SUM(t.input_tokens) i, SUM(t.output_tokens) o,
|
|
5283
|
+
SUM(t.cache_creation_input_tokens) cw, SUM(t.cache_read_input_tokens) cr,
|
|
5284
|
+
COUNT(DISTINCT t.session_id) sessions
|
|
5285
|
+
FROM token_usage t JOIN sessions s ON s.id = t.session_id ${w.sql}
|
|
5286
|
+
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`,
|
|
5287
|
+
...w.params
|
|
5288
|
+
);
|
|
5280
5289
|
return modelRows.map((u) => ({
|
|
5281
5290
|
model: u.model,
|
|
5282
5291
|
tokens: { input: u.i ?? 0, output: u.o ?? 0, cache_creation: u.cw ?? 0, cache_read: u.cr ?? 0 },
|
|
5283
5292
|
total_tokens: (u.i ?? 0) + (u.o ?? 0) + (u.cw ?? 0) + (u.cr ?? 0),
|
|
5284
|
-
cost: Number(costForUsage(u.model,
|
|
5293
|
+
cost: Number(costForUsage(u.model, usageForCost(u)).toFixed(4)),
|
|
5285
5294
|
sessions: u.sessions ?? 0,
|
|
5286
5295
|
priced: !!rateForModel(u.model)
|
|
5287
5296
|
}));
|
|
5288
5297
|
}
|
|
5289
5298
|
function sourceBreakdown(db, w) {
|
|
5290
|
-
return db
|
|
5291
|
-
|
|
5299
|
+
return queryAll(db, `SELECT COALESCE(s.source_id, '(none)') source, COUNT(*) sessions, SUM(s.turn_count) turns
|
|
5300
|
+
FROM sessions s ${w.sql} GROUP BY s.source_id ORDER BY sessions DESC`, ...w.params);
|
|
5292
5301
|
}
|
|
5293
5302
|
function classificationBreakdowns(db, w) {
|
|
5294
5303
|
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
|
-
|
|
5304
|
+
const byCategory = queryAll(db, `SELECT c.category, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id
|
|
5305
|
+
${mainW.sql} GROUP BY c.category ORDER BY n DESC`, ...mainW.params);
|
|
5306
|
+
const byComplexity = queryAll(db, `SELECT c.complexity_band band, COUNT(*) n FROM classifications c JOIN sessions s ON s.id = c.target_id
|
|
5307
|
+
${mainW.sql} GROUP BY c.complexity_band ORDER BY n DESC`, ...mainW.params);
|
|
5299
5308
|
return { byCategory, byComplexity };
|
|
5300
5309
|
}
|
|
5301
5310
|
function toolBreakdown(db, w) {
|
|
5302
|
-
return db
|
|
5303
|
-
|
|
5311
|
+
return queryAll(db, `SELECT tc.tool_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5312
|
+
${w.sql} GROUP BY tc.tool_name ORDER BY n DESC LIMIT 20`, ...w.params);
|
|
5304
5313
|
}
|
|
5305
5314
|
function skillBreakdowns(db, w) {
|
|
5306
|
-
const skills = db
|
|
5307
|
-
|
|
5308
|
-
const skillVersions = db
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5315
|
+
const skills = queryAll(db, `SELECT tc.skill_name name, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5316
|
+
${andWhere(w)} tc.skill_name IS NOT NULL GROUP BY tc.skill_name ORDER BY n DESC LIMIT 20`, ...w.params);
|
|
5317
|
+
const skillVersions = queryAll(db, `SELECT tc.skill_name name, sk.id version_id, sk.summary, sk.last_seen, COUNT(*) n
|
|
5318
|
+
FROM tool_calls tc
|
|
5319
|
+
JOIN skills sk ON sk.id = tc.skill_id
|
|
5320
|
+
JOIN sessions s ON s.id = tc.session_id
|
|
5321
|
+
${andWhere(w)} tc.skill_id IS NOT NULL
|
|
5322
|
+
GROUP BY tc.skill_name, sk.id ORDER BY name, n DESC`, ...w.params);
|
|
5314
5323
|
return { skills, skillVersions };
|
|
5315
5324
|
}
|
|
5316
5325
|
function subagentFanoutBreakdown(db, w) {
|
|
5317
|
-
const subagentByType = db
|
|
5318
|
-
|
|
5319
|
-
const perSession = db
|
|
5320
|
-
|
|
5321
|
-
|
|
5326
|
+
const subagentByType = queryAll(db, `SELECT tc.agent_type type, COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5327
|
+
${andWhere(w)} tc.agent_type IS NOT NULL GROUP BY tc.agent_type ORDER BY n DESC`, ...w.params);
|
|
5328
|
+
const perSession = queryAll(db, `SELECT COUNT(*) n FROM tool_calls tc JOIN sessions s ON s.id = tc.session_id
|
|
5329
|
+
${andWhere(w)} s.is_sidechain = 0 AND tc.tool_name IN ('Agent','Task')
|
|
5330
|
+
GROUP BY tc.session_id`, ...w.params);
|
|
5322
5331
|
const counts = perSession.map((r) => r.n);
|
|
5323
5332
|
return {
|
|
5324
5333
|
by_type: subagentByType,
|
|
@@ -5329,8 +5338,8 @@ function subagentFanoutBreakdown(db, w) {
|
|
|
5329
5338
|
};
|
|
5330
5339
|
}
|
|
5331
5340
|
function errorTypeBreakdown(db, w) {
|
|
5332
|
-
const errorRows = db
|
|
5333
|
-
|
|
5341
|
+
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
|
|
5342
|
+
${andWhere(w)} tc.status = 'error' GROUP BY tc.error_type ORDER BY n DESC`, ...w.params);
|
|
5334
5343
|
let failures = 0;
|
|
5335
5344
|
let rejections = 0;
|
|
5336
5345
|
const byType = errorRows.map((r) => {
|