deepdebug-local-agent 0.3.11 ā 0.3.13
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/package.json +1 -1
- package/server.js +150 -1
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -557,6 +557,7 @@ app.use(cors({
|
|
|
557
557
|
// Cloud Run URLs (regex patterns)
|
|
558
558
|
/https:\/\/.*\.run\.app$/,
|
|
559
559
|
/https:\/\/.*\.web\.app$/,
|
|
560
|
+
// Production frontend
|
|
560
561
|
"https://deepdebug.ai",
|
|
561
562
|
"https://www.deepdebug.ai"
|
|
562
563
|
],
|
|
@@ -5082,10 +5083,138 @@ app.post("/workspace/:workspaceId/run", async (req, res) => {
|
|
|
5082
5083
|
}
|
|
5083
5084
|
});
|
|
5084
5085
|
|
|
5086
|
+
|
|
5087
|
+
// ============================================
|
|
5088
|
+
// š WEBSOCKET REVERSE TUNNEL
|
|
5089
|
+
// Connects Local Agent to Gateway ā no public URL needed
|
|
5090
|
+
// ============================================
|
|
5091
|
+
async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
5092
|
+
if (!gatewayUrl || !apiKey || !tenantId) {
|
|
5093
|
+
console.log('ā ļø WebSocket tunnel skipped: missing gatewayUrl, apiKey or tenantId in ~/.deepdebug/config.json');
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
5096
|
+
|
|
5097
|
+
const wsUrl = gatewayUrl.replace(/^https:\/\//, 'wss://').replace(/^http:\/\//, 'ws://');
|
|
5098
|
+
const fullUrl = `${wsUrl}/api/v1/agent/ws?tenantId=${encodeURIComponent(tenantId)}&apiKey=${encodeURIComponent(apiKey)}`;
|
|
5099
|
+
|
|
5100
|
+
let reconnectDelay = 2000;
|
|
5101
|
+
let isShuttingDown = false;
|
|
5102
|
+
let ws = null;
|
|
5103
|
+
|
|
5104
|
+
async function connect() {
|
|
5105
|
+
if (isShuttingDown) return;
|
|
5106
|
+
try {
|
|
5107
|
+
const { default: WebSocket } = await import('ws');
|
|
5108
|
+
ws = new WebSocket(fullUrl);
|
|
5109
|
+
|
|
5110
|
+
ws.on('open', () => {
|
|
5111
|
+
console.log(`ā
WebSocket tunnel connected to Gateway`);
|
|
5112
|
+
reconnectDelay = 2000;
|
|
5113
|
+
ws.send(JSON.stringify({ type: 'register', tenantId, port, workspacePath: WORKSPACE_ROOT || null }));
|
|
5114
|
+
const hb = setInterval(() => {
|
|
5115
|
+
if (ws.readyState === WebSocket.OPEN) ws.send('ping');
|
|
5116
|
+
else clearInterval(hb);
|
|
5117
|
+
}, 30000);
|
|
5118
|
+
});
|
|
5119
|
+
|
|
5120
|
+
ws.on('message', async (data) => {
|
|
5121
|
+
const text = data.toString();
|
|
5122
|
+
if (text === 'pong') return;
|
|
5123
|
+
let request;
|
|
5124
|
+
try { request = JSON.parse(text); } catch { return; }
|
|
5125
|
+
const { requestId, command, params } = request;
|
|
5126
|
+
if (!requestId || !command) return;
|
|
5127
|
+
console.log(`šØ WS command: ${command}`);
|
|
5128
|
+
let result;
|
|
5129
|
+
try { result = await handleWsCommand(command, params || {}); }
|
|
5130
|
+
catch (err) { result = { error: err.message }; }
|
|
5131
|
+
ws.send(JSON.stringify({ requestId, command, ...result }));
|
|
5132
|
+
});
|
|
5133
|
+
|
|
5134
|
+
ws.on('close', () => {
|
|
5135
|
+
if (!isShuttingDown) {
|
|
5136
|
+
console.log(`š WS disconnected. Reconnecting in ${reconnectDelay/1000}s...`);
|
|
5137
|
+
setTimeout(connect, reconnectDelay);
|
|
5138
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
5139
|
+
}
|
|
5140
|
+
});
|
|
5141
|
+
|
|
5142
|
+
ws.on('error', (err) => console.warn(`ā ļø WS error: ${err.message}`));
|
|
5143
|
+
|
|
5144
|
+
} catch (err) {
|
|
5145
|
+
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
5146
|
+
console.warn('ā ļø WebSocket tunnel requires "ws" package. Run: npm install ws');
|
|
5147
|
+
} else {
|
|
5148
|
+
console.warn(`ā ļø WS tunnel error: ${err.message}`);
|
|
5149
|
+
setTimeout(connect, reconnectDelay);
|
|
5150
|
+
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
async function handleWsCommand(command, params) {
|
|
5156
|
+
const localBase = `http://localhost:${port}`;
|
|
5157
|
+
switch (command) {
|
|
5158
|
+
case 'health':
|
|
5159
|
+
return { status: 'ok', workspace: WORKSPACE_ROOT, openWorkspaces: wsManager ? wsManager.list().length : 0 };
|
|
5160
|
+
|
|
5161
|
+
case 'workspace.open': {
|
|
5162
|
+
const { root } = params;
|
|
5163
|
+
if (!root) return { error: 'root is required' };
|
|
5164
|
+
await wsManager.open('default', root);
|
|
5165
|
+
WORKSPACE_ROOT = root;
|
|
5166
|
+
return { ok: true, root };
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
case 'workspace.file-content': {
|
|
5170
|
+
const { path: filePath } = params;
|
|
5171
|
+
if (!filePath) return { error: 'path is required' };
|
|
5172
|
+
if (!WORKSPACE_ROOT) return { error: 'No workspace open' };
|
|
5173
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(WORKSPACE_ROOT, filePath);
|
|
5174
|
+
try {
|
|
5175
|
+
const content = await fsPromises.readFile(absPath, 'utf-8');
|
|
5176
|
+
return { content, path: filePath, found: true };
|
|
5177
|
+
} catch (err) {
|
|
5178
|
+
return { content: null, found: false, error: err.message };
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5182
|
+
case 'workspace.safe-patch':
|
|
5183
|
+
case 'workspace.compile':
|
|
5184
|
+
case 'workspace.rollback':
|
|
5185
|
+
case 'workspace.validate-diff':
|
|
5186
|
+
case 'workspace.backups': {
|
|
5187
|
+
const endpointMap = {
|
|
5188
|
+
'workspace.safe-patch': '/workspace/safe-patch',
|
|
5189
|
+
'workspace.compile': '/workspace/compile',
|
|
5190
|
+
'workspace.rollback': '/workspace/rollback',
|
|
5191
|
+
'workspace.validate-diff': '/workspace/validate-diff',
|
|
5192
|
+
'workspace.backups': '/workspace/backups'
|
|
5193
|
+
};
|
|
5194
|
+
const endpoint = endpointMap[command];
|
|
5195
|
+
const method = command === 'workspace.backups' ? 'GET' : 'POST';
|
|
5196
|
+
const res = await fetch(`${localBase}${endpoint}`, {
|
|
5197
|
+
method,
|
|
5198
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5199
|
+
...(method === 'POST' ? { body: JSON.stringify(params) } : {})
|
|
5200
|
+
});
|
|
5201
|
+
return await res.json();
|
|
5202
|
+
}
|
|
5203
|
+
|
|
5204
|
+
default:
|
|
5205
|
+
return { error: `Unknown command: ${command}` };
|
|
5206
|
+
}
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5209
|
+
process.on('SIGINT', () => { isShuttingDown = true; if (ws) ws.close(); });
|
|
5210
|
+
process.on('SIGTERM', () => { isShuttingDown = true; if (ws) ws.close(); });
|
|
5211
|
+
connect();
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5085
5214
|
// ============================================
|
|
5086
5215
|
// START SERVER
|
|
5087
5216
|
// ============================================
|
|
5088
|
-
app.listen(PORT, '0.0.0.0', () => {
|
|
5217
|
+
app.listen(PORT, '0.0.0.0', async () => {
|
|
5089
5218
|
console.log(`\nš DeepDebug Local Agent listening on port ${PORT}`);
|
|
5090
5219
|
console.log(`š¦ Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
5091
5220
|
console.log(`š¦ Process Manager initialized`);
|
|
@@ -5096,6 +5225,26 @@ app.listen(PORT, '0.0.0.0', () => {
|
|
|
5096
5225
|
console.log(` Max Retries: ${aiEngine.maxRetries}`);
|
|
5097
5226
|
}
|
|
5098
5227
|
console.log(`\nš Ready to receive requests!\n`);
|
|
5228
|
+
|
|
5229
|
+
// š Start WebSocket reverse tunnel
|
|
5230
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
5231
|
+
const configPath = path.join(homeDir, '.deepdebug', 'config.json');
|
|
5232
|
+
let cfg = {};
|
|
5233
|
+
try { cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch {}
|
|
5234
|
+
|
|
5235
|
+
const gwUrl = cfg.gatewayUrl || process.env.GATEWAY_URL;
|
|
5236
|
+
const apiKey = cfg.apiKey || process.env.DEEPDEBUG_API_KEY;
|
|
5237
|
+
let tenantId = cfg.tenantId;
|
|
5238
|
+
if (!tenantId && apiKey) {
|
|
5239
|
+
try {
|
|
5240
|
+
const payload = JSON.parse(Buffer.from(apiKey.split('.')[1], 'base64').toString());
|
|
5241
|
+
tenantId = payload.tenantId;
|
|
5242
|
+
} catch {}
|
|
5243
|
+
}
|
|
5244
|
+
|
|
5245
|
+
if (process.env.DISABLE_WS_TUNNEL !== 'true') {
|
|
5246
|
+
startWebSocketTunnel(PORT, gwUrl, apiKey, tenantId).then(() => console.log("WS tunnel started")).catch(err => console.error("WS tunnel failed:", err));
|
|
5247
|
+
}
|
|
5099
5248
|
});
|
|
5100
5249
|
|
|
5101
5250
|
// Start MCP HTTP Bridge em paralelo (port 5056)
|