idletime 0.1.3 → 0.2.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/README.md +70 -3
- package/dist/idletime.js +1820 -864
- package/package.json +1 -1
package/dist/idletime.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "idletime",
|
|
5
|
-
version: "0.
|
|
5
|
+
version: "0.2.0",
|
|
6
6
|
description: "Visual CLI for Codex focus, token burn, spikes, and idle time from local session logs.",
|
|
7
7
|
author: "ParkerRex",
|
|
8
8
|
main: "./dist/idletime.js",
|
|
@@ -64,6 +64,211 @@ var package_default = {
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
// src/reporting/types.ts
|
|
68
|
+
var jsonReportSchemaVersion = 1;
|
|
69
|
+
|
|
70
|
+
// src/reporting/serialize-json-common.ts
|
|
71
|
+
function stringifyJsonSnapshot(snapshot) {
|
|
72
|
+
return `${JSON.stringify(snapshot, null, 2)}
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
function serializeIsoTimestamp(timestamp) {
|
|
76
|
+
return timestamp.toISOString();
|
|
77
|
+
}
|
|
78
|
+
function serializeTimeInterval(interval) {
|
|
79
|
+
return {
|
|
80
|
+
start: serializeIsoTimestamp(interval.start),
|
|
81
|
+
end: serializeIsoTimestamp(interval.end)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function serializeReportWindow(window) {
|
|
85
|
+
return {
|
|
86
|
+
label: window.label,
|
|
87
|
+
start: serializeIsoTimestamp(window.start),
|
|
88
|
+
end: serializeIsoTimestamp(window.end),
|
|
89
|
+
timeZone: window.timeZone
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function serializeSessionFilters(filters) {
|
|
93
|
+
return {
|
|
94
|
+
workspaceOnlyPrefix: filters.workspaceOnlyPrefix,
|
|
95
|
+
sessionKind: filters.sessionKind,
|
|
96
|
+
model: filters.model,
|
|
97
|
+
reasoningEffort: filters.reasoningEffort
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function serializeActivityMetrics(metrics) {
|
|
101
|
+
return {
|
|
102
|
+
strictEngagementBlocks: metrics.strictEngagementBlocks.map(serializeTimeInterval),
|
|
103
|
+
directActivityBlocks: metrics.directActivityBlocks.map(serializeTimeInterval),
|
|
104
|
+
agentCoverageBlocks: metrics.agentCoverageBlocks.map(serializeTimeInterval),
|
|
105
|
+
agentOnlyBlocks: metrics.agentOnlyBlocks.map(serializeTimeInterval),
|
|
106
|
+
perAgentTaskBlocks: metrics.perAgentTaskBlocks.map((taskBlocks) => taskBlocks.map(serializeTimeInterval)),
|
|
107
|
+
strictEngagementMs: metrics.strictEngagementMs,
|
|
108
|
+
directActivityMs: metrics.directActivityMs,
|
|
109
|
+
agentCoverageMs: metrics.agentCoverageMs,
|
|
110
|
+
agentOnlyMs: metrics.agentOnlyMs,
|
|
111
|
+
cumulativeAgentMs: metrics.cumulativeAgentMs,
|
|
112
|
+
peakConcurrentAgents: metrics.peakConcurrentAgents
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function serializeWakeWindowSummary(wakeWindowSummary) {
|
|
116
|
+
return {
|
|
117
|
+
wakeDurationMs: wakeWindowSummary.wakeDurationMs,
|
|
118
|
+
strictEngagementMs: wakeWindowSummary.strictEngagementMs,
|
|
119
|
+
directActivityMs: wakeWindowSummary.directActivityMs,
|
|
120
|
+
agentOnlyMs: wakeWindowSummary.agentOnlyMs,
|
|
121
|
+
awakeIdleMs: wakeWindowSummary.awakeIdleMs,
|
|
122
|
+
awakeIdlePercentage: wakeWindowSummary.awakeIdlePercentage,
|
|
123
|
+
longestIdleGapMs: wakeWindowSummary.longestIdleGapMs
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/reporting/serialize-hourly-report.ts
|
|
128
|
+
function serializeHourlyReportPayload(hourlyReport) {
|
|
129
|
+
return {
|
|
130
|
+
appliedFilters: serializeSessionFilters(hourlyReport.appliedFilters),
|
|
131
|
+
agentConcurrencySource: hourlyReport.agentConcurrencySource,
|
|
132
|
+
buckets: hourlyReport.buckets.map(serializeHourlyBucket),
|
|
133
|
+
hasWakeWindow: hourlyReport.hasWakeWindow,
|
|
134
|
+
idleCutoffMs: hourlyReport.idleCutoffMs,
|
|
135
|
+
maxValues: {
|
|
136
|
+
agentOnlyMs: hourlyReport.maxValues.agentOnlyMs,
|
|
137
|
+
directActivityMs: hourlyReport.maxValues.directActivityMs,
|
|
138
|
+
engagedMs: hourlyReport.maxValues.engagedMs,
|
|
139
|
+
practicalBurn: hourlyReport.maxValues.practicalBurn
|
|
140
|
+
},
|
|
141
|
+
window: serializeReportWindow(hourlyReport.window)
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function serializeHourlySnapshot(input) {
|
|
145
|
+
const snapshot = {
|
|
146
|
+
schemaVersion: jsonReportSchemaVersion,
|
|
147
|
+
mode: "hourly",
|
|
148
|
+
generatedAt: input.generatedAt.toISOString(),
|
|
149
|
+
command: {
|
|
150
|
+
idleCutoffMs: input.command.idleCutoffMs,
|
|
151
|
+
filters: serializeSessionFilters(input.command.filters),
|
|
152
|
+
wakeWindow: input.command.wakeWindow ? { ...input.command.wakeWindow } : null
|
|
153
|
+
},
|
|
154
|
+
hourlyReport: serializeHourlyReportPayload(input.hourlyReport)
|
|
155
|
+
};
|
|
156
|
+
return stringifyJsonSnapshot(snapshot);
|
|
157
|
+
}
|
|
158
|
+
function serializeHourlyBucket(bucket) {
|
|
159
|
+
return {
|
|
160
|
+
start: serializeTimeInterval({
|
|
161
|
+
start: bucket.start,
|
|
162
|
+
end: bucket.end
|
|
163
|
+
}).start,
|
|
164
|
+
end: serializeTimeInterval({
|
|
165
|
+
start: bucket.start,
|
|
166
|
+
end: bucket.end
|
|
167
|
+
}).end,
|
|
168
|
+
agentOnlyMs: bucket.agentOnlyMs,
|
|
169
|
+
awakeIdleMs: bucket.awakeIdleMs,
|
|
170
|
+
directActivityMs: bucket.directActivityMs,
|
|
171
|
+
engagedMs: bucket.engagedMs,
|
|
172
|
+
peakConcurrentAgents: bucket.peakConcurrentAgents,
|
|
173
|
+
practicalBurn: bucket.practicalBurn,
|
|
174
|
+
rawTotalTokens: bucket.rawTotalTokens,
|
|
175
|
+
sessionCount: bucket.sessionCount
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/reporting/serialize-live-report.ts
|
|
180
|
+
function serializeLiveReportPayload(liveReport) {
|
|
181
|
+
return {
|
|
182
|
+
appliedFilters: serializeSessionFilters(liveReport.appliedFilters),
|
|
183
|
+
doneRecentCount: liveReport.doneRecentCount,
|
|
184
|
+
doneRecentWindowMs: liveReport.doneRecentWindowMs,
|
|
185
|
+
doneThisTurnCount: liveReport.doneThisTurnCount,
|
|
186
|
+
observedAt: serializeIsoTimestamp(liveReport.observedAt),
|
|
187
|
+
peakTodayCount: liveReport.peakTodayCount,
|
|
188
|
+
recentConcurrencyValues: [...liveReport.recentConcurrencyValues],
|
|
189
|
+
runningCount: liveReport.runningCount,
|
|
190
|
+
runningLocations: liveReport.runningLocations.map((location) => ({
|
|
191
|
+
cwd: location.cwd,
|
|
192
|
+
runningCount: location.runningCount
|
|
193
|
+
})),
|
|
194
|
+
waitingThreads: liveReport.waitingThreads.map((waitingThread) => ({
|
|
195
|
+
cwd: waitingThread.cwd,
|
|
196
|
+
sessionId: waitingThread.sessionId,
|
|
197
|
+
waitDurationMs: waitingThread.waitDurationMs
|
|
198
|
+
})),
|
|
199
|
+
waitingOnUserCount: liveReport.waitingOnUserCount,
|
|
200
|
+
waitingOnUserLocations: liveReport.waitingOnUserLocations.map((location) => ({
|
|
201
|
+
cwd: location.cwd,
|
|
202
|
+
waitingCount: location.waitingCount
|
|
203
|
+
})),
|
|
204
|
+
scope: liveReport.scope,
|
|
205
|
+
workspacePrefix: liveReport.workspacePrefix
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function serializeLiveSnapshot(input) {
|
|
209
|
+
const snapshot = {
|
|
210
|
+
schemaVersion: jsonReportSchemaVersion,
|
|
211
|
+
mode: "live",
|
|
212
|
+
generatedAt: input.generatedAt.toISOString(),
|
|
213
|
+
command: {
|
|
214
|
+
filters: serializeSessionFilters(input.command.filters)
|
|
215
|
+
},
|
|
216
|
+
liveReport: serializeLiveReportPayload(input.liveReport)
|
|
217
|
+
};
|
|
218
|
+
return stringifyJsonSnapshot(snapshot);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// src/reporting/serialize-summary-report.ts
|
|
222
|
+
function serializeSummarySnapshot(input) {
|
|
223
|
+
const snapshot = {
|
|
224
|
+
schemaVersion: jsonReportSchemaVersion,
|
|
225
|
+
mode: input.mode,
|
|
226
|
+
generatedAt: input.generatedAt.toISOString(),
|
|
227
|
+
command: {
|
|
228
|
+
idleCutoffMs: input.command.idleCutoffMs,
|
|
229
|
+
filters: serializeSessionFilters(input.command.filters),
|
|
230
|
+
groupBy: [...input.command.groupBy],
|
|
231
|
+
wakeWindow: input.command.wakeWindow ? { ...input.command.wakeWindow } : null
|
|
232
|
+
},
|
|
233
|
+
hourlyReport: input.hourlyReport ? serializeHourlyReportPayload(input.hourlyReport) : null,
|
|
234
|
+
summaryReport: serializeSummaryReportPayload(input.summaryReport)
|
|
235
|
+
};
|
|
236
|
+
return stringifyJsonSnapshot(snapshot);
|
|
237
|
+
}
|
|
238
|
+
function serializeSummaryReportPayload(summaryReport) {
|
|
239
|
+
return {
|
|
240
|
+
activityWindow: summaryReport.activityWindow ? serializeTimeInterval(summaryReport.activityWindow) : null,
|
|
241
|
+
appliedFilters: serializeSessionFilters(summaryReport.appliedFilters),
|
|
242
|
+
comparisonCutoffMs: summaryReport.comparisonCutoffMs,
|
|
243
|
+
comparisonMetrics: serializeActivityMetrics(summaryReport.comparisonMetrics),
|
|
244
|
+
directTokenTotals: { ...summaryReport.directTokenTotals },
|
|
245
|
+
groupBreakdowns: summaryReport.groupBreakdowns.map(serializeSummaryBreakdown),
|
|
246
|
+
idleCutoffMs: summaryReport.idleCutoffMs,
|
|
247
|
+
metrics: serializeActivityMetrics(summaryReport.metrics),
|
|
248
|
+
sessionCounts: { ...summaryReport.sessionCounts },
|
|
249
|
+
tokenTotals: { ...summaryReport.tokenTotals },
|
|
250
|
+
wakeSummary: summaryReport.wakeSummary ? serializeWakeWindowSummary(summaryReport.wakeSummary) : null,
|
|
251
|
+
window: serializeReportWindow(summaryReport.window)
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function serializeSummaryBreakdown(breakdown) {
|
|
255
|
+
return {
|
|
256
|
+
dimension: breakdown.dimension,
|
|
257
|
+
rows: breakdown.rows.map(serializeSummaryBreakdownRow)
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function serializeSummaryBreakdownRow(row) {
|
|
261
|
+
return {
|
|
262
|
+
key: row.key,
|
|
263
|
+
sessionCount: row.sessionCount,
|
|
264
|
+
directActivityMs: row.directActivityMs,
|
|
265
|
+
agentCoverageMs: row.agentCoverageMs,
|
|
266
|
+
cumulativeAgentMs: row.cumulativeAgentMs,
|
|
267
|
+
practicalBurn: row.practicalBurn,
|
|
268
|
+
rawTotalTokens: row.rawTotalTokens
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
67
272
|
// src/report-window/parse-duration.ts
|
|
68
273
|
var durationPattern = /^(\d+)(m|h|d)$/;
|
|
69
274
|
function parseDurationToMs(durationText) {
|
|
@@ -313,6 +518,7 @@ function parseIdletimeCommand(argv) {
|
|
|
313
518
|
let helpRequested = false;
|
|
314
519
|
let hourlyWindowMs = defaultWindowMs;
|
|
315
520
|
let idleCutoffMs = defaultIdleCutoffMs;
|
|
521
|
+
let outputFormat = "text";
|
|
316
522
|
let shareMode = false;
|
|
317
523
|
let versionRequested = false;
|
|
318
524
|
let wakeWindow = null;
|
|
@@ -329,6 +535,10 @@ function parseIdletimeCommand(argv) {
|
|
|
329
535
|
versionRequested = true;
|
|
330
536
|
continue;
|
|
331
537
|
}
|
|
538
|
+
if (argument === "--json") {
|
|
539
|
+
outputFormat = "json";
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
332
542
|
if (argument === "--window") {
|
|
333
543
|
hourlyWindowMs = parseDurationToMs(readFlagValue(argument, args));
|
|
334
544
|
continue;
|
|
@@ -341,6 +551,10 @@ function parseIdletimeCommand(argv) {
|
|
|
341
551
|
filters.workspaceOnlyPrefix = readFlagValue(argument, args);
|
|
342
552
|
continue;
|
|
343
553
|
}
|
|
554
|
+
if (argument === "--global") {
|
|
555
|
+
filters.workspaceOnlyPrefix = null;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
344
558
|
if (argument === "--session-kind") {
|
|
345
559
|
filters.sessionKind = parseSessionKind(readFlagValue(argument, args));
|
|
346
560
|
continue;
|
|
@@ -373,6 +587,18 @@ function parseIdletimeCommand(argv) {
|
|
|
373
587
|
}
|
|
374
588
|
throw new Error(`Unknown argument "${argument}".`);
|
|
375
589
|
}
|
|
590
|
+
if (!helpRequested && !versionRequested) {
|
|
591
|
+
validateParsedCommand({
|
|
592
|
+
commandName,
|
|
593
|
+
filters,
|
|
594
|
+
groupBy,
|
|
595
|
+
hourlyWindowMs,
|
|
596
|
+
idleCutoffMs,
|
|
597
|
+
outputFormat,
|
|
598
|
+
shareMode,
|
|
599
|
+
wakeWindow
|
|
600
|
+
});
|
|
601
|
+
}
|
|
376
602
|
return {
|
|
377
603
|
commandName,
|
|
378
604
|
filters,
|
|
@@ -380,6 +606,7 @@ function parseIdletimeCommand(argv) {
|
|
|
380
606
|
helpRequested,
|
|
381
607
|
hourlyWindowMs,
|
|
382
608
|
idleCutoffMs,
|
|
609
|
+
outputFormat,
|
|
383
610
|
shareMode,
|
|
384
611
|
versionRequested,
|
|
385
612
|
wakeWindow
|
|
@@ -391,13 +618,15 @@ function renderHelpText() {
|
|
|
391
618
|
"Track Codex focus, activity, idle time, and token burn from local session logs.",
|
|
392
619
|
"",
|
|
393
620
|
"Usage:",
|
|
394
|
-
" idletime [last24h|today|hourly] [options]",
|
|
395
|
-
" inside this repo: bun run idletime [last24h|today|hourly] [options]",
|
|
621
|
+
" idletime [last24h|today|hourly|live|refresh-bests] [options]",
|
|
622
|
+
" inside this repo: bun run idletime [last24h|today|hourly|live|refresh-bests] [options]",
|
|
396
623
|
"",
|
|
397
624
|
"Modes:",
|
|
398
625
|
" last24h default. visual trailing-24h dashboard with rhythm, spikes, and stats",
|
|
399
626
|
" today local-midnight-to-now summary for the current day",
|
|
400
627
|
" hourly trailing-window chart plus the detailed per-hour table",
|
|
628
|
+
" live repainting task scoreboard; global by default",
|
|
629
|
+
" refresh-bests full-history best-metrics refresh; updates BEST records",
|
|
401
630
|
"",
|
|
402
631
|
"How To Read The Dashboard:",
|
|
403
632
|
" focus strict engagement inferred from actual user_message arrivals",
|
|
@@ -408,7 +637,9 @@ function renderHelpText() {
|
|
|
408
637
|
"",
|
|
409
638
|
"Options:",
|
|
410
639
|
" --window <24h> trailing window for hourly or last24h",
|
|
640
|
+
" --json print a machine-readable JSON snapshot",
|
|
411
641
|
" --idle-cutoff <15m> how long activity stays live after the last event",
|
|
642
|
+
" --global clear workspace scoping and read all sessions",
|
|
412
643
|
" --workspace-only <dir> include only sessions whose cwd starts with this path",
|
|
413
644
|
" --session-kind <kind> direct or subagent",
|
|
414
645
|
" --model <name> include only one primary model",
|
|
@@ -424,12 +655,59 @@ function renderHelpText() {
|
|
|
424
655
|
" idletime --wake 07:45-23:30 --share",
|
|
425
656
|
" idletime today --workspace-only /path/to/demo-workspace",
|
|
426
657
|
" idletime hourly --window 24h --workspace-only /path/to/demo-workspace",
|
|
658
|
+
" idletime live",
|
|
659
|
+
" idletime live --workspace-only /path/to/demo-workspace",
|
|
660
|
+
" idletime --json",
|
|
661
|
+
" idletime live --json",
|
|
662
|
+
" idletime refresh-bests",
|
|
427
663
|
" idletime --version"
|
|
428
664
|
].join(`
|
|
429
665
|
`);
|
|
430
666
|
}
|
|
431
667
|
function isCommandName(value) {
|
|
432
|
-
return value === "hourly" || value === "last24h" || value === "today";
|
|
668
|
+
return value === "hourly" || value === "last24h" || value === "live" || value === "refresh-bests" || value === "today";
|
|
669
|
+
}
|
|
670
|
+
function validateParsedCommand(input) {
|
|
671
|
+
if (input.outputFormat === "json" && input.shareMode) {
|
|
672
|
+
throw new Error("--share is only supported for human-readable output.");
|
|
673
|
+
}
|
|
674
|
+
if (input.commandName !== "refresh-bests") {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const unsupportedFlags = [];
|
|
678
|
+
if (input.outputFormat !== "text") {
|
|
679
|
+
unsupportedFlags.push("--json");
|
|
680
|
+
}
|
|
681
|
+
if (input.shareMode) {
|
|
682
|
+
unsupportedFlags.push("--share");
|
|
683
|
+
}
|
|
684
|
+
if (input.hourlyWindowMs !== defaultWindowMs) {
|
|
685
|
+
unsupportedFlags.push("--window");
|
|
686
|
+
}
|
|
687
|
+
if (input.idleCutoffMs !== defaultIdleCutoffMs) {
|
|
688
|
+
unsupportedFlags.push("--idle-cutoff");
|
|
689
|
+
}
|
|
690
|
+
if (input.wakeWindow) {
|
|
691
|
+
unsupportedFlags.push("--wake");
|
|
692
|
+
}
|
|
693
|
+
if (input.groupBy.length > 0) {
|
|
694
|
+
unsupportedFlags.push("--group-by");
|
|
695
|
+
}
|
|
696
|
+
if (input.filters.workspaceOnlyPrefix) {
|
|
697
|
+
unsupportedFlags.push("--workspace-only");
|
|
698
|
+
}
|
|
699
|
+
if (input.filters.sessionKind) {
|
|
700
|
+
unsupportedFlags.push("--session-kind");
|
|
701
|
+
}
|
|
702
|
+
if (input.filters.model) {
|
|
703
|
+
unsupportedFlags.push("--model");
|
|
704
|
+
}
|
|
705
|
+
if (input.filters.reasoningEffort) {
|
|
706
|
+
unsupportedFlags.push("--effort");
|
|
707
|
+
}
|
|
708
|
+
if (unsupportedFlags.length > 0) {
|
|
709
|
+
throw new Error(`refresh-bests does not support ${unsupportedFlags.join(", ")}.`);
|
|
710
|
+
}
|
|
433
711
|
}
|
|
434
712
|
function parseSessionKind(sessionKindText) {
|
|
435
713
|
if (sessionKindText === "direct" || sessionKindText === "subagent") {
|
|
@@ -674,6 +952,145 @@ function resolvePrimaryReasoningEffort(turnAttributions) {
|
|
|
674
952
|
return null;
|
|
675
953
|
}
|
|
676
954
|
|
|
955
|
+
// src/codex-session-log/task-windows.ts
|
|
956
|
+
var defaultTaskWindowStaleAfterMs = 5 * 60000;
|
|
957
|
+
var standardTaskWindowStaleAfterMs = 2 * 60000;
|
|
958
|
+
function extractTaskWindows(records, options) {
|
|
959
|
+
const turnContexts = new Map;
|
|
960
|
+
const activeTaskWindows = new Map;
|
|
961
|
+
const orderedTaskWindows = [];
|
|
962
|
+
let currentTurnId = null;
|
|
963
|
+
for (const record of records) {
|
|
964
|
+
if (record.type === "session_meta") {
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (record.type === "turn_context") {
|
|
968
|
+
const payload2 = expectObject(record.payload, "turn_context.payload");
|
|
969
|
+
const turnId = readString(payload2, "turn_id", "turn_context.payload");
|
|
970
|
+
const turnContext = {
|
|
971
|
+
cwd: readString(payload2, "cwd", "turn_context.payload"),
|
|
972
|
+
model: readOptionalString(payload2, "model"),
|
|
973
|
+
reasoningEffort: readOptionalString(payload2, "effort")
|
|
974
|
+
};
|
|
975
|
+
turnContexts.set(turnId, turnContext);
|
|
976
|
+
const activeTaskWindow = activeTaskWindows.get(turnId);
|
|
977
|
+
if (activeTaskWindow) {
|
|
978
|
+
activeTaskWindow.cwd = turnContext.cwd;
|
|
979
|
+
activeTaskWindow.model = turnContext.model;
|
|
980
|
+
activeTaskWindow.reasoningEffort = turnContext.reasoningEffort;
|
|
981
|
+
activeTaskWindow.lastActivityAt = record.timestamp;
|
|
982
|
+
currentTurnId = turnId;
|
|
983
|
+
}
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (record.type !== "event_msg") {
|
|
987
|
+
touchCurrentTaskWindow(activeTaskWindows, currentTurnId, record.timestamp);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
const payload = expectObject(record.payload, "event_msg.payload");
|
|
991
|
+
const eventType = readOptionalString(payload, "type");
|
|
992
|
+
if (eventType === "task_started") {
|
|
993
|
+
const turnId = readString(payload, "turn_id", "event_msg.payload");
|
|
994
|
+
const turnContext = turnContexts.get(turnId);
|
|
995
|
+
const taskWindow = {
|
|
996
|
+
completedAt: null,
|
|
997
|
+
cwd: turnContext?.cwd ?? options.cwd,
|
|
998
|
+
lastActivityAt: record.timestamp,
|
|
999
|
+
model: turnContext?.model ?? null,
|
|
1000
|
+
reasoningEffort: turnContext?.reasoningEffort ?? null,
|
|
1001
|
+
startedAt: record.timestamp,
|
|
1002
|
+
turnId
|
|
1003
|
+
};
|
|
1004
|
+
activeTaskWindows.set(turnId, taskWindow);
|
|
1005
|
+
orderedTaskWindows.push(taskWindow);
|
|
1006
|
+
currentTurnId = turnId;
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
if (eventType === "task_complete") {
|
|
1010
|
+
const turnId = readString(payload, "turn_id", "event_msg.payload");
|
|
1011
|
+
const activeTaskWindow = activeTaskWindows.get(turnId) ?? createFallbackTaskWindow(record.timestamp, turnId, turnContexts.get(turnId), options.cwd);
|
|
1012
|
+
if (!activeTaskWindows.has(turnId)) {
|
|
1013
|
+
orderedTaskWindows.push(activeTaskWindow);
|
|
1014
|
+
}
|
|
1015
|
+
activeTaskWindow.lastActivityAt = record.timestamp;
|
|
1016
|
+
activeTaskWindow.completedAt = record.timestamp;
|
|
1017
|
+
activeTaskWindows.delete(turnId);
|
|
1018
|
+
currentTurnId = currentTurnId === turnId ? null : currentTurnId;
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
const eventTurnId = readOptionalString(payload, "turn_id");
|
|
1022
|
+
if (eventTurnId && activeTaskWindows.has(eventTurnId)) {
|
|
1023
|
+
touchCurrentTaskWindow(activeTaskWindows, eventTurnId, record.timestamp);
|
|
1024
|
+
currentTurnId = eventTurnId;
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
touchCurrentTaskWindow(activeTaskWindows, currentTurnId, record.timestamp);
|
|
1028
|
+
}
|
|
1029
|
+
return orderedTaskWindows.map((taskWindow, taskWindowIndex) => ({
|
|
1030
|
+
taskId: `${options.sessionId}:${taskWindow.turnId}:${taskWindowIndex}`,
|
|
1031
|
+
sessionId: options.sessionId,
|
|
1032
|
+
parentSessionId: options.parentSessionId,
|
|
1033
|
+
sessionKind: options.sessionKind,
|
|
1034
|
+
cwd: taskWindow.cwd,
|
|
1035
|
+
turnId: taskWindow.turnId,
|
|
1036
|
+
model: taskWindow.model,
|
|
1037
|
+
reasoningEffort: taskWindow.reasoningEffort,
|
|
1038
|
+
startedAt: taskWindow.startedAt,
|
|
1039
|
+
lastActivityAt: taskWindow.lastActivityAt,
|
|
1040
|
+
completedAt: taskWindow.completedAt,
|
|
1041
|
+
staleAfterMs: resolveTaskWindowStaleAfterMs(taskWindow.reasoningEffort)
|
|
1042
|
+
}));
|
|
1043
|
+
}
|
|
1044
|
+
function buildTaskWindowInterval(taskWindow, observedAt) {
|
|
1045
|
+
const intervalEnd = resolveTaskWindowEnd(taskWindow, observedAt);
|
|
1046
|
+
if (intervalEnd.getTime() <= taskWindow.startedAt.getTime()) {
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
start: taskWindow.startedAt,
|
|
1051
|
+
end: intervalEnd
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function isTaskWindowRunning(taskWindow, observedAt) {
|
|
1055
|
+
return taskWindow.completedAt === null && taskWindow.lastActivityAt.getTime() + taskWindow.staleAfterMs > observedAt.getTime();
|
|
1056
|
+
}
|
|
1057
|
+
function isTaskWindowCompletedBetween(taskWindow, windowStart, windowEnd) {
|
|
1058
|
+
if (!taskWindow.completedAt) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
return taskWindow.completedAt.getTime() > windowStart.getTime() && taskWindow.completedAt.getTime() <= windowEnd.getTime();
|
|
1062
|
+
}
|
|
1063
|
+
function resolveTaskWindowEnd(taskWindow, observedAt) {
|
|
1064
|
+
if (taskWindow.completedAt) {
|
|
1065
|
+
return taskWindow.completedAt;
|
|
1066
|
+
}
|
|
1067
|
+
const staleDeadline = new Date(taskWindow.lastActivityAt.getTime() + taskWindow.staleAfterMs);
|
|
1068
|
+
return staleDeadline.getTime() < observedAt.getTime() ? staleDeadline : observedAt;
|
|
1069
|
+
}
|
|
1070
|
+
function resolveTaskWindowStaleAfterMs(reasoningEffort) {
|
|
1071
|
+
return reasoningEffort === "medium" || reasoningEffort === "high" ? standardTaskWindowStaleAfterMs : defaultTaskWindowStaleAfterMs;
|
|
1072
|
+
}
|
|
1073
|
+
function createFallbackTaskWindow(timestamp, turnId, turnContext, defaultCwd) {
|
|
1074
|
+
return {
|
|
1075
|
+
completedAt: null,
|
|
1076
|
+
cwd: turnContext?.cwd ?? defaultCwd,
|
|
1077
|
+
lastActivityAt: timestamp,
|
|
1078
|
+
model: turnContext?.model ?? null,
|
|
1079
|
+
reasoningEffort: turnContext?.reasoningEffort ?? null,
|
|
1080
|
+
startedAt: timestamp,
|
|
1081
|
+
turnId
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function touchCurrentTaskWindow(activeTaskWindows, turnId, timestamp) {
|
|
1085
|
+
if (!turnId) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const activeTaskWindow = activeTaskWindows.get(turnId);
|
|
1089
|
+
if (activeTaskWindow) {
|
|
1090
|
+
activeTaskWindow.lastActivityAt = timestamp;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
677
1094
|
// src/codex-session-log/extract-user-message-timestamps.ts
|
|
678
1095
|
function extractUserMessageTimestamps(records) {
|
|
679
1096
|
const timestamps = [];
|
|
@@ -708,13 +1125,17 @@ async function parseCodexSession(sourceFilePath) {
|
|
|
708
1125
|
const tokenPoints = extractTokenPoints(records);
|
|
709
1126
|
const userMessageTimestamps = extractUserMessageTimestamps(records);
|
|
710
1127
|
const { turnAttributions, agentSpawnRequests } = extractTurnAttribution(records);
|
|
1128
|
+
const sessionId = readString(sessionMetaPayload, "id", "session_meta.payload");
|
|
1129
|
+
const cwd = readString(sessionMetaPayload, "cwd", "session_meta.payload");
|
|
1130
|
+
const forkedFromSessionId = readOptionalString(sessionMetaPayload, "forked_from_id");
|
|
1131
|
+
const kind = classifySessionKind(sessionMetaPayload.source);
|
|
711
1132
|
const eventTimestamps = records.filter((record) => record.type !== "session_meta").map((record) => record.timestamp);
|
|
712
1133
|
return {
|
|
713
|
-
sessionId
|
|
1134
|
+
sessionId,
|
|
714
1135
|
sourceFilePath,
|
|
715
|
-
cwd
|
|
716
|
-
kind
|
|
717
|
-
forkedFromSessionId
|
|
1136
|
+
cwd,
|
|
1137
|
+
kind,
|
|
1138
|
+
forkedFromSessionId,
|
|
718
1139
|
firstTimestamp: firstRecord.timestamp,
|
|
719
1140
|
lastTimestamp: lastRecord.timestamp,
|
|
720
1141
|
eventTimestamps: eventTimestamps.length > 0 ? eventTimestamps : [firstRecord.timestamp],
|
|
@@ -723,6 +1144,12 @@ async function parseCodexSession(sourceFilePath) {
|
|
|
723
1144
|
userMessageTimestamps,
|
|
724
1145
|
turnAttributions,
|
|
725
1146
|
agentSpawnRequests,
|
|
1147
|
+
taskWindows: extractTaskWindows(records, {
|
|
1148
|
+
cwd,
|
|
1149
|
+
parentSessionId: forkedFromSessionId,
|
|
1150
|
+
sessionId,
|
|
1151
|
+
sessionKind: kind
|
|
1152
|
+
}),
|
|
726
1153
|
primaryModel: resolvePrimaryModel(turnAttributions),
|
|
727
1154
|
primaryReasoningEffort: resolvePrimaryReasoningEffort(turnAttributions)
|
|
728
1155
|
};
|
|
@@ -815,26 +1242,29 @@ function buildActivityBlocks(timestamps, idleCutoffMs) {
|
|
|
815
1242
|
}
|
|
816
1243
|
|
|
817
1244
|
// src/reporting/activity-metrics.ts
|
|
818
|
-
function buildActivityMetrics(sessions, idleCutoffMs) {
|
|
1245
|
+
function buildActivityMetrics(sessions, idleCutoffMs, observedAt = new Date) {
|
|
819
1246
|
const directSessions = sessions.filter((session) => session.kind === "direct");
|
|
820
1247
|
const subagentSessions = sessions.filter((session) => session.kind === "subagent");
|
|
821
1248
|
const strictEngagementBlocks = buildActivityBlocks(directSessions.flatMap((session) => session.userMessageTimestamps), idleCutoffMs);
|
|
822
1249
|
const directActivityBlocks = buildActivityBlocks(directSessions.flatMap((session) => session.eventTimestamps), idleCutoffMs);
|
|
823
|
-
const
|
|
824
|
-
|
|
1250
|
+
const perAgentTaskBlocks = subagentSessions.map((session) => session.taskWindows.length > 0 ? session.taskWindows.flatMap((taskWindow) => {
|
|
1251
|
+
const taskWindowInterval = buildTaskWindowInterval(taskWindow, observedAt);
|
|
1252
|
+
return taskWindowInterval ? [taskWindowInterval] : [];
|
|
1253
|
+
}) : buildActivityBlocks(session.eventTimestamps, idleCutoffMs));
|
|
1254
|
+
const agentCoverageBlocks = mergeTimeIntervals(perAgentTaskBlocks.flat());
|
|
825
1255
|
const agentOnlyBlocks = subtractTimeIntervals(agentCoverageBlocks, directActivityBlocks);
|
|
826
1256
|
return {
|
|
827
1257
|
strictEngagementBlocks,
|
|
828
1258
|
directActivityBlocks,
|
|
829
1259
|
agentCoverageBlocks,
|
|
830
1260
|
agentOnlyBlocks,
|
|
831
|
-
|
|
1261
|
+
perAgentTaskBlocks,
|
|
832
1262
|
strictEngagementMs: sumTimeIntervalsMs(strictEngagementBlocks),
|
|
833
1263
|
directActivityMs: sumTimeIntervalsMs(directActivityBlocks),
|
|
834
1264
|
agentCoverageMs: sumTimeIntervalsMs(agentCoverageBlocks),
|
|
835
1265
|
agentOnlyMs: sumTimeIntervalsMs(agentOnlyBlocks),
|
|
836
|
-
cumulativeAgentMs:
|
|
837
|
-
peakConcurrentAgents: peakConcurrency(
|
|
1266
|
+
cumulativeAgentMs: perAgentTaskBlocks.reduce((totalDurationMs, taskBlocks) => totalDurationMs + sumTimeIntervalsMs(taskBlocks), 0),
|
|
1267
|
+
peakConcurrentAgents: peakConcurrency(perAgentTaskBlocks)
|
|
838
1268
|
};
|
|
839
1269
|
}
|
|
840
1270
|
|
|
@@ -870,11 +1300,12 @@ function groupSessions(sessions, dimension) {
|
|
|
870
1300
|
// src/reporting/build-hourly-report.ts
|
|
871
1301
|
function buildHourlyReport(sessions, query) {
|
|
872
1302
|
const filteredSessions = filterSessions(sessions, query.filters);
|
|
873
|
-
const metrics = buildActivityMetrics(filteredSessions, query.idleCutoffMs);
|
|
1303
|
+
const metrics = buildActivityMetrics(filteredSessions, query.idleCutoffMs, query.window.end);
|
|
874
1304
|
const wakeIntervals = query.wakeWindow ? buildWakeIntervalsForReportWindow(query.wakeWindow, query.window) : null;
|
|
875
1305
|
const buckets = buildBuckets(query.window.start, query.window.end, filteredSessions, metrics, wakeIntervals);
|
|
876
1306
|
return {
|
|
877
1307
|
appliedFilters: query.filters,
|
|
1308
|
+
agentConcurrencySource: filteredSessions.some((session) => session.kind === "subagent" && session.taskWindows.length === 0) ? "task-window-adapter-with-session-fallback" : "task-window-adapter",
|
|
878
1309
|
buckets,
|
|
879
1310
|
hasWakeWindow: query.wakeWindow !== null,
|
|
880
1311
|
idleCutoffMs: query.idleCutoffMs,
|
|
@@ -903,7 +1334,7 @@ function buildBuckets(windowStart, windowEnd, sessions, metrics, wakeIntervals)
|
|
|
903
1334
|
awakeIdleMs,
|
|
904
1335
|
directActivityMs,
|
|
905
1336
|
engagedMs: measureOverlapMs(metrics.strictEngagementBlocks, bucketInterval),
|
|
906
|
-
peakConcurrentAgents: peakConcurrency(metrics.
|
|
1337
|
+
peakConcurrentAgents: peakConcurrency(metrics.perAgentTaskBlocks.map((taskBlocks) => clipIntervals(taskBlocks, bucketInterval))),
|
|
907
1338
|
practicalBurn: sumTokenBurn(sessions, bucketInterval),
|
|
908
1339
|
rawTotalTokens: sumRawTokenDeltas(sessions, bucketInterval),
|
|
909
1340
|
sessionCount: countSessionsInBucket(sessions, bucketInterval)
|
|
@@ -1028,6 +1459,13 @@ function formatHourOfDay(timestamp, reportWindow) {
|
|
|
1028
1459
|
hour12: false
|
|
1029
1460
|
}).format(timestamp);
|
|
1030
1461
|
}
|
|
1462
|
+
function formatAxisTimeLabel(timestamp, reportWindow) {
|
|
1463
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
1464
|
+
timeZone: reportWindow.timeZone,
|
|
1465
|
+
hour: "numeric",
|
|
1466
|
+
hour12: true
|
|
1467
|
+
}).format(timestamp).toLowerCase().replace(/\s+/g, "");
|
|
1468
|
+
}
|
|
1031
1469
|
function buildSparkline(values) {
|
|
1032
1470
|
const levels = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
1033
1471
|
const maxValue = Math.max(...values, 0);
|
|
@@ -1256,6 +1694,27 @@ function clamp(value, minValue, maxValue) {
|
|
|
1256
1694
|
return Math.min(maxValue, Math.max(minValue, value));
|
|
1257
1695
|
}
|
|
1258
1696
|
|
|
1697
|
+
// src/reporting/render-time-axis.ts
|
|
1698
|
+
var axisGroupSize = 4;
|
|
1699
|
+
function buildGroupedTrack(text) {
|
|
1700
|
+
const groups = [];
|
|
1701
|
+
for (let index = 0;index < text.length; index += axisGroupSize) {
|
|
1702
|
+
groups.push(text.slice(index, index + axisGroupSize));
|
|
1703
|
+
}
|
|
1704
|
+
return groups.join("│");
|
|
1705
|
+
}
|
|
1706
|
+
function buildTimeAxisLine(report) {
|
|
1707
|
+
const axisGroups = [];
|
|
1708
|
+
for (let bucketIndex = 0;bucketIndex < report.buckets.length; bucketIndex += axisGroupSize) {
|
|
1709
|
+
const bucket = report.buckets[bucketIndex];
|
|
1710
|
+
if (!bucket) {
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1713
|
+
axisGroups.push(padRight(formatAxisTimeLabel(bucket.start, report.window), Math.min(axisGroupSize, report.buckets.length - bucketIndex)));
|
|
1714
|
+
}
|
|
1715
|
+
return axisGroups.join("│");
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1259
1718
|
// src/reporting/render-layout.ts
|
|
1260
1719
|
function buildPanel(title, lines) {
|
|
1261
1720
|
const innerWidth = Math.max(56, title.length + 4, ...lines.map((line) => line.length));
|
|
@@ -1282,8 +1741,17 @@ function renderSectionTitle(title, options) {
|
|
|
1282
1741
|
];
|
|
1283
1742
|
}
|
|
1284
1743
|
|
|
1744
|
+
// src/reporting/render-agent-section.ts
|
|
1745
|
+
function buildAgentSection(report, options) {
|
|
1746
|
+
return [
|
|
1747
|
+
...renderSectionTitle("Agents", options),
|
|
1748
|
+
paint(` time ${buildTimeAxisLine(report)}`, "muted", options),
|
|
1749
|
+
`${paint(" conc ", "agent", options)}${paint(buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.peakConcurrentAgents))), "agent", options)} ${paint(`${Math.max(...report.buckets.map((bucket) => bucket.peakConcurrentAgents), 0)} peak`, "value", options)}`,
|
|
1750
|
+
`${paint(" unit ", "muted", options)}${paint(report.agentConcurrencySource === "task-window-adapter" ? "task windows" : "task windows with session fallback", "muted", options)} ${paint(padRight(formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.agentOnlyMs, 0)), 5), "agent", options)} ${paint("agent-only", "muted", options)}`
|
|
1751
|
+
];
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1285
1754
|
// src/reporting/render-rhythm-section.ts
|
|
1286
|
-
var groupSize = 4;
|
|
1287
1755
|
function buildRhythmSection(report, options) {
|
|
1288
1756
|
const quietValues = report.buckets.map((bucket) => Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs));
|
|
1289
1757
|
const idleValues = report.hasWakeWindow ? report.buckets.map((bucket) => bucket.awakeIdleMs) : quietValues;
|
|
@@ -1291,7 +1759,7 @@ function buildRhythmSection(report, options) {
|
|
|
1291
1759
|
const idleTotal = formatDurationCompact(idleValues.reduce((totalDurationMs, idleDurationMs) => totalDurationMs + idleDurationMs, 0));
|
|
1292
1760
|
const lines = [
|
|
1293
1761
|
...renderSectionTitle("24h Rhythm", options),
|
|
1294
|
-
paint(`
|
|
1762
|
+
paint(` time ${buildTimeAxisLine(report)}`, "muted", options),
|
|
1295
1763
|
renderRhythmRow("focus", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.engagedMs))), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.engagedMs, 0)), "focus", options),
|
|
1296
1764
|
renderRhythmRow("active", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.directActivityMs))), formatDurationCompact(report.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + bucket.directActivityMs, 0)), "active", options)
|
|
1297
1765
|
];
|
|
@@ -1299,24 +1767,6 @@ function buildRhythmSection(report, options) {
|
|
|
1299
1767
|
lines.push(renderRhythmRow("burn", buildGroupedTrack(buildSparkline(report.buckets.map((bucket) => bucket.practicalBurn))), formatCompactInteger(report.buckets.reduce((totalBurn, bucket) => totalBurn + bucket.practicalBurn, 0)), "burn", options));
|
|
1300
1768
|
return lines;
|
|
1301
1769
|
}
|
|
1302
|
-
function buildGroupedTrack(text) {
|
|
1303
|
-
const groups = [];
|
|
1304
|
-
for (let i = 0;i < text.length; i += groupSize) {
|
|
1305
|
-
groups.push(text.slice(i, i + groupSize));
|
|
1306
|
-
}
|
|
1307
|
-
return groups.join("│");
|
|
1308
|
-
}
|
|
1309
|
-
function buildHourMarkerLine(report) {
|
|
1310
|
-
const markerGroups = [];
|
|
1311
|
-
for (let index = 0;index < report.buckets.length; index += groupSize) {
|
|
1312
|
-
const bucket = report.buckets[index];
|
|
1313
|
-
if (!bucket) {
|
|
1314
|
-
continue;
|
|
1315
|
-
}
|
|
1316
|
-
markerGroups.push(padRight(formatHourOfDay(bucket.start, report.window), Math.min(groupSize, report.buckets.length - index)));
|
|
1317
|
-
}
|
|
1318
|
-
return markerGroups.join("│");
|
|
1319
|
-
}
|
|
1320
1770
|
function renderRhythmRow(label, sparkline, totalText, role, options) {
|
|
1321
1771
|
return `${paint(` ${padRight(label, 6)}`, role, options)} ${paint(sparkline, role, options)} ${paint(totalText, "value", options)}`;
|
|
1322
1772
|
}
|
|
@@ -1353,6 +1803,8 @@ function renderFullHourlyReport(report, options) {
|
|
|
1353
1803
|
lines.push("");
|
|
1354
1804
|
lines.push(...panelLines);
|
|
1355
1805
|
lines.push("");
|
|
1806
|
+
lines.push(...buildAgentSection(report, options));
|
|
1807
|
+
lines.push("");
|
|
1356
1808
|
lines.push(...buildRhythmSection(report, options));
|
|
1357
1809
|
lines.push("");
|
|
1358
1810
|
lines.push(...buildSpikeSection(report, options));
|
|
@@ -1382,6 +1834,8 @@ function renderShareHourlyReport(report, options) {
|
|
|
1382
1834
|
lines.push("");
|
|
1383
1835
|
lines.push(...panelLines);
|
|
1384
1836
|
lines.push("");
|
|
1837
|
+
lines.push(...buildAgentSection(report, options));
|
|
1838
|
+
lines.push("");
|
|
1385
1839
|
lines.push(...buildRhythmSection(report, options));
|
|
1386
1840
|
lines.push("");
|
|
1387
1841
|
lines.push(...buildSpikeSection(report, options));
|
|
@@ -1408,985 +1862,1410 @@ function buildFilterLine(report) {
|
|
|
1408
1862
|
}
|
|
1409
1863
|
|
|
1410
1864
|
// src/cli/run-hourly-command.ts
|
|
1411
|
-
async function
|
|
1412
|
-
const window = resolveTrailingReportWindow({
|
|
1865
|
+
async function buildHourlyCommandResult(command, options = {}) {
|
|
1866
|
+
const window = resolveTrailingReportWindow({
|
|
1867
|
+
durationMs: command.hourlyWindowMs,
|
|
1868
|
+
now: options.now
|
|
1869
|
+
});
|
|
1413
1870
|
const sessions = await readCodexSessions({
|
|
1414
1871
|
windowStart: window.start,
|
|
1415
|
-
windowEnd: window.end
|
|
1872
|
+
windowEnd: window.end,
|
|
1873
|
+
sessionRootDirectory: options.sessionRootDirectory
|
|
1416
1874
|
});
|
|
1417
|
-
return
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1875
|
+
return {
|
|
1876
|
+
hourlyReport: buildHourlyReport(sessions, {
|
|
1877
|
+
filters: command.filters,
|
|
1878
|
+
idleCutoffMs: command.idleCutoffMs,
|
|
1879
|
+
wakeWindow: command.wakeWindow,
|
|
1880
|
+
window
|
|
1881
|
+
})
|
|
1882
|
+
};
|
|
1423
1883
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
import { existsSync } from "node:fs";
|
|
1428
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
1429
|
-
import { promisify } from "node:util";
|
|
1430
|
-
var execFileAsync = promisify(execFile);
|
|
1431
|
-
async function deliverLocalNotifications(notifications, options = {}) {
|
|
1432
|
-
const platform = options.platform ?? process.platform;
|
|
1433
|
-
if (platform !== "darwin" || notifications.length === 0) {
|
|
1434
|
-
return;
|
|
1435
|
-
}
|
|
1436
|
-
const notifier = options.notifier ?? sendMacOsNotification;
|
|
1437
|
-
for (const notification of notifications) {
|
|
1438
|
-
try {
|
|
1439
|
-
await notifier(notification);
|
|
1440
|
-
} catch {
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1884
|
+
async function runHourlyCommand(command, options = {}) {
|
|
1885
|
+
const commandResult = await buildHourlyCommandResult(command, options);
|
|
1886
|
+
return renderHourlyReport(commandResult.hourlyReport, createRenderOptions(command.shareMode));
|
|
1444
1887
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1888
|
+
|
|
1889
|
+
// src/best-metrics/read-best-ledger.ts
|
|
1890
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1891
|
+
import { homedir as homedir2 } from "node:os";
|
|
1892
|
+
import { join as join2 } from "node:path";
|
|
1893
|
+
|
|
1894
|
+
// src/best-metrics/types.ts
|
|
1895
|
+
var bestMetricsLedgerVersion = 1;
|
|
1896
|
+
var rollingWindowDurationMs = 24 * 60 * 60 * 1000;
|
|
1897
|
+
var defaultBestMetricsIdleCutoffMs = 15 * 60 * 1000;
|
|
1898
|
+
|
|
1899
|
+
// src/best-metrics/read-best-ledger.ts
|
|
1900
|
+
var bestLedgerFileName = "bests-v1.json";
|
|
1901
|
+
async function readBestLedger(options = {}) {
|
|
1447
1902
|
try {
|
|
1448
|
-
await
|
|
1449
|
-
|
|
1450
|
-
notification.title,
|
|
1451
|
-
"-message",
|
|
1452
|
-
notification.body,
|
|
1453
|
-
...notificationIconPath ? ["-appIcon", pathToFileURL(notificationIconPath).href] : []
|
|
1454
|
-
]);
|
|
1455
|
-
return;
|
|
1903
|
+
const rawLedgerText = await readFile2(resolveBestLedgerPath(options), "utf8");
|
|
1904
|
+
return parseBestLedger(JSON.parse(rawLedgerText));
|
|
1456
1905
|
} catch (error) {
|
|
1457
|
-
if (
|
|
1458
|
-
|
|
1906
|
+
if (isMissingFileError(error)) {
|
|
1907
|
+
return null;
|
|
1459
1908
|
}
|
|
1909
|
+
throw error;
|
|
1460
1910
|
}
|
|
1461
|
-
await execFileAsync("osascript", [
|
|
1462
|
-
"-e",
|
|
1463
|
-
`display notification "${escapeAppleScriptText(notification.body)}" with title "${escapeAppleScriptText(notification.title)}"`
|
|
1464
|
-
]);
|
|
1465
1911
|
}
|
|
1466
|
-
function
|
|
1467
|
-
|
|
1912
|
+
function parseBestLedger(value) {
|
|
1913
|
+
const ledgerRecord = expectObject(value, "bestMetricsLedger");
|
|
1914
|
+
const version = readNumber(ledgerRecord, "version", "bestMetricsLedger");
|
|
1915
|
+
if (version !== bestMetricsLedgerVersion) {
|
|
1916
|
+
throw new Error(`bestMetricsLedger.version must be ${bestMetricsLedgerVersion}.`);
|
|
1917
|
+
}
|
|
1918
|
+
return {
|
|
1919
|
+
version: bestMetricsLedgerVersion,
|
|
1920
|
+
initializedAt: readIsoTimestamp(ledgerRecord.initializedAt, "bestMetricsLedger.initializedAt"),
|
|
1921
|
+
lastScannedAt: readIsoTimestamp(ledgerRecord.lastScannedAt, "bestMetricsLedger.lastScannedAt"),
|
|
1922
|
+
bestConcurrentAgents: parseBestMetricRecord(ledgerRecord.bestConcurrentAgents, "bestMetricsLedger.bestConcurrentAgents"),
|
|
1923
|
+
best24hRawBurn: parseBestMetricRecord(ledgerRecord.best24hRawBurn, "bestMetricsLedger.best24hRawBurn"),
|
|
1924
|
+
best24hAgentSumMs: parseBestMetricRecord(ledgerRecord.best24hAgentSumMs, "bestMetricsLedger.best24hAgentSumMs")
|
|
1925
|
+
};
|
|
1468
1926
|
}
|
|
1469
|
-
function
|
|
1470
|
-
|
|
1471
|
-
fileURLToPath(new URL("../../assets/idle-time-notification-icon.png", import.meta.url)),
|
|
1472
|
-
fileURLToPath(new URL("../assets/idle-time-notification-icon.png", import.meta.url))
|
|
1473
|
-
];
|
|
1474
|
-
return candidatePaths.find((candidatePath) => existsSync(candidatePath)) ?? null;
|
|
1927
|
+
function resolveBestLedgerPath(options = {}) {
|
|
1928
|
+
return join2(resolveBestStateDirectory(options), bestLedgerFileName);
|
|
1475
1929
|
}
|
|
1476
|
-
function
|
|
1477
|
-
|
|
1930
|
+
function serializeBestLedger(ledger) {
|
|
1931
|
+
const serializedLedger = {
|
|
1932
|
+
version: ledger.version,
|
|
1933
|
+
initializedAt: ledger.initializedAt.toISOString(),
|
|
1934
|
+
lastScannedAt: ledger.lastScannedAt.toISOString(),
|
|
1935
|
+
bestConcurrentAgents: serializeBestMetricRecord(ledger.bestConcurrentAgents),
|
|
1936
|
+
best24hRawBurn: serializeBestMetricRecord(ledger.best24hRawBurn),
|
|
1937
|
+
best24hAgentSumMs: serializeBestMetricRecord(ledger.best24hAgentSumMs)
|
|
1938
|
+
};
|
|
1939
|
+
return `${JSON.stringify(serializedLedger, null, 2)}
|
|
1940
|
+
`;
|
|
1478
1941
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
import { homedir as homedir2 } from "node:os";
|
|
1483
|
-
import { join as join2 } from "node:path";
|
|
1484
|
-
var nearBestNotificationStateFileName = "near-best-notifications-v1.json";
|
|
1485
|
-
var nearBestNotificationVersion = 1;
|
|
1486
|
-
async function notifyNearBestMetrics(currentMetrics, ledger, options = {}) {
|
|
1487
|
-
const now = options.now ?? new Date;
|
|
1488
|
-
const state = await ensureNearBestNotificationState(options);
|
|
1489
|
-
if (!state.nearBestEnabled) {
|
|
1490
|
-
return [];
|
|
1491
|
-
}
|
|
1492
|
-
const metricsToNotify = buildNearBestMetricKeys(currentMetrics, ledger, state, now);
|
|
1493
|
-
if (metricsToNotify.length === 0) {
|
|
1494
|
-
return [];
|
|
1942
|
+
function parseBestMetricRecord(value, label) {
|
|
1943
|
+
if (value === null || value === undefined) {
|
|
1944
|
+
return null;
|
|
1495
1945
|
}
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
}
|
|
1946
|
+
const record = expectObject(value, label);
|
|
1947
|
+
return {
|
|
1948
|
+
value: readNumber(record, "value", label),
|
|
1949
|
+
observedAt: readIsoTimestamp(record.observedAt, `${label}.observedAt`),
|
|
1950
|
+
windowStart: readIsoTimestamp(record.windowStart, `${label}.windowStart`),
|
|
1951
|
+
windowEnd: readIsoTimestamp(record.windowEnd, `${label}.windowEnd`)
|
|
1502
1952
|
};
|
|
1503
|
-
await writeNearBestNotificationState(nextState, options);
|
|
1504
|
-
await deliverLocalNotifications(metricsToNotify.map((metric) => buildNearBestNotification(metric, currentMetrics[metric], ledger[metric]?.value ?? 0)), options);
|
|
1505
|
-
return metricsToNotify;
|
|
1506
|
-
}
|
|
1507
|
-
async function ensureNearBestNotificationState(options) {
|
|
1508
|
-
const existingState = await readNearBestNotificationState(options);
|
|
1509
|
-
if (existingState) {
|
|
1510
|
-
return existingState;
|
|
1511
|
-
}
|
|
1512
|
-
const defaultState = createDefaultNearBestNotificationState();
|
|
1513
|
-
await writeNearBestNotificationState(defaultState, options);
|
|
1514
|
-
return defaultState;
|
|
1515
|
-
}
|
|
1516
|
-
async function readNearBestNotificationState(options) {
|
|
1517
|
-
try {
|
|
1518
|
-
const rawStateText = await readFile2(resolveNearBestNotificationStatePath(options), "utf8");
|
|
1519
|
-
return parseNearBestNotificationState(JSON.parse(rawStateText));
|
|
1520
|
-
} catch (error) {
|
|
1521
|
-
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
1522
|
-
return null;
|
|
1523
|
-
}
|
|
1524
|
-
throw error;
|
|
1525
|
-
}
|
|
1526
1953
|
}
|
|
1527
|
-
function
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
if (version !== nearBestNotificationVersion) {
|
|
1531
|
-
throw new Error(`nearBestNotificationState.version must be ${nearBestNotificationVersion}.`);
|
|
1954
|
+
function serializeBestMetricRecord(record) {
|
|
1955
|
+
if (!record) {
|
|
1956
|
+
return null;
|
|
1532
1957
|
}
|
|
1533
|
-
const lastNotifiedAtRecord = expectObject(stateRecord.lastNotifiedAt, "nearBestNotificationState.lastNotifiedAt");
|
|
1534
1958
|
return {
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
lastNotifiedAt: {
|
|
1540
|
-
bestConcurrentAgents: readOptionalIsoTimestamp(lastNotifiedAtRecord.bestConcurrentAgents, "nearBestNotificationState.lastNotifiedAt.bestConcurrentAgents"),
|
|
1541
|
-
best24hRawBurn: readOptionalIsoTimestamp(lastNotifiedAtRecord.best24hRawBurn, "nearBestNotificationState.lastNotifiedAt.best24hRawBurn"),
|
|
1542
|
-
best24hAgentSumMs: readOptionalIsoTimestamp(lastNotifiedAtRecord.best24hAgentSumMs, "nearBestNotificationState.lastNotifiedAt.best24hAgentSumMs")
|
|
1543
|
-
}
|
|
1959
|
+
value: record.value,
|
|
1960
|
+
observedAt: record.observedAt.toISOString(),
|
|
1961
|
+
windowStart: record.windowStart.toISOString(),
|
|
1962
|
+
windowEnd: record.windowEnd.toISOString()
|
|
1544
1963
|
};
|
|
1545
1964
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
const stateDirectory = options.stateDirectory ?? join2(homedir2(), ".idletime");
|
|
1549
|
-
await mkdir(stateDirectory, { recursive: true });
|
|
1550
|
-
const temporaryPath = join2(stateDirectory, `.near-best-notifications.${process.pid}.${Date.now()}.tmp`);
|
|
1551
|
-
await writeFile(temporaryPath, `${JSON.stringify({
|
|
1552
|
-
version: state.version,
|
|
1553
|
-
nearBestEnabled: state.nearBestEnabled,
|
|
1554
|
-
thresholdRatio: state.thresholdRatio,
|
|
1555
|
-
cooldownMs: state.cooldownMs,
|
|
1556
|
-
lastNotifiedAt: {
|
|
1557
|
-
bestConcurrentAgents: state.lastNotifiedAt.bestConcurrentAgents?.toISOString() ?? null,
|
|
1558
|
-
best24hRawBurn: state.lastNotifiedAt.best24hRawBurn?.toISOString() ?? null,
|
|
1559
|
-
best24hAgentSumMs: state.lastNotifiedAt.best24hAgentSumMs?.toISOString() ?? null
|
|
1560
|
-
}
|
|
1561
|
-
}, null, 2)}
|
|
1562
|
-
`, "utf8");
|
|
1563
|
-
await rename(temporaryPath, statePath);
|
|
1965
|
+
function resolveBestStateDirectory(options) {
|
|
1966
|
+
return options.stateDirectory ?? join2(homedir2(), ".idletime");
|
|
1564
1967
|
}
|
|
1565
|
-
function
|
|
1566
|
-
return
|
|
1567
|
-
"bestConcurrentAgents",
|
|
1568
|
-
"best24hRawBurn",
|
|
1569
|
-
"best24hAgentSumMs"
|
|
1570
|
-
].filter((metric) => {
|
|
1571
|
-
const bestValue = ledger[metric]?.value ?? 0;
|
|
1572
|
-
if (bestValue <= 0) {
|
|
1573
|
-
return false;
|
|
1574
|
-
}
|
|
1575
|
-
const currentValue = currentMetrics[metric];
|
|
1576
|
-
if (currentValue <= 0 || currentValue >= bestValue) {
|
|
1577
|
-
return false;
|
|
1578
|
-
}
|
|
1579
|
-
if (currentValue / bestValue < state.thresholdRatio) {
|
|
1580
|
-
return false;
|
|
1581
|
-
}
|
|
1582
|
-
const lastNotifiedAt = state.lastNotifiedAt[metric];
|
|
1583
|
-
return lastNotifiedAt === null || now.getTime() - lastNotifiedAt.getTime() >= state.cooldownMs;
|
|
1584
|
-
});
|
|
1968
|
+
function isMissingFileError(error) {
|
|
1969
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1585
1970
|
}
|
|
1586
|
-
|
|
1971
|
+
|
|
1972
|
+
// src/reporting/build-summary-report.ts
|
|
1973
|
+
function buildSummaryReport(sessions, query) {
|
|
1974
|
+
const filteredSessions = filterSessions(sessions, query.filters);
|
|
1975
|
+
const windowInterval = {
|
|
1976
|
+
start: query.window.start,
|
|
1977
|
+
end: query.window.end
|
|
1978
|
+
};
|
|
1979
|
+
const metrics = clipActivityMetricsToWindow(buildActivityMetrics(filteredSessions, query.idleCutoffMs, query.window.end), windowInterval);
|
|
1980
|
+
const comparisonCutoffMs = parseDurationToMs("30m");
|
|
1981
|
+
const sessionCounts = {
|
|
1982
|
+
total: filteredSessions.length,
|
|
1983
|
+
direct: filteredSessions.filter((session) => session.kind === "direct").length,
|
|
1984
|
+
subagent: filteredSessions.filter((session) => session.kind === "subagent").length
|
|
1985
|
+
};
|
|
1587
1986
|
return {
|
|
1588
|
-
|
|
1589
|
-
|
|
1987
|
+
activityWindow: resolveActivityWindow(filteredSessions, windowInterval),
|
|
1988
|
+
appliedFilters: query.filters,
|
|
1989
|
+
comparisonCutoffMs,
|
|
1990
|
+
comparisonMetrics: query.idleCutoffMs === comparisonCutoffMs ? metrics : clipActivityMetricsToWindow(buildActivityMetrics(filteredSessions, comparisonCutoffMs, query.window.end), windowInterval),
|
|
1991
|
+
directTokenTotals: sumTokenTotals(filteredSessions.filter((session) => session.kind === "direct"), windowInterval),
|
|
1992
|
+
groupBreakdowns: buildGroupBreakdowns(filteredSessions, query.groupBy, query.idleCutoffMs, windowInterval),
|
|
1993
|
+
idleCutoffMs: query.idleCutoffMs,
|
|
1994
|
+
metrics,
|
|
1995
|
+
sessionCounts,
|
|
1996
|
+
tokenTotals: sumTokenTotals(filteredSessions, windowInterval),
|
|
1997
|
+
wakeSummary: query.wakeWindow ? summarizeWakeWindow(query.wakeWindow, query.window, metrics) : null,
|
|
1998
|
+
window: query.window
|
|
1590
1999
|
};
|
|
1591
2000
|
}
|
|
1592
|
-
function
|
|
2001
|
+
function buildGroupBreakdowns(sessions, dimensions, idleCutoffMs, windowInterval) {
|
|
2002
|
+
return dimensions.map((dimension) => ({
|
|
2003
|
+
dimension,
|
|
2004
|
+
rows: groupSessions(sessions, dimension).map((groupedSessions) => buildGroupRow(groupedSessions.key, groupedSessions.sessions, idleCutoffMs, windowInterval, windowInterval.end))
|
|
2005
|
+
}));
|
|
2006
|
+
}
|
|
2007
|
+
function buildGroupRow(key, sessions, idleCutoffMs, windowInterval, observedAt) {
|
|
2008
|
+
const metrics = clipActivityMetricsToWindow(buildActivityMetrics(sessions, idleCutoffMs, observedAt), windowInterval);
|
|
2009
|
+
const tokenTotals = sumTokenTotals(sessions, windowInterval);
|
|
1593
2010
|
return {
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
best24hAgentSumMs: null
|
|
1602
|
-
}
|
|
2011
|
+
key,
|
|
2012
|
+
sessionCount: sessions.length,
|
|
2013
|
+
directActivityMs: metrics.directActivityMs,
|
|
2014
|
+
agentCoverageMs: metrics.agentCoverageMs,
|
|
2015
|
+
cumulativeAgentMs: metrics.cumulativeAgentMs,
|
|
2016
|
+
practicalBurn: tokenTotals.practicalBurn,
|
|
2017
|
+
rawTotalTokens: tokenTotals.rawTotalTokens
|
|
1603
2018
|
};
|
|
1604
2019
|
}
|
|
1605
|
-
function
|
|
1606
|
-
return
|
|
2020
|
+
function sumTokenTotals(sessions, windowInterval) {
|
|
2021
|
+
return sessions.reduce((tokenTotals, session) => {
|
|
2022
|
+
const sessionWindowTotals = buildTokenDeltaPoints(session.tokenPoints).filter((tokenDeltaPoint) => tokenDeltaPoint.timestamp.getTime() >= windowInterval.start.getTime() && tokenDeltaPoint.timestamp.getTime() <= windowInterval.end.getTime()).reduce((sessionTotals, tokenDeltaPoint) => ({
|
|
2023
|
+
practicalBurn: sessionTotals.practicalBurn + tokenDeltaPoint.deltaUsage.practicalBurn,
|
|
2024
|
+
rawTotalTokens: sessionTotals.rawTotalTokens + tokenDeltaPoint.deltaUsage.totalTokens
|
|
2025
|
+
}), { practicalBurn: 0, rawTotalTokens: 0 });
|
|
2026
|
+
return {
|
|
2027
|
+
practicalBurn: tokenTotals.practicalBurn + sessionWindowTotals.practicalBurn,
|
|
2028
|
+
rawTotalTokens: tokenTotals.rawTotalTokens + sessionWindowTotals.rawTotalTokens
|
|
2029
|
+
};
|
|
2030
|
+
}, {
|
|
2031
|
+
practicalBurn: 0,
|
|
2032
|
+
rawTotalTokens: 0
|
|
2033
|
+
});
|
|
1607
2034
|
}
|
|
1608
|
-
function
|
|
1609
|
-
if (
|
|
2035
|
+
function resolveActivityWindow(sessions, windowInterval) {
|
|
2036
|
+
if (sessions.length === 0) {
|
|
1610
2037
|
return null;
|
|
1611
2038
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
function formatAgentSumHours2(durationMs) {
|
|
1615
|
-
const hours = durationMs / 3600000;
|
|
1616
|
-
return hours >= 10 ? Math.round(hours).toString() : (Math.round(hours * 10) / 10).toString();
|
|
1617
|
-
}
|
|
1618
|
-
function formatCompactInteger2(value) {
|
|
1619
|
-
return new Intl.NumberFormat("en-US", {
|
|
1620
|
-
notation: "compact",
|
|
1621
|
-
maximumFractionDigits: 1
|
|
1622
|
-
}).format(Math.round(value)).toUpperCase();
|
|
1623
|
-
}
|
|
1624
|
-
function formatInteger2(value) {
|
|
1625
|
-
return new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
// src/best-metrics/notify-best-events.ts
|
|
1629
|
-
async function notifyBestEvents(bestEvents, options = {}) {
|
|
1630
|
-
await deliverLocalNotifications(bestEvents.map((bestEvent) => buildBestEventNotification(bestEvent)), options);
|
|
1631
|
-
}
|
|
1632
|
-
function buildBestEventNotification(bestEvent) {
|
|
2039
|
+
const firstTimestamp = sessions.reduce((earliestTimestamp, session) => session.firstTimestamp.getTime() < earliestTimestamp.getTime() ? session.firstTimestamp : earliestTimestamp, sessions[0].firstTimestamp);
|
|
2040
|
+
const lastTimestamp = sessions.reduce((latestTimestamp, session) => session.lastTimestamp.getTime() > latestTimestamp.getTime() ? session.lastTimestamp : latestTimestamp, sessions[0].lastTimestamp);
|
|
1633
2041
|
return {
|
|
1634
|
-
|
|
1635
|
-
|
|
2042
|
+
start: new Date(Math.max(firstTimestamp.getTime(), windowInterval.start.getTime())),
|
|
2043
|
+
end: new Date(Math.min(lastTimestamp.getTime(), windowInterval.end.getTime()))
|
|
1636
2044
|
};
|
|
1637
2045
|
}
|
|
1638
|
-
function
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
function formatAgentSumHours3(durationMs) {
|
|
1645
|
-
const hours = durationMs / 3600000;
|
|
1646
|
-
return hours >= 10 ? Math.round(hours).toString() : (Math.round(hours * 10) / 10).toString();
|
|
1647
|
-
}
|
|
1648
|
-
function formatCompactInteger3(value) {
|
|
1649
|
-
return new Intl.NumberFormat("en-US", {
|
|
1650
|
-
notation: "compact",
|
|
1651
|
-
maximumFractionDigits: 1
|
|
1652
|
-
}).format(Math.round(value)).toUpperCase();
|
|
1653
|
-
}
|
|
1654
|
-
function formatInteger3(value) {
|
|
1655
|
-
return new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
// src/best-metrics/types.ts
|
|
1659
|
-
var bestMetricsLedgerVersion = 1;
|
|
1660
|
-
var rollingWindowDurationMs = 24 * 60 * 60 * 1000;
|
|
1661
|
-
var defaultBestMetricsIdleCutoffMs = 15 * 60 * 1000;
|
|
1662
|
-
|
|
1663
|
-
// src/best-metrics/build-current-best-metrics.ts
|
|
1664
|
-
function buildCurrentBestMetricValues(sessions, options = {}) {
|
|
1665
|
-
const idleCutoffMs = options.idleCutoffMs ?? defaultBestMetricsIdleCutoffMs;
|
|
1666
|
-
const now = options.now ?? new Date;
|
|
1667
|
-
const activityMetrics = buildActivityMetrics(sessions, idleCutoffMs);
|
|
1668
|
-
const currentWindow = {
|
|
1669
|
-
start: new Date(now.getTime() - rollingWindowDurationMs),
|
|
1670
|
-
end: now
|
|
1671
|
-
};
|
|
2046
|
+
function clipActivityMetricsToWindow(metrics, windowInterval) {
|
|
2047
|
+
const strictEngagementBlocks = intersectTimeIntervals(metrics.strictEngagementBlocks, [windowInterval]);
|
|
2048
|
+
const directActivityBlocks = intersectTimeIntervals(metrics.directActivityBlocks, [windowInterval]);
|
|
2049
|
+
const agentCoverageBlocks = intersectTimeIntervals(metrics.agentCoverageBlocks, [windowInterval]);
|
|
2050
|
+
const agentOnlyBlocks = intersectTimeIntervals(metrics.agentOnlyBlocks, [windowInterval]);
|
|
2051
|
+
const perAgentTaskBlocks = metrics.perAgentTaskBlocks.map((taskBlocks) => intersectTimeIntervals(taskBlocks, [windowInterval]));
|
|
1672
2052
|
return {
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
2053
|
+
strictEngagementBlocks,
|
|
2054
|
+
directActivityBlocks,
|
|
2055
|
+
agentCoverageBlocks,
|
|
2056
|
+
agentOnlyBlocks,
|
|
2057
|
+
perAgentTaskBlocks,
|
|
2058
|
+
strictEngagementMs: sumTimeIntervalsMs(strictEngagementBlocks),
|
|
2059
|
+
directActivityMs: sumTimeIntervalsMs(directActivityBlocks),
|
|
2060
|
+
agentCoverageMs: sumTimeIntervalsMs(agentCoverageBlocks),
|
|
2061
|
+
agentOnlyMs: sumTimeIntervalsMs(agentOnlyBlocks),
|
|
2062
|
+
cumulativeAgentMs: perAgentTaskBlocks.reduce((totalDurationMs, taskBlocks) => totalDurationMs + sumTimeIntervalsMs(taskBlocks), 0),
|
|
2063
|
+
peakConcurrentAgents: peakConcurrency(perAgentTaskBlocks)
|
|
1676
2064
|
};
|
|
1677
2065
|
}
|
|
1678
|
-
function countLiveSubagents(intervalGroups, now) {
|
|
1679
|
-
return intervalGroups.reduce((liveCount, intervalGroup) => liveCount + Number(intervalGroup.some((interval) => interval.start.getTime() <= now.getTime() && interval.end.getTime() > now.getTime())), 0);
|
|
1680
|
-
}
|
|
1681
2066
|
|
|
1682
|
-
// src/
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
2067
|
+
// src/reporting/render-summary-report.ts
|
|
2068
|
+
var summaryBarWidth = 18;
|
|
2069
|
+
function renderSummaryReport(report, options, hourlyReport, bestPlaque = null) {
|
|
2070
|
+
return options.shareMode ? renderShareSummaryReport(report, options, hourlyReport, bestPlaque) : renderFullSummaryReport(report, options, hourlyReport, bestPlaque);
|
|
2071
|
+
}
|
|
2072
|
+
function renderFullSummaryReport(report, options, hourlyReport, bestPlaque = null) {
|
|
2073
|
+
const lines = [];
|
|
2074
|
+
const requestedMetrics = report.metrics;
|
|
2075
|
+
const actualComparisonMetrics = report.comparisonMetrics;
|
|
2076
|
+
const windowDurationMs = report.window.end.getTime() - report.window.start.getTime();
|
|
2077
|
+
const headerLines = buildSummaryHeaderLines(report, hourlyReport);
|
|
2078
|
+
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
2079
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
2080
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
2081
|
+
lines.push(...buildLogoSection(logoSectionWidth, options, bestPlaque));
|
|
2082
|
+
lines.push("");
|
|
2083
|
+
lines.push(...panelLines);
|
|
2084
|
+
if (hourlyReport) {
|
|
2085
|
+
lines.push("");
|
|
2086
|
+
lines.push(...buildAgentSection(hourlyReport, options));
|
|
2087
|
+
lines.push("");
|
|
2088
|
+
lines.push(...buildRhythmSection(hourlyReport, options));
|
|
1690
2089
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
2090
|
+
lines.push("");
|
|
2091
|
+
lines.push(...renderSectionTitle("Activity", options));
|
|
2092
|
+
lines.push(renderMetricRow("strict", requestedMetrics.strictEngagementMs, windowDurationMs, formatDurationHours(requestedMetrics.strictEngagementMs), `${formatSignedDurationHours(actualComparisonMetrics.strictEngagementMs - requestedMetrics.strictEngagementMs)} at ${formatDurationLabel(report.comparisonCutoffMs)}`, "█", "focus", options));
|
|
2093
|
+
lines.push(renderMetricRow("direct", requestedMetrics.directActivityMs, windowDurationMs, formatDurationHours(requestedMetrics.directActivityMs), `${formatSignedDurationHours(actualComparisonMetrics.directActivityMs - requestedMetrics.directActivityMs)} at ${formatDurationLabel(report.comparisonCutoffMs)}`, "▓", "active", options));
|
|
2094
|
+
lines.push(renderMetricRow("agent live", requestedMetrics.agentCoverageMs, windowDurationMs, formatDurationHours(requestedMetrics.agentCoverageMs), "coverage", "▒", "agent", options));
|
|
2095
|
+
lines.push(renderMetricRow("agent sum", requestedMetrics.cumulativeAgentMs, Math.max(windowDurationMs, requestedMetrics.cumulativeAgentMs), formatDurationHours(requestedMetrics.cumulativeAgentMs), `peak ${requestedMetrics.peakConcurrentAgents} concurrent`, "▚", "agent", options));
|
|
2096
|
+
lines.push(`${paint(padRight(" session mix", 14), "muted", options)} ${paint(buildSplitBar([
|
|
2097
|
+
{
|
|
2098
|
+
filledCharacter: "█",
|
|
2099
|
+
value: report.sessionCounts.direct
|
|
2100
|
+
},
|
|
2101
|
+
{
|
|
2102
|
+
filledCharacter: "▓",
|
|
2103
|
+
value: report.sessionCounts.subagent
|
|
2104
|
+
}
|
|
2105
|
+
], summaryBarWidth), "active", options)} ${paint(`${report.sessionCounts.direct} direct / ${report.sessionCounts.subagent} subagent`, "value", options)}`);
|
|
2106
|
+
lines.push("");
|
|
2107
|
+
lines.push(...renderSectionTitle("Tokens", options));
|
|
2108
|
+
const maxBurnValue = Math.max(report.tokenTotals.practicalBurn, report.directTokenTotals.practicalBurn);
|
|
2109
|
+
const maxRawValue = Math.max(report.tokenTotals.rawTotalTokens, report.directTokenTotals.rawTotalTokens);
|
|
2110
|
+
lines.push(renderMetricRow("practical burn", report.tokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.tokenTotals.practicalBurn), `${formatPercentage(report.tokenTotals.practicalBurn / report.tokenTotals.rawTotalTokens)} of raw`, "█", "burn", options, "burn"));
|
|
2111
|
+
lines.push(renderMetricRow("all raw", report.tokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.tokenTotals.rawTotalTokens), `${formatInteger(report.tokenTotals.rawTotalTokens)} total`, "█", "raw", options, "raw"));
|
|
2112
|
+
lines.push(renderMetricRow("direct burn", report.directTokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.directTokenTotals.practicalBurn), `${formatPercentage(report.directTokenTotals.practicalBurn / report.tokenTotals.practicalBurn)} of burn`, "▒", "burn", options, "burn"));
|
|
2113
|
+
lines.push(renderMetricRow("direct raw", report.directTokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.directTokenTotals.rawTotalTokens), `${formatPercentage(report.directTokenTotals.rawTotalTokens / report.tokenTotals.rawTotalTokens)} of raw`, "▒", "raw", options, "raw"));
|
|
2114
|
+
if (report.wakeSummary) {
|
|
2115
|
+
lines.push("");
|
|
2116
|
+
lines.push(...renderSectionTitle("Wake Window", options));
|
|
2117
|
+
lines.push(renderMetricRow("direct awake", report.wakeSummary.directActivityMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.directActivityMs), `of ${formatDurationClock(report.wakeSummary.wakeDurationMs)} wake`, "▓", "active", options));
|
|
2118
|
+
lines.push(renderMetricRow("strict awake", report.wakeSummary.strictEngagementMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.strictEngagementMs), "engaged", "█", "focus", options));
|
|
2119
|
+
lines.push(renderMetricRow("agent awake", report.wakeSummary.agentOnlyMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.agentOnlyMs), "agent-only", "▒", "agent", options));
|
|
2120
|
+
lines.push(renderMetricRow("awake idle", report.wakeSummary.awakeIdleMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.awakeIdleMs), `${formatPercentage(report.wakeSummary.awakeIdlePercentage)} idle`, "░", "idle", options));
|
|
2121
|
+
lines.push(`${paint(padRight(" longest gap", 14), "muted", options)} ${paint(formatDurationClock(report.wakeSummary.longestIdleGapMs), "value", options)} ${dim("largest quiet stretch", options)}`);
|
|
2122
|
+
}
|
|
2123
|
+
for (const groupBreakdown of report.groupBreakdowns) {
|
|
2124
|
+
lines.push("");
|
|
2125
|
+
lines.push(...renderSectionTitle(groupBreakdown.dimension === "model" ? "Model Breakdown" : "Effort Breakdown", options));
|
|
2126
|
+
const maxBreakdownBurn = Math.max(...groupBreakdown.rows.map((row) => row.practicalBurn), 0);
|
|
2127
|
+
for (const row of groupBreakdown.rows) {
|
|
2128
|
+
lines.push(`${paint(padRight(` ${row.key}`, 20), "muted", options)} ${paint(buildBar(row.practicalBurn, maxBreakdownBurn, 14, "█"), "burn", options)} ${paint(padRight(formatCompactInteger(row.practicalBurn), 6), "value", options)} ${dim("burn", options)} ${paint(padRight(formatDurationCompact(row.directActivityMs), 5), "active", options)} ${dim("direct", options)} ${paint(padRight(formatDurationCompact(row.agentCoverageMs), 5), "agent", options)} ${dim("live", options)} ${paint(`${row.sessionCount} s`, "value", options)}`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return lines.join(`
|
|
2132
|
+
`);
|
|
1696
2133
|
}
|
|
1697
|
-
function
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
2134
|
+
function renderShareSummaryReport(report, options, hourlyReport, bestPlaque = null) {
|
|
2135
|
+
const lines = [];
|
|
2136
|
+
const headerLines = buildSummaryHeaderLines(report, hourlyReport);
|
|
2137
|
+
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
2138
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
2139
|
+
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
2140
|
+
lines.push(...buildLogoSection(logoSectionWidth, options, bestPlaque));
|
|
2141
|
+
lines.push("");
|
|
2142
|
+
lines.push(...panelLines);
|
|
2143
|
+
if (hourlyReport) {
|
|
2144
|
+
lines.push("");
|
|
2145
|
+
lines.push(...buildAgentSection(hourlyReport, options));
|
|
2146
|
+
lines.push("");
|
|
2147
|
+
lines.push(...buildRhythmSection(hourlyReport, options));
|
|
2148
|
+
}
|
|
2149
|
+
lines.push("");
|
|
2150
|
+
lines.push(...renderSectionTitle("Snapshot", options));
|
|
2151
|
+
lines.push(renderSnapshotRow("focus", formatDurationHours(report.metrics.strictEngagementMs), "focused time", "focus", options));
|
|
2152
|
+
lines.push(renderSnapshotRow("active", formatDurationHours(report.metrics.directActivityMs), "direct-session movement", "active", options));
|
|
2153
|
+
lines.push(renderSnapshotRow(report.wakeSummary ? "idle" : "quiet", report.wakeSummary ? formatDurationClock(report.wakeSummary.awakeIdleMs) : hourlyReport ? formatDurationCompact(hourlyReport.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs), 0)) : "n/a", report.wakeSummary ? "awake idle" : "quiet hours", "idle", options));
|
|
2154
|
+
lines.push(renderSnapshotRow("burn", formatCompactInteger(report.tokenTotals.practicalBurn), `${formatPercentage(report.tokenTotals.practicalBurn / report.tokenTotals.rawTotalTokens)} of raw`, "burn", options));
|
|
2155
|
+
lines.push(renderSnapshotRow("agents", `${report.metrics.peakConcurrentAgents} peak`, `${formatDurationHours(report.metrics.cumulativeAgentMs)} cumulative`, "agent", options));
|
|
2156
|
+
lines.push(renderSnapshotRow("sessions", `${report.sessionCounts.total}`, `${report.sessionCounts.direct} direct / ${report.sessionCounts.subagent} subagent`, "value", options));
|
|
2157
|
+
return lines.join(`
|
|
2158
|
+
`);
|
|
2159
|
+
}
|
|
2160
|
+
function formatAppliedFilters(report) {
|
|
2161
|
+
const appliedFilters = [];
|
|
2162
|
+
if (report.appliedFilters.workspaceOnlyPrefix) {
|
|
2163
|
+
appliedFilters.push(`workspace=${shortenPath(report.appliedFilters.workspaceOnlyPrefix, 48)}`);
|
|
2164
|
+
}
|
|
2165
|
+
if (report.appliedFilters.sessionKind) {
|
|
2166
|
+
appliedFilters.push(`kind=${report.appliedFilters.sessionKind}`);
|
|
2167
|
+
}
|
|
2168
|
+
if (report.appliedFilters.model) {
|
|
2169
|
+
appliedFilters.push(`model=${report.appliedFilters.model}`);
|
|
2170
|
+
}
|
|
2171
|
+
if (report.appliedFilters.reasoningEffort) {
|
|
2172
|
+
appliedFilters.push(`effort=${report.appliedFilters.reasoningEffort}`);
|
|
2173
|
+
}
|
|
2174
|
+
return appliedFilters;
|
|
2175
|
+
}
|
|
2176
|
+
function buildSummaryHeaderLines(report, hourlyReport) {
|
|
2177
|
+
if (!hourlyReport) {
|
|
2178
|
+
return [
|
|
2179
|
+
formatTimeRange(report.window.start, report.window.end, report.window),
|
|
2180
|
+
`${report.sessionCounts.total} sessions · ${formatDurationHours(report.metrics.strictEngagementMs)} focused · ${formatCompactInteger(report.tokenTotals.practicalBurn)} tokens`,
|
|
2181
|
+
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
2182
|
+
];
|
|
2183
|
+
}
|
|
2184
|
+
return [
|
|
2185
|
+
buildPostureLine(report, hourlyReport),
|
|
2186
|
+
buildBiggestStoryLine(report, hourlyReport),
|
|
2187
|
+
buildSupportFactsLine(report),
|
|
2188
|
+
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
2189
|
+
];
|
|
2190
|
+
}
|
|
2191
|
+
function buildPostureLine(report, hourlyReport) {
|
|
2192
|
+
const directActivityMs = Math.max(report.metrics.directActivityMs, 1);
|
|
2193
|
+
const focusRatio = report.metrics.strictEngagementMs / directActivityMs;
|
|
2194
|
+
const agentCoverageRatio = report.metrics.agentCoverageMs / directActivityMs;
|
|
2195
|
+
const quietRatio = sumQuietMs(hourlyReport) / Math.max(1, report.window.end.getTime() - report.window.start.getTime());
|
|
2196
|
+
const posture = quietRatio >= 0.45 ? "Fragmented day" : agentCoverageRatio >= 0.75 && focusRatio < 0.65 ? "Mostly orchestrating" : focusRatio >= 0.8 ? "Mostly in the loop" : report.metrics.peakConcurrentAgents >= 6 ? "Heavy agent day" : "Balanced day";
|
|
2197
|
+
return `${posture}: ${formatDurationHours(report.metrics.strictEngagementMs)} focused, ${formatDurationHours(report.metrics.agentCoverageMs)} agent live`;
|
|
2198
|
+
}
|
|
2199
|
+
function buildBiggestStoryLine(report, hourlyReport) {
|
|
2200
|
+
const longestQuietRun = findLongestQuietRun(hourlyReport);
|
|
2201
|
+
const peakBurnBucket = hourlyReport.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, hourlyReport.buckets[0]);
|
|
2202
|
+
const quietPhrase = longestQuietRun.durationMs >= 2 * 3600000 ? `long quiet stretch ${describeDayPeriod(longestQuietRun.start, report)}` : "steady rhythm overall";
|
|
2203
|
+
return `Biggest story: ${quietPhrase}, big burn ${describeDayPeriod(peakBurnBucket.start, report)}`;
|
|
2204
|
+
}
|
|
2205
|
+
function buildSupportFactsLine(report) {
|
|
2206
|
+
return `${report.sessionCounts.direct} direct / ${report.sessionCounts.subagent} subagent • ${report.metrics.peakConcurrentAgents} peak • ${formatCompactInteger(report.tokenTotals.practicalBurn)} burn`;
|
|
2207
|
+
}
|
|
2208
|
+
function sumQuietMs(hourlyReport) {
|
|
2209
|
+
return hourlyReport.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs), 0);
|
|
2210
|
+
}
|
|
2211
|
+
function findLongestQuietRun(hourlyReport) {
|
|
2212
|
+
let longestQuietRun = {
|
|
2213
|
+
durationMs: 0,
|
|
2214
|
+
start: hourlyReport.buckets[0]?.start ?? new Date(0)
|
|
2215
|
+
};
|
|
2216
|
+
let currentStart = null;
|
|
2217
|
+
let currentDurationMs = 0;
|
|
2218
|
+
for (const bucket of hourlyReport.buckets) {
|
|
2219
|
+
const quietMs = Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs);
|
|
2220
|
+
const isQuietBucket = quietMs >= 30 * 60000;
|
|
2221
|
+
if (isQuietBucket) {
|
|
2222
|
+
currentStart ??= bucket.start;
|
|
2223
|
+
currentDurationMs += quietMs;
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
if (currentStart && currentDurationMs > longestQuietRun.durationMs) {
|
|
2227
|
+
longestQuietRun = {
|
|
2228
|
+
durationMs: currentDurationMs,
|
|
2229
|
+
start: currentStart
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
currentStart = null;
|
|
2233
|
+
currentDurationMs = 0;
|
|
2234
|
+
}
|
|
2235
|
+
if (currentStart && currentDurationMs > longestQuietRun.durationMs) {
|
|
2236
|
+
longestQuietRun = {
|
|
2237
|
+
durationMs: currentDurationMs,
|
|
2238
|
+
start: currentStart
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
if (longestQuietRun.durationMs > 0) {
|
|
2242
|
+
return longestQuietRun;
|
|
2243
|
+
}
|
|
2244
|
+
const quietestBucket = hourlyReport.buckets.reduce((currentQuietest, bucket) => {
|
|
2245
|
+
const quietMs = Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs);
|
|
2246
|
+
return quietMs > currentQuietest.durationMs ? { durationMs: quietMs, start: bucket.start } : currentQuietest;
|
|
2247
|
+
}, longestQuietRun);
|
|
2248
|
+
return quietestBucket;
|
|
2249
|
+
}
|
|
2250
|
+
function describeDayPeriod(timestamp, report) {
|
|
2251
|
+
const hourOfDay = Number.parseInt(formatHourOfDay(timestamp, report.window), 10);
|
|
2252
|
+
if (hourOfDay >= 21 || hourOfDay < 5) {
|
|
2253
|
+
return "overnight";
|
|
2254
|
+
}
|
|
2255
|
+
if (hourOfDay < 12) {
|
|
2256
|
+
return "this morning";
|
|
2257
|
+
}
|
|
2258
|
+
if (hourOfDay < 17) {
|
|
2259
|
+
return "this afternoon";
|
|
2260
|
+
}
|
|
2261
|
+
return "this evening";
|
|
2262
|
+
}
|
|
2263
|
+
function formatDurationLabel(durationMs) {
|
|
2264
|
+
return `${Math.round(durationMs / 60000)}m`;
|
|
2265
|
+
}
|
|
2266
|
+
function renderMetricRow(label, value, maxValue, primaryText, detailText, filledCharacter, role, options, valueRole = "value") {
|
|
2267
|
+
return `${paint(padRight(` ${label}`, 14), "muted", options)} ${paint(buildBar(value, maxValue, summaryBarWidth, filledCharacter), role, options)} ${paint(padRight(primaryText, 7), valueRole, options)} ${dim(detailText, options)}`;
|
|
2268
|
+
}
|
|
2269
|
+
function renderSnapshotRow(label, primaryText, detailText, role, options) {
|
|
2270
|
+
return `${paint(padRight(` ${label}`, 12), role, options)} ${paint(padRight(primaryText, 10), "value", options)} ${dim(detailText, options)}`;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// src/cli/run-last24h-command.ts
|
|
2274
|
+
async function buildLast24hCommandResult(command, options = {}) {
|
|
2275
|
+
const window = resolveTrailingReportWindow({
|
|
2276
|
+
durationMs: command.hourlyWindowMs,
|
|
2277
|
+
now: options.now
|
|
2278
|
+
});
|
|
2279
|
+
const bestLedgerPromise = readBestLedger({ stateDirectory: options.stateDirectory });
|
|
2280
|
+
const sessionsPromise = readCodexSessions({
|
|
2281
|
+
windowStart: window.start,
|
|
2282
|
+
windowEnd: window.end,
|
|
2283
|
+
sessionRootDirectory: options.sessionRootDirectory
|
|
2284
|
+
});
|
|
2285
|
+
const [bestLedger, sessions] = await Promise.all([
|
|
2286
|
+
bestLedgerPromise,
|
|
2287
|
+
sessionsPromise
|
|
2288
|
+
]);
|
|
2289
|
+
const summaryReport = buildSummaryReport(sessions, {
|
|
2290
|
+
filters: command.filters,
|
|
2291
|
+
groupBy: command.groupBy,
|
|
2292
|
+
idleCutoffMs: command.idleCutoffMs,
|
|
2293
|
+
wakeWindow: command.wakeWindow,
|
|
2294
|
+
window
|
|
2295
|
+
});
|
|
2296
|
+
const hourlyReport = buildHourlyReport(sessions, {
|
|
2297
|
+
filters: command.filters,
|
|
2298
|
+
idleCutoffMs: command.idleCutoffMs,
|
|
2299
|
+
wakeWindow: command.wakeWindow,
|
|
2300
|
+
window
|
|
1706
2301
|
});
|
|
2302
|
+
return {
|
|
2303
|
+
bestLedger,
|
|
2304
|
+
hourlyReport,
|
|
2305
|
+
summaryReport
|
|
2306
|
+
};
|
|
1707
2307
|
}
|
|
1708
|
-
function
|
|
1709
|
-
|
|
2308
|
+
async function runLast24hCommand(command, options = {}) {
|
|
2309
|
+
const commandResult = await buildLast24hCommandResult(command, options);
|
|
2310
|
+
return renderSummaryReport(commandResult.summaryReport, createRenderOptions(command.shareMode), commandResult.hourlyReport, commandResult.bestLedger ? buildBestPlaque(commandResult.bestLedger) : null);
|
|
1710
2311
|
}
|
|
1711
2312
|
|
|
1712
|
-
// src/
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2313
|
+
// src/reporting/build-live-report.ts
|
|
2314
|
+
var doneRecentWindowMs = 15 * 60000;
|
|
2315
|
+
var directSessionWarmMs = 15 * 60000;
|
|
2316
|
+
var recentConcurrencyBucketCount = 15;
|
|
2317
|
+
var recentConcurrencyBucketMs = 60000;
|
|
2318
|
+
var waitingOnUserWarmMs = 30 * 60000;
|
|
2319
|
+
function buildLiveReport(sessions, query) {
|
|
2320
|
+
const observedAt = query.observedAt ?? new Date;
|
|
2321
|
+
const appliedFilters = { ...query.filters };
|
|
2322
|
+
const filteredSessions = filterSessions(sessions, appliedFilters);
|
|
2323
|
+
const liveTaskWindows = filteredSessions.flatMap((session) => session.taskWindows);
|
|
2324
|
+
const runningTaskWindows = liveTaskWindows.filter((taskWindow) => isTaskWindowRunning(taskWindow, observedAt));
|
|
2325
|
+
const waitingOnUserThreads = resolveWaitingOnUserThreads(filteredSessions, observedAt);
|
|
2326
|
+
const childTaskWindows = filteredSessions.filter((session) => session.kind === "subagent").flatMap((session) => session.taskWindows);
|
|
2327
|
+
const recentWindowStart = new Date(observedAt.getTime() - doneRecentWindowMs);
|
|
2328
|
+
return {
|
|
2329
|
+
appliedFilters,
|
|
2330
|
+
doneRecentCount: liveTaskWindows.filter((taskWindow) => isTaskWindowCompletedBetween(taskWindow, recentWindowStart, observedAt)).length,
|
|
2331
|
+
doneRecentWindowMs,
|
|
2332
|
+
doneThisTurnCount: countDoneThisTurn(filteredSessions, childTaskWindows, observedAt),
|
|
2333
|
+
observedAt,
|
|
2334
|
+
peakTodayCount: peakConcurrency([
|
|
2335
|
+
collectTaskIntervalsForWindow(liveTaskWindows, observedAt, startOfLocalDay2(observedAt), observedAt)
|
|
2336
|
+
]),
|
|
2337
|
+
recentConcurrencyValues: buildRecentConcurrencyValues(liveTaskWindows, observedAt),
|
|
2338
|
+
runningCount: runningTaskWindows.length,
|
|
2339
|
+
runningLocations: buildRunningLocations(runningTaskWindows),
|
|
2340
|
+
waitingThreads: waitingOnUserThreads,
|
|
2341
|
+
waitingOnUserCount: waitingOnUserThreads.length,
|
|
2342
|
+
waitingOnUserLocations: buildWaitingOnUserLocations(waitingOnUserThreads),
|
|
2343
|
+
scope: appliedFilters.workspaceOnlyPrefix ? "workspace" : "global",
|
|
2344
|
+
workspacePrefix: appliedFilters.workspaceOnlyPrefix
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
function buildRunningLocations(runningTaskWindows) {
|
|
2348
|
+
const runningLocations = new Map;
|
|
2349
|
+
for (const taskWindow of runningTaskWindows) {
|
|
2350
|
+
runningLocations.set(taskWindow.cwd, (runningLocations.get(taskWindow.cwd) ?? 0) + 1);
|
|
1717
2351
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
for (
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
2352
|
+
return [...runningLocations.entries()].map(([cwd, runningCount]) => ({ cwd, runningCount })).sort((leftLocation, rightLocation) => rightLocation.runningCount - leftLocation.runningCount || leftLocation.cwd.localeCompare(rightLocation.cwd));
|
|
2353
|
+
}
|
|
2354
|
+
function buildWaitingOnUserLocations(waitingOnUserThreads) {
|
|
2355
|
+
const waitingLocations = new Map;
|
|
2356
|
+
for (const waitingThread of waitingOnUserThreads) {
|
|
2357
|
+
waitingLocations.set(waitingThread.cwd, (waitingLocations.get(waitingThread.cwd) ?? 0) + 1);
|
|
2358
|
+
}
|
|
2359
|
+
return [...waitingLocations.entries()].map(([cwd, waitingCount]) => ({ cwd, waitingCount })).sort((leftLocation, rightLocation) => rightLocation.waitingCount - leftLocation.waitingCount || leftLocation.cwd.localeCompare(rightLocation.cwd));
|
|
2360
|
+
}
|
|
2361
|
+
function buildRecentConcurrencyValues(taskWindows, observedAt) {
|
|
2362
|
+
const values = [];
|
|
2363
|
+
for (let bucketIndex = recentConcurrencyBucketCount - 1;bucketIndex >= 0; bucketIndex -= 1) {
|
|
2364
|
+
const bucketStart = new Date(observedAt.getTime() - (bucketIndex + 1) * recentConcurrencyBucketMs);
|
|
2365
|
+
const bucketEnd = new Date(bucketStart.getTime() + recentConcurrencyBucketMs);
|
|
2366
|
+
values.push(peakConcurrency([
|
|
2367
|
+
collectTaskIntervalsForWindow(taskWindows, observedAt, bucketStart, bucketEnd)
|
|
2368
|
+
]));
|
|
2369
|
+
}
|
|
2370
|
+
return values;
|
|
2371
|
+
}
|
|
2372
|
+
function collectTaskIntervalsForWindow(taskWindows, observedAt, windowStart, windowEnd) {
|
|
2373
|
+
return taskWindows.flatMap((taskWindow) => {
|
|
2374
|
+
const taskWindowInterval = buildTaskWindowInterval(taskWindow, observedAt);
|
|
2375
|
+
if (!taskWindowInterval) {
|
|
2376
|
+
return [];
|
|
1728
2377
|
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
2378
|
+
const clippedStart = new Date(Math.max(taskWindowInterval.start.getTime(), windowStart.getTime()));
|
|
2379
|
+
const clippedEnd = new Date(Math.min(taskWindowInterval.end.getTime(), windowEnd.getTime()));
|
|
2380
|
+
if (clippedStart.getTime() >= clippedEnd.getTime()) {
|
|
2381
|
+
return [];
|
|
1732
2382
|
}
|
|
2383
|
+
return [{ start: clippedStart, end: clippedEnd }];
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
function countDoneThisTurn(sessions, subagentTaskWindows, observedAt) {
|
|
2387
|
+
const rootTurnAnchor = resolveRootTurnAnchor(sessions, observedAt);
|
|
2388
|
+
if (!rootTurnAnchor) {
|
|
2389
|
+
return 0;
|
|
1733
2390
|
}
|
|
1734
|
-
|
|
2391
|
+
return subagentTaskWindows.filter((taskWindow) => {
|
|
2392
|
+
if (taskWindow.parentSessionId !== rootTurnAnchor.sessionId || !taskWindow.completedAt) {
|
|
2393
|
+
return false;
|
|
2394
|
+
}
|
|
2395
|
+
return taskWindow.completedAt.getTime() > rootTurnAnchor.startedAt.getTime() && taskWindow.completedAt.getTime() <= observedAt.getTime();
|
|
2396
|
+
}).length;
|
|
2397
|
+
}
|
|
2398
|
+
function resolveRootTurnAnchor(sessions, observedAt) {
|
|
2399
|
+
const warmCutoff = observedAt.getTime() - directSessionWarmMs;
|
|
2400
|
+
const warmDirectSessions = sessions.filter((session) => session.kind === "direct").filter((session) => session.lastTimestamp.getTime() >= warmCutoff).filter((session) => session.userMessageTimestamps.length > 0);
|
|
2401
|
+
if (warmDirectSessions.length === 0) {
|
|
1735
2402
|
return null;
|
|
1736
2403
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
const slopeChanges = intervals.flatMap(buildSlopeChanges);
|
|
1741
|
-
if (slopeChanges.length === 0) {
|
|
2404
|
+
const activeRootSession = warmDirectSessions.reduce((latestSession, session) => session.lastTimestamp.getTime() > latestSession.lastTimestamp.getTime() ? session : latestSession);
|
|
2405
|
+
const latestUserMessageTimestamp = activeRootSession.userMessageTimestamps[activeRootSession.userMessageTimestamps.length - 1] ?? null;
|
|
2406
|
+
if (!latestUserMessageTimestamp) {
|
|
1742
2407
|
return null;
|
|
1743
2408
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
if (currentValue > bestValue) {
|
|
1755
|
-
bestValue = currentValue;
|
|
1756
|
-
bestTimestampMs = timestampMs;
|
|
2409
|
+
return {
|
|
2410
|
+
sessionId: activeRootSession.sessionId,
|
|
2411
|
+
startedAt: latestUserMessageTimestamp
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
function resolveWaitingOnUserThreads(sessions, observedAt) {
|
|
2415
|
+
const warmCutoff = observedAt.getTime() - waitingOnUserWarmMs;
|
|
2416
|
+
return sessions.flatMap((session) => {
|
|
2417
|
+
if (session.kind !== "direct" || session.taskWindows.length === 0) {
|
|
2418
|
+
return [];
|
|
1757
2419
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
2420
|
+
const latestTaskWindow = session.taskWindows.reduce((latestWindow, taskWindow) => taskWindow.startedAt.getTime() > latestWindow.startedAt.getTime() ? taskWindow : latestWindow);
|
|
2421
|
+
const latestUserMessageTimestamp = session.userMessageTimestamps[session.userMessageTimestamps.length - 1] ?? null;
|
|
2422
|
+
if (latestTaskWindow.completedAt === null || !latestUserMessageTimestamp || latestTaskWindow.completedAt.getTime() <= latestUserMessageTimestamp.getTime()) {
|
|
2423
|
+
return [];
|
|
1761
2424
|
}
|
|
1762
|
-
|
|
2425
|
+
if (latestTaskWindow.completedAt.getTime() < warmCutoff) {
|
|
2426
|
+
return [];
|
|
2427
|
+
}
|
|
2428
|
+
return [
|
|
2429
|
+
{
|
|
2430
|
+
cwd: session.cwd,
|
|
2431
|
+
sessionId: session.sessionId,
|
|
2432
|
+
waitDurationMs: observedAt.getTime() - latestTaskWindow.completedAt.getTime(),
|
|
2433
|
+
waitingSince: latestTaskWindow.completedAt
|
|
2434
|
+
}
|
|
2435
|
+
];
|
|
2436
|
+
}).sort((leftThread, rightThread) => rightThread.waitDurationMs - leftThread.waitDurationMs || leftThread.waitingSince.getTime() - rightThread.waitingSince.getTime());
|
|
2437
|
+
}
|
|
2438
|
+
function startOfLocalDay2(timestamp) {
|
|
2439
|
+
return new Date(timestamp.getFullYear(), timestamp.getMonth(), timestamp.getDate(), 0, 0, 0, 0);
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
// src/reporting/render-live-report.ts
|
|
2443
|
+
import { basename } from "node:path";
|
|
2444
|
+
var bigDigitGlyphs = {
|
|
2445
|
+
"0": ["█▀█", "█ █", "▀▀▀"],
|
|
2446
|
+
"1": [" ▄█", " █", "▄▄█"],
|
|
2447
|
+
"2": ["█▀█", " ▄▀", "█▄▄"],
|
|
2448
|
+
"3": ["█▀█", " ▀▄", "█▄█"],
|
|
2449
|
+
"4": ["█ █", "█▄█", " █"],
|
|
2450
|
+
"5": ["█▀▀", "▀▀▄", "▄▄█"],
|
|
2451
|
+
"6": ["█▀▀", "█▀█", "█▄█"],
|
|
2452
|
+
"7": ["█▀█", " █", " █"],
|
|
2453
|
+
"8": ["█▀█", "█▄█", "█▄█"],
|
|
2454
|
+
"9": ["█▀█", "█▄█", " █"]
|
|
2455
|
+
};
|
|
2456
|
+
function renderLiveReport(report, options) {
|
|
2457
|
+
const headerLines = renderPanel("idletime live", [
|
|
2458
|
+
buildScopeLine(report),
|
|
2459
|
+
`observed ${formatTimestamp(report.observedAt, { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone })} • refresh 5s`,
|
|
2460
|
+
buildContextLine(report)
|
|
2461
|
+
], options);
|
|
2462
|
+
const panelWidth = measureVisibleTextWidth(headerLines[0] ?? "");
|
|
2463
|
+
const lines = [];
|
|
2464
|
+
lines.push(...buildLogoSection(panelWidth, options));
|
|
2465
|
+
lines.push("");
|
|
2466
|
+
lines.push(...headerLines);
|
|
2467
|
+
lines.push("");
|
|
2468
|
+
lines.push(...buildScoreboardSection(report, options));
|
|
2469
|
+
return lines.join(`
|
|
2470
|
+
`);
|
|
2471
|
+
}
|
|
2472
|
+
function renderLiveErrorReport(workspacePrefix, error, options) {
|
|
2473
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2474
|
+
const panelLines = renderPanel("idletime live", [
|
|
2475
|
+
workspacePrefix ? `scope workspace • ${shortenPath(workspacePrefix, 40)}` : "scope global",
|
|
2476
|
+
"live refresh failed; retrying in 5s",
|
|
2477
|
+
errorMessage
|
|
2478
|
+
], options);
|
|
2479
|
+
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
2480
|
+
return [...buildLogoSection(panelWidth, options), "", ...panelLines].join(`
|
|
2481
|
+
`);
|
|
2482
|
+
}
|
|
2483
|
+
function buildScopeLine(report) {
|
|
2484
|
+
return report.scope === "workspace" && report.workspacePrefix ? `scope workspace • ${shortenPath(report.workspacePrefix, 40)}` : "scope global";
|
|
2485
|
+
}
|
|
2486
|
+
function buildContextLine(report) {
|
|
2487
|
+
const filterParts = [];
|
|
2488
|
+
if (report.appliedFilters.model) {
|
|
2489
|
+
filterParts.push(`model ${report.appliedFilters.model}`);
|
|
2490
|
+
}
|
|
2491
|
+
if (report.appliedFilters.reasoningEffort) {
|
|
2492
|
+
filterParts.push(`effort ${report.appliedFilters.reasoningEffort}`);
|
|
2493
|
+
}
|
|
2494
|
+
if (report.appliedFilters.sessionKind) {
|
|
2495
|
+
filterParts.push(`kind ${report.appliedFilters.sessionKind}`);
|
|
2496
|
+
}
|
|
2497
|
+
return filterParts.length > 0 ? filterParts.join(" • ") : "all sessions";
|
|
2498
|
+
}
|
|
2499
|
+
function renderBigDigits(value) {
|
|
2500
|
+
const rows = ["", "", ""];
|
|
2501
|
+
for (const character of value) {
|
|
2502
|
+
const glyph = bigDigitGlyphs[character] ?? [" ", " ? ", " "];
|
|
2503
|
+
rows[0] += `${glyph[0]} `;
|
|
2504
|
+
rows[1] += `${glyph[1]} `;
|
|
2505
|
+
rows[2] += `${glyph[2]} `;
|
|
2506
|
+
}
|
|
2507
|
+
return rows;
|
|
2508
|
+
}
|
|
2509
|
+
function buildScoreboardSection(report, options) {
|
|
2510
|
+
const waitingLabel = "waiting on you";
|
|
2511
|
+
const runningLabel = "running";
|
|
2512
|
+
const waitingDigits = renderBigDigits(report.waitingOnUserCount.toString());
|
|
2513
|
+
const runningDigits = renderBigDigits(report.runningCount.toString());
|
|
2514
|
+
const leftColumnWidth = Math.max(waitingLabel.length, ...waitingDigits.map((line) => line.length));
|
|
2515
|
+
const rightColumnWidth = Math.max(runningLabel.length, ...runningDigits.map((line) => line.length));
|
|
2516
|
+
const columnGap = " ";
|
|
2517
|
+
const lines = [
|
|
2518
|
+
joinScoreboardColumns(waitingLabel, runningLabel, leftColumnWidth, rightColumnWidth, options),
|
|
2519
|
+
...waitingDigits.map((line, lineIndex) => joinScoreboardColumns(line, runningDigits[lineIndex] ?? "", leftColumnWidth, rightColumnWidth, options)),
|
|
2520
|
+
"",
|
|
2521
|
+
`${paint(" recent ", "muted", options)}${paint(buildSparkline(report.recentConcurrencyValues), "agent", options)}`,
|
|
2522
|
+
...buildRunningLocationLines(report, options),
|
|
2523
|
+
...buildWaitingLocationLines(report, options),
|
|
2524
|
+
...buildWaitingThreadLines(report, options),
|
|
2525
|
+
`${paint(" this turn ", "muted", options)}${paint(`${report.doneThisTurnCount} done`, "value", options)}`,
|
|
2526
|
+
`${paint(" today peak ", "muted", options)}${paint(`${report.peakTodayCount} concurrent`, "value", options)}`
|
|
2527
|
+
];
|
|
2528
|
+
function joinScoreboardColumns(leftValue, rightValue, leftWidth, rightWidth, renderOptions) {
|
|
2529
|
+
return ` ${paint(padRight(leftValue, leftWidth), "agent", renderOptions)}${columnGap}${paint(padRight(rightValue, rightWidth), "value", renderOptions)}`;
|
|
2530
|
+
}
|
|
2531
|
+
return lines;
|
|
2532
|
+
}
|
|
2533
|
+
function buildRunningLocationLines(report, options) {
|
|
2534
|
+
if (report.runningLocations.length === 0) {
|
|
2535
|
+
return [
|
|
2536
|
+
`${paint(" running at ", "muted", options)}${paint("no active tasks", "muted", options)}`
|
|
2537
|
+
];
|
|
1763
2538
|
}
|
|
1764
|
-
|
|
1765
|
-
|
|
2539
|
+
const visibleLocations = report.runningLocations.slice(0, 3);
|
|
2540
|
+
const lines = visibleLocations.map((location, locationIndex) => {
|
|
2541
|
+
const label = locationIndex === 0 ? " running at " : " ";
|
|
2542
|
+
return `${paint(label, "muted", options)}${paint(`${location.runningCount} ${formatLocationLabel(location.cwd)}`, "value", options)}`;
|
|
2543
|
+
});
|
|
2544
|
+
const hiddenLocationCount = report.runningLocations.length - visibleLocations.length;
|
|
2545
|
+
if (hiddenLocationCount > 0) {
|
|
2546
|
+
lines.push(`${paint(" ", "muted", options)}${paint(`+${hiddenLocationCount} more`, "muted", options)}`);
|
|
1766
2547
|
}
|
|
1767
|
-
return
|
|
2548
|
+
return lines;
|
|
1768
2549
|
}
|
|
1769
|
-
function
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2550
|
+
function buildWaitingLocationLines(report, options) {
|
|
2551
|
+
if (report.waitingOnUserLocations.length === 0) {
|
|
2552
|
+
return [
|
|
2553
|
+
`${paint(" waiting at ", "muted", options)}${paint("nothing waiting", "muted", options)}`
|
|
2554
|
+
];
|
|
2555
|
+
}
|
|
2556
|
+
const visibleLocations = report.waitingOnUserLocations.slice(0, 3);
|
|
2557
|
+
const lines = visibleLocations.map((location, locationIndex) => {
|
|
2558
|
+
const label = locationIndex === 0 ? " waiting at " : " ";
|
|
2559
|
+
return `${paint(label, "muted", options)}${paint(`${location.waitingCount} ${formatLocationLabel(location.cwd)}`, "value", options)}`;
|
|
2560
|
+
});
|
|
2561
|
+
const hiddenLocationCount = report.waitingOnUserLocations.length - visibleLocations.length;
|
|
2562
|
+
if (hiddenLocationCount > 0) {
|
|
2563
|
+
lines.push(`${paint(" ", "muted", options)}${paint(`+${hiddenLocationCount} more`, "muted", options)}`);
|
|
2564
|
+
}
|
|
2565
|
+
return lines;
|
|
2566
|
+
}
|
|
2567
|
+
function buildWaitingThreadLines(report, options) {
|
|
2568
|
+
if (report.waitingThreads.length === 0) {
|
|
1773
2569
|
return [];
|
|
1774
2570
|
}
|
|
1775
|
-
return
|
|
1776
|
-
|
|
1777
|
-
{
|
|
1778
|
-
|
|
1779
|
-
{ timestampMs: endMs + rollingWindowDurationMs, deltaSlope: 1 }
|
|
1780
|
-
];
|
|
2571
|
+
return report.waitingThreads.slice(0, 3).map((waitingThread, waitingIndex) => {
|
|
2572
|
+
const label = waitingIndex === 0 ? " top waiting " : " ";
|
|
2573
|
+
return `${paint(label, "muted", options)}${paint(`${formatLocationLabel(waitingThread.cwd)} • ${formatDurationCompact(waitingThread.waitDurationMs)} • ${formatThreadLabel(waitingThread.sessionId)}`, "value", options)}`;
|
|
2574
|
+
});
|
|
1781
2575
|
}
|
|
1782
|
-
function
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
windowEnd: new Date(observedAtMs)
|
|
1788
|
-
};
|
|
2576
|
+
function formatLocationLabel(cwd) {
|
|
2577
|
+
if (cwd.endsWith("/.agents")) {
|
|
2578
|
+
return "~/.agents";
|
|
2579
|
+
}
|
|
2580
|
+
return basename(cwd) || shortenPath(cwd, 24);
|
|
1789
2581
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
function buildBestMetricCandidates(sessions, options = {}) {
|
|
1793
|
-
const idleCutoffMs = options.idleCutoffMs ?? defaultBestMetricsIdleCutoffMs;
|
|
1794
|
-
const activityMetrics = buildActivityMetrics(sessions, idleCutoffMs);
|
|
1795
|
-
return {
|
|
1796
|
-
bestConcurrentAgents: findBestConcurrentAgents(activityMetrics.perSubagentBlocks),
|
|
1797
|
-
best24hRawBurn: findBestRollingWindowTotal(sessions.flatMap((session) => buildTokenDeltaPoints(session.tokenPoints).map((tokenDeltaPoint) => ({
|
|
1798
|
-
timestamp: tokenDeltaPoint.timestamp,
|
|
1799
|
-
value: tokenDeltaPoint.deltaUsage.totalTokens
|
|
1800
|
-
})))),
|
|
1801
|
-
best24hAgentSumMs: findBestRollingWindowOverlap(activityMetrics.perSubagentBlocks.flatMap((sessionBlocks) => sessionBlocks))
|
|
1802
|
-
};
|
|
2582
|
+
function formatThreadLabel(sessionId) {
|
|
2583
|
+
return sessionId.slice(-6);
|
|
1803
2584
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
2585
|
+
|
|
2586
|
+
// src/cli/run-live-command.ts
|
|
2587
|
+
var liveRefreshIntervalMs = 5000;
|
|
2588
|
+
var enterLiveScreenSequence = "\x1B[?1049h\x1B[2J\x1B[H\x1B[?25l";
|
|
2589
|
+
var exitLiveScreenSequence = "\x1B[0m\x1B[?25h\x1B[?1049l";
|
|
2590
|
+
async function runLiveCommand(command) {
|
|
2591
|
+
const renderOptions = createRenderOptions(false);
|
|
2592
|
+
if (!process.stdout.isTTY) {
|
|
2593
|
+
const liveReport = await takeLiveSnapshot(command);
|
|
2594
|
+
console.log(renderLiveReport(liveReport, renderOptions));
|
|
2595
|
+
return;
|
|
1811
2596
|
}
|
|
1812
|
-
|
|
1813
|
-
let
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2597
|
+
let shouldStop = false;
|
|
2598
|
+
let previousFrameLineCount = 0;
|
|
2599
|
+
const canCaptureInput = process.stdin.isTTY && typeof process.stdin.setRawMode === "function";
|
|
2600
|
+
const stopLiveLoop = () => {
|
|
2601
|
+
shouldStop = true;
|
|
2602
|
+
};
|
|
2603
|
+
const handleInput = (input) => {
|
|
2604
|
+
const inputText = typeof input === "string" ? input : input.toString("utf8");
|
|
2605
|
+
if (inputText.includes("\x03") || inputText.toLowerCase().includes("q")) {
|
|
2606
|
+
stopLiveLoop();
|
|
1821
2607
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2608
|
+
};
|
|
2609
|
+
process.on("SIGINT", stopLiveLoop);
|
|
2610
|
+
process.on("SIGTERM", stopLiveLoop);
|
|
2611
|
+
if (canCaptureInput) {
|
|
2612
|
+
process.stdin.setRawMode(true);
|
|
2613
|
+
process.stdin.resume();
|
|
2614
|
+
process.stdin.on("data", handleInput);
|
|
2615
|
+
}
|
|
2616
|
+
process.stdout.write(enterLiveScreenSequence);
|
|
2617
|
+
try {
|
|
2618
|
+
while (!shouldStop) {
|
|
2619
|
+
const observedAt = new Date;
|
|
2620
|
+
try {
|
|
2621
|
+
const liveReport = await takeLiveSnapshot(command, {
|
|
2622
|
+
observedAt
|
|
2623
|
+
});
|
|
2624
|
+
previousFrameLineCount = drawFrame(renderLiveReport(liveReport, renderOptions), previousFrameLineCount);
|
|
2625
|
+
} catch (error) {
|
|
2626
|
+
previousFrameLineCount = drawFrame(renderLiveErrorReport(command.filters.workspaceOnlyPrefix, error, renderOptions), previousFrameLineCount);
|
|
2627
|
+
}
|
|
2628
|
+
if (shouldStop) {
|
|
2629
|
+
break;
|
|
2630
|
+
}
|
|
2631
|
+
await waitForNextRefresh();
|
|
1825
2632
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2633
|
+
} finally {
|
|
2634
|
+
process.off("SIGINT", stopLiveLoop);
|
|
2635
|
+
process.off("SIGTERM", stopLiveLoop);
|
|
2636
|
+
if (canCaptureInput) {
|
|
2637
|
+
process.stdin.off("data", handleInput);
|
|
2638
|
+
process.stdin.setRawMode(false);
|
|
2639
|
+
process.stdin.pause();
|
|
1833
2640
|
}
|
|
2641
|
+
process.stdout.write(exitLiveScreenSequence);
|
|
1834
2642
|
}
|
|
1835
|
-
return bestRecord;
|
|
1836
2643
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
return
|
|
2644
|
+
async function takeLiveSnapshot(command, options = {}) {
|
|
2645
|
+
const observedAt = options.observedAt ?? new Date;
|
|
2646
|
+
const recentWindow = resolveTrailingReportWindow({
|
|
2647
|
+
durationMs: 24 * 60 * 60 * 1000,
|
|
2648
|
+
now: observedAt
|
|
2649
|
+
});
|
|
2650
|
+
const sessions = await readCodexSessions({
|
|
2651
|
+
windowStart: recentWindow.start,
|
|
2652
|
+
windowEnd: observedAt,
|
|
2653
|
+
sessionRootDirectory: options.sessionRootDirectory
|
|
2654
|
+
});
|
|
2655
|
+
return buildLiveReport(sessions, {
|
|
2656
|
+
filters: command.filters,
|
|
2657
|
+
observedAt
|
|
2658
|
+
});
|
|
1849
2659
|
}
|
|
1850
|
-
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
if (directoryEntry.isDirectory()) {
|
|
1859
|
-
pendingDirectories.push(entryPath);
|
|
1860
|
-
continue;
|
|
1861
|
-
}
|
|
1862
|
-
if (directoryEntry.isFile() && directoryEntry.name.endsWith(".jsonl")) {
|
|
1863
|
-
sessionFiles.push(entryPath);
|
|
1864
|
-
}
|
|
2660
|
+
function drawFrame(frameText, previousFrameLineCount) {
|
|
2661
|
+
const frameLines = frameText.split(`
|
|
2662
|
+
`);
|
|
2663
|
+
process.stdout.write("\x1B[H");
|
|
2664
|
+
for (const [lineIndex, line] of frameLines.entries()) {
|
|
2665
|
+
if (lineIndex > 0) {
|
|
2666
|
+
process.stdout.write(`
|
|
2667
|
+
\r`);
|
|
1865
2668
|
}
|
|
2669
|
+
process.stdout.write("\x1B[2K");
|
|
2670
|
+
process.stdout.write(line);
|
|
1866
2671
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
try {
|
|
1871
|
-
return await readdir2(directoryPath, { withFileTypes: true });
|
|
1872
|
-
} catch (error) {
|
|
1873
|
-
if (isMissingDirectoryError2(error)) {
|
|
1874
|
-
return [];
|
|
1875
|
-
}
|
|
1876
|
-
throw error;
|
|
2672
|
+
for (let lineIndex = frameLines.length;lineIndex < previousFrameLineCount; lineIndex += 1) {
|
|
2673
|
+
process.stdout.write(`
|
|
2674
|
+
\r\x1B[2K`);
|
|
1877
2675
|
}
|
|
2676
|
+
return frameLines.length;
|
|
1878
2677
|
}
|
|
1879
|
-
function
|
|
1880
|
-
return
|
|
2678
|
+
function waitForNextRefresh() {
|
|
2679
|
+
return new Promise((resolve) => {
|
|
2680
|
+
setTimeout(resolve, liveRefreshIntervalMs);
|
|
2681
|
+
});
|
|
1881
2682
|
}
|
|
1882
2683
|
|
|
1883
|
-
// src/best-metrics/
|
|
1884
|
-
import {
|
|
1885
|
-
import {
|
|
1886
|
-
import {
|
|
1887
|
-
|
|
1888
|
-
|
|
2684
|
+
// src/best-metrics/notification-delivery.ts
|
|
2685
|
+
import { execFile } from "node:child_process";
|
|
2686
|
+
import { existsSync } from "node:fs";
|
|
2687
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2688
|
+
import { promisify } from "node:util";
|
|
2689
|
+
var execFileAsync = promisify(execFile);
|
|
2690
|
+
async function deliverLocalNotifications(notifications, options = {}) {
|
|
2691
|
+
const platform = options.platform ?? process.platform;
|
|
2692
|
+
if (platform !== "darwin" || notifications.length === 0) {
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
const notifier = options.notifier ?? sendMacOsNotification;
|
|
2696
|
+
for (const notification of notifications) {
|
|
2697
|
+
try {
|
|
2698
|
+
await notifier(notification);
|
|
2699
|
+
} catch {
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
async function sendMacOsNotification(notification) {
|
|
2705
|
+
const notificationIconPath = resolveNotificationIconPath();
|
|
1889
2706
|
try {
|
|
1890
|
-
|
|
1891
|
-
|
|
2707
|
+
await execFileAsync("terminal-notifier", [
|
|
2708
|
+
"-title",
|
|
2709
|
+
notification.title,
|
|
2710
|
+
"-message",
|
|
2711
|
+
notification.body,
|
|
2712
|
+
...notificationIconPath ? ["-appIcon", pathToFileURL(notificationIconPath).href] : []
|
|
2713
|
+
]);
|
|
2714
|
+
return;
|
|
1892
2715
|
} catch (error) {
|
|
1893
|
-
if (
|
|
1894
|
-
|
|
2716
|
+
if (!isCommandMissingError(error)) {
|
|
2717
|
+
throw error;
|
|
1895
2718
|
}
|
|
1896
|
-
throw error;
|
|
1897
2719
|
}
|
|
2720
|
+
await execFileAsync("osascript", [
|
|
2721
|
+
"-e",
|
|
2722
|
+
`display notification "${escapeAppleScriptText(notification.body)}" with title "${escapeAppleScriptText(notification.title)}"`
|
|
2723
|
+
]);
|
|
1898
2724
|
}
|
|
1899
|
-
function
|
|
1900
|
-
|
|
1901
|
-
const version = readNumber(ledgerRecord, "version", "bestMetricsLedger");
|
|
1902
|
-
if (version !== bestMetricsLedgerVersion) {
|
|
1903
|
-
throw new Error(`bestMetricsLedger.version must be ${bestMetricsLedgerVersion}.`);
|
|
1904
|
-
}
|
|
1905
|
-
return {
|
|
1906
|
-
version: bestMetricsLedgerVersion,
|
|
1907
|
-
initializedAt: readIsoTimestamp(ledgerRecord.initializedAt, "bestMetricsLedger.initializedAt"),
|
|
1908
|
-
lastScannedAt: readIsoTimestamp(ledgerRecord.lastScannedAt, "bestMetricsLedger.lastScannedAt"),
|
|
1909
|
-
bestConcurrentAgents: parseBestMetricRecord(ledgerRecord.bestConcurrentAgents, "bestMetricsLedger.bestConcurrentAgents"),
|
|
1910
|
-
best24hRawBurn: parseBestMetricRecord(ledgerRecord.best24hRawBurn, "bestMetricsLedger.best24hRawBurn"),
|
|
1911
|
-
best24hAgentSumMs: parseBestMetricRecord(ledgerRecord.best24hAgentSumMs, "bestMetricsLedger.best24hAgentSumMs")
|
|
1912
|
-
};
|
|
2725
|
+
function escapeAppleScriptText(value) {
|
|
2726
|
+
return value.replaceAll("\\", "\\\\").replaceAll('"', "\\\"");
|
|
1913
2727
|
}
|
|
1914
|
-
function
|
|
1915
|
-
|
|
2728
|
+
function resolveNotificationIconPath() {
|
|
2729
|
+
const candidatePaths = [
|
|
2730
|
+
fileURLToPath(new URL("../../assets/idle-time-notification-icon.png", import.meta.url)),
|
|
2731
|
+
fileURLToPath(new URL("../assets/idle-time-notification-icon.png", import.meta.url))
|
|
2732
|
+
];
|
|
2733
|
+
return candidatePaths.find((candidatePath) => existsSync(candidatePath)) ?? null;
|
|
1916
2734
|
}
|
|
1917
|
-
function
|
|
1918
|
-
|
|
1919
|
-
version: ledger.version,
|
|
1920
|
-
initializedAt: ledger.initializedAt.toISOString(),
|
|
1921
|
-
lastScannedAt: ledger.lastScannedAt.toISOString(),
|
|
1922
|
-
bestConcurrentAgents: serializeBestMetricRecord(ledger.bestConcurrentAgents),
|
|
1923
|
-
best24hRawBurn: serializeBestMetricRecord(ledger.best24hRawBurn),
|
|
1924
|
-
best24hAgentSumMs: serializeBestMetricRecord(ledger.best24hAgentSumMs)
|
|
1925
|
-
};
|
|
1926
|
-
return `${JSON.stringify(serializedLedger, null, 2)}
|
|
1927
|
-
`;
|
|
2735
|
+
function isCommandMissingError(error) {
|
|
2736
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1928
2737
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2738
|
+
|
|
2739
|
+
// src/best-metrics/near-best-notifications.ts
|
|
2740
|
+
import { mkdir, readFile as readFile3, rename, writeFile } from "node:fs/promises";
|
|
2741
|
+
import { homedir as homedir3 } from "node:os";
|
|
2742
|
+
import { join as join3 } from "node:path";
|
|
2743
|
+
var nearBestNotificationStateFileName = "near-best-notifications-v1.json";
|
|
2744
|
+
var nearBestNotificationVersion = 1;
|
|
2745
|
+
async function notifyNearBestMetrics(currentMetrics, ledger, options = {}) {
|
|
2746
|
+
const now = options.now ?? new Date;
|
|
2747
|
+
const state = await ensureNearBestNotificationState(options);
|
|
2748
|
+
if (!state.nearBestEnabled) {
|
|
2749
|
+
return [];
|
|
1932
2750
|
}
|
|
1933
|
-
const
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
2751
|
+
const metricsToNotify = buildNearBestMetricKeys(currentMetrics, ledger, state, now);
|
|
2752
|
+
if (metricsToNotify.length === 0) {
|
|
2753
|
+
return [];
|
|
2754
|
+
}
|
|
2755
|
+
const nextState = {
|
|
2756
|
+
...state,
|
|
2757
|
+
lastNotifiedAt: {
|
|
2758
|
+
...state.lastNotifiedAt,
|
|
2759
|
+
...Object.fromEntries(metricsToNotify.map((metric) => [metric, now]))
|
|
2760
|
+
}
|
|
1939
2761
|
};
|
|
2762
|
+
await writeNearBestNotificationState(nextState, options);
|
|
2763
|
+
await deliverLocalNotifications(metricsToNotify.map((metric) => buildNearBestNotification(metric, currentMetrics[metric], ledger[metric]?.value ?? 0)), options);
|
|
2764
|
+
return metricsToNotify;
|
|
2765
|
+
}
|
|
2766
|
+
async function ensureNearBestNotificationState(options) {
|
|
2767
|
+
const existingState = await readNearBestNotificationState(options);
|
|
2768
|
+
if (existingState) {
|
|
2769
|
+
return existingState;
|
|
2770
|
+
}
|
|
2771
|
+
const defaultState = createDefaultNearBestNotificationState();
|
|
2772
|
+
await writeNearBestNotificationState(defaultState, options);
|
|
2773
|
+
return defaultState;
|
|
2774
|
+
}
|
|
2775
|
+
async function readNearBestNotificationState(options) {
|
|
2776
|
+
try {
|
|
2777
|
+
const rawStateText = await readFile3(resolveNearBestNotificationStatePath(options), "utf8");
|
|
2778
|
+
return parseNearBestNotificationState(JSON.parse(rawStateText));
|
|
2779
|
+
} catch (error) {
|
|
2780
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
2781
|
+
return null;
|
|
2782
|
+
}
|
|
2783
|
+
throw error;
|
|
2784
|
+
}
|
|
1940
2785
|
}
|
|
1941
|
-
function
|
|
1942
|
-
|
|
1943
|
-
|
|
2786
|
+
function parseNearBestNotificationState(value) {
|
|
2787
|
+
const stateRecord = expectObject(value, "nearBestNotificationState");
|
|
2788
|
+
const version = readNumber(stateRecord, "version", "nearBestNotificationState");
|
|
2789
|
+
if (version !== nearBestNotificationVersion) {
|
|
2790
|
+
throw new Error(`nearBestNotificationState.version must be ${nearBestNotificationVersion}.`);
|
|
1944
2791
|
}
|
|
2792
|
+
const lastNotifiedAtRecord = expectObject(stateRecord.lastNotifiedAt, "nearBestNotificationState.lastNotifiedAt");
|
|
1945
2793
|
return {
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2794
|
+
version,
|
|
2795
|
+
nearBestEnabled: Boolean(stateRecord.nearBestEnabled),
|
|
2796
|
+
thresholdRatio: readNumber(stateRecord, "thresholdRatio", "nearBestNotificationState"),
|
|
2797
|
+
cooldownMs: readNumber(stateRecord, "cooldownMs", "nearBestNotificationState"),
|
|
2798
|
+
lastNotifiedAt: {
|
|
2799
|
+
bestConcurrentAgents: readOptionalIsoTimestamp(lastNotifiedAtRecord.bestConcurrentAgents, "nearBestNotificationState.lastNotifiedAt.bestConcurrentAgents"),
|
|
2800
|
+
best24hRawBurn: readOptionalIsoTimestamp(lastNotifiedAtRecord.best24hRawBurn, "nearBestNotificationState.lastNotifiedAt.best24hRawBurn"),
|
|
2801
|
+
best24hAgentSumMs: readOptionalIsoTimestamp(lastNotifiedAtRecord.best24hAgentSumMs, "nearBestNotificationState.lastNotifiedAt.best24hAgentSumMs")
|
|
2802
|
+
}
|
|
1950
2803
|
};
|
|
1951
2804
|
}
|
|
1952
|
-
function
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2805
|
+
async function writeNearBestNotificationState(state, options) {
|
|
2806
|
+
const statePath = resolveNearBestNotificationStatePath(options);
|
|
2807
|
+
const stateDirectory = options.stateDirectory ?? join3(homedir3(), ".idletime");
|
|
2808
|
+
await mkdir(stateDirectory, { recursive: true });
|
|
2809
|
+
const temporaryPath = join3(stateDirectory, `.near-best-notifications.${process.pid}.${Date.now()}.tmp`);
|
|
2810
|
+
await writeFile(temporaryPath, `${JSON.stringify({
|
|
2811
|
+
version: state.version,
|
|
2812
|
+
nearBestEnabled: state.nearBestEnabled,
|
|
2813
|
+
thresholdRatio: state.thresholdRatio,
|
|
2814
|
+
cooldownMs: state.cooldownMs,
|
|
2815
|
+
lastNotifiedAt: {
|
|
2816
|
+
bestConcurrentAgents: state.lastNotifiedAt.bestConcurrentAgents?.toISOString() ?? null,
|
|
2817
|
+
best24hRawBurn: state.lastNotifiedAt.best24hRawBurn?.toISOString() ?? null,
|
|
2818
|
+
best24hAgentSumMs: state.lastNotifiedAt.best24hAgentSumMs?.toISOString() ?? null
|
|
2819
|
+
}
|
|
2820
|
+
}, null, 2)}
|
|
2821
|
+
`, "utf8");
|
|
2822
|
+
await rename(temporaryPath, statePath);
|
|
1969
2823
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2824
|
+
function buildNearBestMetricKeys(currentMetrics, ledger, state, now) {
|
|
2825
|
+
return [
|
|
2826
|
+
"bestConcurrentAgents",
|
|
2827
|
+
"best24hRawBurn",
|
|
2828
|
+
"best24hAgentSumMs"
|
|
2829
|
+
].filter((metric) => {
|
|
2830
|
+
const bestValue = ledger[metric]?.value ?? 0;
|
|
2831
|
+
if (bestValue <= 0) {
|
|
2832
|
+
return false;
|
|
2833
|
+
}
|
|
2834
|
+
const currentValue = currentMetrics[metric];
|
|
2835
|
+
if (currentValue <= 0 || currentValue >= bestValue) {
|
|
2836
|
+
return false;
|
|
2837
|
+
}
|
|
2838
|
+
if (currentValue / bestValue < state.thresholdRatio) {
|
|
2839
|
+
return false;
|
|
2840
|
+
}
|
|
2841
|
+
const lastNotifiedAt = state.lastNotifiedAt[metric];
|
|
2842
|
+
return lastNotifiedAt === null || now.getTime() - lastNotifiedAt.getTime() >= state.cooldownMs;
|
|
1981
2843
|
});
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
version: bestMetricsLedgerVersion,
|
|
1985
|
-
initializedAt: refreshedAt,
|
|
1986
|
-
lastScannedAt: refreshedAt,
|
|
1987
|
-
...bestMetricCandidates
|
|
1988
|
-
};
|
|
1989
|
-
await writeBestLedger(bootstrappedLedger, options);
|
|
1990
|
-
return {
|
|
1991
|
-
currentMetrics,
|
|
1992
|
-
ledger: bootstrappedLedger,
|
|
1993
|
-
newBestEvents: [],
|
|
1994
|
-
refreshMode: "bootstrap"
|
|
1995
|
-
};
|
|
1996
|
-
}
|
|
1997
|
-
const newBestEvents = buildNewBestEvents(existingLedger, bestMetricCandidates);
|
|
1998
|
-
const refreshedLedger = {
|
|
1999
|
-
...existingLedger,
|
|
2000
|
-
lastScannedAt: refreshedAt,
|
|
2001
|
-
...mergeBestMetricCandidates(existingLedger, bestMetricCandidates)
|
|
2002
|
-
};
|
|
2003
|
-
await writeBestLedger(refreshedLedger, options);
|
|
2004
|
-
await appendBestEvents(newBestEvents, options);
|
|
2844
|
+
}
|
|
2845
|
+
function buildNearBestNotification(metric, currentValue, bestValue) {
|
|
2005
2846
|
return {
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
newBestEvents,
|
|
2009
|
-
refreshMode: "refresh"
|
|
2847
|
+
title: metric === "bestConcurrentAgents" ? "Close to best concurrent agents" : metric === "best24hRawBurn" ? "Close to best 24hr raw burn" : "Close to best agent sum",
|
|
2848
|
+
body: metric === "bestConcurrentAgents" ? `${formatInteger2(currentValue)} of ${formatInteger2(bestValue)} concurrent agents` : metric === "best24hRawBurn" ? `${formatCompactInteger2(currentValue)} of ${formatCompactInteger2(bestValue)} 24hr raw burn` : `${formatAgentSumHours2(currentValue)} of ${formatAgentSumHours2(bestValue)} agent sum`
|
|
2010
2849
|
};
|
|
2011
2850
|
}
|
|
2012
|
-
function
|
|
2851
|
+
function createDefaultNearBestNotificationState() {
|
|
2013
2852
|
return {
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2853
|
+
version: nearBestNotificationVersion,
|
|
2854
|
+
nearBestEnabled: false,
|
|
2855
|
+
thresholdRatio: 0.97,
|
|
2856
|
+
cooldownMs: 24 * 60 * 60 * 1000,
|
|
2857
|
+
lastNotifiedAt: {
|
|
2858
|
+
bestConcurrentAgents: null,
|
|
2859
|
+
best24hRawBurn: null,
|
|
2860
|
+
best24hAgentSumMs: null
|
|
2861
|
+
}
|
|
2017
2862
|
};
|
|
2018
2863
|
}
|
|
2019
|
-
function
|
|
2020
|
-
|
|
2021
|
-
return currentRecord;
|
|
2022
|
-
}
|
|
2023
|
-
if (!currentRecord || candidateRecord.value > currentRecord.value) {
|
|
2024
|
-
return candidateRecord;
|
|
2025
|
-
}
|
|
2026
|
-
return currentRecord;
|
|
2027
|
-
}
|
|
2028
|
-
function buildNewBestEvents(currentLedger, candidateLedger) {
|
|
2029
|
-
return [
|
|
2030
|
-
buildNewBestEvent("bestConcurrentAgents", currentLedger.bestConcurrentAgents, candidateLedger.bestConcurrentAgents),
|
|
2031
|
-
buildNewBestEvent("best24hRawBurn", currentLedger.best24hRawBurn, candidateLedger.best24hRawBurn),
|
|
2032
|
-
buildNewBestEvent("best24hAgentSumMs", currentLedger.best24hAgentSumMs, candidateLedger.best24hAgentSumMs)
|
|
2033
|
-
].flatMap((bestEvent) => bestEvent ? [bestEvent] : []);
|
|
2864
|
+
function resolveNearBestNotificationStatePath(options) {
|
|
2865
|
+
return join3(options.stateDirectory ?? join3(homedir3(), ".idletime"), nearBestNotificationStateFileName);
|
|
2034
2866
|
}
|
|
2035
|
-
function
|
|
2036
|
-
if (
|
|
2867
|
+
function readOptionalIsoTimestamp(value, label) {
|
|
2868
|
+
if (value === null || value === undefined) {
|
|
2037
2869
|
return null;
|
|
2038
2870
|
}
|
|
2039
|
-
return
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2871
|
+
return readIsoTimestamp(value, label);
|
|
2872
|
+
}
|
|
2873
|
+
function formatAgentSumHours2(durationMs) {
|
|
2874
|
+
const hours = durationMs / 3600000;
|
|
2875
|
+
return hours >= 10 ? Math.round(hours).toString() : (Math.round(hours * 10) / 10).toString();
|
|
2876
|
+
}
|
|
2877
|
+
function formatCompactInteger2(value) {
|
|
2878
|
+
return new Intl.NumberFormat("en-US", {
|
|
2879
|
+
notation: "compact",
|
|
2880
|
+
maximumFractionDigits: 1
|
|
2881
|
+
}).format(Math.round(value)).toUpperCase();
|
|
2882
|
+
}
|
|
2883
|
+
function formatInteger2(value) {
|
|
2884
|
+
return new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
2048
2885
|
}
|
|
2049
2886
|
|
|
2050
|
-
// src/
|
|
2051
|
-
function
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
end: query.window.end
|
|
2056
|
-
};
|
|
2057
|
-
const metrics = clipActivityMetricsToWindow(buildActivityMetrics(filteredSessions, query.idleCutoffMs), windowInterval);
|
|
2058
|
-
const comparisonCutoffMs = parseDurationToMs("30m");
|
|
2059
|
-
const sessionCounts = {
|
|
2060
|
-
total: filteredSessions.length,
|
|
2061
|
-
direct: filteredSessions.filter((session) => session.kind === "direct").length,
|
|
2062
|
-
subagent: filteredSessions.filter((session) => session.kind === "subagent").length
|
|
2063
|
-
};
|
|
2887
|
+
// src/best-metrics/notify-best-events.ts
|
|
2888
|
+
async function notifyBestEvents(bestEvents, options = {}) {
|
|
2889
|
+
await deliverLocalNotifications(bestEvents.map((bestEvent) => buildBestEventNotification(bestEvent)), options);
|
|
2890
|
+
}
|
|
2891
|
+
function buildBestEventNotification(bestEvent) {
|
|
2064
2892
|
return {
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
comparisonCutoffMs,
|
|
2068
|
-
comparisonMetrics: query.idleCutoffMs === comparisonCutoffMs ? metrics : clipActivityMetricsToWindow(buildActivityMetrics(filteredSessions, comparisonCutoffMs), windowInterval),
|
|
2069
|
-
directTokenTotals: sumTokenTotals(filteredSessions.filter((session) => session.kind === "direct"), windowInterval),
|
|
2070
|
-
groupBreakdowns: buildGroupBreakdowns(filteredSessions, query.groupBy, query.idleCutoffMs, windowInterval),
|
|
2071
|
-
idleCutoffMs: query.idleCutoffMs,
|
|
2072
|
-
metrics,
|
|
2073
|
-
sessionCounts,
|
|
2074
|
-
tokenTotals: sumTokenTotals(filteredSessions, windowInterval),
|
|
2075
|
-
wakeSummary: query.wakeWindow ? summarizeWakeWindow(query.wakeWindow, query.window, metrics) : null,
|
|
2076
|
-
window: query.window
|
|
2893
|
+
title: resolveNotificationTitle(bestEvent.metric),
|
|
2894
|
+
body: resolveNotificationBody(bestEvent)
|
|
2077
2895
|
};
|
|
2078
2896
|
}
|
|
2079
|
-
function
|
|
2080
|
-
return
|
|
2081
|
-
dimension,
|
|
2082
|
-
rows: groupSessions(sessions, dimension).map((groupedSessions) => buildGroupRow(groupedSessions.key, groupedSessions.sessions, idleCutoffMs, windowInterval))
|
|
2083
|
-
}));
|
|
2897
|
+
function resolveNotificationTitle(bestMetricKey) {
|
|
2898
|
+
return bestMetricKey === "bestConcurrentAgents" ? "New best concurrent agents" : bestMetricKey === "best24hRawBurn" ? "New best 24hr raw burn" : "New best agent sum";
|
|
2084
2899
|
}
|
|
2085
|
-
function
|
|
2086
|
-
|
|
2087
|
-
const tokenTotals = sumTokenTotals(sessions, windowInterval);
|
|
2088
|
-
return {
|
|
2089
|
-
key,
|
|
2090
|
-
sessionCount: sessions.length,
|
|
2091
|
-
directActivityMs: metrics.directActivityMs,
|
|
2092
|
-
agentCoverageMs: metrics.agentCoverageMs,
|
|
2093
|
-
cumulativeAgentMs: metrics.cumulativeAgentMs,
|
|
2094
|
-
practicalBurn: tokenTotals.practicalBurn,
|
|
2095
|
-
rawTotalTokens: tokenTotals.rawTotalTokens
|
|
2096
|
-
};
|
|
2900
|
+
function resolveNotificationBody(bestEvent) {
|
|
2901
|
+
return bestEvent.metric === "bestConcurrentAgents" ? `${formatInteger3(bestEvent.value)} concurrent agents` : bestEvent.metric === "best24hRawBurn" ? `${formatCompactInteger3(bestEvent.value)} 24hr raw burn` : `${formatAgentSumHours3(bestEvent.value)} agent sum`;
|
|
2097
2902
|
}
|
|
2098
|
-
function
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
practicalBurn: sessionTotals.practicalBurn + tokenDeltaPoint.deltaUsage.practicalBurn,
|
|
2102
|
-
rawTotalTokens: sessionTotals.rawTotalTokens + tokenDeltaPoint.deltaUsage.totalTokens
|
|
2103
|
-
}), { practicalBurn: 0, rawTotalTokens: 0 });
|
|
2104
|
-
return {
|
|
2105
|
-
practicalBurn: tokenTotals.practicalBurn + sessionWindowTotals.practicalBurn,
|
|
2106
|
-
rawTotalTokens: tokenTotals.rawTotalTokens + sessionWindowTotals.rawTotalTokens
|
|
2107
|
-
};
|
|
2108
|
-
}, {
|
|
2109
|
-
practicalBurn: 0,
|
|
2110
|
-
rawTotalTokens: 0
|
|
2111
|
-
});
|
|
2903
|
+
function formatAgentSumHours3(durationMs) {
|
|
2904
|
+
const hours = durationMs / 3600000;
|
|
2905
|
+
return hours >= 10 ? Math.round(hours).toString() : (Math.round(hours * 10) / 10).toString();
|
|
2112
2906
|
}
|
|
2113
|
-
function
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
const lastTimestamp = sessions.reduce((latestTimestamp, session) => session.lastTimestamp.getTime() > latestTimestamp.getTime() ? session.lastTimestamp : latestTimestamp, sessions[0].lastTimestamp);
|
|
2119
|
-
return {
|
|
2120
|
-
start: new Date(Math.max(firstTimestamp.getTime(), windowInterval.start.getTime())),
|
|
2121
|
-
end: new Date(Math.min(lastTimestamp.getTime(), windowInterval.end.getTime()))
|
|
2122
|
-
};
|
|
2907
|
+
function formatCompactInteger3(value) {
|
|
2908
|
+
return new Intl.NumberFormat("en-US", {
|
|
2909
|
+
notation: "compact",
|
|
2910
|
+
maximumFractionDigits: 1
|
|
2911
|
+
}).format(Math.round(value)).toUpperCase();
|
|
2123
2912
|
}
|
|
2124
|
-
function
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2913
|
+
function formatInteger3(value) {
|
|
2914
|
+
return new Intl.NumberFormat("en-US").format(Math.round(value));
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
// src/best-metrics/build-current-best-metrics.ts
|
|
2918
|
+
function buildCurrentBestMetricValues(sessions, options = {}) {
|
|
2919
|
+
const idleCutoffMs = options.idleCutoffMs ?? defaultBestMetricsIdleCutoffMs;
|
|
2920
|
+
const now = options.now ?? new Date;
|
|
2921
|
+
const activityMetrics = buildActivityMetrics(sessions, idleCutoffMs, now);
|
|
2922
|
+
const currentWindow = {
|
|
2923
|
+
start: new Date(now.getTime() - rollingWindowDurationMs),
|
|
2924
|
+
end: now
|
|
2925
|
+
};
|
|
2130
2926
|
return {
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
agentOnlyBlocks,
|
|
2135
|
-
perSubagentBlocks,
|
|
2136
|
-
strictEngagementMs: sumTimeIntervalsMs(strictEngagementBlocks),
|
|
2137
|
-
directActivityMs: sumTimeIntervalsMs(directActivityBlocks),
|
|
2138
|
-
agentCoverageMs: sumTimeIntervalsMs(agentCoverageBlocks),
|
|
2139
|
-
agentOnlyMs: sumTimeIntervalsMs(agentOnlyBlocks),
|
|
2140
|
-
cumulativeAgentMs: perSubagentBlocks.reduce((totalDurationMs, sessionBlocks) => totalDurationMs + sumTimeIntervalsMs(sessionBlocks), 0),
|
|
2141
|
-
peakConcurrentAgents: peakConcurrency(perSubagentBlocks)
|
|
2927
|
+
bestConcurrentAgents: countLiveSubagents(activityMetrics.perAgentTaskBlocks, now),
|
|
2928
|
+
best24hRawBurn: sessions.reduce((rawBurnTotal, session) => rawBurnTotal + buildTokenDeltaPoints(session.tokenPoints).filter((tokenDeltaPoint) => tokenDeltaPoint.timestamp.getTime() >= currentWindow.start.getTime() && tokenDeltaPoint.timestamp.getTime() <= currentWindow.end.getTime()).reduce((sessionTotal, tokenDeltaPoint) => sessionTotal + tokenDeltaPoint.deltaUsage.totalTokens, 0), 0),
|
|
2929
|
+
best24hAgentSumMs: measureOverlapMs(activityMetrics.perAgentTaskBlocks.flatMap((taskBlocks) => taskBlocks), currentWindow)
|
|
2142
2930
|
};
|
|
2143
2931
|
}
|
|
2932
|
+
function countLiveSubagents(intervalGroups, now) {
|
|
2933
|
+
return intervalGroups.reduce((liveCount, intervalGroup) => liveCount + Number(intervalGroup.some((interval) => interval.start.getTime() <= now.getTime() && interval.end.getTime() > now.getTime())), 0);
|
|
2934
|
+
}
|
|
2144
2935
|
|
|
2145
|
-
// src/
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2936
|
+
// src/best-metrics/append-best-events.ts
|
|
2937
|
+
import { appendFile, mkdir as mkdir2 } from "node:fs/promises";
|
|
2938
|
+
import { homedir as homedir4 } from "node:os";
|
|
2939
|
+
import { join as join4 } from "node:path";
|
|
2940
|
+
var bestEventsFileName = "best-events.ndjson";
|
|
2941
|
+
async function appendBestEvents(bestEvents, options = {}) {
|
|
2942
|
+
if (bestEvents.length === 0) {
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
const stateDirectory = resolveBestStateDirectory2(options);
|
|
2946
|
+
await mkdir2(stateDirectory, { recursive: true });
|
|
2947
|
+
await appendFile(join4(stateDirectory, bestEventsFileName), `${bestEvents.map(serializeBestEvent).join(`
|
|
2948
|
+
`)}
|
|
2949
|
+
`, "utf8");
|
|
2149
2950
|
}
|
|
2150
|
-
function
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2951
|
+
function serializeBestEvent(bestEvent) {
|
|
2952
|
+
return JSON.stringify({
|
|
2953
|
+
metric: bestEvent.metric,
|
|
2954
|
+
previousValue: bestEvent.previousValue,
|
|
2955
|
+
value: bestEvent.value,
|
|
2956
|
+
observedAt: bestEvent.observedAt.toISOString(),
|
|
2957
|
+
windowStart: bestEvent.windowStart.toISOString(),
|
|
2958
|
+
windowEnd: bestEvent.windowEnd.toISOString(),
|
|
2959
|
+
version: bestEvent.version
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
function resolveBestStateDirectory2(options) {
|
|
2963
|
+
return options.stateDirectory ?? join4(homedir4(), ".idletime");
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
// src/best-metrics/build-rolling-24h-windows.ts
|
|
2967
|
+
function findBestRollingWindowTotal(weightedPoints) {
|
|
2968
|
+
const sortedPoints = weightedPoints.filter((point) => point.value > 0).slice().sort((leftPoint, rightPoint) => leftPoint.timestamp.getTime() - rightPoint.timestamp.getTime());
|
|
2969
|
+
if (sortedPoints.length === 0) {
|
|
2970
|
+
return null;
|
|
2165
2971
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
{
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
},
|
|
2177
|
-
{
|
|
2178
|
-
filledCharacter: "▓",
|
|
2179
|
-
value: report.sessionCounts.subagent
|
|
2972
|
+
let bestValue = 0;
|
|
2973
|
+
let bestTimestampMs = 0;
|
|
2974
|
+
let currentTotal = 0;
|
|
2975
|
+
let leftIndex = 0;
|
|
2976
|
+
for (let rightIndex = 0;rightIndex < sortedPoints.length; rightIndex += 1) {
|
|
2977
|
+
const rightPoint = sortedPoints[rightIndex];
|
|
2978
|
+
currentTotal += rightPoint.value;
|
|
2979
|
+
while (rightPoint.timestamp.getTime() - sortedPoints[leftIndex].timestamp.getTime() > rollingWindowDurationMs) {
|
|
2980
|
+
currentTotal -= sortedPoints[leftIndex].value;
|
|
2981
|
+
leftIndex += 1;
|
|
2180
2982
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
const maxBurnValue = Math.max(report.tokenTotals.practicalBurn, report.directTokenTotals.practicalBurn);
|
|
2185
|
-
const maxRawValue = Math.max(report.tokenTotals.rawTotalTokens, report.directTokenTotals.rawTotalTokens);
|
|
2186
|
-
lines.push(renderMetricRow("practical burn", report.tokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.tokenTotals.practicalBurn), `${formatPercentage(report.tokenTotals.practicalBurn / report.tokenTotals.rawTotalTokens)} of raw`, "█", "burn", options, "burn"));
|
|
2187
|
-
lines.push(renderMetricRow("all raw", report.tokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.tokenTotals.rawTotalTokens), `${formatInteger(report.tokenTotals.rawTotalTokens)} total`, "█", "raw", options, "raw"));
|
|
2188
|
-
lines.push(renderMetricRow("direct burn", report.directTokenTotals.practicalBurn, maxBurnValue, formatCompactInteger(report.directTokenTotals.practicalBurn), `${formatPercentage(report.directTokenTotals.practicalBurn / report.tokenTotals.practicalBurn)} of burn`, "▒", "burn", options, "burn"));
|
|
2189
|
-
lines.push(renderMetricRow("direct raw", report.directTokenTotals.rawTotalTokens, maxRawValue, formatCompactInteger(report.directTokenTotals.rawTotalTokens), `${formatPercentage(report.directTokenTotals.rawTotalTokens / report.tokenTotals.rawTotalTokens)} of raw`, "▒", "raw", options, "raw"));
|
|
2190
|
-
if (report.wakeSummary) {
|
|
2191
|
-
lines.push("");
|
|
2192
|
-
lines.push(...renderSectionTitle("Wake Window", options));
|
|
2193
|
-
lines.push(renderMetricRow("direct awake", report.wakeSummary.directActivityMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.directActivityMs), `of ${formatDurationClock(report.wakeSummary.wakeDurationMs)} wake`, "▓", "active", options));
|
|
2194
|
-
lines.push(renderMetricRow("strict awake", report.wakeSummary.strictEngagementMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.strictEngagementMs), "engaged", "█", "focus", options));
|
|
2195
|
-
lines.push(renderMetricRow("agent awake", report.wakeSummary.agentOnlyMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.agentOnlyMs), "agent-only", "▒", "agent", options));
|
|
2196
|
-
lines.push(renderMetricRow("awake idle", report.wakeSummary.awakeIdleMs, report.wakeSummary.wakeDurationMs, formatDurationClock(report.wakeSummary.awakeIdleMs), `${formatPercentage(report.wakeSummary.awakeIdlePercentage)} idle`, "░", "idle", options));
|
|
2197
|
-
lines.push(`${paint(padRight(" longest gap", 14), "muted", options)} ${paint(formatDurationClock(report.wakeSummary.longestIdleGapMs), "value", options)} ${dim("largest quiet stretch", options)}`);
|
|
2198
|
-
}
|
|
2199
|
-
for (const groupBreakdown of report.groupBreakdowns) {
|
|
2200
|
-
lines.push("");
|
|
2201
|
-
lines.push(...renderSectionTitle(groupBreakdown.dimension === "model" ? "Model Breakdown" : "Effort Breakdown", options));
|
|
2202
|
-
const maxBreakdownBurn = Math.max(...groupBreakdown.rows.map((row) => row.practicalBurn), 0);
|
|
2203
|
-
for (const row of groupBreakdown.rows) {
|
|
2204
|
-
lines.push(`${paint(padRight(` ${row.key}`, 20), "muted", options)} ${paint(buildBar(row.practicalBurn, maxBreakdownBurn, 14, "█"), "burn", options)} ${paint(padRight(formatCompactInteger(row.practicalBurn), 6), "value", options)} ${dim("burn", options)} ${paint(padRight(formatDurationCompact(row.directActivityMs), 5), "active", options)} ${dim("direct", options)} ${paint(padRight(formatDurationCompact(row.agentCoverageMs), 5), "agent", options)} ${dim("live", options)} ${paint(`${row.sessionCount} s`, "value", options)}`);
|
|
2983
|
+
if (currentTotal > bestValue) {
|
|
2984
|
+
bestValue = currentTotal;
|
|
2985
|
+
bestTimestampMs = rightPoint.timestamp.getTime();
|
|
2205
2986
|
}
|
|
2206
2987
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
}
|
|
2210
|
-
function renderShareSummaryReport(report, options, hourlyReport, bestPlaque = null) {
|
|
2211
|
-
const lines = [];
|
|
2212
|
-
const headerLines = buildSummaryHeaderLines(report, hourlyReport);
|
|
2213
|
-
const panelLines = renderPanel(`idletime • ${report.window.label}`, headerLines, options);
|
|
2214
|
-
const panelWidth = measureVisibleTextWidth(panelLines[0] ?? "");
|
|
2215
|
-
const logoSectionWidth = resolveLogoSectionWidth(panelWidth, options);
|
|
2216
|
-
lines.push(...buildLogoSection(logoSectionWidth, options, bestPlaque));
|
|
2217
|
-
lines.push("");
|
|
2218
|
-
lines.push(...panelLines);
|
|
2219
|
-
if (hourlyReport) {
|
|
2220
|
-
lines.push("");
|
|
2221
|
-
lines.push(...buildRhythmSection(hourlyReport, options));
|
|
2988
|
+
if (bestValue === 0) {
|
|
2989
|
+
return null;
|
|
2222
2990
|
}
|
|
2223
|
-
|
|
2224
|
-
lines.push(...renderSectionTitle("Snapshot", options));
|
|
2225
|
-
lines.push(renderSnapshotRow("focus", formatDurationHours(report.metrics.strictEngagementMs), "focused time", "focus", options));
|
|
2226
|
-
lines.push(renderSnapshotRow("active", formatDurationHours(report.metrics.directActivityMs), "direct-session movement", "active", options));
|
|
2227
|
-
lines.push(renderSnapshotRow(report.wakeSummary ? "idle" : "quiet", report.wakeSummary ? formatDurationClock(report.wakeSummary.awakeIdleMs) : hourlyReport ? formatDurationCompact(hourlyReport.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs), 0)) : "n/a", report.wakeSummary ? "awake idle" : "quiet hours", "idle", options));
|
|
2228
|
-
lines.push(renderSnapshotRow("burn", formatCompactInteger(report.tokenTotals.practicalBurn), `${formatPercentage(report.tokenTotals.practicalBurn / report.tokenTotals.rawTotalTokens)} of raw`, "burn", options));
|
|
2229
|
-
lines.push(renderSnapshotRow("agents", `${report.metrics.peakConcurrentAgents} peak`, `${formatDurationHours(report.metrics.cumulativeAgentMs)} cumulative`, "agent", options));
|
|
2230
|
-
lines.push(renderSnapshotRow("sessions", `${report.sessionCounts.total}`, `${report.sessionCounts.direct} direct / ${report.sessionCounts.subagent} subagent`, "value", options));
|
|
2231
|
-
return lines.join(`
|
|
2232
|
-
`);
|
|
2991
|
+
return createRollingRecord(bestValue, bestTimestampMs);
|
|
2233
2992
|
}
|
|
2234
|
-
function
|
|
2235
|
-
const
|
|
2236
|
-
if (
|
|
2237
|
-
|
|
2238
|
-
}
|
|
2239
|
-
if (report.appliedFilters.sessionKind) {
|
|
2240
|
-
appliedFilters.push(`kind=${report.appliedFilters.sessionKind}`);
|
|
2993
|
+
function findBestRollingWindowOverlap(intervals) {
|
|
2994
|
+
const slopeChanges = intervals.flatMap(buildSlopeChanges);
|
|
2995
|
+
if (slopeChanges.length === 0) {
|
|
2996
|
+
return null;
|
|
2241
2997
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2998
|
+
slopeChanges.sort((leftChange, rightChange) => leftChange.timestampMs - rightChange.timestampMs);
|
|
2999
|
+
let bestTimestampMs = 0;
|
|
3000
|
+
let bestValue = 0;
|
|
3001
|
+
let currentSlope = 0;
|
|
3002
|
+
let currentValue = 0;
|
|
3003
|
+
let previousTimestampMs = slopeChanges[0].timestampMs;
|
|
3004
|
+
let index = 0;
|
|
3005
|
+
while (index < slopeChanges.length) {
|
|
3006
|
+
const timestampMs = slopeChanges[index].timestampMs;
|
|
3007
|
+
currentValue += currentSlope * (timestampMs - previousTimestampMs);
|
|
3008
|
+
if (currentValue > bestValue) {
|
|
3009
|
+
bestValue = currentValue;
|
|
3010
|
+
bestTimestampMs = timestampMs;
|
|
3011
|
+
}
|
|
3012
|
+
while (index < slopeChanges.length && slopeChanges[index].timestampMs === timestampMs) {
|
|
3013
|
+
currentSlope += slopeChanges[index].deltaSlope;
|
|
3014
|
+
index += 1;
|
|
3015
|
+
}
|
|
3016
|
+
previousTimestampMs = timestampMs;
|
|
2244
3017
|
}
|
|
2245
|
-
if (
|
|
2246
|
-
|
|
3018
|
+
if (bestValue === 0) {
|
|
3019
|
+
return null;
|
|
2247
3020
|
}
|
|
2248
|
-
return
|
|
3021
|
+
return createRollingRecord(bestValue, bestTimestampMs);
|
|
2249
3022
|
}
|
|
2250
|
-
function
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
...formatAppliedFilters(report).map((filter) => `filter ${filter}`)
|
|
2256
|
-
];
|
|
3023
|
+
function buildSlopeChanges(interval) {
|
|
3024
|
+
const startMs = interval.start.getTime();
|
|
3025
|
+
const endMs = interval.end.getTime();
|
|
3026
|
+
if (endMs <= startMs) {
|
|
3027
|
+
return [];
|
|
2257
3028
|
}
|
|
2258
3029
|
return [
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
3030
|
+
{ timestampMs: startMs, deltaSlope: 1 },
|
|
3031
|
+
{ timestampMs: endMs, deltaSlope: -1 },
|
|
3032
|
+
{ timestampMs: startMs + rollingWindowDurationMs, deltaSlope: -1 },
|
|
3033
|
+
{ timestampMs: endMs + rollingWindowDurationMs, deltaSlope: 1 }
|
|
2263
3034
|
];
|
|
2264
3035
|
}
|
|
2265
|
-
function
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
}
|
|
2273
|
-
function buildBiggestStoryLine(report, hourlyReport) {
|
|
2274
|
-
const longestQuietRun = findLongestQuietRun(hourlyReport);
|
|
2275
|
-
const peakBurnBucket = hourlyReport.buckets.reduce((currentPeak, bucket) => bucket.practicalBurn > currentPeak.practicalBurn ? bucket : currentPeak, hourlyReport.buckets[0]);
|
|
2276
|
-
const quietPhrase = longestQuietRun.durationMs >= 2 * 3600000 ? `long quiet stretch ${describeDayPeriod(longestQuietRun.start, report)}` : "steady rhythm overall";
|
|
2277
|
-
return `Biggest story: ${quietPhrase}, big burn ${describeDayPeriod(peakBurnBucket.start, report)}`;
|
|
2278
|
-
}
|
|
2279
|
-
function buildSupportFactsLine(report) {
|
|
2280
|
-
return `${report.sessionCounts.direct} direct / ${report.sessionCounts.subagent} subagent • ${report.metrics.peakConcurrentAgents} peak • ${formatCompactInteger(report.tokenTotals.practicalBurn)} burn`;
|
|
2281
|
-
}
|
|
2282
|
-
function sumQuietMs(hourlyReport) {
|
|
2283
|
-
return hourlyReport.buckets.reduce((totalDurationMs, bucket) => totalDurationMs + Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs), 0);
|
|
3036
|
+
function createRollingRecord(value, observedAtMs) {
|
|
3037
|
+
return {
|
|
3038
|
+
value,
|
|
3039
|
+
observedAt: new Date(observedAtMs),
|
|
3040
|
+
windowStart: new Date(observedAtMs - rollingWindowDurationMs),
|
|
3041
|
+
windowEnd: new Date(observedAtMs)
|
|
3042
|
+
};
|
|
2284
3043
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
3044
|
+
|
|
3045
|
+
// src/best-metrics/build-best-metrics.ts
|
|
3046
|
+
function buildBestMetricCandidates(sessions, options = {}) {
|
|
3047
|
+
const idleCutoffMs = options.idleCutoffMs ?? defaultBestMetricsIdleCutoffMs;
|
|
3048
|
+
const activityMetrics = buildActivityMetrics(sessions, idleCutoffMs);
|
|
3049
|
+
return {
|
|
3050
|
+
bestConcurrentAgents: findBestConcurrentAgents(activityMetrics.perAgentTaskBlocks),
|
|
3051
|
+
best24hRawBurn: findBestRollingWindowTotal(sessions.flatMap((session) => buildTokenDeltaPoints(session.tokenPoints).map((tokenDeltaPoint) => ({
|
|
3052
|
+
timestamp: tokenDeltaPoint.timestamp,
|
|
3053
|
+
value: tokenDeltaPoint.deltaUsage.totalTokens
|
|
3054
|
+
})))),
|
|
3055
|
+
best24hAgentSumMs: findBestRollingWindowOverlap(activityMetrics.perAgentTaskBlocks.flatMap((taskBlocks) => taskBlocks))
|
|
2289
3056
|
};
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
3057
|
+
}
|
|
3058
|
+
function findBestConcurrentAgents(intervalGroups) {
|
|
3059
|
+
const concurrencyEdges = intervalGroups.flatMap((intervalGroup) => intervalGroup.flatMap((interval) => [
|
|
3060
|
+
{ timestampMs: interval.start.getTime(), delta: 1 },
|
|
3061
|
+
{ timestampMs: interval.end.getTime(), delta: -1 }
|
|
3062
|
+
]));
|
|
3063
|
+
if (concurrencyEdges.length === 0) {
|
|
3064
|
+
return null;
|
|
3065
|
+
}
|
|
3066
|
+
concurrencyEdges.sort((leftEdge, rightEdge) => leftEdge.timestampMs - rightEdge.timestampMs);
|
|
3067
|
+
let activeCount = 0;
|
|
3068
|
+
let bestRecord = null;
|
|
3069
|
+
let index = 0;
|
|
3070
|
+
while (index < concurrencyEdges.length) {
|
|
3071
|
+
const timestampMs = concurrencyEdges[index].timestampMs;
|
|
3072
|
+
while (index < concurrencyEdges.length && concurrencyEdges[index].timestampMs === timestampMs) {
|
|
3073
|
+
activeCount += concurrencyEdges[index].delta;
|
|
3074
|
+
index += 1;
|
|
3075
|
+
}
|
|
3076
|
+
const nextTimestampMs = concurrencyEdges[index]?.timestampMs ?? timestampMs;
|
|
3077
|
+
if (nextTimestampMs <= timestampMs || activeCount <= 0) {
|
|
2298
3078
|
continue;
|
|
2299
3079
|
}
|
|
2300
|
-
if (
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
3080
|
+
if (!bestRecord || activeCount > bestRecord.value) {
|
|
3081
|
+
bestRecord = {
|
|
3082
|
+
value: activeCount,
|
|
3083
|
+
observedAt: new Date(timestampMs),
|
|
3084
|
+
windowStart: new Date(timestampMs),
|
|
3085
|
+
windowEnd: new Date(nextTimestampMs)
|
|
2304
3086
|
};
|
|
2305
3087
|
}
|
|
2306
|
-
currentStart = null;
|
|
2307
|
-
currentDurationMs = 0;
|
|
2308
3088
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
3089
|
+
return bestRecord;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
// src/best-metrics/read-all-codex-sessions.ts
|
|
3093
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
3094
|
+
import { homedir as homedir5 } from "node:os";
|
|
3095
|
+
import { join as join5 } from "node:path";
|
|
3096
|
+
var defaultSessionRootDirectory2 = join5(homedir5(), ".codex", "sessions");
|
|
3097
|
+
async function readAllCodexSessions(options = {}) {
|
|
3098
|
+
const sessionRootDirectory = options.sessionRootDirectory ?? defaultSessionRootDirectory2;
|
|
3099
|
+
const sessionFiles = await listAllSessionFiles(sessionRootDirectory);
|
|
3100
|
+
const parsedSessionResults = await Promise.allSettled(sessionFiles.map((sessionFilePath) => parseCodexSession(sessionFilePath)));
|
|
3101
|
+
const parsedSessions = parsedSessionResults.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
3102
|
+
return parsedSessions.sort((leftSession, rightSession) => leftSession.firstTimestamp.getTime() - rightSession.firstTimestamp.getTime());
|
|
3103
|
+
}
|
|
3104
|
+
async function listAllSessionFiles(rootDirectory) {
|
|
3105
|
+
const pendingDirectories = [rootDirectory];
|
|
3106
|
+
const sessionFiles = [];
|
|
3107
|
+
while (pendingDirectories.length > 0) {
|
|
3108
|
+
const currentDirectory = pendingDirectories.pop();
|
|
3109
|
+
const directoryEntries = await readDirectoryEntries2(currentDirectory);
|
|
3110
|
+
for (const directoryEntry of directoryEntries) {
|
|
3111
|
+
const entryPath = join5(currentDirectory, directoryEntry.name);
|
|
3112
|
+
if (directoryEntry.isDirectory()) {
|
|
3113
|
+
pendingDirectories.push(entryPath);
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
if (directoryEntry.isFile() && directoryEntry.name.endsWith(".jsonl")) {
|
|
3117
|
+
sessionFiles.push(entryPath);
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
2314
3120
|
}
|
|
2315
|
-
|
|
2316
|
-
|
|
3121
|
+
return sessionFiles.sort();
|
|
3122
|
+
}
|
|
3123
|
+
async function readDirectoryEntries2(directoryPath) {
|
|
3124
|
+
try {
|
|
3125
|
+
return await readdir2(directoryPath, { withFileTypes: true });
|
|
3126
|
+
} catch (error) {
|
|
3127
|
+
if (isMissingDirectoryError2(error)) {
|
|
3128
|
+
return [];
|
|
3129
|
+
}
|
|
3130
|
+
throw error;
|
|
2317
3131
|
}
|
|
2318
|
-
const quietestBucket = hourlyReport.buckets.reduce((currentQuietest, bucket) => {
|
|
2319
|
-
const quietMs = Math.max(0, bucket.end.getTime() - bucket.start.getTime() - bucket.directActivityMs - bucket.agentOnlyMs);
|
|
2320
|
-
return quietMs > currentQuietest.durationMs ? { durationMs: quietMs, start: bucket.start } : currentQuietest;
|
|
2321
|
-
}, longestQuietRun);
|
|
2322
|
-
return quietestBucket;
|
|
2323
3132
|
}
|
|
2324
|
-
function
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
3133
|
+
function isMissingDirectoryError2(error) {
|
|
3134
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// src/best-metrics/write-best-ledger.ts
|
|
3138
|
+
import { mkdir as mkdir3, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
|
|
3139
|
+
import { join as join6 } from "node:path";
|
|
3140
|
+
async function writeBestLedger(ledger, options = {}) {
|
|
3141
|
+
const ledgerPath = resolveBestLedgerPath(options);
|
|
3142
|
+
const stateDirectory = options.stateDirectory ?? ledgerPath.slice(0, ledgerPath.lastIndexOf("/"));
|
|
3143
|
+
await mkdir3(stateDirectory, { recursive: true });
|
|
3144
|
+
const temporaryPath = join6(stateDirectory, `.bests-v1.${process.pid}.${Date.now()}.tmp`);
|
|
3145
|
+
await writeFile2(temporaryPath, serializeBestLedger(ledger), "utf8");
|
|
3146
|
+
await rename2(temporaryPath, ledgerPath);
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
// src/best-metrics/refresh-best-metrics.ts
|
|
3150
|
+
async function refreshBestMetrics(options = {}) {
|
|
3151
|
+
const refreshedAt = options.now ?? new Date;
|
|
3152
|
+
const existingLedger = await readBestLedger(options);
|
|
3153
|
+
const sessions = await readAllCodexSessions({
|
|
3154
|
+
sessionRootDirectory: options.sessionRootDirectory
|
|
3155
|
+
});
|
|
3156
|
+
const bestMetricCandidates = buildBestMetricCandidates(sessions);
|
|
3157
|
+
const currentMetrics = buildCurrentBestMetricValues(sessions, {
|
|
3158
|
+
now: refreshedAt
|
|
3159
|
+
});
|
|
3160
|
+
if (!existingLedger) {
|
|
3161
|
+
const bootstrappedLedger = {
|
|
3162
|
+
version: bestMetricsLedgerVersion,
|
|
3163
|
+
initializedAt: refreshedAt,
|
|
3164
|
+
lastScannedAt: refreshedAt,
|
|
3165
|
+
...bestMetricCandidates
|
|
3166
|
+
};
|
|
3167
|
+
await writeBestLedger(bootstrappedLedger, options);
|
|
3168
|
+
return {
|
|
3169
|
+
currentMetrics,
|
|
3170
|
+
ledger: bootstrappedLedger,
|
|
3171
|
+
newBestEvents: [],
|
|
3172
|
+
refreshMode: "bootstrap"
|
|
3173
|
+
};
|
|
2328
3174
|
}
|
|
2329
|
-
|
|
2330
|
-
|
|
3175
|
+
const newBestEvents = buildNewBestEvents(existingLedger, bestMetricCandidates);
|
|
3176
|
+
const refreshedLedger = {
|
|
3177
|
+
...existingLedger,
|
|
3178
|
+
lastScannedAt: refreshedAt,
|
|
3179
|
+
...mergeBestMetricCandidates(existingLedger, bestMetricCandidates)
|
|
3180
|
+
};
|
|
3181
|
+
await writeBestLedger(refreshedLedger, options);
|
|
3182
|
+
await appendBestEvents(newBestEvents, options);
|
|
3183
|
+
return {
|
|
3184
|
+
currentMetrics,
|
|
3185
|
+
ledger: refreshedLedger,
|
|
3186
|
+
newBestEvents,
|
|
3187
|
+
refreshMode: "refresh"
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
function mergeBestMetricCandidates(currentLedger, candidateLedger) {
|
|
3191
|
+
return {
|
|
3192
|
+
bestConcurrentAgents: pickBetterRecord(currentLedger.bestConcurrentAgents, candidateLedger.bestConcurrentAgents),
|
|
3193
|
+
best24hRawBurn: pickBetterRecord(currentLedger.best24hRawBurn, candidateLedger.best24hRawBurn),
|
|
3194
|
+
best24hAgentSumMs: pickBetterRecord(currentLedger.best24hAgentSumMs, candidateLedger.best24hAgentSumMs)
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3197
|
+
function pickBetterRecord(currentRecord, candidateRecord) {
|
|
3198
|
+
if (!candidateRecord) {
|
|
3199
|
+
return currentRecord;
|
|
2331
3200
|
}
|
|
2332
|
-
if (
|
|
2333
|
-
return
|
|
3201
|
+
if (!currentRecord || candidateRecord.value > currentRecord.value) {
|
|
3202
|
+
return candidateRecord;
|
|
2334
3203
|
}
|
|
2335
|
-
return
|
|
2336
|
-
}
|
|
2337
|
-
function formatDurationLabel(durationMs) {
|
|
2338
|
-
return `${Math.round(durationMs / 60000)}m`;
|
|
3204
|
+
return currentRecord;
|
|
2339
3205
|
}
|
|
2340
|
-
function
|
|
2341
|
-
return
|
|
3206
|
+
function buildNewBestEvents(currentLedger, candidateLedger) {
|
|
3207
|
+
return [
|
|
3208
|
+
buildNewBestEvent("bestConcurrentAgents", currentLedger.bestConcurrentAgents, candidateLedger.bestConcurrentAgents),
|
|
3209
|
+
buildNewBestEvent("best24hRawBurn", currentLedger.best24hRawBurn, candidateLedger.best24hRawBurn),
|
|
3210
|
+
buildNewBestEvent("best24hAgentSumMs", currentLedger.best24hAgentSumMs, candidateLedger.best24hAgentSumMs)
|
|
3211
|
+
].flatMap((bestEvent) => bestEvent ? [bestEvent] : []);
|
|
2342
3212
|
}
|
|
2343
|
-
function
|
|
2344
|
-
|
|
3213
|
+
function buildNewBestEvent(metric, currentRecord, candidateRecord) {
|
|
3214
|
+
if (!candidateRecord || currentRecord !== null && candidateRecord.value <= currentRecord.value) {
|
|
3215
|
+
return null;
|
|
3216
|
+
}
|
|
3217
|
+
return {
|
|
3218
|
+
metric,
|
|
3219
|
+
previousValue: currentRecord?.value ?? null,
|
|
3220
|
+
value: candidateRecord.value,
|
|
3221
|
+
observedAt: candidateRecord.observedAt,
|
|
3222
|
+
windowStart: candidateRecord.windowStart,
|
|
3223
|
+
windowEnd: candidateRecord.windowEnd,
|
|
3224
|
+
version: bestMetricsLedgerVersion
|
|
3225
|
+
};
|
|
2345
3226
|
}
|
|
2346
3227
|
|
|
2347
|
-
// src/cli/run-
|
|
2348
|
-
async function
|
|
2349
|
-
const
|
|
2350
|
-
|
|
2351
|
-
await
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
groupBy: command.groupBy,
|
|
2360
|
-
idleCutoffMs: command.idleCutoffMs,
|
|
2361
|
-
wakeWindow: command.wakeWindow,
|
|
2362
|
-
window
|
|
2363
|
-
});
|
|
2364
|
-
const hourlyReport = buildHourlyReport(sessions, {
|
|
2365
|
-
filters: command.filters,
|
|
2366
|
-
idleCutoffMs: command.idleCutoffMs,
|
|
2367
|
-
wakeWindow: command.wakeWindow,
|
|
2368
|
-
window
|
|
2369
|
-
});
|
|
2370
|
-
return renderSummaryReport(summaryReport, createRenderOptions(command.shareMode), hourlyReport, buildBestPlaque(bestMetrics.ledger));
|
|
3228
|
+
// src/cli/run-refresh-bests-command.ts
|
|
3229
|
+
async function runRefreshBestsCommand(options = {}) {
|
|
3230
|
+
const refreshedBestMetrics = await refreshBestMetrics(options);
|
|
3231
|
+
await notifyBestEvents(refreshedBestMetrics.newBestEvents, options);
|
|
3232
|
+
await notifyNearBestMetrics(refreshedBestMetrics.currentMetrics, refreshedBestMetrics.ledger, options);
|
|
3233
|
+
return [
|
|
3234
|
+
"BEST metrics refreshed",
|
|
3235
|
+
`mode: ${refreshedBestMetrics.refreshMode}`,
|
|
3236
|
+
`new bests: ${refreshedBestMetrics.newBestEvents.length}`,
|
|
3237
|
+
`last scanned: ${refreshedBestMetrics.ledger.lastScannedAt.toISOString()}`
|
|
3238
|
+
].join(`
|
|
3239
|
+
`);
|
|
2371
3240
|
}
|
|
2372
3241
|
|
|
2373
3242
|
// src/cli/run-today-command.ts
|
|
2374
|
-
async function
|
|
2375
|
-
const window = resolveTodayReportWindow();
|
|
2376
|
-
const
|
|
2377
|
-
|
|
2378
|
-
await notifyNearBestMetrics(bestMetrics.currentMetrics, bestMetrics.ledger);
|
|
2379
|
-
const sessions = await readCodexSessions({
|
|
3243
|
+
async function buildTodayCommandResult(command, options = {}) {
|
|
3244
|
+
const window = resolveTodayReportWindow({ now: options.now });
|
|
3245
|
+
const bestLedgerPromise = readBestLedger({ stateDirectory: options.stateDirectory });
|
|
3246
|
+
const sessionsPromise = readCodexSessions({
|
|
2380
3247
|
windowStart: window.start,
|
|
2381
|
-
windowEnd: window.end
|
|
3248
|
+
windowEnd: window.end,
|
|
3249
|
+
sessionRootDirectory: options.sessionRootDirectory
|
|
2382
3250
|
});
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
3251
|
+
const [bestLedger, sessions] = await Promise.all([
|
|
3252
|
+
bestLedgerPromise,
|
|
3253
|
+
sessionsPromise
|
|
3254
|
+
]);
|
|
3255
|
+
return {
|
|
3256
|
+
bestLedger,
|
|
3257
|
+
summaryReport: buildSummaryReport(sessions, {
|
|
3258
|
+
filters: command.filters,
|
|
3259
|
+
groupBy: command.groupBy,
|
|
3260
|
+
idleCutoffMs: command.idleCutoffMs,
|
|
3261
|
+
wakeWindow: command.wakeWindow,
|
|
3262
|
+
window
|
|
3263
|
+
})
|
|
3264
|
+
};
|
|
3265
|
+
}
|
|
3266
|
+
async function runTodayCommand(command, options = {}) {
|
|
3267
|
+
const commandResult = await buildTodayCommandResult(command, options);
|
|
3268
|
+
return renderSummaryReport(commandResult.summaryReport, createRenderOptions(command.shareMode), undefined, commandResult.bestLedger ? buildBestPlaque(commandResult.bestLedger) : null);
|
|
2390
3269
|
}
|
|
2391
3270
|
|
|
2392
3271
|
// src/cli/run-idletime.ts
|
|
@@ -2400,9 +3279,86 @@ async function runIdletimeCli(argv) {
|
|
|
2400
3279
|
console.log(package_default.version);
|
|
2401
3280
|
return;
|
|
2402
3281
|
}
|
|
3282
|
+
if (command.commandName === "refresh-bests") {
|
|
3283
|
+
console.log(await runRefreshBestsCommand());
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
if (command.outputFormat === "json") {
|
|
3287
|
+
console.log(await buildJsonOutput(command));
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
if (command.commandName === "live") {
|
|
3291
|
+
await runLiveCommand(command);
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
2403
3294
|
const output = command.commandName === "hourly" ? await runHourlyCommand(command) : command.commandName === "today" ? await runTodayCommand(command) : await runLast24hCommand(command);
|
|
2404
3295
|
console.log(output);
|
|
2405
3296
|
}
|
|
3297
|
+
async function buildJsonOutput(command) {
|
|
3298
|
+
const generatedAt = new Date;
|
|
3299
|
+
if (command.commandName === "live") {
|
|
3300
|
+
const liveSnapshot = await takeLiveSnapshot(command, {
|
|
3301
|
+
observedAt: generatedAt
|
|
3302
|
+
});
|
|
3303
|
+
return serializeLiveSnapshot({
|
|
3304
|
+
command: buildJsonLiveSnapshotCommand(command),
|
|
3305
|
+
generatedAt,
|
|
3306
|
+
liveReport: liveSnapshot
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
if (command.commandName === "hourly") {
|
|
3310
|
+
const commandResult2 = await buildHourlyCommandResult(command, {
|
|
3311
|
+
now: generatedAt
|
|
3312
|
+
});
|
|
3313
|
+
return serializeHourlySnapshot({
|
|
3314
|
+
command: buildJsonHourlySnapshotCommand(command),
|
|
3315
|
+
generatedAt,
|
|
3316
|
+
hourlyReport: commandResult2.hourlyReport
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3319
|
+
if (command.commandName === "today") {
|
|
3320
|
+
const commandResult2 = await buildTodayCommandResult(command, {
|
|
3321
|
+
now: generatedAt
|
|
3322
|
+
});
|
|
3323
|
+
return serializeSummarySnapshot({
|
|
3324
|
+
command: buildJsonSummarySnapshotCommand(command),
|
|
3325
|
+
generatedAt,
|
|
3326
|
+
hourlyReport: null,
|
|
3327
|
+
mode: "today",
|
|
3328
|
+
summaryReport: commandResult2.summaryReport
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
const commandResult = await buildLast24hCommandResult(command, {
|
|
3332
|
+
now: generatedAt
|
|
3333
|
+
});
|
|
3334
|
+
return serializeSummarySnapshot({
|
|
3335
|
+
command: buildJsonSummarySnapshotCommand(command),
|
|
3336
|
+
generatedAt,
|
|
3337
|
+
hourlyReport: commandResult.hourlyReport,
|
|
3338
|
+
mode: "last24h",
|
|
3339
|
+
summaryReport: commandResult.summaryReport
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
function buildJsonSummarySnapshotCommand(command) {
|
|
3343
|
+
return {
|
|
3344
|
+
idleCutoffMs: command.idleCutoffMs,
|
|
3345
|
+
filters: { ...command.filters },
|
|
3346
|
+
groupBy: [...command.groupBy],
|
|
3347
|
+
wakeWindow: command.wakeWindow ? { ...command.wakeWindow } : null
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
function buildJsonHourlySnapshotCommand(command) {
|
|
3351
|
+
return {
|
|
3352
|
+
idleCutoffMs: command.idleCutoffMs,
|
|
3353
|
+
filters: { ...command.filters },
|
|
3354
|
+
wakeWindow: command.wakeWindow ? { ...command.wakeWindow } : null
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
function buildJsonLiveSnapshotCommand(command) {
|
|
3358
|
+
return {
|
|
3359
|
+
filters: { ...command.filters }
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
2406
3362
|
|
|
2407
3363
|
// src/cli/idletime-bin.ts
|
|
2408
3364
|
await runIdletimeCli(process.argv.slice(2));
|