@dreb/coding-agent 2.66.3 → 2.68.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 +4 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +12 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -4
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +5 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +16 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/daily-cost-tracker.d.ts +22 -6
- package/dist/core/daily-cost-tracker.d.ts.map +1 -1
- package/dist/core/daily-cost-tracker.js +117 -47
- package/dist/core/daily-cost-tracker.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -2
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +17 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +2 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +32 -4
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +234 -108
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/subagent-session-cost-tracker.d.ts +22 -0
- package/dist/core/subagent-session-cost-tracker.d.ts.map +1 -0
- package/dist/core/subagent-session-cost-tracker.js +94 -0
- package/dist/core/subagent-session-cost-tracker.js.map +1 -0
- package/dist/core/tools/subagent.d.ts +14 -8
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +145 -71
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +16 -7
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +5 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +7 -2
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +3 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +2 -2
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +34 -9
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +2 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/dashboard.md +27 -15
- package/docs/rpc.md +9 -5
- package/docs/session.md +16 -7
- package/docs/settings.md +17 -5
- package/docs/skills.md +1 -1
- package/package.json +1 -1
- package/skills/model-routing-guide/SKILL.md +10 -5
|
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { readdir, readFile, stat, unlink } from "node:fs/promises";
|
|
3
3
|
import { randomUUID } from "crypto";
|
|
4
4
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, statSync, writeFileSync, } from "fs";
|
|
5
|
-
import { join, resolve } from "path";
|
|
5
|
+
import { dirname, join, resolve, sep } from "path";
|
|
6
6
|
import { getAgentDir as getDefaultAgentDir, getSessionsDir } from "../config.js";
|
|
7
7
|
import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, } from "./messages.js";
|
|
8
8
|
export const CURRENT_SESSION_VERSION = 3;
|
|
@@ -340,99 +340,197 @@ function getSessionCreatedDate(header, statsMtime) {
|
|
|
340
340
|
function resolveForDeletion(filePath) {
|
|
341
341
|
return resolve(filePath);
|
|
342
342
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
343
|
+
const sessionMetadataCache = new Map();
|
|
344
|
+
const sessionMetadataInflight = new Map();
|
|
345
|
+
function cloneSessionMetadata(metadata) {
|
|
346
|
+
return {
|
|
347
|
+
...metadata,
|
|
348
|
+
created: new Date(metadata.created),
|
|
349
|
+
modified: new Date(metadata.modified),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function toSessionMetadata(info) {
|
|
353
|
+
const { allMessagesText: _allMessagesText, ...metadata } = info;
|
|
354
|
+
return metadata;
|
|
355
|
+
}
|
|
356
|
+
async function parseSessionInfo(filePath, stats, includeAllMessagesText) {
|
|
357
|
+
const content = await readFile(filePath, "utf8");
|
|
358
|
+
const entries = [];
|
|
359
|
+
const lines = content.trim().split("\n");
|
|
360
|
+
for (const line of lines) {
|
|
361
|
+
if (!line.trim())
|
|
362
|
+
continue;
|
|
363
|
+
try {
|
|
364
|
+
entries.push(JSON.parse(line));
|
|
357
365
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
366
|
+
catch {
|
|
367
|
+
// Skip malformed lines
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (entries.length === 0)
|
|
371
|
+
return null;
|
|
372
|
+
const header = entries[0];
|
|
373
|
+
if (header.type !== "session")
|
|
374
|
+
return null;
|
|
375
|
+
let messageCount = 0;
|
|
376
|
+
let firstMessage = "";
|
|
377
|
+
const allMessages = [];
|
|
378
|
+
let name;
|
|
379
|
+
for (const entry of entries) {
|
|
380
|
+
// Extract session name (use latest, including explicit clears)
|
|
381
|
+
if (entry.type === "session_info") {
|
|
382
|
+
const infoEntry = entry;
|
|
383
|
+
name = infoEntry.name?.trim() || undefined;
|
|
384
|
+
}
|
|
385
|
+
if (entry.type !== "message")
|
|
386
|
+
continue;
|
|
387
|
+
messageCount++;
|
|
388
|
+
const message = entry.message;
|
|
389
|
+
if (!isMessageWithContent(message))
|
|
390
|
+
continue;
|
|
391
|
+
if (message.role !== "user" && message.role !== "assistant")
|
|
392
|
+
continue;
|
|
393
|
+
const textContent = extractTextContent(message);
|
|
394
|
+
if (!textContent)
|
|
395
|
+
continue;
|
|
396
|
+
if (includeAllMessagesText)
|
|
385
397
|
allMessages.push(textContent);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
398
|
+
if (!firstMessage && message.role === "user") {
|
|
399
|
+
firstMessage = textContent;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const cwd = typeof header.cwd === "string" ? header.cwd : "";
|
|
403
|
+
const parentSessionPath = header.parentSession;
|
|
404
|
+
const modified = getSessionModifiedDate(entries, header, stats.mtime);
|
|
405
|
+
const created = getSessionCreatedDate(header, stats.mtime);
|
|
406
|
+
return {
|
|
407
|
+
path: filePath,
|
|
408
|
+
id: header.id,
|
|
409
|
+
cwd,
|
|
410
|
+
name,
|
|
411
|
+
parentSessionPath,
|
|
412
|
+
created,
|
|
413
|
+
modified,
|
|
414
|
+
messageCount,
|
|
415
|
+
firstMessage: firstMessage || "(no messages)",
|
|
416
|
+
allMessagesText: allMessages.join(" "),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function isFilesystemError(error) {
|
|
420
|
+
return typeof error?.code === "string";
|
|
421
|
+
}
|
|
422
|
+
async function buildSessionInfo(filePath, throwOnFilesystemError = false) {
|
|
423
|
+
try {
|
|
424
|
+
const stats = await stat(filePath);
|
|
425
|
+
return await parseSessionInfo(filePath, stats, true);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
if (throwOnFilesystemError && isFilesystemError(error))
|
|
429
|
+
throw error;
|
|
430
|
+
/* Corrupt session file — exclude from listing */
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async function buildSessionMetadata(filePath, throwOnFilesystemError = false) {
|
|
435
|
+
try {
|
|
436
|
+
const stats = await stat(filePath);
|
|
437
|
+
const cached = sessionMetadataCache.get(filePath);
|
|
438
|
+
if (cached?.mtimeMs === stats.mtimeMs && cached.size === stats.size) {
|
|
439
|
+
return cached.metadata ? cloneSessionMetadata(cached.metadata) : null;
|
|
440
|
+
}
|
|
441
|
+
const inflight = sessionMetadataInflight.get(filePath);
|
|
442
|
+
if (inflight?.mtimeMs === stats.mtimeMs && inflight.size === stats.size) {
|
|
443
|
+
const metadata = await inflight.promise;
|
|
444
|
+
return metadata ? cloneSessionMetadata(metadata) : null;
|
|
445
|
+
}
|
|
446
|
+
const promise = parseSessionInfo(filePath, stats, false).then((info) => {
|
|
447
|
+
const metadata = info ? toSessionMetadata(info) : null;
|
|
448
|
+
sessionMetadataCache.set(filePath, {
|
|
449
|
+
mtimeMs: stats.mtimeMs,
|
|
450
|
+
size: stats.size,
|
|
451
|
+
metadata: metadata ? cloneSessionMetadata(metadata) : null,
|
|
452
|
+
});
|
|
453
|
+
return metadata;
|
|
454
|
+
});
|
|
455
|
+
const current = { mtimeMs: stats.mtimeMs, size: stats.size, promise };
|
|
456
|
+
sessionMetadataInflight.set(filePath, current);
|
|
457
|
+
try {
|
|
458
|
+
const metadata = await promise;
|
|
459
|
+
return metadata ? cloneSessionMetadata(metadata) : null;
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
if (sessionMetadataInflight.get(filePath) === current)
|
|
463
|
+
sessionMetadataInflight.delete(filePath);
|
|
389
464
|
}
|
|
390
|
-
const cwd = typeof header.cwd === "string" ? header.cwd : "";
|
|
391
|
-
const parentSessionPath = header.parentSession;
|
|
392
|
-
const modified = getSessionModifiedDate(entries, header, stats.mtime);
|
|
393
|
-
const created = getSessionCreatedDate(header, stats.mtime);
|
|
394
|
-
return {
|
|
395
|
-
path: filePath,
|
|
396
|
-
id: header.id,
|
|
397
|
-
cwd,
|
|
398
|
-
name,
|
|
399
|
-
parentSessionPath,
|
|
400
|
-
created,
|
|
401
|
-
modified,
|
|
402
|
-
messageCount,
|
|
403
|
-
firstMessage: firstMessage || "(no messages)",
|
|
404
|
-
allMessagesText: allMessages.join(" "),
|
|
405
|
-
};
|
|
406
465
|
}
|
|
407
|
-
catch {
|
|
466
|
+
catch (error) {
|
|
467
|
+
if (throwOnFilesystemError && isFilesystemError(error))
|
|
468
|
+
throw error;
|
|
408
469
|
/* Corrupt session file — exclude from listing */
|
|
409
470
|
return null;
|
|
410
471
|
}
|
|
411
472
|
}
|
|
412
|
-
async function
|
|
473
|
+
async function collectSessionMetadata(files, isInScope, onProgress, throwOnFilesystemError = false) {
|
|
474
|
+
// Prune cache entries for deleted files, but only within the scanned scope so
|
|
475
|
+
// default and custom-directory listings don't evict each other's entries.
|
|
476
|
+
const currentFiles = new Set(files);
|
|
477
|
+
for (const cachedPath of sessionMetadataCache.keys()) {
|
|
478
|
+
if (isInScope(cachedPath) && !currentFiles.has(cachedPath))
|
|
479
|
+
sessionMetadataCache.delete(cachedPath);
|
|
480
|
+
}
|
|
481
|
+
let loaded = 0;
|
|
482
|
+
const results = await Promise.all(files.map(async (file) => {
|
|
483
|
+
const metadata = await buildSessionMetadata(file, throwOnFilesystemError);
|
|
484
|
+
loaded++;
|
|
485
|
+
onProgress?.(loaded, files.length);
|
|
486
|
+
return metadata;
|
|
487
|
+
}));
|
|
488
|
+
const sessions = results.filter((metadata) => metadata !== null);
|
|
489
|
+
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
490
|
+
return sessions;
|
|
491
|
+
}
|
|
492
|
+
async function listAllSessionFiles() {
|
|
493
|
+
const sessionsDir = getSessionsDir();
|
|
494
|
+
if (!existsSync(sessionsDir))
|
|
495
|
+
return [];
|
|
496
|
+
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
|
497
|
+
const dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => join(sessionsDir, entry.name));
|
|
498
|
+
const dirFiles = await Promise.all(dirs.map(async (dir) => (await readdir(dir)).filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file))));
|
|
499
|
+
return dirFiles.flat();
|
|
500
|
+
}
|
|
501
|
+
async function listSessionsFromDir(dir, onProgress, progressOffset = 0, progressTotal, throwOnFilesystemError = false) {
|
|
413
502
|
const sessions = [];
|
|
414
|
-
if (!existsSync(dir)) {
|
|
503
|
+
if (!throwOnFilesystemError && !existsSync(dir)) {
|
|
415
504
|
return sessions;
|
|
416
505
|
}
|
|
506
|
+
let dirEntries;
|
|
507
|
+
try {
|
|
508
|
+
dirEntries = await readdir(dir);
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
if (throwOnFilesystemError && error?.code !== "ENOENT")
|
|
512
|
+
throw error;
|
|
513
|
+
return sessions;
|
|
514
|
+
}
|
|
515
|
+
const files = dirEntries.filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file));
|
|
516
|
+
const total = progressTotal ?? files.length;
|
|
417
517
|
try {
|
|
418
|
-
const dirEntries = await readdir(dir);
|
|
419
|
-
const files = dirEntries.filter((f) => f.endsWith(".jsonl")).map((f) => join(dir, f));
|
|
420
|
-
const total = progressTotal ?? files.length;
|
|
421
518
|
let loaded = 0;
|
|
422
519
|
const results = await Promise.all(files.map(async (file) => {
|
|
423
|
-
const info = await buildSessionInfo(file);
|
|
520
|
+
const info = await buildSessionInfo(file, throwOnFilesystemError);
|
|
424
521
|
loaded++;
|
|
425
522
|
onProgress?.(progressOffset + loaded, total);
|
|
426
523
|
return info;
|
|
427
524
|
}));
|
|
428
525
|
for (const info of results) {
|
|
429
|
-
if (info)
|
|
526
|
+
if (info)
|
|
430
527
|
sessions.push(info);
|
|
431
|
-
}
|
|
432
528
|
}
|
|
433
529
|
}
|
|
434
|
-
catch {
|
|
435
|
-
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (throwOnFilesystemError)
|
|
532
|
+
throw error;
|
|
533
|
+
/* Session file read failed — return partial results */
|
|
436
534
|
}
|
|
437
535
|
return sessions;
|
|
438
536
|
}
|
|
@@ -451,6 +549,7 @@ export class SessionManager {
|
|
|
451
549
|
sessionId = "";
|
|
452
550
|
sessionFile;
|
|
453
551
|
sessionDir;
|
|
552
|
+
customSessionInventoryRoot;
|
|
454
553
|
cwd;
|
|
455
554
|
persist;
|
|
456
555
|
flushed = false;
|
|
@@ -458,9 +557,10 @@ export class SessionManager {
|
|
|
458
557
|
byId = new Map();
|
|
459
558
|
labelsById = new Map();
|
|
460
559
|
leafId = null;
|
|
461
|
-
constructor(cwd, sessionDir, sessionFile, persist) {
|
|
560
|
+
constructor(cwd, sessionDir, sessionFile, persist, customSessionInventoryRoot) {
|
|
462
561
|
this.cwd = cwd;
|
|
463
562
|
this.sessionDir = sessionDir;
|
|
563
|
+
this.customSessionInventoryRoot = customSessionInventoryRoot;
|
|
464
564
|
this.persist = persist;
|
|
465
565
|
if (persist && sessionDir && !existsSync(sessionDir)) {
|
|
466
566
|
mkdirSync(sessionDir, { recursive: true });
|
|
@@ -588,6 +688,13 @@ export class SessionManager {
|
|
|
588
688
|
getSessionDir() {
|
|
589
689
|
return this.sessionDir;
|
|
590
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* Flat inventory root selected explicitly for this manager, if any.
|
|
693
|
+
* Built-in per-project managers and in-memory managers return undefined.
|
|
694
|
+
*/
|
|
695
|
+
getCustomSessionInventoryRoot() {
|
|
696
|
+
return this.customSessionInventoryRoot;
|
|
697
|
+
}
|
|
591
698
|
getSessionId() {
|
|
592
699
|
return this.sessionId;
|
|
593
700
|
}
|
|
@@ -1050,9 +1157,10 @@ export class SessionManager {
|
|
|
1050
1157
|
* @param cwd Working directory (stored in session header)
|
|
1051
1158
|
* @param sessionDir Optional session directory. If omitted, uses default (~/.dreb/agent/sessions/<encoded-cwd>/).
|
|
1052
1159
|
*/
|
|
1053
|
-
static create(cwd, sessionDir) {
|
|
1160
|
+
static create(cwd, sessionDir, options = {}) {
|
|
1054
1161
|
const dir = sessionDir ?? getDefaultSessionDir(cwd);
|
|
1055
|
-
|
|
1162
|
+
const customInventoryRoot = sessionDir !== undefined && options.customSessionInventory !== false ? sessionDir : undefined;
|
|
1163
|
+
return new SessionManager(cwd, dir, undefined, true, customInventoryRoot);
|
|
1056
1164
|
}
|
|
1057
1165
|
/**
|
|
1058
1166
|
* Open a specific session file.
|
|
@@ -1066,7 +1174,7 @@ export class SessionManager {
|
|
|
1066
1174
|
const cwd = header?.cwd ?? process.cwd();
|
|
1067
1175
|
// If no sessionDir provided, derive from file's parent directory
|
|
1068
1176
|
const dir = sessionDir ?? resolve(path, "..");
|
|
1069
|
-
return new SessionManager(cwd, dir, path, true);
|
|
1177
|
+
return new SessionManager(cwd, dir, path, true, sessionDir);
|
|
1070
1178
|
}
|
|
1071
1179
|
/**
|
|
1072
1180
|
* Continue the most recent session, or create new if none.
|
|
@@ -1077,13 +1185,13 @@ export class SessionManager {
|
|
|
1077
1185
|
const dir = sessionDir ?? getDefaultSessionDir(cwd);
|
|
1078
1186
|
const mostRecent = findMostRecentSession(dir);
|
|
1079
1187
|
if (mostRecent) {
|
|
1080
|
-
return new SessionManager(cwd, dir, mostRecent, true);
|
|
1188
|
+
return new SessionManager(cwd, dir, mostRecent, true, sessionDir);
|
|
1081
1189
|
}
|
|
1082
|
-
return new SessionManager(cwd, dir, undefined, true);
|
|
1190
|
+
return new SessionManager(cwd, dir, undefined, true, sessionDir);
|
|
1083
1191
|
}
|
|
1084
|
-
/** Create an in-memory session (no file persistence) */
|
|
1085
|
-
static inMemory(cwd = process.cwd()) {
|
|
1086
|
-
return new SessionManager(cwd, "", undefined, false);
|
|
1192
|
+
/** Create an in-memory session (no active-file persistence), optionally retaining a flat inventory root. */
|
|
1193
|
+
static inMemory(cwd = process.cwd(), customSessionInventoryRoot) {
|
|
1194
|
+
return new SessionManager(cwd, "", undefined, false, customSessionInventoryRoot);
|
|
1087
1195
|
}
|
|
1088
1196
|
/**
|
|
1089
1197
|
* Fork a session from another project directory into the current project.
|
|
@@ -1126,7 +1234,7 @@ export class SessionManager {
|
|
|
1126
1234
|
appendFileSync(newSessionFile, `${JSON.stringify(entry)}\n`);
|
|
1127
1235
|
}
|
|
1128
1236
|
}
|
|
1129
|
-
return new SessionManager(targetCwd, dir, newSessionFile, true);
|
|
1237
|
+
return new SessionManager(targetCwd, dir, newSessionFile, true, sessionDir);
|
|
1130
1238
|
}
|
|
1131
1239
|
/**
|
|
1132
1240
|
* Delete a session file, trying the `trash` CLI first, then falling back to unlink.
|
|
@@ -1201,46 +1309,64 @@ export class SessionManager {
|
|
|
1201
1309
|
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1202
1310
|
return sessions;
|
|
1203
1311
|
}
|
|
1312
|
+
/**
|
|
1313
|
+
* List every session stored directly in one explicit flat directory.
|
|
1314
|
+
* This is intentionally separate from listAll(), whose default contract scans
|
|
1315
|
+
* the built-in nested all-project tree.
|
|
1316
|
+
*/
|
|
1317
|
+
static async listAllFromDir(dir, onProgress) {
|
|
1318
|
+
const sessions = await listSessionsFromDir(dir, onProgress, 0, undefined, true);
|
|
1319
|
+
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1320
|
+
return sessions;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* List lightweight metadata for every session stored directly in one explicit flat directory.
|
|
1324
|
+
* Uses the same mtime/size cache as listAllMetadata(); filesystem errors propagate.
|
|
1325
|
+
*/
|
|
1326
|
+
static async listAllMetadataFromDir(dir, onProgress) {
|
|
1327
|
+
let dirEntries;
|
|
1328
|
+
try {
|
|
1329
|
+
dirEntries = await readdir(dir);
|
|
1330
|
+
}
|
|
1331
|
+
catch (error) {
|
|
1332
|
+
if (error?.code === "ENOENT")
|
|
1333
|
+
return [];
|
|
1334
|
+
throw error;
|
|
1335
|
+
}
|
|
1336
|
+
const files = dirEntries.filter((file) => file.endsWith(".jsonl")).map((file) => join(dir, file));
|
|
1337
|
+
const scope = resolve(dir);
|
|
1338
|
+
return collectSessionMetadata(files, (cachedPath) => dirname(resolve(cachedPath)) === scope, onProgress, true);
|
|
1339
|
+
}
|
|
1204
1340
|
/**
|
|
1205
1341
|
* List all sessions across all project directories.
|
|
1206
1342
|
* @param onProgress Optional callback for progress updates (loaded, total)
|
|
1207
1343
|
*/
|
|
1208
1344
|
static async listAll(onProgress) {
|
|
1209
|
-
const sessionsDir = getSessionsDir();
|
|
1210
1345
|
// A missing sessions directory is a legitimate empty state (fresh install), not a
|
|
1211
|
-
// failure
|
|
1212
|
-
//
|
|
1213
|
-
|
|
1214
|
-
if (!existsSync(sessionsDir)) {
|
|
1215
|
-
return [];
|
|
1216
|
-
}
|
|
1217
|
-
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
|
1218
|
-
const dirs = entries.filter((e) => e.isDirectory()).map((e) => join(sessionsDir, e.name));
|
|
1219
|
-
// Count total files first for accurate progress
|
|
1220
|
-
let totalFiles = 0;
|
|
1221
|
-
const dirFiles = [];
|
|
1222
|
-
for (const dir of dirs) {
|
|
1223
|
-
const files = (await readdir(dir)).filter((f) => f.endsWith(".jsonl"));
|
|
1224
|
-
dirFiles.push(files.map((f) => join(dir, f)));
|
|
1225
|
-
totalFiles += files.length;
|
|
1226
|
-
}
|
|
1227
|
-
// Process all files with progress tracking
|
|
1346
|
+
// failure. Actual I/O failures propagate so callers can distinguish "no sessions"
|
|
1347
|
+
// from "listing failed".
|
|
1348
|
+
const allFiles = await listAllSessionFiles();
|
|
1228
1349
|
let loaded = 0;
|
|
1229
|
-
const sessions = [];
|
|
1230
|
-
const allFiles = dirFiles.flat();
|
|
1231
1350
|
const results = await Promise.all(allFiles.map(async (file) => {
|
|
1232
1351
|
const info = await buildSessionInfo(file);
|
|
1233
1352
|
loaded++;
|
|
1234
|
-
onProgress?.(loaded,
|
|
1353
|
+
onProgress?.(loaded, allFiles.length);
|
|
1235
1354
|
return info;
|
|
1236
1355
|
}));
|
|
1237
|
-
|
|
1238
|
-
if (info) {
|
|
1239
|
-
sessions.push(info);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1356
|
+
const sessions = results.filter((info) => info !== null);
|
|
1242
1357
|
sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
1243
1358
|
return sessions;
|
|
1244
1359
|
}
|
|
1360
|
+
/**
|
|
1361
|
+
* List lightweight metadata across all project directories.
|
|
1362
|
+
*
|
|
1363
|
+
* Results are cached by file mtime and size, so dashboard inventory refreshes
|
|
1364
|
+
* stat every session but only reread and parse files that changed.
|
|
1365
|
+
*/
|
|
1366
|
+
static async listAllMetadata(onProgress) {
|
|
1367
|
+
const allFiles = await listAllSessionFiles();
|
|
1368
|
+
const scope = resolve(getSessionsDir()) + sep;
|
|
1369
|
+
return collectSessionMetadata(allFiles, (cachedPath) => resolve(cachedPath).startsWith(scope), onProgress);
|
|
1370
|
+
}
|
|
1245
1371
|
}
|
|
1246
1372
|
//# sourceMappingURL=session-manager.js.map
|