forkit-connect 0.1.5 → 0.1.6

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.
@@ -0,0 +1,821 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseClaudeSessionJsonl = parseClaudeSessionJsonl;
7
+ exports.parseEditorActivityThreadsBlob = parseEditorActivityThreadsBlob;
8
+ exports.parseCopilotChatSessionIndexBlob = parseCopilotChatSessionIndexBlob;
9
+ exports.parseCopilotSelectedModelLabels = parseCopilotSelectedModelLabels;
10
+ exports.parseEditorOpenFileStateBlob = parseEditorOpenFileStateBlob;
11
+ exports.collectCodexEditorThreads = collectCodexEditorThreads;
12
+ exports.collectCopilotEditorActivity = collectCopilotEditorActivity;
13
+ exports.collectClaudeProjectActivity = collectClaudeProjectActivity;
14
+ const node_fs_1 = __importDefault(require("node:fs"));
15
+ const node_os_1 = __importDefault(require("node:os"));
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const node_child_process_1 = require("node:child_process");
18
+ const MAX_ACTIVITY_THREADS = 4;
19
+ const MAX_ACTIVITY_THREAD_LABEL_LENGTH = 96;
20
+ const MAX_ACTIVITY_MODEL_LABEL_LENGTH = 64;
21
+ const VSCODE_AGENT_SESSIONS_STATE_KEY = 'agentSessions.model.cache';
22
+ const COPILOT_EXTENSION_STATE_KEY = 'GitHub.copilot-chat';
23
+ const COPILOT_CHAT_SESSION_INDEX_KEY = 'chat.ChatSessionStore.index';
24
+ const COPILOT_ACTIVE_SESSION_STATE_KEY = 'memento/interactive-session-view-copilot';
25
+ const EDITOR_OPEN_FILE_STATE_KEY = 'memento/workbench.editors.files.textFileEditor';
26
+ const MAX_ACTIVITY_SCOPE_FILES = 6;
27
+ function trimOptional(value) {
28
+ const normalized = String(value ?? '').trim();
29
+ return normalized.length ? normalized : null;
30
+ }
31
+ function normalizeActivityLabel(value, maxLength = MAX_ACTIVITY_THREAD_LABEL_LENGTH) {
32
+ if (typeof value !== 'string')
33
+ return null;
34
+ const normalized = value.replace(/\s+/g, ' ').trim();
35
+ if (!normalized)
36
+ return null;
37
+ if (normalized.length <= maxLength)
38
+ return normalized;
39
+ return `${normalized.slice(0, Math.max(1, maxLength - 1)).trimEnd()}…`;
40
+ }
41
+ function realpathSafe(value) {
42
+ const resolved = node_path_1.default.resolve(value);
43
+ try {
44
+ const nativeRealpath = node_fs_1.default.realpathSync.native;
45
+ return typeof nativeRealpath === 'function' ? nativeRealpath(resolved) : node_fs_1.default.realpathSync(resolved);
46
+ }
47
+ catch {
48
+ return resolved;
49
+ }
50
+ }
51
+ function normalizeIsoTimestamp(value) {
52
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
53
+ return new Date(value).toISOString();
54
+ }
55
+ if (typeof value === 'string') {
56
+ const numeric = Number(value);
57
+ if (Number.isFinite(numeric) && numeric > 0) {
58
+ return new Date(numeric).toISOString();
59
+ }
60
+ const parsed = Date.parse(value);
61
+ if (Number.isFinite(parsed)) {
62
+ return new Date(parsed).toISOString();
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+ function decodeWorkspaceFolderLocation(value) {
68
+ const normalized = trimOptional(value);
69
+ if (!normalized)
70
+ return null;
71
+ if (normalized.startsWith('file://')) {
72
+ try {
73
+ return realpathSafe(decodeURIComponent(new URL(normalized).pathname));
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ return realpathSafe(normalized);
80
+ }
81
+ function readWorkspaceStorageConfigPaths(value) {
82
+ if (!value || typeof value !== 'object' || Array.isArray(value))
83
+ return [];
84
+ const record = value;
85
+ const resolved = [
86
+ typeof record.folder === 'string' ? decodeWorkspaceFolderLocation(record.folder) : null,
87
+ typeof record.configuration === 'string' ? decodeWorkspaceFolderLocation(record.configuration) : null,
88
+ ];
89
+ if (record.workspace && typeof record.workspace === 'object' && !Array.isArray(record.workspace)) {
90
+ const nested = record.workspace;
91
+ resolved.push(typeof nested.folder === 'string' ? decodeWorkspaceFolderLocation(nested.folder) : null, typeof nested.configuration === 'string' ? decodeWorkspaceFolderLocation(nested.configuration) : null);
92
+ }
93
+ return resolved.filter((item) => Boolean(item));
94
+ }
95
+ function candidateWorkspaceStorageDirs(host) {
96
+ const home = node_os_1.default.homedir();
97
+ const appData = process.env.APPDATA || node_path_1.default.join(home, 'AppData', 'Roaming');
98
+ if (process.platform === 'darwin') {
99
+ return [
100
+ host === 'vscode'
101
+ ? node_path_1.default.join(home, 'Library', 'Application Support', 'Code', 'User', 'workspaceStorage')
102
+ : node_path_1.default.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'workspaceStorage'),
103
+ ];
104
+ }
105
+ if (process.platform === 'win32') {
106
+ return [
107
+ host === 'vscode'
108
+ ? node_path_1.default.join(appData, 'Code', 'User', 'workspaceStorage')
109
+ : node_path_1.default.join(appData, 'Cursor', 'User', 'workspaceStorage'),
110
+ ];
111
+ }
112
+ return [
113
+ host === 'vscode'
114
+ ? node_path_1.default.join(home, '.config', 'Code', 'User', 'workspaceStorage')
115
+ : node_path_1.default.join(home, '.config', 'Cursor', 'User', 'workspaceStorage'),
116
+ ];
117
+ }
118
+ const EDITOR_WORKSPACE_THREAD_SOURCES = [
119
+ {
120
+ host: 'vscode',
121
+ stateCacheKey: VSCODE_AGENT_SESSIONS_STATE_KEY,
122
+ workspaceStorageDirs: candidateWorkspaceStorageDirs('vscode'),
123
+ },
124
+ {
125
+ host: 'cursor',
126
+ stateCacheKey: VSCODE_AGENT_SESSIONS_STATE_KEY,
127
+ workspaceStorageDirs: candidateWorkspaceStorageDirs('cursor'),
128
+ },
129
+ ];
130
+ function findEditorWorkspaceStorageDir(workspaceStorageDirs, repoRoot) {
131
+ const normalizedRepoRoot = realpathSafe(repoRoot);
132
+ for (const workspaceStorageDir of workspaceStorageDirs) {
133
+ let directoryEntries = [];
134
+ try {
135
+ directoryEntries = node_fs_1.default.readdirSync(workspaceStorageDir, { withFileTypes: true });
136
+ }
137
+ catch {
138
+ continue;
139
+ }
140
+ for (const entry of directoryEntries) {
141
+ if (!entry.isDirectory())
142
+ continue;
143
+ const candidateDir = node_path_1.default.join(workspaceStorageDir, entry.name);
144
+ const workspaceJsonPath = node_path_1.default.join(candidateDir, 'workspace.json');
145
+ let parsedWorkspaceConfig;
146
+ try {
147
+ parsedWorkspaceConfig = JSON.parse(node_fs_1.default.readFileSync(workspaceJsonPath, 'utf8'));
148
+ }
149
+ catch {
150
+ continue;
151
+ }
152
+ const workspacePaths = readWorkspaceStorageConfigPaths(parsedWorkspaceConfig);
153
+ if (workspacePaths.some((candidate) => candidate === normalizedRepoRoot)) {
154
+ return candidateDir;
155
+ }
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+ function readSqliteItemValue(dbPath, key) {
161
+ if (!node_fs_1.default.existsSync(dbPath))
162
+ return null;
163
+ const escapedKey = key.replace(/'/g, "''");
164
+ try {
165
+ const result = (0, node_child_process_1.spawnSync)('sqlite3', ['-readonly', dbPath, `select value from ItemTable where key='${escapedKey}' limit 1;`], {
166
+ encoding: 'utf8',
167
+ timeout: 2500,
168
+ maxBuffer: 512 * 1024,
169
+ });
170
+ if (result.status !== 0)
171
+ return null;
172
+ return trimOptional(result.stdout);
173
+ }
174
+ catch {
175
+ return null;
176
+ }
177
+ }
178
+ function extractThreadIdFromResource(value) {
179
+ const resource = trimOptional(typeof value === 'string' ? value : null);
180
+ if (!resource)
181
+ return null;
182
+ const trailing = resource.split('/').pop();
183
+ return trimOptional(trailing) ?? resource;
184
+ }
185
+ function normalizeActivityModelLabel(value) {
186
+ const normalized = normalizeActivityLabel(value, MAX_ACTIVITY_MODEL_LABEL_LENGTH);
187
+ if (!normalized)
188
+ return null;
189
+ if (/^<[^>]+>$/.test(normalized))
190
+ return null;
191
+ return normalized;
192
+ }
193
+ function normalizeRepoRelativeFilePath(repoRoot, filePath) {
194
+ const normalizedRoot = realpathSafe(repoRoot);
195
+ const normalizedFilePath = node_path_1.default.resolve(filePath);
196
+ const relative = node_path_1.default.relative(normalizedRoot, normalizedFilePath).replace(/\\/g, '/');
197
+ if (!relative || relative.startsWith('..'))
198
+ return null;
199
+ return normalizeActivityLabel(relative, 160);
200
+ }
201
+ function decodeFileUri(value) {
202
+ try {
203
+ const url = new URL(value);
204
+ if (url.protocol !== 'file:')
205
+ return null;
206
+ return realpathSafe(decodeURIComponent(url.pathname));
207
+ }
208
+ catch {
209
+ return null;
210
+ }
211
+ }
212
+ function normalizeClaudeProjectDirName(repoRoot) {
213
+ return repoRoot
214
+ .replace(/\\/g, '/')
215
+ .replace(/[^a-zA-Z0-9._/-]+/g, '-')
216
+ .replace(/\//g, '-');
217
+ }
218
+ function extractClaudePromptTitle(value) {
219
+ if (typeof value === 'string') {
220
+ const normalized = value.replace(/\\n/g, '\n');
221
+ const lines = normalized.split('\n').map((line) => line.trim()).filter(Boolean);
222
+ for (let index = 0; index < lines.length - 1; index += 1) {
223
+ const marker = lines[index]?.toLowerCase().replace(/[:#*-]+/g, '').trim();
224
+ if (marker === 'goal' || marker === 'title' || marker === 'project') {
225
+ const candidate = normalizeActivityLabel(lines[index + 1]);
226
+ if (candidate)
227
+ return candidate;
228
+ }
229
+ }
230
+ return normalizeActivityLabel(lines[0] || normalized);
231
+ }
232
+ if (Array.isArray(value)) {
233
+ for (const item of value) {
234
+ if (!item || typeof item !== 'object' || Array.isArray(item))
235
+ continue;
236
+ const record = item;
237
+ if (record.type === 'tool_result' || record.tool_use_id)
238
+ continue;
239
+ const nested = typeof record.text === 'string'
240
+ ? record.text
241
+ : typeof record.content === 'string'
242
+ ? record.content
243
+ : null;
244
+ const normalized = extractClaudePromptTitle(nested);
245
+ if (normalized)
246
+ return normalized;
247
+ }
248
+ }
249
+ return null;
250
+ }
251
+ const MAX_ACTIVITY_FILE_LABELS = 16;
252
+ function trimTrailingPathPunctuation(value) {
253
+ return value.replace(/[)\]}",'`;:.!?]+$/g, '');
254
+ }
255
+ function repoRootCandidates(repoRoot) {
256
+ const candidates = [
257
+ node_path_1.default.resolve(repoRoot),
258
+ realpathSafe(repoRoot),
259
+ ].map((value) => value.replace(/\\/g, '/'));
260
+ return [...new Set(candidates)];
261
+ }
262
+ function isRepoRelativeFilePath(repoRoot, candidate) {
263
+ const normalizedCandidate = node_path_1.default.resolve(candidate);
264
+ let relative = null;
265
+ for (const rootCandidate of repoRootCandidates(repoRoot)) {
266
+ const nextRelative = node_path_1.default.relative(rootCandidate, normalizedCandidate).replace(/\\/g, '/');
267
+ if (!nextRelative || nextRelative.startsWith('..'))
268
+ continue;
269
+ relative = nextRelative;
270
+ break;
271
+ }
272
+ if (!relative)
273
+ return null;
274
+ try {
275
+ const stats = node_fs_1.default.statSync(normalizedCandidate);
276
+ if (!stats.isFile())
277
+ return null;
278
+ }
279
+ catch {
280
+ return null;
281
+ }
282
+ return normalizeActivityLabel(relative, 160);
283
+ }
284
+ function extractRepoScopedPathsFromString(value, repoRoot) {
285
+ const normalizedValue = value.replace(/\\/g, '/');
286
+ const results = [];
287
+ const delimiters = new Set([' ', '\n', '\r', '\t', '"', '\'', '`', '<', '>', '|', '&', ';', '(', ')', '[', ']', '{', '}', ',']);
288
+ for (const rootCandidate of repoRootCandidates(repoRoot)) {
289
+ if (!normalizedValue.includes(rootCandidate))
290
+ continue;
291
+ let cursor = 0;
292
+ while (cursor < normalizedValue.length) {
293
+ const start = normalizedValue.indexOf(rootCandidate, cursor);
294
+ if (start === -1)
295
+ break;
296
+ let end = start + rootCandidate.length;
297
+ while (end < normalizedValue.length && !delimiters.has(normalizedValue[end] ?? '')) {
298
+ end += 1;
299
+ }
300
+ results.push(trimTrailingPathPunctuation(normalizedValue.slice(start, end)));
301
+ cursor = end;
302
+ }
303
+ }
304
+ return results;
305
+ }
306
+ function extractClaudeFileLabelsFromValue(value, repoRoot) {
307
+ const queue = [value];
308
+ const visited = new Set();
309
+ const labels = [];
310
+ const seen = new Set();
311
+ let inspected = 0;
312
+ while (queue.length > 0 && inspected < 200) {
313
+ const next = queue.shift();
314
+ inspected += 1;
315
+ if (next === null || next === undefined || visited.has(next))
316
+ continue;
317
+ if (typeof next === 'string') {
318
+ for (const candidate of extractRepoScopedPathsFromString(next, repoRoot)) {
319
+ const relative = isRepoRelativeFilePath(repoRoot, candidate);
320
+ if (!relative)
321
+ continue;
322
+ const key = relative.toLowerCase();
323
+ if (seen.has(key))
324
+ continue;
325
+ seen.add(key);
326
+ labels.push(relative);
327
+ if (labels.length >= MAX_ACTIVITY_FILE_LABELS) {
328
+ return labels;
329
+ }
330
+ }
331
+ continue;
332
+ }
333
+ if (typeof next !== 'object')
334
+ continue;
335
+ visited.add(next);
336
+ if (Array.isArray(next)) {
337
+ queue.push(...next);
338
+ continue;
339
+ }
340
+ queue.push(...Object.values(next));
341
+ }
342
+ return labels;
343
+ }
344
+ function parseClaudeSessionJsonl(raw, options) {
345
+ if (!raw) {
346
+ return { thread: null, modelLabels: [], fileLabels: [] };
347
+ }
348
+ let createdAt = null;
349
+ let lastActiveAt = null;
350
+ let threadLabel = null;
351
+ let sessionId = null;
352
+ const modelLabels = [];
353
+ const seenModels = new Set();
354
+ const fileLabels = [];
355
+ const seenFiles = new Set();
356
+ try {
357
+ for (const line of raw.split(/\r?\n/)) {
358
+ const trimmed = line.trim();
359
+ if (!trimmed)
360
+ continue;
361
+ let parsed;
362
+ try {
363
+ parsed = JSON.parse(trimmed);
364
+ }
365
+ catch {
366
+ continue;
367
+ }
368
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
369
+ continue;
370
+ const record = parsed;
371
+ const timestamp = normalizeIsoTimestamp(record.timestamp);
372
+ if (timestamp) {
373
+ if (!createdAt || Date.parse(timestamp) < Date.parse(createdAt))
374
+ createdAt = timestamp;
375
+ if (!lastActiveAt || Date.parse(timestamp) > Date.parse(lastActiveAt))
376
+ lastActiveAt = timestamp;
377
+ }
378
+ sessionId = trimOptional(typeof record.sessionId === 'string' ? record.sessionId : sessionId);
379
+ if (record.type === 'user' && !threadLabel) {
380
+ const message = record.message && typeof record.message === 'object' && !Array.isArray(record.message)
381
+ ? record.message
382
+ : {};
383
+ const title = extractClaudePromptTitle(message.content);
384
+ if (title) {
385
+ threadLabel = title;
386
+ }
387
+ }
388
+ if (record.type === 'assistant') {
389
+ const message = record.message && typeof record.message === 'object' && !Array.isArray(record.message)
390
+ ? record.message
391
+ : {};
392
+ const model = normalizeActivityModelLabel(message.model);
393
+ if (model) {
394
+ const key = model.toLowerCase();
395
+ if (!seenModels.has(key)) {
396
+ seenModels.add(key);
397
+ modelLabels.push(model);
398
+ }
399
+ }
400
+ if (options.repoRoot && Array.isArray(message.content)) {
401
+ for (const label of extractClaudeFileLabelsFromValue(message.content, options.repoRoot)) {
402
+ const key = label.toLowerCase();
403
+ if (seenFiles.has(key))
404
+ continue;
405
+ seenFiles.add(key);
406
+ fileLabels.push(label);
407
+ if (fileLabels.length >= MAX_ACTIVITY_FILE_LABELS)
408
+ break;
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+ catch {
415
+ return { thread: null, modelLabels: [], fileLabels: [] };
416
+ }
417
+ const folderScope = normalizeActivityLabel(options.folderScope, 96);
418
+ return {
419
+ thread: threadLabel
420
+ ? {
421
+ provider: 'Claude',
422
+ actorLabel: 'Claude',
423
+ threadLabel,
424
+ ...(sessionId ? { threadId: sessionId } : {}),
425
+ ...(createdAt ? { createdAt } : {}),
426
+ ...(lastActiveAt ? { lastActiveAt } : {}),
427
+ ...(folderScope ? { folderScope } : {}),
428
+ workspaceHost: 'claude',
429
+ }
430
+ : null,
431
+ modelLabels,
432
+ fileLabels,
433
+ };
434
+ }
435
+ function summarizeClaudeSessionFile(sessionPath, folderScope, repoRoot) {
436
+ try {
437
+ return parseClaudeSessionJsonl(node_fs_1.default.readFileSync(sessionPath, 'utf8'), { folderScope, repoRoot });
438
+ }
439
+ catch {
440
+ return { thread: null, modelLabels: [], fileLabels: [] };
441
+ }
442
+ }
443
+ function parseEditorActivityThreadsBlob(raw, options) {
444
+ if (!raw)
445
+ return [];
446
+ let parsed;
447
+ try {
448
+ parsed = JSON.parse(raw);
449
+ }
450
+ catch {
451
+ return [];
452
+ }
453
+ if (!Array.isArray(parsed))
454
+ return [];
455
+ const allowedProviderTypes = options.providerTypes ?? null;
456
+ const summaries = parsed
457
+ .filter((item) => Boolean(item) && typeof item === 'object' && !Array.isArray(item))
458
+ .reduce((accumulator, item) => {
459
+ const providerType = trimOptional(typeof item.providerType === 'string' ? item.providerType : null);
460
+ if (allowedProviderTypes && (!providerType || !allowedProviderTypes.has(providerType))) {
461
+ return accumulator;
462
+ }
463
+ const threadLabel = normalizeActivityLabel(item.label);
464
+ if (!threadLabel || threadLabel.toLowerCase() === 'new task' || threadLabel.toLowerCase() === 'new chat') {
465
+ return accumulator;
466
+ }
467
+ const provider = normalizeActivityLabel(item.providerLabel, 40)
468
+ ?? normalizeActivityLabel(options.providerLabel, 40)
469
+ ?? normalizeActivityLabel(providerType, 40)
470
+ ?? options.actorLabel;
471
+ const timing = item.timing && typeof item.timing === 'object' && !Array.isArray(item.timing)
472
+ ? item.timing
473
+ : {};
474
+ const threadId = extractThreadIdFromResource(item.resource);
475
+ const createdAt = normalizeIsoTimestamp(timing.created);
476
+ const lastActiveAt = normalizeIsoTimestamp(timing.lastRequestStarted ?? timing.lastResponseEnded ?? timing.created);
477
+ const folderScope = normalizeActivityLabel(options.folderScope, 96);
478
+ accumulator.push({
479
+ provider,
480
+ actorLabel: options.actorLabel,
481
+ threadLabel,
482
+ ...(threadId ? { threadId } : {}),
483
+ ...(createdAt ? { createdAt } : {}),
484
+ ...(lastActiveAt ? { lastActiveAt } : {}),
485
+ ...(folderScope ? { folderScope } : {}),
486
+ workspaceHost: options.workspaceHost,
487
+ });
488
+ return accumulator;
489
+ }, [])
490
+ .sort((left, right) => {
491
+ const rightTime = Date.parse(right.lastActiveAt || right.createdAt || '');
492
+ const leftTime = Date.parse(left.lastActiveAt || left.createdAt || '');
493
+ if (Number.isFinite(rightTime) && Number.isFinite(leftTime) && rightTime !== leftTime) {
494
+ return rightTime - leftTime;
495
+ }
496
+ return left.threadLabel.localeCompare(right.threadLabel);
497
+ });
498
+ const deduped = [];
499
+ const seen = new Set();
500
+ for (const item of summaries) {
501
+ const key = `${item.provider}|${item.threadId || item.threadLabel}`;
502
+ if (seen.has(key))
503
+ continue;
504
+ seen.add(key);
505
+ deduped.push(item);
506
+ if (deduped.length >= MAX_ACTIVITY_THREADS)
507
+ break;
508
+ }
509
+ return deduped;
510
+ }
511
+ function parseCopilotChatSessionIndexBlob(raw, options) {
512
+ if (!raw)
513
+ return [];
514
+ let parsed;
515
+ try {
516
+ parsed = JSON.parse(raw);
517
+ }
518
+ catch {
519
+ return [];
520
+ }
521
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
522
+ return [];
523
+ const entries = parsed.entries;
524
+ if (!entries || typeof entries !== 'object' || Array.isArray(entries))
525
+ return [];
526
+ const summaries = Object.entries(entries)
527
+ .map(([entryId, value]) => {
528
+ if (!value || typeof value !== 'object' || Array.isArray(value))
529
+ return null;
530
+ const record = value;
531
+ const threadLabel = normalizeActivityLabel(record.title);
532
+ if (!threadLabel || threadLabel.toLowerCase() === 'new chat' || record.isEmpty === true) {
533
+ return null;
534
+ }
535
+ const timing = record.timing && typeof record.timing === 'object' && !Array.isArray(record.timing)
536
+ ? record.timing
537
+ : {};
538
+ const createdAt = normalizeIsoTimestamp(timing.created);
539
+ const lastActiveAt = normalizeIsoTimestamp(record.lastMessageDate ?? timing.lastRequestEnded ?? timing.lastResponseEnded ?? timing.created);
540
+ const threadId = trimOptional(typeof record.sessionId === 'string' ? record.sessionId : entryId);
541
+ const folderScope = normalizeActivityLabel(options.folderScope, 96);
542
+ return {
543
+ provider: 'GitHub Copilot Chat',
544
+ actorLabel: 'Copilot',
545
+ threadLabel,
546
+ ...(threadId ? { threadId } : {}),
547
+ ...(createdAt ? { createdAt } : {}),
548
+ ...(lastActiveAt ? { lastActiveAt } : {}),
549
+ ...(folderScope ? { folderScope } : {}),
550
+ workspaceHost: options.workspaceHost,
551
+ };
552
+ })
553
+ .filter((item) => Boolean(item))
554
+ .sort((left, right) => {
555
+ const rightTime = Date.parse(right.lastActiveAt || right.createdAt || '');
556
+ const leftTime = Date.parse(left.lastActiveAt || left.createdAt || '');
557
+ if (Number.isFinite(rightTime) && Number.isFinite(leftTime) && rightTime !== leftTime) {
558
+ return rightTime - leftTime;
559
+ }
560
+ return left.threadLabel.localeCompare(right.threadLabel);
561
+ });
562
+ const deduped = [];
563
+ const seen = new Set();
564
+ for (const item of summaries) {
565
+ const key = `${item.provider}|${item.threadId || item.threadLabel}`;
566
+ if (seen.has(key))
567
+ continue;
568
+ seen.add(key);
569
+ deduped.push(item);
570
+ if (deduped.length >= MAX_ACTIVITY_THREADS)
571
+ break;
572
+ }
573
+ return deduped;
574
+ }
575
+ function parseCopilotSelectedModelLabels(raw) {
576
+ if (!raw)
577
+ return [];
578
+ let parsed;
579
+ try {
580
+ parsed = JSON.parse(raw);
581
+ }
582
+ catch {
583
+ return [];
584
+ }
585
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
586
+ return [];
587
+ const root = parsed;
588
+ const selectedModel = root.selectedModel;
589
+ if (!selectedModel || typeof selectedModel !== 'object' || Array.isArray(selectedModel))
590
+ return [];
591
+ const selectedRecord = selectedModel;
592
+ const metadata = selectedRecord.metadata && typeof selectedRecord.metadata === 'object' && !Array.isArray(selectedRecord.metadata)
593
+ ? selectedRecord.metadata
594
+ : {};
595
+ const extension = metadata.extension && typeof metadata.extension === 'object' && !Array.isArray(metadata.extension)
596
+ ? metadata.extension
597
+ : {};
598
+ const extensionValue = normalizeActivityLabel(extension.value, 64);
599
+ if (extensionValue && extensionValue.toLowerCase() !== 'github.copilot-chat') {
600
+ return [];
601
+ }
602
+ const candidates = [
603
+ normalizeActivityModelLabel(metadata.version),
604
+ normalizeActivityModelLabel(metadata.id),
605
+ normalizeActivityModelLabel(selectedRecord.identifier),
606
+ normalizeActivityModelLabel(metadata.name),
607
+ ].filter((item) => Boolean(item));
608
+ const result = [];
609
+ const seen = new Set();
610
+ for (const candidate of candidates) {
611
+ const key = candidate.toLowerCase();
612
+ if (seen.has(key))
613
+ continue;
614
+ seen.add(key);
615
+ result.push(candidate);
616
+ }
617
+ return result;
618
+ }
619
+ function parseEditorOpenFileStateBlob(raw, options) {
620
+ if (!raw)
621
+ return [];
622
+ let parsed;
623
+ try {
624
+ parsed = JSON.parse(raw);
625
+ }
626
+ catch {
627
+ return [];
628
+ }
629
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
630
+ return [];
631
+ const textEditorViewState = parsed.textEditorViewState;
632
+ if (!Array.isArray(textEditorViewState))
633
+ return [];
634
+ const results = [];
635
+ const seen = new Set();
636
+ for (const entry of textEditorViewState) {
637
+ if (!Array.isArray(entry) || typeof entry[0] !== 'string')
638
+ continue;
639
+ const decodedPath = decodeFileUri(entry[0]);
640
+ if (!decodedPath)
641
+ continue;
642
+ const relative = normalizeRepoRelativeFilePath(options.repoRoot, decodedPath);
643
+ if (!relative)
644
+ continue;
645
+ const key = relative.toLowerCase();
646
+ if (seen.has(key))
647
+ continue;
648
+ seen.add(key);
649
+ results.push(relative);
650
+ if (results.length >= MAX_ACTIVITY_SCOPE_FILES)
651
+ break;
652
+ }
653
+ return results;
654
+ }
655
+ function collectCodexEditorThreads(repo) {
656
+ if (!repo.isGitRepo || !repo.rootPath)
657
+ return [];
658
+ const folderScope = normalizeActivityLabel(repo.relativeCwd || '.', 96) || '.';
659
+ const threadEntries = [];
660
+ const seen = new Set();
661
+ for (const source of EDITOR_WORKSPACE_THREAD_SOURCES) {
662
+ const workspaceDir = findEditorWorkspaceStorageDir(source.workspaceStorageDirs, repo.rootPath);
663
+ if (!workspaceDir)
664
+ continue;
665
+ const rawState = readSqliteItemValue(node_path_1.default.join(workspaceDir, 'state.vscdb'), source.stateCacheKey);
666
+ const nextEntries = parseEditorActivityThreadsBlob(rawState, {
667
+ actorLabel: 'Codex',
668
+ providerTypes: new Set(['openai-codex']),
669
+ providerLabel: 'Codex',
670
+ folderScope,
671
+ workspaceHost: source.host,
672
+ });
673
+ for (const entry of nextEntries) {
674
+ const key = `${entry.provider}|${entry.threadId || entry.threadLabel}`;
675
+ if (seen.has(key))
676
+ continue;
677
+ seen.add(key);
678
+ threadEntries.push(entry);
679
+ if (threadEntries.length >= MAX_ACTIVITY_THREADS) {
680
+ return threadEntries;
681
+ }
682
+ }
683
+ }
684
+ return threadEntries;
685
+ }
686
+ function collectCopilotEditorActivity(repo) {
687
+ if (!repo.isGitRepo || !repo.rootPath) {
688
+ return {
689
+ threads: [],
690
+ modelLabels: [],
691
+ fileLabels: [],
692
+ scopeFileLabels: [],
693
+ };
694
+ }
695
+ const folderScope = normalizeActivityLabel(repo.relativeCwd || '.', 96) || '.';
696
+ const threadEntries = [];
697
+ const modelLabels = [];
698
+ const scopeFileLabels = [];
699
+ const seenThreads = new Set();
700
+ const seenModels = new Set();
701
+ const seenScopeFiles = new Set();
702
+ for (const source of EDITOR_WORKSPACE_THREAD_SOURCES) {
703
+ const workspaceDir = findEditorWorkspaceStorageDir(source.workspaceStorageDirs, repo.rootPath);
704
+ if (!workspaceDir)
705
+ continue;
706
+ const dbPath = node_path_1.default.join(workspaceDir, 'state.vscdb');
707
+ const copilotExtensionState = readSqliteItemValue(dbPath, COPILOT_EXTENSION_STATE_KEY);
708
+ if (!copilotExtensionState)
709
+ continue;
710
+ const nextThreads = parseCopilotChatSessionIndexBlob(readSqliteItemValue(dbPath, COPILOT_CHAT_SESSION_INDEX_KEY), {
711
+ folderScope,
712
+ workspaceHost: source.host,
713
+ });
714
+ for (const entry of nextThreads) {
715
+ const key = `${entry.provider}|${entry.threadId || entry.threadLabel}`;
716
+ if (seenThreads.has(key))
717
+ continue;
718
+ seenThreads.add(key);
719
+ threadEntries.push(entry);
720
+ if (threadEntries.length >= MAX_ACTIVITY_THREADS)
721
+ break;
722
+ }
723
+ for (const label of parseCopilotSelectedModelLabels(readSqliteItemValue(dbPath, COPILOT_ACTIVE_SESSION_STATE_KEY))) {
724
+ const key = label.toLowerCase();
725
+ if (seenModels.has(key))
726
+ continue;
727
+ seenModels.add(key);
728
+ modelLabels.push(label);
729
+ }
730
+ for (const label of parseEditorOpenFileStateBlob(readSqliteItemValue(dbPath, EDITOR_OPEN_FILE_STATE_KEY), { repoRoot: repo.rootPath })) {
731
+ const key = label.toLowerCase();
732
+ if (seenScopeFiles.has(key))
733
+ continue;
734
+ seenScopeFiles.add(key);
735
+ scopeFileLabels.push(label);
736
+ if (scopeFileLabels.length >= MAX_ACTIVITY_SCOPE_FILES)
737
+ break;
738
+ }
739
+ if (threadEntries.length >= MAX_ACTIVITY_THREADS)
740
+ break;
741
+ }
742
+ return {
743
+ threads: threadEntries,
744
+ modelLabels,
745
+ fileLabels: [],
746
+ scopeFileLabels,
747
+ };
748
+ }
749
+ function collectClaudeProjectActivity(repo) {
750
+ if (!repo.isGitRepo || !repo.rootPath) {
751
+ return {
752
+ threads: [],
753
+ modelLabels: [],
754
+ fileLabels: [],
755
+ scopeFileLabels: [],
756
+ };
757
+ }
758
+ const folderScope = normalizeActivityLabel(repo.relativeCwd || '.', 96) || '.';
759
+ const projectDir = node_path_1.default.join(node_os_1.default.homedir(), '.claude', 'projects', normalizeClaudeProjectDirName(repo.rootPath));
760
+ if (!node_fs_1.default.existsSync(projectDir)) {
761
+ return {
762
+ threads: [],
763
+ modelLabels: [],
764
+ fileLabels: [],
765
+ scopeFileLabels: [],
766
+ };
767
+ }
768
+ const modelLabels = [];
769
+ const fileLabels = [];
770
+ const seenModels = new Set();
771
+ const seenFiles = new Set();
772
+ const threadEntries = [];
773
+ const sessionFiles = node_fs_1.default.readdirSync(projectDir, { withFileTypes: true })
774
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
775
+ .map((entry) => {
776
+ const fullPath = node_path_1.default.join(projectDir, entry.name);
777
+ const stats = node_fs_1.default.statSync(fullPath);
778
+ return {
779
+ fullPath,
780
+ mtimeMs: stats.mtimeMs,
781
+ };
782
+ })
783
+ .sort((left, right) => right.mtimeMs - left.mtimeMs)
784
+ .slice(0, MAX_ACTIVITY_THREADS);
785
+ const seenThreads = new Set();
786
+ for (const sessionFile of sessionFiles) {
787
+ const summary = summarizeClaudeSessionFile(sessionFile.fullPath, folderScope, repo.rootPath);
788
+ if (summary.thread) {
789
+ const key = `${summary.thread.provider}|${summary.thread.threadId || summary.thread.threadLabel}`;
790
+ if (!seenThreads.has(key)) {
791
+ seenThreads.add(key);
792
+ threadEntries.push(summary.thread);
793
+ }
794
+ }
795
+ for (const label of summary.modelLabels) {
796
+ const key = label.toLowerCase();
797
+ if (seenModels.has(key))
798
+ continue;
799
+ seenModels.add(key);
800
+ modelLabels.push(label);
801
+ }
802
+ for (const label of summary.fileLabels) {
803
+ const key = label.toLowerCase();
804
+ if (seenFiles.has(key))
805
+ continue;
806
+ seenFiles.add(key);
807
+ fileLabels.push(label);
808
+ if (fileLabels.length >= MAX_ACTIVITY_FILE_LABELS)
809
+ break;
810
+ }
811
+ if (threadEntries.length >= MAX_ACTIVITY_THREADS)
812
+ break;
813
+ }
814
+ return {
815
+ threads: threadEntries,
816
+ modelLabels,
817
+ fileLabels,
818
+ scopeFileLabels: [],
819
+ };
820
+ }
821
+ //# sourceMappingURL=runtime-editor-activity.js.map