sidekick-shared 0.22.0 → 0.23.1
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/README.md +75 -63
- package/dist/accountManager.js +10 -0
- package/dist/accountRegistry.d.ts +2 -0
- package/dist/accountStatus.d.ts +1 -0
- package/dist/accountStatus.js +6 -0
- package/dist/accounts.js +2 -0
- package/dist/aggregation/EventAggregator.d.ts +36 -2
- package/dist/aggregation/EventAggregator.js +259 -33
- package/dist/aggregation/snapshot.d.ts +1 -1
- package/dist/aggregation/snapshot.js +2 -1
- package/dist/aggregation/types.d.ts +25 -1
- package/dist/analytics/codeImpact.d.ts +20 -0
- package/dist/analytics/codeImpact.js +21 -0
- package/dist/analytics/compactionLedger.d.ts +11 -0
- package/dist/analytics/compactionLedger.js +31 -0
- package/dist/analytics/qualityScore.d.ts +26 -0
- package/dist/analytics/qualityScore.js +115 -0
- package/dist/autoSwitch.d.ts +1 -1
- package/dist/autoSwitch.js +14 -3
- package/dist/codexProfiles.js +2 -0
- package/dist/codexQuota.d.ts +2 -0
- package/dist/codexQuota.js +27 -0
- package/dist/codexQuotaWatcher.d.ts +7 -0
- package/dist/codexQuotaWatcher.js +37 -19
- package/dist/context/composer.d.ts +3 -2
- package/dist/context/composer.js +5 -5
- package/dist/context/sessionContext.js +35 -9
- package/dist/credentialIO.js +19 -5
- package/dist/doctor.d.ts +55 -0
- package/dist/doctor.js +244 -0
- package/dist/errorHistory.d.ts +33 -0
- package/dist/errorHistory.js +95 -0
- package/dist/extractors/errorTaxonomy.d.ts +6 -0
- package/dist/extractors/errorTaxonomy.js +47 -0
- package/dist/extractors/toolCall.d.ts +9 -0
- package/dist/extractors/toolCall.js +79 -0
- package/dist/handoffUrl.d.ts +7 -0
- package/dist/handoffUrl.js +26 -0
- package/dist/index.d.ts +32 -9
- package/dist/index.js +64 -24
- package/dist/modelInfo.js +6 -0
- package/dist/multiProviderQuotaService.js +13 -2
- package/dist/parsers/codexParser.js +17 -1
- package/dist/parsers/openCodeParser.js +13 -0
- package/dist/parsers/planExtractor.js +17 -6
- package/dist/parsers/sessionActivityDetector.js +2 -0
- package/dist/parsers/sessionPathResolver.js +19 -16
- package/dist/parsers/subagentScanner.d.ts +1 -1
- package/dist/parsers/subagentScanner.js +4 -5
- package/dist/parsers/subagentTraceParser.js +4 -7
- package/dist/paths.d.ts +10 -0
- package/dist/paths.js +18 -5
- package/dist/projectMigration.d.ts +23 -0
- package/dist/projectMigration.js +95 -0
- package/dist/providerStatus.js +13 -2
- package/dist/providers/claudeCode.js +18 -4
- package/dist/providers/codex.js +42 -13
- package/dist/providers/codexDatabase.d.ts +1 -11
- package/dist/providers/detect.js +32 -3
- package/dist/providers/openCodeDatabase.d.ts +1 -31
- package/dist/providers/openCodeDatabase.js +21 -11
- package/dist/providers/types.d.ts +1 -0
- package/dist/quota.js +8 -0
- package/dist/quotaPoller.js +17 -6
- package/dist/quotaSnapshots.js +80 -26
- package/dist/readers/decisions.d.ts +2 -1
- package/dist/readers/decisions.js +5 -5
- package/dist/readers/handoff.d.ts +2 -1
- package/dist/readers/handoff.js +13 -8
- package/dist/readers/helpers.d.ts +3 -0
- package/dist/readers/helpers.js +12 -0
- package/dist/readers/notes.d.ts +2 -1
- package/dist/readers/notes.js +11 -8
- package/dist/readers/plans.js +3 -5
- package/dist/readers/tasks.d.ts +2 -1
- package/dist/readers/tasks.js +5 -5
- package/dist/report/htmlHelpers.js +6 -1
- package/dist/report/openBrowser.js +3 -3
- package/dist/schemas/index.d.ts +2 -0
- package/dist/schemas/index.js +10 -1
- package/dist/schemas/observedSessionV1.d.ts +499 -0
- package/dist/schemas/observedSessionV1.js +69 -0
- package/dist/schemas/sessionEvent.d.ts +13 -0
- package/dist/schemas/sessionEvent.js +19 -0
- package/dist/search/sessionSearch.d.ts +2 -2
- package/dist/search/sessionSearch.js +2 -3
- package/dist/sessionMonitor.d.ts +29 -0
- package/dist/sessionMonitor.js +71 -0
- package/dist/statusline/BurnRateCalculator.d.ts +14 -0
- package/dist/statusline/BurnRateCalculator.js +45 -0
- package/dist/statusline/formatter.d.ts +16 -0
- package/dist/statusline/formatter.js +84 -0
- package/dist/statusline/index.d.ts +7 -0
- package/dist/statusline/index.js +13 -0
- package/dist/terminalSync.js +16 -7
- package/dist/types/codex.d.ts +2 -0
- package/dist/types/historicalData.d.ts +49 -1
- package/dist/types/historicalData.js +28 -2
- package/dist/types/observedSessionV1.d.ts +70 -0
- package/dist/types/observedSessionV1.js +186 -0
- package/dist/types/opencode.d.ts +1 -0
- package/dist/types/sessionEvent.d.ts +19 -1
- package/dist/watchers/eventBridge.js +1 -0
- package/dist/watchers/factory.js +14 -3
- package/dist/watchers/jsonlTail.js +34 -14
- package/dist/watchers/jsonlWatcher.js +5 -3
- package/dist/watchers/providerReaderWatcher.js +4 -2
- package/dist/watchers/sqliteWatcher.d.ts +2 -0
- package/dist/watchers/sqliteWatcher.js +27 -14
- package/dist/watchers/types.d.ts +5 -0
- package/dist/writers/atomic.d.ts +4 -0
- package/dist/writers/atomic.js +200 -0
- package/dist/writers/decisions.d.ts +12 -0
- package/dist/writers/decisions.js +88 -0
- package/dist/writers/notes.d.ts +8 -0
- package/dist/writers/notes.js +82 -0
- package/dist/writers/tasks.d.ts +6 -0
- package/dist/writers/tasks.js +100 -0
- package/dist/zaiQuotaWatcher.js +6 -0
- package/package.json +5 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @module aggregation/EventAggregator
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.EventAggregator = void 0;
|
|
12
|
+
exports.EventAggregator = exports.SNAPSHOT_SCHEMA_VERSION = void 0;
|
|
13
13
|
exports.parseTodoDependencies = parseTodoDependencies;
|
|
14
14
|
const jsonl_1 = require("../parsers/jsonl");
|
|
15
15
|
const planExtractor_1 = require("../parsers/planExtractor");
|
|
@@ -20,14 +20,16 @@ const FrequencyTracker_1 = require("./FrequencyTracker");
|
|
|
20
20
|
const HeatmapTracker_1 = require("./HeatmapTracker");
|
|
21
21
|
const PatternExtractor_1 = require("./PatternExtractor");
|
|
22
22
|
const modelInfo_1 = require("../modelInfo");
|
|
23
|
+
const errorTaxonomy_1 = require("../extractors/errorTaxonomy");
|
|
23
24
|
// ── Defaults ──
|
|
24
25
|
const DEFAULT_TIMELINE_CAP = 200;
|
|
25
26
|
const DEFAULT_LATENCY_CAP = 100;
|
|
27
|
+
const DEFAULT_CONTEXT_TIMELINE_CAP = 2_000;
|
|
26
28
|
const DEFAULT_BURN_WINDOW_MS = 5 * 60_000;
|
|
27
29
|
const DEFAULT_BURN_SAMPLE_MS = 10_000;
|
|
28
30
|
const COMPACTION_DROP_THRESHOLD = 0.8; // >20% drop
|
|
29
31
|
/** Schema version for serialized snapshots. */
|
|
30
|
-
|
|
32
|
+
exports.SNAPSHOT_SCHEMA_VERSION = 3;
|
|
31
33
|
// ── Goal gate detection regex ──
|
|
32
34
|
const GOAL_GATE_REGEX = /\b(CRITICAL|MUST|blocker|required|must.?complete|goal.?gate|essential|do.?not.?skip|blocking)\b/i;
|
|
33
35
|
// ── Todo dependency parsing ──
|
|
@@ -79,6 +81,12 @@ class EventAggregator {
|
|
|
79
81
|
// Context size tracking
|
|
80
82
|
currentContextSize = 0;
|
|
81
83
|
previousContextSize = 0;
|
|
84
|
+
/**
|
|
85
|
+
* Index of a heuristic compaction event still awaiting its real
|
|
86
|
+
* post-compaction size from the next usage event. Prevents the usage-drop
|
|
87
|
+
* detector from double-counting an explicit compaction marker.
|
|
88
|
+
*/
|
|
89
|
+
pendingHeuristicCompactionIndex = null;
|
|
82
90
|
// Compaction
|
|
83
91
|
compactionEvents = [];
|
|
84
92
|
// Truncation
|
|
@@ -86,6 +94,10 @@ class EventAggregator {
|
|
|
86
94
|
// Tool analytics
|
|
87
95
|
toolAnalytics = new Map();
|
|
88
96
|
pendingToolCalls = new Map();
|
|
97
|
+
errorByToolCategory = new Map();
|
|
98
|
+
errorBuckets = new Map();
|
|
99
|
+
retryAttempts = 0;
|
|
100
|
+
finishReasons = new Map();
|
|
89
101
|
// Burn rate
|
|
90
102
|
burnSamples = [];
|
|
91
103
|
lastBurnSampleTime = 0;
|
|
@@ -99,6 +111,8 @@ class EventAggregator {
|
|
|
99
111
|
pendingSubagents = new Map(); // toolUseId -> index
|
|
100
112
|
// Plan state
|
|
101
113
|
planExtractor;
|
|
114
|
+
planState = null;
|
|
115
|
+
planEnteredAt;
|
|
102
116
|
// Context attribution
|
|
103
117
|
contextAttribution = {
|
|
104
118
|
systemPrompt: 0,
|
|
@@ -114,6 +128,7 @@ class EventAggregator {
|
|
|
114
128
|
permissionModeHistory = [];
|
|
115
129
|
// Context timeline
|
|
116
130
|
contextTimeline = [];
|
|
131
|
+
contextTimelineCap;
|
|
117
132
|
contextTurnIndex = 0;
|
|
118
133
|
// Timeline
|
|
119
134
|
timeline = [];
|
|
@@ -137,6 +152,7 @@ class EventAggregator {
|
|
|
137
152
|
this.latencyCap = options?.latencyCap ?? DEFAULT_LATENCY_CAP;
|
|
138
153
|
this.burnWindowMs = options?.burnWindowMs ?? DEFAULT_BURN_WINDOW_MS;
|
|
139
154
|
this.burnSampleMs = options?.burnSampleMs ?? DEFAULT_BURN_SAMPLE_MS;
|
|
155
|
+
this.contextTimelineCap = options?.contextTimelineCap ?? DEFAULT_CONTEXT_TIMELINE_CAP;
|
|
140
156
|
this.computeContextSize = options?.computeContextSize ?? null;
|
|
141
157
|
this.providerId = options?.providerId ?? null;
|
|
142
158
|
this._providerId = this.providerId;
|
|
@@ -154,12 +170,17 @@ class EventAggregator {
|
|
|
154
170
|
this.lastEventTime = event.timestamp;
|
|
155
171
|
// Guard: some event types (e.g. 'summary') have no message field in the raw JSONL
|
|
156
172
|
if (!event.message) {
|
|
173
|
+
if (event.type === 'summary') {
|
|
174
|
+
this.recordExplicitCompaction(event.timestamp, event.compaction);
|
|
175
|
+
this.addTimelineFromSessionEvent(event);
|
|
176
|
+
}
|
|
157
177
|
return;
|
|
158
178
|
}
|
|
159
179
|
// 2. Track model
|
|
160
180
|
if (event.message.model) {
|
|
161
181
|
this.currentModel = event.message.model;
|
|
162
182
|
}
|
|
183
|
+
this.recordProviderMetadata(event);
|
|
163
184
|
// 3. Skip system/audit events and synthetic token-count events for messageCount
|
|
164
185
|
const msgId = event.message.id ?? '';
|
|
165
186
|
if (event.type !== 'system' && !msgId.startsWith('token-count-')) {
|
|
@@ -171,7 +192,8 @@ class EventAggregator {
|
|
|
171
192
|
}
|
|
172
193
|
// 5. Token accumulation
|
|
173
194
|
if (event.message.usage) {
|
|
174
|
-
this.accumulateUsage(event.message.usage, event.timestamp, event.message.model);
|
|
195
|
+
const usageCost = this.accumulateUsage(event.message.usage, event.timestamp, event.message.model);
|
|
196
|
+
this.attributePlanUsage(event.message.usage.input_tokens + event.message.usage.output_tokens, usageCost);
|
|
175
197
|
}
|
|
176
198
|
if (event.type !== 'system') {
|
|
177
199
|
// 6. Tool extraction from content blocks
|
|
@@ -187,6 +209,9 @@ class EventAggregator {
|
|
|
187
209
|
this.attributeContextFromEvent(event);
|
|
188
210
|
// 11. Permission mode tracking
|
|
189
211
|
this.trackPermissionMode(event);
|
|
212
|
+
if (event.type === 'summary') {
|
|
213
|
+
this.recordExplicitCompaction(event.timestamp, event.compaction);
|
|
214
|
+
}
|
|
190
215
|
// 12. Timeline
|
|
191
216
|
this.addTimelineFromSessionEvent(event);
|
|
192
217
|
// 13. Gonzo/Lazyjournal analytics
|
|
@@ -222,6 +247,7 @@ class EventAggregator {
|
|
|
222
247
|
this.outputTokens += outputTok;
|
|
223
248
|
this.cacheReadTokens += cacheRead;
|
|
224
249
|
this.cacheWriteTokens += cacheWrite;
|
|
250
|
+
this.attributePlanUsage(inputTok + outputTok, event.cost ?? 0);
|
|
225
251
|
// Tokens since last burn sample
|
|
226
252
|
this.tokensSinceLastSample += inputTok + outputTok;
|
|
227
253
|
// Context size
|
|
@@ -238,13 +264,17 @@ class EventAggregator {
|
|
|
238
264
|
contextSize = inputTok + cacheWrite + cacheRead;
|
|
239
265
|
}
|
|
240
266
|
// Compaction detection
|
|
241
|
-
if (this.
|
|
267
|
+
if (this.resolvePendingHeuristicCompaction(contextSize)) {
|
|
268
|
+
// The drop belongs to the explicit compaction marker just recorded.
|
|
269
|
+
}
|
|
270
|
+
else if (this.previousContextSize > 0 &&
|
|
242
271
|
contextSize < this.previousContextSize * COMPACTION_DROP_THRESHOLD) {
|
|
243
272
|
this.compactionEvents.push({
|
|
244
273
|
timestamp: new Date(event.timestamp),
|
|
245
274
|
contextBefore: this.previousContextSize,
|
|
246
275
|
contextAfter: contextSize,
|
|
247
276
|
tokensReclaimed: this.previousContextSize - contextSize,
|
|
277
|
+
source: 'heuristic',
|
|
248
278
|
});
|
|
249
279
|
}
|
|
250
280
|
this.previousContextSize = contextSize;
|
|
@@ -277,7 +307,7 @@ class EventAggregator {
|
|
|
277
307
|
priced: true,
|
|
278
308
|
};
|
|
279
309
|
acc.calls++;
|
|
280
|
-
acc.tokens += inputTok + outputTok;
|
|
310
|
+
acc.tokens += inputTok + outputTok + cacheWrite + cacheRead;
|
|
281
311
|
acc.inputTokens += inputTok;
|
|
282
312
|
acc.outputTokens += outputTok;
|
|
283
313
|
acc.cacheWriteTokens += cacheWrite;
|
|
@@ -297,6 +327,7 @@ class EventAggregator {
|
|
|
297
327
|
// 4. Tool tracking from FollowEvent
|
|
298
328
|
if (event.type === 'tool_use' && event.toolName) {
|
|
299
329
|
this.recordFollowToolUse(event);
|
|
330
|
+
this.attributePlanToolCall(event.toolName);
|
|
300
331
|
}
|
|
301
332
|
else if (event.type === 'tool_result') {
|
|
302
333
|
this.recordFollowToolResult(event);
|
|
@@ -306,7 +337,7 @@ class EventAggregator {
|
|
|
306
337
|
// 6. Subagent tracking
|
|
307
338
|
this.extractSubagentFromFollowEvent(event);
|
|
308
339
|
// 7. Plan extraction
|
|
309
|
-
this.
|
|
340
|
+
this.processPlanFollowEvent(event);
|
|
310
341
|
// 8. Context attribution
|
|
311
342
|
this.attributeContextFromFollowEvent(event);
|
|
312
343
|
// 9. Truncation detection from tool results
|
|
@@ -315,19 +346,14 @@ class EventAggregator {
|
|
|
315
346
|
}
|
|
316
347
|
// 10. Explicit compaction event (summary type)
|
|
317
348
|
if (event.type === 'summary') {
|
|
318
|
-
this.
|
|
319
|
-
timestamp: new Date(event.timestamp),
|
|
320
|
-
contextBefore: this.previousContextSize,
|
|
321
|
-
contextAfter: 0,
|
|
322
|
-
tokensReclaimed: this.previousContextSize,
|
|
323
|
-
});
|
|
349
|
+
this.recordExplicitCompaction(event.timestamp, event.compaction);
|
|
324
350
|
}
|
|
325
351
|
// 11. Timeline
|
|
326
352
|
this.addTimelineFromFollowEvent(event);
|
|
327
353
|
// 12. Gonzo/Lazyjournal analytics
|
|
328
354
|
this.feedAnalytics(event.timestamp, event.summary, event.toolName);
|
|
329
355
|
// Message count (skip system events)
|
|
330
|
-
if (event.type
|
|
356
|
+
if (event.type === 'user' || event.type === 'assistant') {
|
|
331
357
|
this.messageCount++;
|
|
332
358
|
}
|
|
333
359
|
}
|
|
@@ -354,6 +380,15 @@ class EventAggregator {
|
|
|
354
380
|
b.pendingCount -
|
|
355
381
|
(a.successCount + a.failureCount + a.pendingCount));
|
|
356
382
|
}
|
|
383
|
+
getErrorRollup() {
|
|
384
|
+
return {
|
|
385
|
+
totalFailures: Array.from(this.errorByToolCategory.values()).reduce((sum, entry) => sum + entry.count, 0),
|
|
386
|
+
byToolCategory: Array.from(this.errorByToolCategory.values()).sort((a, b) => b.count - a.count),
|
|
387
|
+
byHourModel: Array.from(this.errorBuckets.values()).sort((a, b) => a.hour.localeCompare(b.hour)),
|
|
388
|
+
retryAttempts: this.retryAttempts,
|
|
389
|
+
finishReasons: Array.from(this.finishReasons, ([reason, count]) => ({ reason, count })).sort((a, b) => b.count - a.count),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
357
392
|
getCompactionEvents() {
|
|
358
393
|
return [...this.compactionEvents];
|
|
359
394
|
}
|
|
@@ -379,10 +414,9 @@ class EventAggregator {
|
|
|
379
414
|
return [...this.subagents];
|
|
380
415
|
}
|
|
381
416
|
getPlan() {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return this.convertExtractedPlanToPlanState(extracted);
|
|
417
|
+
return this.planState
|
|
418
|
+
? { ...this.planState, steps: this.planState.steps.map((step) => ({ ...step })) }
|
|
419
|
+
: null;
|
|
386
420
|
}
|
|
387
421
|
getContextAttribution() {
|
|
388
422
|
return { ...this.contextAttribution };
|
|
@@ -424,6 +458,7 @@ class EventAggregator {
|
|
|
424
458
|
truncationCount: this.truncationEvents.length,
|
|
425
459
|
truncationEvents: this.getTruncationEvents(),
|
|
426
460
|
toolStats: this.getToolStats(),
|
|
461
|
+
errorRollup: this.getErrorRollup(),
|
|
427
462
|
burnRate: this.getBurnRate(),
|
|
428
463
|
taskState: this.getTaskState(),
|
|
429
464
|
subagents: this.getSubagents(),
|
|
@@ -455,6 +490,10 @@ class EventAggregator {
|
|
|
455
490
|
this.truncationEvents = [];
|
|
456
491
|
this.toolAnalytics.clear();
|
|
457
492
|
this.pendingToolCalls.clear();
|
|
493
|
+
this.errorByToolCategory.clear();
|
|
494
|
+
this.errorBuckets.clear();
|
|
495
|
+
this.retryAttempts = 0;
|
|
496
|
+
this.finishReasons.clear();
|
|
458
497
|
this.burnSamples = [];
|
|
459
498
|
this.lastBurnSampleTime = 0;
|
|
460
499
|
this.tokensSinceLastSample = 0;
|
|
@@ -464,6 +503,8 @@ class EventAggregator {
|
|
|
464
503
|
this.subagents = [];
|
|
465
504
|
this.pendingSubagents.clear();
|
|
466
505
|
this.planExtractor.reset();
|
|
506
|
+
this.planState = null;
|
|
507
|
+
this.planEnteredAt = undefined;
|
|
467
508
|
this.currentPermissionMode = null;
|
|
468
509
|
this.permissionModeHistory = [];
|
|
469
510
|
this.contextTimeline = [];
|
|
@@ -509,7 +550,7 @@ class EventAggregator {
|
|
|
509
550
|
/** Serializes all mutable state to a JSON-safe object for snapshot persistence. */
|
|
510
551
|
serialize() {
|
|
511
552
|
return {
|
|
512
|
-
version: SNAPSHOT_SCHEMA_VERSION,
|
|
553
|
+
version: exports.SNAPSHOT_SCHEMA_VERSION,
|
|
513
554
|
tokens: {
|
|
514
555
|
input: this.inputTokens,
|
|
515
556
|
output: this.outputTokens,
|
|
@@ -523,6 +564,10 @@ class EventAggregator {
|
|
|
523
564
|
compactionEvents: this.compactionEvents,
|
|
524
565
|
truncationEvents: this.truncationEvents,
|
|
525
566
|
toolAnalytics: Array.from(this.toolAnalytics.entries()),
|
|
567
|
+
errorByToolCategory: Array.from(this.errorByToolCategory.entries()),
|
|
568
|
+
errorBuckets: Array.from(this.errorBuckets.entries()),
|
|
569
|
+
retryAttempts: this.retryAttempts,
|
|
570
|
+
finishReasons: Array.from(this.finishReasons.entries()),
|
|
526
571
|
contextAttribution: { ...this.contextAttribution },
|
|
527
572
|
burnSamples: [...this.burnSamples],
|
|
528
573
|
lastBurnSampleTime: this.lastBurnSampleTime,
|
|
@@ -549,8 +594,8 @@ class EventAggregator {
|
|
|
549
594
|
}
|
|
550
595
|
/** Restores mutable state from a serialized snapshot. Clears transient state (pending calls). */
|
|
551
596
|
restore(state) {
|
|
552
|
-
if (state.version !== SNAPSHOT_SCHEMA_VERSION) {
|
|
553
|
-
return;
|
|
597
|
+
if (state.version !== exports.SNAPSHOT_SCHEMA_VERSION) {
|
|
598
|
+
return false;
|
|
554
599
|
}
|
|
555
600
|
this.inputTokens = state.tokens.input;
|
|
556
601
|
this.outputTokens = state.tokens.output;
|
|
@@ -563,6 +608,10 @@ class EventAggregator {
|
|
|
563
608
|
this.compactionEvents = [...state.compactionEvents];
|
|
564
609
|
this.truncationEvents = [...state.truncationEvents];
|
|
565
610
|
this.toolAnalytics = new Map(state.toolAnalytics);
|
|
611
|
+
this.errorByToolCategory = new Map(state.errorByToolCategory);
|
|
612
|
+
this.errorBuckets = new Map(state.errorBuckets);
|
|
613
|
+
this.retryAttempts = state.retryAttempts;
|
|
614
|
+
this.finishReasons = new Map(state.finishReasons);
|
|
566
615
|
this.contextAttribution = { ...state.contextAttribution };
|
|
567
616
|
this.burnSamples = [...state.burnSamples];
|
|
568
617
|
this.lastBurnSampleTime = state.lastBurnSampleTime;
|
|
@@ -575,7 +624,9 @@ class EventAggregator {
|
|
|
575
624
|
this.permissionModeHistory = state.permissionModeHistory
|
|
576
625
|
? [...state.permissionModeHistory]
|
|
577
626
|
: [];
|
|
578
|
-
this.contextTimeline = state.contextTimeline
|
|
627
|
+
this.contextTimeline = state.contextTimeline
|
|
628
|
+
? state.contextTimeline.slice(-this.contextTimelineCap)
|
|
629
|
+
: [];
|
|
579
630
|
this.contextTurnIndex = state.contextTurnIndex ?? 0;
|
|
580
631
|
this.timeline = [...state.timeline];
|
|
581
632
|
this.messageCount = state.messageCount;
|
|
@@ -598,6 +649,7 @@ class EventAggregator {
|
|
|
598
649
|
this.pendingSubagents.clear();
|
|
599
650
|
this.pendingUserRequest = null;
|
|
600
651
|
this.planExtractor.reset();
|
|
652
|
+
return true;
|
|
601
653
|
}
|
|
602
654
|
// ═══════════════════════════════════════════════════════════════════════
|
|
603
655
|
// Private: Token & Context
|
|
@@ -653,13 +705,17 @@ class EventAggregator {
|
|
|
653
705
|
contextSize = inputTok + cacheWrite + cacheRead;
|
|
654
706
|
}
|
|
655
707
|
// Compaction detection
|
|
656
|
-
if (this.
|
|
708
|
+
if (this.resolvePendingHeuristicCompaction(contextSize)) {
|
|
709
|
+
// The drop belongs to the explicit compaction marker just recorded.
|
|
710
|
+
}
|
|
711
|
+
else if (this.previousContextSize > 0 &&
|
|
657
712
|
contextSize < this.previousContextSize * COMPACTION_DROP_THRESHOLD) {
|
|
658
713
|
this.compactionEvents.push({
|
|
659
714
|
timestamp: new Date(timestamp),
|
|
660
715
|
contextBefore: this.previousContextSize,
|
|
661
716
|
contextAfter: contextSize,
|
|
662
717
|
tokensReclaimed: this.previousContextSize - contextSize,
|
|
718
|
+
source: 'heuristic',
|
|
663
719
|
});
|
|
664
720
|
}
|
|
665
721
|
this.previousContextSize = contextSize;
|
|
@@ -670,6 +726,9 @@ class EventAggregator {
|
|
|
670
726
|
inputTokens: contextSize,
|
|
671
727
|
turnIndex: this.contextTurnIndex++,
|
|
672
728
|
});
|
|
729
|
+
if (this.contextTimeline.length > this.contextTimelineCap) {
|
|
730
|
+
this.contextTimeline.splice(0, this.contextTimeline.length - this.contextTimelineCap);
|
|
731
|
+
}
|
|
673
732
|
// Per-model usage. Accumulate reasoning tokens and an inverted-sticky
|
|
674
733
|
// `priced` flag so one unpriced event taints the aggregate (UI renders "—").
|
|
675
734
|
const perModelKey = model ?? this.currentModel ?? 'unknown';
|
|
@@ -685,7 +744,7 @@ class EventAggregator {
|
|
|
685
744
|
priced: true,
|
|
686
745
|
};
|
|
687
746
|
acc.calls++;
|
|
688
|
-
acc.tokens += inputTok + outputTok;
|
|
747
|
+
acc.tokens += inputTok + outputTok + cacheWrite + cacheRead;
|
|
689
748
|
acc.inputTokens += inputTok;
|
|
690
749
|
acc.outputTokens += outputTok;
|
|
691
750
|
acc.cacheWriteTokens += cacheWrite;
|
|
@@ -697,6 +756,7 @@ class EventAggregator {
|
|
|
697
756
|
this.modelUsage.set(perModelKey, acc);
|
|
698
757
|
// Burn rate sampling
|
|
699
758
|
this.updateBurnRate(timestamp);
|
|
759
|
+
return cost;
|
|
700
760
|
}
|
|
701
761
|
// ═══════════════════════════════════════════════════════════════════════
|
|
702
762
|
// Private: Burn Rate
|
|
@@ -788,6 +848,7 @@ class EventAggregator {
|
|
|
788
848
|
name,
|
|
789
849
|
startTime: new Date(event.timestamp),
|
|
790
850
|
});
|
|
851
|
+
this.attributePlanToolCall(name);
|
|
791
852
|
}
|
|
792
853
|
else if (block.type === 'tool_result') {
|
|
793
854
|
// User content: tool_result block
|
|
@@ -797,6 +858,9 @@ class EventAggregator {
|
|
|
797
858
|
continue;
|
|
798
859
|
const pending = this.pendingToolCalls.get(toolUseId);
|
|
799
860
|
if (pending) {
|
|
861
|
+
if (isError) {
|
|
862
|
+
this.recordError(pending.name, (0, errorTaxonomy_1.categorizeError)(block.content), event.timestamp, this.currentModel ?? 'unknown');
|
|
863
|
+
}
|
|
800
864
|
this.pendingToolCalls.delete(toolUseId);
|
|
801
865
|
const analytics = this.toolAnalytics.get(pending.name);
|
|
802
866
|
if (analytics) {
|
|
@@ -890,6 +954,7 @@ class EventAggregator {
|
|
|
890
954
|
const isError = raw.is_error === true;
|
|
891
955
|
if (isError) {
|
|
892
956
|
analytics.failureCount++;
|
|
957
|
+
this.recordError(pending.name, (0, errorTaxonomy_1.categorizeError)(raw.content ?? event.summary), event.timestamp, event.model ?? this.currentModel ?? 'unknown');
|
|
893
958
|
}
|
|
894
959
|
else {
|
|
895
960
|
analytics.successCount++;
|
|
@@ -900,6 +965,36 @@ class EventAggregator {
|
|
|
900
965
|
}
|
|
901
966
|
}
|
|
902
967
|
}
|
|
968
|
+
recordProviderMetadata(event) {
|
|
969
|
+
const metadata = event.providerMetadata;
|
|
970
|
+
if (!metadata)
|
|
971
|
+
return;
|
|
972
|
+
this.retryAttempts += metadata.retryAttempts?.length ?? 0;
|
|
973
|
+
for (const reason of metadata.finishReasons ?? []) {
|
|
974
|
+
this.finishReasons.set(reason, (this.finishReasons.get(reason) ?? 0) + 1);
|
|
975
|
+
}
|
|
976
|
+
if (metadata.providerError) {
|
|
977
|
+
this.recordError('ProviderAPI', (0, errorTaxonomy_1.categorizeError)(metadata.providerError, metadata.providerError.type), event.timestamp, event.message.model ?? this.currentModel ?? 'unknown');
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
recordError(tool, category, timestamp, model) {
|
|
981
|
+
const categoryKey = `${tool}\u0000${category}`;
|
|
982
|
+
const current = this.errorByToolCategory.get(categoryKey);
|
|
983
|
+
if (current)
|
|
984
|
+
current.count++;
|
|
985
|
+
else
|
|
986
|
+
this.errorByToolCategory.set(categoryKey, { tool, category, count: 1 });
|
|
987
|
+
const parsed = new Date(timestamp);
|
|
988
|
+
const hour = Number.isNaN(parsed.getTime())
|
|
989
|
+
? timestamp.slice(0, 13)
|
|
990
|
+
: parsed.toISOString().slice(0, 13);
|
|
991
|
+
const bucketKey = `${hour}\u0000${model}\u0000${tool}\u0000${category}`;
|
|
992
|
+
const bucket = this.errorBuckets.get(bucketKey);
|
|
993
|
+
if (bucket)
|
|
994
|
+
bucket.count++;
|
|
995
|
+
else
|
|
996
|
+
this.errorBuckets.set(bucketKey, { hour, model, tool, category, count: 1 });
|
|
997
|
+
}
|
|
903
998
|
detectTruncationFromFollowEvent(event) {
|
|
904
999
|
const raw = event.raw;
|
|
905
1000
|
if (!raw)
|
|
@@ -936,6 +1031,49 @@ class EventAggregator {
|
|
|
936
1031
|
}
|
|
937
1032
|
}
|
|
938
1033
|
}
|
|
1034
|
+
recordExplicitCompaction(timestamp, reported) {
|
|
1035
|
+
const hasReported = reported !== undefined &&
|
|
1036
|
+
Number.isFinite(reported.tokensBefore) &&
|
|
1037
|
+
Number.isFinite(reported.tokensAfter) &&
|
|
1038
|
+
reported.tokensBefore >= reported.tokensAfter &&
|
|
1039
|
+
reported.tokensAfter >= 0;
|
|
1040
|
+
// Without reported counts, assume the full context was reclaimed until
|
|
1041
|
+
// the next usage event supplies the real post-compaction size.
|
|
1042
|
+
const contextBefore = hasReported ? reported.tokensBefore : this.previousContextSize;
|
|
1043
|
+
const contextAfter = hasReported ? reported.tokensAfter : 0;
|
|
1044
|
+
this.compactionEvents.push({
|
|
1045
|
+
timestamp: new Date(timestamp),
|
|
1046
|
+
contextBefore,
|
|
1047
|
+
contextAfter,
|
|
1048
|
+
tokensReclaimed: Math.max(0, contextBefore - contextAfter),
|
|
1049
|
+
source: hasReported ? 'reported' : 'heuristic',
|
|
1050
|
+
});
|
|
1051
|
+
if (hasReported) {
|
|
1052
|
+
this.previousContextSize = contextAfter;
|
|
1053
|
+
this.currentContextSize = contextAfter;
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
this.pendingHeuristicCompactionIndex = this.compactionEvents.length - 1;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Fills a pending heuristic compaction with the first observed
|
|
1061
|
+
* post-compaction context size. Returns true when the observed drop was
|
|
1062
|
+
* consumed by that pending event, so the caller must not record it again.
|
|
1063
|
+
*/
|
|
1064
|
+
resolvePendingHeuristicCompaction(contextSize) {
|
|
1065
|
+
if (this.pendingHeuristicCompactionIndex === null)
|
|
1066
|
+
return false;
|
|
1067
|
+
const compaction = this.compactionEvents[this.pendingHeuristicCompactionIndex];
|
|
1068
|
+
this.pendingHeuristicCompactionIndex = null;
|
|
1069
|
+
if (!compaction)
|
|
1070
|
+
return false;
|
|
1071
|
+
if (contextSize < compaction.contextBefore) {
|
|
1072
|
+
compaction.contextAfter = contextSize;
|
|
1073
|
+
compaction.tokensReclaimed = compaction.contextBefore - contextSize;
|
|
1074
|
+
}
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
939
1077
|
// ═══════════════════════════════════════════════════════════════════════
|
|
940
1078
|
// Private: Task State from SessionEvent
|
|
941
1079
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -962,6 +1100,7 @@ class EventAggregator {
|
|
|
962
1100
|
}
|
|
963
1101
|
else if (name === 'TaskUpdate') {
|
|
964
1102
|
this.applyTaskUpdate(input);
|
|
1103
|
+
this.syncPlanStepFromTaskUpdate(input, new Date(event.timestamp));
|
|
965
1104
|
}
|
|
966
1105
|
else if (name === 'TodoWrite') {
|
|
967
1106
|
this.handleTodoWrite(input, new Date(event.timestamp));
|
|
@@ -1005,6 +1144,7 @@ class EventAggregator {
|
|
|
1005
1144
|
}
|
|
1006
1145
|
else if (event.toolName === 'TaskUpdate') {
|
|
1007
1146
|
this.applyTaskUpdate(input);
|
|
1147
|
+
this.syncPlanStepFromTaskUpdate(input, new Date(event.timestamp));
|
|
1008
1148
|
}
|
|
1009
1149
|
else if (event.toolName === 'TodoWrite') {
|
|
1010
1150
|
this.handleTodoWrite(input, new Date(event.timestamp));
|
|
@@ -1102,6 +1242,36 @@ class EventAggregator {
|
|
|
1102
1242
|
}
|
|
1103
1243
|
}
|
|
1104
1244
|
}
|
|
1245
|
+
syncPlanStepFromTaskUpdate(input, now) {
|
|
1246
|
+
if (!this.planState || !input.status)
|
|
1247
|
+
return;
|
|
1248
|
+
const taskId = String(input.taskId ?? input.task_id ?? '');
|
|
1249
|
+
const stepId = taskId.startsWith('plan-') ? `step-${taskId.slice(5)}` : taskId;
|
|
1250
|
+
const step = this.planState.steps.find((candidate) => candidate.id === stepId);
|
|
1251
|
+
if (!step)
|
|
1252
|
+
return;
|
|
1253
|
+
const rawStatus = String(input.status).toLowerCase();
|
|
1254
|
+
const status = rawStatus === 'completed'
|
|
1255
|
+
? 'completed'
|
|
1256
|
+
: rawStatus === 'in_progress' || rawStatus === 'in-progress'
|
|
1257
|
+
? 'in_progress'
|
|
1258
|
+
: rawStatus === 'deleted'
|
|
1259
|
+
? 'skipped'
|
|
1260
|
+
: 'pending';
|
|
1261
|
+
if (status === 'in_progress' && step.status !== 'in_progress') {
|
|
1262
|
+
step.startedAt = now.toISOString();
|
|
1263
|
+
}
|
|
1264
|
+
if (status === 'completed' && step.status !== status) {
|
|
1265
|
+
step.startedAt ??= now.toISOString();
|
|
1266
|
+
step.completedAt = now.toISOString();
|
|
1267
|
+
step.durationMs = Math.max(0, now.getTime() - new Date(step.startedAt).getTime());
|
|
1268
|
+
}
|
|
1269
|
+
step.status = status;
|
|
1270
|
+
const completed = this.planState.steps.filter((candidate) => candidate.status === 'completed');
|
|
1271
|
+
this.planState.completionRate =
|
|
1272
|
+
this.planState.steps.length > 0 ? completed.length / this.planState.steps.length : 0;
|
|
1273
|
+
this.planState.active = this.planState.steps.some((candidate) => candidate.status === 'pending' || candidate.status === 'in_progress');
|
|
1274
|
+
}
|
|
1105
1275
|
resolveTaskCreate(toolUseId, content, timestamp, isError) {
|
|
1106
1276
|
const pending = this.pendingTaskCreates.get(toolUseId);
|
|
1107
1277
|
if (!pending)
|
|
@@ -1408,27 +1578,83 @@ class EventAggregator {
|
|
|
1408
1578
|
const providerId = this._providerId ?? 'claude-code';
|
|
1409
1579
|
const followEvents = (0, eventBridge_1.toFollowEvents)(event, providerId);
|
|
1410
1580
|
for (const fe of followEvents) {
|
|
1411
|
-
this.
|
|
1581
|
+
this.processPlanFollowEvent(fe);
|
|
1412
1582
|
}
|
|
1413
1583
|
}
|
|
1414
|
-
|
|
1584
|
+
processPlanFollowEvent(event) {
|
|
1585
|
+
const now = new Date(event.timestamp);
|
|
1586
|
+
if (event.type === 'tool_use' && event.toolName === 'EnterPlanMode') {
|
|
1587
|
+
this.planEnteredAt = now;
|
|
1588
|
+
this.planState = {
|
|
1589
|
+
active: true,
|
|
1590
|
+
steps: [],
|
|
1591
|
+
enteredAt: now,
|
|
1592
|
+
source: event.providerId,
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
const updated = this.planExtractor.processEvent(event);
|
|
1596
|
+
if (updated && this.planExtractor.plan) {
|
|
1597
|
+
this.planState = this.convertExtractedPlanToPlanState(this.planExtractor.plan, now);
|
|
1598
|
+
}
|
|
1599
|
+
if (event.type === 'tool_use' && event.toolName === 'ExitPlanMode' && this.planState) {
|
|
1600
|
+
this.planState.exitedAt = now;
|
|
1601
|
+
if (this.planState.enteredAt) {
|
|
1602
|
+
this.planState.totalDurationMs = now.getTime() - this.planState.enteredAt.getTime();
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
convertExtractedPlanToPlanState(extracted, now) {
|
|
1607
|
+
const previous = this.planState;
|
|
1608
|
+
const previousSteps = new Map((previous?.steps ?? []).map((step) => [`${step.id}\u0000${step.description}`, step]));
|
|
1415
1609
|
const completed = extracted.steps.filter((s) => s.status === 'completed').length;
|
|
1416
1610
|
const total = extracted.steps.length;
|
|
1611
|
+
const steps = extracted.steps.map((step) => {
|
|
1612
|
+
const old = previousSteps.get(`${step.id}\u0000${step.description}`);
|
|
1613
|
+
const merged = { ...step, ...old, status: step.status };
|
|
1614
|
+
if (step.status === 'in_progress' && old?.status !== 'in_progress' && !merged.startedAt) {
|
|
1615
|
+
merged.startedAt = now.toISOString();
|
|
1616
|
+
}
|
|
1617
|
+
if ((step.status === 'completed' || step.status === 'failed') &&
|
|
1618
|
+
old?.status !== step.status &&
|
|
1619
|
+
!merged.completedAt) {
|
|
1620
|
+
merged.startedAt ??= previous?.enteredAt?.toISOString() ?? now.toISOString();
|
|
1621
|
+
merged.completedAt = now.toISOString();
|
|
1622
|
+
merged.durationMs = Math.max(0, now.getTime() - new Date(merged.startedAt).getTime());
|
|
1623
|
+
}
|
|
1624
|
+
return merged;
|
|
1625
|
+
});
|
|
1417
1626
|
return {
|
|
1418
|
-
active:
|
|
1419
|
-
steps
|
|
1420
|
-
id: s.id,
|
|
1421
|
-
description: s.description,
|
|
1422
|
-
status: s.status,
|
|
1423
|
-
phase: s.phase,
|
|
1424
|
-
complexity: s.complexity,
|
|
1425
|
-
})),
|
|
1627
|
+
active: steps.some((step) => step.status === 'pending' || step.status === 'in_progress'),
|
|
1628
|
+
steps,
|
|
1426
1629
|
title: extracted.title,
|
|
1427
1630
|
source: extracted.source,
|
|
1428
1631
|
completionRate: total > 0 ? completed / total : 0,
|
|
1429
1632
|
rawMarkdown: extracted.rawMarkdown,
|
|
1633
|
+
enteredAt: previous?.enteredAt ?? this.planEnteredAt ?? now,
|
|
1634
|
+
exitedAt: previous?.exitedAt,
|
|
1635
|
+
totalDurationMs: previous?.totalDurationMs,
|
|
1636
|
+
prompt: previous?.prompt,
|
|
1637
|
+
revision: previous?.revision,
|
|
1430
1638
|
};
|
|
1431
1639
|
}
|
|
1640
|
+
getActivePlanStep() {
|
|
1641
|
+
return this.planState?.steps.find((step) => step.status === 'in_progress');
|
|
1642
|
+
}
|
|
1643
|
+
attributePlanUsage(tokens, costUsd) {
|
|
1644
|
+
const step = this.getActivePlanStep();
|
|
1645
|
+
if (!step)
|
|
1646
|
+
return;
|
|
1647
|
+
step.tokensUsed = (step.tokensUsed ?? 0) + tokens;
|
|
1648
|
+
if (costUsd > 0)
|
|
1649
|
+
step.costUsd = (step.costUsd ?? 0) + costUsd;
|
|
1650
|
+
}
|
|
1651
|
+
attributePlanToolCall(toolName) {
|
|
1652
|
+
if (['EnterPlanMode', 'ExitPlanMode', 'UpdatePlan', 'TaskUpdate'].includes(toolName))
|
|
1653
|
+
return;
|
|
1654
|
+
const step = this.getActivePlanStep();
|
|
1655
|
+
if (step)
|
|
1656
|
+
step.toolCalls = (step.toolCalls ?? 0) + 1;
|
|
1657
|
+
}
|
|
1432
1658
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1433
1659
|
// Private: Context Attribution from SessionEvent
|
|
1434
1660
|
// ═══════════════════════════════════════════════════════════════════════
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module aggregation/snapshot
|
|
9
9
|
*/
|
|
10
|
-
import type
|
|
10
|
+
import { type SerializedAggregatorState } from './EventAggregator';
|
|
11
11
|
/** Snapshot sidecar file content. */
|
|
12
12
|
export interface SessionSnapshot {
|
|
13
13
|
/** Schema version for forward-compatibility checks. */
|
|
@@ -50,8 +50,9 @@ exports.isSnapshotValid = isSnapshotValid;
|
|
|
50
50
|
const fs = __importStar(require("fs"));
|
|
51
51
|
const path = __importStar(require("path"));
|
|
52
52
|
const paths_1 = require("../paths");
|
|
53
|
+
const EventAggregator_1 = require("./EventAggregator");
|
|
53
54
|
/** Current schema version — bump when format changes. */
|
|
54
|
-
const SNAPSHOT_VERSION =
|
|
55
|
+
const SNAPSHOT_VERSION = EventAggregator_1.SNAPSHOT_SCHEMA_VERSION;
|
|
55
56
|
/** Returns the directory for snapshot files. */
|
|
56
57
|
function getSnapshotsDir() {
|
|
57
58
|
return path.join((0, paths_1.getConfigDir)(), 'snapshots');
|
|
@@ -2,14 +2,37 @@
|
|
|
2
2
|
* Aggregated metrics output types for EventAggregator.
|
|
3
3
|
* Re-exports canonical types from sessionEvent and defines aggregation-specific shapes.
|
|
4
4
|
*/
|
|
5
|
-
export type { SessionEvent,
|
|
5
|
+
export type { SessionEvent, MessageUsage, TokenUsage, ToolCall, ToolAnalytics, TimelineEvent, PendingToolCall, TrackedTask, TaskState, TaskStatus, SubagentStats, PlanState, PlanStep, CompactionEvent, TruncationEvent, ContextAttribution, TurnAttribution, ContextSizePoint, PendingUserRequest, ResponseLatency, LatencyStats, SessionStats, PermissionMode, PermissionModeChange, } from '../types/sessionEvent';
|
|
6
6
|
import type { ContextAttribution, ContextSizePoint, CompactionEvent, TruncationEvent, ToolAnalytics, TimelineEvent, TaskState, PlanState, LatencyStats, PermissionMode, PermissionModeChange } from '../types/sessionEvent';
|
|
7
|
+
import type { ErrorCategory } from '../extractors/errorTaxonomy';
|
|
8
|
+
export interface ErrorRollupEntry {
|
|
9
|
+
tool: string;
|
|
10
|
+
category: ErrorCategory;
|
|
11
|
+
count: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ErrorBucketMetric extends ErrorRollupEntry {
|
|
14
|
+
/** ISO hour bucket, e.g. 2026-07-18T14. */
|
|
15
|
+
hour: string;
|
|
16
|
+
model: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ErrorRollup {
|
|
19
|
+
totalFailures: number;
|
|
20
|
+
byToolCategory: ErrorRollupEntry[];
|
|
21
|
+
byHourModel: ErrorBucketMetric[];
|
|
22
|
+
retryAttempts: number;
|
|
23
|
+
finishReasons: Array<{
|
|
24
|
+
reason: string;
|
|
25
|
+
count: number;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
7
28
|
/** Configuration options for EventAggregator. */
|
|
8
29
|
export interface EventAggregatorOptions {
|
|
9
30
|
/** Maximum timeline events to retain (default 200). */
|
|
10
31
|
timelineCap?: number;
|
|
11
32
|
/** Maximum latency records to retain (default 100). */
|
|
12
33
|
latencyCap?: number;
|
|
34
|
+
/** Maximum context-size points to retain (default 2000). */
|
|
35
|
+
contextTimelineCap?: number;
|
|
13
36
|
/** Burn rate sliding window in ms (default 5 * 60_000). */
|
|
14
37
|
burnWindowMs?: number;
|
|
15
38
|
/** Burn rate sample interval in ms (default 10_000). */
|
|
@@ -105,6 +128,7 @@ export interface AggregatedMetrics {
|
|
|
105
128
|
truncationCount: number;
|
|
106
129
|
truncationEvents: TruncationEvent[];
|
|
107
130
|
toolStats: ToolAnalytics[];
|
|
131
|
+
errorRollup: ErrorRollup;
|
|
108
132
|
burnRate: BurnRateInfo;
|
|
109
133
|
taskState: TaskState;
|
|
110
134
|
subagents: SubagentLifecycle[];
|