@rejacky/opencode-insights 0.1.0
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/DEVELOPMENT.md +97 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/capture-gQauLsdn.d.ts +48 -0
- package/dist/chunk-G7E4J6OW.js +257 -0
- package/dist/chunk-G7E4J6OW.js.map +1 -0
- package/dist/chunk-WF6BN2SB.js +462 -0
- package/dist/chunk-WF6BN2SB.js.map +1 -0
- package/dist/cli.d.ts +89 -0
- package/dist/cli.js +1360 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/tui.d.ts +11 -0
- package/dist/tui.js +234 -0
- package/dist/tui.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// src/metrics.ts
|
|
2
|
+
var STREAM_WINDOW_MS = 5e3;
|
|
3
|
+
var LIVE_STALE_MS = 1500;
|
|
4
|
+
var SINGLE_SAMPLE_MS = 1e3;
|
|
5
|
+
function createMetricsState() {
|
|
6
|
+
return {
|
|
7
|
+
streamSamplesBySession: {},
|
|
8
|
+
messageTimingByID: {},
|
|
9
|
+
sessionAverageByID: {}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function estimateStreamTokens(delta) {
|
|
13
|
+
return Math.max(1, Math.ceil(Buffer.byteLength(delta, "utf8") / 5));
|
|
14
|
+
}
|
|
15
|
+
function recordAssistantMessage(state, input) {
|
|
16
|
+
if (typeof input.completedAt !== "number") {
|
|
17
|
+
const existing = state.messageTimingByID[input.messageID];
|
|
18
|
+
state.messageTimingByID[input.messageID] = {
|
|
19
|
+
sessionID: input.sessionID,
|
|
20
|
+
requestStartAt: input.createdAt,
|
|
21
|
+
firstResponseAt: existing?.firstResponseAt,
|
|
22
|
+
firstTokenAt: existing?.firstTokenAt,
|
|
23
|
+
lastTokenAt: existing?.lastTokenAt,
|
|
24
|
+
lastToolCallAt: existing?.lastToolCallAt
|
|
25
|
+
};
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const timing = state.messageTimingByID[input.messageID];
|
|
29
|
+
if (timing?.sessionID === input.sessionID && typeof timing.firstResponseAt === "number") {
|
|
30
|
+
const totalTokens = (input.outputTokens ?? 0) + (input.reasoningTokens ?? 0);
|
|
31
|
+
const endAt = input.finish === "tool-calls" ? timing.lastToolCallAt : input.completedAt;
|
|
32
|
+
const durationMs = typeof endAt === "number" ? Math.max(endAt - timing.firstResponseAt, 1) : void 0;
|
|
33
|
+
const ttftMs = Math.max(timing.firstResponseAt - timing.requestStartAt, 0);
|
|
34
|
+
if (totalTokens > 0 && durationMs) {
|
|
35
|
+
const totals = state.sessionAverageByID[input.sessionID] ?? {
|
|
36
|
+
totalTokens: 0,
|
|
37
|
+
totalDurationMs: 0,
|
|
38
|
+
totalTtftMs: 0,
|
|
39
|
+
messageCount: 0
|
|
40
|
+
};
|
|
41
|
+
state.sessionAverageByID[input.sessionID] = {
|
|
42
|
+
totalTokens: totals.totalTokens + totalTokens,
|
|
43
|
+
totalDurationMs: totals.totalDurationMs + durationMs,
|
|
44
|
+
totalTtftMs: totals.totalTtftMs + ttftMs,
|
|
45
|
+
messageCount: totals.messageCount + 1
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
delete state.messageTimingByID[input.messageID];
|
|
50
|
+
pruneSamples(state, input.completedAt);
|
|
51
|
+
}
|
|
52
|
+
function recordAssistantDelta(state, input) {
|
|
53
|
+
const sample = {
|
|
54
|
+
at: input.at,
|
|
55
|
+
tokens: estimateStreamTokens(input.delta)
|
|
56
|
+
};
|
|
57
|
+
state.streamSamplesBySession[input.sessionID] = [
|
|
58
|
+
...(state.streamSamplesBySession[input.sessionID] ?? []).filter((item) => input.at - item.at <= STREAM_WINDOW_MS),
|
|
59
|
+
sample
|
|
60
|
+
];
|
|
61
|
+
const timing = state.messageTimingByID[input.messageID];
|
|
62
|
+
if (timing) {
|
|
63
|
+
state.messageTimingByID[input.messageID] = timing.firstTokenAt ? { ...timing, lastTokenAt: input.at } : {
|
|
64
|
+
...timing,
|
|
65
|
+
firstResponseAt: timing.firstResponseAt ?? input.at,
|
|
66
|
+
firstTokenAt: input.at,
|
|
67
|
+
lastTokenAt: input.at
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function recordToolActivity(state, sessionID, messageID, at = Date.now()) {
|
|
72
|
+
if (state.streamSamplesBySession[sessionID]?.length) {
|
|
73
|
+
delete state.streamSamplesBySession[sessionID];
|
|
74
|
+
}
|
|
75
|
+
const timing = state.messageTimingByID[messageID];
|
|
76
|
+
if (timing) {
|
|
77
|
+
state.messageTimingByID[messageID] = {
|
|
78
|
+
...timing,
|
|
79
|
+
lastToolCallAt: at,
|
|
80
|
+
firstResponseAt: timing.firstResponseAt ?? at
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function renderMetricsText(state, sessionID, options = {}) {
|
|
85
|
+
const live = liveTps(state, sessionID, options) ?? "-";
|
|
86
|
+
const avg = sessionAverage(state, sessionID) ?? "-";
|
|
87
|
+
const ttft = sessionTtft(state, sessionID) ?? "-";
|
|
88
|
+
return `TPS ${live} | AVG ${avg} | TTFT ${ttft}`;
|
|
89
|
+
}
|
|
90
|
+
function pruneSamples(state, now = Date.now()) {
|
|
91
|
+
for (const [sessionID, samples] of Object.entries(state.streamSamplesBySession)) {
|
|
92
|
+
const next = samples.filter((sample) => now - sample.at <= STREAM_WINDOW_MS);
|
|
93
|
+
if (next.length > 0) state.streamSamplesBySession[sessionID] = next;
|
|
94
|
+
else delete state.streamSamplesBySession[sessionID];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function sessionAverage(state, sessionID) {
|
|
98
|
+
const totals = state.sessionAverageByID[sessionID];
|
|
99
|
+
if (!totals || totals.totalTokens <= 0 || totals.totalDurationMs <= 0) return void 0;
|
|
100
|
+
return formatRate(totals.totalTokens / (totals.totalDurationMs / 1e3), "AVG");
|
|
101
|
+
}
|
|
102
|
+
function sessionTtft(state, sessionID) {
|
|
103
|
+
const totals = state.sessionAverageByID[sessionID];
|
|
104
|
+
if (!totals || totals.messageCount <= 0 || totals.totalTtftMs < 0) return void 0;
|
|
105
|
+
return formatTtft(totals.totalTtftMs / totals.messageCount / 1e3);
|
|
106
|
+
}
|
|
107
|
+
function liveTps(state, sessionID, options = {}) {
|
|
108
|
+
if (options.idle) return void 0;
|
|
109
|
+
const now = options.now ?? Date.now();
|
|
110
|
+
const samples = state.streamSamplesBySession[sessionID] ?? [];
|
|
111
|
+
const relevant = samples.filter((sample) => now - sample.at <= STREAM_WINDOW_MS);
|
|
112
|
+
if (relevant.length === 0) return void 0;
|
|
113
|
+
const lastSample = relevant.at(-1);
|
|
114
|
+
if (!lastSample || now - lastSample.at > LIVE_STALE_MS) return void 0;
|
|
115
|
+
const total = relevant.reduce((sum, sample) => sum + sample.tokens, 0);
|
|
116
|
+
const durationSeconds = activeDurationMs(relevant, now) / 1e3;
|
|
117
|
+
if (durationSeconds <= 0) return void 0;
|
|
118
|
+
return formatRate(total / durationSeconds, "TPS");
|
|
119
|
+
}
|
|
120
|
+
function activeDurationMs(samples, tailAt) {
|
|
121
|
+
if (samples.length === 0) return 0;
|
|
122
|
+
if (samples.length === 1) {
|
|
123
|
+
const tailDuration = tailAt ? Math.max(0, tailAt - samples[0].at) : SINGLE_SAMPLE_MS;
|
|
124
|
+
return Math.min(Math.max(tailDuration, 250), SINGLE_SAMPLE_MS);
|
|
125
|
+
}
|
|
126
|
+
let duration = 0;
|
|
127
|
+
for (let index = 1; index < samples.length; index++) {
|
|
128
|
+
duration += Math.max(0, samples[index].at - samples[index - 1].at);
|
|
129
|
+
}
|
|
130
|
+
if (tailAt) {
|
|
131
|
+
duration += Math.max(0, tailAt - samples.at(-1).at);
|
|
132
|
+
}
|
|
133
|
+
return Math.max(duration, SINGLE_SAMPLE_MS);
|
|
134
|
+
}
|
|
135
|
+
function formatRate(value, label) {
|
|
136
|
+
if (!Number.isFinite(value) || value <= 0) return void 0;
|
|
137
|
+
const suffix = label === "TPS" ? " TPS" : "";
|
|
138
|
+
if (value >= 100) return `${Math.round(value)}${suffix}`;
|
|
139
|
+
if (value >= 10) return `${value.toFixed(1)}${suffix}`;
|
|
140
|
+
return `${value.toFixed(2)}${suffix}`;
|
|
141
|
+
}
|
|
142
|
+
function formatTtft(value) {
|
|
143
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
144
|
+
return `${value.toFixed(1)}s`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/subagents.ts
|
|
148
|
+
function createSubagentState() {
|
|
149
|
+
return { children: {}, totalExecuted: 0 };
|
|
150
|
+
}
|
|
151
|
+
function applySubagentEvent(state, event) {
|
|
152
|
+
const created = extractTaskToolSubagent(event) ?? extractSubagent(event) ?? updateExistingSubagent(state, event);
|
|
153
|
+
if (!created) return false;
|
|
154
|
+
const previous = state.children[created.id];
|
|
155
|
+
const preserveTerminal = !!previous && isTerminalStatus(previous.status) && created.status === "running";
|
|
156
|
+
const status = preserveTerminal ? previous.status : created.status;
|
|
157
|
+
const title = preserveTerminal ? previous.title : created.title;
|
|
158
|
+
const startedAt = preserveTerminal ? previous.startedAt : created.startedAt;
|
|
159
|
+
const endedAt = preserveTerminal ? previous.endedAt : created.endedAt;
|
|
160
|
+
const next = {
|
|
161
|
+
...previous,
|
|
162
|
+
...created,
|
|
163
|
+
title,
|
|
164
|
+
status,
|
|
165
|
+
startedAt,
|
|
166
|
+
updatedAt: created.updatedAt,
|
|
167
|
+
endedAt,
|
|
168
|
+
elapsedMs: elapsedMs(startedAt, endedAt ?? created.updatedAt),
|
|
169
|
+
tokens: created.tokens ?? previous?.tokens
|
|
170
|
+
};
|
|
171
|
+
if (!previous) state.totalExecuted += 1;
|
|
172
|
+
state.children[created.id] = next;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
function isTerminalStatus(status) {
|
|
176
|
+
return status === "done" || status === "error";
|
|
177
|
+
}
|
|
178
|
+
function renderSubagentStatus(state, options = {}) {
|
|
179
|
+
const children = getSubagentItems(state);
|
|
180
|
+
const running = children.filter((child) => child.status === "running").length;
|
|
181
|
+
const done = children.filter((child) => child.status === "done").length;
|
|
182
|
+
const error = children.filter((child) => child.status === "error").length;
|
|
183
|
+
const aggregate = `${running} running \xB7 ${done} done \xB7 ${error} failed \xB7 ${state.totalExecuted} total`;
|
|
184
|
+
if (children.length === 0) return aggregate;
|
|
185
|
+
const details = children.map((child) => {
|
|
186
|
+
const nowIso = new Date(options.now ?? Date.now()).toISOString();
|
|
187
|
+
const duration = child.status === "running" || child.status === "error" && !child.endedAt ? formatDuration(elapsedMs(child.startedAt, child.endedAt ?? nowIso)) : formatDuration(child.elapsedMs ?? elapsedMs(child.startedAt, child.endedAt ?? nowIso));
|
|
188
|
+
const context = formatContext(child);
|
|
189
|
+
return [child.title, duration, context].filter(Boolean).join(" ");
|
|
190
|
+
}).join(" \xB7 ");
|
|
191
|
+
return `${aggregate} \xB7 ${details}`;
|
|
192
|
+
}
|
|
193
|
+
function getSubagentItems(state, parentID) {
|
|
194
|
+
return Object.values(state.children).filter((child) => !parentID || child.parentID === parentID).sort((a, b) => {
|
|
195
|
+
const statusRank = statusSortRank(a.status) - statusSortRank(b.status);
|
|
196
|
+
if (statusRank !== 0) return statusRank;
|
|
197
|
+
return b.startedAt.localeCompare(a.startedAt);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function pruneStaleSubagents(state, options = {}) {
|
|
201
|
+
const now = options.now ?? Date.now();
|
|
202
|
+
const staleMs = options.staleMs ?? 18e4;
|
|
203
|
+
let changed = false;
|
|
204
|
+
for (const [id, child] of Object.entries(state.children)) {
|
|
205
|
+
if (child.status === "running") continue;
|
|
206
|
+
const lastActivity = Date.parse(child.endedAt ?? child.updatedAt);
|
|
207
|
+
if (!Number.isFinite(lastActivity)) continue;
|
|
208
|
+
if (now - lastActivity <= staleMs) continue;
|
|
209
|
+
delete state.children[id];
|
|
210
|
+
changed = true;
|
|
211
|
+
}
|
|
212
|
+
return changed;
|
|
213
|
+
}
|
|
214
|
+
function getSubagentSidebarModel(state, parentID, options = {}) {
|
|
215
|
+
pruneStaleSubagents(state, options);
|
|
216
|
+
const children = getSubagentItems(state, parentID);
|
|
217
|
+
if (children.length === 0) return void 0;
|
|
218
|
+
const running = children.filter((child) => child.status === "running").length;
|
|
219
|
+
const done = children.filter((child) => child.status === "done").length;
|
|
220
|
+
const error = children.filter((child) => child.status === "error").length;
|
|
221
|
+
return {
|
|
222
|
+
title: "Subagents",
|
|
223
|
+
summary: `${running} running \xB7 ${done} done \xB7 ${error} error`,
|
|
224
|
+
rows: children.map((child) => ({
|
|
225
|
+
id: child.id,
|
|
226
|
+
title: formatSubagentTitle(child.title),
|
|
227
|
+
subtitle: [formatSubagentDuration(child, options.now), formatUsage(child)].filter(Boolean).join(" \xB7 "),
|
|
228
|
+
status: child.status
|
|
229
|
+
}))
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function renderSubagentSidebar(state, parentID, options = {}) {
|
|
233
|
+
const model = getSubagentSidebarModel(state, parentID, options);
|
|
234
|
+
if (!model) return "";
|
|
235
|
+
return [
|
|
236
|
+
model.title,
|
|
237
|
+
model.summary,
|
|
238
|
+
...model.rows.flatMap((row) => [row.title, row.subtitle].filter(Boolean))
|
|
239
|
+
].join("\n");
|
|
240
|
+
}
|
|
241
|
+
function renderSubagentFooter(state, parentID, options = {}) {
|
|
242
|
+
const model = getSubagentSidebarModel(state, parentID, options);
|
|
243
|
+
if (!model) return "";
|
|
244
|
+
return `Subagents ${model.summary}`;
|
|
245
|
+
}
|
|
246
|
+
function extractTaskToolSubagent(event) {
|
|
247
|
+
if (!isRecord(event)) return void 0;
|
|
248
|
+
const evt = event;
|
|
249
|
+
if (evt.type !== "message.part.updated") return void 0;
|
|
250
|
+
const part = isRecord(evt.properties?.part) ? evt.properties.part : void 0;
|
|
251
|
+
if (!part || part.type !== "tool" || part.tool !== "task") return void 0;
|
|
252
|
+
const state = isRecord(part.state) ? part.state : void 0;
|
|
253
|
+
if (!state) return void 0;
|
|
254
|
+
const metadata = isRecord(state.metadata) ? state.metadata : isRecord(part.metadata) ? part.metadata : void 0;
|
|
255
|
+
const id = asString(metadata?.sessionId) ?? sessionIdFromTaskOutput(asString(state.output));
|
|
256
|
+
const parentID = asString(metadata?.parentSessionId) ?? asString(evt.properties?.sessionID);
|
|
257
|
+
if (!id || !parentID || id === parentID) return void 0;
|
|
258
|
+
const status = taskToolStatus(state);
|
|
259
|
+
const startedMs = numberFromPath(state.time, "start") ?? numberFromPath(state.time, "created") ?? Date.now();
|
|
260
|
+
const endedMs = numberFromPath(state.time, "end");
|
|
261
|
+
const updatedMs = endedMs ?? numberFromPath(state.time, "updated") ?? startedMs;
|
|
262
|
+
const input = isRecord(state.input) ? state.input : void 0;
|
|
263
|
+
const agent = asString(input?.subagent_type);
|
|
264
|
+
const taskTitle = asString(state.title) ?? asString(input?.description) ?? "task";
|
|
265
|
+
return {
|
|
266
|
+
id,
|
|
267
|
+
parentID,
|
|
268
|
+
title: agent ? `${agentTitle(agent)}: ${taskTitle}` : taskTitle,
|
|
269
|
+
status,
|
|
270
|
+
startedAt: new Date(startedMs).toISOString(),
|
|
271
|
+
updatedAt: new Date(updatedMs).toISOString(),
|
|
272
|
+
endedAt: status === "running" || typeof endedMs !== "number" ? void 0 : new Date(endedMs).toISOString(),
|
|
273
|
+
elapsedMs: Math.max(0, updatedMs - startedMs),
|
|
274
|
+
tokens: extractTokens(state.tokens)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function taskToolStatus(state) {
|
|
278
|
+
const status = asString(state.status);
|
|
279
|
+
if (status === "completed") return "done";
|
|
280
|
+
if (status === "error") return "error";
|
|
281
|
+
return "running";
|
|
282
|
+
}
|
|
283
|
+
function sessionIdFromTaskOutput(output) {
|
|
284
|
+
return output?.match(/<task\s+id="([^"]+)"/)?.[1];
|
|
285
|
+
}
|
|
286
|
+
function agentTitle(agent) {
|
|
287
|
+
return agent.split(/[-_\s]+/).filter(Boolean).map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
288
|
+
}
|
|
289
|
+
function extractSubagent(event) {
|
|
290
|
+
if (!isRecord(event)) return void 0;
|
|
291
|
+
const evt = event;
|
|
292
|
+
if (evt.type !== "session.created" && evt.type !== "session.updated") return void 0;
|
|
293
|
+
const info = isRecord(evt.properties?.info) ? evt.properties.info : void 0;
|
|
294
|
+
if (!info) return void 0;
|
|
295
|
+
const parentID = asString(info.parentID);
|
|
296
|
+
const id = asString(info.id);
|
|
297
|
+
if (!parentID || !id || id === parentID) return void 0;
|
|
298
|
+
const startedMs = numberFromPath(info.time, "created") ?? numberFromPath(info.time, "started") ?? Date.now();
|
|
299
|
+
const completedMs = numberFromPath(info.time, "completed");
|
|
300
|
+
const explicitUpdatedMs = numberFromPath(info.time, "updated");
|
|
301
|
+
const updatedMs = completedMs ?? explicitUpdatedMs ?? startedMs;
|
|
302
|
+
const infoStatus = asString(info.status);
|
|
303
|
+
const hasError = info.error !== void 0 || infoStatus === "error";
|
|
304
|
+
const status = hasError ? "error" : typeof completedMs === "number" || infoStatus === "idle" ? "done" : "running";
|
|
305
|
+
const terminalMs = typeof completedMs === "number" || infoStatus === "idle" ? updatedMs : status === "error" && typeof explicitUpdatedMs === "number" ? explicitUpdatedMs : void 0;
|
|
306
|
+
const endedAt = typeof terminalMs === "number" ? new Date(terminalMs).toISOString() : void 0;
|
|
307
|
+
return {
|
|
308
|
+
id,
|
|
309
|
+
parentID,
|
|
310
|
+
title: asString(info.title) ?? asString(info.name) ?? "subagent",
|
|
311
|
+
status,
|
|
312
|
+
startedAt: new Date(startedMs).toISOString(),
|
|
313
|
+
updatedAt: new Date(updatedMs).toISOString(),
|
|
314
|
+
endedAt,
|
|
315
|
+
elapsedMs: Math.max(0, updatedMs - startedMs),
|
|
316
|
+
tokens: extractTokens(info.tokens)
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function updateExistingSubagent(state, event) {
|
|
320
|
+
if (!isRecord(event)) return void 0;
|
|
321
|
+
const evt = event;
|
|
322
|
+
const sessionID = asString(evt.properties?.sessionID) ?? asString(evt.properties?.sessionId);
|
|
323
|
+
if (!sessionID) return void 0;
|
|
324
|
+
const previous = state.children[sessionID];
|
|
325
|
+
if (!previous) return void 0;
|
|
326
|
+
const info = isRecord(evt.properties?.info) ? evt.properties.info : void 0;
|
|
327
|
+
const completedMs = numberFromPath(info?.time, "completed");
|
|
328
|
+
const messageCompleted = evt.type === "message.updated" && asString(info?.role) === "assistant" && typeof completedMs === "number";
|
|
329
|
+
const messageFailed = messageCompleted && info?.error !== void 0;
|
|
330
|
+
const status = messageFailed ? "error" : messageCompleted ? "done" : statusFromEvent(event) ?? previous.status;
|
|
331
|
+
const timestamp = messageCompleted ? new Date(completedMs).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
332
|
+
const done = status === "done" || status === "error";
|
|
333
|
+
return {
|
|
334
|
+
...previous,
|
|
335
|
+
status,
|
|
336
|
+
updatedAt: timestamp,
|
|
337
|
+
endedAt: done ? previous.endedAt ?? timestamp : previous.endedAt,
|
|
338
|
+
tokens: extractTokens(info?.tokens) ?? previous.tokens
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function statusFromEvent(event) {
|
|
342
|
+
if (!isRecord(event)) return void 0;
|
|
343
|
+
const evt = event;
|
|
344
|
+
if (evt.type === "session.error") return "error";
|
|
345
|
+
if (evt.type === "session.idle") return "done";
|
|
346
|
+
if (evt.type === "session.status" && isRecord(evt.properties?.status)) {
|
|
347
|
+
const statusType = asString(evt.properties.status.type);
|
|
348
|
+
if (statusType === "busy" || statusType === "running") return "running";
|
|
349
|
+
if (statusType === "idle") return "done";
|
|
350
|
+
if (statusType === "error") return "error";
|
|
351
|
+
}
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
function extractTokens(value) {
|
|
355
|
+
if (!isRecord(value)) return void 0;
|
|
356
|
+
const input = asNumber(value.input);
|
|
357
|
+
const output = asNumber(value.output);
|
|
358
|
+
const reasoning = asNumber(value.reasoning);
|
|
359
|
+
const cache = isRecord(value.cache) ? value.cache : void 0;
|
|
360
|
+
const cacheRead = asNumber(cache?.read);
|
|
361
|
+
const cacheWrite = asNumber(cache?.write);
|
|
362
|
+
const total = asNumber(value.total) ?? [input, output, reasoning, cacheRead, cacheWrite].reduce(
|
|
363
|
+
(sum, item) => item === void 0 ? sum : (sum ?? 0) + item,
|
|
364
|
+
void 0
|
|
365
|
+
);
|
|
366
|
+
const contextPercent = asNumber(value.contextPercent);
|
|
367
|
+
if (input === void 0 && output === void 0 && total === void 0 && contextPercent === void 0) return void 0;
|
|
368
|
+
return compactUndefined({ input, output, total, contextPercent });
|
|
369
|
+
}
|
|
370
|
+
function compactUndefined(value) {
|
|
371
|
+
for (const key of Object.keys(value)) {
|
|
372
|
+
if (value[key] === void 0) delete value[key];
|
|
373
|
+
}
|
|
374
|
+
return value;
|
|
375
|
+
}
|
|
376
|
+
function formatContext(child) {
|
|
377
|
+
const total = child.tokens?.total ?? ((child.tokens?.input ?? 0) + (child.tokens?.output ?? 0) || void 0);
|
|
378
|
+
if (typeof total !== "number" || !Number.isFinite(total)) return "";
|
|
379
|
+
const label = Math.round(total) === 1 ? "token" : "tokens";
|
|
380
|
+
return `ctx ${Math.max(0, Math.round(total)).toLocaleString("en-US")} ${label}`;
|
|
381
|
+
}
|
|
382
|
+
function formatUsage(child) {
|
|
383
|
+
const context = formatContext(child);
|
|
384
|
+
const percent = child.tokens?.contextPercent;
|
|
385
|
+
if (typeof percent === "number") return `${context} ${Math.round(percent)}%`.trim();
|
|
386
|
+
return context;
|
|
387
|
+
}
|
|
388
|
+
function formatSubagentDuration(child, now) {
|
|
389
|
+
const nowIso = new Date(now ?? Date.now()).toISOString();
|
|
390
|
+
if (child.status === "running" || child.status === "error" && !child.endedAt) {
|
|
391
|
+
return formatDuration(elapsedMs(child.startedAt, child.endedAt ?? nowIso));
|
|
392
|
+
}
|
|
393
|
+
return formatDuration(child.elapsedMs ?? elapsedMs(child.startedAt, child.endedAt ?? nowIso));
|
|
394
|
+
}
|
|
395
|
+
function formatDuration(ms) {
|
|
396
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
397
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
398
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
399
|
+
const seconds = totalSeconds % 60;
|
|
400
|
+
if (hours > 0) {
|
|
401
|
+
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
402
|
+
}
|
|
403
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
404
|
+
}
|
|
405
|
+
function statusSortRank(status) {
|
|
406
|
+
if (status === "running") return 0;
|
|
407
|
+
if (status === "error") return 1;
|
|
408
|
+
return 2;
|
|
409
|
+
}
|
|
410
|
+
function formatSubagentTitle(title) {
|
|
411
|
+
const match = title.match(/^(?:[✓✗!]\s*)?([A-Za-z][\w -]*?)\s+[—-]\s+(.+)$/u);
|
|
412
|
+
if (!match) return truncateMiddle(title, 36);
|
|
413
|
+
const agent = match[1]?.trim();
|
|
414
|
+
const task = match[2]?.trim();
|
|
415
|
+
if (!agent || !task) return truncateMiddle(title, 36);
|
|
416
|
+
return truncateMiddle(`${agent}: ${task}`, 36);
|
|
417
|
+
}
|
|
418
|
+
function truncateMiddle(value, maxLength) {
|
|
419
|
+
if (value.length <= maxLength) return value;
|
|
420
|
+
if (maxLength <= 3) return value.slice(0, maxLength);
|
|
421
|
+
const marker = "...";
|
|
422
|
+
const left = Math.ceil((maxLength - marker.length) / 2);
|
|
423
|
+
const right = Math.floor((maxLength - marker.length) / 2);
|
|
424
|
+
return `${value.slice(0, left)}${marker}${value.slice(value.length - right)}`;
|
|
425
|
+
}
|
|
426
|
+
function elapsedMs(start, end) {
|
|
427
|
+
const startMs = Date.parse(start);
|
|
428
|
+
const endMs = Date.parse(end);
|
|
429
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return 0;
|
|
430
|
+
return Math.max(0, endMs - startMs);
|
|
431
|
+
}
|
|
432
|
+
function numberFromPath(value, key) {
|
|
433
|
+
if (!isRecord(value)) return void 0;
|
|
434
|
+
return asNumber(value[key]);
|
|
435
|
+
}
|
|
436
|
+
function asNumber(value) {
|
|
437
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
438
|
+
}
|
|
439
|
+
function asString(value) {
|
|
440
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
441
|
+
}
|
|
442
|
+
function isRecord(value) {
|
|
443
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export {
|
|
447
|
+
createMetricsState,
|
|
448
|
+
estimateStreamTokens,
|
|
449
|
+
recordAssistantMessage,
|
|
450
|
+
recordAssistantDelta,
|
|
451
|
+
recordToolActivity,
|
|
452
|
+
renderMetricsText,
|
|
453
|
+
createSubagentState,
|
|
454
|
+
applySubagentEvent,
|
|
455
|
+
renderSubagentStatus,
|
|
456
|
+
getSubagentItems,
|
|
457
|
+
pruneStaleSubagents,
|
|
458
|
+
getSubagentSidebarModel,
|
|
459
|
+
renderSubagentSidebar,
|
|
460
|
+
renderSubagentFooter
|
|
461
|
+
};
|
|
462
|
+
//# sourceMappingURL=chunk-WF6BN2SB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/metrics.ts","../src/subagents.ts"],"sourcesContent":["export type StreamSample = {\n at: number;\n tokens: number;\n};\n\nexport type MessageTiming = {\n sessionID: string;\n requestStartAt: number;\n firstResponseAt?: number | undefined;\n firstTokenAt?: number | undefined;\n lastTokenAt?: number | undefined;\n lastToolCallAt?: number | undefined;\n};\n\nexport type SessionAverage = {\n totalTokens: number;\n totalDurationMs: number;\n totalTtftMs: number;\n messageCount: number;\n};\n\nexport type MetricsState = {\n streamSamplesBySession: Record<string, StreamSample[]>;\n messageTimingByID: Record<string, MessageTiming>;\n sessionAverageByID: Record<string, SessionAverage>;\n};\n\nconst STREAM_WINDOW_MS = 5_000;\nconst LIVE_STALE_MS = 1_500;\nconst SINGLE_SAMPLE_MS = 1_000;\n\nexport function createMetricsState(): MetricsState {\n return {\n streamSamplesBySession: {},\n messageTimingByID: {},\n sessionAverageByID: {}\n };\n}\n\nexport function estimateStreamTokens(delta: string) {\n return Math.max(1, Math.ceil(Buffer.byteLength(delta, \"utf8\") / 5));\n}\n\nexport function recordAssistantMessage(\n state: MetricsState,\n input: {\n sessionID: string;\n messageID: string;\n createdAt: number;\n completedAt?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n finish?: string;\n }\n) {\n if (typeof input.completedAt !== \"number\") {\n const existing = state.messageTimingByID[input.messageID];\n state.messageTimingByID[input.messageID] = {\n sessionID: input.sessionID,\n requestStartAt: input.createdAt,\n firstResponseAt: existing?.firstResponseAt,\n firstTokenAt: existing?.firstTokenAt,\n lastTokenAt: existing?.lastTokenAt,\n lastToolCallAt: existing?.lastToolCallAt\n };\n return;\n }\n\n const timing = state.messageTimingByID[input.messageID];\n if (timing?.sessionID === input.sessionID && typeof timing.firstResponseAt === \"number\") {\n const totalTokens = (input.outputTokens ?? 0) + (input.reasoningTokens ?? 0);\n const endAt = input.finish === \"tool-calls\" ? timing.lastToolCallAt : input.completedAt;\n const durationMs = typeof endAt === \"number\" ? Math.max(endAt - timing.firstResponseAt, 1) : undefined;\n const ttftMs = Math.max(timing.firstResponseAt - timing.requestStartAt, 0);\n if (totalTokens > 0 && durationMs) {\n const totals =\n state.sessionAverageByID[input.sessionID] ??\n {\n totalTokens: 0,\n totalDurationMs: 0,\n totalTtftMs: 0,\n messageCount: 0\n };\n state.sessionAverageByID[input.sessionID] = {\n totalTokens: totals.totalTokens + totalTokens,\n totalDurationMs: totals.totalDurationMs + durationMs,\n totalTtftMs: totals.totalTtftMs + ttftMs,\n messageCount: totals.messageCount + 1\n };\n }\n }\n\n delete state.messageTimingByID[input.messageID];\n pruneSamples(state, input.completedAt);\n}\n\nexport function recordAssistantDelta(\n state: MetricsState,\n input: { sessionID: string; messageID: string; delta: string; at: number }\n) {\n const sample = {\n at: input.at,\n tokens: estimateStreamTokens(input.delta)\n };\n state.streamSamplesBySession[input.sessionID] = [\n ...(state.streamSamplesBySession[input.sessionID] ?? []).filter((item) => input.at - item.at <= STREAM_WINDOW_MS),\n sample\n ];\n\n const timing = state.messageTimingByID[input.messageID];\n if (timing) {\n state.messageTimingByID[input.messageID] = timing.firstTokenAt\n ? { ...timing, lastTokenAt: input.at }\n : {\n ...timing,\n firstResponseAt: timing.firstResponseAt ?? input.at,\n firstTokenAt: input.at,\n lastTokenAt: input.at\n };\n }\n}\n\nexport function recordToolActivity(state: MetricsState, sessionID: string, messageID: string, at = Date.now()) {\n if (state.streamSamplesBySession[sessionID]?.length) {\n delete state.streamSamplesBySession[sessionID];\n }\n const timing = state.messageTimingByID[messageID];\n if (timing) {\n state.messageTimingByID[messageID] = {\n ...timing,\n lastToolCallAt: at,\n firstResponseAt: timing.firstResponseAt ?? at\n };\n }\n}\n\nexport function renderMetricsText(\n state: MetricsState,\n sessionID: string,\n options: { now?: number; idle?: boolean } = {}\n) {\n const live = liveTps(state, sessionID, options) ?? \"-\";\n const avg = sessionAverage(state, sessionID) ?? \"-\";\n const ttft = sessionTtft(state, sessionID) ?? \"-\";\n return `TPS ${live} | AVG ${avg} | TTFT ${ttft}`;\n}\n\nfunction pruneSamples(state: MetricsState, now = Date.now()) {\n for (const [sessionID, samples] of Object.entries(state.streamSamplesBySession)) {\n const next = samples.filter((sample) => now - sample.at <= STREAM_WINDOW_MS);\n if (next.length > 0) state.streamSamplesBySession[sessionID] = next;\n else delete state.streamSamplesBySession[sessionID];\n }\n}\n\nfunction sessionAverage(state: MetricsState, sessionID: string) {\n const totals = state.sessionAverageByID[sessionID];\n if (!totals || totals.totalTokens <= 0 || totals.totalDurationMs <= 0) return undefined;\n return formatRate(totals.totalTokens / (totals.totalDurationMs / 1000), \"AVG\");\n}\n\nfunction sessionTtft(state: MetricsState, sessionID: string) {\n const totals = state.sessionAverageByID[sessionID];\n if (!totals || totals.messageCount <= 0 || totals.totalTtftMs < 0) return undefined;\n return formatTtft(totals.totalTtftMs / totals.messageCount / 1000);\n}\n\nfunction liveTps(\n state: MetricsState,\n sessionID: string,\n options: { now?: number; idle?: boolean } = {}\n) {\n if (options.idle) return undefined;\n const now = options.now ?? Date.now();\n const samples = state.streamSamplesBySession[sessionID] ?? [];\n const relevant = samples.filter((sample) => now - sample.at <= STREAM_WINDOW_MS);\n if (relevant.length === 0) return undefined;\n const lastSample = relevant.at(-1);\n if (!lastSample || now - lastSample.at > LIVE_STALE_MS) return undefined;\n const total = relevant.reduce((sum, sample) => sum + sample.tokens, 0);\n const durationSeconds = activeDurationMs(relevant, now) / 1000;\n if (durationSeconds <= 0) return undefined;\n return formatRate(total / durationSeconds, \"TPS\");\n}\n\nfunction activeDurationMs(samples: StreamSample[], tailAt?: number) {\n if (samples.length === 0) return 0;\n if (samples.length === 1) {\n const tailDuration = tailAt ? Math.max(0, tailAt - samples[0]!.at) : SINGLE_SAMPLE_MS;\n return Math.min(Math.max(tailDuration, 250), SINGLE_SAMPLE_MS);\n }\n\n let duration = 0;\n for (let index = 1; index < samples.length; index++) {\n duration += Math.max(0, samples[index]!.at - samples[index - 1]!.at);\n }\n\n if (tailAt) {\n duration += Math.max(0, tailAt - samples.at(-1)!.at);\n }\n\n return Math.max(duration, SINGLE_SAMPLE_MS);\n}\n\nfunction formatRate(value: number, label: \"TPS\" | \"AVG\") {\n if (!Number.isFinite(value) || value <= 0) return undefined;\n const suffix = label === \"TPS\" ? \" TPS\" : \"\";\n if (value >= 100) return `${Math.round(value)}${suffix}`;\n if (value >= 10) return `${value.toFixed(1)}${suffix}`;\n return `${value.toFixed(2)}${suffix}`;\n}\n\nfunction formatTtft(value: number) {\n if (!Number.isFinite(value) || value < 0) return undefined;\n return `${value.toFixed(1)}s`;\n}\n","export type SubagentStatus = \"running\" | \"done\" | \"error\";\n\nexport type SubagentInfo = {\n id: string;\n parentID: string;\n title: string;\n status: SubagentStatus;\n startedAt: string;\n updatedAt: string;\n endedAt?: string | undefined;\n elapsedMs?: number | undefined;\n tokens?: {\n input?: number | undefined;\n output?: number | undefined;\n total?: number | undefined;\n contextPercent?: number | undefined;\n } | undefined;\n};\n\nexport type SubagentState = {\n children: Record<string, SubagentInfo>;\n totalExecuted: number;\n};\n\nexport type SubagentSidebarRow = {\n id: string;\n title: string;\n subtitle: string;\n status: SubagentStatus;\n};\n\nexport type SubagentSidebarModel = {\n title: string;\n summary: string;\n rows: SubagentSidebarRow[];\n};\n\ntype EventLike = {\n type?: unknown;\n properties?: {\n info?: Record<string, unknown>;\n part?: unknown;\n sessionID?: unknown;\n sessionId?: unknown;\n status?: unknown;\n };\n};\n\nexport function createSubagentState(): SubagentState {\n return { children: {}, totalExecuted: 0 };\n}\n\nexport function applySubagentEvent(state: SubagentState, event: unknown) {\n const created = extractTaskToolSubagent(event) ?? extractSubagent(event) ?? updateExistingSubagent(state, event);\n if (!created) return false;\n\n const previous = state.children[created.id];\n const preserveTerminal = !!previous && isTerminalStatus(previous.status) && created.status === \"running\";\n const status = preserveTerminal ? previous.status : created.status;\n const title = preserveTerminal ? previous.title : created.title;\n const startedAt = preserveTerminal ? previous.startedAt : created.startedAt;\n const endedAt = preserveTerminal ? previous.endedAt : created.endedAt;\n const next: SubagentInfo = {\n ...previous,\n ...created,\n title,\n status,\n startedAt,\n updatedAt: created.updatedAt,\n endedAt,\n elapsedMs: elapsedMs(startedAt, endedAt ?? created.updatedAt),\n tokens: created.tokens ?? previous?.tokens\n };\n\n if (!previous) state.totalExecuted += 1;\n state.children[created.id] = next;\n return true;\n}\n\nfunction isTerminalStatus(status: SubagentStatus) {\n return status === \"done\" || status === \"error\";\n}\n\nexport function renderSubagentStatus(state: SubagentState, options: { now?: number } = {}) {\n const children = getSubagentItems(state);\n const running = children.filter((child) => child.status === \"running\").length;\n const done = children.filter((child) => child.status === \"done\").length;\n const error = children.filter((child) => child.status === \"error\").length;\n const aggregate = `${running} running · ${done} done · ${error} failed · ${state.totalExecuted} total`;\n\n if (children.length === 0) return aggregate;\n\n const details = children\n .map((child) => {\n const nowIso = new Date(options.now ?? Date.now()).toISOString();\n const duration =\n child.status === \"running\" || (child.status === \"error\" && !child.endedAt)\n ? formatDuration(elapsedMs(child.startedAt, child.endedAt ?? nowIso))\n : formatDuration(child.elapsedMs ?? elapsedMs(child.startedAt, child.endedAt ?? nowIso));\n const context = formatContext(child);\n return [child.title, duration, context].filter(Boolean).join(\" \");\n })\n .join(\" · \");\n\n return `${aggregate} · ${details}`;\n}\n\nexport function getSubagentItems(state: SubagentState, parentID?: string) {\n return Object.values(state.children)\n .filter((child) => !parentID || child.parentID === parentID)\n .sort((a, b) => {\n const statusRank = statusSortRank(a.status) - statusSortRank(b.status);\n if (statusRank !== 0) return statusRank;\n return b.startedAt.localeCompare(a.startedAt);\n });\n}\n\nexport function pruneStaleSubagents(state: SubagentState, options: { now?: number; staleMs?: number } = {}) {\n const now = options.now ?? Date.now();\n const staleMs = options.staleMs ?? 180_000;\n let changed = false;\n\n for (const [id, child] of Object.entries(state.children)) {\n if (child.status === \"running\") continue;\n const lastActivity = Date.parse(child.endedAt ?? child.updatedAt);\n if (!Number.isFinite(lastActivity)) continue;\n if (now - lastActivity <= staleMs) continue;\n delete state.children[id];\n changed = true;\n }\n\n return changed;\n}\n\nexport function getSubagentSidebarModel(\n state: SubagentState,\n parentID: string,\n options: { now?: number; staleMs?: number } = {}\n): SubagentSidebarModel | undefined {\n pruneStaleSubagents(state, options);\n const children = getSubagentItems(state, parentID);\n if (children.length === 0) return undefined;\n\n const running = children.filter((child) => child.status === \"running\").length;\n const done = children.filter((child) => child.status === \"done\").length;\n const error = children.filter((child) => child.status === \"error\").length;\n\n return {\n title: \"Subagents\",\n summary: `${running} running · ${done} done · ${error} error`,\n rows: children.map((child) => ({\n id: child.id,\n title: formatSubagentTitle(child.title),\n subtitle: [formatSubagentDuration(child, options.now), formatUsage(child)].filter(Boolean).join(\" · \"),\n status: child.status\n }))\n };\n}\n\nexport function renderSubagentSidebar(state: SubagentState, parentID: string, options: { now?: number } = {}) {\n const model = getSubagentSidebarModel(state, parentID, options);\n if (!model) return \"\";\n\n return [\n model.title,\n model.summary,\n ...model.rows.flatMap((row) => [row.title, row.subtitle].filter(Boolean))\n ].join(\"\\n\");\n}\n\nexport function renderSubagentFooter(state: SubagentState, parentID: string, options: { now?: number } = {}) {\n const model = getSubagentSidebarModel(state, parentID, options);\n if (!model) return \"\";\n return `Subagents ${model.summary}`;\n}\n\nfunction extractTaskToolSubagent(event: unknown): SubagentInfo | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n if (evt.type !== \"message.part.updated\") return undefined;\n\n const part = isRecord(evt.properties?.part) ? evt.properties.part : undefined;\n if (!part || part.type !== \"tool\" || part.tool !== \"task\") return undefined;\n\n const state = isRecord(part.state) ? part.state : undefined;\n if (!state) return undefined;\n\n const metadata = isRecord(state.metadata) ? state.metadata : isRecord(part.metadata) ? part.metadata : undefined;\n const id = asString(metadata?.sessionId) ?? sessionIdFromTaskOutput(asString(state.output));\n const parentID = asString(metadata?.parentSessionId) ?? asString(evt.properties?.sessionID);\n if (!id || !parentID || id === parentID) return undefined;\n\n const status = taskToolStatus(state);\n const startedMs = numberFromPath(state.time, \"start\") ?? numberFromPath(state.time, \"created\") ?? Date.now();\n const endedMs = numberFromPath(state.time, \"end\");\n const updatedMs = endedMs ?? numberFromPath(state.time, \"updated\") ?? startedMs;\n const input = isRecord(state.input) ? state.input : undefined;\n const agent = asString(input?.subagent_type);\n const taskTitle = asString(state.title) ?? asString(input?.description) ?? \"task\";\n\n return {\n id,\n parentID,\n title: agent ? `${agentTitle(agent)}: ${taskTitle}` : taskTitle,\n status,\n startedAt: new Date(startedMs).toISOString(),\n updatedAt: new Date(updatedMs).toISOString(),\n endedAt: status === \"running\" || typeof endedMs !== \"number\" ? undefined : new Date(endedMs).toISOString(),\n elapsedMs: Math.max(0, updatedMs - startedMs),\n tokens: extractTokens(state.tokens)\n };\n}\n\nfunction taskToolStatus(state: Record<string, unknown>): SubagentStatus {\n const status = asString(state.status);\n if (status === \"completed\") return \"done\";\n if (status === \"error\") return \"error\";\n return \"running\";\n}\n\nfunction sessionIdFromTaskOutput(output: string | undefined) {\n return output?.match(/<task\\s+id=\"([^\"]+)\"/)?.[1];\n}\n\nfunction agentTitle(agent: string) {\n return agent\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)\n .join(\" \");\n}\n\nfunction extractSubagent(event: unknown): SubagentInfo | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n if (evt.type !== \"session.created\" && evt.type !== \"session.updated\") return undefined;\n\n const info = isRecord(evt.properties?.info) ? evt.properties.info : undefined;\n if (!info) return undefined;\n\n const parentID = asString(info.parentID);\n const id = asString(info.id);\n if (!parentID || !id || id === parentID) return undefined;\n\n const startedMs = numberFromPath(info.time, \"created\") ?? numberFromPath(info.time, \"started\") ?? Date.now();\n const completedMs = numberFromPath(info.time, \"completed\");\n const explicitUpdatedMs = numberFromPath(info.time, \"updated\");\n const updatedMs = completedMs ?? explicitUpdatedMs ?? startedMs;\n const infoStatus = asString(info.status);\n const hasError = info.error !== undefined || infoStatus === \"error\";\n const status: SubagentStatus =\n hasError\n ? \"error\"\n : typeof completedMs === \"number\" || infoStatus === \"idle\"\n ? \"done\"\n : \"running\";\n const terminalMs =\n typeof completedMs === \"number\" || infoStatus === \"idle\"\n ? updatedMs\n : status === \"error\" && typeof explicitUpdatedMs === \"number\"\n ? explicitUpdatedMs\n : undefined;\n const endedAt = typeof terminalMs === \"number\" ? new Date(terminalMs).toISOString() : undefined;\n\n return {\n id,\n parentID,\n title: asString(info.title) ?? asString(info.name) ?? \"subagent\",\n status,\n startedAt: new Date(startedMs).toISOString(),\n updatedAt: new Date(updatedMs).toISOString(),\n endedAt,\n elapsedMs: Math.max(0, updatedMs - startedMs),\n tokens: extractTokens(info.tokens)\n };\n}\n\nfunction updateExistingSubagent(state: SubagentState, event: unknown): SubagentInfo | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n const sessionID = asString(evt.properties?.sessionID) ?? asString(evt.properties?.sessionId);\n if (!sessionID) return undefined;\n\n const previous = state.children[sessionID];\n if (!previous) return undefined;\n\n const info = isRecord(evt.properties?.info) ? evt.properties.info : undefined;\n const completedMs = numberFromPath(info?.time, \"completed\");\n const messageCompleted = evt.type === \"message.updated\" && asString(info?.role) === \"assistant\" && typeof completedMs === \"number\";\n const messageFailed = messageCompleted && info?.error !== undefined;\n const status = messageFailed ? \"error\" : messageCompleted ? \"done\" : statusFromEvent(event) ?? previous.status;\n const timestamp = messageCompleted ? new Date(completedMs).toISOString() : new Date().toISOString();\n const done = status === \"done\" || status === \"error\";\n return {\n ...previous,\n status,\n updatedAt: timestamp,\n endedAt: done ? previous.endedAt ?? timestamp : previous.endedAt,\n tokens: extractTokens(info?.tokens) ?? previous.tokens\n };\n}\n\nfunction statusFromEvent(event: unknown): SubagentStatus | undefined {\n if (!isRecord(event)) return undefined;\n const evt = event as EventLike;\n if (evt.type === \"session.error\") return \"error\";\n if (evt.type === \"session.idle\") return \"done\";\n if (evt.type === \"session.status\" && isRecord(evt.properties?.status)) {\n const statusType = asString(evt.properties.status.type);\n if (statusType === \"busy\" || statusType === \"running\") return \"running\";\n if (statusType === \"idle\") return \"done\";\n if (statusType === \"error\") return \"error\";\n }\n return undefined;\n}\n\nfunction extractTokens(value: unknown): SubagentInfo[\"tokens\"] {\n if (!isRecord(value)) return undefined;\n const input = asNumber(value.input);\n const output = asNumber(value.output);\n const reasoning = asNumber(value.reasoning);\n const cache = isRecord(value.cache) ? value.cache : undefined;\n const cacheRead = asNumber(cache?.read);\n const cacheWrite = asNumber(cache?.write);\n const total =\n asNumber(value.total) ??\n [input, output, reasoning, cacheRead, cacheWrite].reduce<number | undefined>(\n (sum, item) => (item === undefined ? sum : (sum ?? 0) + item),\n undefined\n );\n const contextPercent = asNumber(value.contextPercent);\n if (input === undefined && output === undefined && total === undefined && contextPercent === undefined) return undefined;\n return compactUndefined({ input, output, total, contextPercent });\n}\n\nfunction compactUndefined<T extends Record<string, unknown>>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) delete value[key];\n }\n return value;\n}\n\nfunction formatContext(child: SubagentInfo) {\n const total = child.tokens?.total ?? ((child.tokens?.input ?? 0) + (child.tokens?.output ?? 0) || undefined);\n if (typeof total !== \"number\" || !Number.isFinite(total)) return \"\";\n const label = Math.round(total) === 1 ? \"token\" : \"tokens\";\n return `ctx ${Math.max(0, Math.round(total)).toLocaleString(\"en-US\")} ${label}`;\n}\n\nfunction formatUsage(child: SubagentInfo) {\n const context = formatContext(child);\n const percent = child.tokens?.contextPercent;\n if (typeof percent === \"number\") return `${context} ${Math.round(percent)}%`.trim();\n return context;\n}\n\nfunction formatSubagentDuration(child: SubagentInfo, now?: number) {\n const nowIso = new Date(now ?? Date.now()).toISOString();\n if (child.status === \"running\" || (child.status === \"error\" && !child.endedAt)) {\n return formatDuration(elapsedMs(child.startedAt, child.endedAt ?? nowIso));\n }\n return formatDuration(child.elapsedMs ?? elapsedMs(child.startedAt, child.endedAt ?? nowIso));\n}\n\nfunction formatDuration(ms: number) {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) {\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n }\n return `${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n}\n\nfunction statusSortRank(status: SubagentStatus) {\n if (status === \"running\") return 0;\n if (status === \"error\") return 1;\n return 2;\n}\n\nfunction formatSubagentTitle(title: string) {\n const match = title.match(/^(?:[✓✗!]\\s*)?([A-Za-z][\\w -]*?)\\s+[—-]\\s+(.+)$/u);\n if (!match) return truncateMiddle(title, 36);\n const agent = match[1]?.trim();\n const task = match[2]?.trim();\n if (!agent || !task) return truncateMiddle(title, 36);\n return truncateMiddle(`${agent}: ${task}`, 36);\n}\n\nfunction truncateMiddle(value: string, maxLength: number) {\n if (value.length <= maxLength) return value;\n if (maxLength <= 3) return value.slice(0, maxLength);\n const marker = \"...\";\n const left = Math.ceil((maxLength - marker.length) / 2);\n const right = Math.floor((maxLength - marker.length) / 2);\n return `${value.slice(0, left)}${marker}${value.slice(value.length - right)}`;\n}\n\nfunction elapsedMs(start: string, end: string) {\n const startMs = Date.parse(start);\n const endMs = Date.parse(end);\n if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return 0;\n return Math.max(0, endMs - startMs);\n}\n\nfunction numberFromPath(value: unknown, key: string) {\n if (!isRecord(value)) return undefined;\n return asNumber(value[key]);\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { C as CaptureRecord } from './capture-gQauLsdn.js';
|
|
3
|
+
|
|
4
|
+
type HistoryMessage = {
|
|
5
|
+
id: string;
|
|
6
|
+
sessionID: string;
|
|
7
|
+
role: string;
|
|
8
|
+
createdAt?: number | undefined;
|
|
9
|
+
completedAt?: number | undefined;
|
|
10
|
+
text: string;
|
|
11
|
+
requests: HistoryRequest[];
|
|
12
|
+
response?: HistoryResponse | undefined;
|
|
13
|
+
};
|
|
14
|
+
type HistoryRequest = {
|
|
15
|
+
id: string;
|
|
16
|
+
sessionID?: string | undefined;
|
|
17
|
+
messageID?: string | undefined;
|
|
18
|
+
timestamp: number;
|
|
19
|
+
agent?: string | undefined;
|
|
20
|
+
purpose: string;
|
|
21
|
+
providerID?: string | undefined;
|
|
22
|
+
modelID?: string | undefined;
|
|
23
|
+
summary: string;
|
|
24
|
+
payload: Record<string, unknown>;
|
|
25
|
+
headers?: HistoryRequestHeaders | undefined;
|
|
26
|
+
response?: HistoryResponse | undefined;
|
|
27
|
+
};
|
|
28
|
+
type HistoryRequestHeaders = {
|
|
29
|
+
id: string;
|
|
30
|
+
timestamp: number;
|
|
31
|
+
payload: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
type HistoryResponse = {
|
|
34
|
+
id: string;
|
|
35
|
+
sessionID: string;
|
|
36
|
+
role: string;
|
|
37
|
+
parentID?: string | undefined;
|
|
38
|
+
createdAt?: number | undefined;
|
|
39
|
+
completedAt?: number | undefined;
|
|
40
|
+
text: string;
|
|
41
|
+
reasoning: string;
|
|
42
|
+
tokens?: unknown;
|
|
43
|
+
cost?: number | undefined;
|
|
44
|
+
finish?: string | undefined;
|
|
45
|
+
events: Record<string, unknown>[];
|
|
46
|
+
};
|
|
47
|
+
type HistorySession = {
|
|
48
|
+
id: string;
|
|
49
|
+
title?: string | undefined;
|
|
50
|
+
updatedAt?: number | undefined;
|
|
51
|
+
messages: HistoryMessage[];
|
|
52
|
+
requests: HistoryRequest[];
|
|
53
|
+
};
|
|
54
|
+
type RequestHistory = {
|
|
55
|
+
sessions: HistorySession[];
|
|
56
|
+
requests: HistoryRequest[];
|
|
57
|
+
};
|
|
58
|
+
declare function buildRequestHistory(records: CaptureRecord[]): RequestHistory;
|
|
59
|
+
|
|
60
|
+
type CliOptions = {
|
|
61
|
+
dbPath?: string | undefined;
|
|
62
|
+
dataDir?: string | undefined;
|
|
63
|
+
limit: number;
|
|
64
|
+
limitProvided: boolean;
|
|
65
|
+
json: boolean;
|
|
66
|
+
host?: string | undefined;
|
|
67
|
+
port?: number | undefined;
|
|
68
|
+
output?: string | undefined;
|
|
69
|
+
configDir?: string | undefined;
|
|
70
|
+
dryRun: boolean;
|
|
71
|
+
};
|
|
72
|
+
declare function parseOptions(args: string[]): CliOptions;
|
|
73
|
+
declare function summarizeSessions(sessions: ReturnType<typeof buildRequestHistory>["sessions"]): {
|
|
74
|
+
id: string;
|
|
75
|
+
title: string;
|
|
76
|
+
updatedAt: number | undefined;
|
|
77
|
+
messages: number;
|
|
78
|
+
hooks: number;
|
|
79
|
+
responses: number;
|
|
80
|
+
}[];
|
|
81
|
+
declare function formatSessionSummary(rows: ReturnType<typeof summarizeSessions>): string;
|
|
82
|
+
type JsonObject = Record<string, unknown>;
|
|
83
|
+
declare function configureOpenCode(options: CliOptions): Promise<string>;
|
|
84
|
+
declare function defaultOpenCodeConfigDir(): string;
|
|
85
|
+
declare function resolveOpenCodeConfigPath(configDir: string): string;
|
|
86
|
+
declare function stripJsonCommentsAndTrailingCommas(input: string): string;
|
|
87
|
+
declare function addUniquePlugin(config: JsonObject, plugin: string): boolean;
|
|
88
|
+
|
|
89
|
+
export { addUniquePlugin, configureOpenCode, defaultOpenCodeConfigDir, formatSessionSummary, parseOptions, resolveOpenCodeConfigPath, stripJsonCommentsAndTrailingCommas, summarizeSessions };
|