agentel 0.2.0 → 0.2.3
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 +161 -63
- package/agentlog-spec.md +42 -35
- package/bin/agentlog-recall.js +2 -0
- package/bin/agentlog.js +12 -0
- package/docs/code-reference.md +120 -34
- package/docs/history-source-handling.md +236 -81
- package/docs/release.md +8 -8
- package/package.json +5 -4
- package/src/archive.js +279 -20
- package/src/cli.js +3457 -511
- package/src/config.js +42 -1
- package/src/doctor.js +167 -10
- package/src/importers/gemini.js +369 -7
- package/src/importers.js +1893 -133
- package/src/mcp.js +4 -1
- package/src/parser-versions.js +37 -22
- package/src/paths.js +4 -2
- package/src/redaction.js +140 -17
- package/src/search.js +671 -52
- package/src/supervisor.js +206 -57
- package/src/sync.js +459 -12
package/src/importers.js
CHANGED
|
@@ -15,11 +15,14 @@ const { DISABLED_IMPORT_SOURCE_MESSAGES, IMPORT_SOURCE_ORDER } = require("./sour
|
|
|
15
15
|
const { parseAiderHistoryFile } = require("./importers/aider");
|
|
16
16
|
const { extractClaudeMessagesFromEvent, updateClaudeParseContext } = require("./importers/claude");
|
|
17
17
|
const { readClineCheckpointDiffs } = require("./importers/cline");
|
|
18
|
-
const {
|
|
18
|
+
const { parseGeminiCliJsonSessions, parseGeminiCliJsonlSessions } = require("./importers/gemini");
|
|
19
19
|
const { importSourceWithAdapter, providerAdapterForSource, providerAdapterSources } = require("./importers/providers");
|
|
20
20
|
const { parseSince } = require("./importers/shared");
|
|
21
21
|
const { canonicalWebProvider, derivedAccountId, getWebAccount, upsertWebAccount } = require("./web-accounts");
|
|
22
22
|
|
|
23
|
+
const WEB_TOKEN_ESTIMATE_CHARS = 4;
|
|
24
|
+
const WEB_CHAT_TOKEN_ESTIMATION_METHOD = "web-message-parts-chars-v1";
|
|
25
|
+
|
|
23
26
|
function importCliHistory(options = {}, env = process.env) {
|
|
24
27
|
const source = options.source || "all";
|
|
25
28
|
const since = parseSince(options.since || "30d");
|
|
@@ -451,6 +454,8 @@ function importCursorProvider(provider, since, options = {}, env = process.env)
|
|
|
451
454
|
sourceFiles: session.sourceFiles || [session.sourcePath, session.dbPath].filter(Boolean),
|
|
452
455
|
sourceType,
|
|
453
456
|
title: session.title,
|
|
457
|
+
composerId: cursorSessionComposerId(session) || undefined,
|
|
458
|
+
parentComposerId: session.parentComposerId || undefined,
|
|
454
459
|
sharedRawFiles: cursorSessionUsesSharedRawFiles(sourceType),
|
|
455
460
|
replaceSourcePathCopies: sourceType === "cursor-agent-transcripts"
|
|
456
461
|
},
|
|
@@ -534,6 +539,10 @@ function importStructuredProvider(provider, sessions, since, options = {}, env =
|
|
|
534
539
|
sourceFiles: session.sourceFiles || [session.sourcePath].filter(Boolean),
|
|
535
540
|
sourceType,
|
|
536
541
|
title: session.title,
|
|
542
|
+
sessionSummary: session.sessionSummary,
|
|
543
|
+
providerConversationId: session.providerConversationId,
|
|
544
|
+
rawReferences: session.rawReferences,
|
|
545
|
+
sharedRawFiles: structuredSessionUsesSharedRawFiles(provider, sourceType),
|
|
537
546
|
replaceSourcePathCopies: structuredSessionReplaceSourcePathCopies(provider, sourceType)
|
|
538
547
|
},
|
|
539
548
|
env
|
|
@@ -554,6 +563,10 @@ function structuredSessionReplaceSourcePathCopies(provider, sourceType) {
|
|
|
554
563
|
return undefined;
|
|
555
564
|
}
|
|
556
565
|
|
|
566
|
+
function structuredSessionUsesSharedRawFiles(provider, sourceType) {
|
|
567
|
+
return provider === "opencode" && sourceType === "opencode-sqlite-history";
|
|
568
|
+
}
|
|
569
|
+
|
|
557
570
|
function parseAgentJsonl(file, provider) {
|
|
558
571
|
const text = readTextMaybeZstd(file);
|
|
559
572
|
const messages = [];
|
|
@@ -1342,6 +1355,7 @@ function importWebChat(providerInput, file, options = {}, env = process.env) {
|
|
|
1342
1355
|
sourceType,
|
|
1343
1356
|
parserVersion: parserVersionForSource(sourceType),
|
|
1344
1357
|
title: conversation.title,
|
|
1358
|
+
sessionSummary: conversation.sessionSummary,
|
|
1345
1359
|
providerConversationId: conversation.id,
|
|
1346
1360
|
chatAccountId: account.accountId,
|
|
1347
1361
|
chatUsername: account.username,
|
|
@@ -1351,6 +1365,7 @@ function importWebChat(providerInput, file, options = {}, env = process.env) {
|
|
|
1351
1365
|
chatDisplayPath: displayPath,
|
|
1352
1366
|
conversationKind: conversation.kind || "conversation",
|
|
1353
1367
|
pinned: Boolean(conversation.pinned),
|
|
1368
|
+
timeStatus: conversation.timeStatus || "",
|
|
1354
1369
|
replaceSourcePathCopies: false
|
|
1355
1370
|
},
|
|
1356
1371
|
env
|
|
@@ -1372,6 +1387,17 @@ function importWebChat(providerInput, file, options = {}, env = process.env) {
|
|
|
1372
1387
|
return summary;
|
|
1373
1388
|
}
|
|
1374
1389
|
|
|
1390
|
+
function importWindsurfTrajectoryExport(target, options = {}, env = process.env) {
|
|
1391
|
+
const since = parseSince(options.since || "all");
|
|
1392
|
+
return importStructuredProvider(
|
|
1393
|
+
"windsurf",
|
|
1394
|
+
readWindsurfTrajectoryExport(target, options),
|
|
1395
|
+
since,
|
|
1396
|
+
{ ...options, repos: options.repos || [] },
|
|
1397
|
+
env
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1375
1401
|
function readExportJson(file) {
|
|
1376
1402
|
const bundle = readExportBundle(file);
|
|
1377
1403
|
const conversations = bundle.entries.find((entry) => /(^|\/)conversations\.json$/i.test(entry.name));
|
|
@@ -1458,11 +1484,13 @@ function readExportFile(file) {
|
|
|
1458
1484
|
|
|
1459
1485
|
function exportEntry(name, text, sourcePath) {
|
|
1460
1486
|
const data = parseExportText(text, name);
|
|
1487
|
+
const stat = safeStat(String(sourcePath || "").split("#")[0]);
|
|
1461
1488
|
return {
|
|
1462
1489
|
name,
|
|
1463
1490
|
sourcePath,
|
|
1464
1491
|
data,
|
|
1465
1492
|
size: Buffer.byteLength(text),
|
|
1493
|
+
mtime: stat?.mtimeMs ? new Date(stat.mtimeMs).toISOString() : "",
|
|
1466
1494
|
sha256: crypto.createHash("sha256").update(text).digest("hex")
|
|
1467
1495
|
};
|
|
1468
1496
|
}
|
|
@@ -1571,16 +1599,16 @@ function chatgptRawConversations(data) {
|
|
|
1571
1599
|
function chatgptMessages(conversation) {
|
|
1572
1600
|
if (conversation.mapping && typeof conversation.mapping === "object") {
|
|
1573
1601
|
const nodes = chatgptMainPathNodes(conversation);
|
|
1574
|
-
return nodes.map((node) => node && node.message).filter(Boolean).map((message) =>
|
|
1575
|
-
role
|
|
1576
|
-
content
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
}
|
|
1583
|
-
})
|
|
1602
|
+
return nodes.map((node) => node && node.message).filter(Boolean).map((message) => {
|
|
1603
|
+
const role = normalizeEventRole(message.author?.role) || "unknown";
|
|
1604
|
+
const content = extractChatGptContent(message.content);
|
|
1605
|
+
return {
|
|
1606
|
+
role,
|
|
1607
|
+
content,
|
|
1608
|
+
timestamp: toIso(message.create_time || message.update_time),
|
|
1609
|
+
metadata: chatgptMessageMetadata(message, role, content)
|
|
1610
|
+
};
|
|
1611
|
+
});
|
|
1584
1612
|
}
|
|
1585
1613
|
return genericConversationMessages(conversation, "chatgpt-export");
|
|
1586
1614
|
}
|
|
@@ -1610,6 +1638,221 @@ function extractChatGptContent(content) {
|
|
|
1610
1638
|
return extractText(content);
|
|
1611
1639
|
}
|
|
1612
1640
|
|
|
1641
|
+
function chatgptMessageMetadata(message, role, content) {
|
|
1642
|
+
return webWithUsage({
|
|
1643
|
+
source: "chatgpt-export",
|
|
1644
|
+
messageId: message.id || undefined,
|
|
1645
|
+
model: firstString(message.metadata?.model_slug, message.metadata?.model, message.metadata?.default_model_slug) || undefined
|
|
1646
|
+
}, webMessageUsage(message, role, { inputText: content, outputText: content }));
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function webWithUsage(metadata, usage) {
|
|
1650
|
+
if (!usage) return metadata;
|
|
1651
|
+
return { ...metadata, usage };
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function webMessageUsage(message, role, parts = {}) {
|
|
1655
|
+
const providerUsage = webProviderUsage(...webUsageCandidates(message));
|
|
1656
|
+
if (providerUsage) return webUsageWithRoleDirection(providerUsage, role);
|
|
1657
|
+
const normalizedRole = String(role || "").toLowerCase();
|
|
1658
|
+
return webEstimatedMessageUsage({
|
|
1659
|
+
inputText: normalizedRole === "assistant" ? "" : parts.inputText,
|
|
1660
|
+
outputText: normalizedRole === "assistant" ? parts.outputText : "",
|
|
1661
|
+
reasoningText: normalizedRole === "assistant" ? parts.reasoningText : ""
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function webUsageWithRoleDirection(usage, role) {
|
|
1666
|
+
if (!usage || usage.inputTokens || usage.outputTokens || usage.totalInputTokens || usage.totalOutputTokens) return usage;
|
|
1667
|
+
const totalTokens = webPositiveTokenNumber(usage.totalTokens);
|
|
1668
|
+
if (!totalTokens) return usage;
|
|
1669
|
+
const directionalTokens = Math.max(
|
|
1670
|
+
0,
|
|
1671
|
+
totalTokens -
|
|
1672
|
+
webPositiveTokenNumber(usage.cacheInputTokens) -
|
|
1673
|
+
webPositiveTokenNumber(usage.reasoningOutputTokens) -
|
|
1674
|
+
webPositiveTokenNumber(usage.toolUsePromptTokens)
|
|
1675
|
+
);
|
|
1676
|
+
if (!directionalTokens) return usage;
|
|
1677
|
+
if (String(role || "").toLowerCase() === "assistant") return { ...usage, outputTokens: directionalTokens };
|
|
1678
|
+
return { ...usage, inputTokens: directionalTokens };
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function webUsageCandidates(message) {
|
|
1682
|
+
if (!message || typeof message !== "object") return [];
|
|
1683
|
+
const metadata = message.metadata && typeof message.metadata === "object" ? message.metadata : {};
|
|
1684
|
+
return [
|
|
1685
|
+
message.usage,
|
|
1686
|
+
message.token_usage,
|
|
1687
|
+
message.tokenUsage,
|
|
1688
|
+
message.token_counts,
|
|
1689
|
+
message.tokenCounts,
|
|
1690
|
+
message.token_count,
|
|
1691
|
+
message.tokenCount,
|
|
1692
|
+
message.tokens,
|
|
1693
|
+
metadata.usage,
|
|
1694
|
+
metadata.token_usage,
|
|
1695
|
+
metadata.tokenUsage,
|
|
1696
|
+
metadata.token_counts,
|
|
1697
|
+
metadata.tokenCounts,
|
|
1698
|
+
metadata.token_count,
|
|
1699
|
+
metadata.tokenCount,
|
|
1700
|
+
metadata.tokens,
|
|
1701
|
+
message
|
|
1702
|
+
];
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function webProviderUsage(...candidates) {
|
|
1706
|
+
for (const candidate of candidates) {
|
|
1707
|
+
const usage = normalizeWebProviderUsage(candidate);
|
|
1708
|
+
if (usage) return usage;
|
|
1709
|
+
}
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
function normalizeWebProviderUsage(usage) {
|
|
1714
|
+
if (usage == null || usage === "") return null;
|
|
1715
|
+
if (typeof usage !== "object") {
|
|
1716
|
+
const totalTokens = webPositiveTokenNumber(usage);
|
|
1717
|
+
return totalTokens ? { totalTokens } : null;
|
|
1718
|
+
}
|
|
1719
|
+
const inputTokens = webPositiveTokenNumber(numericValue(
|
|
1720
|
+
usage.inputTokens,
|
|
1721
|
+
usage.input_tokens,
|
|
1722
|
+
usage.promptTokens,
|
|
1723
|
+
usage.prompt_tokens,
|
|
1724
|
+
usage.promptTokenCount,
|
|
1725
|
+
usage.prompt_token_count,
|
|
1726
|
+
usage.inputTokenCount,
|
|
1727
|
+
usage.input_token_count,
|
|
1728
|
+
usage.input,
|
|
1729
|
+
usage.prompt
|
|
1730
|
+
));
|
|
1731
|
+
const outputTokens = webPositiveTokenNumber(numericValue(
|
|
1732
|
+
usage.outputTokens,
|
|
1733
|
+
usage.output_tokens,
|
|
1734
|
+
usage.completionTokens,
|
|
1735
|
+
usage.completion_tokens,
|
|
1736
|
+
usage.completionTokenCount,
|
|
1737
|
+
usage.completion_token_count,
|
|
1738
|
+
usage.outputTokenCount,
|
|
1739
|
+
usage.output_token_count,
|
|
1740
|
+
usage.output,
|
|
1741
|
+
usage.completion
|
|
1742
|
+
));
|
|
1743
|
+
const totalInputTokens = webPositiveTokenNumber(numericValue(
|
|
1744
|
+
usage.totalInputTokens,
|
|
1745
|
+
usage.total_input_tokens,
|
|
1746
|
+
usage.totalPromptTokens,
|
|
1747
|
+
usage.total_prompt_tokens,
|
|
1748
|
+
usage.totalPromptTokenCount,
|
|
1749
|
+
usage.total_prompt_token_count
|
|
1750
|
+
));
|
|
1751
|
+
const totalOutputTokens = webPositiveTokenNumber(numericValue(
|
|
1752
|
+
usage.totalOutputTokens,
|
|
1753
|
+
usage.total_output_tokens,
|
|
1754
|
+
usage.totalCompletionTokens,
|
|
1755
|
+
usage.total_completion_tokens,
|
|
1756
|
+
usage.totalCompletionTokenCount,
|
|
1757
|
+
usage.total_completion_token_count
|
|
1758
|
+
));
|
|
1759
|
+
const cacheInputTokens = webSumTokenNumbers(
|
|
1760
|
+
usage.cacheInputTokens,
|
|
1761
|
+
usage.cache_input_tokens,
|
|
1762
|
+
usage.cacheCreationInputTokens,
|
|
1763
|
+
usage.cache_creation_input_tokens,
|
|
1764
|
+
usage.cacheReadInputTokens,
|
|
1765
|
+
usage.cache_read_input_tokens,
|
|
1766
|
+
usage.cacheReadTokens,
|
|
1767
|
+
usage.cache_read_tokens,
|
|
1768
|
+
usage.cachedContentTokenCount,
|
|
1769
|
+
usage.cached_content_token_count,
|
|
1770
|
+
usage.cachedTokens,
|
|
1771
|
+
usage.cached_tokens,
|
|
1772
|
+
usage.prompt_tokens_details?.cached_tokens,
|
|
1773
|
+
usage.promptTokensDetails?.cachedTokens
|
|
1774
|
+
);
|
|
1775
|
+
const reasoningOutputTokens = webSumTokenNumbers(
|
|
1776
|
+
usage.reasoningOutputTokens,
|
|
1777
|
+
usage.reasoning_output_tokens,
|
|
1778
|
+
usage.reasoningTokens,
|
|
1779
|
+
usage.reasoning_tokens,
|
|
1780
|
+
usage.reasoningTokenCount,
|
|
1781
|
+
usage.reasoning_token_count,
|
|
1782
|
+
usage.thoughtsTokens,
|
|
1783
|
+
usage.thoughts_tokens,
|
|
1784
|
+
usage.thoughtsTokenCount,
|
|
1785
|
+
usage.thoughts_token_count,
|
|
1786
|
+
usage.completion_tokens_details?.reasoning_tokens,
|
|
1787
|
+
usage.completionTokensDetails?.reasoningTokens,
|
|
1788
|
+
usage.output_tokens_details?.reasoning_tokens,
|
|
1789
|
+
usage.outputTokensDetails?.reasoningTokens
|
|
1790
|
+
);
|
|
1791
|
+
const toolUsePromptTokens = webSumTokenNumbers(
|
|
1792
|
+
usage.toolUsePromptTokens,
|
|
1793
|
+
usage.tool_use_prompt_tokens,
|
|
1794
|
+
usage.toolUsePromptTokenCount,
|
|
1795
|
+
usage.tool_use_prompt_token_count
|
|
1796
|
+
);
|
|
1797
|
+
const explicitTotalTokens = webPositiveTokenNumber(numericValue(
|
|
1798
|
+
usage.totalTokens,
|
|
1799
|
+
usage.total_tokens,
|
|
1800
|
+
usage.totalTokenCount,
|
|
1801
|
+
usage.total_token_count,
|
|
1802
|
+
usage.tokenCount,
|
|
1803
|
+
usage.token_count,
|
|
1804
|
+
usage.tokens,
|
|
1805
|
+
usage.total
|
|
1806
|
+
));
|
|
1807
|
+
const totalTokens = explicitTotalTokens ||
|
|
1808
|
+
inputTokens + outputTokens + cacheInputTokens + reasoningOutputTokens + toolUsePromptTokens ||
|
|
1809
|
+
totalInputTokens + totalOutputTokens;
|
|
1810
|
+
if (!totalTokens) return null;
|
|
1811
|
+
const result = { totalTokens };
|
|
1812
|
+
if (inputTokens) result.inputTokens = inputTokens;
|
|
1813
|
+
if (outputTokens) result.outputTokens = outputTokens;
|
|
1814
|
+
if (totalInputTokens) result.totalInputTokens = totalInputTokens;
|
|
1815
|
+
if (totalOutputTokens) result.totalOutputTokens = totalOutputTokens;
|
|
1816
|
+
if (cacheInputTokens) result.cacheInputTokens = cacheInputTokens;
|
|
1817
|
+
if (reasoningOutputTokens) result.reasoningOutputTokens = reasoningOutputTokens;
|
|
1818
|
+
if (toolUsePromptTokens) result.toolUsePromptTokens = toolUsePromptTokens;
|
|
1819
|
+
return result;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
function webEstimatedMessageUsage(parts = {}) {
|
|
1823
|
+
const inputTokens = webEstimatedTokenCount(parts.inputText);
|
|
1824
|
+
const outputTokens = webEstimatedTokenCount(parts.outputText);
|
|
1825
|
+
const reasoningOutputTokens = webEstimatedTokenCount(parts.reasoningText);
|
|
1826
|
+
const totalTokens = inputTokens + outputTokens + reasoningOutputTokens;
|
|
1827
|
+
if (!totalTokens) return null;
|
|
1828
|
+
const usage = {
|
|
1829
|
+
totalTokens,
|
|
1830
|
+
estimated: true,
|
|
1831
|
+
estimationMethod: WEB_CHAT_TOKEN_ESTIMATION_METHOD,
|
|
1832
|
+
charsPerToken: WEB_TOKEN_ESTIMATE_CHARS
|
|
1833
|
+
};
|
|
1834
|
+
if (inputTokens) usage.inputTokens = inputTokens;
|
|
1835
|
+
if (outputTokens) usage.outputTokens = outputTokens;
|
|
1836
|
+
if (reasoningOutputTokens) usage.reasoningOutputTokens = reasoningOutputTokens;
|
|
1837
|
+
return usage;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function webEstimatedTokenCount(value) {
|
|
1841
|
+
const text = String(value || "");
|
|
1842
|
+
if (!text.trim()) return 0;
|
|
1843
|
+
return Math.max(1, Math.ceil(text.length / WEB_TOKEN_ESTIMATE_CHARS));
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
function webPositiveTokenNumber(value) {
|
|
1847
|
+
const number = Number(value);
|
|
1848
|
+
if (!Number.isFinite(number) || number <= 0) return 0;
|
|
1849
|
+
return Math.round(number);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function webSumTokenNumbers(...values) {
|
|
1853
|
+
return values.reduce((sum, value) => sum + webPositiveTokenNumber(value), 0);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1613
1856
|
function normalizeClaudeWebExport(source) {
|
|
1614
1857
|
const projectMap = claudeProjectMap(source);
|
|
1615
1858
|
const conversations = [];
|
|
@@ -1643,7 +1886,11 @@ function claudeConversationsFromEntry(entry, projectMap = new Map()) {
|
|
|
1643
1886
|
return values.map((conversation, index) => {
|
|
1644
1887
|
const id = firstString(conversation.uuid, conversation.id, conversation.conversation_uuid, conversation.conversation_id) || `claude-${hashId(`${entry.name}:${index}`)}`;
|
|
1645
1888
|
const title = firstString(conversation.name, conversation.title, conversation.summary) || "Claude conversation";
|
|
1646
|
-
const
|
|
1889
|
+
const sessionSummary = claudeConversationSessionSummary(conversation);
|
|
1890
|
+
const messages = [
|
|
1891
|
+
...claudeConversationSummaryMessages(conversation),
|
|
1892
|
+
...claudeMessages(conversation)
|
|
1893
|
+
].filter((message) => message.content.trim());
|
|
1647
1894
|
const sorted = sortConversationMessages(messages);
|
|
1648
1895
|
return {
|
|
1649
1896
|
id,
|
|
@@ -1655,7 +1902,8 @@ function claudeConversationsFromEntry(entry, projectMap = new Map()) {
|
|
|
1655
1902
|
projectPath: claudeConversationProjectPath(conversation, projectPath, projectMap),
|
|
1656
1903
|
entryPath: entry.name,
|
|
1657
1904
|
sourceType: "claude-web-export",
|
|
1658
|
-
kind: "conversation"
|
|
1905
|
+
kind: "conversation",
|
|
1906
|
+
sessionSummary
|
|
1659
1907
|
};
|
|
1660
1908
|
});
|
|
1661
1909
|
}
|
|
@@ -1701,16 +1949,158 @@ function claudeProjectPath(entry) {
|
|
|
1701
1949
|
|
|
1702
1950
|
function claudeMessages(conversation) {
|
|
1703
1951
|
const messages = conversation.chat_messages || conversation.messages || conversation.children || [];
|
|
1704
|
-
return messages.
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1952
|
+
return messages.flatMap(claudeMessageRows);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
function claudeConversationSessionSummary(conversation) {
|
|
1956
|
+
const summary = firstString(conversation.summary, conversation.conversation_summary, conversation.conversationSummary);
|
|
1957
|
+
if (!summary) return undefined;
|
|
1958
|
+
return {
|
|
1959
|
+
source: "claude-web-export",
|
|
1960
|
+
summary,
|
|
1961
|
+
updatedAt: toIso(conversation.updated_at || conversation.updatedAt) || undefined
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function claudeConversationSummaryMessages(conversation) {
|
|
1966
|
+
const summary = firstString(conversation.summary, conversation.conversation_summary, conversation.conversationSummary);
|
|
1967
|
+
if (!summary) return [];
|
|
1968
|
+
return [{
|
|
1969
|
+
role: "assistant",
|
|
1970
|
+
content: claudeSupplementContent("Claude conversation summary", summary),
|
|
1971
|
+
timestamp: toIso(conversation.created_at || conversation.createdAt || conversation.updated_at || conversation.updatedAt) || "",
|
|
1708
1972
|
metadata: {
|
|
1709
1973
|
source: "claude-web-export",
|
|
1710
|
-
|
|
1711
|
-
|
|
1974
|
+
eventType: "claude-conversation-summary",
|
|
1975
|
+
supplementary: true,
|
|
1976
|
+
summaryKind: "conversation_summary"
|
|
1712
1977
|
}
|
|
1713
|
-
}
|
|
1978
|
+
}];
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
function claudeMessageRows(message) {
|
|
1982
|
+
const role = normalizeEventRole(message.sender || message.role || message.author?.role || message.author) || "unknown";
|
|
1983
|
+
const fallbackTimestamp = toIso(message.created_at || message.createdAt || message.updated_at || message.updatedAt || message.timestamp);
|
|
1984
|
+
const metadata = claudeWebMessageMetadata(message);
|
|
1985
|
+
if (role === "assistant") return claudeAssistantMessageRows(message, fallbackTimestamp, metadata);
|
|
1986
|
+
const content = claudeVisibleMessageText(message);
|
|
1987
|
+
return [{
|
|
1988
|
+
role,
|
|
1989
|
+
content,
|
|
1990
|
+
timestamp: fallbackTimestamp,
|
|
1991
|
+
metadata: webWithUsage(metadata, webMessageUsage(message, role, { inputText: content }))
|
|
1992
|
+
}];
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
function claudeAssistantMessageRows(message, fallbackTimestamp, metadata) {
|
|
1996
|
+
const parts = claudeContentParts(message.content);
|
|
1997
|
+
if (!parts.length) {
|
|
1998
|
+
const content = extractText(message.text || message.content || message.message);
|
|
1999
|
+
return [{
|
|
2000
|
+
role: "assistant",
|
|
2001
|
+
content,
|
|
2002
|
+
timestamp: fallbackTimestamp,
|
|
2003
|
+
metadata: webWithUsage(metadata, webMessageUsage(message, "assistant", { outputText: content }))
|
|
2004
|
+
}];
|
|
2005
|
+
}
|
|
2006
|
+
const thinkingParts = claudeThinkingParts(parts);
|
|
2007
|
+
const visibleParts = claudeVisibleParts(parts);
|
|
2008
|
+
const thinking = claudeThinkingText(thinkingParts);
|
|
2009
|
+
const visible = claudeVisibleText(visibleParts);
|
|
2010
|
+
const usage = webMessageUsage(message, "assistant", { outputText: visible, reasoningText: thinking });
|
|
2011
|
+
const rows = [];
|
|
2012
|
+
if (thinking) {
|
|
2013
|
+
const thinkingSummaries = claudeThinkingSummaries(thinkingParts);
|
|
2014
|
+
rows.push({
|
|
2015
|
+
role: "assistant",
|
|
2016
|
+
content: claudeSupplementContent("Claude thinking", thinking),
|
|
2017
|
+
timestamp: claudePartsTimestamp(thinkingParts, fallbackTimestamp),
|
|
2018
|
+
metadata: webWithUsage({
|
|
2019
|
+
...metadata,
|
|
2020
|
+
eventType: "claude-thinking",
|
|
2021
|
+
supplementary: true,
|
|
2022
|
+
summaryKind: "thinking",
|
|
2023
|
+
thinkingSummaries: thinkingSummaries.length ? thinkingSummaries : undefined
|
|
2024
|
+
}, visible ? null : usage)
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
if (visible) {
|
|
2028
|
+
rows.push({
|
|
2029
|
+
role: "assistant",
|
|
2030
|
+
content: visible,
|
|
2031
|
+
timestamp: claudePartsTimestamp(visibleParts, fallbackTimestamp),
|
|
2032
|
+
metadata: webWithUsage(metadata, usage)
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
return rows;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
function claudeWebMessageMetadata(message) {
|
|
2039
|
+
return {
|
|
2040
|
+
source: "claude-web-export",
|
|
2041
|
+
messageId: firstString(message.uuid, message.id) || undefined,
|
|
2042
|
+
model: firstString(message.model, message.model_name, message.modelName) || undefined
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
function claudeContentParts(content) {
|
|
2047
|
+
return Array.isArray(content) ? content.filter((part) => part && typeof part === "object") : [];
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
function claudeVisibleMessageText(message) {
|
|
2051
|
+
const parts = claudeContentParts(message.content);
|
|
2052
|
+
if (parts.length) return claudeVisibleText(claudeVisibleParts(parts));
|
|
2053
|
+
return extractText(message.text || message.content || message.message);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function claudeVisibleParts(parts) {
|
|
2057
|
+
return parts.filter((part) => {
|
|
2058
|
+
const type = String(part.type || part.kind || "").toLowerCase();
|
|
2059
|
+
return !/(tool_use|tool_result|function_call|function_result|thinking|redacted_thinking)/.test(type);
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function claudeThinkingParts(parts) {
|
|
2064
|
+
return parts.filter((part) => /thinking/.test(String(part.type || part.kind || "").toLowerCase()));
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
function claudeVisibleText(parts) {
|
|
2068
|
+
return parts.map((part) => extractText(part)).filter(Boolean).join("\n").trim();
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
function claudeThinkingText(parts) {
|
|
2072
|
+
return parts
|
|
2073
|
+
.map((part) => firstString(part.thinking, part.text, part.content, part.summary, extractText(part.content)))
|
|
2074
|
+
.filter(Boolean)
|
|
2075
|
+
.join("\n\n")
|
|
2076
|
+
.trim();
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function claudeThinkingSummaries(parts) {
|
|
2080
|
+
return parts.flatMap((part) => Array.isArray(part.summaries)
|
|
2081
|
+
? part.summaries.map((summary) => firstString(summary.summary, summary.text, summary.content))
|
|
2082
|
+
: []
|
|
2083
|
+
).filter(Boolean);
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function claudePartsTimestamp(parts, fallbackTimestamp) {
|
|
2087
|
+
const timestamps = parts
|
|
2088
|
+
.map((part) => toIso(
|
|
2089
|
+
part.stop_timestamp ||
|
|
2090
|
+
part.stopTimestamp ||
|
|
2091
|
+
part.end_timestamp ||
|
|
2092
|
+
part.endTimestamp ||
|
|
2093
|
+
part.start_timestamp ||
|
|
2094
|
+
part.startTimestamp ||
|
|
2095
|
+
part.created_at ||
|
|
2096
|
+
part.createdAt
|
|
2097
|
+
))
|
|
2098
|
+
.filter(Boolean);
|
|
2099
|
+
return timestamps[timestamps.length - 1] || fallbackTimestamp || "";
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
function claudeSupplementContent(title, content) {
|
|
2103
|
+
return `### ${title}\n\n${String(content || "").trim()}`;
|
|
1714
2104
|
}
|
|
1715
2105
|
|
|
1716
2106
|
function claudeMemoryConversations(entry, projectMap = new Map()) {
|
|
@@ -1718,7 +2108,7 @@ function claudeMemoryConversations(entry, projectMap = new Map()) {
|
|
|
1718
2108
|
const conversations = [];
|
|
1719
2109
|
for (const row of rows) {
|
|
1720
2110
|
const rootContent = renderClaudeConversationMemory(row);
|
|
1721
|
-
if (rootContent.trim()) conversations.push(claudeMemoryConversation("memory", "Claude Memory", rootContent, "memory", entry
|
|
2111
|
+
if (rootContent.trim()) conversations.push(claudeMemoryConversation("memory", "Claude Memory", rootContent, "memory", entry));
|
|
1722
2112
|
const projectMemories = row && typeof row === "object" && row.project_memories && typeof row.project_memories === "object"
|
|
1723
2113
|
? row.project_memories
|
|
1724
2114
|
: {};
|
|
@@ -1727,14 +2117,14 @@ function claudeMemoryConversations(entry, projectMap = new Map()) {
|
|
|
1727
2117
|
if (!content.trim()) continue;
|
|
1728
2118
|
const project = projectMap.get(projectId);
|
|
1729
2119
|
const projectTitle = project?.title || project?.path || sanitizeProjectPath(projectId) || projectId;
|
|
1730
|
-
conversations.push(claudeMemoryConversation(`memory-${projectId}`, `Claude Project Memory: ${projectTitle}`, content, "memory", entry
|
|
2120
|
+
conversations.push(claudeMemoryConversation(`memory-${projectId}`, `Claude Project Memory: ${projectTitle}`, content, "memory", entry));
|
|
1731
2121
|
}
|
|
1732
2122
|
}
|
|
1733
2123
|
return conversations;
|
|
1734
2124
|
}
|
|
1735
2125
|
|
|
1736
|
-
function claudeMemoryConversation(id, title, content, projectPath,
|
|
1737
|
-
const timestamp =
|
|
2126
|
+
function claudeMemoryConversation(id, title, content, projectPath, entry) {
|
|
2127
|
+
const timestamp = claudeMemorySyntheticTimestamp(entry);
|
|
1738
2128
|
return {
|
|
1739
2129
|
id,
|
|
1740
2130
|
title,
|
|
@@ -1743,13 +2133,23 @@ function claudeMemoryConversation(id, title, content, projectPath, entryPath) {
|
|
|
1743
2133
|
endedAt: timestamp,
|
|
1744
2134
|
updatedAt: timestamp,
|
|
1745
2135
|
projectPath: projectPath || "",
|
|
1746
|
-
entryPath,
|
|
2136
|
+
entryPath: entry?.name || "",
|
|
1747
2137
|
sourceType: "claude-web-memory",
|
|
1748
2138
|
kind: "memory",
|
|
1749
|
-
pinned: true
|
|
2139
|
+
pinned: true,
|
|
2140
|
+
timeStatus: "recovered-time-unknown",
|
|
2141
|
+
sessionSummary: {
|
|
2142
|
+
source: "claude-web-memory",
|
|
2143
|
+
timeStatus: "recovered-time-unknown",
|
|
2144
|
+
note: "Claude memory exports do not include reliable memory creation or update timestamps."
|
|
2145
|
+
}
|
|
1750
2146
|
};
|
|
1751
2147
|
}
|
|
1752
2148
|
|
|
2149
|
+
function claudeMemorySyntheticTimestamp(entry) {
|
|
2150
|
+
return toIso(entry?.mtime) || "1970-01-01T00:00:00.000Z";
|
|
2151
|
+
}
|
|
2152
|
+
|
|
1753
2153
|
function renderClaudeConversationMemory(data) {
|
|
1754
2154
|
if (data && typeof data === "object" && typeof data.conversations_memory === "string") {
|
|
1755
2155
|
return data.conversations_memory;
|
|
@@ -1781,12 +2181,16 @@ function renderClaudeMemoryItem(item) {
|
|
|
1781
2181
|
|
|
1782
2182
|
function genericConversationMessages(conversation, source) {
|
|
1783
2183
|
const messages = conversation.messages || conversation.chat_messages || [];
|
|
1784
|
-
return messages.map((message) =>
|
|
1785
|
-
role
|
|
1786
|
-
content
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
2184
|
+
return messages.map((message) => {
|
|
2185
|
+
const role = normalizeEventRole(message.role || message.sender || message.author?.role) || "unknown";
|
|
2186
|
+
const content = extractText(message.content || message.text || message.message);
|
|
2187
|
+
return {
|
|
2188
|
+
role,
|
|
2189
|
+
content,
|
|
2190
|
+
timestamp: toIso(message.created_at || message.create_time || message.timestamp),
|
|
2191
|
+
metadata: webWithUsage({ source }, webMessageUsage(message, role, { inputText: content, outputText: content }))
|
|
2192
|
+
};
|
|
2193
|
+
});
|
|
1790
2194
|
}
|
|
1791
2195
|
|
|
1792
2196
|
function sortConversationMessages(messages) {
|
|
@@ -1805,7 +2209,8 @@ function webConversationSessionId(provider, accountId, conversationId) {
|
|
|
1805
2209
|
|
|
1806
2210
|
function webConversationFingerprint(sourceType, accountId, conversation) {
|
|
1807
2211
|
const body = conversation.messages.map((message) => `${message.role}:${message.timestamp}:${message.content}`).join("\n");
|
|
1808
|
-
|
|
2212
|
+
const summary = JSON.stringify(conversation.sessionSummary || {});
|
|
2213
|
+
return `${fingerprintPrefix(sourceType)}:${accountId}:${conversation.projectPath || ""}:${conversation.updatedAt || conversation.endedAt || ""}:${conversation.messages.length}:${hashId(`${body}\n${summary}`)}`;
|
|
1809
2214
|
}
|
|
1810
2215
|
|
|
1811
2216
|
function webConversationScope(provider, accountId, projectPath = "") {
|
|
@@ -2005,8 +2410,8 @@ function discoverCliHistory(env = process.env, options = {}) {
|
|
|
2005
2410
|
summarizeStructuredSessions(
|
|
2006
2411
|
readAntigravitySessions({
|
|
2007
2412
|
onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "Antigravity" })
|
|
2008
|
-
}),
|
|
2009
|
-
"Antigravity task/plan/walkthrough artifacts; binary protobuf transcripts are counted separately"
|
|
2413
|
+
}, env),
|
|
2414
|
+
"Antigravity task/plan/walkthrough artifacts plus trajectory summaries; binary protobuf transcripts are counted separately"
|
|
2010
2415
|
)
|
|
2011
2416
|
);
|
|
2012
2417
|
|
|
@@ -2044,7 +2449,7 @@ function discoverCliHistory(env = process.env, options = {}) {
|
|
|
2044
2449
|
readOpenCodeSessions(env, {
|
|
2045
2450
|
onProgress: (event) => reportDiscoveryProgress(options, { ...event, provider: "OpenCode" })
|
|
2046
2451
|
}),
|
|
2047
|
-
"OpenCode JSON session/message/part storage"
|
|
2452
|
+
"OpenCode SQLite database and JSON session/message/part storage"
|
|
2048
2453
|
)
|
|
2049
2454
|
);
|
|
2050
2455
|
|
|
@@ -2233,6 +2638,8 @@ function summarizeStructuredSessionDetails(sessions) {
|
|
|
2233
2638
|
if (session.detailKey) acc[session.detailKey] = (acc[session.detailKey] || 0) + 1;
|
|
2234
2639
|
if (session.artifactCount) acc.artifacts = (acc.artifacts || 0) + session.artifactCount;
|
|
2235
2640
|
if (session.binaryCount) acc.binaryOnly = (acc.binaryOnly || 0) + session.binaryCount;
|
|
2641
|
+
if (session.partialSummary) acc.partialSummaries = (acc.partialSummaries || 0) + 1;
|
|
2642
|
+
if (session.stateDbCount) acc.stateDbs = (acc.stateDbs || 0) + session.stateDbCount;
|
|
2236
2643
|
return acc;
|
|
2237
2644
|
}, {});
|
|
2238
2645
|
}
|
|
@@ -2413,7 +2820,7 @@ function readCodexThreads(env = process.env) {
|
|
|
2413
2820
|
"where rollout_path != ''",
|
|
2414
2821
|
"order by updated_at desc"
|
|
2415
2822
|
].join(" ");
|
|
2416
|
-
const result = spawnSync("sqlite3", [db, "-json", query], { encoding: "utf8", maxBuffer: 1024 * 1024 * 50 });
|
|
2823
|
+
const result = spawnSync("sqlite3", [db, "-json", query], { argv0: "agentlog-sqlite", encoding: "utf8", maxBuffer: 1024 * 1024 * 50 });
|
|
2417
2824
|
if (result.status !== 0 || !result.stdout.trim()) return [];
|
|
2418
2825
|
try {
|
|
2419
2826
|
return JSON.parse(result.stdout).map((row) => ({
|
|
@@ -2550,18 +2957,37 @@ function normalizeSourcePath(file) {
|
|
|
2550
2957
|
}
|
|
2551
2958
|
|
|
2552
2959
|
function sqliteTableExists(dbPath, tableName) {
|
|
2553
|
-
const result = runSqliteJson(
|
|
2554
|
-
dbPath,
|
|
2555
|
-
`select name from sqlite_master where type = 'table' and name = ${sqlQuote(tableName)} limit 1`
|
|
2556
|
-
);
|
|
2557
|
-
if (!result.ok) return false;
|
|
2558
2960
|
try {
|
|
2559
|
-
return
|
|
2961
|
+
return readSqliteJson(
|
|
2962
|
+
dbPath,
|
|
2963
|
+
`select name from sqlite_master where type = 'table' and name = ${sqlQuote(tableName)} limit 1`,
|
|
2964
|
+
`${tableName} table check`
|
|
2965
|
+
).length > 0;
|
|
2560
2966
|
} catch {
|
|
2561
2967
|
return false;
|
|
2562
2968
|
}
|
|
2563
2969
|
}
|
|
2564
2970
|
|
|
2971
|
+
function sqliteTableColumns(dbPath, tableName) {
|
|
2972
|
+
try {
|
|
2973
|
+
return new Set(
|
|
2974
|
+
readSqliteJson(dbPath, `select name from pragma_table_info(${sqlQuote(tableName)})`, `${tableName} column check`)
|
|
2975
|
+
.map((row) => String(row.name || ""))
|
|
2976
|
+
.filter(Boolean)
|
|
2977
|
+
);
|
|
2978
|
+
} catch {
|
|
2979
|
+
return new Set();
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
function sqliteSelectMaybe(columns, tableAlias, columnName, outputName = columnName) {
|
|
2984
|
+
if (columns.has(columnName)) {
|
|
2985
|
+
const value = `${tableAlias}.${columnName}`;
|
|
2986
|
+
return outputName === columnName ? value : `${value} as ${outputName}`;
|
|
2987
|
+
}
|
|
2988
|
+
return `null as ${outputName}`;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2565
2991
|
function codexStateDb(env = process.env) {
|
|
2566
2992
|
return env.CODEX_STATE_DB || path.join(codexHome(env), "state_5.sqlite");
|
|
2567
2993
|
}
|
|
@@ -2848,11 +3274,13 @@ function readCursorProjectTranscriptSessions(options = {}) {
|
|
|
2848
3274
|
return stat && stat.mtime >= options.modifiedSince;
|
|
2849
3275
|
}));
|
|
2850
3276
|
}
|
|
3277
|
+
const composerInfoLookup = options.composerInfoLookup
|
|
3278
|
+
|| (options.composerTitleLookup ? (id) => ({ title: options.composerTitleLookup(id), model: "" }) : cursorBuildComposerInfoLookup(env));
|
|
2851
3279
|
const sessions = [];
|
|
2852
3280
|
reportDiscoveryProgress(options, { current: 0, total: groups.length, message: "reading Cursor agent transcripts" });
|
|
2853
3281
|
for (let index = 0; index < groups.length; index++) {
|
|
2854
3282
|
const group = groups[index];
|
|
2855
|
-
const session = parseCursorTranscriptGroup(group);
|
|
3283
|
+
const session = parseCursorTranscriptGroup({ ...group, composerInfoLookup });
|
|
2856
3284
|
if (session) sessions.push(session);
|
|
2857
3285
|
reportDiscoveryProgress(options, {
|
|
2858
3286
|
current: index + 1,
|
|
@@ -2864,6 +3292,87 @@ function readCursorProjectTranscriptSessions(options = {}) {
|
|
|
2864
3292
|
return sessions;
|
|
2865
3293
|
}
|
|
2866
3294
|
|
|
3295
|
+
function cursorBuildComposerTitleLookup(env = process.env) {
|
|
3296
|
+
const lookup = cursorBuildComposerInfoLookup(env);
|
|
3297
|
+
return (composerId) => lookup(composerId).title;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
function cursorBuildComposerInfoLookup(env = process.env) {
|
|
3301
|
+
const info = new Map();
|
|
3302
|
+
for (const db of cursorGlobalStorageDbs(env)) {
|
|
3303
|
+
try {
|
|
3304
|
+
const composerRows = readSqliteJson(
|
|
3305
|
+
db,
|
|
3306
|
+
[
|
|
3307
|
+
"select",
|
|
3308
|
+
"key,",
|
|
3309
|
+
"json_extract(value, '$.name') as name,",
|
|
3310
|
+
"json_extract(value, '$.title') as title,",
|
|
3311
|
+
"json_extract(value, '$.chatTitle') as chatTitle,",
|
|
3312
|
+
"json_extract(value, '$.modelConfig.modelName') as modelConfigModelName",
|
|
3313
|
+
"from cursorDiskKV where",
|
|
3314
|
+
"json_valid(value) and (",
|
|
3315
|
+
cursorDiskKvPrefixRangeCondition("composerData:"),
|
|
3316
|
+
"or",
|
|
3317
|
+
cursorDiskKvPrefixRangeCondition("_composerData:"),
|
|
3318
|
+
")"
|
|
3319
|
+
].join(" "),
|
|
3320
|
+
"Cursor global SQLite store"
|
|
3321
|
+
);
|
|
3322
|
+
for (const row of composerRows) {
|
|
3323
|
+
const match = String(row.key || "").match(/^_?composerData:(.+)$/);
|
|
3324
|
+
if (!match) continue;
|
|
3325
|
+
const composerId = match[1].toLowerCase();
|
|
3326
|
+
const title = firstString(row.name, row.title, row.chatTitle);
|
|
3327
|
+
const entry = info.get(composerId) || { title: "", modelHist: new Map() };
|
|
3328
|
+
if (title && !entry.title) entry.title = title;
|
|
3329
|
+
const configModel = firstString(row.modelConfigModelName);
|
|
3330
|
+
if (configModel) entry.configModel = (entry.configModel || configModel);
|
|
3331
|
+
info.set(composerId, entry);
|
|
3332
|
+
}
|
|
3333
|
+
const bubbleRows = readSqliteJson(
|
|
3334
|
+
db,
|
|
3335
|
+
[
|
|
3336
|
+
"select",
|
|
3337
|
+
"key,",
|
|
3338
|
+
"json_extract(value, '$.modelId') as modelId,",
|
|
3339
|
+
"json_extract(value, '$.modelName') as modelName,",
|
|
3340
|
+
"json_extract(value, '$.model') as model",
|
|
3341
|
+
"from cursorDiskKV where",
|
|
3342
|
+
"json_valid(value) and",
|
|
3343
|
+
cursorDiskKvPrefixRangeCondition("bubbleId:")
|
|
3344
|
+
].join(" "),
|
|
3345
|
+
"Cursor global SQLite store"
|
|
3346
|
+
);
|
|
3347
|
+
for (const row of bubbleRows) {
|
|
3348
|
+
const keyMatch = String(row.key || "").match(/^bubbleId:([^:]+):/);
|
|
3349
|
+
if (!keyMatch) continue;
|
|
3350
|
+
const composerId = keyMatch[1].toLowerCase();
|
|
3351
|
+
const model = firstString(row.modelId, row.modelName, row.model);
|
|
3352
|
+
if (!model) continue;
|
|
3353
|
+
const entry = info.get(composerId) || { title: "", modelHist: new Map() };
|
|
3354
|
+
entry.modelHist.set(model, (entry.modelHist.get(model) || 0) + 1);
|
|
3355
|
+
info.set(composerId, entry);
|
|
3356
|
+
}
|
|
3357
|
+
} catch {
|
|
3358
|
+
// global store may be locked or absent; degrade silently.
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
const result = new Map();
|
|
3362
|
+
for (const [id, entry] of info) {
|
|
3363
|
+
let dominantModel = "";
|
|
3364
|
+
let bestCount = 0;
|
|
3365
|
+
for (const [model, count] of entry.modelHist || []) {
|
|
3366
|
+
if (count > bestCount) { bestCount = count; dominantModel = model; }
|
|
3367
|
+
}
|
|
3368
|
+
result.set(id, {
|
|
3369
|
+
title: entry.title || "",
|
|
3370
|
+
model: dominantModel || entry.configModel || ""
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
return (composerId) => result.get(String(composerId || "").toLowerCase()) || { title: "", model: "" };
|
|
3374
|
+
}
|
|
3375
|
+
|
|
2867
3376
|
function cursorProjectTranscriptFiles(env = process.env) {
|
|
2868
3377
|
const root = cursorProjectsRoot(env);
|
|
2869
3378
|
const files = [];
|
|
@@ -2884,13 +3393,28 @@ function groupCursorTranscriptFiles(files, env = process.env) {
|
|
|
2884
3393
|
if (agentIndex < 1) continue;
|
|
2885
3394
|
const projectSlug = parts[0];
|
|
2886
3395
|
const rest = parts.slice(agentIndex + 1);
|
|
2887
|
-
const
|
|
2888
|
-
|
|
2889
|
-
|
|
3396
|
+
const subagentIndex = rest.indexOf("subagents");
|
|
3397
|
+
let id;
|
|
3398
|
+
let parentId = "";
|
|
3399
|
+
let root;
|
|
3400
|
+
let key;
|
|
3401
|
+
if (subagentIndex >= 0 && rest.length > subagentIndex + 1) {
|
|
3402
|
+
// <projectSlug>/agent-transcripts/<parent>/subagents/<subagent>/...
|
|
3403
|
+
parentId = rest.slice(0, subagentIndex).filter(Boolean)[0] || "";
|
|
3404
|
+
const subagentRest = rest.slice(subagentIndex + 1);
|
|
3405
|
+
id = subagentRest.length > 1 ? subagentRest[0] : path.basename(file, path.extname(file));
|
|
3406
|
+
root = path.join(projectsRoot, projectSlug, "agent-transcripts", parentId, "subagents", id);
|
|
3407
|
+
key = `${projectSlug}:${parentId}/subagents/${id}`;
|
|
3408
|
+
} else {
|
|
3409
|
+
id = rest.length > 1 ? rest[0] : path.basename(file, path.extname(file));
|
|
3410
|
+
root = path.join(projectsRoot, projectSlug, "agent-transcripts", id);
|
|
3411
|
+
key = `${projectSlug}:${id}`;
|
|
3412
|
+
}
|
|
2890
3413
|
if (!groups.has(key)) {
|
|
2891
3414
|
groups.set(key, {
|
|
2892
3415
|
key,
|
|
2893
|
-
id
|
|
3416
|
+
id,
|
|
3417
|
+
parentId: parentId || undefined,
|
|
2894
3418
|
projectSlug,
|
|
2895
3419
|
projectDir: path.join(projectsRoot, projectSlug),
|
|
2896
3420
|
root,
|
|
@@ -2905,16 +3429,28 @@ function groupCursorTranscriptFiles(files, env = process.env) {
|
|
|
2905
3429
|
|
|
2906
3430
|
function parseCursorTranscriptGroup(group) {
|
|
2907
3431
|
const parsedFiles = group.files.map(parseCursorTranscriptFile).filter(Boolean);
|
|
2908
|
-
const
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
if (!
|
|
3432
|
+
const rawMessages = parsedFiles
|
|
3433
|
+
.flatMap((parsed) => parsed.messages || [])
|
|
3434
|
+
.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp)));
|
|
3435
|
+
if (!rawMessages.length) return null;
|
|
3436
|
+
const composerInfoLookup = group.composerInfoLookup
|
|
3437
|
+
|| (group.composerLookup ? (id) => ({ title: group.composerLookup(id), model: "" }) : () => ({ title: "", model: "" }));
|
|
3438
|
+
const info = composerInfoLookup(group.id);
|
|
3439
|
+
const fallbackModel = info.model || "";
|
|
3440
|
+
const enrichedRaw = fallbackModel
|
|
3441
|
+
? rawMessages.map((message) => cursorMessageWithFallbackModel(message, fallbackModel))
|
|
3442
|
+
: rawMessages;
|
|
3443
|
+
const messages = stampMessages(dedupeAdjacentMessages(enrichedRaw), "cursor-agent-transcripts");
|
|
2912
3444
|
const cwd = firstString(...parsedFiles.map((parsed) => parsed.cwd), cursorSlugToPath(group.projectSlug, group.env));
|
|
3445
|
+
const fileTitle = firstString(...parsedFiles.map((parsed) => parsed.title));
|
|
3446
|
+
const userTitle = cursorTranscriptTitleFromMessages(messages);
|
|
3447
|
+
const title = firstString(fileTitle, info.title, userTitle, group.id.replace(/[-_]+/g, " "));
|
|
2913
3448
|
return {
|
|
2914
3449
|
sessionId: `cursor-${hashId(group.key)}`,
|
|
2915
3450
|
id: group.id,
|
|
3451
|
+
parentComposerId: group.parentId || undefined,
|
|
2916
3452
|
sourceKey: "cursor-agent-transcripts",
|
|
2917
|
-
title
|
|
3453
|
+
title,
|
|
2918
3454
|
cwd,
|
|
2919
3455
|
startedAt: messages[0]?.timestamp || new Date().toISOString(),
|
|
2920
3456
|
endedAt: messages[messages.length - 1]?.timestamp || messages[0]?.timestamp || new Date().toISOString(),
|
|
@@ -2928,6 +3464,19 @@ function parseCursorTranscriptGroup(group) {
|
|
|
2928
3464
|
};
|
|
2929
3465
|
}
|
|
2930
3466
|
|
|
3467
|
+
function cursorTranscriptTitleFromMessages(messages) {
|
|
3468
|
+
for (const message of messages || []) {
|
|
3469
|
+
if (message.role !== "user") continue;
|
|
3470
|
+
const text = String(message.content || "").trim();
|
|
3471
|
+
if (!text) continue;
|
|
3472
|
+
const firstLineText = text.split(/\r?\n/, 1)[0].trim();
|
|
3473
|
+
if (!firstLineText) continue;
|
|
3474
|
+
const truncated = firstLineText.length > 80 ? firstLineText.slice(0, 77).trimEnd() + "…" : firstLineText;
|
|
3475
|
+
return truncated;
|
|
3476
|
+
}
|
|
3477
|
+
return "";
|
|
3478
|
+
}
|
|
3479
|
+
|
|
2931
3480
|
function parseCursorTranscriptFile(file) {
|
|
2932
3481
|
const stat = safeStat(file);
|
|
2933
3482
|
const fallbackTime = new Date(stat?.mtimeMs || Date.now()).toISOString();
|
|
@@ -3231,6 +3780,7 @@ function readCursorGlobalDiskKvSessionsFromDb(dbPath, options = {}) {
|
|
|
3231
3780
|
"json_extract(value, '$.workspaceIdentifier') as workspaceIdentifier",
|
|
3232
3781
|
"json_extract(value, '$.workspace') as workspace",
|
|
3233
3782
|
"json_extract(value, '$.context') as context",
|
|
3783
|
+
"json_extract(value, '$.modelConfig') as modelConfig",
|
|
3234
3784
|
"json_extract(value, '$.fullConversationHeadersOnly') as fullConversationHeadersOnly",
|
|
3235
3785
|
"length(json_extract(value, '$.conversation')) as conversationBytes"
|
|
3236
3786
|
].join(", "),
|
|
@@ -3289,6 +3839,7 @@ function cursorGlobalComposerDataFromRow(row) {
|
|
|
3289
3839
|
workspaceIdentifier: cursorParseSqliteJsonColumn(row.workspaceIdentifier),
|
|
3290
3840
|
workspace: cursorParseSqliteJsonColumn(row.workspace),
|
|
3291
3841
|
context: cursorParseSqliteJsonColumn(row.context),
|
|
3842
|
+
modelConfig: cursorParseSqliteJsonColumn(row.modelConfig),
|
|
3292
3843
|
fullConversationHeadersOnly: cursorParseSqliteJsonColumn(row.fullConversationHeadersOnly),
|
|
3293
3844
|
_hasConversation: Number(row.conversationBytes || 0) > 2
|
|
3294
3845
|
};
|
|
@@ -3408,6 +3959,8 @@ function cursorGlobalBubbleSelectColumns(valueExpression = "value", keyExpressio
|
|
|
3408
3959
|
`json_extract(${valueExpression}, '$.modelID') as modelID`,
|
|
3409
3960
|
`json_extract(${valueExpression}, '$.modelName') as modelName`,
|
|
3410
3961
|
`json_extract(${valueExpression}, '$.modelSlug') as modelSlug`,
|
|
3962
|
+
`json_extract(${valueExpression}, '$.modelConfig') as modelConfig`,
|
|
3963
|
+
`json_extract(${valueExpression}, '$.providerOptions') as providerOptions`,
|
|
3411
3964
|
`json_extract(${valueExpression}, '$.status') as status`,
|
|
3412
3965
|
`json_extract(${valueExpression}, '$.state') as state`,
|
|
3413
3966
|
`json_extract(${valueExpression}, '$.phase') as phase`,
|
|
@@ -3422,6 +3975,8 @@ function cursorGlobalBubbleSelectColumns(valueExpression = "value", keyExpressio
|
|
|
3422
3975
|
`json_extract(${valueExpression}, '$.usage') as usage`,
|
|
3423
3976
|
`json_extract(${valueExpression}, '$.tokenUsage') as tokenUsage`,
|
|
3424
3977
|
`json_extract(${valueExpression}, '$.token_usage') as token_usage`,
|
|
3978
|
+
`json_extract(${valueExpression}, '$.tokenCount') as tokenCount`,
|
|
3979
|
+
`json_extract(${valueExpression}, '$.token_count') as token_count`,
|
|
3425
3980
|
`json_extract(${valueExpression}, '$.tokens') as tokens`,
|
|
3426
3981
|
`json_extract(${valueExpression}, '$.terminalSelections') as terminalSelections`,
|
|
3427
3982
|
`json_extract(${valueExpression}, '$.fileSelections') as fileSelections`,
|
|
@@ -3458,6 +4013,8 @@ function cursorGlobalBubbleDataFromRow(row) {
|
|
|
3458
4013
|
modelID: row.modelID,
|
|
3459
4014
|
modelName: row.modelName,
|
|
3460
4015
|
modelSlug: row.modelSlug,
|
|
4016
|
+
modelConfig: cursorParseSqliteJsonColumn(row.modelConfig),
|
|
4017
|
+
providerOptions: cursorParseSqliteJsonColumn(row.providerOptions),
|
|
3461
4018
|
status: row.status,
|
|
3462
4019
|
state: cursorParseSqliteJsonColumn(row.state) || row.state,
|
|
3463
4020
|
phase: row.phase,
|
|
@@ -3472,6 +4029,8 @@ function cursorGlobalBubbleDataFromRow(row) {
|
|
|
3472
4029
|
usage: cursorParseSqliteJsonColumn(row.usage),
|
|
3473
4030
|
tokenUsage: cursorParseSqliteJsonColumn(row.tokenUsage),
|
|
3474
4031
|
token_usage: cursorParseSqliteJsonColumn(row.token_usage),
|
|
4032
|
+
tokenCount: cursorParseSqliteJsonColumn(row.tokenCount),
|
|
4033
|
+
token_count: cursorParseSqliteJsonColumn(row.token_count),
|
|
3475
4034
|
tokens: cursorParseSqliteJsonColumn(row.tokens),
|
|
3476
4035
|
terminalSelections: cursorParseSqliteJsonColumn(row.terminalSelections) || [],
|
|
3477
4036
|
fileSelections: cursorParseSqliteJsonColumn(row.fileSelections) || [],
|
|
@@ -3500,6 +4059,7 @@ function cursorGlobalComposerSession(dbPath, composerId, composer, bubbleMap, op
|
|
|
3500
4059
|
const headers = cursorGlobalComposerHeaders(composer);
|
|
3501
4060
|
const startedAt = cursorIso(composer.createdAt || composer.created_at) || eventTimestamp(composer) || new Date().toISOString();
|
|
3502
4061
|
const endedAt = cursorIso(composer.lastUpdatedAt || composer.updatedAt || composer.updated_at) || startedAt;
|
|
4062
|
+
const composerModel = cursorModel(composer);
|
|
3503
4063
|
const messages = [];
|
|
3504
4064
|
for (let index = 0; index < headers.length; index++) {
|
|
3505
4065
|
const header = headers[index] || {};
|
|
@@ -3507,14 +4067,14 @@ function cursorGlobalComposerSession(dbPath, composerId, composer, bubbleMap, op
|
|
|
3507
4067
|
const record = bubbleMap.get(bubbleId) || header;
|
|
3508
4068
|
const timestamp = offsetTimestamp(startedAt, index);
|
|
3509
4069
|
for (const message of cursorMessagesFromRecord(record, "cursor-global-sqlite", timestamp)) {
|
|
3510
|
-
messages.push({ ...message, timestamp });
|
|
4070
|
+
messages.push(cursorMessageWithFallbackModel({ ...message, timestamp }, composerModel));
|
|
3511
4071
|
}
|
|
3512
4072
|
}
|
|
3513
4073
|
if (!messages.length) {
|
|
3514
4074
|
for (const [index, record] of [...bubbleMap.values()].sort(cursorGlobalBubbleRecordCompare).entries()) {
|
|
3515
4075
|
const timestamp = offsetTimestamp(startedAt, index);
|
|
3516
4076
|
for (const message of cursorMessagesFromRecord(record, "cursor-global-sqlite", timestamp)) {
|
|
3517
|
-
messages.push({ ...message, timestamp });
|
|
4077
|
+
messages.push(cursorMessageWithFallbackModel({ ...message, timestamp }, composerModel));
|
|
3518
4078
|
}
|
|
3519
4079
|
}
|
|
3520
4080
|
}
|
|
@@ -3540,6 +4100,17 @@ function cursorGlobalComposerSession(dbPath, composerId, composer, bubbleMap, op
|
|
|
3540
4100
|
};
|
|
3541
4101
|
}
|
|
3542
4102
|
|
|
4103
|
+
function cursorMessageWithFallbackModel(message, model) {
|
|
4104
|
+
if (!model || message?.role !== "assistant" || message?.metadata?.model) return message;
|
|
4105
|
+
return {
|
|
4106
|
+
...message,
|
|
4107
|
+
metadata: {
|
|
4108
|
+
...(message.metadata || {}),
|
|
4109
|
+
model
|
|
4110
|
+
}
|
|
4111
|
+
};
|
|
4112
|
+
}
|
|
4113
|
+
|
|
3543
4114
|
function cursorGlobalComposerHeaders(composer) {
|
|
3544
4115
|
if (Array.isArray(composer.fullConversationHeadersOnly) && composer.fullConversationHeadersOnly.length) {
|
|
3545
4116
|
return composer.fullConversationHeadersOnly;
|
|
@@ -3894,24 +4465,46 @@ function cursorCwdFromObject(data) {
|
|
|
3894
4465
|
);
|
|
3895
4466
|
const workspaceCwd = cursorExistingCwdFromPath(workspacePath, true);
|
|
3896
4467
|
if (workspaceCwd) return workspaceCwd;
|
|
4468
|
+
const candidates = cursorPathCandidatesFromValue(data);
|
|
4469
|
+
const mostFrequent = cursorMostFrequentExistingCwd(candidates);
|
|
4470
|
+
if (mostFrequent) return mostFrequent;
|
|
3897
4471
|
const filePath = cursorFirstPathInObject(data);
|
|
3898
4472
|
return cursorExistingCwdFromPath(filePath, false);
|
|
3899
4473
|
}
|
|
3900
4474
|
|
|
3901
4475
|
function cursorCwdFromMessages(messages) {
|
|
4476
|
+
const allCandidates = [];
|
|
3902
4477
|
for (const message of messages || []) {
|
|
3903
|
-
|
|
3904
|
-
for (const candidate of metadataPaths) {
|
|
3905
|
-
const cwd = cursorExistingCwdFromPath(candidate, false);
|
|
3906
|
-
if (cwd) return cwd;
|
|
3907
|
-
}
|
|
4478
|
+
allCandidates.push(...cursorPathCandidatesFromValue(message?.metadata));
|
|
3908
4479
|
}
|
|
3909
4480
|
const text = (messages || []).map((message) => message?.content || "").join("\n");
|
|
3910
|
-
|
|
4481
|
+
allCandidates.push(...cursorPathCandidatesFromValue(text));
|
|
4482
|
+
const mostFrequent = cursorMostFrequentExistingCwd(allCandidates);
|
|
4483
|
+
if (mostFrequent) return mostFrequent;
|
|
4484
|
+
return cursorCwdFromTerminalPromptText(text);
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
function cursorMostFrequentExistingCwd(candidates) {
|
|
4488
|
+
if (!candidates || !candidates.length) return "";
|
|
4489
|
+
const counts = new Map();
|
|
4490
|
+
const order = [];
|
|
4491
|
+
for (const candidate of candidates) {
|
|
3911
4492
|
const cwd = cursorExistingCwdFromPath(candidate, false);
|
|
3912
|
-
if (cwd)
|
|
4493
|
+
if (!cwd) continue;
|
|
4494
|
+
if (!counts.has(cwd)) order.push(cwd);
|
|
4495
|
+
counts.set(cwd, (counts.get(cwd) || 0) + 1);
|
|
3913
4496
|
}
|
|
3914
|
-
|
|
4497
|
+
if (!counts.size) return "";
|
|
4498
|
+
let bestCwd = "";
|
|
4499
|
+
let bestCount = -1;
|
|
4500
|
+
for (const cwd of order) {
|
|
4501
|
+
const count = counts.get(cwd) || 0;
|
|
4502
|
+
if (count > bestCount) {
|
|
4503
|
+
bestCount = count;
|
|
4504
|
+
bestCwd = cwd;
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
return bestCwd;
|
|
3915
4508
|
}
|
|
3916
4509
|
|
|
3917
4510
|
function cursorPathCandidatesFromValue(value, depth = 0, candidates = []) {
|
|
@@ -4020,15 +4613,29 @@ function cursorNormalizePathCandidate(value) {
|
|
|
4020
4613
|
function cursorAttributionCwd(value) {
|
|
4021
4614
|
const candidate = cursorNormalizePathCandidate(value);
|
|
4022
4615
|
if (!candidate || !path.isAbsolute(candidate)) return "";
|
|
4023
|
-
|
|
4616
|
+
if (cursorIsSystemRootPath(candidate)) return "";
|
|
4617
|
+
const climbed = cursorClimbOutOfDependencyDirs(candidate);
|
|
4618
|
+
const existing = cursorExistingCwdFromPath(climbed, true);
|
|
4024
4619
|
if (existing) return existing;
|
|
4025
4620
|
const appSupportCursorWorkspace = `${path.sep}Application Support${path.sep}Cursor${path.sep}Workspaces${path.sep}`;
|
|
4026
|
-
if (
|
|
4621
|
+
if (climbed.includes(appSupportCursorWorkspace)) return "";
|
|
4622
|
+
return climbed;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
function cursorClimbOutOfDependencyDirs(candidate) {
|
|
4626
|
+
const segments = candidate.split(path.sep);
|
|
4627
|
+
// Strip trailing path beyond the first node_modules / .pnpm / vendor segment
|
|
4628
|
+
// so workspace folders that point inside a dependency resolve to the project root.
|
|
4629
|
+
for (const marker of ["node_modules", ".pnpm", "bower_components", "vendor"]) {
|
|
4630
|
+
const index = segments.indexOf(marker);
|
|
4631
|
+
if (index > 0) return segments.slice(0, index).join(path.sep) || candidate;
|
|
4632
|
+
}
|
|
4027
4633
|
return candidate;
|
|
4028
4634
|
}
|
|
4029
4635
|
|
|
4030
4636
|
function cursorExistingCwdFromPath(candidate, assumeDirectory = false) {
|
|
4031
4637
|
if (!candidate || !path.isAbsolute(candidate)) return "";
|
|
4638
|
+
if (cursorIsSystemRootPath(candidate)) return "";
|
|
4032
4639
|
const stat = safeStat(candidate);
|
|
4033
4640
|
let start = "";
|
|
4034
4641
|
if (stat?.isDirectory()) start = candidate;
|
|
@@ -4042,13 +4649,34 @@ function cursorExistingCwdFromPath(candidate, assumeDirectory = false) {
|
|
|
4042
4649
|
function cursorNearestProjectDir(start) {
|
|
4043
4650
|
let current = path.resolve(start);
|
|
4044
4651
|
for (;;) {
|
|
4045
|
-
if (
|
|
4652
|
+
if (cursorIsSystemRootPath(current)) return "";
|
|
4653
|
+
const insideDependency = cursorPathInsideDependencyDir(current);
|
|
4654
|
+
const hasMarker = fs.existsSync(path.join(current, ".git")) || fs.existsSync(path.join(current, "package.json"));
|
|
4655
|
+
if (hasMarker && !insideDependency) return current;
|
|
4046
4656
|
const parent = path.dirname(current);
|
|
4047
|
-
if (parent === current) return fs.existsSync(start) ? start : "";
|
|
4657
|
+
if (parent === current) return cursorIsSystemRootPath(start) ? "" : (fs.existsSync(start) ? start : "");
|
|
4048
4658
|
current = parent;
|
|
4049
4659
|
}
|
|
4050
4660
|
}
|
|
4051
4661
|
|
|
4662
|
+
function cursorPathInsideDependencyDir(candidate) {
|
|
4663
|
+
const segments = String(candidate || "").split(path.sep);
|
|
4664
|
+
return ["node_modules", ".pnpm", "bower_components", "vendor"].some((marker) => segments.includes(marker));
|
|
4665
|
+
}
|
|
4666
|
+
|
|
4667
|
+
function cursorIsSystemRootPath(candidate) {
|
|
4668
|
+
const normalized = String(candidate || "").replace(/\/+$/, "") || "/";
|
|
4669
|
+
if (normalized === "/" || normalized === path.parse(normalized).root) return true;
|
|
4670
|
+
const home = os.homedir();
|
|
4671
|
+
if (normalized === home) return true;
|
|
4672
|
+
// Top-level system directories that are never project roots on their own.
|
|
4673
|
+
const blocked = new Set([
|
|
4674
|
+
"/Users", "/home", "/Volumes", "/private", "/tmp", "/var",
|
|
4675
|
+
"/Applications", "/Library", "/System", "/opt", "/etc", "/usr"
|
|
4676
|
+
]);
|
|
4677
|
+
return blocked.has(normalized);
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4052
4680
|
function readSqliteJson(dbPath, query, label = "SQLite store") {
|
|
4053
4681
|
const result = runSqliteJson(dbPath, query);
|
|
4054
4682
|
if (result.ok) return parseSqliteJson(result.stdout);
|
|
@@ -4058,6 +4686,7 @@ function readSqliteJson(dbPath, query, label = "SQLite store") {
|
|
|
4058
4686
|
|
|
4059
4687
|
function runSqliteJson(dbPath, query) {
|
|
4060
4688
|
const result = spawnSync("sqlite3", [dbPath, "-json", query], {
|
|
4689
|
+
argv0: "agentlog-sqlite",
|
|
4061
4690
|
encoding: "utf8",
|
|
4062
4691
|
maxBuffer: 1024 * 1024 * 200,
|
|
4063
4692
|
timeout: SQLITE_QUERY_TIMEOUT_MS
|
|
@@ -4131,9 +4760,13 @@ function extractCursorConversations(data, sourceKey, fallbackTime) {
|
|
|
4131
4760
|
const visit = (node) => {
|
|
4132
4761
|
if (!node || typeof node !== "object") return;
|
|
4133
4762
|
if (Array.isArray(node.bubbles)) {
|
|
4134
|
-
const
|
|
4763
|
+
const containerModel = cursorModel(node);
|
|
4764
|
+
const rawMessages = node.bubbles
|
|
4135
4765
|
.flatMap((bubble, index) => cursorMessagesFromRecord(bubble, sourceKey, offsetTimestamp(fallbackTime, index)))
|
|
4136
4766
|
.filter(Boolean);
|
|
4767
|
+
const messages = containerModel
|
|
4768
|
+
? rawMessages.map((message) => cursorMessageWithFallbackModel(message, containerModel))
|
|
4769
|
+
: rawMessages;
|
|
4137
4770
|
if (messages.length) {
|
|
4138
4771
|
conversations.push({
|
|
4139
4772
|
id: node.tabId || node.composerId || node.id || hashId(JSON.stringify(messages.slice(0, 2))),
|
|
@@ -4297,6 +4930,7 @@ function cursorAiServiceGenerationMessage(entry, fallbackTime, index) {
|
|
|
4297
4930
|
const timestamp = cursorIso(entry.unixMs) || offsetTimestamp(fallbackTime, index);
|
|
4298
4931
|
const role = type === "composer" ? "user" : "assistant";
|
|
4299
4932
|
const content = type === "apply" ? `Applied changes: ${text}` : text;
|
|
4933
|
+
const commandType = cursorNormalizeCommandType(entry.commandType ?? entry.command_type);
|
|
4300
4934
|
return {
|
|
4301
4935
|
role,
|
|
4302
4936
|
content,
|
|
@@ -4305,7 +4939,9 @@ function cursorAiServiceGenerationMessage(entry, fallbackTime, index) {
|
|
|
4305
4939
|
provider: "cursor",
|
|
4306
4940
|
source: "cursor-ai-service-history",
|
|
4307
4941
|
eventType: firstString(type, "aiService.generation"),
|
|
4308
|
-
requestId: firstString(entry.generationUUID, entry.generationId, entry.id) || undefined
|
|
4942
|
+
requestId: firstString(entry.generationUUID, entry.generationId, entry.id) || undefined,
|
|
4943
|
+
commandType: commandType || undefined,
|
|
4944
|
+
model: cursorModel(entry) || undefined
|
|
4309
4945
|
}
|
|
4310
4946
|
};
|
|
4311
4947
|
}
|
|
@@ -4313,6 +4949,7 @@ function cursorAiServiceGenerationMessage(entry, fallbackTime, index) {
|
|
|
4313
4949
|
function cursorAiServicePromptMessage(prompt, fallbackTime, index) {
|
|
4314
4950
|
const text = firstString(prompt?.text, prompt?.prompt, prompt?.value);
|
|
4315
4951
|
if (!text) return null;
|
|
4952
|
+
const commandType = cursorNormalizeCommandType(prompt?.commandType ?? prompt?.command_type);
|
|
4316
4953
|
return {
|
|
4317
4954
|
role: "user",
|
|
4318
4955
|
content: text,
|
|
@@ -4320,11 +4957,31 @@ function cursorAiServicePromptMessage(prompt, fallbackTime, index) {
|
|
|
4320
4957
|
metadata: {
|
|
4321
4958
|
provider: "cursor",
|
|
4322
4959
|
source: "cursor-ai-service-history",
|
|
4323
|
-
eventType: "aiService.prompt"
|
|
4960
|
+
eventType: "aiService.prompt",
|
|
4961
|
+
commandType: commandType || undefined
|
|
4324
4962
|
}
|
|
4325
4963
|
};
|
|
4326
4964
|
}
|
|
4327
4965
|
|
|
4966
|
+
function cursorNormalizeCommandType(value) {
|
|
4967
|
+
if (value == null || value === "") return "";
|
|
4968
|
+
const text = String(value).trim();
|
|
4969
|
+
if (!text) return "";
|
|
4970
|
+
// Cursor stores commandType as either an integer enum or a string. Normalize the
|
|
4971
|
+
// common integer mapping so downstream consumers see human-readable labels.
|
|
4972
|
+
const numeric = Number(text);
|
|
4973
|
+
if (Number.isInteger(numeric)) {
|
|
4974
|
+
switch (numeric) {
|
|
4975
|
+
case 1: return "chat";
|
|
4976
|
+
case 2: return "edit";
|
|
4977
|
+
case 3: return "agent";
|
|
4978
|
+
case 4: return "ask";
|
|
4979
|
+
default: return `command-${numeric}`;
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
return text.toLowerCase();
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4328
4985
|
function cursorMs(...values) {
|
|
4329
4986
|
for (const value of values) {
|
|
4330
4987
|
if (value == null || value === "") continue;
|
|
@@ -4476,43 +5133,184 @@ function cursorStructuredText(value) {
|
|
|
4476
5133
|
|
|
4477
5134
|
function cursorMessageMetadata(record, source) {
|
|
4478
5135
|
const usage = cursorUsage(record);
|
|
5136
|
+
const commandType = cursorNormalizeCommandType(record?.commandType ?? record?.command_type ?? record?.message?.commandType);
|
|
4479
5137
|
return {
|
|
4480
5138
|
provider: "cursor",
|
|
4481
5139
|
source,
|
|
4482
5140
|
bubbleType: String(record?.type || "").trim() || undefined,
|
|
4483
5141
|
eventType: firstString(record?.eventType, record?.event_type, record?.kind, record?.type) || undefined,
|
|
4484
|
-
model:
|
|
5142
|
+
model: cursorModel(record) || undefined,
|
|
4485
5143
|
status: firstString(record?.status, record?.state, record?.phase) || undefined,
|
|
4486
5144
|
requestId: firstString(record?.requestId, record?.request_id, record?.messageId, record?.messageID) || undefined,
|
|
4487
5145
|
composerId: firstString(record?.composerId, record?.composerID, record?.conversationId, record?.conversation_id) || undefined,
|
|
5146
|
+
commandType: commandType || undefined,
|
|
4488
5147
|
usage: usage || undefined
|
|
4489
5148
|
};
|
|
4490
5149
|
}
|
|
4491
5150
|
|
|
5151
|
+
function cursorModel(record) {
|
|
5152
|
+
return firstCursorModel(
|
|
5153
|
+
record?.model,
|
|
5154
|
+
record?.modelId,
|
|
5155
|
+
record?.modelID,
|
|
5156
|
+
record?.modelName,
|
|
5157
|
+
record?.modelSlug,
|
|
5158
|
+
record?.message?.model,
|
|
5159
|
+
record?.message?.modelId,
|
|
5160
|
+
record?.message?.modelName,
|
|
5161
|
+
record?.providerOptions?.cursor?.modelName,
|
|
5162
|
+
record?.providerOptions?.cursor?.modelId,
|
|
5163
|
+
record?.provider_options?.cursor?.modelName,
|
|
5164
|
+
record?.provider_options?.cursor?.modelId,
|
|
5165
|
+
record?.message?.providerOptions?.cursor?.modelName,
|
|
5166
|
+
record?.message?.providerOptions?.cursor?.modelId,
|
|
5167
|
+
cursorModelFromModelConfig(record?.modelConfig),
|
|
5168
|
+
cursorModelFromModelConfig(record?.message?.modelConfig),
|
|
5169
|
+
cursorModelFromParts(record?.content),
|
|
5170
|
+
cursorModelFromParts(record?.message?.content),
|
|
5171
|
+
cursorModelFromParts(record?.parts)
|
|
5172
|
+
);
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
function cursorModelFromModelConfig(config) {
|
|
5176
|
+
if (!config || typeof config !== "object") return "";
|
|
5177
|
+
const direct = firstCursorModel(config.modelName, config.model, config.modelId, config.model_slug, config.modelSlug);
|
|
5178
|
+
if (direct) return direct;
|
|
5179
|
+
const selected = Array.isArray(config.selectedModels) ? config.selectedModels : [];
|
|
5180
|
+
for (const item of selected) {
|
|
5181
|
+
const model = firstCursorModel(item?.modelName, item?.modelId, item?.id, item?.model);
|
|
5182
|
+
if (model) return model;
|
|
5183
|
+
}
|
|
5184
|
+
return "";
|
|
5185
|
+
}
|
|
5186
|
+
|
|
5187
|
+
function cursorModelFromParts(value, depth = 0) {
|
|
5188
|
+
if (!value || depth > 5) return "";
|
|
5189
|
+
if (Array.isArray(value)) {
|
|
5190
|
+
for (const item of value) {
|
|
5191
|
+
const model = cursorModelFromParts(item, depth + 1);
|
|
5192
|
+
if (model) return model;
|
|
5193
|
+
}
|
|
5194
|
+
return "";
|
|
5195
|
+
}
|
|
5196
|
+
if (typeof value !== "object") return "";
|
|
5197
|
+
const direct = firstCursorModel(
|
|
5198
|
+
value.providerOptions?.cursor?.modelName,
|
|
5199
|
+
value.providerOptions?.cursor?.modelId,
|
|
5200
|
+
value.provider_options?.cursor?.modelName,
|
|
5201
|
+
value.provider_options?.cursor?.modelId
|
|
5202
|
+
);
|
|
5203
|
+
if (direct) return direct;
|
|
5204
|
+
const configured = cursorModelFromModelConfig(value.modelConfig);
|
|
5205
|
+
if (configured) return configured;
|
|
5206
|
+
for (const child of Object.values(value)) {
|
|
5207
|
+
if (child && typeof child === "object") {
|
|
5208
|
+
const model = cursorModelFromParts(child, depth + 1);
|
|
5209
|
+
if (model) return model;
|
|
5210
|
+
}
|
|
5211
|
+
}
|
|
5212
|
+
return "";
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
function firstCursorModel(...values) {
|
|
5216
|
+
for (const value of values) {
|
|
5217
|
+
if (typeof value !== "string") continue;
|
|
5218
|
+
const trimmed = value.trim();
|
|
5219
|
+
if (!trimmed || /^default$/i.test(trimmed)) continue;
|
|
5220
|
+
return trimmed;
|
|
5221
|
+
}
|
|
5222
|
+
return "";
|
|
5223
|
+
}
|
|
5224
|
+
|
|
4492
5225
|
function cursorUsage(record) {
|
|
4493
5226
|
const candidates = [
|
|
5227
|
+
record?.tokenCount,
|
|
5228
|
+
record?.token_count,
|
|
4494
5229
|
record?.usage,
|
|
4495
5230
|
record?.tokenUsage,
|
|
4496
5231
|
record?.token_usage,
|
|
4497
5232
|
record?.tokens,
|
|
4498
5233
|
record?.metrics?.usage,
|
|
5234
|
+
record?.message?.tokenCount,
|
|
5235
|
+
record?.message?.token_count,
|
|
4499
5236
|
record?.message?.usage,
|
|
4500
5237
|
record?.message?.tokenUsage
|
|
4501
5238
|
];
|
|
5239
|
+
let input = null;
|
|
5240
|
+
let output = null;
|
|
5241
|
+
let cacheInput = null;
|
|
5242
|
+
let total = null;
|
|
4502
5243
|
for (const item of candidates) {
|
|
4503
5244
|
if (!item || typeof item !== "object") continue;
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
5245
|
+
input = preferredCursorTokenValue(input, numericValue(
|
|
5246
|
+
item.inputTokens,
|
|
5247
|
+
item.input_tokens,
|
|
5248
|
+
item.inputTokenCount,
|
|
5249
|
+
item.input_token_count,
|
|
5250
|
+
item.promptTokens,
|
|
5251
|
+
item.prompt_tokens,
|
|
5252
|
+
item.promptTokenCount,
|
|
5253
|
+
item.prompt_token_count,
|
|
5254
|
+
item.prompt
|
|
5255
|
+
));
|
|
5256
|
+
output = preferredCursorTokenValue(output, numericValue(
|
|
5257
|
+
item.outputTokens,
|
|
5258
|
+
item.output_tokens,
|
|
5259
|
+
item.outputTokenCount,
|
|
5260
|
+
item.output_token_count,
|
|
5261
|
+
item.completionTokens,
|
|
5262
|
+
item.completion_tokens,
|
|
5263
|
+
item.completionTokenCount,
|
|
5264
|
+
item.completion_token_count,
|
|
5265
|
+
item.completion
|
|
5266
|
+
));
|
|
5267
|
+
cacheInput = preferredCursorTokenValue(cacheInput, cursorCacheInputTokens(item));
|
|
5268
|
+
total = preferredCursorTokenValue(total, numericValue(item.totalTokens, item.total_tokens, item.totalTokenCount, item.total_token_count, item.total));
|
|
5269
|
+
}
|
|
5270
|
+
if (![input, output, cacheInput, total].some((value) => value != null && value > 0)) return null;
|
|
5271
|
+
const usage = {};
|
|
5272
|
+
if (input != null) usage.inputTokens = input;
|
|
5273
|
+
if (output != null) usage.outputTokens = output;
|
|
5274
|
+
if (cacheInput != null) usage.cacheInputTokens = cacheInput;
|
|
5275
|
+
if (total != null) usage.totalTokens = total;
|
|
5276
|
+
else if (input != null || output != null) usage.totalTokens = (input || 0) + (output || 0);
|
|
5277
|
+
return usage;
|
|
5278
|
+
}
|
|
5279
|
+
|
|
5280
|
+
function preferredCursorTokenValue(current, next) {
|
|
5281
|
+
if (next == null) return current;
|
|
5282
|
+
if (current == null) return next;
|
|
5283
|
+
if (current <= 0 && next > 0) return next;
|
|
5284
|
+
return current;
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
function cursorCacheInputTokens(item) {
|
|
5288
|
+
const cacheInput = numericValue(item.cacheInputTokens, item.cache_input_tokens);
|
|
5289
|
+
const cacheCreation = numericValue(
|
|
5290
|
+
item.cacheCreationInputTokens,
|
|
5291
|
+
item.cache_creation_input_tokens,
|
|
5292
|
+
item.cacheCreationTokens,
|
|
5293
|
+
item.cache_creation_tokens
|
|
5294
|
+
);
|
|
5295
|
+
const cacheRead = numericValue(
|
|
5296
|
+
item.cacheReadInputTokens,
|
|
5297
|
+
item.cache_read_input_tokens,
|
|
5298
|
+
item.cacheReadTokens,
|
|
5299
|
+
item.cache_read_tokens
|
|
5300
|
+
);
|
|
5301
|
+
const cached = numericValue(
|
|
5302
|
+
item.cachedContentTokenCount,
|
|
5303
|
+
item.cached_content_token_count,
|
|
5304
|
+
item.cachedTokens,
|
|
5305
|
+
item.cached_tokens,
|
|
5306
|
+
item.cacheTokens,
|
|
5307
|
+
item.cache_tokens,
|
|
5308
|
+
item.cached
|
|
5309
|
+
);
|
|
5310
|
+
const total = [cacheInput, cacheCreation, cacheRead, cached]
|
|
5311
|
+
.filter((value) => value != null && value > 0)
|
|
5312
|
+
.reduce((sum, value) => sum + value, 0);
|
|
5313
|
+
return total > 0 ? total : null;
|
|
4516
5314
|
}
|
|
4517
5315
|
|
|
4518
5316
|
function cursorToolCallsFromRecord(record) {
|
|
@@ -4622,7 +5420,7 @@ function cursorToolName(node, keyToken, type) {
|
|
|
4622
5420
|
if (node.command || node.cmd || node.terminalCommand) return "run_terminal_cmd";
|
|
4623
5421
|
if (node.diff || node.patch || node.old_string || node.new_string || node.oldText || node.newText) return "edit";
|
|
4624
5422
|
if (node.query || /search|grep/.test(type || keyToken)) return "search";
|
|
4625
|
-
if (node.path || node.file || node.uri) {
|
|
5423
|
+
if (node.path || node.file || node.filename || node.filePath || node.fsPath || node.uri) {
|
|
4626
5424
|
if (/read|open|view/.test(type || keyToken)) return "read_file";
|
|
4627
5425
|
if (/write|edit|diff|patch/.test(type || keyToken)) return "edit_file";
|
|
4628
5426
|
}
|
|
@@ -4634,16 +5432,22 @@ function cursorToolArguments(node, name, keyToken, type) {
|
|
|
4634
5432
|
const value = node.input ?? node.args ?? node.arguments ?? node.params ?? node.parameters ?? node.function?.arguments ?? action.input;
|
|
4635
5433
|
if (value != null) return value;
|
|
4636
5434
|
if (node.command || node.cmd || node.terminalCommand) return { command: node.command || node.cmd || node.terminalCommand };
|
|
4637
|
-
if (node.diff || node.patch) return { diff: node.diff || node.patch, path: firstString(node.path, node.file, node.filename) || undefined };
|
|
5435
|
+
if (node.diff || node.patch) return { diff: node.diff || node.patch, path: firstString(node.path, node.file, node.filename, node.filePath, node.fsPath) || undefined };
|
|
4638
5436
|
if (node.old_string || node.new_string || node.oldText || node.newText) {
|
|
4639
5437
|
return {
|
|
4640
|
-
path: firstString(node.path, node.file, node.filename) || undefined,
|
|
5438
|
+
path: firstString(node.path, node.file, node.filename, node.filePath, node.fsPath) || undefined,
|
|
4641
5439
|
old_string: firstString(node.old_string, node.oldText),
|
|
4642
5440
|
new_string: firstString(node.new_string, node.newText)
|
|
4643
5441
|
};
|
|
4644
5442
|
}
|
|
4645
5443
|
if (node.query) return { query: node.query };
|
|
4646
|
-
if (node.path || node.file || node.
|
|
5444
|
+
if (node.path || node.file || node.filename || node.filePath || node.fsPath || node.uri) {
|
|
5445
|
+
return {
|
|
5446
|
+
path: firstString(node.path, node.file, node.filename, node.filePath, node.fsPath, cursorUriPath(node.uri)),
|
|
5447
|
+
instruction: firstString(node.instruction, node.text, node.content) || undefined,
|
|
5448
|
+
name: name || type || keyToken
|
|
5449
|
+
};
|
|
5450
|
+
}
|
|
4647
5451
|
return null;
|
|
4648
5452
|
}
|
|
4649
5453
|
|
|
@@ -4653,7 +5457,11 @@ function cursorDiffToolCalls(node) {
|
|
|
4653
5457
|
.concat(Array.isArray(node.diffs) ? node.diffs : [])
|
|
4654
5458
|
.concat(Array.isArray(node.fileDiffs) ? node.fileDiffs : [])
|
|
4655
5459
|
.concat(Array.isArray(node.file_diffs) ? node.file_diffs : [])
|
|
4656
|
-
.concat(Array.isArray(node.edits) ? node.edits : [])
|
|
5460
|
+
.concat(Array.isArray(node.edits) ? node.edits : [])
|
|
5461
|
+
.concat(Array.isArray(node.suggestedCodeBlocks) ? node.suggestedCodeBlocks : [])
|
|
5462
|
+
.concat(Array.isArray(node.suggested_code_blocks) ? node.suggested_code_blocks : [])
|
|
5463
|
+
.concat(Array.isArray(node.diffHistories) ? node.diffHistories : [])
|
|
5464
|
+
.concat(Array.isArray(node.diff_histories) ? node.diff_histories : []);
|
|
4657
5465
|
return diffs.map((diff) => cursorNormalizeToolCall(diff, "fileDiffs")).filter(Boolean);
|
|
4658
5466
|
}
|
|
4659
5467
|
|
|
@@ -4703,9 +5511,9 @@ function cursorLooksLikeToolResult(node, keyToken, type) {
|
|
|
4703
5511
|
|
|
4704
5512
|
function cursorToolTarget(args, node) {
|
|
4705
5513
|
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
4706
|
-
return firstString(args.path, args.file, args.filename, args.target_file, args.targetFile, args.uri);
|
|
5514
|
+
return firstString(args.path, args.file, args.filename, args.filePath, args.fsPath, args.target_file, args.targetFile, args.uri);
|
|
4707
5515
|
}
|
|
4708
|
-
return firstString(node?.path, node?.file, node?.filename, node?.target_file, node?.targetFile, node?.uri);
|
|
5516
|
+
return firstString(node?.path, node?.file, node?.filename, node?.filePath, node?.fsPath, node?.target_file, node?.targetFile, node?.uri);
|
|
4709
5517
|
}
|
|
4710
5518
|
|
|
4711
5519
|
function dedupeCursorToolCalls(calls) {
|
|
@@ -4761,8 +5569,12 @@ function cursorBubbleContext(bubble) {
|
|
|
4761
5569
|
if (value) parts.push(`Terminal selection:\n${value}`);
|
|
4762
5570
|
}
|
|
4763
5571
|
}
|
|
4764
|
-
if (Array.isArray(bubble.fileSelections)) {
|
|
4765
|
-
const files =
|
|
5572
|
+
if (Array.isArray(bubble.fileSelections) || Array.isArray(bubble.selections)) {
|
|
5573
|
+
const files = []
|
|
5574
|
+
.concat(Array.isArray(bubble.fileSelections) ? bubble.fileSelections : [])
|
|
5575
|
+
.concat(Array.isArray(bubble.selections) ? bubble.selections : [])
|
|
5576
|
+
.map((selection) => cursorUriPath(selection?.uri) || cursorNormalizePathCandidate(firstString(selection?.fsPath, selection?.path, selection?.filePath)))
|
|
5577
|
+
.filter(Boolean);
|
|
4766
5578
|
if (files.length) parts.push(`Files:\n${files.join("\n")}`);
|
|
4767
5579
|
}
|
|
4768
5580
|
const nestedPaths = [
|
|
@@ -5045,17 +5857,106 @@ function dedupeCursorSessions(sessions) {
|
|
|
5045
5857
|
seen.add(key);
|
|
5046
5858
|
exact.push(session);
|
|
5047
5859
|
}
|
|
5048
|
-
|
|
5860
|
+
cursorPropagateCwdFromComposerSiblings(exact);
|
|
5861
|
+
const composerDeduped = cursorDedupeByComposerId(exact);
|
|
5862
|
+
const contentDeduped = cursorDedupeSessionsByContent(composerDeduped);
|
|
5049
5863
|
cursorPropagateCwdFromFallbackSessions(contentDeduped);
|
|
5050
5864
|
return contentDeduped.filter(
|
|
5051
5865
|
(session) =>
|
|
5052
5866
|
!cursorSessionExactDuplicateInBetterSession(session, contentDeduped) &&
|
|
5053
5867
|
!cursorSessionNearDuplicateInBetterSession(session, contentDeduped) &&
|
|
5054
5868
|
!cursorSessionCoveredByBetterSessions(session, contentDeduped) &&
|
|
5055
|
-
!cursorSessionContainedInBetterSession(session, contentDeduped)
|
|
5869
|
+
!cursorSessionContainedInBetterSession(session, contentDeduped) &&
|
|
5870
|
+
!cursorSessionIsEmptyApplyStub(session, contentDeduped)
|
|
5056
5871
|
);
|
|
5057
5872
|
}
|
|
5058
5873
|
|
|
5874
|
+
function cursorPropagateCwdFromComposerSiblings(sessions) {
|
|
5875
|
+
const cwdByComposerId = new Map();
|
|
5876
|
+
for (const session of sessions) {
|
|
5877
|
+
const composerId = cursorSessionComposerId(session);
|
|
5878
|
+
if (!composerId || !session.cwd) continue;
|
|
5879
|
+
const existing = cwdByComposerId.get(composerId);
|
|
5880
|
+
if (!existing || cursorSourceRank(session) > existing.rank) {
|
|
5881
|
+
cwdByComposerId.set(composerId, { cwd: session.cwd, rank: cursorSourceRank(session) });
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
for (const target of sessions) {
|
|
5885
|
+
if (!target || target.cwd) continue;
|
|
5886
|
+
const composerId = cursorSessionComposerId(target);
|
|
5887
|
+
if (!composerId) continue;
|
|
5888
|
+
const sibling = cwdByComposerId.get(composerId);
|
|
5889
|
+
if (!sibling?.cwd) continue;
|
|
5890
|
+
target.cwd = sibling.cwd;
|
|
5891
|
+
if (target.scopeCanonical === "cursor/uncategorized") target.scopeCanonical = "";
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
|
|
5895
|
+
function cursorDedupeByComposerId(sessions) {
|
|
5896
|
+
const byComposerId = new Map();
|
|
5897
|
+
const result = [];
|
|
5898
|
+
for (const session of sessions) {
|
|
5899
|
+
const composerId = cursorSessionComposerId(session);
|
|
5900
|
+
if (!composerId) {
|
|
5901
|
+
result.push(session);
|
|
5902
|
+
continue;
|
|
5903
|
+
}
|
|
5904
|
+
const existing = byComposerId.get(composerId);
|
|
5905
|
+
if (!existing) {
|
|
5906
|
+
byComposerId.set(composerId, session);
|
|
5907
|
+
result.push(session);
|
|
5908
|
+
continue;
|
|
5909
|
+
}
|
|
5910
|
+
if (cursorPreferSession(session, existing) === session) {
|
|
5911
|
+
const index = result.indexOf(existing);
|
|
5912
|
+
if (index >= 0) result[index] = session;
|
|
5913
|
+
byComposerId.set(composerId, session);
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
return result;
|
|
5917
|
+
}
|
|
5918
|
+
|
|
5919
|
+
function cursorSessionComposerId(session) {
|
|
5920
|
+
if (!session) return "";
|
|
5921
|
+
const direct = firstString(session.composerId, session.id);
|
|
5922
|
+
if (cursorLooksLikeComposerId(direct)) return String(direct).trim();
|
|
5923
|
+
const fromPath = cursorComposerIdFromSourcePath(session.sourcePath || "");
|
|
5924
|
+
if (fromPath) return fromPath;
|
|
5925
|
+
return "";
|
|
5926
|
+
}
|
|
5927
|
+
|
|
5928
|
+
function cursorComposerIdFromSourcePath(sourcePath) {
|
|
5929
|
+
const text = String(sourcePath || "");
|
|
5930
|
+
if (!text) return "";
|
|
5931
|
+
const matches = text.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi);
|
|
5932
|
+
if (!matches) return "";
|
|
5933
|
+
// Prefer the last UUID in the path — that's the composer/transcript id, not the workspace hash.
|
|
5934
|
+
return matches[matches.length - 1].toLowerCase();
|
|
5935
|
+
}
|
|
5936
|
+
|
|
5937
|
+
function cursorSessionIsEmptyApplyStub(session, sessions) {
|
|
5938
|
+
if (!session || !cursorLikelyCursorPromptHistoryFallback(session)) return false;
|
|
5939
|
+
if (!cursorSessionAssistantMessagesAreOnlyApplyStubs(session)) return false;
|
|
5940
|
+
const userProbes = cursorDedupeUserProbes(session);
|
|
5941
|
+
const rank = cursorSourceRank(session);
|
|
5942
|
+
for (const other of sessions) {
|
|
5943
|
+
if (other === session) continue;
|
|
5944
|
+
if (cursorSourceRank(other) <= rank) continue;
|
|
5945
|
+
if (!cursorSessionsComparable(session, other)) continue;
|
|
5946
|
+
if (!userProbes.length) return true;
|
|
5947
|
+
const text = cursorSessionSearchText(other);
|
|
5948
|
+
if (userProbes.some((probe) => text.includes(probe))) return true;
|
|
5949
|
+
}
|
|
5950
|
+
return false;
|
|
5951
|
+
}
|
|
5952
|
+
|
|
5953
|
+
function cursorSessionAssistantMessagesAreOnlyApplyStubs(session) {
|
|
5954
|
+
const messages = session?.messages || [];
|
|
5955
|
+
const assistantMessages = messages.filter((message) => message.role === "assistant");
|
|
5956
|
+
if (!assistantMessages.length) return true;
|
|
5957
|
+
return assistantMessages.every((message) => /^applied changes:/i.test(String(message.content || "").trim()));
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5059
5960
|
function cursorPropagateCwdFromFallbackSessions(sessions) {
|
|
5060
5961
|
for (const target of sessions) {
|
|
5061
5962
|
if (!target || target.cwd) continue;
|
|
@@ -5261,6 +6162,9 @@ function cursorSessionSearchText(session) {
|
|
|
5261
6162
|
}
|
|
5262
6163
|
|
|
5263
6164
|
function cursorSessionsComparable(left, right) {
|
|
6165
|
+
const leftId = cursorSessionComposerId(left);
|
|
6166
|
+
const rightId = cursorSessionComposerId(right);
|
|
6167
|
+
if (leftId && rightId && leftId === rightId) return true;
|
|
5264
6168
|
if (left.cwd && right.cwd && left.cwd !== right.cwd) return false;
|
|
5265
6169
|
return true;
|
|
5266
6170
|
}
|
|
@@ -5286,7 +6190,11 @@ function pruneCursorArchivedDuplicates(env = process.env, state = null, options
|
|
|
5286
6190
|
const archived = listSessions(env)
|
|
5287
6191
|
.filter((session) => session.provider === "cursor")
|
|
5288
6192
|
.filter((session) => !sourcePaths || sourcePaths.has(cursorSessionSourcePath(session)))
|
|
5289
|
-
.map((session) => cursorSessionWithInferredCwd({
|
|
6193
|
+
.map((session) => cursorSessionWithInferredCwd({
|
|
6194
|
+
...session,
|
|
6195
|
+
id: session.composerId || cursorComposerIdFromSourcePath(session.sourcePath || ""),
|
|
6196
|
+
messages: readTranscript(session.transcriptPath)
|
|
6197
|
+
}));
|
|
5290
6198
|
const kept = new Set(dedupeCursorSessions(archived).map((session) => session.sessionId));
|
|
5291
6199
|
let pruned = 0;
|
|
5292
6200
|
for (const session of archived) {
|
|
@@ -5630,14 +6538,30 @@ function clineTitle(messages) {
|
|
|
5630
6538
|
}
|
|
5631
6539
|
|
|
5632
6540
|
function readOpenCodeSessions(env = process.env, options = {}) {
|
|
6541
|
+
const dbs = openCodeDatabaseFiles(env);
|
|
5633
6542
|
const roots = openCodeStorageRoots(env);
|
|
5634
6543
|
const files = roots.flatMap((root) => openCodeSessionFiles(root).map((file) => ({ root, file })));
|
|
5635
6544
|
const sessions = [];
|
|
6545
|
+
reportDiscoveryProgress(options, { current: 0, total: dbs.length, message: "reading OpenCode SQLite stores" });
|
|
6546
|
+
for (let index = 0; index < dbs.length; index++) {
|
|
6547
|
+
const dbSessions = readOpenCodeSqliteSessionsFromDb(dbs[index]);
|
|
6548
|
+
sessions.push(...dbSessions);
|
|
6549
|
+
reportDiscoveryProgress(options, {
|
|
6550
|
+
current: index + 1,
|
|
6551
|
+
total: dbs.length,
|
|
6552
|
+
message: `${dbSessions.length} SQLite sessions`,
|
|
6553
|
+
path: dbs[index]
|
|
6554
|
+
});
|
|
6555
|
+
}
|
|
6556
|
+
const seenSessionIds = new Set();
|
|
5636
6557
|
reportDiscoveryProgress(options, { current: 0, total: files.length, message: "reading OpenCode storage" });
|
|
5637
6558
|
for (let index = 0; index < files.length; index++) {
|
|
5638
6559
|
const item = files[index];
|
|
5639
6560
|
const session = parseOpenCodeSessionFile(item.file, item.root);
|
|
5640
|
-
if (session)
|
|
6561
|
+
if (session) {
|
|
6562
|
+
sessions.push(session);
|
|
6563
|
+
seenSessionIds.add(session.sessionId.replace(/^opencode-/, ""));
|
|
6564
|
+
}
|
|
5641
6565
|
reportDiscoveryProgress(options, {
|
|
5642
6566
|
current: index + 1,
|
|
5643
6567
|
total: files.length,
|
|
@@ -5645,28 +6569,69 @@ function readOpenCodeSessions(env = process.env, options = {}) {
|
|
|
5645
6569
|
path: item.file
|
|
5646
6570
|
});
|
|
5647
6571
|
}
|
|
6572
|
+
for (const root of roots) {
|
|
6573
|
+
for (const sessionId of openCodeMessageSessionIds(root)) {
|
|
6574
|
+
if (seenSessionIds.has(sessionId)) continue;
|
|
6575
|
+
const session = parseOpenCodeMessageOnlySession(root, sessionId);
|
|
6576
|
+
if (session) {
|
|
6577
|
+
sessions.push(session);
|
|
6578
|
+
seenSessionIds.add(sessionId);
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
5648
6582
|
return dedupeStructuredSessions(sessions, "opencode");
|
|
5649
6583
|
}
|
|
5650
6584
|
|
|
6585
|
+
function openCodeDataRoots(env = process.env) {
|
|
6586
|
+
const configured = env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR;
|
|
6587
|
+
if (configured) return existingUniquePaths([configured]);
|
|
6588
|
+
const home = env.HOME || os.homedir();
|
|
6589
|
+
const roots = [
|
|
6590
|
+
path.join(home, ".local", "share", "opencode"),
|
|
6591
|
+
path.join(home, "Library", "Application Support", "opencode"),
|
|
6592
|
+
path.join(home, ".local", "share", "ai.opencode.app"),
|
|
6593
|
+
path.join(home, "Library", "Application Support", "ai.opencode.app")
|
|
6594
|
+
];
|
|
6595
|
+
const appData = env.APPDATA || env.LOCALAPPDATA || env.LocalAppData;
|
|
6596
|
+
if (appData) {
|
|
6597
|
+
roots.push(path.join(appData, "opencode"));
|
|
6598
|
+
roots.push(path.join(appData, "ai.opencode.app"));
|
|
6599
|
+
}
|
|
6600
|
+
return existingUniquePaths(roots);
|
|
6601
|
+
}
|
|
6602
|
+
|
|
5651
6603
|
function openCodeStorageRoots(env = process.env) {
|
|
5652
6604
|
const explicit = envPathList(env.AGENTLOG_OPENCODE_STORAGE_ROOTS || env.AGENTLOG_OPENCODE_STORAGE_DIR);
|
|
5653
6605
|
if (explicit.length) return existingUniquePaths(explicit);
|
|
5654
|
-
const
|
|
5655
|
-
const roots = [
|
|
5656
|
-
const
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
entries =
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
6606
|
+
const dataRoots = openCodeDataRoots(env);
|
|
6607
|
+
const roots = [];
|
|
6608
|
+
for (const dataRoot of dataRoots) {
|
|
6609
|
+
roots.push(path.join(dataRoot, "storage"));
|
|
6610
|
+
const projectRoot = path.join(dataRoot, "project");
|
|
6611
|
+
let entries = [];
|
|
6612
|
+
try {
|
|
6613
|
+
entries = fs.readdirSync(projectRoot, { withFileTypes: true });
|
|
6614
|
+
} catch {
|
|
6615
|
+
entries = [];
|
|
6616
|
+
}
|
|
6617
|
+
for (const entry of entries) {
|
|
6618
|
+
if (entry.isDirectory()) roots.push(path.join(projectRoot, entry.name, "storage"));
|
|
6619
|
+
}
|
|
6620
|
+
if (path.basename(dataRoot) === "storage") roots.push(dataRoot);
|
|
5665
6621
|
}
|
|
5666
|
-
if (path.basename(dataRoot) === "storage") roots.push(dataRoot);
|
|
5667
6622
|
return existingUniquePaths(roots);
|
|
5668
6623
|
}
|
|
5669
6624
|
|
|
6625
|
+
function openCodeDatabaseFiles(env = process.env) {
|
|
6626
|
+
const explicit = envPathList(env.AGENTLOG_OPENCODE_DB || env.AGENTLOG_OPENCODE_DATABASE || env.OPENCODE_DB);
|
|
6627
|
+
if (explicit.length) return existingUniquePaths(explicit);
|
|
6628
|
+
if ((env.AGENTLOG_OPENCODE_STORAGE_ROOTS || env.AGENTLOG_OPENCODE_STORAGE_DIR) && !(env.AGENTLOG_OPENCODE_DATA_DIR || env.OPENCODE_DATA_DIR)) return [];
|
|
6629
|
+
return existingUniquePaths(openCodeDataRoots(env).flatMap((root) => [
|
|
6630
|
+
path.join(root, "opencode.db"),
|
|
6631
|
+
path.join(root, "storage", "opencode.db")
|
|
6632
|
+
]));
|
|
6633
|
+
}
|
|
6634
|
+
|
|
5670
6635
|
function openCodeSessionFiles(root) {
|
|
5671
6636
|
const sessionRoot = path.join(root, "session");
|
|
5672
6637
|
const files = [];
|
|
@@ -5676,6 +6641,216 @@ function openCodeSessionFiles(root) {
|
|
|
5676
6641
|
return files.sort((a, b) => a.localeCompare(b));
|
|
5677
6642
|
}
|
|
5678
6643
|
|
|
6644
|
+
function openCodeMessageSessionIds(root) {
|
|
6645
|
+
const messageRoot = path.join(root, "message");
|
|
6646
|
+
let entries = [];
|
|
6647
|
+
try {
|
|
6648
|
+
entries = fs.readdirSync(messageRoot, { withFileTypes: true });
|
|
6649
|
+
} catch {
|
|
6650
|
+
return [];
|
|
6651
|
+
}
|
|
6652
|
+
return entries
|
|
6653
|
+
.filter((entry) => entry.isDirectory())
|
|
6654
|
+
.map((entry) => entry.name)
|
|
6655
|
+
.filter(Boolean)
|
|
6656
|
+
.sort((a, b) => a.localeCompare(b));
|
|
6657
|
+
}
|
|
6658
|
+
|
|
6659
|
+
function readOpenCodeSqliteSessionsFromDb(dbPath) {
|
|
6660
|
+
if (!safeStat(dbPath)) return [];
|
|
6661
|
+
if (!sqliteTableExists(dbPath, "session") || !sqliteTableExists(dbPath, "message") || !sqliteTableExists(dbPath, "part")) return [];
|
|
6662
|
+
const sessionRows = readOpenCodeSqliteSessionRows(dbPath);
|
|
6663
|
+
const messageRows = readOpenCodeSqliteMessageRows(dbPath);
|
|
6664
|
+
const partRows = readOpenCodeSqlitePartRows(dbPath);
|
|
6665
|
+
const messagesBySession = groupRowsBy(messageRows, "session_id");
|
|
6666
|
+
const partsByMessage = groupRowsBy(partRows, "message_id");
|
|
6667
|
+
const storageRoot = path.join(path.dirname(dbPath), "storage");
|
|
6668
|
+
const sessions = [];
|
|
6669
|
+
for (const row of sessionRows) {
|
|
6670
|
+
const rows = messagesBySession.get(row.id) || [];
|
|
6671
|
+
const messages = stampMessages(
|
|
6672
|
+
dedupeAdjacentMessages(rows.flatMap((messageRow, index) => openCodeSqliteMessagesFromRow(messageRow, partsByMessage.get(messageRow.id) || [], index)))
|
|
6673
|
+
.sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
|
|
6674
|
+
"opencode-sqlite-history"
|
|
6675
|
+
);
|
|
6676
|
+
const diffFile = path.join(storageRoot, "session_diff", `${row.id}.json`);
|
|
6677
|
+
const diffMessage = openCodeDiffMessage(diffFile, messages[messages.length - 1]?.timestamp || toIso(row.time_updated || row.time_created));
|
|
6678
|
+
const finalMessages = diffMessage ? messages.concat(stampMessages([diffMessage], "opencode-sqlite-history")) : messages;
|
|
6679
|
+
if (!finalMessages.length) continue;
|
|
6680
|
+
const sourceFiles = [dbPath, safeStat(diffFile) ? diffFile : ""].filter(Boolean);
|
|
6681
|
+
const startedAt = toIso(row.time_created) || finalMessages[0]?.timestamp || new Date(safeStat(dbPath)?.mtimeMs || Date.now()).toISOString();
|
|
6682
|
+
const endedAt = toIso(row.time_updated) || finalMessages[finalMessages.length - 1]?.timestamp || startedAt;
|
|
6683
|
+
const cwd = firstString(row.directory, row.path, row.project_worktree, openCodeCwdFromMessages(finalMessages));
|
|
6684
|
+
sessions.push({
|
|
6685
|
+
sessionId: `opencode-${row.id}`,
|
|
6686
|
+
title: firstString(row.title, row.slug, clineTitle(finalMessages), row.id),
|
|
6687
|
+
cwd,
|
|
6688
|
+
startedAt,
|
|
6689
|
+
endedAt,
|
|
6690
|
+
messages: finalMessages,
|
|
6691
|
+
sourcePath: `${dbPath}#${row.id}`,
|
|
6692
|
+
sourceFiles,
|
|
6693
|
+
sourceType: "opencode-sqlite-history",
|
|
6694
|
+
fingerprint: openCodeSqliteSessionFingerprint(dbPath, row, rows, sourceFiles),
|
|
6695
|
+
detailKey: "sqliteSessions",
|
|
6696
|
+
sessionSummary: {
|
|
6697
|
+
projectId: row.project_id || undefined,
|
|
6698
|
+
parentId: row.parent_id || undefined,
|
|
6699
|
+
workspaceId: row.workspace_id || undefined,
|
|
6700
|
+
slug: row.slug || undefined,
|
|
6701
|
+
version: row.version || undefined,
|
|
6702
|
+
agent: row.agent || undefined,
|
|
6703
|
+
model: row.model || undefined,
|
|
6704
|
+
projectName: row.project_name || undefined,
|
|
6705
|
+
projectWorktree: row.project_worktree || undefined
|
|
6706
|
+
}
|
|
6707
|
+
});
|
|
6708
|
+
}
|
|
6709
|
+
return sessions;
|
|
6710
|
+
}
|
|
6711
|
+
|
|
6712
|
+
function readOpenCodeSqliteSessionRows(dbPath) {
|
|
6713
|
+
const sessionColumns = sqliteTableColumns(dbPath, "session");
|
|
6714
|
+
if (!sessionColumns.has("id")) return [];
|
|
6715
|
+
const projectColumns = sqliteTableExists(dbPath, "project") ? sqliteTableColumns(dbPath, "project") : new Set();
|
|
6716
|
+
const canJoinProject = sessionColumns.has("project_id") && projectColumns.has("id");
|
|
6717
|
+
const selects = [
|
|
6718
|
+
"s.id",
|
|
6719
|
+
sqliteSelectMaybe(sessionColumns, "s", "project_id"),
|
|
6720
|
+
sqliteSelectMaybe(sessionColumns, "s", "parent_id"),
|
|
6721
|
+
sqliteSelectMaybe(sessionColumns, "s", "slug"),
|
|
6722
|
+
sqliteSelectMaybe(sessionColumns, "s", "directory"),
|
|
6723
|
+
sqliteSelectMaybe(sessionColumns, "s", "title"),
|
|
6724
|
+
sqliteSelectMaybe(sessionColumns, "s", "version"),
|
|
6725
|
+
sqliteSelectMaybe(sessionColumns, "s", "share_url"),
|
|
6726
|
+
sqliteSelectMaybe(sessionColumns, "s", "time_created"),
|
|
6727
|
+
sqliteSelectMaybe(sessionColumns, "s", "time_updated"),
|
|
6728
|
+
sqliteSelectMaybe(sessionColumns, "s", "time_archived"),
|
|
6729
|
+
sqliteSelectMaybe(sessionColumns, "s", "workspace_id"),
|
|
6730
|
+
sqliteSelectMaybe(sessionColumns, "s", "path"),
|
|
6731
|
+
sqliteSelectMaybe(sessionColumns, "s", "agent"),
|
|
6732
|
+
sqliteSelectMaybe(sessionColumns, "s", "model"),
|
|
6733
|
+
canJoinProject && projectColumns.has("worktree") ? "p.worktree as project_worktree" : "null as project_worktree",
|
|
6734
|
+
canJoinProject && projectColumns.has("name") ? "p.name as project_name" : "null as project_name"
|
|
6735
|
+
];
|
|
6736
|
+
const queryParts = [`select ${selects.join(", ")}`, "from session s"];
|
|
6737
|
+
if (canJoinProject) queryParts.push("left join project p on p.id = s.project_id");
|
|
6738
|
+
if (sessionColumns.has("time_archived")) queryParts.push("where coalesce(s.time_archived, 0) = 0");
|
|
6739
|
+
const orderColumns = [];
|
|
6740
|
+
if (sessionColumns.has("time_updated")) orderColumns.push("s.time_updated desc");
|
|
6741
|
+
if (sessionColumns.has("time_created")) orderColumns.push("s.time_created desc");
|
|
6742
|
+
orderColumns.push("s.id");
|
|
6743
|
+
queryParts.push(`order by ${orderColumns.join(", ")}`);
|
|
6744
|
+
return readSqliteJson(dbPath, queryParts.join(" "), "OpenCode SQLite sessions");
|
|
6745
|
+
}
|
|
6746
|
+
|
|
6747
|
+
function readOpenCodeSqliteMessageRows(dbPath) {
|
|
6748
|
+
const columns = sqliteTableColumns(dbPath, "message");
|
|
6749
|
+
if (!columns.has("id") || !columns.has("session_id")) return [];
|
|
6750
|
+
const selects = [
|
|
6751
|
+
"id",
|
|
6752
|
+
"session_id",
|
|
6753
|
+
sqliteSelectMaybe(columns, "message", "time_created"),
|
|
6754
|
+
sqliteSelectMaybe(columns, "message", "time_updated"),
|
|
6755
|
+
sqliteSelectMaybe(columns, "message", "data")
|
|
6756
|
+
];
|
|
6757
|
+
const orderColumns = ["session_id"];
|
|
6758
|
+
if (columns.has("time_created")) orderColumns.push("time_created");
|
|
6759
|
+
orderColumns.push("id");
|
|
6760
|
+
return readSqliteJson(dbPath, `select ${selects.join(", ")} from message order by ${orderColumns.join(", ")}`, "OpenCode SQLite messages");
|
|
6761
|
+
}
|
|
6762
|
+
|
|
6763
|
+
function readOpenCodeSqlitePartRows(dbPath) {
|
|
6764
|
+
const columns = sqliteTableColumns(dbPath, "part");
|
|
6765
|
+
if (!columns.has("id") || !columns.has("message_id") || !columns.has("session_id")) return [];
|
|
6766
|
+
const selects = [
|
|
6767
|
+
"id",
|
|
6768
|
+
"message_id",
|
|
6769
|
+
"session_id",
|
|
6770
|
+
sqliteSelectMaybe(columns, "part", "time_created"),
|
|
6771
|
+
sqliteSelectMaybe(columns, "part", "time_updated"),
|
|
6772
|
+
sqliteSelectMaybe(columns, "part", "data")
|
|
6773
|
+
];
|
|
6774
|
+
const orderColumns = ["session_id", "message_id"];
|
|
6775
|
+
if (columns.has("time_created")) orderColumns.push("time_created");
|
|
6776
|
+
orderColumns.push("id");
|
|
6777
|
+
return readSqliteJson(dbPath, `select ${selects.join(", ")} from part order by ${orderColumns.join(", ")}`, "OpenCode SQLite parts");
|
|
6778
|
+
}
|
|
6779
|
+
|
|
6780
|
+
function openCodeSqliteMessagesFromRow(row, partRows, index) {
|
|
6781
|
+
const data = parseJsonObject(row.data);
|
|
6782
|
+
const parts = (partRows || []).map((partRow) => ({
|
|
6783
|
+
id: partRow.id,
|
|
6784
|
+
messageID: partRow.message_id,
|
|
6785
|
+
messageId: partRow.message_id,
|
|
6786
|
+
sessionID: partRow.session_id,
|
|
6787
|
+
sessionId: partRow.session_id,
|
|
6788
|
+
timeCreated: partRow.time_created,
|
|
6789
|
+
timeUpdated: partRow.time_updated,
|
|
6790
|
+
...parseJsonObject(partRow.data)
|
|
6791
|
+
}));
|
|
6792
|
+
const role = normalizeEventRole(data.role || data.type || data.actor) || openCodeRoleFromParts(parts);
|
|
6793
|
+
const timestamp = toIso(data.time?.created || data.createdAt || data.created_at || row.time_created) || offsetTimestamp(new Date(Number(row.time_created) || Date.now()).toISOString(), index);
|
|
6794
|
+
const text = firstString(data.content, data.text, data.message, openCodePartText(parts));
|
|
6795
|
+
const toolCalls = parts.map(openCodeToolCall).filter(Boolean);
|
|
6796
|
+
const toolResults = parts.map(openCodeToolResult).filter(Boolean);
|
|
6797
|
+
const messageId = firstString(row.id, data.id, data.messageID, data.messageId);
|
|
6798
|
+
const usage = openCodeUsageFromMessageData(data, parts);
|
|
6799
|
+
const result = [];
|
|
6800
|
+
if (role && (text || toolCalls.length)) {
|
|
6801
|
+
result.push({
|
|
6802
|
+
role,
|
|
6803
|
+
content: text,
|
|
6804
|
+
timestamp,
|
|
6805
|
+
metadata: {
|
|
6806
|
+
provider: "opencode",
|
|
6807
|
+
messageId,
|
|
6808
|
+
parentMessageId: firstString(data.parentID, data.parentId) || undefined,
|
|
6809
|
+
requestId: usage ? messageId : undefined,
|
|
6810
|
+
model: firstString(data.modelID, data.modelId, data.model?.modelID, data.model?.modelId, data.model) || undefined,
|
|
6811
|
+
providerId: firstString(data.providerID, data.providerId, data.model?.providerID, data.model?.providerId) || undefined,
|
|
6812
|
+
mode: firstString(data.mode) || undefined,
|
|
6813
|
+
agent: firstString(data.agent) || undefined,
|
|
6814
|
+
finish: firstString(data.finish) || undefined,
|
|
6815
|
+
cwd: firstString(data.path?.cwd) || undefined,
|
|
6816
|
+
root: firstString(data.path?.root) || undefined,
|
|
6817
|
+
usage: usage || undefined,
|
|
6818
|
+
cost: Number.isFinite(Number(data.cost)) ? Number(data.cost) : undefined,
|
|
6819
|
+
toolCalls: toolCalls.length ? toolCalls : undefined
|
|
6820
|
+
}
|
|
6821
|
+
});
|
|
6822
|
+
}
|
|
6823
|
+
for (const toolResult of toolResults) {
|
|
6824
|
+
result.push({ role: "tool", content: toolResult.output, timestamp, metadata: { provider: "opencode", messageId, toolResult } });
|
|
6825
|
+
}
|
|
6826
|
+
return result;
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
function openCodeUsageFromMessageData(data, parts = []) {
|
|
6830
|
+
const finishPart = (parts || []).find((part) => String(part?.type || "").toLowerCase() === "step-finish" && part.tokens && typeof part.tokens === "object");
|
|
6831
|
+
const tokens = data?.tokens && typeof data.tokens === "object" ? data.tokens : finishPart?.tokens;
|
|
6832
|
+
if (!tokens || typeof tokens !== "object") return null;
|
|
6833
|
+
const usage = {
|
|
6834
|
+
input_tokens: Number.isFinite(Number(tokens.input)) ? Number(tokens.input) : undefined,
|
|
6835
|
+
output_tokens: Number.isFinite(Number(tokens.output)) ? Number(tokens.output) : undefined,
|
|
6836
|
+
total_tokens: Number.isFinite(Number(tokens.total)) ? Number(tokens.total) : undefined,
|
|
6837
|
+
reasoning_tokens: Number.isFinite(Number(tokens.reasoning)) ? Number(tokens.reasoning) : undefined,
|
|
6838
|
+
cache_creation_input_tokens: Number.isFinite(Number(tokens.cache?.write)) ? Number(tokens.cache.write) : undefined,
|
|
6839
|
+
cache_read_input_tokens: Number.isFinite(Number(tokens.cache?.read)) ? Number(tokens.cache.read) : undefined
|
|
6840
|
+
};
|
|
6841
|
+
return Object.values(usage).some((value) => value !== undefined) ? usage : null;
|
|
6842
|
+
}
|
|
6843
|
+
|
|
6844
|
+
function openCodeSqliteSessionFingerprint(dbPath, row, messageRows, sourceFiles) {
|
|
6845
|
+
const sessionRevision = [
|
|
6846
|
+
row.id,
|
|
6847
|
+
row.time_updated || row.time_created || "",
|
|
6848
|
+
messageRows.length,
|
|
6849
|
+
messageRows.map((message) => `${message.id}:${message.time_updated || message.time_created || ""}`).join("|")
|
|
6850
|
+
].join(":");
|
|
6851
|
+
return `${fingerprintPrefix("opencode-sqlite-history")}:${structuredSessionFingerprint({ sourcePath: dbPath, sourceFiles })}:${hashId(sessionRevision)}`;
|
|
6852
|
+
}
|
|
6853
|
+
|
|
5679
6854
|
function parseOpenCodeSessionFile(file, storageRoot) {
|
|
5680
6855
|
const info = readJsonMaybe(file, null);
|
|
5681
6856
|
if (!info || typeof info !== "object") return null;
|
|
@@ -5717,6 +6892,63 @@ function parseOpenCodeSessionFile(file, storageRoot) {
|
|
|
5717
6892
|
};
|
|
5718
6893
|
}
|
|
5719
6894
|
|
|
6895
|
+
function parseOpenCodeMessageOnlySession(storageRoot, sessionId) {
|
|
6896
|
+
if (!sessionId) return null;
|
|
6897
|
+
const messageFiles = openCodeMessageFiles(storageRoot, sessionId);
|
|
6898
|
+
const parsedMessages = messageFiles.flatMap((messageFile, index) => openCodeMessagesFromFile(messageFile, storageRoot, index));
|
|
6899
|
+
const diffFile = path.join(storageRoot, "session_diff", `${sessionId}.json`);
|
|
6900
|
+
const diffMessage = openCodeDiffMessage(diffFile, parsedMessages[parsedMessages.length - 1]?.timestamp);
|
|
6901
|
+
const messages = stampMessages(
|
|
6902
|
+
dedupeAdjacentMessages(parsedMessages.concat(diffMessage ? [diffMessage] : [])).sort((a, b) => String(a.timestamp).localeCompare(String(b.timestamp))),
|
|
6903
|
+
"opencode-history"
|
|
6904
|
+
);
|
|
6905
|
+
if (!messages.length) return null;
|
|
6906
|
+
const sourceFiles = [
|
|
6907
|
+
...messageFiles,
|
|
6908
|
+
...messageFiles.flatMap((messageFile) => openCodePartFiles(storageRoot, firstString(readJsonMaybe(messageFile, {})?.id, path.basename(messageFile, ".json")))),
|
|
6909
|
+
safeStat(diffFile) ? diffFile : ""
|
|
6910
|
+
].filter(Boolean);
|
|
6911
|
+
const startedAt = messages[0]?.timestamp || "";
|
|
6912
|
+
const endedAt = messages[messages.length - 1]?.timestamp || startedAt;
|
|
6913
|
+
const cwd = openCodeCwdFromMessages(messages) || openCodeProjectPathFromStorage(storageRoot);
|
|
6914
|
+
return {
|
|
6915
|
+
sessionId: `opencode-${sessionId}`,
|
|
6916
|
+
title: clineTitle(messages) || sessionId,
|
|
6917
|
+
cwd,
|
|
6918
|
+
startedAt,
|
|
6919
|
+
endedAt,
|
|
6920
|
+
messages,
|
|
6921
|
+
sourcePath: path.join(storageRoot, "message", sessionId),
|
|
6922
|
+
sourceFiles,
|
|
6923
|
+
sourceType: "opencode-history",
|
|
6924
|
+
fingerprint: `${fingerprintPrefix("opencode-history")}:${structuredSessionFingerprint({ sourcePath: path.join(storageRoot, "message", sessionId), sourceFiles })}`,
|
|
6925
|
+
detailKey: "sessions"
|
|
6926
|
+
};
|
|
6927
|
+
}
|
|
6928
|
+
|
|
6929
|
+
function openCodeCwdFromMessages(messages) {
|
|
6930
|
+
for (const message of messages || []) {
|
|
6931
|
+
const cwd = firstString(message?.metadata?.cwd, message?.metadata?.directory, message?.metadata?.projectPath);
|
|
6932
|
+
if (cwd) return cwd;
|
|
6933
|
+
const fromContent = openCodePathFromText(message?.content);
|
|
6934
|
+
if (fromContent) return fromContent;
|
|
6935
|
+
for (const toolCall of message?.metadata?.toolCalls || []) {
|
|
6936
|
+
const fromTool = openCodePathFromText(JSON.stringify(toolCall.arguments || toolCall.argument || ""));
|
|
6937
|
+
if (fromTool) return fromTool;
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6940
|
+
return "";
|
|
6941
|
+
}
|
|
6942
|
+
|
|
6943
|
+
function openCodePathFromText(text) {
|
|
6944
|
+
const value = String(text || "");
|
|
6945
|
+
const cdMatch = value.match(/\bcd\s+["']?([^"'\s]+)["']?/);
|
|
6946
|
+
const candidate = cdMatch ? cdMatch[1] : value.match(/(?:working\s+)?directory[:=\s]+["']?([^"'\s]+)["']?/i)?.[1];
|
|
6947
|
+
if (!candidate) return "";
|
|
6948
|
+
const expanded = candidate.startsWith("~/") ? path.join(os.homedir(), candidate.slice(2)) : candidate;
|
|
6949
|
+
return path.isAbsolute(expanded) && fs.existsSync(expanded) ? expanded : "";
|
|
6950
|
+
}
|
|
6951
|
+
|
|
5720
6952
|
function openCodeProjectInfo(storageRoot, projectId) {
|
|
5721
6953
|
const file = projectId ? path.join(storageRoot, "project", `${projectId}.json`) : "";
|
|
5722
6954
|
const data = file ? readJsonMaybe(file, {}) : {};
|
|
@@ -5887,10 +7119,14 @@ function openCodeDiffText(value) {
|
|
|
5887
7119
|
if (typeof value === "string") return value.trim();
|
|
5888
7120
|
if (Array.isArray(value)) return value.map(openCodeDiffText).filter(Boolean).join("\n");
|
|
5889
7121
|
if (typeof value !== "object") return String(value);
|
|
7122
|
+
const file = firstString(value.path, value.file, value.filename);
|
|
5890
7123
|
for (const key of ["diff", "patch", "text", "content"]) {
|
|
5891
|
-
if (typeof value[key] === "string" && value[key].trim())
|
|
7124
|
+
if (typeof value[key] === "string" && value[key].trim()) {
|
|
7125
|
+
const body = value[key].trim();
|
|
7126
|
+
if (file && !/^diff --git\s/m.test(body)) return `diff --git a/${file} b/${file}\n${body}`;
|
|
7127
|
+
return body;
|
|
7128
|
+
}
|
|
5892
7129
|
}
|
|
5893
|
-
const file = firstString(value.path, value.file, value.filename);
|
|
5894
7130
|
const body = openCodeDiffText(value.hunks || value.changes || value.edits);
|
|
5895
7131
|
if (file && body) return `diff --git a/${file} b/${file}\n${body}`;
|
|
5896
7132
|
return "";
|
|
@@ -6024,6 +7260,7 @@ function aiderMarkdownMessages(text, fallbackTime) {
|
|
|
6024
7260
|
}
|
|
6025
7261
|
|
|
6026
7262
|
function dedupeStructuredSessions(sessions, provider) {
|
|
7263
|
+
if (provider === "opencode") return dedupeOpenCodeSessions(sessions);
|
|
6027
7264
|
const seen = new Set();
|
|
6028
7265
|
const result = [];
|
|
6029
7266
|
for (const session of sessions) {
|
|
@@ -6035,6 +7272,30 @@ function dedupeStructuredSessions(sessions, provider) {
|
|
|
6035
7272
|
return result;
|
|
6036
7273
|
}
|
|
6037
7274
|
|
|
7275
|
+
function dedupeOpenCodeSessions(sessions) {
|
|
7276
|
+
const bySessionId = new Map();
|
|
7277
|
+
const order = [];
|
|
7278
|
+
for (const session of sessions || []) {
|
|
7279
|
+
const key = `opencode:${session.sessionId}`;
|
|
7280
|
+
const existing = bySessionId.get(key);
|
|
7281
|
+
if (!existing) {
|
|
7282
|
+
bySessionId.set(key, session);
|
|
7283
|
+
order.push(key);
|
|
7284
|
+
continue;
|
|
7285
|
+
}
|
|
7286
|
+
const preferred = openCodeSourceRank(session.sourceType) > openCodeSourceRank(existing.sourceType) ? session : existing;
|
|
7287
|
+
preferred.sourceFiles = existingUniquePaths([...(existing.sourceFiles || []), ...(session.sourceFiles || [])]);
|
|
7288
|
+
bySessionId.set(key, preferred);
|
|
7289
|
+
}
|
|
7290
|
+
return order.map((key) => bySessionId.get(key)).filter(Boolean);
|
|
7291
|
+
}
|
|
7292
|
+
|
|
7293
|
+
function openCodeSourceRank(sourceType) {
|
|
7294
|
+
if (sourceType === "opencode-sqlite-history") return 3;
|
|
7295
|
+
if (sourceType === "opencode-history") return 2;
|
|
7296
|
+
return 1;
|
|
7297
|
+
}
|
|
7298
|
+
|
|
6038
7299
|
function readDevinSessions(env = process.env, options = {}) {
|
|
6039
7300
|
const db = devinSessionsDb(env);
|
|
6040
7301
|
reportDiscoveryProgress(options, { current: 0, total: fs.existsSync(db) ? 1 : 0, message: "opening Devin sessions database", path: db });
|
|
@@ -6150,6 +7411,9 @@ function devinMessagesFromNode(row) {
|
|
|
6150
7411
|
if (role === "system" || devinIsContextUserMessage(role, content)) return [];
|
|
6151
7412
|
const toolResult = role === "tool" ? devinNormalizeToolResult(content, data) : null;
|
|
6152
7413
|
const body = role === "tool" ? content : devinVisibleContent(content, toolCalls);
|
|
7414
|
+
const usage = role === "assistant" ? devinUsage(data) : null;
|
|
7415
|
+
const model = role === "assistant" ? firstString(data.metadata?.generation_model, data.model, data.model_id, data.modelId) : "";
|
|
7416
|
+
const requestId = role === "assistant" ? firstString(data.metadata?.request_id, data.request_id, data.message_id) : "";
|
|
6153
7417
|
if (!body && !toolCalls.length && !toolResult) return [];
|
|
6154
7418
|
return [
|
|
6155
7419
|
{
|
|
@@ -6162,6 +7426,9 @@ function devinMessagesFromNode(row) {
|
|
|
6162
7426
|
parentNodeId: row.parent_node_id ?? undefined,
|
|
6163
7427
|
toolCalls: toolCalls.length ? toolCalls.map(devinPublicToolCall) : undefined,
|
|
6164
7428
|
toolResult: toolResult || undefined,
|
|
7429
|
+
usage: usage || undefined,
|
|
7430
|
+
model: model || undefined,
|
|
7431
|
+
requestId: requestId || undefined,
|
|
6165
7432
|
sourceType: "devin-cli-history",
|
|
6166
7433
|
parserVersion: parserVersionForSource("devin-cli-history")
|
|
6167
7434
|
}
|
|
@@ -6169,6 +7436,25 @@ function devinMessagesFromNode(row) {
|
|
|
6169
7436
|
];
|
|
6170
7437
|
}
|
|
6171
7438
|
|
|
7439
|
+
function devinUsage(message = {}) {
|
|
7440
|
+
const metrics = message?.metadata?.metrics;
|
|
7441
|
+
if (!metrics || typeof metrics !== "object") return null;
|
|
7442
|
+
const inputTokens = numericValue(metrics.input_tokens, metrics.inputTokens, metrics.prompt_tokens, metrics.promptTokens);
|
|
7443
|
+
const outputTokens = numericValue(metrics.output_tokens, metrics.outputTokens, metrics.completion_tokens, metrics.completionTokens, message?.metadata?.num_tokens);
|
|
7444
|
+
const cacheCreationInputTokens = numericValue(metrics.cache_creation_tokens, metrics.cache_creation_input_tokens, metrics.cacheCreationInputTokens);
|
|
7445
|
+
const cacheReadInputTokens = numericValue(metrics.cache_read_tokens, metrics.cache_read_input_tokens, metrics.cacheReadInputTokens);
|
|
7446
|
+
if ([inputTokens, outputTokens, cacheCreationInputTokens, cacheReadInputTokens].every((value) => value == null)) return null;
|
|
7447
|
+
const usage = {
|
|
7448
|
+
inputTokens: inputTokens ?? undefined,
|
|
7449
|
+
outputTokens: outputTokens ?? undefined,
|
|
7450
|
+
cacheCreationInputTokens: cacheCreationInputTokens ?? undefined,
|
|
7451
|
+
cacheReadInputTokens: cacheReadInputTokens ?? undefined
|
|
7452
|
+
};
|
|
7453
|
+
const totalTokens = [inputTokens, outputTokens].reduce((sum, value) => sum + (value || 0), 0);
|
|
7454
|
+
if (totalTokens) usage.totalTokens = totalTokens;
|
|
7455
|
+
return usage;
|
|
7456
|
+
}
|
|
7457
|
+
|
|
6172
7458
|
function devinVisibleContent(content, toolCalls) {
|
|
6173
7459
|
const value = String(content || "").trim();
|
|
6174
7460
|
if (toolCalls.length && /^none$/i.test(value)) return "";
|
|
@@ -6292,8 +7578,9 @@ function readGeminiCliSessions(options = {}, env = process.env) {
|
|
|
6292
7578
|
reportDiscoveryProgress(options, { current: 0, total: files.length, message: "reading saved chats and checkpoints" });
|
|
6293
7579
|
for (let index = 0; index < files.length; index++) {
|
|
6294
7580
|
const file = files[index];
|
|
6295
|
-
const parsed
|
|
6296
|
-
|
|
7581
|
+
for (const parsed of asArray(parseGeminiCliHistoryFile(file))) {
|
|
7582
|
+
if (parsed) sessions.push(parsed);
|
|
7583
|
+
}
|
|
6297
7584
|
reportDiscoveryProgress(options, { current: index + 1, total: files.length, message: `${sessions.length} importable`, path: file });
|
|
6298
7585
|
}
|
|
6299
7586
|
return coalesceGeminiCliSessions(sessions);
|
|
@@ -6476,17 +7763,26 @@ function parseGeminiCliHistoryFile(file) {
|
|
|
6476
7763
|
const stat = safeStat(file);
|
|
6477
7764
|
const fallbackTime = new Date(stat?.mtimeMs || Date.now()).toISOString();
|
|
6478
7765
|
const ext = path.extname(file).toLowerCase();
|
|
6479
|
-
let
|
|
7766
|
+
let parsedItems = [];
|
|
6480
7767
|
try {
|
|
6481
|
-
if (ext === ".jsonl")
|
|
6482
|
-
else if (ext === ".md" || ext === ".markdown")
|
|
6483
|
-
else
|
|
7768
|
+
if (ext === ".jsonl") parsedItems = parseGeminiCliJsonlSessions(fs.readFileSync(file, "utf8"), { fallbackTime });
|
|
7769
|
+
else if (ext === ".md" || ext === ".markdown") parsedItems = [parseMarkdownChatFile(file, "gemini-cli", fallbackTime)].filter(Boolean);
|
|
7770
|
+
else parsedItems = parseGeminiCliJsonSessions(JSON.parse(fs.readFileSync(file, "utf8")), { fallbackTime });
|
|
6484
7771
|
} catch {
|
|
6485
7772
|
return null;
|
|
6486
7773
|
}
|
|
7774
|
+
const multiSessionSource = parsedItems.length > 1;
|
|
7775
|
+
return parsedItems
|
|
7776
|
+
.map((parsed, index) => geminiCliParsedSession(file, stat, fallbackTime, ext, parsed, { multiSessionSource, index }))
|
|
7777
|
+
.filter(Boolean);
|
|
7778
|
+
}
|
|
7779
|
+
|
|
7780
|
+
function geminiCliParsedSession(file, stat, fallbackTime, ext, parsed, options = {}) {
|
|
6487
7781
|
if (!parsed || !parsed.messages.length) return null;
|
|
6488
7782
|
const cwd = parsed.cwd || geminiProjectCwd(file);
|
|
6489
7783
|
const sessionId = parsed.sessionId || stableSessionId("gemini_cli", file, parsed.startedAt || fallbackTime, parsed.messages);
|
|
7784
|
+
const sourceFingerprint = fileFingerprint(file, stat);
|
|
7785
|
+
const fingerprint = options.multiSessionSource ? `${sourceFingerprint}:session:${hashId(sessionId || options.index)}` : sourceFingerprint;
|
|
6490
7786
|
return {
|
|
6491
7787
|
sessionId,
|
|
6492
7788
|
title: parsed.title || path.basename(file, ext),
|
|
@@ -6497,8 +7793,9 @@ function parseGeminiCliHistoryFile(file) {
|
|
|
6497
7793
|
sourcePath: file,
|
|
6498
7794
|
sourceFiles: [file],
|
|
6499
7795
|
sourceType: "gemini-cli-history",
|
|
6500
|
-
fingerprint: `${fingerprintPrefix("gemini-cli-history")}:${
|
|
6501
|
-
detailKey: "files"
|
|
7796
|
+
fingerprint: `${fingerprintPrefix("gemini-cli-history")}:${fingerprint}`,
|
|
7797
|
+
detailKey: "files",
|
|
7798
|
+
sessionSummary: parsed.sessionSummary || undefined
|
|
6502
7799
|
};
|
|
6503
7800
|
}
|
|
6504
7801
|
|
|
@@ -6526,6 +7823,121 @@ function parseMarkdownChatFile(file, source, fallbackTime) {
|
|
|
6526
7823
|
};
|
|
6527
7824
|
}
|
|
6528
7825
|
|
|
7826
|
+
function readWindsurfTrajectoryExport(target, options = {}) {
|
|
7827
|
+
const files = windsurfTrajectoryFiles(target);
|
|
7828
|
+
const sessions = [];
|
|
7829
|
+
reportDiscoveryProgress(options, { current: 0, total: files.length, message: "reading Windsurf trajectory exports" });
|
|
7830
|
+
for (let index = 0; index < files.length; index++) {
|
|
7831
|
+
const file = files[index];
|
|
7832
|
+
const session = parseWindsurfTrajectoryFile(file);
|
|
7833
|
+
if (session) sessions.push(session);
|
|
7834
|
+
reportDiscoveryProgress(options, { current: index + 1, total: files.length, message: `${sessions.length} importable`, path: file });
|
|
7835
|
+
}
|
|
7836
|
+
return sessions;
|
|
7837
|
+
}
|
|
7838
|
+
|
|
7839
|
+
function windsurfTrajectoryFiles(target) {
|
|
7840
|
+
const resolved = path.resolve(target);
|
|
7841
|
+
const stat = safeStat(resolved);
|
|
7842
|
+
if (!stat) throw new Error(`Cannot read Windsurf trajectory export path ${target}`);
|
|
7843
|
+
if (stat.isFile()) return isMarkdownFile(resolved) ? [resolved] : [];
|
|
7844
|
+
if (!stat.isDirectory()) return [];
|
|
7845
|
+
const files = [];
|
|
7846
|
+
collectFiles(resolved, (file) => {
|
|
7847
|
+
if (isMarkdownFile(file)) files.push(file);
|
|
7848
|
+
});
|
|
7849
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
7850
|
+
}
|
|
7851
|
+
|
|
7852
|
+
function isMarkdownFile(file) {
|
|
7853
|
+
return [".md", ".markdown"].includes(path.extname(file).toLowerCase());
|
|
7854
|
+
}
|
|
7855
|
+
|
|
7856
|
+
function parseWindsurfTrajectoryFile(file) {
|
|
7857
|
+
const stat = safeStat(file);
|
|
7858
|
+
const fallbackTime = new Date(stat?.mtimeMs || Date.now()).toISOString();
|
|
7859
|
+
let text = "";
|
|
7860
|
+
try {
|
|
7861
|
+
text = fs.readFileSync(file, "utf8");
|
|
7862
|
+
} catch {
|
|
7863
|
+
return null;
|
|
7864
|
+
}
|
|
7865
|
+
if (!looksLikeWindsurfTrajectory(text)) return null;
|
|
7866
|
+
const messages = windsurfTrajectoryMessages(text, fallbackTime);
|
|
7867
|
+
if (!messages.length) return null;
|
|
7868
|
+
const stampedMessages = stampMessages(messages, "windsurf-trajectory-export");
|
|
7869
|
+
const startedAt = stampedMessages[0]?.timestamp || fallbackTime;
|
|
7870
|
+
const endedAt = stampedMessages[stampedMessages.length - 1]?.timestamp || fallbackTime;
|
|
7871
|
+
const cwd = inferCwdFromMarkdownText(text);
|
|
7872
|
+
return {
|
|
7873
|
+
sessionId: stableSessionId("windsurf", file, startedAt, stampedMessages),
|
|
7874
|
+
title: windsurfTrajectoryTitle(text, stampedMessages, file),
|
|
7875
|
+
cwd,
|
|
7876
|
+
scopeCanonical: cwd ? "" : uncategorizedScope("windsurf"),
|
|
7877
|
+
startedAt,
|
|
7878
|
+
endedAt,
|
|
7879
|
+
messages: stampedMessages,
|
|
7880
|
+
sourcePath: file,
|
|
7881
|
+
sourceFiles: [file],
|
|
7882
|
+
sourceType: "windsurf-trajectory-export",
|
|
7883
|
+
fingerprint: `${fingerprintPrefix("windsurf-trajectory-export")}:${fileFingerprint(file, stat)}:${hashId(stampedMessages.map((message) => `${message.role}:${message.content}`).join("\n"))}`,
|
|
7884
|
+
detailKey: "exports"
|
|
7885
|
+
};
|
|
7886
|
+
}
|
|
7887
|
+
|
|
7888
|
+
function looksLikeWindsurfTrajectory(text) {
|
|
7889
|
+
return /^#\s+Cascade Chat Conversation\s*$/im.test(String(text || "")) && /^###\s+(User Input|Planner Response|Assistant Response|Cascade Response)\s*$/im.test(String(text || ""));
|
|
7890
|
+
}
|
|
7891
|
+
|
|
7892
|
+
function windsurfTrajectoryMessages(text, fallbackTime) {
|
|
7893
|
+
const messages = [];
|
|
7894
|
+
let role = "";
|
|
7895
|
+
let heading = "";
|
|
7896
|
+
let current = [];
|
|
7897
|
+
const flush = () => {
|
|
7898
|
+
const content = current.join("\n").trim();
|
|
7899
|
+
if (role && content) {
|
|
7900
|
+
messages.push({
|
|
7901
|
+
role,
|
|
7902
|
+
content,
|
|
7903
|
+
timestamp: new Date(new Date(fallbackTime).getTime() + messages.length).toISOString(),
|
|
7904
|
+
metadata: { source: "windsurf-trajectory-export", heading }
|
|
7905
|
+
});
|
|
7906
|
+
}
|
|
7907
|
+
current = [];
|
|
7908
|
+
};
|
|
7909
|
+
for (const line of String(text || "").split(/\r?\n/)) {
|
|
7910
|
+
const match = line.match(/^###\s+(.+?)\s*$/);
|
|
7911
|
+
const nextRole = match ? windsurfTrajectoryRole(match[1]) : "";
|
|
7912
|
+
if (match && nextRole) {
|
|
7913
|
+
flush();
|
|
7914
|
+
role = nextRole;
|
|
7915
|
+
heading = match[1].trim();
|
|
7916
|
+
} else if (role) {
|
|
7917
|
+
current.push(line);
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
flush();
|
|
7921
|
+
return messages;
|
|
7922
|
+
}
|
|
7923
|
+
|
|
7924
|
+
function windsurfTrajectoryRole(label) {
|
|
7925
|
+
const value = String(label || "").trim().toLowerCase();
|
|
7926
|
+
if (/(user|human).*(input|prompt|message)?/.test(value)) return "user";
|
|
7927
|
+
if (/(planner|assistant|cascade|agent|model).*(response|output|message)?/.test(value)) return "assistant";
|
|
7928
|
+
if (/system/.test(value)) return "system";
|
|
7929
|
+
if (/tool/.test(value)) return "tool";
|
|
7930
|
+
return "";
|
|
7931
|
+
}
|
|
7932
|
+
|
|
7933
|
+
function windsurfTrajectoryTitle(text, messages, file) {
|
|
7934
|
+
const title = markdownTitle(text);
|
|
7935
|
+
if (title && !/^Cascade Chat Conversation$/i.test(title)) return title;
|
|
7936
|
+
const firstUser = messages.find((message) => message.role === "user");
|
|
7937
|
+
const line = firstLine(firstUser?.content);
|
|
7938
|
+
return line ? line.slice(0, 120) : path.basename(file, path.extname(file));
|
|
7939
|
+
}
|
|
7940
|
+
|
|
6529
7941
|
function readWindsurfSessions(options = {}) {
|
|
6530
7942
|
// Kept for future decoder work only. Windsurf's current Cascade transcript
|
|
6531
7943
|
// stores are encrypted binary files, so this source is disabled from public
|
|
@@ -6560,8 +7972,9 @@ function readWindsurfSessions(options = {}) {
|
|
|
6560
7972
|
}
|
|
6561
7973
|
|
|
6562
7974
|
function readAntigravitySessions(options = {}, env = process.env) {
|
|
6563
|
-
const
|
|
6564
|
-
const
|
|
7975
|
+
const home = antigravityHome(env);
|
|
7976
|
+
const roots = [path.join(home, "brain")];
|
|
7977
|
+
const artifactSessions = readMarkdownArtifactSessions({
|
|
6565
7978
|
provider: "antigravity",
|
|
6566
7979
|
roots,
|
|
6567
7980
|
sourceType: "antigravity-brain",
|
|
@@ -6569,7 +7982,11 @@ function readAntigravitySessions(options = {}, env = process.env) {
|
|
|
6569
7982
|
artifactNames: ["task.md", "implementation_plan.md", "walkthrough.md", "plan.md"],
|
|
6570
7983
|
options
|
|
6571
7984
|
});
|
|
6572
|
-
const
|
|
7985
|
+
const artifactSessionIds = new Set(artifactSessions.map((session) => session.sessionId));
|
|
7986
|
+
const trajectorySessions = readAntigravityTrajectorySummarySessions(options, env)
|
|
7987
|
+
.filter((session) => !artifactSessionIds.has(session.sessionId));
|
|
7988
|
+
const sessions = [...artifactSessions, ...trajectorySessions];
|
|
7989
|
+
const binaryCount = countFiles(path.join(home, "conversations"), (file) => file.endsWith(".pb"));
|
|
6573
7990
|
if (binaryCount && sessions.length) sessions[0].binaryCount = binaryCount;
|
|
6574
7991
|
else if (binaryCount) {
|
|
6575
7992
|
sessions.push({
|
|
@@ -6580,7 +7997,7 @@ function readAntigravitySessions(options = {}, env = process.env) {
|
|
|
6580
7997
|
startedAt: new Date().toISOString(),
|
|
6581
7998
|
endedAt: new Date().toISOString(),
|
|
6582
7999
|
messages: [],
|
|
6583
|
-
sourcePath: path.join(
|
|
8000
|
+
sourcePath: path.join(home, "conversations"),
|
|
6584
8001
|
sourceType: "antigravity-protobuf",
|
|
6585
8002
|
binaryCount,
|
|
6586
8003
|
detailKey: "tasks"
|
|
@@ -6589,6 +8006,205 @@ function readAntigravitySessions(options = {}, env = process.env) {
|
|
|
6589
8006
|
return sessions;
|
|
6590
8007
|
}
|
|
6591
8008
|
|
|
8009
|
+
function antigravityHome(env = process.env) {
|
|
8010
|
+
return env.AGENTLOG_ANTIGRAVITY_HOME_DIR || path.join(geminiHome(env), "antigravity");
|
|
8011
|
+
}
|
|
8012
|
+
|
|
8013
|
+
function antigravityGlobalStateDbs(env = process.env) {
|
|
8014
|
+
const explicit = envPathList(env.AGENTLOG_ANTIGRAVITY_GLOBAL_STORAGE_DB || env.AGENTLOG_ANTIGRAVITY_GLOBAL_STATE_DB);
|
|
8015
|
+
if (explicit.length) return existingUniquePaths(explicit);
|
|
8016
|
+
const appRoots = envPathList(env.AGENTLOG_ANTIGRAVITY_APP_SUPPORT_DIR);
|
|
8017
|
+
if (!appRoots.length) {
|
|
8018
|
+
appRoots.push(
|
|
8019
|
+
path.join(os.homedir(), "Library", "Application Support", "Antigravity"),
|
|
8020
|
+
path.join(os.homedir(), ".config", "Antigravity"),
|
|
8021
|
+
path.join(os.homedir(), "AppData", "Roaming", "Antigravity")
|
|
8022
|
+
);
|
|
8023
|
+
}
|
|
8024
|
+
return existingUniquePaths([
|
|
8025
|
+
...explicit,
|
|
8026
|
+
...appRoots.flatMap((root) => [
|
|
8027
|
+
path.join(root, "User", "globalStorage", "state.vscdb"),
|
|
8028
|
+
path.join(root, "User", "globalStorage", "state.vscdb.backup")
|
|
8029
|
+
])
|
|
8030
|
+
]);
|
|
8031
|
+
}
|
|
8032
|
+
|
|
8033
|
+
function readAntigravityTrajectorySummarySessions(options = {}, env = process.env) {
|
|
8034
|
+
const dbs = antigravityGlobalStateDbs(env);
|
|
8035
|
+
if (!dbs.length) return [];
|
|
8036
|
+
const sessions = [];
|
|
8037
|
+
const seen = new Set();
|
|
8038
|
+
reportDiscoveryProgress(options, { current: 0, total: dbs.length, message: "reading trajectory summaries" });
|
|
8039
|
+
for (let index = 0; index < dbs.length; index++) {
|
|
8040
|
+
const db = dbs[index];
|
|
8041
|
+
try {
|
|
8042
|
+
for (const session of antigravityTrajectorySummarySessionsFromDb(db, env)) {
|
|
8043
|
+
if (seen.has(session.sessionId)) continue;
|
|
8044
|
+
seen.add(session.sessionId);
|
|
8045
|
+
sessions.push(session);
|
|
8046
|
+
}
|
|
8047
|
+
} catch (error) {
|
|
8048
|
+
reportDiscoveryProgress(options, { current: index + 1, total: dbs.length, message: error.message, path: db });
|
|
8049
|
+
continue;
|
|
8050
|
+
}
|
|
8051
|
+
reportDiscoveryProgress(options, { current: index + 1, total: dbs.length, message: `${sessions.length} summaries`, path: db });
|
|
8052
|
+
}
|
|
8053
|
+
if (sessions.length) sessions[0].stateDbCount = dbs.length;
|
|
8054
|
+
return sessions.sort((a, b) => String(a.startedAt).localeCompare(String(b.startedAt)) || a.sessionId.localeCompare(b.sessionId));
|
|
8055
|
+
}
|
|
8056
|
+
|
|
8057
|
+
function antigravityTrajectorySummarySessionsFromDb(db, env = process.env) {
|
|
8058
|
+
if (!fs.existsSync(db)) return [];
|
|
8059
|
+
const rows = readSqliteJson(
|
|
8060
|
+
db,
|
|
8061
|
+
"SELECT value FROM ItemTable WHERE key='antigravityUnifiedStateSync.trajectorySummaries'",
|
|
8062
|
+
"Antigravity global state"
|
|
8063
|
+
);
|
|
8064
|
+
const encoded = firstString(...rows.map((row) => row.value));
|
|
8065
|
+
if (!encoded) return [];
|
|
8066
|
+
return parseAntigravityTrajectorySummaries(encoded, { db, env });
|
|
8067
|
+
}
|
|
8068
|
+
|
|
8069
|
+
function parseAntigravityTrajectorySummaries(encoded, context = {}) {
|
|
8070
|
+
const outer = decodeProtoMessage(bufferFromBase64(encoded));
|
|
8071
|
+
const sessions = [];
|
|
8072
|
+
for (const entry of outer.filter((field) => field.number === 1 && field.wireType === 2)) {
|
|
8073
|
+
const session = antigravityTrajectorySummarySession(entry.bytes, context);
|
|
8074
|
+
if (session) sessions.push(session);
|
|
8075
|
+
}
|
|
8076
|
+
return sessions;
|
|
8077
|
+
}
|
|
8078
|
+
|
|
8079
|
+
function antigravityTrajectorySummarySession(entryBytes, context = {}) {
|
|
8080
|
+
const entry = decodeProtoMessage(entryBytes);
|
|
8081
|
+
const id = protoString(firstProtoField(entry, 1));
|
|
8082
|
+
const wrapper = firstProtoField(entry, 2);
|
|
8083
|
+
const summaryEnvelope = wrapper?.bytes ? decodeProtoMessage(wrapper.bytes) : [];
|
|
8084
|
+
const summaryEncoded = protoString(firstProtoField(summaryEnvelope, 1));
|
|
8085
|
+
if (!id || !summaryEncoded) return null;
|
|
8086
|
+
|
|
8087
|
+
const sourceType = "antigravity-trajectory-summary";
|
|
8088
|
+
const summaryBytes = bufferFromBase64(summaryEncoded);
|
|
8089
|
+
const fields = decodeProtoMessage(summaryBytes);
|
|
8090
|
+
const prompt = cleanAntigravityText(protoString(firstProtoField(fields, 1)));
|
|
8091
|
+
if (!prompt) return null;
|
|
8092
|
+
const startedAt = antigravitySummaryTimestamp(fields, 7) || antigravitySummaryTimestamp(fields, 3) || antigravitySummaryTimestamp(fields, 10) || new Date().toISOString();
|
|
8093
|
+
const endedAt = antigravitySummaryTimestamp(fields, 10) || antigravitySummaryTimestamp(fields, 3) || startedAt;
|
|
8094
|
+
const cwd = antigravitySummaryCwd(fields);
|
|
8095
|
+
const assistantSummary = antigravityAssistantSummary(fields, prompt);
|
|
8096
|
+
const messages = [
|
|
8097
|
+
{
|
|
8098
|
+
role: "user",
|
|
8099
|
+
content: prompt,
|
|
8100
|
+
timestamp: startedAt,
|
|
8101
|
+
metadata: {
|
|
8102
|
+
provider: "antigravity",
|
|
8103
|
+
source: sourceType,
|
|
8104
|
+
providerConversationId: id,
|
|
8105
|
+
partialSummary: true
|
|
8106
|
+
}
|
|
8107
|
+
}
|
|
8108
|
+
];
|
|
8109
|
+
if (assistantSummary) {
|
|
8110
|
+
messages.push({
|
|
8111
|
+
role: "assistant",
|
|
8112
|
+
content: `# Antigravity Trajectory Summary\n\n${assistantSummary}`,
|
|
8113
|
+
timestamp: endedAt,
|
|
8114
|
+
metadata: {
|
|
8115
|
+
provider: "antigravity",
|
|
8116
|
+
source: sourceType,
|
|
8117
|
+
providerConversationId: id,
|
|
8118
|
+
partialSummary: true
|
|
8119
|
+
}
|
|
8120
|
+
});
|
|
8121
|
+
}
|
|
8122
|
+
const db = context.db || "";
|
|
8123
|
+
return {
|
|
8124
|
+
sessionId: `antigravity-${id}`,
|
|
8125
|
+
providerConversationId: id,
|
|
8126
|
+
title: antigravitySummaryTitle(prompt),
|
|
8127
|
+
cwd,
|
|
8128
|
+
scopeCanonical: cwd ? "" : "antigravity/uncategorized",
|
|
8129
|
+
startedAt,
|
|
8130
|
+
endedAt,
|
|
8131
|
+
messages: stampMessages(messages, sourceType),
|
|
8132
|
+
sourcePath: `antigravity-state:${db || "globalStorage/state.vscdb"}#trajectorySummaries/${id}`,
|
|
8133
|
+
sourceFiles: [],
|
|
8134
|
+
sourceType,
|
|
8135
|
+
fingerprint: `${fingerprintPrefix(sourceType)}:${db}:${id}:${hashId(summaryEncoded)}`,
|
|
8136
|
+
detailKey: "trajectorySummaries",
|
|
8137
|
+
partialSummary: true,
|
|
8138
|
+
rawReferences: db
|
|
8139
|
+
? [
|
|
8140
|
+
{
|
|
8141
|
+
originalPath: db,
|
|
8142
|
+
entryPath: `ItemTable/antigravityUnifiedStateSync.trajectorySummaries/${id}`,
|
|
8143
|
+
conversationId: id,
|
|
8144
|
+
note: "Antigravity trajectory summary state row. The global state DB is referenced but not copied because it can contain auth tokens."
|
|
8145
|
+
}
|
|
8146
|
+
]
|
|
8147
|
+
: undefined,
|
|
8148
|
+
sessionSummary: {
|
|
8149
|
+
source: sourceType,
|
|
8150
|
+
partial: true,
|
|
8151
|
+
note: "Imported from Antigravity trajectory summary metadata. Full binary conversation bodies are not decoded."
|
|
8152
|
+
}
|
|
8153
|
+
};
|
|
8154
|
+
}
|
|
8155
|
+
|
|
8156
|
+
function antigravitySummaryTimestamp(fields, number) {
|
|
8157
|
+
const field = firstProtoField(fields, number);
|
|
8158
|
+
if (!field?.bytes) return "";
|
|
8159
|
+
const timestamp = decodeProtoMessage(field.bytes);
|
|
8160
|
+
const seconds = protoVarintValue(firstProtoField(timestamp, 1));
|
|
8161
|
+
const nanos = protoVarintValue(firstProtoField(timestamp, 2)) || 0;
|
|
8162
|
+
if (!Number.isFinite(seconds) || seconds < 946684800 || seconds > 4102444800) return "";
|
|
8163
|
+
return new Date((seconds * 1000) + Math.floor(nanos / 1e6)).toISOString();
|
|
8164
|
+
}
|
|
8165
|
+
|
|
8166
|
+
function antigravitySummaryCwd(fields) {
|
|
8167
|
+
const candidates = collectProtoStrings(fields)
|
|
8168
|
+
.filter((value) => value.startsWith("file://"))
|
|
8169
|
+
.map((value) => {
|
|
8170
|
+
try {
|
|
8171
|
+
return fileURLToPath(value);
|
|
8172
|
+
} catch {
|
|
8173
|
+
return "";
|
|
8174
|
+
}
|
|
8175
|
+
})
|
|
8176
|
+
.filter(Boolean)
|
|
8177
|
+
.map((candidate) => {
|
|
8178
|
+
const stat = safeStat(candidate);
|
|
8179
|
+
return stat?.isFile() ? path.dirname(candidate) : candidate;
|
|
8180
|
+
})
|
|
8181
|
+
.filter((candidate) => candidate && !candidate.includes(`${path.sep}.gemini${path.sep}antigravity${path.sep}`))
|
|
8182
|
+
.filter((candidate) => !cursorIsSystemRootPath(candidate));
|
|
8183
|
+
return candidates[0] || "";
|
|
8184
|
+
}
|
|
8185
|
+
|
|
8186
|
+
function antigravityAssistantSummary(fields, prompt) {
|
|
8187
|
+
const promptValue = cleanAntigravityText(prompt);
|
|
8188
|
+
return collectProtoStrings(fields)
|
|
8189
|
+
.map(cleanAntigravityText)
|
|
8190
|
+
.filter((value) => value && value !== promptValue)
|
|
8191
|
+
.filter((value) => value.length >= 80)
|
|
8192
|
+
.filter((value) => !value.startsWith("file://"))
|
|
8193
|
+
.filter((value) => !isLikelyBase64(value))
|
|
8194
|
+
.filter((value) => !/^[0-9a-f-]{32,}$/i.test(value))
|
|
8195
|
+
.sort((a, b) => b.length - a.length)[0] || "";
|
|
8196
|
+
}
|
|
8197
|
+
|
|
8198
|
+
function antigravitySummaryTitle(prompt) {
|
|
8199
|
+
const value = cleanAntigravityText(prompt);
|
|
8200
|
+
if (!value) return "Antigravity trajectory summary";
|
|
8201
|
+
return value.length > 80 ? `${value.slice(0, 77).trimEnd()}...` : value;
|
|
8202
|
+
}
|
|
8203
|
+
|
|
8204
|
+
function cleanAntigravityText(value) {
|
|
8205
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
8206
|
+
}
|
|
8207
|
+
|
|
6592
8208
|
function readMarkdownArtifactSessions({ provider, roots, sourceType, detailKey, artifactNames, options = {} }) {
|
|
6593
8209
|
const dirs = [];
|
|
6594
8210
|
for (const root of roots) {
|
|
@@ -6738,16 +8354,22 @@ function inferCwdFromMarkdownFiles(files) {
|
|
|
6738
8354
|
} catch {
|
|
6739
8355
|
continue;
|
|
6740
8356
|
}
|
|
6741
|
-
const
|
|
6742
|
-
if (
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
8357
|
+
const cwd = inferCwdFromMarkdownText(text);
|
|
8358
|
+
if (cwd) return cwd;
|
|
8359
|
+
}
|
|
8360
|
+
return "";
|
|
8361
|
+
}
|
|
8362
|
+
|
|
8363
|
+
function inferCwdFromMarkdownText(text) {
|
|
8364
|
+
const match = String(text || "").match(/file:\/\/([^)\]\s]+)/);
|
|
8365
|
+
if (!match) return "";
|
|
8366
|
+
try {
|
|
8367
|
+
const pathname = fileURLToPath(match[0]);
|
|
8368
|
+
if (fs.existsSync(pathname)) return fs.statSync(pathname).isDirectory() ? pathname : path.dirname(pathname);
|
|
8369
|
+
const existing = nearestExistingParent(pathname);
|
|
8370
|
+
if (existing) return existing;
|
|
8371
|
+
} catch {
|
|
8372
|
+
return "";
|
|
6751
8373
|
}
|
|
6752
8374
|
return "";
|
|
6753
8375
|
}
|
|
@@ -6813,6 +8435,111 @@ function countFiles(root, predicate) {
|
|
|
6813
8435
|
return count;
|
|
6814
8436
|
}
|
|
6815
8437
|
|
|
8438
|
+
function bufferFromBase64(value) {
|
|
8439
|
+
try {
|
|
8440
|
+
return Buffer.from(String(value || ""), "base64");
|
|
8441
|
+
} catch {
|
|
8442
|
+
return Buffer.alloc(0);
|
|
8443
|
+
}
|
|
8444
|
+
}
|
|
8445
|
+
|
|
8446
|
+
function decodeProtoMessage(buffer) {
|
|
8447
|
+
const fields = [];
|
|
8448
|
+
const bytes = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer || []);
|
|
8449
|
+
let offset = 0;
|
|
8450
|
+
while (offset < bytes.length) {
|
|
8451
|
+
const key = readProtoVarint(bytes, offset);
|
|
8452
|
+
if (!key) break;
|
|
8453
|
+
offset = key.offset;
|
|
8454
|
+
const fieldNumber = Math.floor(key.value / 8);
|
|
8455
|
+
const wireType = key.value % 8;
|
|
8456
|
+
if (!fieldNumber) break;
|
|
8457
|
+
if (wireType === 0) {
|
|
8458
|
+
const value = readProtoVarint(bytes, offset);
|
|
8459
|
+
if (!value) break;
|
|
8460
|
+
offset = value.offset;
|
|
8461
|
+
fields.push({ number: fieldNumber, wireType, value: value.value });
|
|
8462
|
+
continue;
|
|
8463
|
+
}
|
|
8464
|
+
if (wireType === 1) {
|
|
8465
|
+
if (offset + 8 > bytes.length) break;
|
|
8466
|
+
fields.push({ number: fieldNumber, wireType, bytes: bytes.subarray(offset, offset + 8) });
|
|
8467
|
+
offset += 8;
|
|
8468
|
+
continue;
|
|
8469
|
+
}
|
|
8470
|
+
if (wireType === 2) {
|
|
8471
|
+
const length = readProtoVarint(bytes, offset);
|
|
8472
|
+
if (!length) break;
|
|
8473
|
+
offset = length.offset;
|
|
8474
|
+
if (length.value < 0 || offset + length.value > bytes.length) break;
|
|
8475
|
+
const value = bytes.subarray(offset, offset + length.value);
|
|
8476
|
+
offset += length.value;
|
|
8477
|
+
fields.push({ number: fieldNumber, wireType, bytes: value, text: printableUtf8(value) });
|
|
8478
|
+
continue;
|
|
8479
|
+
}
|
|
8480
|
+
if (wireType === 5) {
|
|
8481
|
+
if (offset + 4 > bytes.length) break;
|
|
8482
|
+
fields.push({ number: fieldNumber, wireType, bytes: bytes.subarray(offset, offset + 4) });
|
|
8483
|
+
offset += 4;
|
|
8484
|
+
continue;
|
|
8485
|
+
}
|
|
8486
|
+
break;
|
|
8487
|
+
}
|
|
8488
|
+
return fields;
|
|
8489
|
+
}
|
|
8490
|
+
|
|
8491
|
+
function readProtoVarint(buffer, offset) {
|
|
8492
|
+
let value = 0n;
|
|
8493
|
+
let shift = 0n;
|
|
8494
|
+
for (let index = offset; index < buffer.length; index++) {
|
|
8495
|
+
const byte = buffer[index];
|
|
8496
|
+
value |= BigInt(byte & 0x7f) << shift;
|
|
8497
|
+
if ((byte & 0x80) === 0) {
|
|
8498
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) return null;
|
|
8499
|
+
return { value: Number(value), offset: index + 1 };
|
|
8500
|
+
}
|
|
8501
|
+
shift += 7n;
|
|
8502
|
+
if (shift > 63n) return null;
|
|
8503
|
+
}
|
|
8504
|
+
return null;
|
|
8505
|
+
}
|
|
8506
|
+
|
|
8507
|
+
function printableUtf8(buffer) {
|
|
8508
|
+
if (!buffer?.length) return "";
|
|
8509
|
+
const text = buffer.toString("utf8");
|
|
8510
|
+
if (text.includes("\uFFFD")) return "";
|
|
8511
|
+
if (/[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(text)) return "";
|
|
8512
|
+
return text;
|
|
8513
|
+
}
|
|
8514
|
+
|
|
8515
|
+
function firstProtoField(fields, number) {
|
|
8516
|
+
return (fields || []).find((field) => field.number === number) || null;
|
|
8517
|
+
}
|
|
8518
|
+
|
|
8519
|
+
function protoString(field) {
|
|
8520
|
+
return field?.wireType === 2 ? field.text || "" : "";
|
|
8521
|
+
}
|
|
8522
|
+
|
|
8523
|
+
function protoVarintValue(field) {
|
|
8524
|
+
return field?.wireType === 0 ? field.value : null;
|
|
8525
|
+
}
|
|
8526
|
+
|
|
8527
|
+
function collectProtoStrings(fields, depth = 0) {
|
|
8528
|
+
const strings = [];
|
|
8529
|
+
if (depth > 8) return strings;
|
|
8530
|
+
for (const field of fields || []) {
|
|
8531
|
+
if (field.wireType !== 2 || !field.bytes) continue;
|
|
8532
|
+
if (field.text) strings.push(field.text);
|
|
8533
|
+
strings.push(...collectProtoStrings(decodeProtoMessage(field.bytes), depth + 1));
|
|
8534
|
+
}
|
|
8535
|
+
return strings;
|
|
8536
|
+
}
|
|
8537
|
+
|
|
8538
|
+
function isLikelyBase64(value) {
|
|
8539
|
+
const text = String(value || "").trim();
|
|
8540
|
+
return text.length >= 80 && text.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(text);
|
|
8541
|
+
}
|
|
8542
|
+
|
|
6816
8543
|
function envPathList(value) {
|
|
6817
8544
|
return String(value || "")
|
|
6818
8545
|
.split(new RegExp(`[${escapeRegExp(path.delimiter)},]`))
|
|
@@ -6843,6 +8570,30 @@ function readJsonMaybe(file, fallback = null) {
|
|
|
6843
8570
|
}
|
|
6844
8571
|
}
|
|
6845
8572
|
|
|
8573
|
+
function parseJsonObject(value, fallback = {}) {
|
|
8574
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
8575
|
+
if (typeof value !== "string" || !value.trim()) return fallback;
|
|
8576
|
+
try {
|
|
8577
|
+
const parsed = JSON.parse(value);
|
|
8578
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : fallback;
|
|
8579
|
+
} catch {
|
|
8580
|
+
return fallback;
|
|
8581
|
+
}
|
|
8582
|
+
}
|
|
8583
|
+
|
|
8584
|
+
function groupRowsBy(rows, key) {
|
|
8585
|
+
const grouped = new Map();
|
|
8586
|
+
for (const row of rows || []) {
|
|
8587
|
+
const value = row?.[key];
|
|
8588
|
+
if (value === undefined || value === null || value === "") continue;
|
|
8589
|
+
const groupKey = String(value);
|
|
8590
|
+
const group = grouped.get(groupKey) || [];
|
|
8591
|
+
group.push(row);
|
|
8592
|
+
grouped.set(groupKey, group);
|
|
8593
|
+
}
|
|
8594
|
+
return grouped;
|
|
8595
|
+
}
|
|
8596
|
+
|
|
6846
8597
|
function defaultSkipDirs() {
|
|
6847
8598
|
return new Set([".git", "node_modules", "vendor", "dist", "build", ".next", ".cache", ".venv", "venv", "__pycache__"]);
|
|
6848
8599
|
}
|
|
@@ -7037,6 +8788,7 @@ module.exports = {
|
|
|
7037
8788
|
mergeCursorRawAssistantOnlySessions,
|
|
7038
8789
|
mergeCursorRawCompanionSessions,
|
|
7039
8790
|
importCliHistory,
|
|
8791
|
+
importWindsurfTrajectoryExport,
|
|
7040
8792
|
importWebChat,
|
|
7041
8793
|
normalizeEventRole,
|
|
7042
8794
|
parseClaudeDesktopSessionFile,
|
|
@@ -7046,18 +8798,26 @@ module.exports = {
|
|
|
7046
8798
|
providerAdapterForSource,
|
|
7047
8799
|
providerAdapterSources,
|
|
7048
8800
|
geminiCliHistoryFiles,
|
|
8801
|
+
openCodeDatabaseFiles,
|
|
7049
8802
|
openCodeStorageRoots,
|
|
7050
8803
|
readCursorProjectTranscriptSessions,
|
|
7051
8804
|
readCursorGlobalDiskKvSessionsFromDb,
|
|
7052
8805
|
readCursorRawSqliteSalvageSessionsFromDb,
|
|
7053
8806
|
readAiderSessions,
|
|
8807
|
+
readAntigravitySessions,
|
|
7054
8808
|
readClineSessions,
|
|
7055
8809
|
readDevinSessionsFromDb,
|
|
7056
8810
|
readGeminiCliSessions,
|
|
7057
8811
|
readOpenCodeSessions,
|
|
8812
|
+
readWindsurfTrajectoryExport,
|
|
7058
8813
|
readExportBundle,
|
|
7059
8814
|
readExportJson,
|
|
7060
8815
|
normalizeWebConversations,
|
|
7061
8816
|
claudeFileHistoryFiles,
|
|
7062
|
-
claudeFileHistoryRoot
|
|
8817
|
+
claudeFileHistoryRoot,
|
|
8818
|
+
__cursorTestables: {
|
|
8819
|
+
cursorAttributionCwd,
|
|
8820
|
+
cursorIsSystemRootPath,
|
|
8821
|
+
cursorMostFrequentExistingCwd
|
|
8822
|
+
}
|
|
7063
8823
|
};
|