trickle-cli 0.1.198 → 0.1.199
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/summarize.d.ts +9 -0
- package/dist/commands/summarize.js +224 -0
- package/dist/index.js +9 -0
- package/package.json +1 -1
- package/src/commands/summarize.ts +208 -0
- package/src/index.ts +10 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trickle summarize — Compress verbose agent traces into key decision points.
|
|
3
|
+
*
|
|
4
|
+
* Reads agents.jsonl, llm.jsonl, mcp.jsonl and produces a concise
|
|
5
|
+
* narrative of what the agent did, why, and at what cost.
|
|
6
|
+
*/
|
|
7
|
+
export declare function summarizeCommand(opts: {
|
|
8
|
+
json?: boolean;
|
|
9
|
+
}): void;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* trickle summarize — Compress verbose agent traces into key decision points.
|
|
4
|
+
*
|
|
5
|
+
* Reads agents.jsonl, llm.jsonl, mcp.jsonl and produces a concise
|
|
6
|
+
* narrative of what the agent did, why, and at what cost.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.summarizeCommand = summarizeCommand;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
49
|
+
function readJsonl(fp) {
|
|
50
|
+
if (!fs.existsSync(fp))
|
|
51
|
+
return [];
|
|
52
|
+
return fs.readFileSync(fp, 'utf-8').split('\n').filter(Boolean)
|
|
53
|
+
.map(l => { try {
|
|
54
|
+
return JSON.parse(l);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
} }).filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
function summarizeCommand(opts) {
|
|
61
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
62
|
+
const agentEvents = readJsonl(path.join(dir, 'agents.jsonl'));
|
|
63
|
+
const llmCalls = readJsonl(path.join(dir, 'llm.jsonl'));
|
|
64
|
+
const mcpCalls = readJsonl(path.join(dir, 'mcp.jsonl'));
|
|
65
|
+
const errors = readJsonl(path.join(dir, 'errors.jsonl'));
|
|
66
|
+
if (agentEvents.length === 0 && llmCalls.length === 0 && mcpCalls.length === 0) {
|
|
67
|
+
console.log(chalk_1.default.yellow(' No agent, LLM, or MCP data to summarize.'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const summary = buildSummary(agentEvents, llmCalls, mcpCalls, errors);
|
|
71
|
+
if (opts.json) {
|
|
72
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log(chalk_1.default.bold(' trickle summarize'));
|
|
77
|
+
console.log(chalk_1.default.gray(' ' + '─'.repeat(60)));
|
|
78
|
+
// One-line overview
|
|
79
|
+
console.log(` ${summary.overview}`);
|
|
80
|
+
console.log('');
|
|
81
|
+
// Key decisions
|
|
82
|
+
if (summary.decisions.length > 0) {
|
|
83
|
+
console.log(chalk_1.default.bold(' Key Decisions'));
|
|
84
|
+
for (const d of summary.decisions) {
|
|
85
|
+
const icon = d.type === 'error' ? chalk_1.default.red('✗') : d.type === 'tool' ? chalk_1.default.green('⚙') : d.type === 'llm' ? chalk_1.default.magenta('✦') : chalk_1.default.blue('→');
|
|
86
|
+
console.log(` ${icon} ${d.description}`);
|
|
87
|
+
}
|
|
88
|
+
console.log('');
|
|
89
|
+
}
|
|
90
|
+
// Cost breakdown
|
|
91
|
+
if (summary.cost.total > 0) {
|
|
92
|
+
console.log(chalk_1.default.bold(' Cost'));
|
|
93
|
+
console.log(` ${chalk_1.default.green('$' + summary.cost.total.toFixed(4))} total across ${summary.cost.llmCalls} LLM calls (${formatTokens(summary.cost.tokens)} tokens)`);
|
|
94
|
+
if (summary.cost.mostExpensive) {
|
|
95
|
+
console.log(chalk_1.default.gray(` Most expensive: $${summary.cost.mostExpensive.cost.toFixed(4)} — ${summary.cost.mostExpensive.description}`));
|
|
96
|
+
}
|
|
97
|
+
console.log('');
|
|
98
|
+
}
|
|
99
|
+
// Issues
|
|
100
|
+
if (summary.issues.length > 0) {
|
|
101
|
+
console.log(chalk_1.default.bold(' Issues'));
|
|
102
|
+
for (const issue of summary.issues) {
|
|
103
|
+
console.log(` ${chalk_1.default.red('!')} ${issue}`);
|
|
104
|
+
}
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
console.log(chalk_1.default.gray(' ' + '─'.repeat(60)));
|
|
108
|
+
console.log('');
|
|
109
|
+
}
|
|
110
|
+
function buildSummary(agentEvents, llmCalls, mcpCalls, errors) {
|
|
111
|
+
const decisions = [];
|
|
112
|
+
const issues = [];
|
|
113
|
+
// Extract key agent decisions
|
|
114
|
+
const crewStarts = agentEvents.filter(e => e.event === 'crew_start' || e.event === 'chain_start');
|
|
115
|
+
const crewEnds = agentEvents.filter(e => e.event === 'crew_end' || e.event === 'chain_end');
|
|
116
|
+
const toolStarts = agentEvents.filter(e => e.event === 'tool_start');
|
|
117
|
+
const toolEnds = agentEvents.filter(e => e.event === 'tool_end');
|
|
118
|
+
const toolErrors = agentEvents.filter(e => e.event === 'tool_error');
|
|
119
|
+
const agentActions = agentEvents.filter(e => e.event === 'action');
|
|
120
|
+
// Tools used
|
|
121
|
+
const toolNames = [...new Set(toolStarts.map(e => e.tool).filter(Boolean))];
|
|
122
|
+
if (toolNames.length > 0) {
|
|
123
|
+
decisions.push({ type: 'tool', description: `Used ${toolNames.length} tools: ${toolNames.join(', ')}` });
|
|
124
|
+
}
|
|
125
|
+
// Agent reasoning (from action events with thoughts)
|
|
126
|
+
for (const a of agentActions.slice(0, 3)) {
|
|
127
|
+
if (a.thought) {
|
|
128
|
+
decisions.push({ type: 'reasoning', description: `Thought: "${truncate(a.thought, 80)}"` });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Handoffs
|
|
132
|
+
const handoffs = agentEvents.filter(e => e.tool === 'handoff');
|
|
133
|
+
for (const h of handoffs) {
|
|
134
|
+
decisions.push({ type: 'handoff', description: `Handoff: ${truncate(h.toolInput || '', 80)}` });
|
|
135
|
+
}
|
|
136
|
+
// LLM calls summary
|
|
137
|
+
const models = [...new Set(llmCalls.map(c => `${c.provider}/${c.model}`))];
|
|
138
|
+
if (models.length > 0) {
|
|
139
|
+
decisions.push({ type: 'llm', description: `${llmCalls.length} LLM calls using ${models.join(', ')}` });
|
|
140
|
+
}
|
|
141
|
+
// MCP tool calls
|
|
142
|
+
const mcpTools = [...new Set(mcpCalls.filter(c => c.tool !== '__list_tools').map(c => c.tool))];
|
|
143
|
+
if (mcpTools.length > 0) {
|
|
144
|
+
decisions.push({ type: 'tool', description: `${mcpCalls.length} MCP tool calls: ${mcpTools.join(', ')}` });
|
|
145
|
+
}
|
|
146
|
+
// Errors
|
|
147
|
+
for (const te of toolErrors.slice(0, 2)) {
|
|
148
|
+
decisions.push({ type: 'error', description: `Tool "${te.tool}" failed: ${truncate(te.error || '', 60)}` });
|
|
149
|
+
}
|
|
150
|
+
for (const err of errors.slice(0, 2)) {
|
|
151
|
+
issues.push(`${err.type || 'Error'}: ${truncate(err.message || '', 80)}`);
|
|
152
|
+
}
|
|
153
|
+
// Detect patterns
|
|
154
|
+
const toolNameList = toolStarts.map(e => e.tool || '');
|
|
155
|
+
for (let i = 0; i < toolNameList.length - 2; i++) {
|
|
156
|
+
if (toolNameList[i] === toolNameList[i + 1] && toolNameList[i] === toolNameList[i + 2]) {
|
|
157
|
+
issues.push(`Tool "${toolNameList[i]}" called 3+ times — possible retry loop`);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const llmErrors = llmCalls.filter(c => c.error);
|
|
162
|
+
if (llmErrors.length > 0) {
|
|
163
|
+
issues.push(`${llmErrors.length}/${llmCalls.length} LLM calls failed`);
|
|
164
|
+
}
|
|
165
|
+
// Cost
|
|
166
|
+
const totalCost = llmCalls.reduce((s, c) => s + (c.estimatedCostUsd || 0), 0);
|
|
167
|
+
const totalTokens = llmCalls.reduce((s, c) => s + (c.totalTokens || 0), 0);
|
|
168
|
+
const mostExpensiveCall = llmCalls.length > 0
|
|
169
|
+
? llmCalls.reduce((max, c) => (c.estimatedCostUsd || 0) > (max.estimatedCostUsd || 0) ? c : max, llmCalls[0])
|
|
170
|
+
: null;
|
|
171
|
+
// Duration
|
|
172
|
+
const allTimestamps = [
|
|
173
|
+
...agentEvents.map(e => e.timestamp),
|
|
174
|
+
...llmCalls.map(c => c.timestamp),
|
|
175
|
+
...mcpCalls.map(c => c.timestamp),
|
|
176
|
+
].filter(Boolean).sort();
|
|
177
|
+
const duration = allTimestamps.length >= 2 ? allTimestamps[allTimestamps.length - 1] - allTimestamps[0] : 0;
|
|
178
|
+
// Build overview
|
|
179
|
+
const parts = [];
|
|
180
|
+
if (crewStarts.length > 0) {
|
|
181
|
+
const frameworks = [...new Set(agentEvents.map(e => e.framework).filter(Boolean))];
|
|
182
|
+
parts.push(`${crewStarts.length} agent run(s)${frameworks.length > 0 ? ` (${frameworks.join(', ')})` : ''}`);
|
|
183
|
+
}
|
|
184
|
+
if (llmCalls.length > 0)
|
|
185
|
+
parts.push(`${llmCalls.length} LLM calls ($${totalCost.toFixed(4)})`);
|
|
186
|
+
if (toolNames.length > 0)
|
|
187
|
+
parts.push(`${toolStarts.length} tool calls`);
|
|
188
|
+
if (mcpTools.length > 0)
|
|
189
|
+
parts.push(`${mcpCalls.length} MCP calls`);
|
|
190
|
+
if (toolErrors.length > 0)
|
|
191
|
+
parts.push(`${toolErrors.length} tool errors`);
|
|
192
|
+
if (duration > 0)
|
|
193
|
+
parts.push(formatDuration(duration));
|
|
194
|
+
const completed = crewEnds.length > 0 ? 'completed' : crewStarts.length > 0 ? 'started' : '';
|
|
195
|
+
const overview = parts.length > 0
|
|
196
|
+
? `${completed ? completed.charAt(0).toUpperCase() + completed.slice(1) + ': ' : ''}${parts.join(', ')}`
|
|
197
|
+
: 'No significant events captured';
|
|
198
|
+
return {
|
|
199
|
+
overview, decisions, issues, duration,
|
|
200
|
+
cost: {
|
|
201
|
+
total: totalCost, llmCalls: llmCalls.length, tokens: totalTokens,
|
|
202
|
+
mostExpensive: mostExpensiveCall && mostExpensiveCall.estimatedCostUsd > 0
|
|
203
|
+
? { cost: mostExpensiveCall.estimatedCostUsd, description: `${mostExpensiveCall.model}: "${truncate(mostExpensiveCall.inputPreview || '', 50)}"` }
|
|
204
|
+
: null,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function truncate(s, len) {
|
|
209
|
+
return s.length > len ? s.substring(0, len) + '...' : s;
|
|
210
|
+
}
|
|
211
|
+
function formatTokens(n) {
|
|
212
|
+
if (n >= 1_000_000)
|
|
213
|
+
return (n / 1_000_000).toFixed(1) + 'M';
|
|
214
|
+
if (n >= 1_000)
|
|
215
|
+
return (n / 1_000).toFixed(1) + 'K';
|
|
216
|
+
return String(n);
|
|
217
|
+
}
|
|
218
|
+
function formatDuration(ms) {
|
|
219
|
+
if (ms >= 60_000)
|
|
220
|
+
return (ms / 60_000).toFixed(1) + 'min';
|
|
221
|
+
if (ms >= 1_000)
|
|
222
|
+
return (ms / 1_000).toFixed(1) + 's';
|
|
223
|
+
return ms + 'ms';
|
|
224
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -920,6 +920,15 @@ program
|
|
|
920
920
|
const { whyCommand } = await Promise.resolve().then(() => __importStar(require("./commands/why")));
|
|
921
921
|
whyCommand(query, opts);
|
|
922
922
|
});
|
|
923
|
+
// trickle summarize
|
|
924
|
+
program
|
|
925
|
+
.command("summarize")
|
|
926
|
+
.description("Compress agent traces into key decisions — what happened, why, at what cost")
|
|
927
|
+
.option("--json", "Output structured JSON")
|
|
928
|
+
.action(async (opts) => {
|
|
929
|
+
const { summarizeCommand } = await Promise.resolve().then(() => __importStar(require("./commands/summarize")));
|
|
930
|
+
summarizeCommand(opts);
|
|
931
|
+
});
|
|
923
932
|
// trickle cleanup
|
|
924
933
|
program
|
|
925
934
|
.command("cleanup")
|
package/package.json
CHANGED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trickle summarize — Compress verbose agent traces into key decision points.
|
|
3
|
+
*
|
|
4
|
+
* Reads agents.jsonl, llm.jsonl, mcp.jsonl and produces a concise
|
|
5
|
+
* narrative of what the agent did, why, and at what cost.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
|
|
12
|
+
function readJsonl(fp: string): any[] {
|
|
13
|
+
if (!fs.existsSync(fp)) return [];
|
|
14
|
+
return fs.readFileSync(fp, 'utf-8').split('\n').filter(Boolean)
|
|
15
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function summarizeCommand(opts: { json?: boolean }): void {
|
|
19
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
20
|
+
const agentEvents = readJsonl(path.join(dir, 'agents.jsonl'));
|
|
21
|
+
const llmCalls = readJsonl(path.join(dir, 'llm.jsonl'));
|
|
22
|
+
const mcpCalls = readJsonl(path.join(dir, 'mcp.jsonl'));
|
|
23
|
+
const errors = readJsonl(path.join(dir, 'errors.jsonl'));
|
|
24
|
+
|
|
25
|
+
if (agentEvents.length === 0 && llmCalls.length === 0 && mcpCalls.length === 0) {
|
|
26
|
+
console.log(chalk.yellow(' No agent, LLM, or MCP data to summarize.'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const summary = buildSummary(agentEvents, llmCalls, mcpCalls, errors);
|
|
31
|
+
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(chalk.bold(' trickle summarize'));
|
|
39
|
+
console.log(chalk.gray(' ' + '─'.repeat(60)));
|
|
40
|
+
|
|
41
|
+
// One-line overview
|
|
42
|
+
console.log(` ${summary.overview}`);
|
|
43
|
+
console.log('');
|
|
44
|
+
|
|
45
|
+
// Key decisions
|
|
46
|
+
if (summary.decisions.length > 0) {
|
|
47
|
+
console.log(chalk.bold(' Key Decisions'));
|
|
48
|
+
for (const d of summary.decisions) {
|
|
49
|
+
const icon = d.type === 'error' ? chalk.red('✗') : d.type === 'tool' ? chalk.green('⚙') : d.type === 'llm' ? chalk.magenta('✦') : chalk.blue('→');
|
|
50
|
+
console.log(` ${icon} ${d.description}`);
|
|
51
|
+
}
|
|
52
|
+
console.log('');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Cost breakdown
|
|
56
|
+
if (summary.cost.total > 0) {
|
|
57
|
+
console.log(chalk.bold(' Cost'));
|
|
58
|
+
console.log(` ${chalk.green('$' + summary.cost.total.toFixed(4))} total across ${summary.cost.llmCalls} LLM calls (${formatTokens(summary.cost.tokens)} tokens)`);
|
|
59
|
+
if (summary.cost.mostExpensive) {
|
|
60
|
+
console.log(chalk.gray(` Most expensive: $${summary.cost.mostExpensive.cost.toFixed(4)} — ${summary.cost.mostExpensive.description}`));
|
|
61
|
+
}
|
|
62
|
+
console.log('');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Issues
|
|
66
|
+
if (summary.issues.length > 0) {
|
|
67
|
+
console.log(chalk.bold(' Issues'));
|
|
68
|
+
for (const issue of summary.issues) {
|
|
69
|
+
console.log(` ${chalk.red('!')} ${issue}`);
|
|
70
|
+
}
|
|
71
|
+
console.log('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(chalk.gray(' ' + '─'.repeat(60)));
|
|
75
|
+
console.log('');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface TraceSummary {
|
|
79
|
+
overview: string;
|
|
80
|
+
decisions: Array<{ type: string; description: string }>;
|
|
81
|
+
cost: { total: number; llmCalls: number; tokens: number; mostExpensive: { cost: number; description: string } | null };
|
|
82
|
+
issues: string[];
|
|
83
|
+
duration: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildSummary(agentEvents: any[], llmCalls: any[], mcpCalls: any[], errors: any[]): TraceSummary {
|
|
87
|
+
const decisions: TraceSummary['decisions'] = [];
|
|
88
|
+
const issues: string[] = [];
|
|
89
|
+
|
|
90
|
+
// Extract key agent decisions
|
|
91
|
+
const crewStarts = agentEvents.filter(e => e.event === 'crew_start' || e.event === 'chain_start');
|
|
92
|
+
const crewEnds = agentEvents.filter(e => e.event === 'crew_end' || e.event === 'chain_end');
|
|
93
|
+
const toolStarts = agentEvents.filter(e => e.event === 'tool_start');
|
|
94
|
+
const toolEnds = agentEvents.filter(e => e.event === 'tool_end');
|
|
95
|
+
const toolErrors = agentEvents.filter(e => e.event === 'tool_error');
|
|
96
|
+
const agentActions = agentEvents.filter(e => e.event === 'action');
|
|
97
|
+
|
|
98
|
+
// Tools used
|
|
99
|
+
const toolNames = [...new Set(toolStarts.map(e => e.tool).filter(Boolean))];
|
|
100
|
+
if (toolNames.length > 0) {
|
|
101
|
+
decisions.push({ type: 'tool', description: `Used ${toolNames.length} tools: ${toolNames.join(', ')}` });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Agent reasoning (from action events with thoughts)
|
|
105
|
+
for (const a of agentActions.slice(0, 3)) {
|
|
106
|
+
if (a.thought) {
|
|
107
|
+
decisions.push({ type: 'reasoning', description: `Thought: "${truncate(a.thought, 80)}"` });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handoffs
|
|
112
|
+
const handoffs = agentEvents.filter(e => e.tool === 'handoff');
|
|
113
|
+
for (const h of handoffs) {
|
|
114
|
+
decisions.push({ type: 'handoff', description: `Handoff: ${truncate(h.toolInput || '', 80)}` });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// LLM calls summary
|
|
118
|
+
const models = [...new Set(llmCalls.map(c => `${c.provider}/${c.model}`))];
|
|
119
|
+
if (models.length > 0) {
|
|
120
|
+
decisions.push({ type: 'llm', description: `${llmCalls.length} LLM calls using ${models.join(', ')}` });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// MCP tool calls
|
|
124
|
+
const mcpTools = [...new Set(mcpCalls.filter(c => c.tool !== '__list_tools').map(c => c.tool))];
|
|
125
|
+
if (mcpTools.length > 0) {
|
|
126
|
+
decisions.push({ type: 'tool', description: `${mcpCalls.length} MCP tool calls: ${mcpTools.join(', ')}` });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Errors
|
|
130
|
+
for (const te of toolErrors.slice(0, 2)) {
|
|
131
|
+
decisions.push({ type: 'error', description: `Tool "${te.tool}" failed: ${truncate(te.error || '', 60)}` });
|
|
132
|
+
}
|
|
133
|
+
for (const err of errors.slice(0, 2)) {
|
|
134
|
+
issues.push(`${err.type || 'Error'}: ${truncate(err.message || '', 80)}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Detect patterns
|
|
138
|
+
const toolNameList = toolStarts.map(e => e.tool || '');
|
|
139
|
+
for (let i = 0; i < toolNameList.length - 2; i++) {
|
|
140
|
+
if (toolNameList[i] === toolNameList[i + 1] && toolNameList[i] === toolNameList[i + 2]) {
|
|
141
|
+
issues.push(`Tool "${toolNameList[i]}" called 3+ times — possible retry loop`);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const llmErrors = llmCalls.filter(c => c.error);
|
|
147
|
+
if (llmErrors.length > 0) {
|
|
148
|
+
issues.push(`${llmErrors.length}/${llmCalls.length} LLM calls failed`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Cost
|
|
152
|
+
const totalCost = llmCalls.reduce((s: number, c: any) => s + (c.estimatedCostUsd || 0), 0);
|
|
153
|
+
const totalTokens = llmCalls.reduce((s: number, c: any) => s + (c.totalTokens || 0), 0);
|
|
154
|
+
const mostExpensiveCall = llmCalls.length > 0
|
|
155
|
+
? llmCalls.reduce((max: any, c: any) => (c.estimatedCostUsd || 0) > (max.estimatedCostUsd || 0) ? c : max, llmCalls[0])
|
|
156
|
+
: null;
|
|
157
|
+
|
|
158
|
+
// Duration
|
|
159
|
+
const allTimestamps = [
|
|
160
|
+
...agentEvents.map(e => e.timestamp),
|
|
161
|
+
...llmCalls.map(c => c.timestamp),
|
|
162
|
+
...mcpCalls.map(c => c.timestamp),
|
|
163
|
+
].filter(Boolean).sort();
|
|
164
|
+
const duration = allTimestamps.length >= 2 ? allTimestamps[allTimestamps.length - 1] - allTimestamps[0] : 0;
|
|
165
|
+
|
|
166
|
+
// Build overview
|
|
167
|
+
const parts: string[] = [];
|
|
168
|
+
if (crewStarts.length > 0) {
|
|
169
|
+
const frameworks = [...new Set(agentEvents.map(e => e.framework).filter(Boolean))];
|
|
170
|
+
parts.push(`${crewStarts.length} agent run(s)${frameworks.length > 0 ? ` (${frameworks.join(', ')})` : ''}`);
|
|
171
|
+
}
|
|
172
|
+
if (llmCalls.length > 0) parts.push(`${llmCalls.length} LLM calls ($${totalCost.toFixed(4)})`);
|
|
173
|
+
if (toolNames.length > 0) parts.push(`${toolStarts.length} tool calls`);
|
|
174
|
+
if (mcpTools.length > 0) parts.push(`${mcpCalls.length} MCP calls`);
|
|
175
|
+
if (toolErrors.length > 0) parts.push(`${toolErrors.length} tool errors`);
|
|
176
|
+
if (duration > 0) parts.push(formatDuration(duration));
|
|
177
|
+
|
|
178
|
+
const completed = crewEnds.length > 0 ? 'completed' : crewStarts.length > 0 ? 'started' : '';
|
|
179
|
+
const overview = parts.length > 0
|
|
180
|
+
? `${completed ? completed.charAt(0).toUpperCase() + completed.slice(1) + ': ' : ''}${parts.join(', ')}`
|
|
181
|
+
: 'No significant events captured';
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
overview, decisions, issues, duration,
|
|
185
|
+
cost: {
|
|
186
|
+
total: totalCost, llmCalls: llmCalls.length, tokens: totalTokens,
|
|
187
|
+
mostExpensive: mostExpensiveCall && mostExpensiveCall.estimatedCostUsd > 0
|
|
188
|
+
? { cost: mostExpensiveCall.estimatedCostUsd, description: `${mostExpensiveCall.model}: "${truncate(mostExpensiveCall.inputPreview || '', 50)}"` }
|
|
189
|
+
: null,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function truncate(s: string, len: number): string {
|
|
195
|
+
return s.length > len ? s.substring(0, len) + '...' : s;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function formatTokens(n: number): string {
|
|
199
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M';
|
|
200
|
+
if (n >= 1_000) return (n / 1_000).toFixed(1) + 'K';
|
|
201
|
+
return String(n);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function formatDuration(ms: number): string {
|
|
205
|
+
if (ms >= 60_000) return (ms / 60_000).toFixed(1) + 'min';
|
|
206
|
+
if (ms >= 1_000) return (ms / 1_000).toFixed(1) + 's';
|
|
207
|
+
return ms + 'ms';
|
|
208
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -953,6 +953,16 @@ program
|
|
|
953
953
|
whyCommand(query, opts);
|
|
954
954
|
});
|
|
955
955
|
|
|
956
|
+
// trickle summarize
|
|
957
|
+
program
|
|
958
|
+
.command("summarize")
|
|
959
|
+
.description("Compress agent traces into key decisions — what happened, why, at what cost")
|
|
960
|
+
.option("--json", "Output structured JSON")
|
|
961
|
+
.action(async (opts) => {
|
|
962
|
+
const { summarizeCommand } = await import("./commands/summarize");
|
|
963
|
+
summarizeCommand(opts);
|
|
964
|
+
});
|
|
965
|
+
|
|
956
966
|
// trickle cleanup
|
|
957
967
|
program
|
|
958
968
|
.command("cleanup")
|