@ouro.bot/cli 0.1.0-alpha.504 → 0.1.0-alpha.505
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/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.505",
|
|
6
|
+
"changes": [
|
|
7
|
+
"New `ouro session-stats <session.json>` CLI for at-a-glance metrics on a saved session: total events, breakdown by role (system/user/assistant/tool), tool-call totals + top 5 by frequency, attachment count, time range with duration, projection breakdown (in/out, input tokens, max tokens, trimmed), and last usage. Read-only.",
|
|
8
|
+
"Pure `computeSessionStats(envelope, path)` core in `src/heart/session-stats.ts` — testable with synthesized envelopes, embeddable in future doctor checks. `runSessionStats(path)` adds the file-load layer; `formatStatsReport(report)` renders human-readable text; `--json` mode for jq piping. Composes with #619 (session-playback) and #622 (nerves-review): three pure-analyzer-plus-thin-CLI tools that together make a stuck session immediately diagnosable end-to-end.",
|
|
9
|
+
"8 tests cover role counts, tool-call name aggregation with frequency-sorted top-5, time range with and without authoredAt timestamps, attachment counting, projection-omission detection, the unrecognized-envelope stub, CLI no-args help, and CLI --json output. Wired as `npm run session:stats -- <path>` and `dist/heart/session-stats-cli-main.js`."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.504",
|
|
6
14
|
"changes": [
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.computeSessionStats = computeSessionStats;
|
|
37
|
+
exports.runSessionStats = runSessionStats;
|
|
38
|
+
exports.formatStatsReport = formatStatsReport;
|
|
39
|
+
exports.runSessionStatsCli = runSessionStatsCli;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const session_events_1 = require("./session-events");
|
|
42
|
+
const ROLES = ["system", "user", "assistant", "tool"];
|
|
43
|
+
function emptyByRole() {
|
|
44
|
+
const counts = {};
|
|
45
|
+
for (const role of ROLES)
|
|
46
|
+
counts[role] = 0;
|
|
47
|
+
return counts;
|
|
48
|
+
}
|
|
49
|
+
function eventTimeMs(event) {
|
|
50
|
+
const value = event.time.authoredAt ?? event.time.observedAt ?? null;
|
|
51
|
+
if (!value)
|
|
52
|
+
return null;
|
|
53
|
+
const ms = Date.parse(value);
|
|
54
|
+
return Number.isFinite(ms) ? ms : null;
|
|
55
|
+
}
|
|
56
|
+
function computeSessionStats(envelope, sessionPath) {
|
|
57
|
+
const byRole = emptyByRole();
|
|
58
|
+
let toolCallTotal = 0;
|
|
59
|
+
let attachments = 0;
|
|
60
|
+
const toolNameCounts = new Map();
|
|
61
|
+
let earliestMs = null;
|
|
62
|
+
let latestMs = null;
|
|
63
|
+
for (const event of envelope.events) {
|
|
64
|
+
byRole[event.role] = (byRole[event.role] ?? 0) + 1;
|
|
65
|
+
attachments += event.attachments.length;
|
|
66
|
+
for (const call of event.toolCalls) {
|
|
67
|
+
toolCallTotal += 1;
|
|
68
|
+
const name = call.function.name;
|
|
69
|
+
toolNameCounts.set(name, (toolNameCounts.get(name) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
const ms = eventTimeMs(event);
|
|
72
|
+
if (ms !== null) {
|
|
73
|
+
if (earliestMs === null || ms < earliestMs)
|
|
74
|
+
earliestMs = ms;
|
|
75
|
+
if (latestMs === null || ms > latestMs)
|
|
76
|
+
latestMs = ms;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const top = [...toolNameCounts.entries()]
|
|
80
|
+
.sort((left, right) => right[1] - left[1])
|
|
81
|
+
.slice(0, 5)
|
|
82
|
+
.map(([name, count]) => ({ name, count }));
|
|
83
|
+
return {
|
|
84
|
+
sessionPath,
|
|
85
|
+
envelopeVersion: envelope.version,
|
|
86
|
+
totalEvents: envelope.events.length,
|
|
87
|
+
byRole,
|
|
88
|
+
toolCalls: {
|
|
89
|
+
total: toolCallTotal,
|
|
90
|
+
distinctNames: toolNameCounts.size,
|
|
91
|
+
topByFrequency: top,
|
|
92
|
+
},
|
|
93
|
+
attachments,
|
|
94
|
+
timeRange: {
|
|
95
|
+
earliest: earliestMs !== null ? new Date(earliestMs).toISOString() : null,
|
|
96
|
+
latest: latestMs !== null ? new Date(latestMs).toISOString() : null,
|
|
97
|
+
durationMs: earliestMs !== null && latestMs !== null ? latestMs - earliestMs : null,
|
|
98
|
+
},
|
|
99
|
+
projection: {
|
|
100
|
+
eventCount: envelope.projection.eventIds.length,
|
|
101
|
+
omittedFromProjection: Math.max(0, envelope.events.length - envelope.projection.eventIds.length),
|
|
102
|
+
inputTokens: envelope.projection.inputTokens,
|
|
103
|
+
maxTokens: envelope.projection.maxTokens,
|
|
104
|
+
trimmed: envelope.projection.trimmed,
|
|
105
|
+
},
|
|
106
|
+
lastUsage: envelope.lastUsage,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function runSessionStats(sessionPath) {
|
|
110
|
+
const text = fs.readFileSync(sessionPath, "utf-8");
|
|
111
|
+
const raw = JSON.parse(text);
|
|
112
|
+
const envelope = (0, session_events_1.parseSessionEnvelope)(raw);
|
|
113
|
+
if (!envelope) {
|
|
114
|
+
return {
|
|
115
|
+
sessionPath,
|
|
116
|
+
envelopeVersion: null,
|
|
117
|
+
totalEvents: 0,
|
|
118
|
+
byRole: emptyByRole(),
|
|
119
|
+
toolCalls: { total: 0, distinctNames: 0, topByFrequency: [] },
|
|
120
|
+
attachments: 0,
|
|
121
|
+
timeRange: { earliest: null, latest: null, durationMs: null },
|
|
122
|
+
projection: { eventCount: 0, omittedFromProjection: 0, inputTokens: null, maxTokens: null, trimmed: false },
|
|
123
|
+
lastUsage: null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return computeSessionStats(envelope, sessionPath);
|
|
127
|
+
}
|
|
128
|
+
function formatStatsReport(report) {
|
|
129
|
+
const lines = [];
|
|
130
|
+
lines.push(`Session stats: ${report.sessionPath}`);
|
|
131
|
+
if (report.envelopeVersion === null) {
|
|
132
|
+
lines.push(" envelope: unrecognized (could not parse)");
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
lines.push(` envelope version: ${report.envelopeVersion}`);
|
|
136
|
+
lines.push(` total events: ${report.totalEvents}`);
|
|
137
|
+
lines.push(` by role: system=${report.byRole.system} user=${report.byRole.user} assistant=${report.byRole.assistant} tool=${report.byRole.tool}`);
|
|
138
|
+
lines.push(` tool calls: ${report.toolCalls.total} (${report.toolCalls.distinctNames} distinct names)`);
|
|
139
|
+
if (report.toolCalls.topByFrequency.length > 0) {
|
|
140
|
+
lines.push(" top tools:");
|
|
141
|
+
for (const { name, count } of report.toolCalls.topByFrequency) {
|
|
142
|
+
lines.push(` ${name}: ${count}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
lines.push(` attachments: ${report.attachments}`);
|
|
146
|
+
if (report.timeRange.earliest && report.timeRange.latest) {
|
|
147
|
+
const durationSec = report.timeRange.durationMs !== null ? Math.round(report.timeRange.durationMs / 1000) : null;
|
|
148
|
+
lines.push(` time range: ${report.timeRange.earliest} → ${report.timeRange.latest}${durationSec !== null ? ` (${durationSec}s)` : ""}`);
|
|
149
|
+
}
|
|
150
|
+
lines.push(" projection:");
|
|
151
|
+
lines.push(` in projection: ${report.projection.eventCount}`);
|
|
152
|
+
lines.push(` omitted: ${report.projection.omittedFromProjection}`);
|
|
153
|
+
if (report.projection.inputTokens !== null)
|
|
154
|
+
lines.push(` input tokens: ${report.projection.inputTokens}`);
|
|
155
|
+
if (report.projection.maxTokens !== null)
|
|
156
|
+
lines.push(` max tokens: ${report.projection.maxTokens}`);
|
|
157
|
+
if (report.projection.trimmed)
|
|
158
|
+
lines.push(" trimmed: true");
|
|
159
|
+
if (report.lastUsage) {
|
|
160
|
+
lines.push(` last usage: ${JSON.stringify(report.lastUsage)}`);
|
|
161
|
+
}
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
function runSessionStatsCli(argv) {
|
|
165
|
+
const positional = argv.filter((token) => !token.startsWith("--"));
|
|
166
|
+
const flags = new Set(argv.filter((token) => token.startsWith("--")));
|
|
167
|
+
if (flags.has("--help") || flags.has("-h") || positional.length === 0) {
|
|
168
|
+
// eslint-disable-next-line no-console -- meta-tooling
|
|
169
|
+
console.log("usage: ouro session-stats <session.json> [--json]");
|
|
170
|
+
return positional.length === 0 ? 2 : 0;
|
|
171
|
+
}
|
|
172
|
+
const report = runSessionStats(positional[0]);
|
|
173
|
+
if (flags.has("--json")) {
|
|
174
|
+
// eslint-disable-next-line no-console -- meta-tooling
|
|
175
|
+
console.log(JSON.stringify(report, null, 2));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
// eslint-disable-next-line no-console -- meta-tooling
|
|
179
|
+
console.log(formatStatsReport(report));
|
|
180
|
+
}
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
@@ -129,6 +129,10 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
129
129
|
// lives at the caller (tools-mail.ts mail_body handler) which fires
|
|
130
130
|
// repertoire.mail_body_cache_hit on cache reuse.
|
|
131
131
|
"mailroom/body-cache",
|
|
132
|
+
// Session stats: read-only session.json analyzer CLI for debugging.
|
|
133
|
+
// Diagnostics-only utility; output is human-readable summary.
|
|
134
|
+
"heart/session-stats-cli-main",
|
|
135
|
+
"heart/session-stats",
|
|
132
136
|
];
|
|
133
137
|
function isDispatchExempt(filePath) {
|
|
134
138
|
return DISPATCH_EXEMPT_PATTERNS.some((pattern) => filePath.includes(pattern));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.505",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"lint": "eslint src/",
|
|
38
38
|
"release:preflight": "node scripts/release-preflight.cjs",
|
|
39
39
|
"release:smoke": "node scripts/release-smoke.cjs",
|
|
40
|
-
"audit:nerves": "npm run build && node dist/nerves/coverage/cli-main.js"
|
|
40
|
+
"audit:nerves": "npm run build && node dist/nerves/coverage/cli-main.js",
|
|
41
|
+
"session:stats": "npm run build && node dist/heart/session-stats-cli-main.js"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@anthropic-ai/sdk": "^0.78.0",
|