agentwaste-core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -7
- package/package.json +2 -2
- package/src/cli.js +50 -175
- package/src/index.js +1 -2
- package/src/report.js +0 -495
- package/src/scanner.js +133 -45
- package/src/tools.js +238 -577
package/src/scanner.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { createReadStream, existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { cpus, homedir } from "node:os";
|
|
3
4
|
import { basename, join, relative } from "node:path";
|
|
4
5
|
import { createInterface } from "node:readline";
|
|
5
6
|
|
|
6
7
|
const DEFAULT_SCAN_DAYS = 365;
|
|
7
|
-
const DEFAULT_MAX_FILES =
|
|
8
|
+
const DEFAULT_MAX_FILES = 5000;
|
|
9
|
+
const EXHAUSTIVE_MAX_FILES = Number.MAX_SAFE_INTEGER;
|
|
10
|
+
const DEFAULT_SCAN_CONCURRENCY = Math.min(32, Math.max(4, cpus().length * 2 || 4));
|
|
11
|
+
const MAX_SCAN_CONCURRENCY = 64;
|
|
8
12
|
const MAX_FILE_BYTES = 64 * 1024 * 1024;
|
|
9
13
|
const CONTEXT_REVIEW_FRESH_INPUT_PER_TOOL_CALL = 4000;
|
|
10
14
|
const DEFAULT_COST_PER_MILLION_TOKENS = 3.0;
|
|
11
15
|
|
|
16
|
+
export const SCAN_DEFAULTS = Object.freeze({
|
|
17
|
+
days: DEFAULT_SCAN_DAYS,
|
|
18
|
+
maxFiles: DEFAULT_MAX_FILES,
|
|
19
|
+
exhaustiveMaxFiles: EXHAUSTIVE_MAX_FILES,
|
|
20
|
+
concurrency: DEFAULT_SCAN_CONCURRENCY,
|
|
21
|
+
});
|
|
22
|
+
|
|
12
23
|
const IGNORED_DIRS = new Set([
|
|
13
24
|
".git",
|
|
14
25
|
".hg",
|
|
@@ -66,20 +77,21 @@ export async function scanAgentWaste(input = {}) {
|
|
|
66
77
|
const homeDir = input.homeDir ?? homedir();
|
|
67
78
|
const projectDir = input.projectDir ?? process.cwd();
|
|
68
79
|
const days = normalizeScanDays(input.days ?? DEFAULT_SCAN_DAYS);
|
|
69
|
-
const maxFiles = input.maxFiles ?? DEFAULT_MAX_FILES;
|
|
80
|
+
const maxFiles = normalizeMaxFiles(input.maxFiles ?? DEFAULT_MAX_FILES);
|
|
81
|
+
const concurrency = normalizeConcurrency(input.concurrency ?? DEFAULT_SCAN_CONCURRENCY);
|
|
70
82
|
const now = input.now ?? Date.now();
|
|
71
83
|
const sinceMs = days === "all" ? 0 : now - days * 24 * 60 * 60 * 1000;
|
|
72
84
|
const totals = createTotals({ homeDir, projectDir, days, sinceMs, now });
|
|
73
85
|
|
|
74
|
-
await scanCodex(homeDir, sinceMs, maxFiles, totals);
|
|
75
|
-
await scanClaude(homeDir, sinceMs, maxFiles, totals);
|
|
76
|
-
await scanPi(homeDir, sinceMs, maxFiles, totals);
|
|
77
|
-
await scanGemini(homeDir, sinceMs, maxFiles, totals);
|
|
78
|
-
await scanOpenCode(homeDir, sinceMs, maxFiles, totals);
|
|
79
|
-
await scanCursor(homeDir, sinceMs, maxFiles, totals);
|
|
80
|
-
await scanWindsurf(homeDir, sinceMs, maxFiles, totals);
|
|
81
|
-
await scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals);
|
|
82
|
-
await scanCopilot(homeDir, sinceMs, maxFiles, totals);
|
|
86
|
+
await scanCodex(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
87
|
+
await scanClaude(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
88
|
+
await scanPi(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
89
|
+
await scanGemini(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
90
|
+
await scanOpenCode(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
91
|
+
await scanCursor(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
92
|
+
await scanWindsurf(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
93
|
+
await scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
94
|
+
await scanCopilot(homeDir, sinceMs, maxFiles, totals, concurrency);
|
|
83
95
|
scanAider(projectDir, sinceMs, totals);
|
|
84
96
|
scanConfigFiles(homeDir, sinceMs, totals);
|
|
85
97
|
scanGlueCode(projectDir, totals);
|
|
@@ -93,6 +105,17 @@ function normalizeScanDays(value) {
|
|
|
93
105
|
return Number.isFinite(days) && days > 0 ? days : DEFAULT_SCAN_DAYS;
|
|
94
106
|
}
|
|
95
107
|
|
|
108
|
+
function normalizeMaxFiles(value) {
|
|
109
|
+
const maxFiles = Number.parseInt(value, 10);
|
|
110
|
+
return Number.isFinite(maxFiles) && maxFiles > 0 ? maxFiles : DEFAULT_MAX_FILES;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizeConcurrency(value) {
|
|
114
|
+
const concurrency = Number.parseInt(value, 10);
|
|
115
|
+
if (!Number.isFinite(concurrency) || concurrency <= 0) return DEFAULT_SCAN_CONCURRENCY;
|
|
116
|
+
return Math.min(MAX_SCAN_CONCURRENCY, concurrency);
|
|
117
|
+
}
|
|
118
|
+
|
|
96
119
|
function createTotals({ homeDir, projectDir, days, sinceMs, now }) {
|
|
97
120
|
return {
|
|
98
121
|
homeDir,
|
|
@@ -175,7 +198,7 @@ function createSourceStats(label) {
|
|
|
175
198
|
};
|
|
176
199
|
}
|
|
177
200
|
|
|
178
|
-
async function scanCodex(homeDir, sinceMs, maxFiles, totals) {
|
|
201
|
+
async function scanCodex(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
179
202
|
const root = join(homeDir, ".codex", "sessions");
|
|
180
203
|
const source = totals.sources.codex;
|
|
181
204
|
if (!existsSync(root)) {
|
|
@@ -186,7 +209,7 @@ async function scanCodex(homeDir, sinceMs, maxFiles, totals) {
|
|
|
186
209
|
const files = recentFiles(root, sinceMs, maxFiles, (path) => path.endsWith(".jsonl"));
|
|
187
210
|
source.files = files.length;
|
|
188
211
|
totals.files += files.length;
|
|
189
|
-
|
|
212
|
+
await scanFiles(files, concurrency, (path) => scanCodexFile(path, totals));
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
async function scanCodexFile(path, totals) {
|
|
@@ -274,7 +297,7 @@ async function scanCodexFile(path, totals) {
|
|
|
274
297
|
if (!session.id) addSession(totals, source, path);
|
|
275
298
|
}
|
|
276
299
|
|
|
277
|
-
async function scanClaude(homeDir, sinceMs, maxFiles, totals) {
|
|
300
|
+
async function scanClaude(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
278
301
|
const root = join(homeDir, ".claude", "projects");
|
|
279
302
|
const source = totals.sources.claude;
|
|
280
303
|
if (!existsSync(root)) {
|
|
@@ -285,7 +308,7 @@ async function scanClaude(homeDir, sinceMs, maxFiles, totals) {
|
|
|
285
308
|
const files = recentFiles(root, sinceMs, maxFiles, (path) => path.endsWith(".jsonl"));
|
|
286
309
|
source.files = files.length;
|
|
287
310
|
totals.files += files.length;
|
|
288
|
-
|
|
311
|
+
await scanFiles(files, concurrency, (path) => scanClaudeFile(path, totals));
|
|
289
312
|
|
|
290
313
|
const facetsRoot = join(homeDir, ".claude", "usage-data", "facets");
|
|
291
314
|
if (existsSync(facetsRoot)) {
|
|
@@ -357,12 +380,12 @@ async function scanClaudeFile(path, totals) {
|
|
|
357
380
|
if (!session.id) addSession(totals, source, path);
|
|
358
381
|
}
|
|
359
382
|
|
|
360
|
-
async function scanPi(homeDir, sinceMs, maxFiles, totals) {
|
|
383
|
+
async function scanPi(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
361
384
|
const source = totals.sources.pi;
|
|
362
385
|
const sessionRoot = join(homeDir, ".pi", "agent", "sessions");
|
|
363
386
|
const files = recentFilesFromRoots([sessionRoot], sinceMs, maxFiles, (path) => path.endsWith(".jsonl"));
|
|
364
387
|
if (files.length === 0 && !existsSync(sessionRoot)) source.unavailable = true;
|
|
365
|
-
|
|
388
|
+
await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: true }));
|
|
366
389
|
|
|
367
390
|
scanKnownFiles([
|
|
368
391
|
join(homeDir, ".pi", "agent", "settings.json"),
|
|
@@ -372,7 +395,7 @@ async function scanPi(homeDir, sinceMs, maxFiles, totals) {
|
|
|
372
395
|
], sinceMs, totals, source, { modelFacing: false, sessionPerFile: false });
|
|
373
396
|
}
|
|
374
397
|
|
|
375
|
-
async function scanGemini(homeDir, sinceMs, maxFiles, totals) {
|
|
398
|
+
async function scanGemini(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
376
399
|
const source = totals.sources.gemini;
|
|
377
400
|
const root = join(homeDir, ".gemini");
|
|
378
401
|
const sessionRoots = [join(root, "tmp"), join(root, "sessions"), join(root, "exports")];
|
|
@@ -384,7 +407,7 @@ async function scanGemini(homeDir, sinceMs, maxFiles, totals) {
|
|
|
384
407
|
name === "collector.log";
|
|
385
408
|
});
|
|
386
409
|
if (files.length === 0 && !existsSync(root)) source.unavailable = true;
|
|
387
|
-
|
|
410
|
+
await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: /(?:session|chat|\.jsonl)/i.test(path) }));
|
|
388
411
|
|
|
389
412
|
scanKnownFiles([
|
|
390
413
|
join(root, "settings.json"),
|
|
@@ -393,12 +416,12 @@ async function scanGemini(homeDir, sinceMs, maxFiles, totals) {
|
|
|
393
416
|
], sinceMs, totals, source, { modelFacing: false, sessionPerFile: false });
|
|
394
417
|
}
|
|
395
418
|
|
|
396
|
-
async function scanOpenCode(homeDir, sinceMs, maxFiles, totals) {
|
|
419
|
+
async function scanOpenCode(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
397
420
|
const source = totals.sources.opencode;
|
|
398
421
|
const root = join(homeDir, ".local", "share", "opencode");
|
|
399
422
|
const files = recentFilesFromRoots([join(root, "log"), join(root, "project")], sinceMs, maxFiles, isLikelyAgentDataFile);
|
|
400
423
|
if (files.length === 0 && !existsSync(root)) source.unavailable = true;
|
|
401
|
-
|
|
424
|
+
await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: path.includes("/project/") }));
|
|
402
425
|
|
|
403
426
|
scanKnownFiles([
|
|
404
427
|
join(root, "auth.json"),
|
|
@@ -407,7 +430,7 @@ async function scanOpenCode(homeDir, sinceMs, maxFiles, totals) {
|
|
|
407
430
|
], sinceMs, totals, source, { modelFacing: false, sessionPerFile: false });
|
|
408
431
|
}
|
|
409
432
|
|
|
410
|
-
async function scanCursor(homeDir, sinceMs, maxFiles, totals) {
|
|
433
|
+
async function scanCursor(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
411
434
|
const source = totals.sources.cursor;
|
|
412
435
|
const roots = editorUserRoots(homeDir, "Cursor");
|
|
413
436
|
const files = recentFilesFromRoots([
|
|
@@ -417,10 +440,10 @@ async function scanCursor(homeDir, sinceMs, maxFiles, totals) {
|
|
|
417
440
|
join(homeDir, ".cursor"),
|
|
418
441
|
], sinceMs, maxFiles, isLikelyAgentDataFile);
|
|
419
442
|
if (files.length === 0 && roots.every((root) => !existsSync(root)) && !existsSync(join(homeDir, ".cursor"))) source.unavailable = true;
|
|
420
|
-
|
|
443
|
+
await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: false }));
|
|
421
444
|
}
|
|
422
445
|
|
|
423
|
-
async function scanWindsurf(homeDir, sinceMs, maxFiles, totals) {
|
|
446
|
+
async function scanWindsurf(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
424
447
|
const source = totals.sources.windsurf;
|
|
425
448
|
const roots = editorUserRoots(homeDir, "Windsurf");
|
|
426
449
|
const files = recentFilesFromRoots([
|
|
@@ -431,10 +454,10 @@ async function scanWindsurf(homeDir, sinceMs, maxFiles, totals) {
|
|
|
431
454
|
join(homeDir, ".windsurf"),
|
|
432
455
|
], sinceMs, maxFiles, isLikelyAgentDataFile);
|
|
433
456
|
if (files.length === 0 && roots.every((root) => !existsSync(root)) && !existsSync(join(homeDir, ".windsurf"))) source.unavailable = true;
|
|
434
|
-
|
|
457
|
+
await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: false }));
|
|
435
458
|
}
|
|
436
459
|
|
|
437
|
-
async function scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals) {
|
|
460
|
+
async function scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
438
461
|
const editorRoots = [
|
|
439
462
|
...editorUserRoots(homeDir, "Code"),
|
|
440
463
|
...editorUserRoots(homeDir, "Code - Insiders"),
|
|
@@ -455,11 +478,11 @@ async function scanEditorExtensionAgents(homeDir, sinceMs, maxFiles, totals) {
|
|
|
455
478
|
}
|
|
456
479
|
const files = recentFilesFromRoots(roots, sinceMs, Math.min(maxFiles, 1000), isLikelyAgentDataFile);
|
|
457
480
|
if (files.length === 0 && roots.every((root) => !existsSync(root))) item.source.unavailable = true;
|
|
458
|
-
|
|
481
|
+
await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, item.source, { modelFacing: true, sessionPerFile: /(?:task|history|session|conversation)/i.test(path) }));
|
|
459
482
|
}
|
|
460
483
|
}
|
|
461
484
|
|
|
462
|
-
async function scanCopilot(homeDir, sinceMs, maxFiles, totals) {
|
|
485
|
+
async function scanCopilot(homeDir, sinceMs, maxFiles, totals, concurrency) {
|
|
463
486
|
const source = totals.sources.copilot;
|
|
464
487
|
const roots = [
|
|
465
488
|
join(homeDir, ".copilot", "session-state"),
|
|
@@ -472,7 +495,7 @@ async function scanCopilot(homeDir, sinceMs, maxFiles, totals) {
|
|
|
472
495
|
return isLikelyAgentDataFile(path);
|
|
473
496
|
});
|
|
474
497
|
if (files.length === 0 && roots.every((root) => !existsSync(root))) source.unavailable = true;
|
|
475
|
-
|
|
498
|
+
await scanFiles(files, concurrency, (path) => scanGenericAgentFile(path, totals, source, { modelFacing: true, sessionPerFile: !path.endsWith(".vscdb") }));
|
|
476
499
|
}
|
|
477
500
|
|
|
478
501
|
function scanAider(projectDir, sinceMs, totals) {
|
|
@@ -529,16 +552,27 @@ function scanKnownFiles(paths, sinceMs, totals, source, options) {
|
|
|
529
552
|
}
|
|
530
553
|
|
|
531
554
|
async function scanGenericAgentFile(path, totals, source, options = {}) {
|
|
532
|
-
registerAgentDataFile(totals, source, path);
|
|
555
|
+
if (!registerAgentDataFile(totals, source, path)) return;
|
|
533
556
|
if (path.endsWith(".jsonl")) {
|
|
534
557
|
await scanGenericJsonLines(path, totals, source, options);
|
|
535
558
|
return;
|
|
536
559
|
}
|
|
537
|
-
|
|
560
|
+
await scanGenericAgentFileText(path, totals, source, options);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async function scanGenericAgentFileText(path, totals, source, options = {}) {
|
|
564
|
+
let text;
|
|
565
|
+
try {
|
|
566
|
+
text = await readFile(path, "utf8");
|
|
567
|
+
} catch {
|
|
568
|
+
totals.diagnostics.push(`Skipped unreadable ${source.label} file ${path}`);
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
scanGenericText(text, totals, source, path, options);
|
|
538
572
|
}
|
|
539
573
|
|
|
540
574
|
function scanGenericAgentFileSync(path, totals, source, options = {}) {
|
|
541
|
-
registerAgentDataFile(totals, source, path);
|
|
575
|
+
if (!registerAgentDataFile(totals, source, path)) return;
|
|
542
576
|
let text;
|
|
543
577
|
try {
|
|
544
578
|
text = readFileSync(path, "utf8");
|
|
@@ -546,6 +580,10 @@ function scanGenericAgentFileSync(path, totals, source, options = {}) {
|
|
|
546
580
|
totals.diagnostics.push(`Skipped unreadable ${source.label} file ${path}`);
|
|
547
581
|
return;
|
|
548
582
|
}
|
|
583
|
+
scanGenericText(text, totals, source, path, options);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function scanGenericText(text, totals, source, path, options = {}) {
|
|
549
587
|
scanTextForSecrets(text, totals, { modelFacing: options.modelFacing === true, file: path });
|
|
550
588
|
scanTextForStateLoss(text, totals, path);
|
|
551
589
|
scanTextForTrace(text, totals, path);
|
|
@@ -553,6 +591,20 @@ function scanGenericAgentFileSync(path, totals, source, options = {}) {
|
|
|
553
591
|
if (path.endsWith(".json")) scanGenericJsonDocument(text, totals, source, path);
|
|
554
592
|
}
|
|
555
593
|
|
|
594
|
+
async function scanFiles(files, concurrency, scanFile) {
|
|
595
|
+
if (files.length === 0) return;
|
|
596
|
+
const workerCount = Math.min(concurrency, files.length);
|
|
597
|
+
let next = 0;
|
|
598
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
599
|
+
while (next < files.length) {
|
|
600
|
+
const path = files[next];
|
|
601
|
+
next += 1;
|
|
602
|
+
await scanFile(path);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
await Promise.all(workers);
|
|
606
|
+
}
|
|
607
|
+
|
|
556
608
|
async function scanGenericJsonLines(path, totals, source, options = {}) {
|
|
557
609
|
const session = createSessionAccumulator(source.label.toLowerCase(), path);
|
|
558
610
|
let sawRecord = false;
|
|
@@ -586,9 +638,11 @@ function noteGenericRecordSignals(record, totals, source, session) {
|
|
|
586
638
|
addSession(totals, source, id);
|
|
587
639
|
}
|
|
588
640
|
|
|
589
|
-
const usage = normalizeGenericUsage(record.usage ?? record.tokenUsage ?? record.token_usage ?? record.metadata?.usage);
|
|
641
|
+
const usage = normalizeGenericUsage(record.usage ?? record.tokenUsage ?? record.token_usage ?? record.message?.usage ?? record.metadata?.usage);
|
|
590
642
|
if (usage.total > 0) addUsage(totals, source, usage);
|
|
591
643
|
|
|
644
|
+
noteNestedMessageToolSignals(record, totals, source, session);
|
|
645
|
+
|
|
592
646
|
if (isGenericToolStart(record)) {
|
|
593
647
|
const callId = record.id ?? record.call_id ?? record.tool_call_id ?? stableSnippet(record).slice(0, 64);
|
|
594
648
|
addToolCall(totals, source);
|
|
@@ -601,6 +655,27 @@ function noteGenericRecordSignals(record, totals, source, session) {
|
|
|
601
655
|
}
|
|
602
656
|
}
|
|
603
657
|
|
|
658
|
+
function noteNestedMessageToolSignals(record, totals, source, session) {
|
|
659
|
+
const message = record.message;
|
|
660
|
+
if (!message || typeof message !== "object") return;
|
|
661
|
+
|
|
662
|
+
for (const block of array(message.content)) {
|
|
663
|
+
if (!block || typeof block !== "object" || String(block.type).toLowerCase() !== "toolcall") continue;
|
|
664
|
+
const callId = block.id ?? record.id ?? stableSnippet(block).slice(0, 64);
|
|
665
|
+
addToolCall(totals, source);
|
|
666
|
+
noteToolStart(session, callId, toolSignature(block.name, block.arguments ?? block.input));
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (String(message.role).toLowerCase() !== "toolresult" && !message.toolCallId && !message.toolName) return;
|
|
670
|
+
const failed = message.isError === true || isGenericToolFailure({ ...record, ...message });
|
|
671
|
+
const callId = message.toolCallId ?? record.toolCallId ?? record.tool_call_id;
|
|
672
|
+
const signature = callId && session.calls.has(callId)
|
|
673
|
+
? session.calls.get(callId)
|
|
674
|
+
: toolSignature(message.toolName ?? record.toolName ?? record.tool_name ?? "tool_result", message.content ?? record.output ?? record.result);
|
|
675
|
+
if (failed) addFailure(totals, source);
|
|
676
|
+
noteToolEnd(totals, session, signature, failed, parseTime(message.timestamp ?? record.timestamp), 0);
|
|
677
|
+
}
|
|
678
|
+
|
|
604
679
|
function visitGenericJson(value, visit, depth = 0) {
|
|
605
680
|
if (depth > 8 || value == null) return;
|
|
606
681
|
if (Array.isArray(value)) {
|
|
@@ -640,7 +715,12 @@ function scanGlueCode(projectDir, totals) {
|
|
|
640
715
|
}
|
|
641
716
|
|
|
642
717
|
function recentFiles(root, sinceMs, maxFiles, predicate) {
|
|
718
|
+
return recentFileEntries(root, sinceMs, maxFiles, predicate).map((item) => item.path);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function recentFileEntries(root, sinceMs, maxFiles, predicate) {
|
|
643
722
|
const files = [];
|
|
723
|
+
const shouldTrim = maxFiles < EXHAUSTIVE_MAX_FILES;
|
|
644
724
|
walk(root, (path, dirent) => {
|
|
645
725
|
if (dirent.isDirectory()) {
|
|
646
726
|
if (IGNORED_DIRS.has(dirent.name)) return false;
|
|
@@ -650,27 +730,35 @@ function recentFiles(root, sinceMs, maxFiles, predicate) {
|
|
|
650
730
|
const stat = safeStat(path);
|
|
651
731
|
if (!stat || stat.size > MAX_FILE_BYTES || stat.mtimeMs < sinceMs) return false;
|
|
652
732
|
files.push({ path, mtimeMs: stat.mtimeMs });
|
|
733
|
+
trimRecentFileEntries(files, maxFiles, shouldTrim);
|
|
653
734
|
return false;
|
|
654
735
|
});
|
|
655
736
|
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
656
|
-
return files.slice(0, maxFiles)
|
|
737
|
+
return files.slice(0, maxFiles);
|
|
657
738
|
}
|
|
658
739
|
|
|
659
740
|
function recentFilesFromRoots(roots, sinceMs, maxFiles, predicate) {
|
|
660
741
|
const seen = new Set();
|
|
661
742
|
const files = [];
|
|
743
|
+
const shouldTrim = maxFiles < EXHAUSTIVE_MAX_FILES;
|
|
662
744
|
for (const root of roots) {
|
|
663
|
-
for (const
|
|
664
|
-
if (seen.has(path)) continue;
|
|
665
|
-
seen.add(path);
|
|
666
|
-
|
|
667
|
-
|
|
745
|
+
for (const item of recentFileEntries(root, sinceMs, maxFiles, predicate)) {
|
|
746
|
+
if (seen.has(item.path)) continue;
|
|
747
|
+
seen.add(item.path);
|
|
748
|
+
files.push(item);
|
|
749
|
+
trimRecentFileEntries(files, maxFiles, shouldTrim);
|
|
668
750
|
}
|
|
669
751
|
}
|
|
670
752
|
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
671
753
|
return files.slice(0, maxFiles).map((item) => item.path);
|
|
672
754
|
}
|
|
673
755
|
|
|
756
|
+
function trimRecentFileEntries(files, maxFiles, shouldTrim) {
|
|
757
|
+
if (!shouldTrim || files.length <= maxFiles * 2) return;
|
|
758
|
+
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
759
|
+
files.length = maxFiles;
|
|
760
|
+
}
|
|
761
|
+
|
|
674
762
|
function isLikelyAgentDataFile(path) {
|
|
675
763
|
const ext = extension(path);
|
|
676
764
|
const name = basename(path);
|
|
@@ -906,7 +994,7 @@ function hasToolResult(content) {
|
|
|
906
994
|
|
|
907
995
|
function isGenericModelFacing(record) {
|
|
908
996
|
const role = String(record?.role ?? record?.message?.role ?? record?.type ?? "").toLowerCase();
|
|
909
|
-
return role === "user" || role === "assistant" || role === "model" || role === "tool_use" || role === "function_call";
|
|
997
|
+
return role === "user" || role === "assistant" || role === "model" || role === "tool_use" || role === "function_call" || role === "toolresult" || role === "tool_result" || role === "tool";
|
|
910
998
|
}
|
|
911
999
|
|
|
912
1000
|
function genericRecordText(record) {
|
|
@@ -984,10 +1072,10 @@ function normalizeClaudeUsage(usage) {
|
|
|
984
1072
|
|
|
985
1073
|
function normalizeGenericUsage(usage) {
|
|
986
1074
|
if (!usage || typeof usage !== "object") return { input: 0, freshInput: 0, output: 0, cached: 0, reasoning: 0, total: 0, active: 0 };
|
|
987
|
-
const input = firstNumber(usage.input_tokens, usage.inputTokens, usage.prompt_tokens, usage.promptTokens, usage.promptTokenCount, usage.inputTokenCount);
|
|
988
|
-
const output = firstNumber(usage.output_tokens, usage.outputTokens, usage.completion_tokens, usage.completionTokens, usage.candidatesTokenCount, usage.outputTokenCount);
|
|
989
|
-
const cached = firstNumber(usage.cached_input_tokens, usage.cache_read_input_tokens, usage.cachedTokens, usage.cachedContentTokenCount);
|
|
990
|
-
const cacheCreation = firstNumber(usage.cache_creation_input_tokens, usage.cacheCreationInputTokens);
|
|
1075
|
+
const input = firstNumber(usage.input_tokens, usage.inputTokens, usage.prompt_tokens, usage.promptTokens, usage.promptTokenCount, usage.inputTokenCount, usage.input);
|
|
1076
|
+
const output = firstNumber(usage.output_tokens, usage.outputTokens, usage.completion_tokens, usage.completionTokens, usage.candidatesTokenCount, usage.outputTokenCount, usage.output);
|
|
1077
|
+
const cached = firstNumber(usage.cached_input_tokens, usage.cache_read_input_tokens, usage.cachedTokens, usage.cachedContentTokenCount, usage.cacheRead);
|
|
1078
|
+
const cacheCreation = firstNumber(usage.cache_creation_input_tokens, usage.cacheCreationInputTokens, usage.cacheWrite);
|
|
991
1079
|
const reasoning = firstNumber(usage.reasoning_output_tokens, usage.reasoningTokens, usage.thoughtsTokenCount);
|
|
992
1080
|
const total = firstNumber(usage.total_tokens, usage.totalTokens, usage.totalTokenCount) || input + output + cached + cacheCreation;
|
|
993
1081
|
const freshInput = Math.max(0, input + cacheCreation - cached);
|