agentlytics 0.1.19 → 0.1.20
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/cache.js +3 -2
- package/editors/antigravity.js +504 -31
- package/editors/base.js +87 -0
- package/editors/claude.js +11 -1
- package/editors/codex.js +11 -0
- package/editors/copilot.js +11 -1
- package/editors/cursor.js +11 -1
- package/editors/gemini.js +11 -1
- package/editors/goose.js +30 -8
- package/editors/index.js +40 -1
- package/editors/kiro.js +11 -1
- package/editors/opencode.js +4 -22
- package/editors/vscode.js +11 -1
- package/editors/windsurf.js +21 -10
- package/editors/zed.js +23 -3
- package/index.js +40 -38
- package/package.json +1 -1
- package/server.js +81 -1
- package/ui/src/App.jsx +75 -16
- package/ui/src/components/AiAuditCard.jsx +4 -5
- package/ui/src/components/AnimatedLoader.jsx +14 -0
- package/ui/src/lib/api.js +13 -0
- package/ui/src/pages/Artifacts.jsx +600 -0
- package/ui/src/pages/CostAnalysis.jsx +2 -1
- package/ui/src/pages/Dashboard.jsx +2 -1
- package/ui/src/pages/ProjectDetail.jsx +3 -1
- package/ui/src/pages/Projects.jsx +2 -1
- package/ui/src/pages/RelayDashboard.jsx +2 -1
- package/ui/src/pages/Settings.jsx +2 -1
- package/ui/src/pages/Subscriptions.jsx +2 -1
package/cache.js
CHANGED
|
@@ -771,8 +771,8 @@ function safeParseJson(s) {
|
|
|
771
771
|
* Async version of scanAll that yields the event loop between iterations.
|
|
772
772
|
* Required for SSE streaming so progress events actually flush to the client.
|
|
773
773
|
*/
|
|
774
|
-
async function scanAllAsync(onProgress) {
|
|
775
|
-
const chats = getAllChats();
|
|
774
|
+
async function scanAllAsync(onProgress, opts = {}) {
|
|
775
|
+
const chats = opts.chats || getAllChats();
|
|
776
776
|
const total = chats.length;
|
|
777
777
|
let scanned = 0;
|
|
778
778
|
let analyzed = 0;
|
|
@@ -1375,6 +1375,7 @@ function getDb() { return db; }
|
|
|
1375
1375
|
module.exports = {
|
|
1376
1376
|
initDb,
|
|
1377
1377
|
scanAll,
|
|
1378
|
+
scanAllAsync,
|
|
1378
1379
|
getCachedChats,
|
|
1379
1380
|
countCachedChats,
|
|
1380
1381
|
getCachedOverview,
|
package/editors/antigravity.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
1
|
+
const { execSync, execFileSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
2
4
|
const os = require('os');
|
|
5
|
+
const Database = require('better-sqlite3');
|
|
6
|
+
const { getAppDataPath } = require('./base');
|
|
7
|
+
|
|
8
|
+
const HOME = os.homedir();
|
|
9
|
+
const ANTIGRAVITY_USER_DIR = path.join(getAppDataPath('Antigravity'), 'User');
|
|
10
|
+
const ANTIGRAVITY_GLOBAL_STORAGE_DB = path.join(ANTIGRAVITY_USER_DIR, 'globalStorage', 'state.vscdb');
|
|
11
|
+
const ANTIGRAVITY_BRAIN_DIR = path.join(HOME, '.gemini', 'antigravity', 'brain');
|
|
12
|
+
const OFFLINE_TRAJECTORY_SUMMARIES_KEYS = [
|
|
13
|
+
'antigravityUnifiedStateSync.trajectorySummaries',
|
|
14
|
+
'unifiedStateSync.trajectorySummaries',
|
|
15
|
+
];
|
|
3
16
|
|
|
4
17
|
// Static fallback for legacy placeholders no longer returned by the LS
|
|
5
18
|
const LEGACY_MODEL_MAP = {
|
|
@@ -53,6 +66,412 @@ function normalizeModel(modelId) {
|
|
|
53
66
|
return modelId;
|
|
54
67
|
}
|
|
55
68
|
|
|
69
|
+
function fileUriToPath(uri) {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = new URL(uri);
|
|
72
|
+
if (parsed.protocol !== 'file:') return null;
|
|
73
|
+
let filePath = decodeURIComponent(parsed.pathname);
|
|
74
|
+
if (IS_WINDOWS && /^\/[A-Za-z]:/.test(filePath)) filePath = filePath.slice(1);
|
|
75
|
+
return filePath || null;
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function base64ToBytes(b64) {
|
|
82
|
+
try {
|
|
83
|
+
return Uint8Array.from(Buffer.from(String(b64 || '').trim(), 'base64'));
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function bytesToUtf8(bytes) {
|
|
90
|
+
try {
|
|
91
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readVarint(buf, offset) {
|
|
98
|
+
let value = 0;
|
|
99
|
+
let shift = 0;
|
|
100
|
+
let i = offset;
|
|
101
|
+
while (i < buf.length) {
|
|
102
|
+
const b = buf[i];
|
|
103
|
+
i += 1;
|
|
104
|
+
value += (b & 0x7f) * (2 ** shift);
|
|
105
|
+
if ((b & 0x80) === 0) return { value, offset: i };
|
|
106
|
+
shift += 7;
|
|
107
|
+
if (shift > 53) return null;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readLengthDelimited(buf, offset) {
|
|
113
|
+
const lenRes = readVarint(buf, offset);
|
|
114
|
+
if (!lenRes) return null;
|
|
115
|
+
const len = lenRes.value;
|
|
116
|
+
const start = lenRes.offset;
|
|
117
|
+
const end = start + len;
|
|
118
|
+
if (end > buf.length) return null;
|
|
119
|
+
return { bytes: buf.subarray(start, end), offset: end };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function skipField(buf, offset, wireType) {
|
|
123
|
+
if (wireType === 0) {
|
|
124
|
+
const v = readVarint(buf, offset);
|
|
125
|
+
return v ? { offset: v.offset } : null;
|
|
126
|
+
}
|
|
127
|
+
if (wireType === 1) {
|
|
128
|
+
const end = offset + 8;
|
|
129
|
+
return end <= buf.length ? { offset: end } : null;
|
|
130
|
+
}
|
|
131
|
+
if (wireType === 2) {
|
|
132
|
+
const ld = readLengthDelimited(buf, offset);
|
|
133
|
+
return ld ? { offset: ld.offset } : null;
|
|
134
|
+
}
|
|
135
|
+
if (wireType === 5) {
|
|
136
|
+
const end = offset + 4;
|
|
137
|
+
return end <= buf.length ? { offset: end } : null;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function* iterAllUtf8StringsInProto(buf, maxDepth, depth = 0) {
|
|
143
|
+
if (depth > maxDepth) return;
|
|
144
|
+
let offset = 0;
|
|
145
|
+
while (offset < buf.length) {
|
|
146
|
+
const tagRes = readVarint(buf, offset);
|
|
147
|
+
if (!tagRes) return;
|
|
148
|
+
offset = tagRes.offset;
|
|
149
|
+
|
|
150
|
+
const wireType = tagRes.value & 0x7;
|
|
151
|
+
if (wireType !== 2) {
|
|
152
|
+
const skipped = skipField(buf, offset, wireType);
|
|
153
|
+
if (!skipped) return;
|
|
154
|
+
offset = skipped.offset;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const ld = readLengthDelimited(buf, offset);
|
|
159
|
+
if (!ld) return;
|
|
160
|
+
offset = ld.offset;
|
|
161
|
+
|
|
162
|
+
const asString = bytesToUtf8(ld.bytes);
|
|
163
|
+
if (asString !== null) yield asString;
|
|
164
|
+
yield* iterAllUtf8StringsInProto(ld.bytes, maxDepth, depth + 1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function parseTimestampMessage(bytes) {
|
|
169
|
+
let seconds = null;
|
|
170
|
+
let nanos = 0;
|
|
171
|
+
let offset = 0;
|
|
172
|
+
|
|
173
|
+
while (offset < bytes.length) {
|
|
174
|
+
const tagRes = readVarint(bytes, offset);
|
|
175
|
+
if (!tagRes) return null;
|
|
176
|
+
offset = tagRes.offset;
|
|
177
|
+
|
|
178
|
+
const fieldNumber = tagRes.value >>> 3;
|
|
179
|
+
const wireType = tagRes.value & 0x7;
|
|
180
|
+
|
|
181
|
+
if (wireType === 0) {
|
|
182
|
+
const valueRes = readVarint(bytes, offset);
|
|
183
|
+
if (!valueRes) return null;
|
|
184
|
+
offset = valueRes.offset;
|
|
185
|
+
if (fieldNumber === 1) seconds = valueRes.value;
|
|
186
|
+
if (fieldNumber === 2) nanos = valueRes.value;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const skipped = skipField(bytes, offset, wireType);
|
|
191
|
+
if (!skipped) return null;
|
|
192
|
+
offset = skipped.offset;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (seconds == null) return null;
|
|
196
|
+
if (seconds < 946684800 || seconds > 4102444800) return null;
|
|
197
|
+
if (nanos >= 1e9) nanos = 0;
|
|
198
|
+
|
|
199
|
+
return Math.round((seconds * 1000) + (nanos / 1e6));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function findTimestampInProto(bytes, maxDepth = 2, depth = 0) {
|
|
203
|
+
const direct = parseTimestampMessage(bytes);
|
|
204
|
+
if (direct) return direct;
|
|
205
|
+
if (depth >= maxDepth) return null;
|
|
206
|
+
|
|
207
|
+
let offset = 0;
|
|
208
|
+
while (offset < bytes.length) {
|
|
209
|
+
const tagRes = readVarint(bytes, offset);
|
|
210
|
+
if (!tagRes) return null;
|
|
211
|
+
offset = tagRes.offset;
|
|
212
|
+
|
|
213
|
+
const wireType = tagRes.value & 0x7;
|
|
214
|
+
if (wireType !== 2) {
|
|
215
|
+
const skipped = skipField(bytes, offset, wireType);
|
|
216
|
+
if (!skipped) return null;
|
|
217
|
+
offset = skipped.offset;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const ld = readLengthDelimited(bytes, offset);
|
|
222
|
+
if (!ld) return null;
|
|
223
|
+
offset = ld.offset;
|
|
224
|
+
|
|
225
|
+
const nested = findTimestampInProto(ld.bytes, maxDepth, depth + 1);
|
|
226
|
+
if (nested) return nested;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function readGlobalStateValue(key) {
|
|
233
|
+
if (!fs.existsSync(ANTIGRAVITY_GLOBAL_STORAGE_DB)) return null;
|
|
234
|
+
|
|
235
|
+
let db = null;
|
|
236
|
+
try {
|
|
237
|
+
db = new Database(ANTIGRAVITY_GLOBAL_STORAGE_DB, { readonly: true, fileMustExist: true });
|
|
238
|
+
const row = db.prepare('SELECT value FROM ItemTable WHERE key = ?').get(key);
|
|
239
|
+
if (!row) return null;
|
|
240
|
+
const v = row.value;
|
|
241
|
+
if (typeof v === 'string') return v;
|
|
242
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return Buffer.from(v).toString('utf-8');
|
|
243
|
+
return v == null ? null : String(v);
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
} finally {
|
|
247
|
+
if (db) db.close();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function extractFolderFromSummaryProtoBytes(summaryProtoBytes) {
|
|
252
|
+
for (const s of iterAllUtf8StringsInProto(summaryProtoBytes, 6)) {
|
|
253
|
+
const match = s.match(/#?file:\/\/[^\s\x00-\x1f"]+/);
|
|
254
|
+
if (!match) continue;
|
|
255
|
+
let uri = match[0];
|
|
256
|
+
if (uri.startsWith('#')) uri = uri.slice(1);
|
|
257
|
+
const folder = fileUriToPath(uri);
|
|
258
|
+
if (folder) return folder;
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function extractOfflineMetaFromSummaryProtoBytes(summaryProtoBytes) {
|
|
264
|
+
let title = null;
|
|
265
|
+
let primaryCount = 0;
|
|
266
|
+
let secondaryCount = 0;
|
|
267
|
+
const timestamps = [];
|
|
268
|
+
|
|
269
|
+
let offset = 0;
|
|
270
|
+
while (offset < summaryProtoBytes.length) {
|
|
271
|
+
const tagRes = readVarint(summaryProtoBytes, offset);
|
|
272
|
+
if (!tagRes) break;
|
|
273
|
+
offset = tagRes.offset;
|
|
274
|
+
|
|
275
|
+
const fieldNumber = tagRes.value >>> 3;
|
|
276
|
+
const wireType = tagRes.value & 0x7;
|
|
277
|
+
|
|
278
|
+
if (wireType === 0) {
|
|
279
|
+
const valueRes = readVarint(summaryProtoBytes, offset);
|
|
280
|
+
if (!valueRes) break;
|
|
281
|
+
offset = valueRes.offset;
|
|
282
|
+
if (fieldNumber === 2) primaryCount = valueRes.value;
|
|
283
|
+
if (fieldNumber === 16) secondaryCount = valueRes.value;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (wireType === 2) {
|
|
288
|
+
const ld = readLengthDelimited(summaryProtoBytes, offset);
|
|
289
|
+
if (!ld) break;
|
|
290
|
+
offset = ld.offset;
|
|
291
|
+
|
|
292
|
+
if (fieldNumber === 1 && !title) {
|
|
293
|
+
const text = bytesToUtf8(ld.bytes);
|
|
294
|
+
if (text && text.trim()) title = text.trim();
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (fieldNumber === 3 || fieldNumber === 7 || fieldNumber === 10 || fieldNumber === 15) {
|
|
299
|
+
const ts = fieldNumber === 15 ? findTimestampInProto(ld.bytes, 2) : (parseTimestampMessage(ld.bytes) || findTimestampInProto(ld.bytes, 1));
|
|
300
|
+
if (ts) timestamps.push(ts);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const skipped = skipField(summaryProtoBytes, offset, wireType);
|
|
308
|
+
if (!skipped) break;
|
|
309
|
+
offset = skipped.offset;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const uniqueTimestamps = [...new Set(timestamps)].sort((a, b) => a - b);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
title,
|
|
316
|
+
folder: extractFolderFromSummaryProtoBytes(summaryProtoBytes),
|
|
317
|
+
bubbleCount: Math.max(primaryCount || 0, secondaryCount || 0),
|
|
318
|
+
createdAt: uniqueTimestamps[0] || null,
|
|
319
|
+
lastUpdatedAt: uniqueTimestamps[uniqueTimestamps.length - 1] || null,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buildOfflineMetaMapFromGlobalStateTrajectorySummariesValue(outerValueBase64) {
|
|
324
|
+
const outerBytes = base64ToBytes(outerValueBase64);
|
|
325
|
+
if (!outerBytes) return {};
|
|
326
|
+
|
|
327
|
+
const chats = {};
|
|
328
|
+
let offset = 0;
|
|
329
|
+
|
|
330
|
+
while (offset < outerBytes.length) {
|
|
331
|
+
const tagRes = readVarint(outerBytes, offset);
|
|
332
|
+
if (!tagRes) break;
|
|
333
|
+
offset = tagRes.offset;
|
|
334
|
+
|
|
335
|
+
const fieldNumber = tagRes.value >>> 3;
|
|
336
|
+
const wireType = tagRes.value & 0x7;
|
|
337
|
+
if (fieldNumber !== 1 || wireType !== 2) {
|
|
338
|
+
const skipped = skipField(outerBytes, offset, wireType);
|
|
339
|
+
if (!skipped) break;
|
|
340
|
+
offset = skipped.offset;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const entryLd = readLengthDelimited(outerBytes, offset);
|
|
345
|
+
if (!entryLd) break;
|
|
346
|
+
offset = entryLd.offset;
|
|
347
|
+
|
|
348
|
+
let composerId = null;
|
|
349
|
+
let summaryBase64 = null;
|
|
350
|
+
let entryOffset = 0;
|
|
351
|
+
|
|
352
|
+
while (entryOffset < entryLd.bytes.length) {
|
|
353
|
+
const entryTag = readVarint(entryLd.bytes, entryOffset);
|
|
354
|
+
if (!entryTag) break;
|
|
355
|
+
entryOffset = entryTag.offset;
|
|
356
|
+
|
|
357
|
+
const entryField = entryTag.value >>> 3;
|
|
358
|
+
const entryWire = entryTag.value & 0x7;
|
|
359
|
+
|
|
360
|
+
if (entryField === 1 && entryWire === 2) {
|
|
361
|
+
const keyLd = readLengthDelimited(entryLd.bytes, entryOffset);
|
|
362
|
+
if (!keyLd) break;
|
|
363
|
+
entryOffset = keyLd.offset;
|
|
364
|
+
composerId = bytesToUtf8(keyLd.bytes);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (entryField === 2 && entryWire === 2) {
|
|
369
|
+
const valueLd = readLengthDelimited(entryLd.bytes, entryOffset);
|
|
370
|
+
if (!valueLd) break;
|
|
371
|
+
entryOffset = valueLd.offset;
|
|
372
|
+
|
|
373
|
+
let valueOffset = 0;
|
|
374
|
+
while (valueOffset < valueLd.bytes.length) {
|
|
375
|
+
const valueTag = readVarint(valueLd.bytes, valueOffset);
|
|
376
|
+
if (!valueTag) break;
|
|
377
|
+
valueOffset = valueTag.offset;
|
|
378
|
+
|
|
379
|
+
const valueField = valueTag.value >>> 3;
|
|
380
|
+
const valueWire = valueTag.value & 0x7;
|
|
381
|
+
if (valueField === 1 && valueWire === 2) {
|
|
382
|
+
const summaryLd = readLengthDelimited(valueLd.bytes, valueOffset);
|
|
383
|
+
if (!summaryLd) break;
|
|
384
|
+
valueOffset = summaryLd.offset;
|
|
385
|
+
summaryBase64 = bytesToUtf8(summaryLd.bytes);
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const skipped = skipField(valueLd.bytes, valueOffset, valueWire);
|
|
390
|
+
if (!skipped) break;
|
|
391
|
+
valueOffset = skipped.offset;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const skipped = skipField(entryLd.bytes, entryOffset, entryWire);
|
|
398
|
+
if (!skipped) break;
|
|
399
|
+
entryOffset = skipped.offset;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!composerId || !summaryBase64) continue;
|
|
403
|
+
const summaryProtoBytes = base64ToBytes(summaryBase64);
|
|
404
|
+
if (!summaryProtoBytes) continue;
|
|
405
|
+
|
|
406
|
+
chats[composerId] = extractOfflineMetaFromSummaryProtoBytes(summaryProtoBytes);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return chats;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function getOfflineChats() {
|
|
413
|
+
for (const key of OFFLINE_TRAJECTORY_SUMMARIES_KEYS) {
|
|
414
|
+
const value = readGlobalStateValue(key);
|
|
415
|
+
if (!value) continue;
|
|
416
|
+
|
|
417
|
+
const map = buildOfflineMetaMapFromGlobalStateTrajectorySummariesValue(value);
|
|
418
|
+
const chats = Object.entries(map).map(([composerId, meta]) => ({
|
|
419
|
+
source: 'antigravity',
|
|
420
|
+
composerId,
|
|
421
|
+
name: meta.title || null,
|
|
422
|
+
createdAt: meta.createdAt || null,
|
|
423
|
+
lastUpdatedAt: meta.lastUpdatedAt || null,
|
|
424
|
+
mode: 'cascade',
|
|
425
|
+
folder: meta.folder || null,
|
|
426
|
+
encrypted: false,
|
|
427
|
+
bubbleCount: meta.bubbleCount || 0,
|
|
428
|
+
_stepCount: meta.bubbleCount || 0,
|
|
429
|
+
_type: 'antigravity-offline',
|
|
430
|
+
_dbPath: ANTIGRAVITY_GLOBAL_STORAGE_DB,
|
|
431
|
+
_rawSource: 'offline-global-state',
|
|
432
|
+
}));
|
|
433
|
+
|
|
434
|
+
if (chats.length > 0) {
|
|
435
|
+
return chats.sort((a, b) => (b.lastUpdatedAt || b.createdAt || 0) - (a.lastUpdatedAt || a.createdAt || 0));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function mergeChats(liveChats, offlineChats) {
|
|
443
|
+
if (liveChats.length === 0) return offlineChats;
|
|
444
|
+
if (offlineChats.length === 0) return liveChats;
|
|
445
|
+
|
|
446
|
+
const map = new Map();
|
|
447
|
+
|
|
448
|
+
for (const chat of offlineChats) {
|
|
449
|
+
map.set(chat.composerId, { ...chat });
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
for (const chat of liveChats) {
|
|
453
|
+
const existing = map.get(chat.composerId);
|
|
454
|
+
if (!existing) {
|
|
455
|
+
map.set(chat.composerId, chat);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
map.set(chat.composerId, {
|
|
460
|
+
...existing,
|
|
461
|
+
...chat,
|
|
462
|
+
name: chat.name || existing.name,
|
|
463
|
+
createdAt: chat.createdAt || existing.createdAt,
|
|
464
|
+
lastUpdatedAt: chat.lastUpdatedAt || existing.lastUpdatedAt,
|
|
465
|
+
folder: chat.folder || existing.folder,
|
|
466
|
+
bubbleCount: chat.bubbleCount || existing.bubbleCount,
|
|
467
|
+
_stepCount: chat._stepCount || existing._stepCount,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return Array.from(map.values()).sort((a, b) => (b.lastUpdatedAt || b.createdAt || 0) - (a.lastUpdatedAt || a.createdAt || 0));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
56
475
|
// ============================================================
|
|
57
476
|
// Cross-platform process utilities
|
|
58
477
|
// ============================================================
|
|
@@ -62,16 +481,19 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
62
481
|
function getProcessList() {
|
|
63
482
|
try {
|
|
64
483
|
if (IS_WINDOWS) {
|
|
65
|
-
|
|
484
|
+
// Use PowerShell Get-Process (WMIC is deprecated in Windows 10/11)
|
|
485
|
+
const output = execFileSync('powershell', ['-Command', 'Get-Process | Select-Object Id, Path, CommandLine | ConvertTo-Csv -NoTypeInformation'], {
|
|
66
486
|
encoding: 'utf-8',
|
|
67
487
|
maxBuffer: 10 * 1024 * 1024,
|
|
68
488
|
});
|
|
489
|
+
// Parse CSV: skip header
|
|
69
490
|
const lines = output.split('\n').slice(1);
|
|
70
491
|
return lines.map(line => {
|
|
71
492
|
const parts = line.split(',');
|
|
72
|
-
if (parts.length <
|
|
73
|
-
const
|
|
74
|
-
const
|
|
493
|
+
if (parts.length < 3) return null;
|
|
494
|
+
const pid = parts[0].trim().replace(/^"|"$/g, '');
|
|
495
|
+
const commandLine = parts[2].trim().replace(/^"|"$/g, '');
|
|
496
|
+
if (!pid || !commandLine) return null;
|
|
75
497
|
return { commandLine, pid };
|
|
76
498
|
}).filter(Boolean);
|
|
77
499
|
} else {
|
|
@@ -90,7 +512,8 @@ function getProcessList() {
|
|
|
90
512
|
function getListeningPorts(pid) {
|
|
91
513
|
try {
|
|
92
514
|
if (IS_WINDOWS) {
|
|
93
|
-
|
|
515
|
+
// Use PowerShell to get netstat output and filter by PID
|
|
516
|
+
const output = execFileSync('powershell', ['-Command', `netstat -ano | Select-String "${pid}$"`], {
|
|
94
517
|
encoding: 'utf-8',
|
|
95
518
|
maxBuffer: 10 * 1024 * 1024,
|
|
96
519
|
});
|
|
@@ -125,9 +548,10 @@ function getListeningPorts(pid) {
|
|
|
125
548
|
// ============================================================
|
|
126
549
|
|
|
127
550
|
let _lsCache = null;
|
|
551
|
+
let _lsCacheCheckedAt = 0;
|
|
128
552
|
|
|
129
553
|
function findLanguageServer() {
|
|
130
|
-
if (_lsCache !== null) return _lsCache;
|
|
554
|
+
if (_lsCache !== null && (Date.now() - _lsCacheCheckedAt) < 10000) return _lsCache || null;
|
|
131
555
|
|
|
132
556
|
const serverProcessName = IS_WINDOWS
|
|
133
557
|
? 'language_server_windows'
|
|
@@ -158,10 +582,12 @@ function findLanguageServer() {
|
|
|
158
582
|
}
|
|
159
583
|
|
|
160
584
|
_lsCache = { port, csrf: csrfMatch[1], pid };
|
|
585
|
+
_lsCacheCheckedAt = Date.now();
|
|
161
586
|
return _lsCache;
|
|
162
587
|
}
|
|
163
588
|
|
|
164
589
|
_lsCache = false;
|
|
590
|
+
_lsCacheCheckedAt = Date.now();
|
|
165
591
|
return null;
|
|
166
592
|
}
|
|
167
593
|
|
|
@@ -197,30 +623,31 @@ const name = 'antigravity';
|
|
|
197
623
|
|
|
198
624
|
function getChats() {
|
|
199
625
|
const resp = callRpc('GetAllCascadeTrajectories', {});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
626
|
+
const liveChats = [];
|
|
627
|
+
|
|
628
|
+
if (resp && resp.trajectorySummaries) {
|
|
629
|
+
for (const [cascadeId, summary] of Object.entries(resp.trajectorySummaries)) {
|
|
630
|
+
const ws = (summary.workspaces || [])[0];
|
|
631
|
+
const folder = ws?.workspaceFolderAbsoluteUri?.replace('file://', '') || null;
|
|
632
|
+
const rawModel = summary.lastGeneratorModelUid;
|
|
633
|
+
liveChats.push({
|
|
634
|
+
source: 'antigravity',
|
|
635
|
+
composerId: cascadeId,
|
|
636
|
+
name: summary.summary || null,
|
|
637
|
+
createdAt: summary.createdTime ? new Date(summary.createdTime).getTime() : null,
|
|
638
|
+
lastUpdatedAt: summary.lastModifiedTime ? new Date(summary.lastModifiedTime).getTime() : null,
|
|
639
|
+
mode: 'cascade',
|
|
640
|
+
folder,
|
|
641
|
+
encrypted: false,
|
|
642
|
+
bubbleCount: summary.stepCount || 0,
|
|
643
|
+
_stepCount: summary.stepCount,
|
|
644
|
+
_model: rawModel ? normalizeModel(rawModel) : rawModel,
|
|
645
|
+
_rawModel: rawModel,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
221
648
|
}
|
|
222
649
|
|
|
223
|
-
return
|
|
650
|
+
return mergeChats(liveChats, getOfflineChats());
|
|
224
651
|
}
|
|
225
652
|
|
|
226
653
|
function getSteps(chat) {
|
|
@@ -500,8 +927,54 @@ function getUsage() {
|
|
|
500
927
|
};
|
|
501
928
|
}
|
|
502
929
|
|
|
503
|
-
function resetCache() { _lsCache = null; _modelMap = null; }
|
|
930
|
+
function resetCache() { _lsCache = null; _lsCacheCheckedAt = 0; _modelMap = null; }
|
|
504
931
|
|
|
505
932
|
const labels = { 'antigravity': 'Antigravity' };
|
|
506
933
|
|
|
507
|
-
|
|
934
|
+
function getArtifacts(folder) {
|
|
935
|
+
const { scanArtifacts } = require('./base');
|
|
936
|
+
const artifacts = folder ? scanArtifacts(folder, {
|
|
937
|
+
editor: 'antigravity',
|
|
938
|
+
label: 'Antigravity',
|
|
939
|
+
files: [],
|
|
940
|
+
dirs: ['.gemini/skills', '.gemini/rules', '.gemini/plans', '.gemini/workflows'],
|
|
941
|
+
}) : [];
|
|
942
|
+
|
|
943
|
+
// Add brain artifacts (task.md, implementation_plan.md, walkthrough.md) per session
|
|
944
|
+
if (fs.existsSync(ANTIGRAVITY_BRAIN_DIR)) {
|
|
945
|
+
try {
|
|
946
|
+
const sessions = fs.readdirSync(ANTIGRAVITY_BRAIN_DIR);
|
|
947
|
+
const brainFileNames = ['task.md', 'implementation_plan.md', 'walkthrough.md'];
|
|
948
|
+
for (const sessionId of sessions) {
|
|
949
|
+
const sessionDir = path.join(ANTIGRAVITY_BRAIN_DIR, sessionId);
|
|
950
|
+
try {
|
|
951
|
+
if (!fs.statSync(sessionDir).isDirectory()) continue;
|
|
952
|
+
} catch { continue; }
|
|
953
|
+
for (const fileName of brainFileNames) {
|
|
954
|
+
const filePath = path.join(sessionDir, fileName);
|
|
955
|
+
if (!fs.existsSync(filePath)) continue;
|
|
956
|
+
try {
|
|
957
|
+
const stat = fs.statSync(filePath);
|
|
958
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
959
|
+
if (!content.trim()) continue;
|
|
960
|
+
const lines = content.split('\n').length;
|
|
961
|
+
artifacts.push({
|
|
962
|
+
name: fileName,
|
|
963
|
+
path: filePath,
|
|
964
|
+
relativePath: `brain/${sessionId.slice(0, 8)}/${fileName}`,
|
|
965
|
+
size: stat.size,
|
|
966
|
+
lines,
|
|
967
|
+
modifiedAt: stat.mtimeMs,
|
|
968
|
+
editor: 'antigravity',
|
|
969
|
+
editorLabel: 'Antigravity',
|
|
970
|
+
});
|
|
971
|
+
} catch { /* skip unreadable */ }
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
} catch { /* skip if brain dir unreadable */ }
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return artifacts;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
module.exports = { name, labels, getChats, getMessages, resetCache, getUsage, getArtifacts };
|