braindump 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/adapters/base-adapter.d.ts +13 -0
- package/dist/adapters/base-adapter.d.ts.map +1 -0
- package/dist/adapters/base-adapter.js +7 -0
- package/dist/adapters/base-adapter.js.map +1 -0
- package/dist/adapters/claude-code/adapter.d.ts +47 -0
- package/dist/adapters/claude-code/adapter.d.ts.map +1 -0
- package/dist/adapters/claude-code/adapter.js +382 -0
- package/dist/adapters/claude-code/adapter.js.map +1 -0
- package/dist/adapters/codex/adapter.d.ts +26 -0
- package/dist/adapters/codex/adapter.d.ts.map +1 -0
- package/dist/adapters/codex/adapter.js +446 -0
- package/dist/adapters/codex/adapter.js.map +1 -0
- package/dist/adapters/cursor/adapter.d.ts +35 -0
- package/dist/adapters/cursor/adapter.d.ts.map +1 -0
- package/dist/adapters/cursor/adapter.js +675 -0
- package/dist/adapters/cursor/adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +19 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +65 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +424 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils.d.ts +10 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +21 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/core/compression.d.ts +11 -0
- package/dist/core/compression.d.ts.map +1 -0
- package/dist/core/compression.js +182 -0
- package/dist/core/compression.js.map +1 -0
- package/dist/core/conversation-analyzer.d.ts +9 -0
- package/dist/core/conversation-analyzer.d.ts.map +1 -0
- package/dist/core/conversation-analyzer.js +220 -0
- package/dist/core/conversation-analyzer.js.map +1 -0
- package/dist/core/project-context.d.ts +7 -0
- package/dist/core/project-context.d.ts.map +1 -0
- package/dist/core/project-context.js +136 -0
- package/dist/core/project-context.js.map +1 -0
- package/dist/core/prompt-builder.d.ts +7 -0
- package/dist/core/prompt-builder.d.ts.map +1 -0
- package/dist/core/prompt-builder.js +88 -0
- package/dist/core/prompt-builder.js.map +1 -0
- package/dist/core/registry.d.ts +10 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +51 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/token-estimator.d.ts +10 -0
- package/dist/core/token-estimator.d.ts.map +1 -0
- package/dist/core/token-estimator.js +14 -0
- package/dist/core/token-estimator.js.map +1 -0
- package/dist/core/validation.d.ts +188 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +61 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/core/watcher.d.ts +20 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +208 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/providers/clipboard-provider.d.ts +8 -0
- package/dist/providers/clipboard-provider.d.ts.map +1 -0
- package/dist/providers/clipboard-provider.js +15 -0
- package/dist/providers/clipboard-provider.js.map +1 -0
- package/dist/providers/file-provider.d.ts +8 -0
- package/dist/providers/file-provider.d.ts.map +1 -0
- package/dist/providers/file-provider.js +14 -0
- package/dist/providers/file-provider.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +18 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/types/index.d.ts +132 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import Database from "better-sqlite3";
|
|
6
|
+
import { BaseAdapter } from "../base-adapter.js";
|
|
7
|
+
import { analyzeConversation } from "../../core/conversation-analyzer.js";
|
|
8
|
+
import { extractProjectContext } from "../../core/project-context.js";
|
|
9
|
+
import { validateSession } from "../../core/validation.js";
|
|
10
|
+
/**
|
|
11
|
+
* Adapter for Cursor sessions.
|
|
12
|
+
* Reads SQLite databases from workspaceStorage/<hash>/state.vscdb
|
|
13
|
+
*/
|
|
14
|
+
export class CursorAdapter extends BaseAdapter {
|
|
15
|
+
agentId = "cursor";
|
|
16
|
+
get workspaceStorageDir() {
|
|
17
|
+
if (process.platform === "darwin") {
|
|
18
|
+
return path.join(os.homedir(), "Library", "Application Support", "Cursor", "User", "workspaceStorage");
|
|
19
|
+
}
|
|
20
|
+
if (process.platform === "linux") {
|
|
21
|
+
return path.join(os.homedir(), ".config", "Cursor", "User", "workspaceStorage");
|
|
22
|
+
}
|
|
23
|
+
const appData = process.env.APPDATA ||
|
|
24
|
+
path.join(os.homedir(), "AppData", "Roaming");
|
|
25
|
+
return path.join(appData, "Cursor", "User", "workspaceStorage");
|
|
26
|
+
}
|
|
27
|
+
async detect() {
|
|
28
|
+
if (!fs.existsSync(this.workspaceStorageDir)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const entries = fs.readdirSync(this.workspaceStorageDir, {
|
|
32
|
+
withFileTypes: true,
|
|
33
|
+
});
|
|
34
|
+
return entries.some((entry) => {
|
|
35
|
+
if (!entry.isDirectory()) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const dbPath = path.join(this.workspaceStorageDir, entry.name, "state.vscdb");
|
|
39
|
+
return fs.existsSync(dbPath);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async listSessions(projectPath) {
|
|
43
|
+
if (!fs.existsSync(this.workspaceStorageDir)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const sessions = [];
|
|
47
|
+
const workspaceEntries = fs
|
|
48
|
+
.readdirSync(this.workspaceStorageDir, { withFileTypes: true })
|
|
49
|
+
.filter((entry) => entry.isDirectory());
|
|
50
|
+
const candidates = [];
|
|
51
|
+
for (const workspaceEntry of workspaceEntries) {
|
|
52
|
+
const workspaceHash = workspaceEntry.name;
|
|
53
|
+
const workspaceDir = path.join(this.workspaceStorageDir, workspaceHash);
|
|
54
|
+
const dbPath = path.join(workspaceDir, "state.vscdb");
|
|
55
|
+
if (!fs.existsSync(dbPath)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const resolvedProjectPath = this.readWorkspaceProjectPath(workspaceDir);
|
|
59
|
+
const stat = fs.statSync(dbPath);
|
|
60
|
+
candidates.push({
|
|
61
|
+
workspaceHash,
|
|
62
|
+
workspaceDir,
|
|
63
|
+
dbPath,
|
|
64
|
+
resolvedProjectPath,
|
|
65
|
+
mtimeMs: stat.mtimeMs,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (candidates.length === 0) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
let selectedCandidates = candidates;
|
|
72
|
+
if (projectPath) {
|
|
73
|
+
const exactMatches = candidates.filter((candidate) => candidate.resolvedProjectPath &&
|
|
74
|
+
this.pathsEqual(projectPath, candidate.resolvedProjectPath));
|
|
75
|
+
const hashMatches = candidates.filter((candidate) => this.matchesWorkspaceHash(projectPath, candidate.workspaceHash));
|
|
76
|
+
if (exactMatches.length > 0) {
|
|
77
|
+
selectedCandidates = exactMatches;
|
|
78
|
+
}
|
|
79
|
+
else if (hashMatches.length > 0) {
|
|
80
|
+
selectedCandidates = hashMatches;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const fallback = [...candidates].sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
|
|
84
|
+
selectedCandidates = fallback ? [fallback] : [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const candidate of selectedCandidates) {
|
|
88
|
+
let db = null;
|
|
89
|
+
try {
|
|
90
|
+
db = this.openDatabase(candidate.dbPath);
|
|
91
|
+
const composers = this.readComposers(db);
|
|
92
|
+
for (const composer of composers) {
|
|
93
|
+
sessions.push({
|
|
94
|
+
id: `${candidate.workspaceHash}:${composer.id}`,
|
|
95
|
+
startedAt: composer.startedAt,
|
|
96
|
+
lastActiveAt: composer.lastActiveAt,
|
|
97
|
+
messageCount: composer.messageCount,
|
|
98
|
+
projectPath: candidate.resolvedProjectPath ??
|
|
99
|
+
(projectPath && this.matchesWorkspaceHash(projectPath, candidate.workspaceHash)
|
|
100
|
+
? projectPath
|
|
101
|
+
: undefined),
|
|
102
|
+
preview: composer.preview,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Skip unreadable workspace DBs.
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
db?.close();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
sessions.sort((a, b) => {
|
|
114
|
+
const aTime = a.lastActiveAt || a.startedAt || "";
|
|
115
|
+
const bTime = b.lastActiveAt || b.startedAt || "";
|
|
116
|
+
return bTime.localeCompare(aTime);
|
|
117
|
+
});
|
|
118
|
+
return sessions;
|
|
119
|
+
}
|
|
120
|
+
async capture(sessionId) {
|
|
121
|
+
const separator = sessionId.indexOf(":");
|
|
122
|
+
if (separator <= 0) {
|
|
123
|
+
throw new Error(`Invalid Cursor session ID: ${sessionId}. Expected <workspace-hash>:<composer-id>`);
|
|
124
|
+
}
|
|
125
|
+
const workspaceHash = sessionId.slice(0, separator);
|
|
126
|
+
const composerId = sessionId.slice(separator + 1);
|
|
127
|
+
const workspaceDir = path.join(this.workspaceStorageDir, workspaceHash);
|
|
128
|
+
const dbPath = path.join(workspaceDir, "state.vscdb");
|
|
129
|
+
if (!fs.existsSync(dbPath)) {
|
|
130
|
+
throw new Error(`Cursor workspace DB not found: ${dbPath}`);
|
|
131
|
+
}
|
|
132
|
+
const projectPath = this.readWorkspaceProjectPath(workspaceDir) || process.cwd();
|
|
133
|
+
const messages = [];
|
|
134
|
+
const fileChanges = new Map();
|
|
135
|
+
let totalTokens = 0;
|
|
136
|
+
let sessionStartedAt;
|
|
137
|
+
let lastAssistantMessage = "";
|
|
138
|
+
const db = this.openDatabase(dbPath);
|
|
139
|
+
try {
|
|
140
|
+
const bubbleRows = this.readBubbleRows(db, composerId);
|
|
141
|
+
for (const row of bubbleRows) {
|
|
142
|
+
const parsed = this.parseCursorPayload(row.value);
|
|
143
|
+
if (parsed.message) {
|
|
144
|
+
messages.push(parsed.message);
|
|
145
|
+
if (!sessionStartedAt && parsed.message.timestamp) {
|
|
146
|
+
sessionStartedAt = parsed.message.timestamp;
|
|
147
|
+
}
|
|
148
|
+
if (parsed.message.role === "assistant") {
|
|
149
|
+
lastAssistantMessage = parsed.message.content;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const toolMessage of parsed.toolMessages) {
|
|
153
|
+
messages.push(toolMessage);
|
|
154
|
+
}
|
|
155
|
+
for (const change of parsed.fileChanges) {
|
|
156
|
+
fileChanges.set(change.path, change);
|
|
157
|
+
}
|
|
158
|
+
totalTokens += parsed.tokenCount;
|
|
159
|
+
}
|
|
160
|
+
if (messages.length === 0) {
|
|
161
|
+
const composerData = this.getJsonValue(db, `composerData:${composerId}`);
|
|
162
|
+
const fallbackPayloads = this.extractMessagesFromComposerData(composerData, composerId);
|
|
163
|
+
for (const payload of fallbackPayloads) {
|
|
164
|
+
const parsed = this.parseCursorPayload(payload);
|
|
165
|
+
if (parsed.message) {
|
|
166
|
+
messages.push(parsed.message);
|
|
167
|
+
if (!sessionStartedAt && parsed.message.timestamp) {
|
|
168
|
+
sessionStartedAt = parsed.message.timestamp;
|
|
169
|
+
}
|
|
170
|
+
if (parsed.message.role === "assistant") {
|
|
171
|
+
lastAssistantMessage = parsed.message.content;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const toolMessage of parsed.toolMessages) {
|
|
175
|
+
messages.push(toolMessage);
|
|
176
|
+
}
|
|
177
|
+
for (const change of parsed.fileChanges) {
|
|
178
|
+
fileChanges.set(change.path, change);
|
|
179
|
+
}
|
|
180
|
+
totalTokens += parsed.tokenCount;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (messages.length === 0) {
|
|
184
|
+
const legacy = this.getJsonValue(db, "workbench.panel.aichat.view.aichat.chatdata");
|
|
185
|
+
const legacyPayloads = this.extractMessagesFromLegacy(legacy, composerId);
|
|
186
|
+
for (const payload of legacyPayloads) {
|
|
187
|
+
const parsed = this.parseCursorPayload(payload);
|
|
188
|
+
if (parsed.message) {
|
|
189
|
+
messages.push(parsed.message);
|
|
190
|
+
if (!sessionStartedAt && parsed.message.timestamp) {
|
|
191
|
+
sessionStartedAt = parsed.message.timestamp;
|
|
192
|
+
}
|
|
193
|
+
if (parsed.message.role === "assistant") {
|
|
194
|
+
lastAssistantMessage = parsed.message.content;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const toolMessage of parsed.toolMessages) {
|
|
198
|
+
messages.push(toolMessage);
|
|
199
|
+
}
|
|
200
|
+
for (const change of parsed.fileChanges) {
|
|
201
|
+
fileChanges.set(change.path, change);
|
|
202
|
+
}
|
|
203
|
+
totalTokens += parsed.tokenCount;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
db.close();
|
|
209
|
+
}
|
|
210
|
+
messages.sort((a, b) => {
|
|
211
|
+
const aTime = a.timestamp || "";
|
|
212
|
+
const bTime = b.timestamp || "";
|
|
213
|
+
return aTime.localeCompare(bTime);
|
|
214
|
+
});
|
|
215
|
+
const projectContext = await extractProjectContext(projectPath);
|
|
216
|
+
const analysis = analyzeConversation(messages);
|
|
217
|
+
const session = {
|
|
218
|
+
version: "1.0",
|
|
219
|
+
source: "cursor",
|
|
220
|
+
capturedAt: new Date().toISOString(),
|
|
221
|
+
sessionId,
|
|
222
|
+
sessionStartedAt,
|
|
223
|
+
project: {
|
|
224
|
+
...projectContext,
|
|
225
|
+
path: projectContext.path || projectPath,
|
|
226
|
+
name: projectContext.name || path.basename(projectPath),
|
|
227
|
+
},
|
|
228
|
+
conversation: {
|
|
229
|
+
messageCount: messages.length,
|
|
230
|
+
estimatedTokens: totalTokens,
|
|
231
|
+
messages,
|
|
232
|
+
},
|
|
233
|
+
filesChanged: Array.from(fileChanges.values()),
|
|
234
|
+
decisions: analysis.decisions,
|
|
235
|
+
blockers: analysis.blockers,
|
|
236
|
+
task: {
|
|
237
|
+
description: analysis.taskDescription,
|
|
238
|
+
completed: analysis.completedSteps,
|
|
239
|
+
remaining: [],
|
|
240
|
+
inProgress: lastAssistantMessage
|
|
241
|
+
? lastAssistantMessage.substring(0, 200)
|
|
242
|
+
: undefined,
|
|
243
|
+
blockers: analysis.blockers,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
return validateSession(session);
|
|
247
|
+
}
|
|
248
|
+
async captureLatest(projectPath) {
|
|
249
|
+
const sessions = await this.listSessions(projectPath);
|
|
250
|
+
if (sessions.length === 0) {
|
|
251
|
+
throw new Error(projectPath
|
|
252
|
+
? `No Cursor sessions found for project: ${projectPath}`
|
|
253
|
+
: "No Cursor sessions found");
|
|
254
|
+
}
|
|
255
|
+
return this.capture(sessions[0].id);
|
|
256
|
+
}
|
|
257
|
+
openDatabase(dbPath) {
|
|
258
|
+
return new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
259
|
+
}
|
|
260
|
+
getAvailableTables(db) {
|
|
261
|
+
const rows = db
|
|
262
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('ItemTable', 'cursorDiskKV')")
|
|
263
|
+
.all();
|
|
264
|
+
return rows.map((row) => row.name);
|
|
265
|
+
}
|
|
266
|
+
getValue(db, key) {
|
|
267
|
+
for (const table of this.getAvailableTables(db)) {
|
|
268
|
+
const row = db
|
|
269
|
+
.prepare(`SELECT value FROM ${table} WHERE key = ? LIMIT 1`)
|
|
270
|
+
.get(key);
|
|
271
|
+
if (row?.value != null) {
|
|
272
|
+
return row.value;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
getJsonValue(db, key) {
|
|
278
|
+
const raw = this.getValue(db, key);
|
|
279
|
+
if (!raw) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
return JSON.parse(raw);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
readBubbleRows(db, composerId) {
|
|
290
|
+
const rows = [];
|
|
291
|
+
for (const table of this.getAvailableTables(db)) {
|
|
292
|
+
const results = db
|
|
293
|
+
.prepare(`SELECT key, value FROM ${table} WHERE key LIKE ? ORDER BY key`)
|
|
294
|
+
.all(`bubbleId:${composerId}:%`);
|
|
295
|
+
for (const row of results) {
|
|
296
|
+
try {
|
|
297
|
+
rows.push({ key: row.key, value: JSON.parse(row.value) });
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Skip malformed rows.
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return rows;
|
|
305
|
+
}
|
|
306
|
+
readComposers(db) {
|
|
307
|
+
const modern = this.getJsonValue(db, "composer.composerData");
|
|
308
|
+
const modernComposers = this.normalizeComposerEntries(modern);
|
|
309
|
+
if (modernComposers.length > 0) {
|
|
310
|
+
return modernComposers;
|
|
311
|
+
}
|
|
312
|
+
const legacy = this.getJsonValue(db, "workbench.panel.aichat.view.aichat.chatdata");
|
|
313
|
+
return this.normalizeComposerEntries(legacy);
|
|
314
|
+
}
|
|
315
|
+
normalizeComposerEntries(payload) {
|
|
316
|
+
const items = this.findComposerArray(payload);
|
|
317
|
+
const composers = [];
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
if (!item || typeof item !== "object") {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const candidate = item;
|
|
323
|
+
const id = candidate.composerId ||
|
|
324
|
+
candidate.id ||
|
|
325
|
+
candidate.uuid;
|
|
326
|
+
if (!id) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const preview = candidate.preview ||
|
|
330
|
+
candidate.title ||
|
|
331
|
+
candidate.initialPrompt ||
|
|
332
|
+
candidate.lastMessagePreview;
|
|
333
|
+
composers.push({
|
|
334
|
+
id,
|
|
335
|
+
startedAt: this.normalizeTimestamp(candidate.createdAt ??
|
|
336
|
+
candidate.startedAt ??
|
|
337
|
+
candidate.firstMessageAt ??
|
|
338
|
+
candidate.timestamp),
|
|
339
|
+
lastActiveAt: this.normalizeTimestamp(candidate.lastUpdatedAt ??
|
|
340
|
+
candidate.lastActiveAt ??
|
|
341
|
+
candidate.updatedAt ??
|
|
342
|
+
candidate.timestamp),
|
|
343
|
+
messageCount: this.normalizeNumber(candidate.messageCount ??
|
|
344
|
+
(Array.isArray(candidate.messages) ? candidate.messages.length : undefined)),
|
|
345
|
+
preview,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return composers;
|
|
349
|
+
}
|
|
350
|
+
findComposerArray(payload) {
|
|
351
|
+
if (Array.isArray(payload)) {
|
|
352
|
+
return payload;
|
|
353
|
+
}
|
|
354
|
+
if (!payload || typeof payload !== "object") {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
const obj = payload;
|
|
358
|
+
if (Array.isArray(obj.allComposers)) {
|
|
359
|
+
return obj.allComposers;
|
|
360
|
+
}
|
|
361
|
+
if (Array.isArray(obj.composers)) {
|
|
362
|
+
return obj.composers;
|
|
363
|
+
}
|
|
364
|
+
if (Array.isArray(obj.sessions)) {
|
|
365
|
+
return obj.sessions;
|
|
366
|
+
}
|
|
367
|
+
if (Array.isArray(obj.chats)) {
|
|
368
|
+
return obj.chats;
|
|
369
|
+
}
|
|
370
|
+
return [];
|
|
371
|
+
}
|
|
372
|
+
extractMessagesFromComposerData(payload, composerId) {
|
|
373
|
+
if (!payload || typeof payload !== "object") {
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
const obj = payload;
|
|
377
|
+
if (Array.isArray(obj.messages)) {
|
|
378
|
+
return obj.messages;
|
|
379
|
+
}
|
|
380
|
+
if (Array.isArray(obj.bubbles)) {
|
|
381
|
+
return obj.bubbles;
|
|
382
|
+
}
|
|
383
|
+
if (Array.isArray(obj.composers) ||
|
|
384
|
+
Array.isArray(obj.allComposers) ||
|
|
385
|
+
Array.isArray(obj.sessions)) {
|
|
386
|
+
const composers = this.findComposerArray(obj);
|
|
387
|
+
for (const entry of composers) {
|
|
388
|
+
if (!entry || typeof entry !== "object") {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const composer = entry;
|
|
392
|
+
const id = composer.composerId ||
|
|
393
|
+
composer.id;
|
|
394
|
+
if (id !== composerId) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (Array.isArray(composer.messages)) {
|
|
398
|
+
return composer.messages;
|
|
399
|
+
}
|
|
400
|
+
if (Array.isArray(composer.bubbles)) {
|
|
401
|
+
return composer.bubbles;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
extractMessagesFromLegacy(payload, composerId) {
|
|
408
|
+
if (!payload || typeof payload !== "object") {
|
|
409
|
+
return [];
|
|
410
|
+
}
|
|
411
|
+
const obj = payload;
|
|
412
|
+
const containers = this.findComposerArray(obj);
|
|
413
|
+
for (const entry of containers) {
|
|
414
|
+
if (!entry || typeof entry !== "object") {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const session = entry;
|
|
418
|
+
const id = session.composerId ||
|
|
419
|
+
session.id;
|
|
420
|
+
if (id && id !== composerId) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (Array.isArray(session.messages)) {
|
|
424
|
+
return session.messages;
|
|
425
|
+
}
|
|
426
|
+
if (Array.isArray(session.bubbles)) {
|
|
427
|
+
return session.bubbles;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return [];
|
|
431
|
+
}
|
|
432
|
+
parseCursorPayload(payload) {
|
|
433
|
+
if (!payload || typeof payload !== "object") {
|
|
434
|
+
return { toolMessages: [], fileChanges: [], tokenCount: 0 };
|
|
435
|
+
}
|
|
436
|
+
const obj = payload;
|
|
437
|
+
const timestamp = this.normalizeTimestamp(obj.timestamp ?? obj.createdAt ?? obj.time);
|
|
438
|
+
const tokenCount = this.normalizeNumber(obj.tokenCount ??
|
|
439
|
+
(obj.usage && typeof obj.usage === "object"
|
|
440
|
+
? obj.usage.total_tokens
|
|
441
|
+
: undefined)) || 0;
|
|
442
|
+
const role = this.mapRole(obj.role ||
|
|
443
|
+
obj.type ||
|
|
444
|
+
obj.sender);
|
|
445
|
+
const text = this.extractText(obj);
|
|
446
|
+
const parsed = {
|
|
447
|
+
toolMessages: [],
|
|
448
|
+
fileChanges: [],
|
|
449
|
+
tokenCount,
|
|
450
|
+
};
|
|
451
|
+
if (text) {
|
|
452
|
+
parsed.message = {
|
|
453
|
+
role,
|
|
454
|
+
content: text,
|
|
455
|
+
timestamp,
|
|
456
|
+
tokenCount: tokenCount || undefined,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const nestedTool = obj.tool;
|
|
460
|
+
const nestedToolName = nestedTool &&
|
|
461
|
+
typeof nestedTool === "object" &&
|
|
462
|
+
typeof nestedTool.name === "string"
|
|
463
|
+
? nestedTool.name
|
|
464
|
+
: undefined;
|
|
465
|
+
const toolName = (typeof obj.toolName === "string" ? obj.toolName : undefined) ||
|
|
466
|
+
nestedToolName;
|
|
467
|
+
if (toolName) {
|
|
468
|
+
parsed.toolMessages.push({
|
|
469
|
+
role: "tool",
|
|
470
|
+
content: JSON.stringify(obj.input ?? obj.toolInput ?? {}),
|
|
471
|
+
toolName,
|
|
472
|
+
timestamp,
|
|
473
|
+
});
|
|
474
|
+
const toolFileChange = this.fileChangeFromPayload(toolName, obj);
|
|
475
|
+
if (toolFileChange) {
|
|
476
|
+
parsed.fileChanges.push(toolFileChange);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (Array.isArray(obj.toolCalls)) {
|
|
480
|
+
for (const toolCall of obj.toolCalls) {
|
|
481
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const call = toolCall;
|
|
485
|
+
const callName = call.name;
|
|
486
|
+
if (!callName) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
parsed.toolMessages.push({
|
|
490
|
+
role: "tool",
|
|
491
|
+
content: JSON.stringify(call.input ?? {}),
|
|
492
|
+
toolName: callName,
|
|
493
|
+
timestamp,
|
|
494
|
+
});
|
|
495
|
+
const callPayload = call.input && typeof call.input === "object"
|
|
496
|
+
? call.input
|
|
497
|
+
: call;
|
|
498
|
+
const callChange = this.fileChangeFromPayload(callName, callPayload);
|
|
499
|
+
if (callChange) {
|
|
500
|
+
parsed.fileChanges.push(callChange);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const directChange = this.fileChangeFromPayload("edit_file", obj);
|
|
505
|
+
if (directChange) {
|
|
506
|
+
parsed.fileChanges.push(directChange);
|
|
507
|
+
}
|
|
508
|
+
return parsed;
|
|
509
|
+
}
|
|
510
|
+
fileChangeFromPayload(toolNameRaw, payload) {
|
|
511
|
+
const filePath = payload.path ||
|
|
512
|
+
payload.filePath ||
|
|
513
|
+
payload.file_path;
|
|
514
|
+
if (!filePath) {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
const toolName = toolNameRaw.toLowerCase();
|
|
518
|
+
const changeType = toolName.includes("write") || toolName.includes("create")
|
|
519
|
+
? "created"
|
|
520
|
+
: toolName.includes("delete")
|
|
521
|
+
? "deleted"
|
|
522
|
+
: "modified";
|
|
523
|
+
const diff = payload.content ||
|
|
524
|
+
payload.diff ||
|
|
525
|
+
payload.new_content;
|
|
526
|
+
const ext = path.extname(filePath).slice(1);
|
|
527
|
+
return {
|
|
528
|
+
path: filePath,
|
|
529
|
+
changeType,
|
|
530
|
+
diff,
|
|
531
|
+
language: ext || undefined,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
extractText(payload) {
|
|
535
|
+
const directContent = payload.content;
|
|
536
|
+
if (typeof directContent === "string") {
|
|
537
|
+
return directContent.trim();
|
|
538
|
+
}
|
|
539
|
+
if (typeof payload.text === "string") {
|
|
540
|
+
return payload.text.trim();
|
|
541
|
+
}
|
|
542
|
+
if (Array.isArray(directContent)) {
|
|
543
|
+
const textParts = [];
|
|
544
|
+
for (const item of directContent) {
|
|
545
|
+
if (typeof item === "string") {
|
|
546
|
+
textParts.push(item);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (!item || typeof item !== "object") {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
const block = item;
|
|
553
|
+
if (typeof block.text === "string") {
|
|
554
|
+
textParts.push(block.text);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return textParts.join("\n").trim();
|
|
558
|
+
}
|
|
559
|
+
const nestedMessage = payload.message;
|
|
560
|
+
if (nestedMessage && typeof nestedMessage === "object") {
|
|
561
|
+
const nested = nestedMessage;
|
|
562
|
+
if (typeof nested.content === "string") {
|
|
563
|
+
return nested.content.trim();
|
|
564
|
+
}
|
|
565
|
+
if (Array.isArray(nested.content)) {
|
|
566
|
+
const parts = [];
|
|
567
|
+
for (const item of nested.content) {
|
|
568
|
+
if (typeof item === "string") {
|
|
569
|
+
parts.push(item);
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (!item || typeof item !== "object") {
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const block = item;
|
|
576
|
+
if (typeof block.text === "string") {
|
|
577
|
+
parts.push(block.text);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return parts.join("\n").trim();
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return "";
|
|
584
|
+
}
|
|
585
|
+
mapRole(rawRole) {
|
|
586
|
+
const role = (rawRole || "").toLowerCase();
|
|
587
|
+
if (role === "user" || role === "assistant" || role === "system" || role === "tool") {
|
|
588
|
+
return role;
|
|
589
|
+
}
|
|
590
|
+
if (role === "developer") {
|
|
591
|
+
return "system";
|
|
592
|
+
}
|
|
593
|
+
if (role === "human") {
|
|
594
|
+
return "user";
|
|
595
|
+
}
|
|
596
|
+
if (role === "ai") {
|
|
597
|
+
return "assistant";
|
|
598
|
+
}
|
|
599
|
+
return "assistant";
|
|
600
|
+
}
|
|
601
|
+
normalizeTimestamp(value) {
|
|
602
|
+
if (typeof value === "string") {
|
|
603
|
+
return value;
|
|
604
|
+
}
|
|
605
|
+
if (typeof value === "number") {
|
|
606
|
+
return new Date(value).toISOString();
|
|
607
|
+
}
|
|
608
|
+
return undefined;
|
|
609
|
+
}
|
|
610
|
+
normalizeNumber(value) {
|
|
611
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
612
|
+
return value;
|
|
613
|
+
}
|
|
614
|
+
if (typeof value === "string") {
|
|
615
|
+
const parsed = Number(value);
|
|
616
|
+
if (Number.isFinite(parsed)) {
|
|
617
|
+
return parsed;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
readWorkspaceProjectPath(workspaceDir) {
|
|
623
|
+
const workspaceJsonPath = path.join(workspaceDir, "workspace.json");
|
|
624
|
+
if (!fs.existsSync(workspaceJsonPath)) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const parsed = JSON.parse(fs.readFileSync(workspaceJsonPath, "utf-8"));
|
|
629
|
+
if (!parsed.folder) {
|
|
630
|
+
return undefined;
|
|
631
|
+
}
|
|
632
|
+
return this.fromFileUri(parsed.folder);
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
635
|
+
return undefined;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
fromFileUri(folder) {
|
|
639
|
+
if (!folder.startsWith("file://")) {
|
|
640
|
+
return folder;
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
const url = new URL(folder);
|
|
644
|
+
let pathname = decodeURIComponent(url.pathname);
|
|
645
|
+
if (process.platform === "win32" && pathname.startsWith("/")) {
|
|
646
|
+
pathname = pathname.slice(1);
|
|
647
|
+
}
|
|
648
|
+
if (process.platform === "win32") {
|
|
649
|
+
pathname = pathname.replace(/\//g, "\\");
|
|
650
|
+
}
|
|
651
|
+
return pathname;
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
return folder.replace(/^file:\/\//, "");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
pathsEqual(a, b) {
|
|
658
|
+
const normalize = (value) => path.resolve(value).replace(/[\\/]+/g, "/").toLowerCase();
|
|
659
|
+
return normalize(a) === normalize(b);
|
|
660
|
+
}
|
|
661
|
+
matchesWorkspaceHash(projectPath, workspaceHash) {
|
|
662
|
+
const normalizedPath = path.resolve(projectPath).replace(/\\/g, "/");
|
|
663
|
+
const uriPath = normalizedPath.startsWith("/") ? normalizedPath : `/${normalizedPath}`;
|
|
664
|
+
const fileUri = `file://${uriPath}`;
|
|
665
|
+
const rawCandidates = [projectPath, normalizedPath, fileUri, encodeURI(fileUri)];
|
|
666
|
+
const hashCandidates = new Set();
|
|
667
|
+
for (const value of rawCandidates) {
|
|
668
|
+
hashCandidates.add(createHash("md5").update(value).digest("hex"));
|
|
669
|
+
hashCandidates.add(createHash("sha1").update(value).digest("hex"));
|
|
670
|
+
hashCandidates.add(createHash("sha256").update(value).digest("hex"));
|
|
671
|
+
}
|
|
672
|
+
return hashCandidates.has(workspaceHash.toLowerCase());
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
//# sourceMappingURL=adapter.js.map
|