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,634 @@
|
|
|
1
|
+
const { execSync, execFileSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// Devin variants. Keep legacy Windsurf identifiers only for detection/config compatibility.
|
|
7
|
+
const VARIANTS = [
|
|
8
|
+
{
|
|
9
|
+
id: 'devin',
|
|
10
|
+
matchKey: 'ide',
|
|
11
|
+
matchVals: ['windsurf', 'devin', 'devin-desktop'],
|
|
12
|
+
https: false,
|
|
13
|
+
appNames: ['Devin Desktop', 'Devin', 'Windsurf'],
|
|
14
|
+
needsMetadata: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'devin-next',
|
|
18
|
+
matchKey: 'ide',
|
|
19
|
+
matchVals: ['windsurf-next', 'devin-next', 'devin-desktop-next'],
|
|
20
|
+
https: false,
|
|
21
|
+
appNames: ['Devin Desktop - Next', 'Devin - Next', 'Windsurf - Next'],
|
|
22
|
+
needsMetadata: true,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// ============================================================
|
|
27
|
+
// Cross-platform process utilities
|
|
28
|
+
// ============================================================
|
|
29
|
+
|
|
30
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
31
|
+
|
|
32
|
+
function getProcessList() {
|
|
33
|
+
try {
|
|
34
|
+
if (IS_WINDOWS) {
|
|
35
|
+
// Use PowerShell Get-Process (WMIC is deprecated in Windows 10/11)
|
|
36
|
+
const output = execFileSync('powershell', ['-Command', 'Get-Process | Select-Object Id, Path, CommandLine | ConvertTo-Csv -NoTypeInformation'], {
|
|
37
|
+
encoding: 'utf-8',
|
|
38
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
39
|
+
});
|
|
40
|
+
// Parse CSV: skip header
|
|
41
|
+
const lines = output.split('\n').slice(1);
|
|
42
|
+
return lines.map(line => {
|
|
43
|
+
const parts = line.split(',');
|
|
44
|
+
if (parts.length < 3) return null;
|
|
45
|
+
const pid = parts[0].trim().replace(/^"|"$/g, '');
|
|
46
|
+
const commandLine = parts[2].trim().replace(/^"|"$/g, '');
|
|
47
|
+
if (!pid || !commandLine) return null;
|
|
48
|
+
return { commandLine, pid };
|
|
49
|
+
}).filter(Boolean);
|
|
50
|
+
} else {
|
|
51
|
+
// ps aux on Unix-like systems
|
|
52
|
+
const output = execSync('ps aux', { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
|
|
53
|
+
return output.split('\n').slice(1).map(line => {
|
|
54
|
+
const parts = line.trim().split(/\s+/);
|
|
55
|
+
if (parts.length < 11) return null;
|
|
56
|
+
const pid = parts[1];
|
|
57
|
+
const commandLine = parts.slice(10).join(' ');
|
|
58
|
+
return { commandLine, pid };
|
|
59
|
+
}).filter(Boolean);
|
|
60
|
+
}
|
|
61
|
+
} catch { return []; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getListeningPorts(pid) {
|
|
65
|
+
try {
|
|
66
|
+
if (IS_WINDOWS) {
|
|
67
|
+
// Use PowerShell to get netstat output and filter by PID
|
|
68
|
+
const output = execFileSync('powershell', ['-Command', `netstat -ano | Select-String "${pid}$"`], {
|
|
69
|
+
encoding: 'utf-8',
|
|
70
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
71
|
+
});
|
|
72
|
+
const ports = [];
|
|
73
|
+
for (const line of output.split('\n')) {
|
|
74
|
+
// Match: 127.0.0.1:PORT ... LISTENING PID
|
|
75
|
+
// Check if line ends with the PID we're looking for
|
|
76
|
+
if (!line.trim().endsWith(pid)) continue;
|
|
77
|
+
const match = line.match(/127\.0\.0\.1:(\d+).*LISTENING/);
|
|
78
|
+
if (match) {
|
|
79
|
+
ports.push(parseInt(match[1]));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return ports;
|
|
83
|
+
} else {
|
|
84
|
+
// lsof on Unix-like systems
|
|
85
|
+
const output = execSync(`lsof -i TCP -P -n -a -p ${pid} 2>/dev/null`, {
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
88
|
+
});
|
|
89
|
+
const ports = [];
|
|
90
|
+
for (const line of output.split('\n')) {
|
|
91
|
+
const match = line.match(/TCP\s+127\.0\.0\.1:(\d+)\s+\(LISTEN\)/);
|
|
92
|
+
if (match) {
|
|
93
|
+
ports.push(parseInt(match[1]));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return ports;
|
|
97
|
+
}
|
|
98
|
+
} catch { return []; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================================
|
|
102
|
+
// Find running Devin language server (port + CSRF token)
|
|
103
|
+
// ============================================================
|
|
104
|
+
|
|
105
|
+
let _lsCache = null;
|
|
106
|
+
|
|
107
|
+
function findLanguageServers() {
|
|
108
|
+
if (_lsCache) return _lsCache;
|
|
109
|
+
_lsCache = [];
|
|
110
|
+
|
|
111
|
+
// Language server executable name varies by platform
|
|
112
|
+
const serverProcessName = IS_WINDOWS
|
|
113
|
+
? 'language_server_windows'
|
|
114
|
+
: process.platform === 'darwin'
|
|
115
|
+
? 'language_server_macos'
|
|
116
|
+
: 'language_server_linux';
|
|
117
|
+
|
|
118
|
+
// On macOS/Linux, also check env vars for CSRF tokens (some variants pass CSRF via env, not CLI arg)
|
|
119
|
+
const envCsrfByPid = {};
|
|
120
|
+
if (!IS_WINDOWS) {
|
|
121
|
+
try {
|
|
122
|
+
const psEnv = execSync('ps eww -A', { encoding: 'utf-8', maxBuffer: 2 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
123
|
+
for (const envLine of psEnv.split('\n')) {
|
|
124
|
+
const envCsrf = envLine.match(/(?:WINDSURF|DEVIN)_CSRF_TOKEN=(\S+)/);
|
|
125
|
+
if (envCsrf) {
|
|
126
|
+
const envPid = envLine.match(/^\s*(\d+)/);
|
|
127
|
+
if (envPid) envCsrfByPid[envPid[1]] = envCsrf[1];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const proc of getProcessList()) {
|
|
134
|
+
const { commandLine, pid } = proc;
|
|
135
|
+
if (!commandLine.includes(serverProcessName)) continue;
|
|
136
|
+
|
|
137
|
+
const csrfMatch = commandLine.match(/--csrf_token\s+(\S+)/);
|
|
138
|
+
const ideMatch = commandLine.match(/--ide_name\s+(\S+)/);
|
|
139
|
+
const appDirMatch = commandLine.match(/--app_data_dir\s+(\S+)/);
|
|
140
|
+
|
|
141
|
+
// Try CLI arg first, then env var fallback
|
|
142
|
+
const csrf = csrfMatch ? csrfMatch[1] : envCsrfByPid[pid] || null;
|
|
143
|
+
if (!csrf) continue;
|
|
144
|
+
|
|
145
|
+
const ide = ideMatch ? ideMatch[1] : null;
|
|
146
|
+
const appDataDir = appDirMatch ? appDirMatch[1] : null;
|
|
147
|
+
|
|
148
|
+
const extCsrfMatch = commandLine.match(/--extension_server_csrf_token\s+(\S+)/);
|
|
149
|
+
|
|
150
|
+
// Check for explicit server port (Antigravity uses --server_port)
|
|
151
|
+
const serverPortMatch = commandLine.match(/--server_port\s+(\d+)/);
|
|
152
|
+
|
|
153
|
+
// Find actual listening ports for this process
|
|
154
|
+
const ports = getListeningPorts(pid);
|
|
155
|
+
if (ports.length === 0) continue;
|
|
156
|
+
|
|
157
|
+
// Use explicit server_port if available, otherwise use lowest port
|
|
158
|
+
let port;
|
|
159
|
+
if (serverPortMatch) {
|
|
160
|
+
port = parseInt(serverPortMatch[1], 10);
|
|
161
|
+
if (!ports.includes(port)) {
|
|
162
|
+
port = Math.min(...ports);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
port = Math.min(...ports);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (ide) {
|
|
169
|
+
_lsCache.push({ ide, appDataDir, port, csrf, pid, extCsrf: extCsrfMatch ? extCsrfMatch[1] : null, isHttps: false });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return _lsCache;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getLsForVariant(variant) {
|
|
177
|
+
const servers = findLanguageServers();
|
|
178
|
+
let matches;
|
|
179
|
+
const matchVals = variant.matchVals || [variant.matchVal];
|
|
180
|
+
if (variant.matchKey === 'appDataDir') {
|
|
181
|
+
matches = servers.filter(s => matchVals.some(matchVal => s.appDataDir?.includes(matchVal)));
|
|
182
|
+
} else {
|
|
183
|
+
matches = servers.filter(s => matchVals.includes(s.ide));
|
|
184
|
+
}
|
|
185
|
+
return matches.length > 0 ? matches[0] : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ============================================================
|
|
189
|
+
// Connect protocol HTTP client for language server RPC
|
|
190
|
+
// ============================================================
|
|
191
|
+
|
|
192
|
+
function callRpc(port, csrf, method, body, extCsrf = null) {
|
|
193
|
+
const data = JSON.stringify(body || {});
|
|
194
|
+
const url = `http://127.0.0.1:${port}/exa.language_server_pb.LanguageServerService/${method}`;
|
|
195
|
+
|
|
196
|
+
const actualCsrf = extCsrf || csrf;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const result = execSync(
|
|
200
|
+
`curl -s -X POST ${JSON.stringify(url)} ` +
|
|
201
|
+
`-H "Content-Type: application/json" ` +
|
|
202
|
+
`-H "x-codeium-csrf-token: ${actualCsrf}" ` +
|
|
203
|
+
`-d ${JSON.stringify(data)} ` +
|
|
204
|
+
`--max-time 10`,
|
|
205
|
+
{ encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
206
|
+
);
|
|
207
|
+
return JSON.parse(result);
|
|
208
|
+
} catch { return null; }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ============================================================
|
|
212
|
+
// Adapter interface
|
|
213
|
+
// ============================================================
|
|
214
|
+
|
|
215
|
+
const name = 'devin';
|
|
216
|
+
const sources = ['devin', 'devin-next'];
|
|
217
|
+
const legacySources = ['windsurf', 'windsurf-next'];
|
|
218
|
+
|
|
219
|
+
function getChats() {
|
|
220
|
+
const chats = [];
|
|
221
|
+
|
|
222
|
+
for (const variant of VARIANTS) {
|
|
223
|
+
const ls = getLsForVariant(variant);
|
|
224
|
+
if (!ls) continue;
|
|
225
|
+
|
|
226
|
+
const resp = callRpc(ls.port, ls.csrf, 'GetAllCascadeTrajectories', {}, ls.extCsrf);
|
|
227
|
+
if (!resp || !resp.trajectorySummaries) continue;
|
|
228
|
+
|
|
229
|
+
for (const [cascadeId, summary] of Object.entries(resp.trajectorySummaries)) {
|
|
230
|
+
const ws = (summary.workspaces || [])[0];
|
|
231
|
+
const folder = ws?.workspaceFolderAbsoluteUri?.replace('file://', '') || null;
|
|
232
|
+
const rawModel = summary.lastGeneratorModelUid;
|
|
233
|
+
chats.push({
|
|
234
|
+
source: variant.id,
|
|
235
|
+
composerId: cascadeId,
|
|
236
|
+
name: summary.summary || null,
|
|
237
|
+
createdAt: summary.createdTime ? new Date(summary.createdTime).getTime() : null,
|
|
238
|
+
lastUpdatedAt: summary.lastModifiedTime ? new Date(summary.lastModifiedTime).getTime() : null,
|
|
239
|
+
mode: 'cascade',
|
|
240
|
+
folder,
|
|
241
|
+
encrypted: false,
|
|
242
|
+
bubbleCount: summary.stepCount || 0,
|
|
243
|
+
_port: ls.port,
|
|
244
|
+
_csrf: ls.csrf,
|
|
245
|
+
_extCsrf: ls.extCsrf,
|
|
246
|
+
_stepCount: summary.stepCount,
|
|
247
|
+
_model: rawModel,
|
|
248
|
+
_rawModel: rawModel,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return chats;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getSteps(chat) {
|
|
257
|
+
if (!chat._port || !chat._csrf) return [];
|
|
258
|
+
|
|
259
|
+
// Prefer GetCascadeTrajectorySteps (returns more steps than GetCascadeTrajectory)
|
|
260
|
+
const resp = callRpc(chat._port, chat._csrf, 'GetCascadeTrajectorySteps', {
|
|
261
|
+
cascadeId: chat.composerId,
|
|
262
|
+
}, chat._extCsrf);
|
|
263
|
+
if (resp && resp.steps && resp.steps.length > 0) return resp.steps;
|
|
264
|
+
|
|
265
|
+
// Fallback to old method
|
|
266
|
+
const resp2 = callRpc(chat._port, chat._csrf, 'GetCascadeTrajectory', {
|
|
267
|
+
cascadeId: chat.composerId,
|
|
268
|
+
}, chat._extCsrf);
|
|
269
|
+
if (resp2 && resp2.trajectory && resp2.trajectory.steps) return resp2.trajectory.steps;
|
|
270
|
+
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get the tail messages beyond the step limit using generatorMetadata.
|
|
276
|
+
* The last generatorMetadata entry with messagePrompts has the conversation context.
|
|
277
|
+
* We find the overlap with step-based messages by matching the last user message content.
|
|
278
|
+
*/
|
|
279
|
+
function getTailMessages(chat, stepMessages) {
|
|
280
|
+
const resp = callRpc(chat._port, chat._csrf, 'GetCascadeTrajectory', {
|
|
281
|
+
cascadeId: chat.composerId,
|
|
282
|
+
}, chat._extCsrf);
|
|
283
|
+
if (!resp || !resp.trajectory) return [];
|
|
284
|
+
|
|
285
|
+
const gm = resp.trajectory.generatorMetadata || [];
|
|
286
|
+
// Find the last entry that has messagePrompts
|
|
287
|
+
let lastWithMsgs = null;
|
|
288
|
+
for (let i = gm.length - 1; i >= 0; i--) {
|
|
289
|
+
if (gm[i].chatModel && gm[i].chatModel.messagePrompts && gm[i].chatModel.messagePrompts.length > 0) {
|
|
290
|
+
lastWithMsgs = gm[i];
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (!lastWithMsgs) return [];
|
|
295
|
+
|
|
296
|
+
const mp = lastWithMsgs.chatModel.messagePrompts;
|
|
297
|
+
|
|
298
|
+
// Find the last user message from step-based parsing
|
|
299
|
+
let lastUserContent = '';
|
|
300
|
+
for (let i = stepMessages.length - 1; i >= 0; i--) {
|
|
301
|
+
if (stepMessages[i].role === 'user' && stepMessages[i].content.length > 20) {
|
|
302
|
+
lastUserContent = stepMessages[i].content;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!lastUserContent) return [];
|
|
307
|
+
|
|
308
|
+
// Find this message in the messagePrompts (search from end for efficiency)
|
|
309
|
+
const needle = lastUserContent.substring(0, 50);
|
|
310
|
+
let matchIdx = -1;
|
|
311
|
+
for (let i = mp.length - 1; i >= 0; i--) {
|
|
312
|
+
if (mp[i].source === 'CHAT_MESSAGE_SOURCE_USER' && mp[i].prompt && mp[i].prompt.includes(needle)) {
|
|
313
|
+
matchIdx = i;
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (matchIdx < 0 || matchIdx >= mp.length - 1) return [];
|
|
318
|
+
|
|
319
|
+
// Convert everything after the match point to messages
|
|
320
|
+
const tail = [];
|
|
321
|
+
for (let i = matchIdx + 1; i < mp.length; i++) {
|
|
322
|
+
const m = mp[i];
|
|
323
|
+
const src = m.source || '';
|
|
324
|
+
const prompt = m.prompt || '';
|
|
325
|
+
if (!prompt || !prompt.trim()) continue;
|
|
326
|
+
|
|
327
|
+
let role;
|
|
328
|
+
if (src === 'CHAT_MESSAGE_SOURCE_USER') role = 'user';
|
|
329
|
+
else if (src === 'CHAT_MESSAGE_SOURCE_SYSTEM') role = 'assistant';
|
|
330
|
+
else if (src === 'CHAT_MESSAGE_SOURCE_TOOL') role = 'tool';
|
|
331
|
+
else continue;
|
|
332
|
+
|
|
333
|
+
tail.push({ role, content: prompt });
|
|
334
|
+
}
|
|
335
|
+
return tail;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function parseStep(step) {
|
|
339
|
+
const type = step.type || '';
|
|
340
|
+
const meta = step.metadata || {};
|
|
341
|
+
|
|
342
|
+
if (type === 'CORTEX_STEP_TYPE_USER_INPUT' && step.userInput) {
|
|
343
|
+
return {
|
|
344
|
+
role: 'user',
|
|
345
|
+
content: step.userInput.userResponse || step.userInput.items?.map(i => i.text).join('') || '',
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (type === 'CORTEX_STEP_TYPE_ASK_USER_QUESTION' && step.askUserQuestion) {
|
|
350
|
+
const q = step.askUserQuestion;
|
|
351
|
+
return {
|
|
352
|
+
role: 'user',
|
|
353
|
+
content: q.userResponse || q.question || '',
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (type === 'CORTEX_STEP_TYPE_PLANNER_RESPONSE' && step.plannerResponse) {
|
|
358
|
+
const pr = step.plannerResponse;
|
|
359
|
+
const parts = [];
|
|
360
|
+
if (pr.thinking) parts.push(`[thinking] ${pr.thinking}`);
|
|
361
|
+
const text = pr.modifiedResponse || pr.response || pr.textContent || '';
|
|
362
|
+
if (text.trim()) parts.push(text.trim());
|
|
363
|
+
const _toolCalls = [];
|
|
364
|
+
if (pr.toolCalls && pr.toolCalls.length > 0) {
|
|
365
|
+
for (const tc of pr.toolCalls) {
|
|
366
|
+
let args = {};
|
|
367
|
+
try { args = tc.argumentsJson ? JSON.parse(tc.argumentsJson) : {}; } catch { args = {}; }
|
|
368
|
+
const argKeys = typeof args === 'object' ? Object.keys(args).join(', ') : '';
|
|
369
|
+
parts.push(`[tool-call: ${tc.name}(${argKeys})]`);
|
|
370
|
+
_toolCalls.push({ name: tc.name, args });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (parts.length > 0) {
|
|
374
|
+
const model = meta.generatorModelUid;
|
|
375
|
+
return {
|
|
376
|
+
role: 'assistant',
|
|
377
|
+
content: parts.join('\n'),
|
|
378
|
+
_model: model,
|
|
379
|
+
_toolCalls,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Tool-like step types
|
|
386
|
+
if (type === 'CORTEX_STEP_TYPE_TOOL_EXECUTION' && step.toolExecution) {
|
|
387
|
+
const te = step.toolExecution;
|
|
388
|
+
const toolName = te.toolName || te.name || 'tool';
|
|
389
|
+
const result = te.output || te.result || '';
|
|
390
|
+
const preview = typeof result === 'string' ? result.substring(0, 500) : JSON.stringify(result).substring(0, 500);
|
|
391
|
+
return { role: 'tool', content: `[${toolName}] ${preview}` };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (type === 'CORTEX_STEP_TYPE_RUN_COMMAND' && step.runCommand) {
|
|
395
|
+
const rc = step.runCommand;
|
|
396
|
+
const cmd = rc.command || rc.commandLine || '';
|
|
397
|
+
const out = (rc.output || rc.stdout || '').substring(0, 500);
|
|
398
|
+
return { role: 'tool', content: `[run_command] ${cmd}${out ? '\n' + out : ''}` };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (type === 'CORTEX_STEP_TYPE_COMMAND_STATUS' && step.commandStatus) {
|
|
402
|
+
const cs = step.commandStatus;
|
|
403
|
+
const out = (cs.output || cs.stdout || '').substring(0, 500);
|
|
404
|
+
return out ? { role: 'tool', content: `[command_status] ${out}` } : null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (type === 'CORTEX_STEP_TYPE_VIEW_FILE' && step.viewFile) {
|
|
408
|
+
const vf = step.viewFile;
|
|
409
|
+
const filePath = vf.filePath || vf.path || '';
|
|
410
|
+
return { role: 'tool', content: `[view_file] ${filePath}` };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (type === 'CORTEX_STEP_TYPE_CODE_ACTION' && step.codeAction) {
|
|
414
|
+
const ca = step.codeAction;
|
|
415
|
+
const filePath = ca.filePath || ca.path || '';
|
|
416
|
+
return { role: 'tool', content: `[code_action] ${filePath}` };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (type === 'CORTEX_STEP_TYPE_GREP_SEARCH' && step.grepSearch) {
|
|
420
|
+
const gs = step.grepSearch;
|
|
421
|
+
const query = gs.query || gs.pattern || '';
|
|
422
|
+
return { role: 'tool', content: `[grep_search] ${query}` };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (type === 'CORTEX_STEP_TYPE_LIST_DIRECTORY' && step.listDirectory) {
|
|
426
|
+
const ld = step.listDirectory;
|
|
427
|
+
const dir = ld.directoryPath || ld.path || '';
|
|
428
|
+
return { role: 'tool', content: `[list_directory] ${dir}` };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (type === 'CORTEX_STEP_TYPE_MCP_TOOL' && step.mcpTool) {
|
|
432
|
+
const mt = step.mcpTool;
|
|
433
|
+
const name = mt.toolName || mt.name || 'mcp_tool';
|
|
434
|
+
return { role: 'tool', content: `[${name}]` };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Skip non-content steps
|
|
438
|
+
if (type === 'CORTEX_STEP_TYPE_CHECKPOINT' || type === 'CORTEX_STEP_TYPE_RETRIEVE_MEMORY' ||
|
|
439
|
+
type === 'CORTEX_STEP_TYPE_MEMORY' || type === 'CORTEX_STEP_TYPE_TODO_LIST' ||
|
|
440
|
+
type === 'CORTEX_STEP_TYPE_EXIT_PLAN_MODE' || type === 'CORTEX_STEP_TYPE_PROXY_WEB_SERVER') {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function getMessages(chat) {
|
|
448
|
+
const steps = getSteps(chat);
|
|
449
|
+
const messages = [];
|
|
450
|
+
for (const step of steps) {
|
|
451
|
+
const msg = parseStep(step);
|
|
452
|
+
if (msg) messages.push(msg);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// If steps are truncated, fill in the tail from generatorMetadata
|
|
456
|
+
const tail = getTailMessages(chat, messages);
|
|
457
|
+
if (tail.length > 0) {
|
|
458
|
+
messages.push(...tail);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return messages;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ============================================================
|
|
465
|
+
// Usage / quota data from language server RPC
|
|
466
|
+
// ============================================================
|
|
467
|
+
|
|
468
|
+
function getDevinApiKey(appNames) {
|
|
469
|
+
if (!appNames) return null;
|
|
470
|
+
const names = Array.isArray(appNames) ? appNames : [appNames];
|
|
471
|
+
const keys = ['windsurfAuthStatus', 'devinAuthStatus'];
|
|
472
|
+
try {
|
|
473
|
+
const HOME = os.homedir();
|
|
474
|
+
for (const appName of names) {
|
|
475
|
+
let dbPath;
|
|
476
|
+
switch (process.platform) {
|
|
477
|
+
case 'darwin':
|
|
478
|
+
dbPath = path.join(HOME, 'Library', 'Application Support', appName, 'User', 'globalStorage', 'state.vscdb');
|
|
479
|
+
break;
|
|
480
|
+
case 'win32':
|
|
481
|
+
dbPath = path.join(HOME, 'AppData', 'Roaming', appName, 'User', 'globalStorage', 'state.vscdb');
|
|
482
|
+
break;
|
|
483
|
+
default:
|
|
484
|
+
dbPath = path.join(HOME, '.config', appName, 'User', 'globalStorage', 'state.vscdb');
|
|
485
|
+
}
|
|
486
|
+
if (!fs.existsSync(dbPath)) continue;
|
|
487
|
+
const Database = require('better-sqlite3');
|
|
488
|
+
const db = new Database(dbPath, { readonly: true });
|
|
489
|
+
try {
|
|
490
|
+
for (const key of keys) {
|
|
491
|
+
const row = db.prepare('SELECT value FROM ItemTable WHERE key = ?').get(key);
|
|
492
|
+
if (!row) continue;
|
|
493
|
+
const parsed = JSON.parse(row.value);
|
|
494
|
+
if (parsed.apiKey) return parsed.apiKey;
|
|
495
|
+
}
|
|
496
|
+
} finally {
|
|
497
|
+
db.close();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return null;
|
|
501
|
+
} catch { return null; }
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function getUsage() {
|
|
505
|
+
const { isSubscriptionAccessAllowed } = require('./base');
|
|
506
|
+
if (!isSubscriptionAccessAllowed()) return [];
|
|
507
|
+
|
|
508
|
+
const results = [];
|
|
509
|
+
|
|
510
|
+
for (const variant of VARIANTS) {
|
|
511
|
+
const ls = getLsForVariant(variant);
|
|
512
|
+
if (!ls) continue;
|
|
513
|
+
|
|
514
|
+
const apiKey = getDevinApiKey(variant.appNames);
|
|
515
|
+
if (!apiKey) continue;
|
|
516
|
+
const body = {
|
|
517
|
+
metadata: {
|
|
518
|
+
api_key: apiKey,
|
|
519
|
+
ide_name: variant.id,
|
|
520
|
+
ide_version: '1.0.0',
|
|
521
|
+
extension_version: '1.0.0',
|
|
522
|
+
locale: 'en',
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const resp = callRpc(ls.port, ls.csrf, 'GetUserStatus', body, ls.extCsrf);
|
|
527
|
+
if (!resp || !resp.userStatus) continue;
|
|
528
|
+
|
|
529
|
+
const us = resp.userStatus;
|
|
530
|
+
const ps = us.planStatus || {};
|
|
531
|
+
const pi = ps.planInfo || {};
|
|
532
|
+
const modelConfigs = (us.cascadeModelConfigData || {}).clientModelConfigs || [];
|
|
533
|
+
|
|
534
|
+
const models = modelConfigs.map((m) => {
|
|
535
|
+
const qi = m.quotaInfo || {};
|
|
536
|
+
return {
|
|
537
|
+
label: m.label || null,
|
|
538
|
+
model: m.modelOrAlias?.model || null,
|
|
539
|
+
remainingFraction: qi.remainingFraction != null ? qi.remainingFraction : null,
|
|
540
|
+
resetTime: qi.resetTime || null,
|
|
541
|
+
supportsImages: m.supportsImages || false,
|
|
542
|
+
};
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Raw values are in internal units (÷100 for display credits)
|
|
546
|
+
const promptAlloc = (ps.availablePromptCredits || 0) / 100;
|
|
547
|
+
const promptUsed = (ps.usedPromptCredits || 0) / 100;
|
|
548
|
+
const flexAlloc = (ps.availableFlexCredits || 0) / 100;
|
|
549
|
+
const flexUsed = (ps.usedFlexCredits || 0) / 100;
|
|
550
|
+
const flowAlloc = (ps.availableFlowCredits || 0) / 100;
|
|
551
|
+
const monthlyDisplay = (pi.monthlyPromptCredits || 0) / 100;
|
|
552
|
+
|
|
553
|
+
const remainingPrompt = Math.max(0, promptAlloc - promptUsed);
|
|
554
|
+
const remainingFlex = Math.max(0, flexAlloc - flexUsed);
|
|
555
|
+
const totalRemaining = remainingPrompt + remainingFlex;
|
|
556
|
+
|
|
557
|
+
// Credit multipliers per model
|
|
558
|
+
const creditMultipliers = (pi.creditMultiplierOverrides || []).reduce((acc, entry) => {
|
|
559
|
+
const model = entry.modelOrAlias?.model;
|
|
560
|
+
if (model && entry.creditMultiplier != null) acc[model] = entry.creditMultiplier;
|
|
561
|
+
return acc;
|
|
562
|
+
}, {});
|
|
563
|
+
|
|
564
|
+
results.push({
|
|
565
|
+
source: variant.id,
|
|
566
|
+
plan: {
|
|
567
|
+
name: pi.planName || null,
|
|
568
|
+
tier: pi.teamsTier || null,
|
|
569
|
+
monthlyPromptCredits: monthlyDisplay,
|
|
570
|
+
monthlyFlowCredits: (pi.monthlyFlowCredits || 0) / 100,
|
|
571
|
+
canBuyMoreCredits: pi.canBuyMoreCredits || false,
|
|
572
|
+
},
|
|
573
|
+
usage: {
|
|
574
|
+
promptCredits: { allocated: promptAlloc, used: promptUsed, remaining: remainingPrompt },
|
|
575
|
+
flexCredits: { allocated: flexAlloc, used: flexUsed, remaining: remainingFlex },
|
|
576
|
+
flowCredits: { allocated: flowAlloc },
|
|
577
|
+
totalRemainingCredits: totalRemaining,
|
|
578
|
+
},
|
|
579
|
+
billingCycle: {
|
|
580
|
+
start: ps.planStart || null,
|
|
581
|
+
end: ps.planEnd || null,
|
|
582
|
+
},
|
|
583
|
+
topUp: ps.topUpStatus ? {
|
|
584
|
+
monthlyAmount: ps.topUpStatus.monthlyTopUpAmount || null,
|
|
585
|
+
increment: ps.topUpStatus.topUpIncrement || null,
|
|
586
|
+
} : null,
|
|
587
|
+
features: {
|
|
588
|
+
webSearch: pi.cascadeWebSearchEnabled || false,
|
|
589
|
+
browser: pi.browserEnabled || false,
|
|
590
|
+
knowledgeBase: pi.knowledgeBaseEnabled || false,
|
|
591
|
+
autoRunCommands: pi.cascadeCanAutoRunCommands || false,
|
|
592
|
+
commitMessages: pi.canGenerateCommitMessages || false,
|
|
593
|
+
},
|
|
594
|
+
models,
|
|
595
|
+
creditMultipliers,
|
|
596
|
+
user: {
|
|
597
|
+
name: us.name || null,
|
|
598
|
+
email: us.email || null,
|
|
599
|
+
},
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return results.length > 0 ? results : null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function resetCache() { _lsCache = null; }
|
|
607
|
+
|
|
608
|
+
const labels = { 'devin': 'Devin', 'devin-next': 'Devin Next' };
|
|
609
|
+
|
|
610
|
+
function getArtifacts(folder) {
|
|
611
|
+
const { scanArtifacts } = require('./base');
|
|
612
|
+
return scanArtifacts(folder, {
|
|
613
|
+
editor: 'devin',
|
|
614
|
+
label: 'Devin',
|
|
615
|
+
files: ['.windsurfrules'],
|
|
616
|
+
dirs: ['.windsurf/workflows', '.windsurf/rules', '.windsurf/plans', '.windsurf/skills'],
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function getMCPServers() {
|
|
621
|
+
const { parseMcpConfigFile } = require('./base');
|
|
622
|
+
const results = [];
|
|
623
|
+
const configs = [
|
|
624
|
+
{ file: path.join(os.homedir(), '.windsurf', 'mcp_config.json'), editor: 'devin', label: 'Devin' },
|
|
625
|
+
{ file: path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'), editor: 'devin', label: 'Devin' },
|
|
626
|
+
{ file: path.join(os.homedir(), '.codeium', 'windsurf-next', 'mcp_config.json'), editor: 'devin-next', label: 'Devin Next' },
|
|
627
|
+
];
|
|
628
|
+
for (const c of configs) {
|
|
629
|
+
results.push(...parseMcpConfigFile(c.file, { editor: c.editor, label: c.label, scope: 'global' }));
|
|
630
|
+
}
|
|
631
|
+
return results;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
module.exports = { name, sources, legacySources, labels, getChats, getMessages, resetCache, getUsage, getArtifacts, getMCPServers };
|