@herdctl/core 5.6.0 → 5.7.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/dist/config/__tests__/merge.test.js +1 -1
- package/dist/config/__tests__/merge.test.js.map +1 -1
- package/dist/config/schema.d.ts +10 -2
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +6 -2
- package/dist/config/schema.js.map +1 -1
- package/dist/scheduler/schedule-runner.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.js +6 -5
- package/dist/scheduler/schedule-runner.js.map +1 -1
- package/dist/state/__tests__/jsonl-parser.test.d.ts +5 -0
- package/dist/state/__tests__/jsonl-parser.test.d.ts.map +1 -0
- package/dist/state/__tests__/jsonl-parser.test.js +332 -0
- package/dist/state/__tests__/jsonl-parser.test.js.map +1 -0
- package/dist/state/__tests__/session-attribution.test.d.ts +2 -0
- package/dist/state/__tests__/session-attribution.test.d.ts.map +1 -0
- package/dist/state/__tests__/session-attribution.test.js +567 -0
- package/dist/state/__tests__/session-attribution.test.js.map +1 -0
- package/dist/state/__tests__/session-discovery.test.d.ts +2 -0
- package/dist/state/__tests__/session-discovery.test.d.ts.map +1 -0
- package/dist/state/__tests__/session-discovery.test.js +953 -0
- package/dist/state/__tests__/session-discovery.test.js.map +1 -0
- package/dist/state/__tests__/session-metadata.test.d.ts +2 -0
- package/dist/state/__tests__/session-metadata.test.d.ts.map +1 -0
- package/dist/state/__tests__/session-metadata.test.js +474 -0
- package/dist/state/__tests__/session-metadata.test.js.map +1 -0
- package/dist/state/__tests__/tool-parsing.test.d.ts +5 -0
- package/dist/state/__tests__/tool-parsing.test.d.ts.map +1 -0
- package/dist/state/__tests__/tool-parsing.test.js +315 -0
- package/dist/state/__tests__/tool-parsing.test.js.map +1 -0
- package/dist/state/index.d.ts +5 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +10 -0
- package/dist/state/index.js.map +1 -1
- package/dist/state/jsonl-parser.d.ts +126 -0
- package/dist/state/jsonl-parser.d.ts.map +1 -0
- package/dist/state/jsonl-parser.js +482 -0
- package/dist/state/jsonl-parser.js.map +1 -0
- package/dist/state/session-attribution.d.ts +35 -0
- package/dist/state/session-attribution.d.ts.map +1 -0
- package/dist/state/session-attribution.js +179 -0
- package/dist/state/session-attribution.js.map +1 -0
- package/dist/state/session-discovery.d.ts +198 -0
- package/dist/state/session-discovery.d.ts.map +1 -0
- package/dist/state/session-discovery.js +555 -0
- package/dist/state/session-discovery.js.map +1 -0
- package/dist/state/session-metadata.d.ts +240 -0
- package/dist/state/session-metadata.d.ts.map +1 -0
- package/dist/state/session-metadata.js +377 -0
- package/dist/state/session-metadata.js.map +1 -0
- package/dist/state/tool-parsing.d.ts +88 -0
- package/dist/state/tool-parsing.d.ts.map +1 -0
- package/dist/state/tool-parsing.js +199 -0
- package/dist/state/tool-parsing.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Discovery Service
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates session enumeration by tying together JSONL parsing,
|
|
5
|
+
* session attribution, and CLI session path utilities. Provides cached
|
|
6
|
+
* discovery of Claude Code sessions from the filesystem.
|
|
7
|
+
*/
|
|
8
|
+
import { readdir, stat } from "node:fs/promises";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { encodePathForCli, getCliSessionFile } from "../runner/runtime/cli-session-path.js";
|
|
12
|
+
import { createLogger } from "../utils/logger.js";
|
|
13
|
+
import { extractFirstMessagePreview, extractLastSummary, extractSessionMetadata, extractSessionUsage, isSidechainSession, parseSessionMessages, } from "./jsonl-parser.js";
|
|
14
|
+
import { buildAttributionIndex, } from "./session-attribution.js";
|
|
15
|
+
import { SessionMetadataStore } from "./session-metadata.js";
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Logger
|
|
18
|
+
// =============================================================================
|
|
19
|
+
const logger = createLogger("SessionDiscoveryService");
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Helper Functions
|
|
22
|
+
// =============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Decode an encoded path back to a display path.
|
|
25
|
+
*
|
|
26
|
+
* The encoded path (e.g., "-Users-ed-Code-herdctl") is decoded by:
|
|
27
|
+
* - Replacing leading "-" with "/" (Unix)
|
|
28
|
+
* - Replacing remaining "-" with "/"
|
|
29
|
+
*
|
|
30
|
+
* This is lossy but good enough for display purposes.
|
|
31
|
+
*/
|
|
32
|
+
function decodePathForDisplay(encodedPath) {
|
|
33
|
+
// Handle Unix paths: leading "-" becomes "/"
|
|
34
|
+
if (encodedPath.startsWith("-")) {
|
|
35
|
+
return "/" + encodedPath.slice(1).replace(/-/g, "/");
|
|
36
|
+
}
|
|
37
|
+
// Handle Windows paths: "C:-Users-..." becomes "C:/Users/..."
|
|
38
|
+
// Check for drive letter pattern
|
|
39
|
+
if (/^[A-Za-z]:-/.test(encodedPath)) {
|
|
40
|
+
return encodedPath[0] + ":" + encodedPath.slice(2).replace(/-/g, "/");
|
|
41
|
+
}
|
|
42
|
+
// Fallback: just replace all hyphens
|
|
43
|
+
return encodedPath.replace(/-/g, "/");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if a path is a temp directory that should be filtered out
|
|
47
|
+
*/
|
|
48
|
+
function isTempDirectory(decodedPath) {
|
|
49
|
+
const tmpDir = os.tmpdir();
|
|
50
|
+
const tempPatterns = ["/tmp/", "/private/tmp/", "/var/folders/", tmpDir];
|
|
51
|
+
for (const pattern of tempPatterns) {
|
|
52
|
+
if (decodedPath.startsWith(pattern)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
// =============================================================================
|
|
59
|
+
// SessionDiscoveryService
|
|
60
|
+
// =============================================================================
|
|
61
|
+
/**
|
|
62
|
+
* Service for discovering and enumerating Claude Code sessions.
|
|
63
|
+
*
|
|
64
|
+
* Provides cached access to session files, with attribution from job metadata
|
|
65
|
+
* and platform session files, plus custom names from the session metadata store.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const discovery = new SessionDiscoveryService({
|
|
70
|
+
* stateDir: '/path/to/.herdctl',
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* // Get sessions for a specific agent
|
|
74
|
+
* const sessions = await discovery.getAgentSessions('my-agent', '/path/to/workspace', false);
|
|
75
|
+
*
|
|
76
|
+
* // Get all sessions grouped by directory
|
|
77
|
+
* const groups = await discovery.getAllSessions([
|
|
78
|
+
* { name: 'agent-1', workingDirectory: '/path/to/project', dockerEnabled: false }
|
|
79
|
+
* ]);
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export class SessionDiscoveryService {
|
|
83
|
+
claudeHomePath;
|
|
84
|
+
stateDir;
|
|
85
|
+
cacheTtlMs;
|
|
86
|
+
attributionIndex = null;
|
|
87
|
+
attributionFetchedAt = 0;
|
|
88
|
+
directoryCache = new Map();
|
|
89
|
+
metadataCache = new Map();
|
|
90
|
+
sessionMetadataStore;
|
|
91
|
+
/**
|
|
92
|
+
* Create a new SessionDiscoveryService
|
|
93
|
+
*
|
|
94
|
+
* @param options - Configuration options
|
|
95
|
+
*/
|
|
96
|
+
constructor(options) {
|
|
97
|
+
this.claudeHomePath = options.claudeHomePath ?? path.join(os.homedir(), ".claude");
|
|
98
|
+
this.stateDir = options.stateDir;
|
|
99
|
+
this.cacheTtlMs = options.cacheTtlMs ?? 30_000;
|
|
100
|
+
this.sessionMetadataStore = new SessionMetadataStore(options.stateDir);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if the attribution index cache is valid
|
|
104
|
+
*/
|
|
105
|
+
isAttributionCacheValid() {
|
|
106
|
+
return (this.attributionIndex !== null && Date.now() - this.attributionFetchedAt < this.cacheTtlMs);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if a directory cache entry is valid
|
|
110
|
+
*/
|
|
111
|
+
isDirectoryCacheValid(entry) {
|
|
112
|
+
return entry !== undefined && Date.now() - entry.fetchedAt < this.cacheTtlMs;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get or refresh the attribution index
|
|
116
|
+
*/
|
|
117
|
+
async getAttributionIndex() {
|
|
118
|
+
if (this.isAttributionCacheValid()) {
|
|
119
|
+
return this.attributionIndex;
|
|
120
|
+
}
|
|
121
|
+
logger.debug("Building attribution index");
|
|
122
|
+
this.attributionIndex = await buildAttributionIndex(this.stateDir);
|
|
123
|
+
this.attributionFetchedAt = Date.now();
|
|
124
|
+
logger.debug(`Attribution index built with ${this.attributionIndex.size} entries`);
|
|
125
|
+
return this.attributionIndex;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* List session files in a directory with their modification times
|
|
129
|
+
*/
|
|
130
|
+
async listSessionFiles(sessionDir) {
|
|
131
|
+
// Check cache first
|
|
132
|
+
const cached = this.directoryCache.get(sessionDir);
|
|
133
|
+
if (this.isDirectoryCacheValid(cached)) {
|
|
134
|
+
return cached.sessions;
|
|
135
|
+
}
|
|
136
|
+
// Read directory
|
|
137
|
+
let fileNames;
|
|
138
|
+
try {
|
|
139
|
+
fileNames = await readdir(sessionDir);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (error.code === "ENOENT") {
|
|
143
|
+
logger.debug(`Session directory does not exist: ${sessionDir}`);
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
logger.warn(`Failed to read session directory: ${sessionDir}: ${error.message}`);
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
// Filter to .jsonl files and get stats
|
|
150
|
+
const jsonlFiles = fileNames.filter((name) => name.endsWith(".jsonl"));
|
|
151
|
+
const sessions = [];
|
|
152
|
+
for (const fileName of jsonlFiles) {
|
|
153
|
+
const filePath = path.join(sessionDir, fileName);
|
|
154
|
+
try {
|
|
155
|
+
const stats = await stat(filePath);
|
|
156
|
+
sessions.push({
|
|
157
|
+
sessionId: fileName.replace(/\.jsonl$/, ""),
|
|
158
|
+
mtime: stats.mtime,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// File may have been deleted between readdir and stat
|
|
163
|
+
logger.debug(`Failed to stat session file: ${filePath}: ${error.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Sort by mtime descending (newest first)
|
|
167
|
+
sessions.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
168
|
+
// Cache the result
|
|
169
|
+
this.directoryCache.set(sessionDir, {
|
|
170
|
+
sessions,
|
|
171
|
+
fetchedAt: Date.now(),
|
|
172
|
+
});
|
|
173
|
+
return sessions;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the auto-generated name for a session.
|
|
177
|
+
*
|
|
178
|
+
* Checks if the cached autoName is still valid (based on file mtime).
|
|
179
|
+
* If not, extracts a new name from the JSONL summary field.
|
|
180
|
+
*
|
|
181
|
+
* @param agentName - The agent's qualified name (use "adhoc" for unattributed sessions)
|
|
182
|
+
* @param sessionId - The session ID
|
|
183
|
+
* @param fileMtime - ISO 8601 timestamp of the session file's modification time
|
|
184
|
+
* @param workingDirectory - The session's working directory
|
|
185
|
+
* @returns Object with autoName and whether an update is needed
|
|
186
|
+
*/
|
|
187
|
+
async resolveAutoName(agentName, sessionId, fileMtime, workingDirectory) {
|
|
188
|
+
// Check cache
|
|
189
|
+
const cached = await this.sessionMetadataStore.getAutoName(agentName, sessionId);
|
|
190
|
+
if (cached?.autoNameMtime && cached.autoNameMtime >= fileMtime) {
|
|
191
|
+
// Cache is valid
|
|
192
|
+
return { autoName: cached.autoName, needsUpdate: false };
|
|
193
|
+
}
|
|
194
|
+
// Need to extract from JSONL
|
|
195
|
+
const filePath = getCliSessionFile(workingDirectory, sessionId);
|
|
196
|
+
const summary = await extractLastSummary(filePath);
|
|
197
|
+
if (summary) {
|
|
198
|
+
return { autoName: summary, needsUpdate: true };
|
|
199
|
+
}
|
|
200
|
+
return { autoName: undefined, needsUpdate: false };
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Resolve the preview (first user message text) for a session, using cache when valid.
|
|
204
|
+
*
|
|
205
|
+
* @param agentName - The agent's qualified name (or "adhoc" for unattributed)
|
|
206
|
+
* @param sessionId - The session ID
|
|
207
|
+
* @param fileMtime - ISO 8601 timestamp of the session file's modification time
|
|
208
|
+
* @param workingDirectory - The session's working directory
|
|
209
|
+
* @returns Object with preview and whether an update is needed
|
|
210
|
+
*/
|
|
211
|
+
async resolvePreview(agentName, sessionId, fileMtime, workingDirectory) {
|
|
212
|
+
// Check cache
|
|
213
|
+
const cached = await this.sessionMetadataStore.getPreview(agentName, sessionId);
|
|
214
|
+
if (cached?.previewMtime && cached.previewMtime >= fileMtime) {
|
|
215
|
+
// Cache is valid
|
|
216
|
+
return { preview: cached.preview, needsUpdate: false };
|
|
217
|
+
}
|
|
218
|
+
// Need to extract from JSONL
|
|
219
|
+
const filePath = getCliSessionFile(workingDirectory, sessionId);
|
|
220
|
+
const preview = await extractFirstMessagePreview(filePath);
|
|
221
|
+
if (preview) {
|
|
222
|
+
return { preview, needsUpdate: true };
|
|
223
|
+
}
|
|
224
|
+
return { preview: undefined, needsUpdate: false };
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get sessions for a specific agent.
|
|
228
|
+
*
|
|
229
|
+
* Returns sessions from the agent's working directory, attributed and
|
|
230
|
+
* enriched with custom names from the metadata store.
|
|
231
|
+
*
|
|
232
|
+
* @param agentName - The agent's qualified name
|
|
233
|
+
* @param workingDirectory - The agent's working directory
|
|
234
|
+
* @param dockerEnabled - Whether Docker is enabled for the agent (affects resumability)
|
|
235
|
+
* @param options - Optional settings (limit for top-N optimization)
|
|
236
|
+
* @returns Array of discovered sessions sorted by mtime descending
|
|
237
|
+
*/
|
|
238
|
+
async getAgentSessions(agentName, workingDirectory, dockerEnabled, options) {
|
|
239
|
+
const limit = options?.limit;
|
|
240
|
+
const encodedPath = encodePathForCli(workingDirectory);
|
|
241
|
+
const sessionDir = path.join(this.claudeHomePath, "projects", encodedPath);
|
|
242
|
+
logger.debug(`Getting sessions for agent ${agentName}`, { sessionDir });
|
|
243
|
+
// Get session files (already sorted by mtime descending)
|
|
244
|
+
const sessionFiles = await this.listSessionFiles(sessionDir);
|
|
245
|
+
if (sessionFiles.length === 0) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
// Only enrich the top N sessions when limit is set
|
|
249
|
+
const filesToEnrich = limit !== undefined ? sessionFiles.slice(0, limit) : sessionFiles;
|
|
250
|
+
// Get attribution index
|
|
251
|
+
const attributionIndex = await this.getAttributionIndex();
|
|
252
|
+
// Build discovered sessions and collect cache updates
|
|
253
|
+
const sessions = [];
|
|
254
|
+
const autoNameUpdates = [];
|
|
255
|
+
const previewUpdates = [];
|
|
256
|
+
for (const { sessionId, mtime } of filesToEnrich) {
|
|
257
|
+
// Filter out sidechain (sub-agent) sessions. Claude Code marks sessions
|
|
258
|
+
// as sidechain when they're Task tool sub-agents or when --resume is used.
|
|
259
|
+
// These are mostly prompt-cache warmup sessions ("Warmup" + single response)
|
|
260
|
+
// that clutter the UI with no useful content. We check only the first JSONL
|
|
261
|
+
// line so this is O(1) per file.
|
|
262
|
+
const filePath = getCliSessionFile(workingDirectory, sessionId);
|
|
263
|
+
if (await isSidechainSession(filePath)) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const attribution = attributionIndex.getAttribute(sessionId);
|
|
267
|
+
// Only show sessions that are attributed to this specific agent.
|
|
268
|
+
// When multiple agents share a working directory, this prevents the same
|
|
269
|
+
// native CLI sessions from appearing under every agent. Unattributed sessions
|
|
270
|
+
// are still visible in the global recent sessions list and All Chats view.
|
|
271
|
+
if (attribution.agentName !== agentName) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const customName = await this.sessionMetadataStore.getCustomName(agentName, sessionId);
|
|
275
|
+
const mtimeStr = mtime.toISOString();
|
|
276
|
+
// Resolve autoName with caching
|
|
277
|
+
const { autoName, needsUpdate } = await this.resolveAutoName(agentName, sessionId, mtimeStr, workingDirectory);
|
|
278
|
+
if (needsUpdate && autoName) {
|
|
279
|
+
autoNameUpdates.push({ sessionId, autoName, mtime: mtimeStr });
|
|
280
|
+
}
|
|
281
|
+
// Resolve preview with caching
|
|
282
|
+
const { preview, needsUpdate: previewNeedsUpdate } = await this.resolvePreview(agentName, sessionId, mtimeStr, workingDirectory);
|
|
283
|
+
if (previewNeedsUpdate && preview) {
|
|
284
|
+
previewUpdates.push({ sessionId, preview, mtime: mtimeStr });
|
|
285
|
+
}
|
|
286
|
+
sessions.push({
|
|
287
|
+
sessionId,
|
|
288
|
+
workingDirectory,
|
|
289
|
+
mtime: mtimeStr,
|
|
290
|
+
origin: attribution.origin,
|
|
291
|
+
agentName: attribution.agentName ?? agentName,
|
|
292
|
+
resumable: !dockerEnabled,
|
|
293
|
+
customName,
|
|
294
|
+
autoName,
|
|
295
|
+
preview,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
// Batch write any cache updates
|
|
299
|
+
if (autoNameUpdates.length > 0) {
|
|
300
|
+
await this.sessionMetadataStore.batchSetAutoNames(agentName, autoNameUpdates);
|
|
301
|
+
}
|
|
302
|
+
if (previewUpdates.length > 0) {
|
|
303
|
+
await this.sessionMetadataStore.batchSetPreviews(agentName, previewUpdates);
|
|
304
|
+
}
|
|
305
|
+
return sessions;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get all sessions grouped by working directory.
|
|
309
|
+
*
|
|
310
|
+
* Scans the Claude projects directory and groups sessions by their
|
|
311
|
+
* working directories. Filters out temp directories and enriches
|
|
312
|
+
* sessions with attribution and custom names.
|
|
313
|
+
*
|
|
314
|
+
* When a limit is provided, only the most recent `limit` sessions
|
|
315
|
+
* (by mtime) are enriched with names, avoiding expensive JSONL parsing
|
|
316
|
+
* for sessions that won't be returned.
|
|
317
|
+
*
|
|
318
|
+
* @param agents - Array of known agents for matching sessions
|
|
319
|
+
* @param options - Optional settings (limit for top-N optimization)
|
|
320
|
+
* @returns Array of directory groups sorted by most recent session
|
|
321
|
+
*/
|
|
322
|
+
async getAllSessions(agents, options) {
|
|
323
|
+
const limit = options?.limit;
|
|
324
|
+
// Build agent lookup by encoded path
|
|
325
|
+
const agentLookup = new Map();
|
|
326
|
+
for (const agent of agents) {
|
|
327
|
+
const encodedPath = encodePathForCli(agent.workingDirectory);
|
|
328
|
+
agentLookup.set(encodedPath, {
|
|
329
|
+
agentName: agent.name,
|
|
330
|
+
dockerEnabled: agent.dockerEnabled,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
// Scan projects directory
|
|
334
|
+
const projectsDir = path.join(this.claudeHomePath, "projects");
|
|
335
|
+
let encodedPaths;
|
|
336
|
+
try {
|
|
337
|
+
encodedPaths = await readdir(projectsDir);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
if (error.code === "ENOENT") {
|
|
341
|
+
logger.debug(`Projects directory does not exist: ${projectsDir}`);
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
logger.warn(`Failed to read projects directory: ${projectsDir}: ${error.message}`);
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
// Get attribution index
|
|
348
|
+
const attributionIndex = await this.getAttributionIndex();
|
|
349
|
+
const directories = [];
|
|
350
|
+
for (const encodedPath of encodedPaths) {
|
|
351
|
+
const sessionDir = path.join(projectsDir, encodedPath);
|
|
352
|
+
// Check if it's a directory
|
|
353
|
+
try {
|
|
354
|
+
const stats = await stat(sessionDir);
|
|
355
|
+
if (!stats.isDirectory()) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
logger.debug(`Failed to stat: ${sessionDir}: ${error.message}`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
// Decode path for display and filtering
|
|
364
|
+
const decodedPath = decodePathForDisplay(encodedPath);
|
|
365
|
+
// Filter out temp directories
|
|
366
|
+
if (isTempDirectory(decodedPath)) {
|
|
367
|
+
logger.debug(`Skipping temp directory: ${decodedPath}`);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
// Get session files (already sorted by mtime descending)
|
|
371
|
+
const sessionFiles = await this.listSessionFiles(sessionDir);
|
|
372
|
+
if (sessionFiles.length === 0) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
// Check if this matches a known agent
|
|
376
|
+
const agentMatch = agentLookup.get(encodedPath);
|
|
377
|
+
const agentName = agentMatch?.agentName;
|
|
378
|
+
const dockerEnabled = agentMatch?.dockerEnabled ?? false;
|
|
379
|
+
const metadataKey = agentName ?? "adhoc";
|
|
380
|
+
directories.push({
|
|
381
|
+
encodedPath,
|
|
382
|
+
decodedPath,
|
|
383
|
+
agentName,
|
|
384
|
+
metadataKey,
|
|
385
|
+
dockerEnabled,
|
|
386
|
+
sessionFiles,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
// Phase 2: If limit is set, find the top N sessions by mtime across all directories
|
|
390
|
+
// Each directory's sessionFiles are already sorted by mtime descending,
|
|
391
|
+
// so we merge-select the top N using pointers into each sorted list.
|
|
392
|
+
let selectedSessionIds;
|
|
393
|
+
if (limit !== undefined) {
|
|
394
|
+
// Merge pointers: index into each directory's sorted sessionFiles
|
|
395
|
+
const pointers = directories.map(() => 0);
|
|
396
|
+
selectedSessionIds = new Set();
|
|
397
|
+
for (let picked = 0; picked < limit; picked++) {
|
|
398
|
+
let bestDir = -1;
|
|
399
|
+
let bestMtime = null;
|
|
400
|
+
for (let d = 0; d < directories.length; d++) {
|
|
401
|
+
const dir = directories[d];
|
|
402
|
+
if (pointers[d] >= dir.sessionFiles.length)
|
|
403
|
+
continue;
|
|
404
|
+
const candidate = dir.sessionFiles[pointers[d]];
|
|
405
|
+
if (bestMtime === null || candidate.mtime > bestMtime) {
|
|
406
|
+
bestMtime = candidate.mtime;
|
|
407
|
+
bestDir = d;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (bestDir === -1)
|
|
411
|
+
break; // No more sessions
|
|
412
|
+
selectedSessionIds.add(directories[bestDir].sessionFiles[pointers[bestDir]].sessionId);
|
|
413
|
+
pointers[bestDir]++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Phase 3: Enrich sessions (only selected ones when limit is set)
|
|
417
|
+
const groups = [];
|
|
418
|
+
for (const dir of directories) {
|
|
419
|
+
const sessions = [];
|
|
420
|
+
const autoNameUpdates = [];
|
|
421
|
+
const previewUpdates = [];
|
|
422
|
+
for (const { sessionId, mtime } of dir.sessionFiles) {
|
|
423
|
+
// Skip sessions not in the selected set when limit is active
|
|
424
|
+
if (selectedSessionIds && !selectedSessionIds.has(sessionId)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
// Filter out sidechain (sub-agent) sessions — see comment in getAgentSessions()
|
|
428
|
+
const filePath = getCliSessionFile(dir.decodedPath, sessionId);
|
|
429
|
+
if (await isSidechainSession(filePath)) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const attribution = attributionIndex.getAttribute(sessionId);
|
|
433
|
+
const mtimeStr = mtime.toISOString();
|
|
434
|
+
// Get custom name (works for both attributed and unattributed sessions)
|
|
435
|
+
const customName = await this.sessionMetadataStore.getCustomName(dir.metadataKey, sessionId);
|
|
436
|
+
// Resolve autoName with caching
|
|
437
|
+
const { autoName, needsUpdate } = await this.resolveAutoName(dir.metadataKey, sessionId, mtimeStr, dir.decodedPath);
|
|
438
|
+
if (needsUpdate && autoName) {
|
|
439
|
+
autoNameUpdates.push({ sessionId, autoName, mtime: mtimeStr });
|
|
440
|
+
}
|
|
441
|
+
// Resolve preview with caching
|
|
442
|
+
const { preview, needsUpdate: previewNeedsUpdate } = await this.resolvePreview(dir.metadataKey, sessionId, mtimeStr, dir.decodedPath);
|
|
443
|
+
if (previewNeedsUpdate && preview) {
|
|
444
|
+
previewUpdates.push({ sessionId, preview, mtime: mtimeStr });
|
|
445
|
+
}
|
|
446
|
+
sessions.push({
|
|
447
|
+
sessionId,
|
|
448
|
+
workingDirectory: dir.decodedPath,
|
|
449
|
+
mtime: mtimeStr,
|
|
450
|
+
origin: attribution.origin,
|
|
451
|
+
agentName: attribution.agentName ?? dir.agentName,
|
|
452
|
+
resumable: !dir.dockerEnabled,
|
|
453
|
+
customName,
|
|
454
|
+
autoName,
|
|
455
|
+
preview,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
// Batch write any cache updates for this directory
|
|
459
|
+
if (autoNameUpdates.length > 0) {
|
|
460
|
+
await this.sessionMetadataStore.batchSetAutoNames(dir.metadataKey, autoNameUpdates);
|
|
461
|
+
}
|
|
462
|
+
if (previewUpdates.length > 0) {
|
|
463
|
+
await this.sessionMetadataStore.batchSetPreviews(dir.metadataKey, previewUpdates);
|
|
464
|
+
}
|
|
465
|
+
if (sessions.length > 0) {
|
|
466
|
+
groups.push({
|
|
467
|
+
workingDirectory: dir.decodedPath,
|
|
468
|
+
encodedPath: dir.encodedPath,
|
|
469
|
+
agentName: dir.agentName,
|
|
470
|
+
sessionCount: dir.sessionFiles.length,
|
|
471
|
+
sessions,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Sort groups by most recent session mtime descending
|
|
476
|
+
groups.sort((a, b) => {
|
|
477
|
+
const aLatest = a.sessions[0]?.mtime ?? "";
|
|
478
|
+
const bLatest = b.sessions[0]?.mtime ?? "";
|
|
479
|
+
return bLatest.localeCompare(aLatest);
|
|
480
|
+
});
|
|
481
|
+
return groups;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get parsed chat messages from a session.
|
|
485
|
+
*
|
|
486
|
+
* Delegates to the JSONL parser.
|
|
487
|
+
*
|
|
488
|
+
* @param workingDirectory - The session's working directory
|
|
489
|
+
* @param sessionId - The session ID
|
|
490
|
+
* @returns Array of chat messages
|
|
491
|
+
*/
|
|
492
|
+
async getSessionMessages(workingDirectory, sessionId) {
|
|
493
|
+
const filePath = getCliSessionFile(workingDirectory, sessionId);
|
|
494
|
+
return parseSessionMessages(filePath);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Get metadata for a session.
|
|
498
|
+
*
|
|
499
|
+
* Caches the result for efficiency when called repeatedly.
|
|
500
|
+
*
|
|
501
|
+
* @param workingDirectory - The session's working directory
|
|
502
|
+
* @param sessionId - The session ID
|
|
503
|
+
* @returns Session metadata
|
|
504
|
+
*/
|
|
505
|
+
async getSessionMetadata(workingDirectory, sessionId) {
|
|
506
|
+
const filePath = getCliSessionFile(workingDirectory, sessionId);
|
|
507
|
+
// Check cache
|
|
508
|
+
const cached = this.metadataCache.get(filePath);
|
|
509
|
+
if (cached !== undefined) {
|
|
510
|
+
return cached;
|
|
511
|
+
}
|
|
512
|
+
// Extract metadata
|
|
513
|
+
const metadata = await extractSessionMetadata(filePath);
|
|
514
|
+
// Cache and return
|
|
515
|
+
this.metadataCache.set(filePath, metadata);
|
|
516
|
+
return metadata;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Get usage data for a session.
|
|
520
|
+
*
|
|
521
|
+
* Delegates to the JSONL parser.
|
|
522
|
+
*
|
|
523
|
+
* @param workingDirectory - The session's working directory
|
|
524
|
+
* @param sessionId - The session ID
|
|
525
|
+
* @returns Session usage data
|
|
526
|
+
*/
|
|
527
|
+
async getSessionUsage(workingDirectory, sessionId) {
|
|
528
|
+
const filePath = getCliSessionFile(workingDirectory, sessionId);
|
|
529
|
+
return extractSessionUsage(filePath);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Invalidate cached data.
|
|
533
|
+
*
|
|
534
|
+
* If a working directory is provided, only that directory's cache entry
|
|
535
|
+
* is cleared. Otherwise, all caches are cleared.
|
|
536
|
+
*
|
|
537
|
+
* @param workingDirectory - Optional working directory to clear cache for
|
|
538
|
+
*/
|
|
539
|
+
invalidateCache(workingDirectory) {
|
|
540
|
+
if (workingDirectory !== undefined) {
|
|
541
|
+
const encodedPath = encodePathForCli(workingDirectory);
|
|
542
|
+
const sessionDir = path.join(this.claudeHomePath, "projects", encodedPath);
|
|
543
|
+
this.directoryCache.delete(sessionDir);
|
|
544
|
+
logger.debug(`Invalidated cache for directory: ${sessionDir}`);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
this.directoryCache.clear();
|
|
548
|
+
this.attributionIndex = null;
|
|
549
|
+
this.attributionFetchedAt = 0;
|
|
550
|
+
this.metadataCache.clear();
|
|
551
|
+
logger.debug("Invalidated all caches");
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
//# sourceMappingURL=session-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-discovery.js","sourceRoot":"","sources":["../../src/state/session-discovery.ts"],"names":[],"mappings":"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"}
|