memhouse 0.0.1 → 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 +22 -0
- package/README.md +59 -3
- package/bin/memhouse.js +487 -0
- package/cache.js +850 -0
- package/ch-ingest.js +329 -0
- package/ch-schema.js +152 -0
- package/clickhouse.js +87 -0
- package/editors/antigravity.js +1019 -0
- package/editors/base.js +321 -0
- package/editors/claude.js +386 -0
- package/editors/codebuff.js +338 -0
- package/editors/codex.js +537 -0
- package/editors/commandcode.js +166 -0
- package/editors/copilot-jetbrains.js +207 -0
- package/editors/copilot.js +260 -0
- package/editors/cursor-agent.js +202 -0
- package/editors/cursor.js +438 -0
- package/editors/gemini.js +231 -0
- package/editors/goose.js +358 -0
- package/editors/gsd.js +366 -0
- package/editors/index.js +179 -0
- package/editors/kiro.js +315 -0
- package/editors/opencode.js +374 -0
- package/editors/vscode.js +413 -0
- package/editors/windsurf.js +634 -0
- package/editors/zed.js +213 -0
- package/index.js +424 -2
- package/mcp-server.js +294 -0
- package/mem-house/DESIGN.md +119 -0
- package/mem-house/delivery/AGENT-INSTALL.md +75 -0
- package/mem-house/delivery/PROMPT.md +42 -0
- package/mem-house/delivery/install.sh +181 -0
- package/mem-house/delivery/kernel-install.md +76 -0
- package/mem-house/delivery/plugin/.claude-plugin/plugin.json +8 -0
- package/mem-house/delivery/skills/memhouse-search/SKILL.md +104 -0
- package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +94 -0
- package/mem-house/delivery/skills/memhouse-sql/SKILL.md +115 -0
- package/mem-house/rls.sql +17 -0
- package/mem-house/schema.sql +145 -0
- package/mem-house/server/queries.js +878 -0
- package/mem-house/server/server.js +228 -0
- package/mem-house/shipper/ship.js +402 -0
- package/misc/syntax-check.js +38 -0
- package/package.json +56 -7
- package/pricing.js +88 -0
- package/pricing.json +992 -0
- package/public/assets/index-BsgjOSTv.js +75 -0
- package/public/assets/index-DV6ONi_F.css +2 -0
- package/public/index.html +16 -0
- package/relay-client.js +306 -0
- package/relay-server.js +577 -0
- package/server.js +860 -0
- package/share-image.js +367 -0
- package/ui/eslint.config.js +29 -0
- package/ui/index.html +15 -0
- package/ui/package-lock.json +4229 -0
- package/ui/package.json +38 -0
- package/ui/src/App.jsx +364 -0
- package/ui/src/components/ActivityHeatmap.jsx +206 -0
- package/ui/src/components/AiAuditCard.jsx +254 -0
- package/ui/src/components/AnimatedLoader.jsx +14 -0
- package/ui/src/components/AnimatedLogo.jsx +96 -0
- package/ui/src/components/ChatSidebar.jsx +226 -0
- package/ui/src/components/DateRangePicker.jsx +104 -0
- package/ui/src/components/EditorDot.jsx +11 -0
- package/ui/src/components/EditorIcon.jsx +117 -0
- package/ui/src/components/KpiCard.jsx +9 -0
- package/ui/src/components/LiveFeed.jsx +128 -0
- package/ui/src/components/LoginScreen.jsx +81 -0
- package/ui/src/components/MessageRenderer.jsx +148 -0
- package/ui/src/components/PageHeader.jsx +11 -0
- package/ui/src/components/SectionTitle.jsx +3 -0
- package/ui/src/components/ShareModal.jsx +305 -0
- package/ui/src/components/TokenTimeline.jsx +258 -0
- package/ui/src/hooks/useLive.jsx +15 -0
- package/ui/src/index.css +83 -0
- package/ui/src/lib/api.js +343 -0
- package/ui/src/lib/constants.js +88 -0
- package/ui/src/lib/theme.jsx +23 -0
- package/ui/src/main.jsx +16 -0
- package/ui/src/pages/Artifacts.jsx +575 -0
- package/ui/src/pages/Compare.jsx +232 -0
- package/ui/src/pages/CostAnalysis.jsx +354 -0
- package/ui/src/pages/Dashboard.jsx +529 -0
- package/ui/src/pages/DeepAnalysis.jsx +495 -0
- package/ui/src/pages/GSD.jsx +726 -0
- package/ui/src/pages/MCPs.jsx +744 -0
- package/ui/src/pages/ProjectDetail.jsx +325 -0
- package/ui/src/pages/Projects.jsx +249 -0
- package/ui/src/pages/RelayDashboard.jsx +590 -0
- package/ui/src/pages/RelayUserDetail.jsx +466 -0
- package/ui/src/pages/Sessions.jsx +535 -0
- package/ui/src/pages/Settings.jsx +241 -0
- package/ui/src/pages/SqlViewer.jsx +344 -0
- package/ui/src/pages/Subscriptions.jsx +411 -0
- package/ui/vite.config.js +18 -0
|
@@ -0,0 +1,1019 @@
|
|
|
1
|
+
const { execSync, execFileSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
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
|
+
];
|
|
16
|
+
|
|
17
|
+
// Static fallback for legacy placeholders no longer returned by the LS
|
|
18
|
+
const LEGACY_MODEL_MAP = {
|
|
19
|
+
'MODEL_PLACEHOLDER_M1': 'Claude 3.5 Sonnet',
|
|
20
|
+
'MODEL_PLACEHOLDER_M2': 'Claude 3.5 Sonnet',
|
|
21
|
+
'MODEL_PLACEHOLDER_M3': 'Claude 3.5 Sonnet',
|
|
22
|
+
'MODEL_PLACEHOLDER_M4': 'Claude 3.5 Haiku',
|
|
23
|
+
'MODEL_PLACEHOLDER_M5': 'Claude 3.5 Haiku',
|
|
24
|
+
'MODEL_PLACEHOLDER_M6': 'Claude 3.5 Haiku',
|
|
25
|
+
'MODEL_PLACEHOLDER_M7': 'Claude 3.5 Sonnet',
|
|
26
|
+
'MODEL_PLACEHOLDER_M8': 'Claude 3.5 Sonnet',
|
|
27
|
+
'MODEL_PLACEHOLDER_M9': 'Claude 3.5 Sonnet',
|
|
28
|
+
'MODEL_PLACEHOLDER_M10': 'Claude 3.5 Sonnet',
|
|
29
|
+
'MODEL_CLAUDE_4_5_SONNET': 'Claude 4.5 Sonnet',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Dynamic model map populated from GetUserStatus RPC (placeholder → friendly label)
|
|
33
|
+
let _modelMap = null;
|
|
34
|
+
|
|
35
|
+
function getModelMap() {
|
|
36
|
+
if (_modelMap) return _modelMap;
|
|
37
|
+
_modelMap = { ...LEGACY_MODEL_MAP };
|
|
38
|
+
try {
|
|
39
|
+
const resp = callRpc('GetUserStatus', {});
|
|
40
|
+
const configs = resp?.userStatus?.cascadeModelConfigData?.clientModelConfigs || [];
|
|
41
|
+
for (const c of configs) {
|
|
42
|
+
const key = c.modelOrAlias?.model;
|
|
43
|
+
const label = c.label;
|
|
44
|
+
if (key && label) _modelMap[key] = label;
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
return _modelMap;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Convert friendly label → pricing-compatible model ID
|
|
51
|
+
// "Gemini 3.1 Pro (High)" → "gemini-3.1-pro"
|
|
52
|
+
// "Claude Sonnet 4.6 (Thinking)" → "claude-sonnet-4.6"
|
|
53
|
+
function labelToModelId(label) {
|
|
54
|
+
return label
|
|
55
|
+
.replace(/\s*\([^)]*\)\s*/g, '') // strip "(High)", "(Thinking)", etc.
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.replace(/\s+/g, '-'); // spaces → dashes
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeModel(modelId) {
|
|
62
|
+
if (!modelId) return null;
|
|
63
|
+
const map = getModelMap();
|
|
64
|
+
const label = map[modelId];
|
|
65
|
+
if (label) return labelToModelId(label);
|
|
66
|
+
return modelId;
|
|
67
|
+
}
|
|
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
|
+
|
|
475
|
+
// ============================================================
|
|
476
|
+
// Cross-platform process utilities
|
|
477
|
+
// ============================================================
|
|
478
|
+
|
|
479
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
480
|
+
|
|
481
|
+
function getProcessList() {
|
|
482
|
+
try {
|
|
483
|
+
if (IS_WINDOWS) {
|
|
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'], {
|
|
486
|
+
encoding: 'utf-8',
|
|
487
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
488
|
+
});
|
|
489
|
+
// Parse CSV: skip header
|
|
490
|
+
const lines = output.split('\n').slice(1);
|
|
491
|
+
return lines.map(line => {
|
|
492
|
+
const parts = line.split(',');
|
|
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;
|
|
497
|
+
return { commandLine, pid };
|
|
498
|
+
}).filter(Boolean);
|
|
499
|
+
} else {
|
|
500
|
+
const output = execSync('ps aux', { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
|
|
501
|
+
return output.split('\n').slice(1).map(line => {
|
|
502
|
+
const parts = line.trim().split(/\s+/);
|
|
503
|
+
if (parts.length < 11) return null;
|
|
504
|
+
const pid = parts[1];
|
|
505
|
+
const commandLine = parts.slice(10).join(' ');
|
|
506
|
+
return { commandLine, pid };
|
|
507
|
+
}).filter(Boolean);
|
|
508
|
+
}
|
|
509
|
+
} catch { return []; }
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function getListeningPorts(pid) {
|
|
513
|
+
try {
|
|
514
|
+
if (IS_WINDOWS) {
|
|
515
|
+
// Use PowerShell to get netstat output and filter by PID
|
|
516
|
+
const output = execFileSync('powershell', ['-Command', `netstat -ano | Select-String "${pid}$"`], {
|
|
517
|
+
encoding: 'utf-8',
|
|
518
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
519
|
+
});
|
|
520
|
+
const ports = [];
|
|
521
|
+
for (const line of output.split('\n')) {
|
|
522
|
+
if (!line.trim().endsWith(pid)) continue;
|
|
523
|
+
const match = line.match(/127\.0\.0\.1:(\d+).*LISTENING/);
|
|
524
|
+
if (match) {
|
|
525
|
+
ports.push(parseInt(match[1]));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return ports;
|
|
529
|
+
} else {
|
|
530
|
+
const output = execSync(`lsof -i TCP -P -n -a -p ${pid} 2>/dev/null`, {
|
|
531
|
+
encoding: 'utf-8',
|
|
532
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
533
|
+
});
|
|
534
|
+
const ports = [];
|
|
535
|
+
for (const line of output.split('\n')) {
|
|
536
|
+
const match = line.match(/TCP\s+127\.0\.0\.1:(\d+)\s+\(LISTEN\)/);
|
|
537
|
+
if (match) {
|
|
538
|
+
ports.push(parseInt(match[1]));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return ports;
|
|
542
|
+
}
|
|
543
|
+
} catch { return []; }
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ============================================================
|
|
547
|
+
// Find running Antigravity language server (port + CSRF token)
|
|
548
|
+
// ============================================================
|
|
549
|
+
|
|
550
|
+
let _lsCache = null;
|
|
551
|
+
let _lsCacheCheckedAt = 0;
|
|
552
|
+
|
|
553
|
+
function findLanguageServer() {
|
|
554
|
+
if (_lsCache !== null && (Date.now() - _lsCacheCheckedAt) < 10000) return _lsCache || null;
|
|
555
|
+
|
|
556
|
+
const serverProcessName = IS_WINDOWS
|
|
557
|
+
? 'language_server_windows'
|
|
558
|
+
: process.platform === 'darwin'
|
|
559
|
+
? 'language_server_macos'
|
|
560
|
+
: 'language_server_linux';
|
|
561
|
+
|
|
562
|
+
for (const proc of getProcessList()) {
|
|
563
|
+
const { commandLine, pid } = proc;
|
|
564
|
+
if (!commandLine.includes(serverProcessName)) continue;
|
|
565
|
+
|
|
566
|
+
const appDirMatch = commandLine.match(/--app_data_dir\s+(\S+)/);
|
|
567
|
+
if (!appDirMatch || !appDirMatch[1].includes('antigravity')) continue;
|
|
568
|
+
|
|
569
|
+
const csrfMatch = commandLine.match(/--csrf_token\s+(\S+)/);
|
|
570
|
+
if (!csrfMatch) continue;
|
|
571
|
+
|
|
572
|
+
const serverPortMatch = commandLine.match(/--server_port\s+(\d+)/);
|
|
573
|
+
const ports = getListeningPorts(pid);
|
|
574
|
+
if (ports.length === 0) continue;
|
|
575
|
+
|
|
576
|
+
let port;
|
|
577
|
+
if (serverPortMatch) {
|
|
578
|
+
port = parseInt(serverPortMatch[1], 10);
|
|
579
|
+
if (!ports.includes(port)) port = Math.min(...ports);
|
|
580
|
+
} else {
|
|
581
|
+
port = Math.min(...ports);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
_lsCache = { port, csrf: csrfMatch[1], pid };
|
|
585
|
+
_lsCacheCheckedAt = Date.now();
|
|
586
|
+
return _lsCache;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
_lsCache = false;
|
|
590
|
+
_lsCacheCheckedAt = Date.now();
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ============================================================
|
|
595
|
+
// Connect protocol HTTP client (always HTTPS, always main CSRF)
|
|
596
|
+
// ============================================================
|
|
597
|
+
|
|
598
|
+
function callRpc(method, body) {
|
|
599
|
+
const ls = findLanguageServer();
|
|
600
|
+
if (!ls) return null;
|
|
601
|
+
|
|
602
|
+
const data = JSON.stringify(body || {});
|
|
603
|
+
const url = `https://127.0.0.1:${ls.port}/exa.language_server_pb.LanguageServerService/${method}`;
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
const result = execSync(
|
|
607
|
+
`curl -s -k -X POST ${JSON.stringify(url)} ` +
|
|
608
|
+
`-H "Content-Type: application/json" ` +
|
|
609
|
+
`-H "x-codeium-csrf-token: ${ls.csrf}" ` +
|
|
610
|
+
`-d ${JSON.stringify(data)} ` +
|
|
611
|
+
`--max-time 10`,
|
|
612
|
+
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
613
|
+
);
|
|
614
|
+
return JSON.parse(result);
|
|
615
|
+
} catch { return null; }
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ============================================================
|
|
619
|
+
// Adapter interface
|
|
620
|
+
// ============================================================
|
|
621
|
+
|
|
622
|
+
const name = 'antigravity';
|
|
623
|
+
|
|
624
|
+
function getChats() {
|
|
625
|
+
const resp = callRpc('GetAllCascadeTrajectories', {});
|
|
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
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return mergeChats(liveChats, getOfflineChats());
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function getSteps(chat) {
|
|
654
|
+
// Prefer GetCascadeTrajectorySteps (returns more steps than GetCascadeTrajectory)
|
|
655
|
+
const resp = callRpc('GetCascadeTrajectorySteps', { cascadeId: chat.composerId });
|
|
656
|
+
if (resp && resp.steps && resp.steps.length > 0) return resp.steps;
|
|
657
|
+
|
|
658
|
+
// Fallback to old method
|
|
659
|
+
const resp2 = callRpc('GetCascadeTrajectory', { cascadeId: chat.composerId });
|
|
660
|
+
if (resp2 && resp2.trajectory && resp2.trajectory.steps) return resp2.trajectory.steps;
|
|
661
|
+
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Get the tail messages beyond the step limit using generatorMetadata.
|
|
667
|
+
* The last generatorMetadata entry with messagePrompts has the conversation context.
|
|
668
|
+
* We find the overlap with step-based messages by matching the last user message content.
|
|
669
|
+
*/
|
|
670
|
+
function getTailMessages(chat, stepMessages) {
|
|
671
|
+
const resp = callRpc('GetCascadeTrajectory', { cascadeId: chat.composerId });
|
|
672
|
+
if (!resp || !resp.trajectory) return [];
|
|
673
|
+
|
|
674
|
+
const gm = resp.trajectory.generatorMetadata || [];
|
|
675
|
+
// Find the last entry that has messagePrompts
|
|
676
|
+
let lastWithMsgs = null;
|
|
677
|
+
for (let i = gm.length - 1; i >= 0; i--) {
|
|
678
|
+
if (gm[i].chatModel && gm[i].chatModel.messagePrompts && gm[i].chatModel.messagePrompts.length > 0) {
|
|
679
|
+
lastWithMsgs = gm[i];
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (!lastWithMsgs) return [];
|
|
684
|
+
|
|
685
|
+
const mp = lastWithMsgs.chatModel.messagePrompts;
|
|
686
|
+
|
|
687
|
+
// Find the last user message from step-based parsing
|
|
688
|
+
let lastUserContent = '';
|
|
689
|
+
for (let i = stepMessages.length - 1; i >= 0; i--) {
|
|
690
|
+
if (stepMessages[i].role === 'user' && stepMessages[i].content.length > 20) {
|
|
691
|
+
lastUserContent = stepMessages[i].content;
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (!lastUserContent) return [];
|
|
696
|
+
|
|
697
|
+
// Find this message in the messagePrompts (search from end for efficiency)
|
|
698
|
+
const needle = lastUserContent.substring(0, 50);
|
|
699
|
+
let matchIdx = -1;
|
|
700
|
+
for (let i = mp.length - 1; i >= 0; i--) {
|
|
701
|
+
if (mp[i].source === 'CHAT_MESSAGE_SOURCE_USER' && mp[i].prompt && mp[i].prompt.includes(needle)) {
|
|
702
|
+
matchIdx = i;
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (matchIdx < 0 || matchIdx >= mp.length - 1) return [];
|
|
707
|
+
|
|
708
|
+
// Convert everything after the match point to messages
|
|
709
|
+
const tail = [];
|
|
710
|
+
for (let i = matchIdx + 1; i < mp.length; i++) {
|
|
711
|
+
const m = mp[i];
|
|
712
|
+
const src = m.source || '';
|
|
713
|
+
const prompt = m.prompt || '';
|
|
714
|
+
if (!prompt || !prompt.trim()) continue;
|
|
715
|
+
|
|
716
|
+
let role;
|
|
717
|
+
if (src === 'CHAT_MESSAGE_SOURCE_USER') role = 'user';
|
|
718
|
+
else if (src === 'CHAT_MESSAGE_SOURCE_SYSTEM') role = 'assistant';
|
|
719
|
+
else if (src === 'CHAT_MESSAGE_SOURCE_TOOL') role = 'tool';
|
|
720
|
+
else continue;
|
|
721
|
+
|
|
722
|
+
tail.push({ role, content: prompt });
|
|
723
|
+
}
|
|
724
|
+
return tail;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function parseStep(step) {
|
|
728
|
+
const type = step.type || '';
|
|
729
|
+
const meta = step.metadata || {};
|
|
730
|
+
|
|
731
|
+
if (type === 'CORTEX_STEP_TYPE_USER_INPUT' && step.userInput) {
|
|
732
|
+
return {
|
|
733
|
+
role: 'user',
|
|
734
|
+
content: step.userInput.userResponse || step.userInput.items?.map(i => i.text).join('') || '',
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (type === 'CORTEX_STEP_TYPE_ASK_USER_QUESTION' && step.askUserQuestion) {
|
|
739
|
+
const q = step.askUserQuestion;
|
|
740
|
+
return {
|
|
741
|
+
role: 'user',
|
|
742
|
+
content: q.userResponse || q.question || '',
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (type === 'CORTEX_STEP_TYPE_PLANNER_RESPONSE' && step.plannerResponse) {
|
|
747
|
+
const pr = step.plannerResponse;
|
|
748
|
+
const parts = [];
|
|
749
|
+
if (pr.thinking) parts.push(`[thinking] ${pr.thinking}`);
|
|
750
|
+
const text = pr.modifiedResponse || pr.response || pr.textContent || '';
|
|
751
|
+
if (text.trim()) parts.push(text.trim());
|
|
752
|
+
const _toolCalls = [];
|
|
753
|
+
if (pr.toolCalls && pr.toolCalls.length > 0) {
|
|
754
|
+
for (const tc of pr.toolCalls) {
|
|
755
|
+
let args = {};
|
|
756
|
+
try { args = tc.argumentsJson ? JSON.parse(tc.argumentsJson) : {}; } catch { args = {}; }
|
|
757
|
+
const argKeys = typeof args === 'object' ? Object.keys(args).join(', ') : '';
|
|
758
|
+
parts.push(`[tool-call: ${tc.name}(${argKeys})]`);
|
|
759
|
+
_toolCalls.push({ name: tc.name, args });
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (parts.length > 0) {
|
|
763
|
+
const model = meta.generatorModel || meta.generatorModelUid;
|
|
764
|
+
return {
|
|
765
|
+
role: 'assistant',
|
|
766
|
+
content: parts.join('\n'),
|
|
767
|
+
_model: model ? normalizeModel(model) : model,
|
|
768
|
+
_toolCalls,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Tool-like step types
|
|
775
|
+
if (type === 'CORTEX_STEP_TYPE_TOOL_EXECUTION' && step.toolExecution) {
|
|
776
|
+
const te = step.toolExecution;
|
|
777
|
+
const toolName = te.toolName || te.name || 'tool';
|
|
778
|
+
const result = te.output || te.result || '';
|
|
779
|
+
const preview = typeof result === 'string' ? result.substring(0, 500) : JSON.stringify(result).substring(0, 500);
|
|
780
|
+
return { role: 'tool', content: `[${toolName}] ${preview}` };
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (type === 'CORTEX_STEP_TYPE_RUN_COMMAND' && step.runCommand) {
|
|
784
|
+
const rc = step.runCommand;
|
|
785
|
+
const cmd = rc.command || rc.commandLine || '';
|
|
786
|
+
const out = (rc.output || rc.stdout || '').substring(0, 500);
|
|
787
|
+
return { role: 'tool', content: `[run_command] ${cmd}${out ? '\n' + out : ''}` };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (type === 'CORTEX_STEP_TYPE_COMMAND_STATUS' && step.commandStatus) {
|
|
791
|
+
const cs = step.commandStatus;
|
|
792
|
+
const out = (cs.output || cs.stdout || '').substring(0, 500);
|
|
793
|
+
return out ? { role: 'tool', content: `[command_status] ${out}` } : null;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (type === 'CORTEX_STEP_TYPE_VIEW_FILE' && step.viewFile) {
|
|
797
|
+
const vf = step.viewFile;
|
|
798
|
+
const filePath = vf.filePath || vf.path || '';
|
|
799
|
+
return { role: 'tool', content: `[view_file] ${filePath}` };
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (type === 'CORTEX_STEP_TYPE_CODE_ACTION' && step.codeAction) {
|
|
803
|
+
const ca = step.codeAction;
|
|
804
|
+
const filePath = ca.filePath || ca.path || '';
|
|
805
|
+
return { role: 'tool', content: `[code_action] ${filePath}` };
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (type === 'CORTEX_STEP_TYPE_GREP_SEARCH' && step.grepSearch) {
|
|
809
|
+
const gs = step.grepSearch;
|
|
810
|
+
const query = gs.query || gs.pattern || '';
|
|
811
|
+
return { role: 'tool', content: `[grep_search] ${query}` };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (type === 'CORTEX_STEP_TYPE_LIST_DIRECTORY' && step.listDirectory) {
|
|
815
|
+
const ld = step.listDirectory;
|
|
816
|
+
const dir = ld.directoryPath || ld.path || '';
|
|
817
|
+
return { role: 'tool', content: `[list_directory] ${dir}` };
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (type === 'CORTEX_STEP_TYPE_MCP_TOOL' && step.mcpTool) {
|
|
821
|
+
const mt = step.mcpTool;
|
|
822
|
+
const name = mt.toolName || mt.name || 'mcp_tool';
|
|
823
|
+
return { role: 'tool', content: `[${name}]` };
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Skip non-content steps
|
|
827
|
+
if (type === 'CORTEX_STEP_TYPE_CHECKPOINT' || type === 'CORTEX_STEP_TYPE_RETRIEVE_MEMORY' ||
|
|
828
|
+
type === 'CORTEX_STEP_TYPE_MEMORY' || type === 'CORTEX_STEP_TYPE_TODO_LIST' ||
|
|
829
|
+
type === 'CORTEX_STEP_TYPE_EXIT_PLAN_MODE' || type === 'CORTEX_STEP_TYPE_PROXY_WEB_SERVER') {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function getMessages(chat) {
|
|
837
|
+
const steps = getSteps(chat);
|
|
838
|
+
const messages = [];
|
|
839
|
+
for (const step of steps) {
|
|
840
|
+
const msg = parseStep(step);
|
|
841
|
+
if (msg) messages.push(msg);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// If steps are truncated, fill in the tail from generatorMetadata
|
|
845
|
+
const tail = getTailMessages(chat, messages);
|
|
846
|
+
if (tail.length > 0) {
|
|
847
|
+
messages.push(...tail);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return messages;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// ============================================================
|
|
854
|
+
// Usage / quota data from language server RPC
|
|
855
|
+
// ============================================================
|
|
856
|
+
|
|
857
|
+
function getUsage() {
|
|
858
|
+
const { isSubscriptionAccessAllowed } = require('./base');
|
|
859
|
+
if (!isSubscriptionAccessAllowed()) return null;
|
|
860
|
+
const resp = callRpc('GetUserStatus', {});
|
|
861
|
+
if (!resp || !resp.userStatus) return null;
|
|
862
|
+
|
|
863
|
+
const us = resp.userStatus;
|
|
864
|
+
const ps = us.planStatus || {};
|
|
865
|
+
const pi = ps.planInfo || {};
|
|
866
|
+
const modelConfigs = (us.cascadeModelConfigData || {}).clientModelConfigs || [];
|
|
867
|
+
|
|
868
|
+
const models = modelConfigs.map((m) => {
|
|
869
|
+
const qi = m.quotaInfo || {};
|
|
870
|
+
return {
|
|
871
|
+
label: m.label || null,
|
|
872
|
+
model: m.modelOrAlias?.model || null,
|
|
873
|
+
remainingFraction: qi.remainingFraction != null ? qi.remainingFraction : null,
|
|
874
|
+
resetTime: qi.resetTime || null,
|
|
875
|
+
supportsImages: m.supportsImages || false,
|
|
876
|
+
};
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// Antigravity returns credits already in display units (no ÷100 needed)
|
|
880
|
+
const promptAlloc = ps.availablePromptCredits || 0;
|
|
881
|
+
const promptUsed = ps.usedPromptCredits || 0;
|
|
882
|
+
const flexAlloc = ps.availableFlexCredits || 0;
|
|
883
|
+
const flexUsed = ps.usedFlexCredits || 0;
|
|
884
|
+
const flowAlloc = ps.availableFlowCredits || 0;
|
|
885
|
+
|
|
886
|
+
const remainingPrompt = Math.max(0, promptAlloc - promptUsed);
|
|
887
|
+
const remainingFlex = Math.max(0, flexAlloc - flexUsed);
|
|
888
|
+
const totalRemaining = remainingPrompt + remainingFlex;
|
|
889
|
+
|
|
890
|
+
// Credit multipliers per model
|
|
891
|
+
const creditMultipliers = (pi.creditMultiplierOverrides || []).reduce((acc, entry) => {
|
|
892
|
+
const model = entry.modelOrAlias?.model;
|
|
893
|
+
if (model && entry.creditMultiplier != null) acc[model] = entry.creditMultiplier;
|
|
894
|
+
return acc;
|
|
895
|
+
}, {});
|
|
896
|
+
|
|
897
|
+
return {
|
|
898
|
+
source: 'antigravity',
|
|
899
|
+
plan: {
|
|
900
|
+
name: pi.planName || null,
|
|
901
|
+
tier: pi.teamsTier || null,
|
|
902
|
+
monthlyPromptCredits: (pi.monthlyPromptCredits || 0) / 100,
|
|
903
|
+
monthlyFlowCredits: (pi.monthlyFlowCredits || 0) / 100,
|
|
904
|
+
canBuyMoreCredits: pi.canBuyMoreCredits || false,
|
|
905
|
+
},
|
|
906
|
+
usage: {
|
|
907
|
+
promptCredits: { allocated: promptAlloc, used: promptUsed, remaining: remainingPrompt },
|
|
908
|
+
flexCredits: { allocated: flexAlloc, used: flexUsed, remaining: remainingFlex },
|
|
909
|
+
flowCredits: { allocated: flowAlloc },
|
|
910
|
+
totalRemainingCredits: totalRemaining,
|
|
911
|
+
},
|
|
912
|
+
billingCycle: {
|
|
913
|
+
start: ps.planStart || null,
|
|
914
|
+
end: ps.planEnd || null,
|
|
915
|
+
},
|
|
916
|
+
features: {
|
|
917
|
+
webSearch: pi.cascadeWebSearchEnabled || false,
|
|
918
|
+
browser: pi.browserEnabled || false,
|
|
919
|
+
knowledgeBase: pi.knowledgeBaseEnabled || false,
|
|
920
|
+
autoRunCommands: pi.cascadeCanAutoRunCommands || false,
|
|
921
|
+
commitMessages: pi.canGenerateCommitMessages || false,
|
|
922
|
+
},
|
|
923
|
+
models,
|
|
924
|
+
creditMultipliers,
|
|
925
|
+
user: {
|
|
926
|
+
name: us.name || null,
|
|
927
|
+
email: us.email || null,
|
|
928
|
+
},
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function resetCache() { _lsCache = null; _lsCacheCheckedAt = 0; _modelMap = null; _sessionFolderMap = null; }
|
|
933
|
+
|
|
934
|
+
const labels = { 'antigravity': 'Antigravity' };
|
|
935
|
+
|
|
936
|
+
// Cache session→folder mapping so we only build it once per process
|
|
937
|
+
let _sessionFolderMap = null;
|
|
938
|
+
function getSessionFolderMap() {
|
|
939
|
+
if (_sessionFolderMap) return _sessionFolderMap;
|
|
940
|
+
_sessionFolderMap = new Map();
|
|
941
|
+
try {
|
|
942
|
+
const chats = getChats();
|
|
943
|
+
for (const chat of chats) {
|
|
944
|
+
if (chat.composerId && chat.folder) {
|
|
945
|
+
_sessionFolderMap.set(chat.composerId, chat.folder);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
} catch { /* skip */ }
|
|
949
|
+
return _sessionFolderMap;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function normalizePath(p) {
|
|
953
|
+
if (!p) return p;
|
|
954
|
+
return p.replace(/\/+$/, '').replace(/^file:\/\//, '');
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function getArtifacts(folder) {
|
|
958
|
+
const { scanArtifacts } = require('./base');
|
|
959
|
+
const artifacts = folder ? scanArtifacts(folder, {
|
|
960
|
+
editor: 'antigravity',
|
|
961
|
+
label: 'Antigravity',
|
|
962
|
+
files: [],
|
|
963
|
+
dirs: ['.gemini/skills', '.gemini/rules', '.gemini/plans', '.gemini/workflows'],
|
|
964
|
+
}) : [];
|
|
965
|
+
|
|
966
|
+
// Add brain artifacts (task.md, implementation_plan.md, walkthrough.md) per session
|
|
967
|
+
if (!folder || !fs.existsSync(ANTIGRAVITY_BRAIN_DIR)) return artifacts;
|
|
968
|
+
|
|
969
|
+
const sessionMap = getSessionFolderMap();
|
|
970
|
+
const normalizedFolder = normalizePath(folder);
|
|
971
|
+
|
|
972
|
+
try {
|
|
973
|
+
const sessions = fs.readdirSync(ANTIGRAVITY_BRAIN_DIR);
|
|
974
|
+
const brainFileNames = ['task.md', 'implementation_plan.md', 'walkthrough.md'];
|
|
975
|
+
for (const sessionId of sessions) {
|
|
976
|
+
// Only include sessions that belong to this project folder
|
|
977
|
+
const sessionFolder = normalizePath(sessionMap.get(sessionId));
|
|
978
|
+
if (!sessionFolder || sessionFolder !== normalizedFolder) continue;
|
|
979
|
+
|
|
980
|
+
const sessionDir = path.join(ANTIGRAVITY_BRAIN_DIR, sessionId);
|
|
981
|
+
try {
|
|
982
|
+
if (!fs.statSync(sessionDir).isDirectory()) continue;
|
|
983
|
+
} catch { continue; }
|
|
984
|
+
for (const fileName of brainFileNames) {
|
|
985
|
+
const filePath = path.join(sessionDir, fileName);
|
|
986
|
+
if (!fs.existsSync(filePath)) continue;
|
|
987
|
+
try {
|
|
988
|
+
const stat = fs.statSync(filePath);
|
|
989
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
990
|
+
if (!content.trim()) continue;
|
|
991
|
+
const lines = content.split('\n').length;
|
|
992
|
+
artifacts.push({
|
|
993
|
+
name: fileName,
|
|
994
|
+
path: filePath,
|
|
995
|
+
relativePath: `brain/${sessionId.slice(0, 8)}/${fileName}`,
|
|
996
|
+
size: stat.size,
|
|
997
|
+
lines,
|
|
998
|
+
modifiedAt: stat.mtimeMs,
|
|
999
|
+
editor: 'antigravity',
|
|
1000
|
+
editorLabel: 'Antigravity',
|
|
1001
|
+
});
|
|
1002
|
+
} catch { /* skip unreadable */ }
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
} catch { /* skip if brain dir unreadable */ }
|
|
1006
|
+
|
|
1007
|
+
return artifacts;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function getMCPServers() {
|
|
1011
|
+
const { parseMcpConfigFile } = require('./base');
|
|
1012
|
+
// Antigravity uses similar config to Windsurf: ~/.codeium/antigravity/mcp_config.json
|
|
1013
|
+
const globalConfig = path.join(HOME, '.codeium', 'antigravity', 'mcp_config.json');
|
|
1014
|
+
return [
|
|
1015
|
+
...parseMcpConfigFile(globalConfig, { editor: 'antigravity', label: 'Antigravity', scope: 'global' }),
|
|
1016
|
+
];
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
module.exports = { name, labels, getChats, getMessages, resetCache, getUsage, getArtifacts, getMCPServers };
|