@klars/agentobs 0.1.5 → 0.1.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/commands/stats.js +32 -8
- package/dist/core/queries.js +8 -0
- package/dist/server/public/app.css +18 -0
- package/dist/server/public/app.js +22 -3
- package/package.json +1 -1
package/dist/commands/stats.js
CHANGED
|
@@ -19,15 +19,39 @@ export async function stats(opts) {
|
|
|
19
19
|
console.log(JSON.stringify({ summary, tools }, null, 2));
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
console.log(`
|
|
23
|
-
AgentObs · ${range}
|
|
24
|
-
|
|
25
|
-
Cost ${money(summary.total_cost_usd)}
|
|
26
|
-
Tool calls ${summary.tool_calls}
|
|
27
|
-
Sessions ${summary.sessions}
|
|
28
|
-
Errors ${summary.errors} (${(summary.error_rate * 100).toFixed(1)}%)
|
|
29
|
-
Blocked ${summary.blocked}
|
|
22
|
+
console.log(`
|
|
23
|
+
AgentObs · ${range}
|
|
24
|
+
|
|
25
|
+
Cost ${money(summary.total_cost_usd)}
|
|
26
|
+
Tool calls ${summary.tool_calls}
|
|
27
|
+
Sessions ${summary.sessions}
|
|
28
|
+
Errors ${summary.errors} (${(summary.error_rate * 100).toFixed(1)}%)
|
|
29
|
+
Blocked ${summary.blocked}
|
|
30
30
|
Tokens ${summary.tokens_in.toLocaleString()} in / ${summary.tokens_out.toLocaleString()} out`);
|
|
31
|
+
// A zero tool-call count next to a non-zero session count is the single most
|
|
32
|
+
// confusing thing a new user sees - it reads as a broken install. Say which
|
|
33
|
+
// integration produced the data and what it can and cannot see.
|
|
34
|
+
if (summary.tool_calls === 0 && summary.coarse_sessions > 0) {
|
|
35
|
+
const n = summary.coarse_sessions;
|
|
36
|
+
console.log([
|
|
37
|
+
'',
|
|
38
|
+
` Why the zeros: all ${n} session${n === 1 ? '' : 's'} came from "agentobs run"`,
|
|
39
|
+
' (process-wrap), which observes a process from the outside - it records',
|
|
40
|
+
' duration and exit code, but cannot see individual tool calls or tokens.',
|
|
41
|
+
'',
|
|
42
|
+
' For per-tool-call detail and cost, use the Claude Code hook (see',
|
|
43
|
+
' "agentobs init") or ingest a structured log with "agentobs watch".',
|
|
44
|
+
].join(String.fromCharCode(10)));
|
|
45
|
+
}
|
|
46
|
+
else if (summary.coarse_sessions > 0 && summary.rich_sessions > 0) {
|
|
47
|
+
// Mixed data: the totals are real but under-count, since coarse sessions
|
|
48
|
+
// contribute no tool calls or tokens of their own.
|
|
49
|
+
console.log([
|
|
50
|
+
'',
|
|
51
|
+
` Note: ${summary.coarse_sessions} of ${summary.sessions} sessions are coarse (process-wrap),`,
|
|
52
|
+
' so they add no tool calls or tokens to these totals.',
|
|
53
|
+
].join(String.fromCharCode(10)));
|
|
54
|
+
}
|
|
31
55
|
// State plainly when the cost total is incomplete rather than letting a
|
|
32
56
|
// partial number read as the whole spend.
|
|
33
57
|
if (summary.uncosted_calls > 0) {
|
package/dist/core/queries.js
CHANGED
|
@@ -57,6 +57,12 @@ export function getSummary(db, range) {
|
|
|
57
57
|
FROM tool_calls ${where}`)
|
|
58
58
|
.get(...args);
|
|
59
59
|
const sessions = db.prepare(`SELECT COUNT(*) AS n FROM sessions ${where}`).get(...args);
|
|
60
|
+
const byFidelity = db
|
|
61
|
+
.prepare(`SELECT
|
|
62
|
+
COALESCE(SUM(CASE WHEN fidelity = 'coarse' THEN 1 ELSE 0 END), 0) AS coarse,
|
|
63
|
+
COALESCE(SUM(CASE WHEN fidelity <> 'coarse' THEN 1 ELSE 0 END), 0) AS rich
|
|
64
|
+
FROM sessions ${where}`)
|
|
65
|
+
.get(...args);
|
|
60
66
|
const toolCalls = Number(calls.tool_calls ?? 0);
|
|
61
67
|
const errors = Number(calls.errors ?? 0);
|
|
62
68
|
return {
|
|
@@ -72,6 +78,8 @@ export function getSummary(db, range) {
|
|
|
72
78
|
tokens_in: Number(calls.tokens_in ?? 0),
|
|
73
79
|
tokens_out: Number(calls.tokens_out ?? 0),
|
|
74
80
|
avg_duration_ms: calls.avg_duration_ms === null ? null : Number(calls.avg_duration_ms),
|
|
81
|
+
coarse_sessions: Number(byFidelity.coarse ?? 0),
|
|
82
|
+
rich_sessions: Number(byFidelity.rich ?? 0),
|
|
75
83
|
previous: previousPeriod(db, range, since),
|
|
76
84
|
};
|
|
77
85
|
}
|
|
@@ -959,3 +959,21 @@ tbody td:first-child {
|
|
|
959
959
|
.tile[data-accent='sessions'] .tile-label .i { color: var(--series-3); }
|
|
960
960
|
.tile[data-accent='errors'] .tile-label .i { color: var(--status-critical); }
|
|
961
961
|
.tile[data-accent='blocked'] .tile-label .i { color: var(--status-serious); }
|
|
962
|
+
|
|
963
|
+
/* The hero note can run to a full explanatory sentence when the data is
|
|
964
|
+
coarse. Cap its width and keep it under the figure so it wraps tightly
|
|
965
|
+
instead of stretching the flex row and opening a gap beside the stats. */
|
|
966
|
+
.hero > div:first-child {
|
|
967
|
+
flex: 1 1 460px;
|
|
968
|
+
min-width: 0;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.hero-note {
|
|
972
|
+
max-width: 62ch;
|
|
973
|
+
line-height: 1.45;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.hero-side {
|
|
977
|
+
flex: 0 0 auto;
|
|
978
|
+
align-self: flex-start;
|
|
979
|
+
}
|
|
@@ -106,6 +106,7 @@ async function refresh() {
|
|
|
106
106
|
fetchJson('/api/sessions'),
|
|
107
107
|
]);
|
|
108
108
|
|
|
109
|
+
state.summary = summary;
|
|
109
110
|
renderSummary(summary);
|
|
110
111
|
state.timeline = timeline;
|
|
111
112
|
drawTimeline();
|
|
@@ -130,8 +131,16 @@ function renderSummary(s) {
|
|
|
130
131
|
// Say plainly when the cost figure is incomplete, rather than presenting a
|
|
131
132
|
// partial total as if it were the whole spend.
|
|
132
133
|
const note = document.getElementById('hero-note');
|
|
133
|
-
if (s.
|
|
134
|
+
if (s.sessions === 0) {
|
|
134
135
|
note.textContent = 'No activity recorded yet.';
|
|
136
|
+
} else if (s.tool_calls === 0 && s.coarse_sessions > 0) {
|
|
137
|
+
// Never say "no activity" while the Sessions tile shows a number - that
|
|
138
|
+
// contradiction reads as a broken install. Name the reason instead.
|
|
139
|
+
const n = s.coarse_sessions;
|
|
140
|
+
note.textContent =
|
|
141
|
+
`${count(n)} coarse session${n === 1 ? '' : 's'} from "agentobs run" — process-wrap sees duration and exit code, not tool calls or tokens. Connect the Claude Code hook for full detail.`;
|
|
142
|
+
} else if (s.coarse_sessions > 0 && s.rich_sessions > 0) {
|
|
143
|
+
note.textContent = `${count(s.coarse_sessions)} of ${count(s.sessions)} sessions are coarse, so they add no tool calls or tokens to these totals.`;
|
|
135
144
|
} else if (s.uncosted_calls > 0) {
|
|
136
145
|
note.textContent = `${count(s.uncosted_calls)} call${s.uncosted_calls === 1 ? '' : 's'} have no price for their model — add it to ~/.agentobs/pricing.json to include them.`;
|
|
137
146
|
} else {
|
|
@@ -182,7 +191,11 @@ function renderTools(rows) {
|
|
|
182
191
|
body.replaceChildren();
|
|
183
192
|
if (rows.length === 0) {
|
|
184
193
|
const tr = document.createElement('tr');
|
|
185
|
-
|
|
194
|
+
const msg =
|
|
195
|
+
state.summary && state.summary.coarse_sessions > 0
|
|
196
|
+
? 'Coarse sessions record no tool calls — connect the Claude Code hook for per-tool detail.'
|
|
197
|
+
: 'No tool calls recorded yet.';
|
|
198
|
+
tr.append(Object.assign(cell(msg, 'empty'), { colSpan: 5 }));
|
|
186
199
|
body.append(tr);
|
|
187
200
|
return;
|
|
188
201
|
}
|
|
@@ -328,7 +341,13 @@ function drawTimeline() {
|
|
|
328
341
|
ctx.fillStyle = muted;
|
|
329
342
|
ctx.font = '13px ' + cssVar('--font');
|
|
330
343
|
ctx.textAlign = 'center';
|
|
331
|
-
|
|
344
|
+
// Distinguish "nothing happened" from "the data here cannot have tool
|
|
345
|
+
// calls", which is what a coarse-only range actually means.
|
|
346
|
+
const msg =
|
|
347
|
+
state.summary && state.summary.coarse_sessions > 0
|
|
348
|
+
? 'Coarse sessions record no tool calls'
|
|
349
|
+
: 'No activity in this range';
|
|
350
|
+
ctx.fillText(msg, cssWidth / 2, cssHeight / 2);
|
|
332
351
|
return;
|
|
333
352
|
}
|
|
334
353
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klars/agentobs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Observability and control layer for AI coding agents - see every tool call, token, and dollar your agents spend, and stop them before they do something risky.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Klars AI",
|