elsabro 2.3.0 → 3.7.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.
Files changed (67) hide show
  1. package/README.md +668 -20
  2. package/bin/install.js +0 -0
  3. package/flows/development-flow.json +452 -0
  4. package/flows/quick-flow.json +118 -0
  5. package/package.json +3 -2
  6. package/references/SYSTEM_INDEX.md +379 -5
  7. package/references/agent-marketplace.md +2274 -0
  8. package/references/agent-protocol.md +1126 -0
  9. package/references/ai-code-suggestions.md +2413 -0
  10. package/references/checkpointing.md +595 -0
  11. package/references/collaboration-patterns.md +851 -0
  12. package/references/collaborative-sessions.md +1081 -0
  13. package/references/configuration-management.md +1810 -0
  14. package/references/cost-tracking.md +1095 -0
  15. package/references/enterprise-sso.md +2001 -0
  16. package/references/error-contracts-v2.md +968 -0
  17. package/references/event-driven.md +1031 -0
  18. package/references/flow-orchestration.md +940 -0
  19. package/references/flow-visualization.md +1557 -0
  20. package/references/ide-integrations.md +3513 -0
  21. package/references/interrupt-system.md +681 -0
  22. package/references/kubernetes-deployment.md +3099 -0
  23. package/references/memory-system.md +683 -0
  24. package/references/mobile-companion.md +3236 -0
  25. package/references/multi-llm-providers.md +2494 -0
  26. package/references/multi-project-memory.md +1182 -0
  27. package/references/observability.md +793 -0
  28. package/references/output-schemas.md +858 -0
  29. package/references/performance-profiler.md +955 -0
  30. package/references/plugin-system.md +1526 -0
  31. package/references/prompt-management.md +292 -0
  32. package/references/sandbox-execution.md +303 -0
  33. package/references/security-system.md +1253 -0
  34. package/references/streaming.md +696 -0
  35. package/references/testing-framework.md +1151 -0
  36. package/references/time-travel.md +802 -0
  37. package/references/tool-registry.md +886 -0
  38. package/references/voice-commands.md +3296 -0
  39. package/templates/agent-marketplace-config.json +220 -0
  40. package/templates/agent-protocol-config.json +136 -0
  41. package/templates/ai-suggestions-config.json +100 -0
  42. package/templates/checkpoint-state.json +61 -0
  43. package/templates/collaboration-config.json +157 -0
  44. package/templates/collaborative-sessions-config.json +153 -0
  45. package/templates/configuration-config.json +245 -0
  46. package/templates/cost-tracking-config.json +148 -0
  47. package/templates/enterprise-sso-config.json +438 -0
  48. package/templates/events-config.json +148 -0
  49. package/templates/flow-visualization-config.json +196 -0
  50. package/templates/ide-integrations-config.json +442 -0
  51. package/templates/kubernetes-config.json +764 -0
  52. package/templates/memory-state.json +84 -0
  53. package/templates/mobile-companion-config.json +600 -0
  54. package/templates/multi-llm-config.json +544 -0
  55. package/templates/multi-project-memory-config.json +145 -0
  56. package/templates/observability-config.json +109 -0
  57. package/templates/performance-profiler-config.json +125 -0
  58. package/templates/plugin-config.json +170 -0
  59. package/templates/prompt-management-config.json +86 -0
  60. package/templates/sandbox-config.json +185 -0
  61. package/templates/schemas-config.json +65 -0
  62. package/templates/security-config.json +120 -0
  63. package/templates/streaming-config.json +72 -0
  64. package/templates/testing-config.json +81 -0
  65. package/templates/timetravel-config.json +62 -0
  66. package/templates/tool-registry-config.json +109 -0
  67. package/templates/voice-commands-config.json +658 -0
@@ -0,0 +1,3513 @@
1
+ # ELSABRO v3.7 - IDE Integrations
2
+
3
+ > Sistema de integracion con IDEs para orquestacion de agentes AI
4
+
5
+ ## Arquitectura General
6
+
7
+ ```
8
+ +------------------------------------------------------------------+
9
+ | ELSABRO IDE INTEGRATION LAYER |
10
+ +------------------------------------------------------------------+
11
+ | |
12
+ | +------------------+ +------------------+ +------------------+ |
13
+ | | VSCode Ext | | JetBrains Plugin | | LSP Server | |
14
+ | | (TypeScript) | | (Kotlin) | | (Universal) | |
15
+ | +--------+---------+ +--------+---------+ +--------+---------+ |
16
+ | | | | |
17
+ | +----------+----------+----------+----------+ |
18
+ | | | |
19
+ | +-------v-------+ +-------v-------+ |
20
+ | | Extension API | | LSP Protocol | |
21
+ | +-------+-------+ +-------+-------+ |
22
+ | | | |
23
+ | +----------+----------+ |
24
+ | | |
25
+ | +----------v----------+ |
26
+ | | ELSABRO Core API | |
27
+ | | (WebSocket/HTTP) | |
28
+ | +---------------------+ |
29
+ | |
30
+ +------------------------------------------------------------------+
31
+ ```
32
+
33
+ ---
34
+
35
+ ## 1. VSCodeExtension
36
+
37
+ ### 1.1 Estructura del Proyecto
38
+
39
+ ```
40
+ elsabro-vscode/
41
+ ├── .vscode/
42
+ │ ├── launch.json
43
+ │ └── tasks.json
44
+ ├── src/
45
+ │ ├── extension.ts # Entry point
46
+ │ ├── activation.ts # Lifecycle management
47
+ │ ├── commands/
48
+ │ │ ├── index.ts
49
+ │ │ ├── runAgent.ts
50
+ │ │ ├── configureSession.ts
51
+ │ │ └── quickActions.ts
52
+ │ ├── providers/
53
+ │ │ ├── treeDataProvider.ts
54
+ │ │ ├── codeLensProvider.ts
55
+ │ │ ├── hoverProvider.ts
56
+ │ │ └── completionProvider.ts
57
+ │ ├── views/
58
+ │ │ ├── sidebarPanel.ts
59
+ │ │ ├── webviewPanel.ts
60
+ │ │ └── statusBar.ts
61
+ │ ├── lsp/
62
+ │ │ └── client.ts
63
+ │ ├── services/
64
+ │ │ ├── elsabroClient.ts
65
+ │ │ ├── sessionManager.ts
66
+ │ │ └── configManager.ts
67
+ │ └── utils/
68
+ │ ├── logger.ts
69
+ │ └── constants.ts
70
+ ├── media/
71
+ │ ├── icons/
72
+ │ └── webview/
73
+ ├── package.json
74
+ ├── tsconfig.json
75
+ └── webpack.config.js
76
+ ```
77
+
78
+ ### 1.2 Package.json Configuration
79
+
80
+ ```json
81
+ {
82
+ "name": "elsabro-vscode",
83
+ "displayName": "ELSABRO AI Orchestration",
84
+ "description": "AI Agent orchestration for development workflows",
85
+ "version": "3.7.0",
86
+ "publisher": "elsabro",
87
+ "engines": {
88
+ "vscode": "^1.85.0"
89
+ },
90
+ "categories": ["AI", "Other"],
91
+ "activationEvents": [
92
+ "onStartupFinished",
93
+ "workspaceContains:.elsabro",
94
+ "onCommand:elsabro.*"
95
+ ],
96
+ "main": "./dist/extension.js",
97
+ "contributes": {
98
+ "commands": [
99
+ {
100
+ "command": "elsabro.runAgent",
101
+ "title": "Run Agent",
102
+ "category": "ELSABRO",
103
+ "icon": "$(play)"
104
+ },
105
+ {
106
+ "command": "elsabro.pauseAgent",
107
+ "title": "Pause Agent",
108
+ "category": "ELSABRO",
109
+ "icon": "$(debug-pause)"
110
+ },
111
+ {
112
+ "command": "elsabro.cancelTask",
113
+ "title": "Cancel Task",
114
+ "category": "ELSABRO",
115
+ "icon": "$(stop)"
116
+ },
117
+ {
118
+ "command": "elsabro.configure",
119
+ "title": "Configure ELSABRO",
120
+ "category": "ELSABRO",
121
+ "icon": "$(gear)"
122
+ },
123
+ {
124
+ "command": "elsabro.showPanel",
125
+ "title": "Show ELSABRO Panel",
126
+ "category": "ELSABRO"
127
+ },
128
+ {
129
+ "command": "elsabro.generateTests",
130
+ "title": "Generate Tests",
131
+ "category": "ELSABRO"
132
+ },
133
+ {
134
+ "command": "elsabro.generateDocs",
135
+ "title": "Generate Documentation",
136
+ "category": "ELSABRO"
137
+ },
138
+ {
139
+ "command": "elsabro.refactor",
140
+ "title": "AI Refactor",
141
+ "category": "ELSABRO"
142
+ }
143
+ ],
144
+ "views": {
145
+ "elsabro-sidebar": [
146
+ {
147
+ "id": "elsabro.agentsView",
148
+ "name": "Agents",
149
+ "icon": "media/icons/agent.svg",
150
+ "contextualTitle": "ELSABRO Agents"
151
+ },
152
+ {
153
+ "id": "elsabro.tasksView",
154
+ "name": "Tasks",
155
+ "icon": "media/icons/task.svg"
156
+ },
157
+ {
158
+ "id": "elsabro.logsView",
159
+ "name": "Logs",
160
+ "icon": "media/icons/log.svg"
161
+ }
162
+ ]
163
+ },
164
+ "viewsContainers": {
165
+ "activitybar": [
166
+ {
167
+ "id": "elsabro-sidebar",
168
+ "title": "ELSABRO",
169
+ "icon": "media/icons/elsabro.svg"
170
+ }
171
+ ]
172
+ },
173
+ "menus": {
174
+ "editor/context": [
175
+ {
176
+ "command": "elsabro.runAgent",
177
+ "group": "elsabro@1",
178
+ "when": "editorHasSelection"
179
+ },
180
+ {
181
+ "command": "elsabro.generateTests",
182
+ "group": "elsabro@2"
183
+ },
184
+ {
185
+ "command": "elsabro.refactor",
186
+ "group": "elsabro@3"
187
+ }
188
+ ],
189
+ "view/title": [
190
+ {
191
+ "command": "elsabro.configure",
192
+ "when": "view == elsabro.agentsView",
193
+ "group": "navigation"
194
+ }
195
+ ],
196
+ "view/item/context": [
197
+ {
198
+ "command": "elsabro.runAgent",
199
+ "when": "viewItem == agent",
200
+ "group": "inline"
201
+ },
202
+ {
203
+ "command": "elsabro.cancelTask",
204
+ "when": "viewItem == runningTask",
205
+ "group": "inline"
206
+ }
207
+ ]
208
+ },
209
+ "configuration": {
210
+ "title": "ELSABRO",
211
+ "properties": {
212
+ "elsabro.serverUrl": {
213
+ "type": "string",
214
+ "default": "ws://localhost:3847",
215
+ "description": "ELSABRO server WebSocket URL"
216
+ },
217
+ "elsabro.autoConnect": {
218
+ "type": "boolean",
219
+ "default": true,
220
+ "description": "Auto-connect on startup"
221
+ },
222
+ "elsabro.showInlineAnnotations": {
223
+ "type": "boolean",
224
+ "default": true,
225
+ "description": "Show inline code annotations"
226
+ },
227
+ "elsabro.logLevel": {
228
+ "type": "string",
229
+ "enum": ["debug", "info", "warn", "error"],
230
+ "default": "info"
231
+ }
232
+ }
233
+ },
234
+ "keybindings": [
235
+ {
236
+ "command": "elsabro.runAgent",
237
+ "key": "ctrl+shift+e r",
238
+ "mac": "cmd+shift+e r",
239
+ "when": "editorTextFocus"
240
+ },
241
+ {
242
+ "command": "elsabro.showPanel",
243
+ "key": "ctrl+shift+e p",
244
+ "mac": "cmd+shift+e p"
245
+ },
246
+ {
247
+ "command": "elsabro.generateTests",
248
+ "key": "ctrl+shift+e t",
249
+ "mac": "cmd+shift+e t",
250
+ "when": "editorTextFocus"
251
+ }
252
+ ]
253
+ }
254
+ }
255
+ ```
256
+
257
+ ### 1.3 Extension Entry Point (extension.ts)
258
+
259
+ ```typescript
260
+ // src/extension.ts
261
+ import * as vscode from 'vscode';
262
+ import { ElsabroClient } from './services/elsabroClient';
263
+ import { SessionManager } from './services/sessionManager';
264
+ import { AgentsTreeProvider } from './providers/treeDataProvider';
265
+ import { ElsabroCodeLensProvider } from './providers/codeLensProvider';
266
+ import { ElsabroHoverProvider } from './providers/hoverProvider';
267
+ import { StatusBarManager } from './views/statusBar';
268
+ import { SidebarPanel } from './views/sidebarPanel';
269
+ import { registerCommands } from './commands';
270
+ import { Logger } from './utils/logger';
271
+
272
+ let client: ElsabroClient;
273
+ let sessionManager: SessionManager;
274
+
275
+ export async function activate(
276
+ context: vscode.ExtensionContext
277
+ ): Promise<void> {
278
+ Logger.info('ELSABRO extension activating...');
279
+
280
+ // Initialize core services
281
+ client = new ElsabroClient(context);
282
+ sessionManager = new SessionManager(client);
283
+
284
+ // Register providers
285
+ const agentsProvider = new AgentsTreeProvider(sessionManager);
286
+ const codeLensProvider = new ElsabroCodeLensProvider(sessionManager);
287
+ const hoverProvider = new ElsabroHoverProvider(sessionManager);
288
+
289
+ // Register tree views
290
+ context.subscriptions.push(
291
+ vscode.window.registerTreeDataProvider(
292
+ 'elsabro.agentsView',
293
+ agentsProvider
294
+ )
295
+ );
296
+
297
+ // Register CodeLens provider
298
+ context.subscriptions.push(
299
+ vscode.languages.registerCodeLensProvider(
300
+ { scheme: 'file' },
301
+ codeLensProvider
302
+ )
303
+ );
304
+
305
+ // Register Hover provider
306
+ context.subscriptions.push(
307
+ vscode.languages.registerHoverProvider(
308
+ { scheme: 'file' },
309
+ hoverProvider
310
+ )
311
+ );
312
+
313
+ // Initialize status bar
314
+ const statusBar = new StatusBarManager(context, sessionManager);
315
+ statusBar.initialize();
316
+
317
+ // Register all commands
318
+ registerCommands(context, client, sessionManager);
319
+
320
+ // Auto-connect if configured
321
+ const config = vscode.workspace.getConfiguration('elsabro');
322
+ if (config.get<boolean>('autoConnect')) {
323
+ await client.connect();
324
+ }
325
+
326
+ Logger.info('ELSABRO extension activated successfully');
327
+ }
328
+
329
+ export function deactivate(): void {
330
+ Logger.info('ELSABRO extension deactivating...');
331
+ client?.disconnect();
332
+ }
333
+ ```
334
+
335
+ ### 1.4 ELSABRO Client Service
336
+
337
+ ```typescript
338
+ // src/services/elsabroClient.ts
339
+ import * as vscode from 'vscode';
340
+ import WebSocket from 'ws';
341
+ import { EventEmitter } from 'events';
342
+ import { Logger } from '../utils/logger';
343
+
344
+ interface ElsabroMessage {
345
+ type: string;
346
+ payload: any;
347
+ requestId?: string;
348
+ }
349
+
350
+ interface AgentStatus {
351
+ id: string;
352
+ name: string;
353
+ status: 'idle' | 'running' | 'paused' | 'error';
354
+ currentTask?: string;
355
+ progress?: number;
356
+ }
357
+
358
+ export class ElsabroClient extends EventEmitter {
359
+ private ws: WebSocket | null = null;
360
+ private reconnectAttempts = 0;
361
+ private maxReconnectAttempts = 5;
362
+ private pendingRequests = new Map<string, {
363
+ resolve: (value: any) => void;
364
+ reject: (reason: any) => void;
365
+ timeout: NodeJS.Timeout;
366
+ }>();
367
+
368
+ constructor(private context: vscode.ExtensionContext) {
369
+ super();
370
+ }
371
+
372
+ async connect(): Promise<void> {
373
+ const config = vscode.workspace.getConfiguration('elsabro');
374
+ const serverUrl = config.get<string>('serverUrl') || 'ws://localhost:3847';
375
+
376
+ return new Promise((resolve, reject) => {
377
+ try {
378
+ this.ws = new WebSocket(serverUrl);
379
+
380
+ this.ws.on('open', () => {
381
+ Logger.info(`Connected to ELSABRO server: ${serverUrl}`);
382
+ this.reconnectAttempts = 0;
383
+ this.emit('connected');
384
+ resolve();
385
+ });
386
+
387
+ this.ws.on('message', (data: WebSocket.Data) => {
388
+ this.handleMessage(data.toString());
389
+ });
390
+
391
+ this.ws.on('close', () => {
392
+ Logger.warn('Connection closed');
393
+ this.emit('disconnected');
394
+ this.attemptReconnect();
395
+ });
396
+
397
+ this.ws.on('error', (error) => {
398
+ Logger.error(`WebSocket error: ${error.message}`);
399
+ this.emit('error', error);
400
+ reject(error);
401
+ });
402
+ } catch (error) {
403
+ reject(error);
404
+ }
405
+ });
406
+ }
407
+
408
+ disconnect(): void {
409
+ if (this.ws) {
410
+ this.ws.close();
411
+ this.ws = null;
412
+ }
413
+ }
414
+
415
+ private handleMessage(data: string): void {
416
+ try {
417
+ const message: ElsabroMessage = JSON.parse(data);
418
+
419
+ // Handle response to pending request
420
+ if (message.requestId && this.pendingRequests.has(message.requestId)) {
421
+ const pending = this.pendingRequests.get(message.requestId)!;
422
+ clearTimeout(pending.timeout);
423
+ this.pendingRequests.delete(message.requestId);
424
+ pending.resolve(message.payload);
425
+ return;
426
+ }
427
+
428
+ // Handle server-initiated messages
429
+ switch (message.type) {
430
+ case 'agent:status':
431
+ this.emit('agentStatus', message.payload as AgentStatus);
432
+ break;
433
+ case 'task:progress':
434
+ this.emit('taskProgress', message.payload);
435
+ break;
436
+ case 'task:complete':
437
+ this.emit('taskComplete', message.payload);
438
+ break;
439
+ case 'task:error':
440
+ this.emit('taskError', message.payload);
441
+ break;
442
+ case 'log':
443
+ this.emit('log', message.payload);
444
+ break;
445
+ case 'annotation':
446
+ this.emit('annotation', message.payload);
447
+ break;
448
+ default:
449
+ Logger.debug(`Unknown message type: ${message.type}`);
450
+ }
451
+ } catch (error) {
452
+ Logger.error(`Failed to parse message: ${error}`);
453
+ }
454
+ }
455
+
456
+ async send<T = any>(type: string, payload: any): Promise<T> {
457
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
458
+ throw new Error('Not connected to ELSABRO server');
459
+ }
460
+
461
+ const requestId = this.generateRequestId();
462
+
463
+ return new Promise((resolve, reject) => {
464
+ const timeout = setTimeout(() => {
465
+ this.pendingRequests.delete(requestId);
466
+ reject(new Error('Request timeout'));
467
+ }, 30000);
468
+
469
+ this.pendingRequests.set(requestId, { resolve, reject, timeout });
470
+
471
+ const message: ElsabroMessage = { type, payload, requestId };
472
+ this.ws!.send(JSON.stringify(message));
473
+ });
474
+ }
475
+
476
+ // Agent operations
477
+ async runAgent(agentId: string, params: any): Promise<string> {
478
+ return this.send('agent:run', { agentId, params });
479
+ }
480
+
481
+ async pauseAgent(agentId: string): Promise<void> {
482
+ return this.send('agent:pause', { agentId });
483
+ }
484
+
485
+ async cancelTask(taskId: string): Promise<void> {
486
+ return this.send('task:cancel', { taskId });
487
+ }
488
+
489
+ async getAgents(): Promise<AgentStatus[]> {
490
+ return this.send('agents:list', {});
491
+ }
492
+
493
+ async getTasks(): Promise<any[]> {
494
+ return this.send('tasks:list', {});
495
+ }
496
+
497
+ private attemptReconnect(): void {
498
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
499
+ Logger.error('Max reconnect attempts reached');
500
+ return;
501
+ }
502
+
503
+ this.reconnectAttempts++;
504
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
505
+
506
+ Logger.info(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
507
+
508
+ setTimeout(() => {
509
+ this.connect().catch(() => {});
510
+ }, delay);
511
+ }
512
+
513
+ private generateRequestId(): string {
514
+ return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
515
+ }
516
+
517
+ get isConnected(): boolean {
518
+ return this.ws?.readyState === WebSocket.OPEN;
519
+ }
520
+ }
521
+ ```
522
+
523
+ ### 1.5 WebView Panel
524
+
525
+ ```typescript
526
+ // src/views/webviewPanel.ts
527
+ import * as vscode from 'vscode';
528
+ import { SessionManager } from '../services/sessionManager';
529
+
530
+ export class ElsabroWebviewPanel {
531
+ public static currentPanel: ElsabroWebviewPanel | undefined;
532
+ private readonly panel: vscode.WebviewPanel;
533
+ private disposables: vscode.Disposable[] = [];
534
+
535
+ private constructor(
536
+ panel: vscode.WebviewPanel,
537
+ private extensionUri: vscode.Uri,
538
+ private sessionManager: SessionManager
539
+ ) {
540
+ this.panel = panel;
541
+ this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
542
+ this.panel.webview.html = this.getHtmlContent();
543
+ this.setupMessageHandler();
544
+ }
545
+
546
+ public static createOrShow(
547
+ extensionUri: vscode.Uri,
548
+ sessionManager: SessionManager
549
+ ): void {
550
+ const column = vscode.window.activeTextEditor
551
+ ? vscode.window.activeTextEditor.viewColumn
552
+ : undefined;
553
+
554
+ if (ElsabroWebviewPanel.currentPanel) {
555
+ ElsabroWebviewPanel.currentPanel.panel.reveal(column);
556
+ return;
557
+ }
558
+
559
+ const panel = vscode.window.createWebviewPanel(
560
+ 'elsabroPanel',
561
+ 'ELSABRO Dashboard',
562
+ column || vscode.ViewColumn.One,
563
+ {
564
+ enableScripts: true,
565
+ retainContextWhenHidden: true,
566
+ localResourceRoots: [
567
+ vscode.Uri.joinPath(extensionUri, 'media')
568
+ ]
569
+ }
570
+ );
571
+
572
+ ElsabroWebviewPanel.currentPanel = new ElsabroWebviewPanel(
573
+ panel,
574
+ extensionUri,
575
+ sessionManager
576
+ );
577
+ }
578
+
579
+ private setupMessageHandler(): void {
580
+ this.panel.webview.onDidReceiveMessage(
581
+ async (message) => {
582
+ switch (message.command) {
583
+ case 'runAgent':
584
+ await this.sessionManager.runAgent(message.agentId, message.params);
585
+ break;
586
+ case 'pauseAgent':
587
+ await this.sessionManager.pauseAgent(message.agentId);
588
+ break;
589
+ case 'getStatus':
590
+ const status = await this.sessionManager.getStatus();
591
+ this.panel.webview.postMessage({ type: 'status', data: status });
592
+ break;
593
+ }
594
+ },
595
+ null,
596
+ this.disposables
597
+ );
598
+
599
+ // Listen to session events
600
+ this.sessionManager.on('statusChanged', (status) => {
601
+ this.panel.webview.postMessage({ type: 'status', data: status });
602
+ });
603
+ }
604
+
605
+ private getHtmlContent(): string {
606
+ const scriptUri = this.panel.webview.asWebviewUri(
607
+ vscode.Uri.joinPath(this.extensionUri, 'media', 'webview', 'main.js')
608
+ );
609
+ const styleUri = this.panel.webview.asWebviewUri(
610
+ vscode.Uri.joinPath(this.extensionUri, 'media', 'webview', 'style.css')
611
+ );
612
+
613
+ return `<!DOCTYPE html>
614
+ <html lang="en">
615
+ <head>
616
+ <meta charset="UTF-8">
617
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
618
+ <meta http-equiv="Content-Security-Policy"
619
+ content="default-src 'none';
620
+ style-src ${this.panel.webview.cspSource};
621
+ script-src ${this.panel.webview.cspSource};">
622
+ <link href="${styleUri}" rel="stylesheet">
623
+ <title>ELSABRO Dashboard</title>
624
+ </head>
625
+ <body>
626
+ <div id="app">
627
+ <header class="dashboard-header">
628
+ <h1>ELSABRO Dashboard</h1>
629
+ <div id="connection-status" class="status-indicator"></div>
630
+ </header>
631
+
632
+ <main class="dashboard-content">
633
+ <section class="agents-section">
634
+ <h2>Active Agents</h2>
635
+ <div id="agents-list"></div>
636
+ </section>
637
+
638
+ <section class="tasks-section">
639
+ <h2>Running Tasks</h2>
640
+ <div id="tasks-list"></div>
641
+ </section>
642
+
643
+ <section class="logs-section">
644
+ <h2>Logs</h2>
645
+ <div id="logs-container"></div>
646
+ </section>
647
+ </main>
648
+ </div>
649
+ <script src="${scriptUri}"></script>
650
+ </body>
651
+ </html>`;
652
+ }
653
+
654
+ public dispose(): void {
655
+ ElsabroWebviewPanel.currentPanel = undefined;
656
+ this.panel.dispose();
657
+ while (this.disposables.length) {
658
+ const d = this.disposables.pop();
659
+ if (d) d.dispose();
660
+ }
661
+ }
662
+ }
663
+ ```
664
+
665
+ ---
666
+
667
+ ## 2. JetBrainsPlugin
668
+
669
+ ### 2.1 Estructura del Proyecto
670
+
671
+ ```
672
+ elsabro-jetbrains/
673
+ ├── src/
674
+ │ └── main/
675
+ │ ├── kotlin/
676
+ │ │ └── com/elsabro/plugin/
677
+ │ │ ├── ElsabroPlugin.kt
678
+ │ │ ├── services/
679
+ │ │ │ ├── ElsabroService.kt
680
+ │ │ │ ├── SessionService.kt
681
+ │ │ │ └── ConfigService.kt
682
+ │ │ ├── actions/
683
+ │ │ │ ├── RunAgentAction.kt
684
+ │ │ │ ├── PauseAgentAction.kt
685
+ │ │ │ └── GenerateAction.kt
686
+ │ │ ├── toolwindow/
687
+ │ │ │ ├── ElsabroToolWindowFactory.kt
688
+ │ │ │ ├── AgentsPanel.kt
689
+ │ │ │ ├── TasksPanel.kt
690
+ │ │ │ └── LogsPanel.kt
691
+ │ │ ├── editor/
692
+ │ │ │ ├── ElsabroAnnotator.kt
693
+ │ │ │ ├── ElsabroLineMarker.kt
694
+ │ │ │ └── ElsabroInlayHintsProvider.kt
695
+ │ │ ├── intentions/
696
+ │ │ │ ├── GenerateTestsIntention.kt
697
+ │ │ │ └── RefactorIntention.kt
698
+ │ │ └── statusbar/
699
+ │ │ └── ElsabroStatusBarWidget.kt
700
+ │ └── resources/
701
+ │ ├── META-INF/
702
+ │ │ └── plugin.xml
703
+ │ └── icons/
704
+ ├── build.gradle.kts
705
+ └── gradle.properties
706
+ ```
707
+
708
+ ### 2.2 Plugin Configuration (plugin.xml)
709
+
710
+ ```xml
711
+ <!-- src/main/resources/META-INF/plugin.xml -->
712
+ <idea-plugin>
713
+ <id>com.elsabro.plugin</id>
714
+ <name>ELSABRO AI Orchestration</name>
715
+ <version>3.7.0</version>
716
+ <vendor email="support@elsabro.dev" url="https://elsabro.dev">
717
+ ELSABRO Team
718
+ </vendor>
719
+
720
+ <description><![CDATA[
721
+ AI Agent orchestration for development workflows.
722
+ Supports IntelliJ IDEA, WebStorm, PyCharm, and all JetBrains IDEs.
723
+ ]]></description>
724
+
725
+ <depends>com.intellij.modules.platform</depends>
726
+ <depends optional="true" config-file="elsabro-java.xml">
727
+ com.intellij.modules.java
728
+ </depends>
729
+ <depends optional="true" config-file="elsabro-kotlin.xml">
730
+ org.jetbrains.kotlin
731
+ </depends>
732
+
733
+ <extensions defaultExtensionNs="com.intellij">
734
+ <!-- Services -->
735
+ <applicationService
736
+ serviceImplementation="com.elsabro.plugin.services.ElsabroService"/>
737
+ <projectService
738
+ serviceImplementation="com.elsabro.plugin.services.SessionService"/>
739
+ <projectService
740
+ serviceImplementation="com.elsabro.plugin.services.ConfigService"/>
741
+
742
+ <!-- Tool Window -->
743
+ <toolWindow id="ELSABRO"
744
+ anchor="right"
745
+ icon="/icons/elsabro.svg"
746
+ factoryClass="com.elsabro.plugin.toolwindow.ElsabroToolWindowFactory"/>
747
+
748
+ <!-- Editor Annotations -->
749
+ <annotator language=""
750
+ implementationClass="com.elsabro.plugin.editor.ElsabroAnnotator"/>
751
+
752
+ <!-- Line Markers -->
753
+ <codeInsight.lineMarkerProvider language=""
754
+ implementationClass="com.elsabro.plugin.editor.ElsabroLineMarker"/>
755
+
756
+ <!-- Inlay Hints -->
757
+ <codeInsight.inlayProvider language=""
758
+ implementationClass="com.elsabro.plugin.editor.ElsabroInlayHintsProvider"/>
759
+
760
+ <!-- Status Bar Widget -->
761
+ <statusBarWidgetFactory id="ElsabroStatusBar"
762
+ implementation="com.elsabro.plugin.statusbar.ElsabroStatusBarWidgetFactory"/>
763
+
764
+ <!-- Settings -->
765
+ <applicationConfigurable
766
+ parentId="tools"
767
+ instance="com.elsabro.plugin.settings.ElsabroConfigurable"
768
+ id="com.elsabro.plugin.settings"
769
+ displayName="ELSABRO"/>
770
+
771
+ <!-- Notifications -->
772
+ <notificationGroup id="ELSABRO Notifications"
773
+ displayType="BALLOON"/>
774
+ </extensions>
775
+
776
+ <actions>
777
+ <!-- Main Action Group -->
778
+ <group id="ElsabroActions"
779
+ text="ELSABRO"
780
+ popup="true"
781
+ icon="/icons/elsabro.svg">
782
+
783
+ <action id="Elsabro.RunAgent"
784
+ class="com.elsabro.plugin.actions.RunAgentAction"
785
+ text="Run Agent"
786
+ description="Run ELSABRO agent on selection"
787
+ icon="/icons/play.svg">
788
+ <keyboard-shortcut keymap="$default"
789
+ first-keystroke="ctrl shift E"
790
+ second-keystroke="R"/>
791
+ </action>
792
+
793
+ <action id="Elsabro.PauseAgent"
794
+ class="com.elsabro.plugin.actions.PauseAgentAction"
795
+ text="Pause Agent"
796
+ icon="/icons/pause.svg">
797
+ <keyboard-shortcut keymap="$default"
798
+ first-keystroke="ctrl shift E"
799
+ second-keystroke="P"/>
800
+ </action>
801
+
802
+ <separator/>
803
+
804
+ <action id="Elsabro.GenerateTests"
805
+ class="com.elsabro.plugin.actions.GenerateTestsAction"
806
+ text="Generate Tests"
807
+ icon="/icons/test.svg">
808
+ <keyboard-shortcut keymap="$default"
809
+ first-keystroke="ctrl shift E"
810
+ second-keystroke="T"/>
811
+ </action>
812
+
813
+ <action id="Elsabro.GenerateDocs"
814
+ class="com.elsabro.plugin.actions.GenerateDocsAction"
815
+ text="Generate Documentation"
816
+ icon="/icons/docs.svg"/>
817
+
818
+ <action id="Elsabro.Refactor"
819
+ class="com.elsabro.plugin.actions.RefactorAction"
820
+ text="AI Refactor"
821
+ icon="/icons/refactor.svg"/>
822
+
823
+ <separator/>
824
+
825
+ <action id="Elsabro.ShowPanel"
826
+ class="com.elsabro.plugin.actions.ShowPanelAction"
827
+ text="Show ELSABRO Panel"/>
828
+
829
+ <add-to-group group-id="EditorPopupMenu" anchor="last"/>
830
+ <add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
831
+ </group>
832
+
833
+ <!-- Toolbar Actions -->
834
+ <group id="ElsabroToolbar">
835
+ <reference ref="Elsabro.RunAgent"/>
836
+ <reference ref="Elsabro.PauseAgent"/>
837
+ <add-to-group group-id="MainToolBar" anchor="last"/>
838
+ </group>
839
+ </actions>
840
+ </idea-plugin>
841
+ ```
842
+
843
+ ### 2.3 Main Service (ElsabroService.kt)
844
+
845
+ ```kotlin
846
+ // src/main/kotlin/com/elsabro/plugin/services/ElsabroService.kt
847
+ package com.elsabro.plugin.services
848
+
849
+ import com.intellij.openapi.Disposable
850
+ import com.intellij.openapi.components.Service
851
+ import com.intellij.openapi.components.service
852
+ import com.intellij.openapi.diagnostic.Logger
853
+ import kotlinx.coroutines.*
854
+ import kotlinx.coroutines.flow.*
855
+ import okhttp3.*
856
+ import okio.ByteString
857
+ import org.json.JSONObject
858
+ import java.util.concurrent.ConcurrentHashMap
859
+ import java.util.concurrent.TimeUnit
860
+
861
+ @Service(Service.Level.APP)
862
+ class ElsabroService : Disposable {
863
+ private val LOG = Logger.getInstance(ElsabroService::class.java)
864
+
865
+ private var webSocket: WebSocket? = null
866
+ private val client = OkHttpClient.Builder()
867
+ .readTimeout(0, TimeUnit.MILLISECONDS)
868
+ .build()
869
+
870
+ private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
871
+
872
+ private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
873
+ val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
874
+
875
+ private val _agents = MutableStateFlow<List<AgentStatus>>(emptyList())
876
+ val agents: StateFlow<List<AgentStatus>> = _agents.asStateFlow()
877
+
878
+ private val _tasks = MutableStateFlow<List<TaskInfo>>(emptyList())
879
+ val tasks: StateFlow<List<TaskInfo>> = _tasks.asStateFlow()
880
+
881
+ private val _logs = MutableSharedFlow<LogEntry>(replay = 100)
882
+ val logs: SharedFlow<LogEntry> = _logs.asSharedFlow()
883
+
884
+ private val pendingRequests = ConcurrentHashMap<String, CompletableDeferred<JSONObject>>()
885
+
886
+ fun connect(serverUrl: String = "ws://localhost:3847") {
887
+ if (_connectionState.value is ConnectionState.Connected) return
888
+
889
+ _connectionState.value = ConnectionState.Connecting
890
+
891
+ val request = Request.Builder()
892
+ .url(serverUrl)
893
+ .build()
894
+
895
+ webSocket = client.newWebSocket(request, object : WebSocketListener() {
896
+ override fun onOpen(webSocket: WebSocket, response: Response) {
897
+ LOG.info("Connected to ELSABRO server")
898
+ _connectionState.value = ConnectionState.Connected
899
+ requestInitialState()
900
+ }
901
+
902
+ override fun onMessage(webSocket: WebSocket, text: String) {
903
+ handleMessage(text)
904
+ }
905
+
906
+ override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
907
+ handleMessage(bytes.utf8())
908
+ }
909
+
910
+ override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
911
+ LOG.info("Connection closing: $reason")
912
+ webSocket.close(1000, null)
913
+ }
914
+
915
+ override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
916
+ LOG.info("Connection closed: $reason")
917
+ _connectionState.value = ConnectionState.Disconnected
918
+ }
919
+
920
+ override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
921
+ LOG.error("Connection failed", t)
922
+ _connectionState.value = ConnectionState.Error(t.message ?: "Unknown error")
923
+ scheduleReconnect()
924
+ }
925
+ })
926
+ }
927
+
928
+ fun disconnect() {
929
+ webSocket?.close(1000, "User disconnected")
930
+ webSocket = null
931
+ _connectionState.value = ConnectionState.Disconnected
932
+ }
933
+
934
+ private fun handleMessage(text: String) {
935
+ try {
936
+ val json = JSONObject(text)
937
+ val type = json.getString("type")
938
+ val payload = json.optJSONObject("payload") ?: JSONObject()
939
+ val requestId = json.optString("requestId", null)
940
+
941
+ // Handle response to pending request
942
+ if (requestId != null && pendingRequests.containsKey(requestId)) {
943
+ pendingRequests.remove(requestId)?.complete(payload)
944
+ return
945
+ }
946
+
947
+ // Handle server-initiated messages
948
+ when (type) {
949
+ "agents:update" -> updateAgents(payload)
950
+ "tasks:update" -> updateTasks(payload)
951
+ "task:progress" -> updateTaskProgress(payload)
952
+ "log" -> emitLog(payload)
953
+ "annotation" -> handleAnnotation(payload)
954
+ }
955
+ } catch (e: Exception) {
956
+ LOG.error("Failed to parse message", e)
957
+ }
958
+ }
959
+
960
+ suspend fun runAgent(agentId: String, params: Map<String, Any>): String {
961
+ val response = sendRequest("agent:run", mapOf(
962
+ "agentId" to agentId,
963
+ "params" to params
964
+ ))
965
+ return response.getString("taskId")
966
+ }
967
+
968
+ suspend fun pauseAgent(agentId: String) {
969
+ sendRequest("agent:pause", mapOf("agentId" to agentId))
970
+ }
971
+
972
+ suspend fun cancelTask(taskId: String) {
973
+ sendRequest("task:cancel", mapOf("taskId" to taskId))
974
+ }
975
+
976
+ private suspend fun sendRequest(type: String, payload: Map<String, Any>): JSONObject {
977
+ val requestId = generateRequestId()
978
+ val deferred = CompletableDeferred<JSONObject>()
979
+ pendingRequests[requestId] = deferred
980
+
981
+ val message = JSONObject().apply {
982
+ put("type", type)
983
+ put("payload", JSONObject(payload))
984
+ put("requestId", requestId)
985
+ }
986
+
987
+ webSocket?.send(message.toString())
988
+ ?: throw IllegalStateException("Not connected")
989
+
990
+ return withTimeout(30_000) { deferred.await() }
991
+ }
992
+
993
+ private fun requestInitialState() {
994
+ scope.launch {
995
+ try {
996
+ sendRequest("agents:list", emptyMap())
997
+ sendRequest("tasks:list", emptyMap())
998
+ } catch (e: Exception) {
999
+ LOG.error("Failed to get initial state", e)
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ private fun updateAgents(payload: JSONObject) {
1005
+ val agentsList = mutableListOf<AgentStatus>()
1006
+ val agentsArray = payload.getJSONArray("agents")
1007
+ for (i in 0 until agentsArray.length()) {
1008
+ val agent = agentsArray.getJSONObject(i)
1009
+ agentsList.add(AgentStatus(
1010
+ id = agent.getString("id"),
1011
+ name = agent.getString("name"),
1012
+ status = AgentState.valueOf(agent.getString("status").uppercase()),
1013
+ currentTask = agent.optString("currentTask", null),
1014
+ progress = agent.optInt("progress", 0)
1015
+ ))
1016
+ }
1017
+ _agents.value = agentsList
1018
+ }
1019
+
1020
+ private fun updateTasks(payload: JSONObject) {
1021
+ // Similar implementation for tasks
1022
+ }
1023
+
1024
+ private fun updateTaskProgress(payload: JSONObject) {
1025
+ val taskId = payload.getString("taskId")
1026
+ val progress = payload.getInt("progress")
1027
+ _tasks.value = _tasks.value.map { task ->
1028
+ if (task.id == taskId) task.copy(progress = progress) else task
1029
+ }
1030
+ }
1031
+
1032
+ private fun emitLog(payload: JSONObject) {
1033
+ scope.launch {
1034
+ _logs.emit(LogEntry(
1035
+ timestamp = payload.getLong("timestamp"),
1036
+ level = payload.getString("level"),
1037
+ message = payload.getString("message"),
1038
+ source = payload.optString("source", "system")
1039
+ ))
1040
+ }
1041
+ }
1042
+
1043
+ private fun handleAnnotation(payload: JSONObject) {
1044
+ // Handle inline annotations
1045
+ }
1046
+
1047
+ private fun scheduleReconnect() {
1048
+ scope.launch {
1049
+ delay(5000)
1050
+ connect()
1051
+ }
1052
+ }
1053
+
1054
+ private fun generateRequestId(): String {
1055
+ return "req_${System.currentTimeMillis()}_${(Math.random() * 1000000).toLong()}"
1056
+ }
1057
+
1058
+ override fun dispose() {
1059
+ disconnect()
1060
+ scope.cancel()
1061
+ }
1062
+
1063
+ companion object {
1064
+ fun getInstance(): ElsabroService = service()
1065
+ }
1066
+ }
1067
+
1068
+ sealed class ConnectionState {
1069
+ object Disconnected : ConnectionState()
1070
+ object Connecting : ConnectionState()
1071
+ object Connected : ConnectionState()
1072
+ data class Error(val message: String) : ConnectionState()
1073
+ }
1074
+
1075
+ data class AgentStatus(
1076
+ val id: String,
1077
+ val name: String,
1078
+ val status: AgentState,
1079
+ val currentTask: String?,
1080
+ val progress: Int
1081
+ )
1082
+
1083
+ enum class AgentState {
1084
+ IDLE, RUNNING, PAUSED, ERROR
1085
+ }
1086
+
1087
+ data class TaskInfo(
1088
+ val id: String,
1089
+ val name: String,
1090
+ val agentId: String,
1091
+ val status: String,
1092
+ val progress: Int,
1093
+ val startTime: Long
1094
+ )
1095
+
1096
+ data class LogEntry(
1097
+ val timestamp: Long,
1098
+ val level: String,
1099
+ val message: String,
1100
+ val source: String
1101
+ )
1102
+ ```
1103
+
1104
+ ### 2.4 Tool Window Factory
1105
+
1106
+ ```kotlin
1107
+ // src/main/kotlin/com/elsabro/plugin/toolwindow/ElsabroToolWindowFactory.kt
1108
+ package com.elsabro.plugin.toolwindow
1109
+
1110
+ import com.intellij.openapi.project.Project
1111
+ import com.intellij.openapi.wm.ToolWindow
1112
+ import com.intellij.openapi.wm.ToolWindowFactory
1113
+ import com.intellij.ui.content.ContentFactory
1114
+
1115
+ class ElsabroToolWindowFactory : ToolWindowFactory {
1116
+
1117
+ override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
1118
+ val contentFactory = ContentFactory.getInstance()
1119
+
1120
+ // Agents Panel
1121
+ val agentsPanel = AgentsPanel(project)
1122
+ val agentsContent = contentFactory.createContent(
1123
+ agentsPanel.component,
1124
+ "Agents",
1125
+ false
1126
+ )
1127
+ toolWindow.contentManager.addContent(agentsContent)
1128
+
1129
+ // Tasks Panel
1130
+ val tasksPanel = TasksPanel(project)
1131
+ val tasksContent = contentFactory.createContent(
1132
+ tasksPanel.component,
1133
+ "Tasks",
1134
+ false
1135
+ )
1136
+ toolWindow.contentManager.addContent(tasksContent)
1137
+
1138
+ // Logs Panel
1139
+ val logsPanel = LogsPanel(project)
1140
+ val logsContent = contentFactory.createContent(
1141
+ logsPanel.component,
1142
+ "Logs",
1143
+ false
1144
+ )
1145
+ toolWindow.contentManager.addContent(logsContent)
1146
+ }
1147
+
1148
+ override fun shouldBeAvailable(project: Project): Boolean = true
1149
+ }
1150
+ ```
1151
+
1152
+ ### 2.5 Run Agent Action
1153
+
1154
+ ```kotlin
1155
+ // src/main/kotlin/com/elsabro/plugin/actions/RunAgentAction.kt
1156
+ package com.elsabro.plugin.actions
1157
+
1158
+ import com.elsabro.plugin.services.ElsabroService
1159
+ import com.intellij.openapi.actionSystem.AnAction
1160
+ import com.intellij.openapi.actionSystem.AnActionEvent
1161
+ import com.intellij.openapi.actionSystem.CommonDataKeys
1162
+ import com.intellij.openapi.ui.DialogWrapper
1163
+ import com.intellij.openapi.ui.Messages
1164
+ import kotlinx.coroutines.*
1165
+ import javax.swing.*
1166
+
1167
+ class RunAgentAction : AnAction() {
1168
+
1169
+ override fun actionPerformed(e: AnActionEvent) {
1170
+ val project = e.project ?: return
1171
+ val editor = e.getData(CommonDataKeys.EDITOR)
1172
+ val file = e.getData(CommonDataKeys.VIRTUAL_FILE)
1173
+
1174
+ val service = ElsabroService.getInstance()
1175
+
1176
+ // Get selected text or current file
1177
+ val selection = editor?.selectionModel?.selectedText
1178
+ val filePath = file?.path
1179
+
1180
+ // Show agent selection dialog
1181
+ val dialog = AgentSelectionDialog(project, service)
1182
+ if (dialog.showAndGet()) {
1183
+ val agentId = dialog.selectedAgent ?: return
1184
+
1185
+ CoroutineScope(Dispatchers.Main).launch {
1186
+ try {
1187
+ val taskId = service.runAgent(agentId, mapOf(
1188
+ "selection" to (selection ?: ""),
1189
+ "filePath" to (filePath ?: ""),
1190
+ "projectPath" to project.basePath
1191
+ ))
1192
+
1193
+ Messages.showInfoMessage(
1194
+ project,
1195
+ "Task started: $taskId",
1196
+ "ELSABRO"
1197
+ )
1198
+ } catch (ex: Exception) {
1199
+ Messages.showErrorDialog(
1200
+ project,
1201
+ "Failed to run agent: ${ex.message}",
1202
+ "ELSABRO Error"
1203
+ )
1204
+ }
1205
+ }
1206
+ }
1207
+ }
1208
+
1209
+ override fun update(e: AnActionEvent) {
1210
+ val service = ElsabroService.getInstance()
1211
+ e.presentation.isEnabled = service.connectionState.value is
1212
+ com.elsabro.plugin.services.ConnectionState.Connected
1213
+ }
1214
+ }
1215
+
1216
+ class AgentSelectionDialog(
1217
+ project: com.intellij.openapi.project.Project,
1218
+ private val service: ElsabroService
1219
+ ) : DialogWrapper(project) {
1220
+
1221
+ var selectedAgent: String? = null
1222
+ private val agentList = JList<String>()
1223
+
1224
+ init {
1225
+ title = "Select Agent"
1226
+ init()
1227
+ loadAgents()
1228
+ }
1229
+
1230
+ private fun loadAgents() {
1231
+ val agents = service.agents.value
1232
+ agentList.setListData(agents.map { it.name }.toTypedArray())
1233
+ }
1234
+
1235
+ override fun createCenterPanel(): JComponent {
1236
+ return JPanel().apply {
1237
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
1238
+ add(JLabel("Select an agent to run:"))
1239
+ add(JScrollPane(agentList))
1240
+ }
1241
+ }
1242
+
1243
+ override fun doOKAction() {
1244
+ val agents = service.agents.value
1245
+ val selectedIndex = agentList.selectedIndex
1246
+ if (selectedIndex >= 0) {
1247
+ selectedAgent = agents[selectedIndex].id
1248
+ }
1249
+ super.doOKAction()
1250
+ }
1251
+ }
1252
+ ```
1253
+
1254
+ ---
1255
+
1256
+ ## 3. LSPServer
1257
+
1258
+ ### 3.1 Arquitectura LSP
1259
+
1260
+ ```
1261
+ +------------------------------------------------------------------+
1262
+ | LSP Server Architecture |
1263
+ +------------------------------------------------------------------+
1264
+ | |
1265
+ | +-------------+ +------------------+ +----------------+ |
1266
+ | | Editor |<--->| LSP Transport |<--->| LSP Server | |
1267
+ | | (Any IDE) | | (stdio/socket) | | (Node.js) | |
1268
+ | +-------------+ +------------------+ +-------+--------+ |
1269
+ | | |
1270
+ | +-------v--------+ |
1271
+ | | ELSABRO Client | |
1272
+ | +----------------+ |
1273
+ | |
1274
+ +------------------------------------------------------------------+
1275
+
1276
+ Standard LSP Capabilities:
1277
+ - textDocument/completion
1278
+ - textDocument/hover
1279
+ - textDocument/codeAction
1280
+ - textDocument/codeLens
1281
+ - textDocument/diagnostic
1282
+
1283
+ Custom ELSABRO Requests:
1284
+ - elsabro/runAgent
1285
+ - elsabro/getAgents
1286
+ - elsabro/getTasks
1287
+ - elsabro/cancelTask
1288
+ ```
1289
+
1290
+ ### 3.2 LSP Server Implementation
1291
+
1292
+ ```typescript
1293
+ // lsp-server/src/server.ts
1294
+ import {
1295
+ createConnection,
1296
+ TextDocuments,
1297
+ ProposedFeatures,
1298
+ InitializeParams,
1299
+ InitializeResult,
1300
+ TextDocumentSyncKind,
1301
+ CompletionItem,
1302
+ CompletionItemKind,
1303
+ Hover,
1304
+ CodeAction,
1305
+ CodeActionKind,
1306
+ CodeLens,
1307
+ Diagnostic,
1308
+ DiagnosticSeverity,
1309
+ Connection,
1310
+ } from 'vscode-languageserver/node';
1311
+ import { TextDocument } from 'vscode-languageserver-textdocument';
1312
+ import { ElsabroClient } from './elsabroClient';
1313
+
1314
+ // Create LSP connection
1315
+ const connection: Connection = createConnection(ProposedFeatures.all);
1316
+ const documents = new TextDocuments(TextDocument);
1317
+
1318
+ let elsabroClient: ElsabroClient;
1319
+ let hasConfigurationCapability = false;
1320
+ let hasWorkspaceFolderCapability = false;
1321
+
1322
+ // Initialize
1323
+ connection.onInitialize((params: InitializeParams): InitializeResult => {
1324
+ const capabilities = params.capabilities;
1325
+
1326
+ hasConfigurationCapability = !!(
1327
+ capabilities.workspace && !!capabilities.workspace.configuration
1328
+ );
1329
+ hasWorkspaceFolderCapability = !!(
1330
+ capabilities.workspace && !!capabilities.workspace.workspaceFolders
1331
+ );
1332
+
1333
+ const result: InitializeResult = {
1334
+ capabilities: {
1335
+ textDocumentSync: TextDocumentSyncKind.Incremental,
1336
+
1337
+ // Completion
1338
+ completionProvider: {
1339
+ resolveProvider: true,
1340
+ triggerCharacters: ['@', '/'],
1341
+ },
1342
+
1343
+ // Hover
1344
+ hoverProvider: true,
1345
+
1346
+ // Code Actions
1347
+ codeActionProvider: {
1348
+ codeActionKinds: [
1349
+ CodeActionKind.QuickFix,
1350
+ CodeActionKind.Refactor,
1351
+ CodeActionKind.Source,
1352
+ ],
1353
+ },
1354
+
1355
+ // Code Lens
1356
+ codeLensProvider: {
1357
+ resolveProvider: true,
1358
+ },
1359
+
1360
+ // Execute Command
1361
+ executeCommandProvider: {
1362
+ commands: [
1363
+ 'elsabro.runAgent',
1364
+ 'elsabro.generateTests',
1365
+ 'elsabro.generateDocs',
1366
+ 'elsabro.refactor',
1367
+ ],
1368
+ },
1369
+ },
1370
+ };
1371
+
1372
+ if (hasWorkspaceFolderCapability) {
1373
+ result.capabilities.workspace = {
1374
+ workspaceFolders: {
1375
+ supported: true,
1376
+ },
1377
+ };
1378
+ }
1379
+
1380
+ return result;
1381
+ });
1382
+
1383
+ connection.onInitialized(async () => {
1384
+ // Initialize ELSABRO client
1385
+ elsabroClient = new ElsabroClient();
1386
+ await elsabroClient.connect();
1387
+
1388
+ // Listen for agent events
1389
+ elsabroClient.on('annotation', (annotation) => {
1390
+ sendDiagnostics(annotation);
1391
+ });
1392
+
1393
+ connection.console.log('ELSABRO LSP Server initialized');
1394
+ });
1395
+
1396
+ // Completion
1397
+ connection.onCompletion((params): CompletionItem[] => {
1398
+ const document = documents.get(params.textDocument.uri);
1399
+ if (!document) return [];
1400
+
1401
+ const text = document.getText();
1402
+ const offset = document.offsetAt(params.position);
1403
+ const linePrefix = text.substring(
1404
+ text.lastIndexOf('\n', offset - 1) + 1,
1405
+ offset
1406
+ );
1407
+
1408
+ const items: CompletionItem[] = [];
1409
+
1410
+ // ELSABRO command completions
1411
+ if (linePrefix.endsWith('@elsabro')) {
1412
+ items.push(
1413
+ {
1414
+ label: '@elsabro:run',
1415
+ kind: CompletionItemKind.Function,
1416
+ detail: 'Run ELSABRO agent',
1417
+ insertText: '@elsabro:run ',
1418
+ },
1419
+ {
1420
+ label: '@elsabro:test',
1421
+ kind: CompletionItemKind.Function,
1422
+ detail: 'Generate tests',
1423
+ insertText: '@elsabro:test ',
1424
+ },
1425
+ {
1426
+ label: '@elsabro:docs',
1427
+ kind: CompletionItemKind.Function,
1428
+ detail: 'Generate documentation',
1429
+ insertText: '@elsabro:docs ',
1430
+ }
1431
+ );
1432
+ }
1433
+
1434
+ // Agent name completions
1435
+ if (linePrefix.includes('@elsabro:run ')) {
1436
+ const agents = elsabroClient.getCachedAgents();
1437
+ agents.forEach((agent) => {
1438
+ items.push({
1439
+ label: agent.name,
1440
+ kind: CompletionItemKind.Module,
1441
+ detail: `Agent: ${agent.id}`,
1442
+ insertText: agent.id,
1443
+ });
1444
+ });
1445
+ }
1446
+
1447
+ return items;
1448
+ });
1449
+
1450
+ // Hover
1451
+ connection.onHover((params): Hover | null => {
1452
+ const document = documents.get(params.textDocument.uri);
1453
+ if (!document) return null;
1454
+
1455
+ const text = document.getText();
1456
+ const offset = document.offsetAt(params.position);
1457
+
1458
+ // Check for ELSABRO annotations
1459
+ const annotation = elsabroClient.getAnnotationAt(
1460
+ params.textDocument.uri,
1461
+ params.position.line
1462
+ );
1463
+
1464
+ if (annotation) {
1465
+ return {
1466
+ contents: {
1467
+ kind: 'markdown',
1468
+ value: [
1469
+ `**ELSABRO ${annotation.type}**`,
1470
+ '',
1471
+ annotation.message,
1472
+ '',
1473
+ annotation.details ? `\`\`\`\n${annotation.details}\n\`\`\`` : '',
1474
+ ].join('\n'),
1475
+ },
1476
+ };
1477
+ }
1478
+
1479
+ return null;
1480
+ });
1481
+
1482
+ // Code Actions
1483
+ connection.onCodeAction((params): CodeAction[] => {
1484
+ const document = documents.get(params.textDocument.uri);
1485
+ if (!document) return [];
1486
+
1487
+ const actions: CodeAction[] = [];
1488
+
1489
+ // Generate Tests action
1490
+ actions.push({
1491
+ title: 'ELSABRO: Generate Tests',
1492
+ kind: CodeActionKind.Source,
1493
+ command: {
1494
+ title: 'Generate Tests',
1495
+ command: 'elsabro.generateTests',
1496
+ arguments: [params.textDocument.uri, params.range],
1497
+ },
1498
+ });
1499
+
1500
+ // Generate Docs action
1501
+ actions.push({
1502
+ title: 'ELSABRO: Generate Documentation',
1503
+ kind: CodeActionKind.Source,
1504
+ command: {
1505
+ title: 'Generate Documentation',
1506
+ command: 'elsabro.generateDocs',
1507
+ arguments: [params.textDocument.uri, params.range],
1508
+ },
1509
+ });
1510
+
1511
+ // AI Refactor action
1512
+ if (params.range.start.line !== params.range.end.line) {
1513
+ actions.push({
1514
+ title: 'ELSABRO: AI Refactor',
1515
+ kind: CodeActionKind.Refactor,
1516
+ command: {
1517
+ title: 'AI Refactor',
1518
+ command: 'elsabro.refactor',
1519
+ arguments: [params.textDocument.uri, params.range],
1520
+ },
1521
+ });
1522
+ }
1523
+
1524
+ // Quick fixes from diagnostics
1525
+ params.context.diagnostics
1526
+ .filter((d) => d.source === 'elsabro')
1527
+ .forEach((diagnostic) => {
1528
+ if (diagnostic.data?.quickFix) {
1529
+ actions.push({
1530
+ title: diagnostic.data.quickFix.title,
1531
+ kind: CodeActionKind.QuickFix,
1532
+ diagnostics: [diagnostic],
1533
+ edit: diagnostic.data.quickFix.edit,
1534
+ });
1535
+ }
1536
+ });
1537
+
1538
+ return actions;
1539
+ });
1540
+
1541
+ // Code Lens
1542
+ connection.onCodeLens((params): CodeLens[] => {
1543
+ const document = documents.get(params.textDocument.uri);
1544
+ if (!document) return [];
1545
+
1546
+ const codeLenses: CodeLens[] = [];
1547
+ const text = document.getText();
1548
+ const lines = text.split('\n');
1549
+
1550
+ lines.forEach((line, index) => {
1551
+ // Add CodeLens for functions/classes
1552
+ if (
1553
+ line.match(/^\s*(function|class|const\s+\w+\s*=\s*(?:async\s*)?\(|def\s+|public\s+|private\s+)/)
1554
+ ) {
1555
+ codeLenses.push({
1556
+ range: {
1557
+ start: { line: index, character: 0 },
1558
+ end: { line: index, character: line.length },
1559
+ },
1560
+ command: {
1561
+ title: 'Run Agent',
1562
+ command: 'elsabro.runAgent',
1563
+ arguments: [params.textDocument.uri, index],
1564
+ },
1565
+ });
1566
+
1567
+ codeLenses.push({
1568
+ range: {
1569
+ start: { line: index, character: 0 },
1570
+ end: { line: index, character: line.length },
1571
+ },
1572
+ command: {
1573
+ title: 'Generate Tests',
1574
+ command: 'elsabro.generateTests',
1575
+ arguments: [params.textDocument.uri, index],
1576
+ },
1577
+ });
1578
+ }
1579
+ });
1580
+
1581
+ return codeLenses;
1582
+ });
1583
+
1584
+ // Execute Command
1585
+ connection.onExecuteCommand(async (params) => {
1586
+ switch (params.command) {
1587
+ case 'elsabro.runAgent':
1588
+ const [uri, agentId] = params.arguments || [];
1589
+ if (uri && agentId) {
1590
+ const taskId = await elsabroClient.runAgent(agentId, {
1591
+ uri,
1592
+ projectPath: getWorkspaceFolder(uri),
1593
+ });
1594
+ connection.console.log(`Task started: ${taskId}`);
1595
+ }
1596
+ break;
1597
+
1598
+ case 'elsabro.generateTests':
1599
+ await handleGenerateTests(params.arguments);
1600
+ break;
1601
+
1602
+ case 'elsabro.generateDocs':
1603
+ await handleGenerateDocs(params.arguments);
1604
+ break;
1605
+
1606
+ case 'elsabro.refactor':
1607
+ await handleRefactor(params.arguments);
1608
+ break;
1609
+ }
1610
+ });
1611
+
1612
+ // Custom ELSABRO requests
1613
+ connection.onRequest('elsabro/runAgent', async (params: {
1614
+ agentId: string;
1615
+ params: any;
1616
+ }) => {
1617
+ return elsabroClient.runAgent(params.agentId, params.params);
1618
+ });
1619
+
1620
+ connection.onRequest('elsabro/getAgents', async () => {
1621
+ return elsabroClient.getAgents();
1622
+ });
1623
+
1624
+ connection.onRequest('elsabro/getTasks', async () => {
1625
+ return elsabroClient.getTasks();
1626
+ });
1627
+
1628
+ connection.onRequest('elsabro/cancelTask', async (params: { taskId: string }) => {
1629
+ return elsabroClient.cancelTask(params.taskId);
1630
+ });
1631
+
1632
+ // Helper functions
1633
+ function sendDiagnostics(annotation: any): void {
1634
+ const diagnostics: Diagnostic[] = [{
1635
+ severity: getSeverity(annotation.level),
1636
+ range: {
1637
+ start: { line: annotation.line, character: 0 },
1638
+ end: { line: annotation.line, character: Number.MAX_SAFE_INTEGER },
1639
+ },
1640
+ message: annotation.message,
1641
+ source: 'elsabro',
1642
+ data: annotation.quickFix ? { quickFix: annotation.quickFix } : undefined,
1643
+ }];
1644
+
1645
+ connection.sendDiagnostics({
1646
+ uri: annotation.uri,
1647
+ diagnostics,
1648
+ });
1649
+ }
1650
+
1651
+ function getSeverity(level: string): DiagnosticSeverity {
1652
+ switch (level) {
1653
+ case 'error':
1654
+ return DiagnosticSeverity.Error;
1655
+ case 'warning':
1656
+ return DiagnosticSeverity.Warning;
1657
+ case 'info':
1658
+ return DiagnosticSeverity.Information;
1659
+ default:
1660
+ return DiagnosticSeverity.Hint;
1661
+ }
1662
+ }
1663
+
1664
+ function getWorkspaceFolder(uri: string): string | undefined {
1665
+ // Implementation to get workspace folder
1666
+ return undefined;
1667
+ }
1668
+
1669
+ async function handleGenerateTests(args: any[]): Promise<void> {
1670
+ const [uri, range] = args;
1671
+ await elsabroClient.runAgent('test-generator', { uri, range });
1672
+ }
1673
+
1674
+ async function handleGenerateDocs(args: any[]): Promise<void> {
1675
+ const [uri, range] = args;
1676
+ await elsabroClient.runAgent('doc-generator', { uri, range });
1677
+ }
1678
+
1679
+ async function handleRefactor(args: any[]): Promise<void> {
1680
+ const [uri, range] = args;
1681
+ await elsabroClient.runAgent('refactoring-specialist', { uri, range });
1682
+ }
1683
+
1684
+ // Start server
1685
+ documents.listen(connection);
1686
+ connection.listen();
1687
+ ```
1688
+
1689
+ ---
1690
+
1691
+ ## 4. SidebarPanel
1692
+
1693
+ ### 4.1 TreeView Provider (VSCode)
1694
+
1695
+ ```typescript
1696
+ // src/providers/treeDataProvider.ts
1697
+ import * as vscode from 'vscode';
1698
+ import { SessionManager } from '../services/sessionManager';
1699
+
1700
+ export class AgentsTreeProvider implements vscode.TreeDataProvider<TreeItem> {
1701
+ private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
1702
+ readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
1703
+
1704
+ constructor(private sessionManager: SessionManager) {
1705
+ sessionManager.on('agentsChanged', () => this.refresh());
1706
+ sessionManager.on('tasksChanged', () => this.refresh());
1707
+ }
1708
+
1709
+ refresh(): void {
1710
+ this._onDidChangeTreeData.fire(undefined);
1711
+ }
1712
+
1713
+ getTreeItem(element: TreeItem): vscode.TreeItem {
1714
+ return element;
1715
+ }
1716
+
1717
+ async getChildren(element?: TreeItem): Promise<TreeItem[]> {
1718
+ if (!element) {
1719
+ // Root level - show categories
1720
+ return [
1721
+ new CategoryItem('Agents', 'agents'),
1722
+ new CategoryItem('Running Tasks', 'tasks'),
1723
+ new CategoryItem('Recent', 'recent'),
1724
+ ];
1725
+ }
1726
+
1727
+ switch (element.contextValue) {
1728
+ case 'category-agents':
1729
+ return this.getAgentItems();
1730
+ case 'category-tasks':
1731
+ return this.getTaskItems();
1732
+ case 'category-recent':
1733
+ return this.getRecentItems();
1734
+ default:
1735
+ return [];
1736
+ }
1737
+ }
1738
+
1739
+ private async getAgentItems(): Promise<AgentItem[]> {
1740
+ const agents = await this.sessionManager.getAgents();
1741
+ return agents.map((agent) => new AgentItem(agent));
1742
+ }
1743
+
1744
+ private async getTaskItems(): Promise<TaskItem[]> {
1745
+ const tasks = await this.sessionManager.getTasks();
1746
+ return tasks
1747
+ .filter((t) => t.status === 'running')
1748
+ .map((task) => new TaskItem(task));
1749
+ }
1750
+
1751
+ private async getRecentItems(): Promise<TaskItem[]> {
1752
+ const tasks = await this.sessionManager.getTasks();
1753
+ return tasks
1754
+ .filter((t) => t.status !== 'running')
1755
+ .slice(0, 10)
1756
+ .map((task) => new TaskItem(task));
1757
+ }
1758
+ }
1759
+
1760
+ class TreeItem extends vscode.TreeItem {
1761
+ constructor(
1762
+ label: string,
1763
+ collapsibleState: vscode.TreeItemCollapsibleState
1764
+ ) {
1765
+ super(label, collapsibleState);
1766
+ }
1767
+ }
1768
+
1769
+ class CategoryItem extends TreeItem {
1770
+ constructor(label: string, category: string) {
1771
+ super(label, vscode.TreeItemCollapsibleState.Expanded);
1772
+ this.contextValue = `category-${category}`;
1773
+ this.iconPath = new vscode.ThemeIcon(this.getIcon(category));
1774
+ }
1775
+
1776
+ private getIcon(category: string): string {
1777
+ switch (category) {
1778
+ case 'agents':
1779
+ return 'symbol-class';
1780
+ case 'tasks':
1781
+ return 'play-circle';
1782
+ case 'recent':
1783
+ return 'history';
1784
+ default:
1785
+ return 'folder';
1786
+ }
1787
+ }
1788
+ }
1789
+
1790
+ class AgentItem extends TreeItem {
1791
+ constructor(agent: any) {
1792
+ super(agent.name, vscode.TreeItemCollapsibleState.None);
1793
+ this.contextValue = 'agent';
1794
+ this.id = agent.id;
1795
+ this.description = agent.status;
1796
+ this.tooltip = `${agent.name}\nStatus: ${agent.status}`;
1797
+ this.iconPath = new vscode.ThemeIcon(this.getStatusIcon(agent.status));
1798
+
1799
+ this.command = {
1800
+ command: 'elsabro.showAgentDetails',
1801
+ title: 'Show Details',
1802
+ arguments: [agent.id],
1803
+ };
1804
+ }
1805
+
1806
+ private getStatusIcon(status: string): string {
1807
+ switch (status) {
1808
+ case 'running':
1809
+ return 'sync~spin';
1810
+ case 'idle':
1811
+ return 'circle-outline';
1812
+ case 'paused':
1813
+ return 'debug-pause';
1814
+ case 'error':
1815
+ return 'error';
1816
+ default:
1817
+ return 'circle-outline';
1818
+ }
1819
+ }
1820
+ }
1821
+
1822
+ class TaskItem extends TreeItem {
1823
+ constructor(task: any) {
1824
+ super(task.name, vscode.TreeItemCollapsibleState.None);
1825
+ this.contextValue = task.status === 'running' ? 'runningTask' : 'task';
1826
+ this.id = task.id;
1827
+ this.description = `${task.progress}%`;
1828
+ this.tooltip = `Task: ${task.name}\nAgent: ${task.agentId}\nProgress: ${task.progress}%`;
1829
+ this.iconPath = new vscode.ThemeIcon(this.getStatusIcon(task.status));
1830
+ }
1831
+
1832
+ private getStatusIcon(status: string): string {
1833
+ switch (status) {
1834
+ case 'running':
1835
+ return 'loading~spin';
1836
+ case 'completed':
1837
+ return 'check';
1838
+ case 'failed':
1839
+ return 'error';
1840
+ case 'cancelled':
1841
+ return 'circle-slash';
1842
+ default:
1843
+ return 'circle-outline';
1844
+ }
1845
+ }
1846
+ }
1847
+ ```
1848
+
1849
+ ### 4.2 Agents Panel (JetBrains)
1850
+
1851
+ ```kotlin
1852
+ // src/main/kotlin/com/elsabro/plugin/toolwindow/AgentsPanel.kt
1853
+ package com.elsabro.plugin.toolwindow
1854
+
1855
+ import com.elsabro.plugin.services.AgentStatus
1856
+ import com.elsabro.plugin.services.ElsabroService
1857
+ import com.intellij.openapi.project.Project
1858
+ import com.intellij.ui.components.JBList
1859
+ import com.intellij.ui.components.JBScrollPane
1860
+ import com.intellij.util.ui.JBUI
1861
+ import kotlinx.coroutines.*
1862
+ import kotlinx.coroutines.flow.collectLatest
1863
+ import java.awt.BorderLayout
1864
+ import java.awt.Component
1865
+ import javax.swing.*
1866
+
1867
+ class AgentsPanel(private val project: Project) {
1868
+ val component: JComponent
1869
+ private val listModel = DefaultListModel<AgentStatus>()
1870
+ private val agentsList = JBList(listModel)
1871
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
1872
+
1873
+ init {
1874
+ component = JPanel(BorderLayout()).apply {
1875
+ border = JBUI.Borders.empty(5)
1876
+
1877
+ // Toolbar
1878
+ val toolbar = createToolbar()
1879
+ add(toolbar, BorderLayout.NORTH)
1880
+
1881
+ // Agents list
1882
+ agentsList.cellRenderer = AgentCellRenderer()
1883
+ agentsList.addMouseListener(object : java.awt.event.MouseAdapter() {
1884
+ override fun mouseClicked(e: java.awt.event.MouseEvent) {
1885
+ if (e.clickCount == 2) {
1886
+ val agent = agentsList.selectedValue
1887
+ if (agent != null) {
1888
+ runAgent(agent)
1889
+ }
1890
+ }
1891
+ }
1892
+ })
1893
+ add(JBScrollPane(agentsList), BorderLayout.CENTER)
1894
+ }
1895
+
1896
+ // Subscribe to agents updates
1897
+ subscribeToAgents()
1898
+ }
1899
+
1900
+ private fun createToolbar(): JPanel {
1901
+ return JPanel().apply {
1902
+ layout = BoxLayout(this, BoxLayout.X_AXIS)
1903
+
1904
+ add(JButton("Run").apply {
1905
+ addActionListener {
1906
+ agentsList.selectedValue?.let { runAgent(it) }
1907
+ }
1908
+ })
1909
+
1910
+ add(Box.createHorizontalStrut(5))
1911
+
1912
+ add(JButton("Pause").apply {
1913
+ addActionListener {
1914
+ agentsList.selectedValue?.let { pauseAgent(it) }
1915
+ }
1916
+ })
1917
+
1918
+ add(Box.createHorizontalStrut(5))
1919
+
1920
+ add(JButton("Refresh").apply {
1921
+ addActionListener { refresh() }
1922
+ })
1923
+ }
1924
+ }
1925
+
1926
+ private fun subscribeToAgents() {
1927
+ scope.launch {
1928
+ ElsabroService.getInstance().agents.collectLatest { agents ->
1929
+ listModel.clear()
1930
+ agents.forEach { listModel.addElement(it) }
1931
+ }
1932
+ }
1933
+ }
1934
+
1935
+ private fun runAgent(agent: AgentStatus) {
1936
+ scope.launch {
1937
+ try {
1938
+ ElsabroService.getInstance().runAgent(agent.id, mapOf(
1939
+ "projectPath" to project.basePath
1940
+ ))
1941
+ } catch (e: Exception) {
1942
+ // Show error notification
1943
+ }
1944
+ }
1945
+ }
1946
+
1947
+ private fun pauseAgent(agent: AgentStatus) {
1948
+ scope.launch {
1949
+ try {
1950
+ ElsabroService.getInstance().pauseAgent(agent.id)
1951
+ } catch (e: Exception) {
1952
+ // Show error notification
1953
+ }
1954
+ }
1955
+ }
1956
+
1957
+ private fun refresh() {
1958
+ // Trigger refresh
1959
+ }
1960
+ }
1961
+
1962
+ class AgentCellRenderer : ListCellRenderer<AgentStatus> {
1963
+ private val panel = JPanel(BorderLayout())
1964
+ private val nameLabel = JLabel()
1965
+ private val statusLabel = JLabel()
1966
+ private val progressBar = JProgressBar()
1967
+
1968
+ init {
1969
+ panel.border = JBUI.Borders.empty(5)
1970
+
1971
+ val textPanel = JPanel().apply {
1972
+ layout = BoxLayout(this, BoxLayout.Y_AXIS)
1973
+ add(nameLabel)
1974
+ add(statusLabel)
1975
+ }
1976
+
1977
+ panel.add(textPanel, BorderLayout.CENTER)
1978
+ panel.add(progressBar, BorderLayout.SOUTH)
1979
+ }
1980
+
1981
+ override fun getListCellRendererComponent(
1982
+ list: JList<out AgentStatus>,
1983
+ value: AgentStatus,
1984
+ index: Int,
1985
+ isSelected: Boolean,
1986
+ cellHasFocus: Boolean
1987
+ ): Component {
1988
+ nameLabel.text = value.name
1989
+ statusLabel.text = value.status.name.lowercase()
1990
+ progressBar.value = value.progress
1991
+ progressBar.isVisible = value.status == com.elsabro.plugin.services.AgentState.RUNNING
1992
+
1993
+ if (isSelected) {
1994
+ panel.background = list.selectionBackground
1995
+ nameLabel.foreground = list.selectionForeground
1996
+ statusLabel.foreground = list.selectionForeground
1997
+ } else {
1998
+ panel.background = list.background
1999
+ nameLabel.foreground = list.foreground
2000
+ statusLabel.foreground = java.awt.Color.GRAY
2001
+ }
2002
+
2003
+ return panel
2004
+ }
2005
+ }
2006
+ ```
2007
+
2008
+ ---
2009
+
2010
+ ## 5. InlineAnnotations
2011
+
2012
+ ### 5.1 CodeLens Provider (VSCode)
2013
+
2014
+ ```typescript
2015
+ // src/providers/codeLensProvider.ts
2016
+ import * as vscode from 'vscode';
2017
+ import { SessionManager } from '../services/sessionManager';
2018
+
2019
+ export class ElsabroCodeLensProvider implements vscode.CodeLensProvider {
2020
+ private _onDidChangeCodeLenses = new vscode.EventEmitter<void>();
2021
+ readonly onDidChangeCodeLenses = this._onDidChangeCodeLenses.event;
2022
+
2023
+ constructor(private sessionManager: SessionManager) {
2024
+ sessionManager.on('annotationsChanged', () => {
2025
+ this._onDidChangeCodeLenses.fire();
2026
+ });
2027
+ }
2028
+
2029
+ provideCodeLenses(
2030
+ document: vscode.TextDocument,
2031
+ token: vscode.CancellationToken
2032
+ ): vscode.CodeLens[] {
2033
+ const codeLenses: vscode.CodeLens[] = [];
2034
+ const text = document.getText();
2035
+ const lines = text.split('\n');
2036
+
2037
+ // Pattern to match functions, classes, methods
2038
+ const patterns = [
2039
+ /^\s*(export\s+)?(async\s+)?function\s+(\w+)/,
2040
+ /^\s*(export\s+)?class\s+(\w+)/,
2041
+ /^\s*(public|private|protected)?\s*(async\s+)?(\w+)\s*\(/,
2042
+ /^\s*const\s+(\w+)\s*=\s*(async\s*)?\(/,
2043
+ /^\s*def\s+(\w+)\s*\(/,
2044
+ ];
2045
+
2046
+ lines.forEach((line, index) => {
2047
+ for (const pattern of patterns) {
2048
+ if (pattern.test(line)) {
2049
+ const range = new vscode.Range(index, 0, index, line.length);
2050
+
2051
+ // Run Agent CodeLens
2052
+ codeLenses.push(
2053
+ new vscode.CodeLens(range, {
2054
+ title: '$(play) Run Agent',
2055
+ command: 'elsabro.runAgent',
2056
+ arguments: [document.uri, index],
2057
+ })
2058
+ );
2059
+
2060
+ // Generate Tests CodeLens
2061
+ codeLenses.push(
2062
+ new vscode.CodeLens(range, {
2063
+ title: '$(beaker) Tests',
2064
+ command: 'elsabro.generateTests',
2065
+ arguments: [document.uri, index],
2066
+ })
2067
+ );
2068
+
2069
+ // Generate Docs CodeLens
2070
+ codeLenses.push(
2071
+ new vscode.CodeLens(range, {
2072
+ title: '$(book) Docs',
2073
+ command: 'elsabro.generateDocs',
2074
+ arguments: [document.uri, index],
2075
+ })
2076
+ );
2077
+
2078
+ break;
2079
+ }
2080
+ }
2081
+ });
2082
+
2083
+ // Add CodeLenses from active annotations
2084
+ const annotations = this.sessionManager.getAnnotations(document.uri.toString());
2085
+ annotations.forEach((annotation) => {
2086
+ const range = new vscode.Range(
2087
+ annotation.line,
2088
+ 0,
2089
+ annotation.line,
2090
+ document.lineAt(annotation.line).text.length
2091
+ );
2092
+
2093
+ codeLenses.push(
2094
+ new vscode.CodeLens(range, {
2095
+ title: `$(${annotation.icon}) ${annotation.title}`,
2096
+ command: annotation.command,
2097
+ arguments: annotation.args,
2098
+ })
2099
+ );
2100
+ });
2101
+
2102
+ return codeLenses;
2103
+ }
2104
+ }
2105
+ ```
2106
+
2107
+ ### 5.2 Decoration Provider (VSCode)
2108
+
2109
+ ```typescript
2110
+ // src/providers/decorationProvider.ts
2111
+ import * as vscode from 'vscode';
2112
+ import { SessionManager } from '../services/sessionManager';
2113
+
2114
+ export class ElsabroDecorationProvider {
2115
+ private decorationTypes: Map<string, vscode.TextEditorDecorationType> = new Map();
2116
+ private disposables: vscode.Disposable[] = [];
2117
+
2118
+ constructor(private sessionManager: SessionManager) {
2119
+ this.initializeDecorationTypes();
2120
+ this.registerListeners();
2121
+ }
2122
+
2123
+ private initializeDecorationTypes(): void {
2124
+ // Warning decoration
2125
+ this.decorationTypes.set(
2126
+ 'warning',
2127
+ vscode.window.createTextEditorDecorationType({
2128
+ backgroundColor: 'rgba(255, 193, 7, 0.1)',
2129
+ borderColor: 'rgba(255, 193, 7, 0.5)',
2130
+ borderWidth: '0 0 0 3px',
2131
+ borderStyle: 'solid',
2132
+ overviewRulerColor: 'rgba(255, 193, 7, 0.8)',
2133
+ overviewRulerLane: vscode.OverviewRulerLane.Right,
2134
+ gutterIconPath: this.getGutterIcon('warning'),
2135
+ gutterIconSize: 'contain',
2136
+ })
2137
+ );
2138
+
2139
+ // Error decoration
2140
+ this.decorationTypes.set(
2141
+ 'error',
2142
+ vscode.window.createTextEditorDecorationType({
2143
+ backgroundColor: 'rgba(244, 67, 54, 0.1)',
2144
+ borderColor: 'rgba(244, 67, 54, 0.5)',
2145
+ borderWidth: '0 0 0 3px',
2146
+ borderStyle: 'solid',
2147
+ overviewRulerColor: 'rgba(244, 67, 54, 0.8)',
2148
+ overviewRulerLane: vscode.OverviewRulerLane.Right,
2149
+ gutterIconPath: this.getGutterIcon('error'),
2150
+ gutterIconSize: 'contain',
2151
+ })
2152
+ );
2153
+
2154
+ // Info decoration
2155
+ this.decorationTypes.set(
2156
+ 'info',
2157
+ vscode.window.createTextEditorDecorationType({
2158
+ backgroundColor: 'rgba(33, 150, 243, 0.1)',
2159
+ borderColor: 'rgba(33, 150, 243, 0.5)',
2160
+ borderWidth: '0 0 0 3px',
2161
+ borderStyle: 'solid',
2162
+ overviewRulerColor: 'rgba(33, 150, 243, 0.8)',
2163
+ overviewRulerLane: vscode.OverviewRulerLane.Right,
2164
+ gutterIconPath: this.getGutterIcon('info'),
2165
+ gutterIconSize: 'contain',
2166
+ })
2167
+ );
2168
+
2169
+ // Success decoration
2170
+ this.decorationTypes.set(
2171
+ 'success',
2172
+ vscode.window.createTextEditorDecorationType({
2173
+ backgroundColor: 'rgba(76, 175, 80, 0.1)',
2174
+ borderColor: 'rgba(76, 175, 80, 0.5)',
2175
+ borderWidth: '0 0 0 3px',
2176
+ borderStyle: 'solid',
2177
+ overviewRulerColor: 'rgba(76, 175, 80, 0.8)',
2178
+ overviewRulerLane: vscode.OverviewRulerLane.Right,
2179
+ gutterIconPath: this.getGutterIcon('success'),
2180
+ gutterIconSize: 'contain',
2181
+ })
2182
+ );
2183
+
2184
+ // Agent running decoration
2185
+ this.decorationTypes.set(
2186
+ 'running',
2187
+ vscode.window.createTextEditorDecorationType({
2188
+ after: {
2189
+ contentText: ' $(sync~spin) Agent running...',
2190
+ color: 'rgba(33, 150, 243, 0.8)',
2191
+ fontStyle: 'italic',
2192
+ },
2193
+ })
2194
+ );
2195
+ }
2196
+
2197
+ private getGutterIcon(type: string): vscode.Uri {
2198
+ // Return URI to gutter icon
2199
+ return vscode.Uri.file(`${__dirname}/../../media/icons/gutter-${type}.svg`);
2200
+ }
2201
+
2202
+ private registerListeners(): void {
2203
+ // Update decorations when annotations change
2204
+ this.sessionManager.on('annotationsChanged', (uri: string) => {
2205
+ this.updateDecorations(uri);
2206
+ });
2207
+
2208
+ // Update decorations when editor changes
2209
+ this.disposables.push(
2210
+ vscode.window.onDidChangeActiveTextEditor((editor) => {
2211
+ if (editor) {
2212
+ this.updateDecorations(editor.document.uri.toString());
2213
+ }
2214
+ })
2215
+ );
2216
+ }
2217
+
2218
+ public updateDecorations(uri: string): void {
2219
+ const editor = vscode.window.visibleTextEditors.find(
2220
+ (e) => e.document.uri.toString() === uri
2221
+ );
2222
+ if (!editor) return;
2223
+
2224
+ const annotations = this.sessionManager.getAnnotations(uri);
2225
+
2226
+ // Group annotations by type
2227
+ const grouped = new Map<string, vscode.DecorationOptions[]>();
2228
+
2229
+ annotations.forEach((annotation) => {
2230
+ const type = annotation.type;
2231
+ if (!grouped.has(type)) {
2232
+ grouped.set(type, []);
2233
+ }
2234
+
2235
+ const range = new vscode.Range(
2236
+ annotation.line,
2237
+ 0,
2238
+ annotation.line,
2239
+ editor.document.lineAt(annotation.line).text.length
2240
+ );
2241
+
2242
+ grouped.get(type)!.push({
2243
+ range,
2244
+ hoverMessage: new vscode.MarkdownString(
2245
+ `**ELSABRO ${annotation.type.toUpperCase()}**\n\n${annotation.message}`
2246
+ ),
2247
+ });
2248
+ });
2249
+
2250
+ // Apply decorations
2251
+ this.decorationTypes.forEach((decorationType, typeName) => {
2252
+ const decorations = grouped.get(typeName) || [];
2253
+ editor.setDecorations(decorationType, decorations);
2254
+ });
2255
+ }
2256
+
2257
+ public dispose(): void {
2258
+ this.decorationTypes.forEach((type) => type.dispose());
2259
+ this.disposables.forEach((d) => d.dispose());
2260
+ }
2261
+ }
2262
+ ```
2263
+
2264
+ ### 5.3 Hover Provider (VSCode)
2265
+
2266
+ ```typescript
2267
+ // src/providers/hoverProvider.ts
2268
+ import * as vscode from 'vscode';
2269
+ import { SessionManager } from '../services/sessionManager';
2270
+
2271
+ export class ElsabroHoverProvider implements vscode.HoverProvider {
2272
+ constructor(private sessionManager: SessionManager) {}
2273
+
2274
+ provideHover(
2275
+ document: vscode.TextDocument,
2276
+ position: vscode.Position,
2277
+ token: vscode.CancellationToken
2278
+ ): vscode.Hover | null {
2279
+ const annotations = this.sessionManager.getAnnotations(document.uri.toString());
2280
+
2281
+ // Find annotation at this line
2282
+ const annotation = annotations.find((a) => a.line === position.line);
2283
+
2284
+ if (annotation) {
2285
+ const markdown = new vscode.MarkdownString();
2286
+ markdown.isTrusted = true;
2287
+ markdown.supportHtml = true;
2288
+
2289
+ // Header with icon
2290
+ markdown.appendMarkdown(`### $(${annotation.icon}) ELSABRO ${annotation.type.toUpperCase()}\n\n`);
2291
+
2292
+ // Message
2293
+ markdown.appendMarkdown(`${annotation.message}\n\n`);
2294
+
2295
+ // Details if available
2296
+ if (annotation.details) {
2297
+ markdown.appendMarkdown(`---\n\n`);
2298
+ markdown.appendCodeblock(annotation.details, annotation.language || 'text');
2299
+ }
2300
+
2301
+ // Agent info
2302
+ if (annotation.agentId) {
2303
+ markdown.appendMarkdown(`\n\n---\n\n`);
2304
+ markdown.appendMarkdown(`*Agent: ${annotation.agentId}*\n`);
2305
+ markdown.appendMarkdown(`*Task: ${annotation.taskId}*`);
2306
+ }
2307
+
2308
+ // Actions
2309
+ if (annotation.actions && annotation.actions.length > 0) {
2310
+ markdown.appendMarkdown(`\n\n---\n\n`);
2311
+ annotation.actions.forEach((action: any) => {
2312
+ markdown.appendMarkdown(
2313
+ `[${action.title}](command:${action.command}?${encodeURIComponent(
2314
+ JSON.stringify(action.args)
2315
+ )}) `
2316
+ );
2317
+ });
2318
+ }
2319
+
2320
+ return new vscode.Hover(markdown);
2321
+ }
2322
+
2323
+ // Check for ELSABRO comments
2324
+ const line = document.lineAt(position.line).text;
2325
+ const elsabroCommentMatch = line.match(/@elsabro:(\w+)\s*(.*)/);
2326
+
2327
+ if (elsabroCommentMatch) {
2328
+ const [, command, args] = elsabroCommentMatch;
2329
+ const markdown = new vscode.MarkdownString();
2330
+ markdown.isTrusted = true;
2331
+
2332
+ markdown.appendMarkdown(`### ELSABRO Command\n\n`);
2333
+ markdown.appendMarkdown(`**Command:** \`${command}\`\n\n`);
2334
+ if (args) {
2335
+ markdown.appendMarkdown(`**Arguments:** ${args}\n\n`);
2336
+ }
2337
+ markdown.appendMarkdown(
2338
+ `[Execute](command:elsabro.executeComment?${encodeURIComponent(
2339
+ JSON.stringify({ line: position.line })
2340
+ )})`
2341
+ );
2342
+
2343
+ return new vscode.Hover(markdown);
2344
+ }
2345
+
2346
+ return null;
2347
+ }
2348
+ }
2349
+ ```
2350
+
2351
+ ---
2352
+
2353
+ ## 6. QuickActions
2354
+
2355
+ ### 6.1 Code Action Provider (VSCode)
2356
+
2357
+ ```typescript
2358
+ // src/providers/codeActionProvider.ts
2359
+ import * as vscode from 'vscode';
2360
+ import { SessionManager } from '../services/sessionManager';
2361
+
2362
+ export class ElsabroCodeActionProvider implements vscode.CodeActionProvider {
2363
+ public static readonly providedCodeActionKinds = [
2364
+ vscode.CodeActionKind.QuickFix,
2365
+ vscode.CodeActionKind.Refactor,
2366
+ vscode.CodeActionKind.Source,
2367
+ ];
2368
+
2369
+ constructor(private sessionManager: SessionManager) {}
2370
+
2371
+ provideCodeActions(
2372
+ document: vscode.TextDocument,
2373
+ range: vscode.Range | vscode.Selection,
2374
+ context: vscode.CodeActionContext,
2375
+ token: vscode.CancellationToken
2376
+ ): vscode.CodeAction[] {
2377
+ const actions: vscode.CodeAction[] = [];
2378
+
2379
+ // Quick fixes from diagnostics
2380
+ context.diagnostics
2381
+ .filter((d) => d.source === 'elsabro')
2382
+ .forEach((diagnostic) => {
2383
+ const fix = this.createQuickFix(document, diagnostic);
2384
+ if (fix) {
2385
+ actions.push(fix);
2386
+ }
2387
+ });
2388
+
2389
+ // Refactoring actions (when there's a selection)
2390
+ if (!range.isEmpty) {
2391
+ actions.push(
2392
+ this.createRefactorAction(
2393
+ 'Extract Function',
2394
+ 'elsabro.refactor.extractFunction',
2395
+ document,
2396
+ range
2397
+ ),
2398
+ this.createRefactorAction(
2399
+ 'Extract Variable',
2400
+ 'elsabro.refactor.extractVariable',
2401
+ document,
2402
+ range
2403
+ ),
2404
+ this.createRefactorAction(
2405
+ 'AI Improve',
2406
+ 'elsabro.refactor.improve',
2407
+ document,
2408
+ range
2409
+ )
2410
+ );
2411
+ }
2412
+
2413
+ // Source actions (always available)
2414
+ actions.push(
2415
+ this.createSourceAction(
2416
+ 'Generate Tests for File',
2417
+ 'elsabro.generateTests',
2418
+ document
2419
+ ),
2420
+ this.createSourceAction(
2421
+ 'Generate Documentation',
2422
+ 'elsabro.generateDocs',
2423
+ document
2424
+ ),
2425
+ this.createSourceAction(
2426
+ 'Optimize Imports',
2427
+ 'elsabro.optimizeImports',
2428
+ document
2429
+ )
2430
+ );
2431
+
2432
+ // Context-aware actions
2433
+ const contextActions = this.getContextAwareActions(document, range);
2434
+ actions.push(...contextActions);
2435
+
2436
+ return actions;
2437
+ }
2438
+
2439
+ private createQuickFix(
2440
+ document: vscode.TextDocument,
2441
+ diagnostic: vscode.Diagnostic
2442
+ ): vscode.CodeAction | null {
2443
+ const data = diagnostic.data as any;
2444
+ if (!data?.fix) return null;
2445
+
2446
+ const action = new vscode.CodeAction(
2447
+ data.fix.title,
2448
+ vscode.CodeActionKind.QuickFix
2449
+ );
2450
+
2451
+ action.edit = new vscode.WorkspaceEdit();
2452
+ action.edit.replace(document.uri, diagnostic.range, data.fix.replacement);
2453
+ action.isPreferred = data.fix.isPreferred;
2454
+ action.diagnostics = [diagnostic];
2455
+
2456
+ return action;
2457
+ }
2458
+
2459
+ private createRefactorAction(
2460
+ title: string,
2461
+ command: string,
2462
+ document: vscode.TextDocument,
2463
+ range: vscode.Range
2464
+ ): vscode.CodeAction {
2465
+ const action = new vscode.CodeAction(
2466
+ `ELSABRO: ${title}`,
2467
+ vscode.CodeActionKind.Refactor
2468
+ );
2469
+
2470
+ action.command = {
2471
+ title,
2472
+ command,
2473
+ arguments: [document.uri, range],
2474
+ };
2475
+
2476
+ return action;
2477
+ }
2478
+
2479
+ private createSourceAction(
2480
+ title: string,
2481
+ command: string,
2482
+ document: vscode.TextDocument
2483
+ ): vscode.CodeAction {
2484
+ const action = new vscode.CodeAction(
2485
+ `ELSABRO: ${title}`,
2486
+ vscode.CodeActionKind.Source
2487
+ );
2488
+
2489
+ action.command = {
2490
+ title,
2491
+ command,
2492
+ arguments: [document.uri],
2493
+ };
2494
+
2495
+ return action;
2496
+ }
2497
+
2498
+ private getContextAwareActions(
2499
+ document: vscode.TextDocument,
2500
+ range: vscode.Range
2501
+ ): vscode.CodeAction[] {
2502
+ const actions: vscode.CodeAction[] = [];
2503
+ const line = document.lineAt(range.start.line).text;
2504
+
2505
+ // Function-specific actions
2506
+ if (line.match(/function|const.*=.*=>|def\s+/)) {
2507
+ actions.push(
2508
+ this.createRefactorAction(
2509
+ 'Add Error Handling',
2510
+ 'elsabro.refactor.addErrorHandling',
2511
+ document,
2512
+ range
2513
+ ),
2514
+ this.createRefactorAction(
2515
+ 'Add Logging',
2516
+ 'elsabro.refactor.addLogging',
2517
+ document,
2518
+ range
2519
+ ),
2520
+ this.createRefactorAction(
2521
+ 'Add Input Validation',
2522
+ 'elsabro.refactor.addValidation',
2523
+ document,
2524
+ range
2525
+ )
2526
+ );
2527
+ }
2528
+
2529
+ // Class-specific actions
2530
+ if (line.match(/class\s+/)) {
2531
+ actions.push(
2532
+ this.createRefactorAction(
2533
+ 'Generate Getters/Setters',
2534
+ 'elsabro.generate.gettersSetters',
2535
+ document,
2536
+ range
2537
+ ),
2538
+ this.createRefactorAction(
2539
+ 'Implement Interface',
2540
+ 'elsabro.refactor.implementInterface',
2541
+ document,
2542
+ range
2543
+ )
2544
+ );
2545
+ }
2546
+
2547
+ // Import-specific actions
2548
+ if (line.match(/import|require|from/)) {
2549
+ actions.push(
2550
+ this.createRefactorAction(
2551
+ 'Organize Imports',
2552
+ 'elsabro.refactor.organizeImports',
2553
+ document,
2554
+ range
2555
+ )
2556
+ );
2557
+ }
2558
+
2559
+ return actions;
2560
+ }
2561
+ }
2562
+ ```
2563
+
2564
+ ### 6.2 Intentions (JetBrains)
2565
+
2566
+ ```kotlin
2567
+ // src/main/kotlin/com/elsabro/plugin/intentions/GenerateTestsIntention.kt
2568
+ package com.elsabro.plugin.intentions
2569
+
2570
+ import com.elsabro.plugin.services.ElsabroService
2571
+ import com.intellij.codeInsight.intention.IntentionAction
2572
+ import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
2573
+ import com.intellij.openapi.editor.Editor
2574
+ import com.intellij.openapi.project.Project
2575
+ import com.intellij.psi.PsiElement
2576
+ import com.intellij.psi.util.PsiTreeUtil
2577
+ import kotlinx.coroutines.CoroutineScope
2578
+ import kotlinx.coroutines.Dispatchers
2579
+ import kotlinx.coroutines.launch
2580
+
2581
+ class GenerateTestsIntention : PsiElementBaseIntentionAction(), IntentionAction {
2582
+
2583
+ override fun getText(): String = "ELSABRO: Generate Tests"
2584
+
2585
+ override fun getFamilyName(): String = "ELSABRO"
2586
+
2587
+ override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
2588
+ // Check if element is inside a function or class
2589
+ return findTargetElement(element) != null
2590
+ }
2591
+
2592
+ override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
2593
+ val target = findTargetElement(element) ?: return
2594
+ val file = element.containingFile?.virtualFile?.path ?: return
2595
+ val startLine = editor?.document?.getLineNumber(target.textRange.startOffset) ?: 0
2596
+
2597
+ CoroutineScope(Dispatchers.Main).launch {
2598
+ try {
2599
+ ElsabroService.getInstance().runAgent("test-generator", mapOf(
2600
+ "filePath" to file,
2601
+ "startLine" to startLine,
2602
+ "elementText" to target.text,
2603
+ "projectPath" to project.basePath
2604
+ ))
2605
+ } catch (e: Exception) {
2606
+ // Show notification
2607
+ }
2608
+ }
2609
+ }
2610
+
2611
+ private fun findTargetElement(element: PsiElement): PsiElement? {
2612
+ // Language-agnostic check for function/method/class
2613
+ var current: PsiElement? = element
2614
+ while (current != null) {
2615
+ val text = current.node?.elementType?.toString() ?: ""
2616
+ if (text.contains("FUNCTION") ||
2617
+ text.contains("METHOD") ||
2618
+ text.contains("CLASS")) {
2619
+ return current
2620
+ }
2621
+ current = current.parent
2622
+ }
2623
+ return null
2624
+ }
2625
+ }
2626
+ ```
2627
+
2628
+ ---
2629
+
2630
+ ## 7. StatusBarIntegration
2631
+
2632
+ ### 7.1 Status Bar Manager (VSCode)
2633
+
2634
+ ```typescript
2635
+ // src/views/statusBar.ts
2636
+ import * as vscode from 'vscode';
2637
+ import { SessionManager } from '../services/sessionManager';
2638
+
2639
+ export class StatusBarManager {
2640
+ private statusBarItem: vscode.StatusBarItem;
2641
+ private progressBarItem: vscode.StatusBarItem;
2642
+
2643
+ constructor(
2644
+ private context: vscode.ExtensionContext,
2645
+ private sessionManager: SessionManager
2646
+ ) {
2647
+ // Main status bar item
2648
+ this.statusBarItem = vscode.window.createStatusBarItem(
2649
+ vscode.StatusBarAlignment.Left,
2650
+ 100
2651
+ );
2652
+ this.statusBarItem.command = 'elsabro.showPanel';
2653
+ context.subscriptions.push(this.statusBarItem);
2654
+
2655
+ // Progress bar item
2656
+ this.progressBarItem = vscode.window.createStatusBarItem(
2657
+ vscode.StatusBarAlignment.Left,
2658
+ 99
2659
+ );
2660
+ context.subscriptions.push(this.progressBarItem);
2661
+ }
2662
+
2663
+ initialize(): void {
2664
+ this.updateStatusBar();
2665
+
2666
+ // Listen for connection changes
2667
+ this.sessionManager.on('connectionChanged', (connected: boolean) => {
2668
+ this.updateStatusBar();
2669
+ });
2670
+
2671
+ // Listen for task updates
2672
+ this.sessionManager.on('taskProgress', (task: any) => {
2673
+ this.updateProgress(task);
2674
+ });
2675
+
2676
+ this.sessionManager.on('taskComplete', () => {
2677
+ this.hideProgress();
2678
+ });
2679
+
2680
+ this.statusBarItem.show();
2681
+ }
2682
+
2683
+ private updateStatusBar(): void {
2684
+ const isConnected = this.sessionManager.isConnected;
2685
+
2686
+ if (isConnected) {
2687
+ this.statusBarItem.text = '$(check) ELSABRO';
2688
+ this.statusBarItem.tooltip = 'ELSABRO: Connected - Click to open panel';
2689
+ this.statusBarItem.backgroundColor = undefined;
2690
+ } else {
2691
+ this.statusBarItem.text = '$(x) ELSABRO';
2692
+ this.statusBarItem.tooltip = 'ELSABRO: Disconnected - Click to reconnect';
2693
+ this.statusBarItem.backgroundColor = new vscode.ThemeColor(
2694
+ 'statusBarItem.errorBackground'
2695
+ );
2696
+ }
2697
+ }
2698
+
2699
+ private updateProgress(task: any): void {
2700
+ this.progressBarItem.text = `$(sync~spin) ${task.name}: ${task.progress}%`;
2701
+ this.progressBarItem.tooltip = `Task: ${task.name}\nAgent: ${task.agentId}\nProgress: ${task.progress}%`;
2702
+ this.progressBarItem.show();
2703
+ }
2704
+
2705
+ private hideProgress(): void {
2706
+ this.progressBarItem.hide();
2707
+ }
2708
+
2709
+ public setStatus(status: 'connected' | 'disconnected' | 'running' | 'error', message?: string): void {
2710
+ const icons: Record<string, string> = {
2711
+ connected: '$(check)',
2712
+ disconnected: '$(x)',
2713
+ running: '$(sync~spin)',
2714
+ error: '$(error)',
2715
+ };
2716
+
2717
+ const backgrounds: Record<string, vscode.ThemeColor | undefined> = {
2718
+ connected: undefined,
2719
+ disconnected: new vscode.ThemeColor('statusBarItem.warningBackground'),
2720
+ running: undefined,
2721
+ error: new vscode.ThemeColor('statusBarItem.errorBackground'),
2722
+ };
2723
+
2724
+ this.statusBarItem.text = `${icons[status]} ELSABRO${message ? `: ${message}` : ''}`;
2725
+ this.statusBarItem.backgroundColor = backgrounds[status];
2726
+ }
2727
+ }
2728
+ ```
2729
+
2730
+ ### 7.2 Status Bar Widget (JetBrains)
2731
+
2732
+ ```kotlin
2733
+ // src/main/kotlin/com/elsabro/plugin/statusbar/ElsabroStatusBarWidget.kt
2734
+ package com.elsabro.plugin.statusbar
2735
+
2736
+ import com.elsabro.plugin.services.ConnectionState
2737
+ import com.elsabro.plugin.services.ElsabroService
2738
+ import com.intellij.openapi.project.Project
2739
+ import com.intellij.openapi.wm.StatusBar
2740
+ import com.intellij.openapi.wm.StatusBarWidget
2741
+ import com.intellij.openapi.wm.StatusBarWidgetFactory
2742
+ import com.intellij.util.Consumer
2743
+ import kotlinx.coroutines.*
2744
+ import kotlinx.coroutines.flow.collectLatest
2745
+ import java.awt.Component
2746
+ import java.awt.event.MouseEvent
2747
+ import javax.swing.Icon
2748
+
2749
+ class ElsabroStatusBarWidgetFactory : StatusBarWidgetFactory {
2750
+ override fun getId(): String = "ElsabroStatusBar"
2751
+ override fun getDisplayName(): String = "ELSABRO"
2752
+ override fun isAvailable(project: Project): Boolean = true
2753
+
2754
+ override fun createWidget(project: Project): StatusBarWidget {
2755
+ return ElsabroStatusBarWidget(project)
2756
+ }
2757
+
2758
+ override fun disposeWidget(widget: StatusBarWidget) {
2759
+ (widget as? ElsabroStatusBarWidget)?.dispose()
2760
+ }
2761
+
2762
+ override fun canBeEnabledOn(statusBar: StatusBar): Boolean = true
2763
+ }
2764
+
2765
+ class ElsabroStatusBarWidget(
2766
+ private val project: Project
2767
+ ) : StatusBarWidget, StatusBarWidget.TextPresentation {
2768
+
2769
+ private var statusBar: StatusBar? = null
2770
+ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
2771
+ private var currentText = "ELSABRO: Disconnected"
2772
+ private var currentTooltip = "Click to connect"
2773
+
2774
+ init {
2775
+ subscribeToConnectionState()
2776
+ }
2777
+
2778
+ private fun subscribeToConnectionState() {
2779
+ scope.launch {
2780
+ ElsabroService.getInstance().connectionState.collectLatest { state ->
2781
+ updateState(state)
2782
+ }
2783
+ }
2784
+
2785
+ scope.launch {
2786
+ ElsabroService.getInstance().tasks.collectLatest { tasks ->
2787
+ val running = tasks.filter { it.status == "running" }
2788
+ if (running.isNotEmpty()) {
2789
+ val task = running.first()
2790
+ currentText = "ELSABRO: ${task.name} (${task.progress}%)"
2791
+ currentTooltip = "Running: ${task.name}"
2792
+ statusBar?.updateWidget(ID())
2793
+ }
2794
+ }
2795
+ }
2796
+ }
2797
+
2798
+ private fun updateState(state: ConnectionState) {
2799
+ when (state) {
2800
+ is ConnectionState.Connected -> {
2801
+ currentText = "ELSABRO: Connected"
2802
+ currentTooltip = "Click to open ELSABRO panel"
2803
+ }
2804
+ is ConnectionState.Connecting -> {
2805
+ currentText = "ELSABRO: Connecting..."
2806
+ currentTooltip = "Connecting to server"
2807
+ }
2808
+ is ConnectionState.Disconnected -> {
2809
+ currentText = "ELSABRO: Disconnected"
2810
+ currentTooltip = "Click to connect"
2811
+ }
2812
+ is ConnectionState.Error -> {
2813
+ currentText = "ELSABRO: Error"
2814
+ currentTooltip = "Error: ${state.message}"
2815
+ }
2816
+ }
2817
+ statusBar?.updateWidget(ID())
2818
+ }
2819
+
2820
+ override fun ID(): String = "ElsabroStatusBar"
2821
+
2822
+ override fun getPresentation(): StatusBarWidget.WidgetPresentation = this
2823
+
2824
+ override fun install(statusBar: StatusBar) {
2825
+ this.statusBar = statusBar
2826
+ }
2827
+
2828
+ override fun dispose() {
2829
+ scope.cancel()
2830
+ }
2831
+
2832
+ // TextPresentation implementation
2833
+ override fun getText(): String = currentText
2834
+
2835
+ override fun getAlignment(): Float = Component.CENTER_ALIGNMENT
2836
+
2837
+ override fun getTooltipText(): String = currentTooltip
2838
+
2839
+ override fun getClickConsumer(): Consumer<MouseEvent> = Consumer {
2840
+ // Open ELSABRO tool window or connect
2841
+ val service = ElsabroService.getInstance()
2842
+ if (service.connectionState.value is ConnectionState.Disconnected) {
2843
+ service.connect()
2844
+ } else {
2845
+ // Open tool window
2846
+ com.intellij.openapi.wm.ToolWindowManager
2847
+ .getInstance(project)
2848
+ .getToolWindow("ELSABRO")
2849
+ ?.show()
2850
+ }
2851
+ }
2852
+ }
2853
+ ```
2854
+
2855
+ ---
2856
+
2857
+ ## 8. KeybindingManager
2858
+
2859
+ ### 8.1 Default Keybindings
2860
+
2861
+ ```
2862
+ +------------------------------------------------------------------+
2863
+ | ELSABRO Default Keybindings |
2864
+ +------------------------------------------------------------------+
2865
+ | |
2866
+ | ACTION | VSCODE | JETBRAINS |
2867
+ | --------------------------|------------------|-------------------|
2868
+ | Run Agent | Ctrl+Shift+E R | Ctrl+Shift+E R |
2869
+ | Pause Agent | Ctrl+Shift+E P | Ctrl+Shift+E P |
2870
+ | Cancel Task | Ctrl+Shift+E C | Ctrl+Shift+E C |
2871
+ | Show Panel | Ctrl+Shift+E E | Ctrl+Shift+E E |
2872
+ | Generate Tests | Ctrl+Shift+E T | Ctrl+Shift+E T |
2873
+ | Generate Docs | Ctrl+Shift+E D | Ctrl+Shift+E D |
2874
+ | AI Refactor | Ctrl+Shift+E F | Ctrl+Shift+E F |
2875
+ | Quick Actions | Ctrl+Shift+E A | Ctrl+Shift+E A |
2876
+ | Toggle Annotations | Ctrl+Shift+E N | Ctrl+Shift+E N |
2877
+ | Focus Agents View | Ctrl+Shift+E 1 | Ctrl+Shift+E 1 |
2878
+ | Focus Tasks View | Ctrl+Shift+E 2 | Ctrl+Shift+E 2 |
2879
+ | Focus Logs View | Ctrl+Shift+E 3 | Ctrl+Shift+E 3 |
2880
+ | |
2881
+ | Note: On macOS, use Cmd instead of Ctrl |
2882
+ +------------------------------------------------------------------+
2883
+ ```
2884
+
2885
+ ### 8.2 Keybinding Manager (VSCode)
2886
+
2887
+ ```typescript
2888
+ // src/services/keybindingManager.ts
2889
+ import * as vscode from 'vscode';
2890
+
2891
+ interface KeybindingConfig {
2892
+ command: string;
2893
+ key: string;
2894
+ mac?: string;
2895
+ when?: string;
2896
+ }
2897
+
2898
+ export class KeybindingManager {
2899
+ private static readonly DEFAULT_KEYBINDINGS: KeybindingConfig[] = [
2900
+ {
2901
+ command: 'elsabro.runAgent',
2902
+ key: 'ctrl+shift+e r',
2903
+ mac: 'cmd+shift+e r',
2904
+ when: 'editorTextFocus',
2905
+ },
2906
+ {
2907
+ command: 'elsabro.pauseAgent',
2908
+ key: 'ctrl+shift+e p',
2909
+ mac: 'cmd+shift+e p',
2910
+ },
2911
+ {
2912
+ command: 'elsabro.cancelTask',
2913
+ key: 'ctrl+shift+e c',
2914
+ mac: 'cmd+shift+e c',
2915
+ },
2916
+ {
2917
+ command: 'elsabro.showPanel',
2918
+ key: 'ctrl+shift+e e',
2919
+ mac: 'cmd+shift+e e',
2920
+ },
2921
+ {
2922
+ command: 'elsabro.generateTests',
2923
+ key: 'ctrl+shift+e t',
2924
+ mac: 'cmd+shift+e t',
2925
+ when: 'editorTextFocus',
2926
+ },
2927
+ {
2928
+ command: 'elsabro.generateDocs',
2929
+ key: 'ctrl+shift+e d',
2930
+ mac: 'cmd+shift+e d',
2931
+ when: 'editorTextFocus',
2932
+ },
2933
+ {
2934
+ command: 'elsabro.refactor',
2935
+ key: 'ctrl+shift+e f',
2936
+ mac: 'cmd+shift+e f',
2937
+ when: 'editorTextFocus && editorHasSelection',
2938
+ },
2939
+ {
2940
+ command: 'elsabro.quickActions',
2941
+ key: 'ctrl+shift+e a',
2942
+ mac: 'cmd+shift+e a',
2943
+ when: 'editorTextFocus',
2944
+ },
2945
+ {
2946
+ command: 'elsabro.toggleAnnotations',
2947
+ key: 'ctrl+shift+e n',
2948
+ mac: 'cmd+shift+e n',
2949
+ },
2950
+ {
2951
+ command: 'elsabro.focusAgentsView',
2952
+ key: 'ctrl+shift+e 1',
2953
+ mac: 'cmd+shift+e 1',
2954
+ },
2955
+ {
2956
+ command: 'elsabro.focusTasksView',
2957
+ key: 'ctrl+shift+e 2',
2958
+ mac: 'cmd+shift+e 2',
2959
+ },
2960
+ {
2961
+ command: 'elsabro.focusLogsView',
2962
+ key: 'ctrl+shift+e 3',
2963
+ mac: 'cmd+shift+e 3',
2964
+ },
2965
+ ];
2966
+
2967
+ private customKeybindings: Map<string, KeybindingConfig> = new Map();
2968
+
2969
+ constructor(private context: vscode.ExtensionContext) {
2970
+ this.loadCustomKeybindings();
2971
+ }
2972
+
2973
+ private loadCustomKeybindings(): void {
2974
+ const config = vscode.workspace.getConfiguration('elsabro');
2975
+ const custom = config.get<Record<string, string>>('keybindings') || {};
2976
+
2977
+ Object.entries(custom).forEach(([command, key]) => {
2978
+ this.customKeybindings.set(command, {
2979
+ command: `elsabro.${command}`,
2980
+ key,
2981
+ });
2982
+ });
2983
+ }
2984
+
2985
+ public getKeybinding(command: string): string | undefined {
2986
+ // Check custom first
2987
+ const custom = this.customKeybindings.get(command);
2988
+ if (custom) {
2989
+ return custom.key;
2990
+ }
2991
+
2992
+ // Fall back to default
2993
+ const defaultBinding = KeybindingManager.DEFAULT_KEYBINDINGS.find(
2994
+ (kb) => kb.command === `elsabro.${command}`
2995
+ );
2996
+
2997
+ if (defaultBinding) {
2998
+ return process.platform === 'darwin'
2999
+ ? defaultBinding.mac || defaultBinding.key
3000
+ : defaultBinding.key;
3001
+ }
3002
+
3003
+ return undefined;
3004
+ }
3005
+
3006
+ public async setCustomKeybinding(command: string, key: string): Promise<void> {
3007
+ const config = vscode.workspace.getConfiguration('elsabro');
3008
+ const keybindings = config.get<Record<string, string>>('keybindings') || {};
3009
+ keybindings[command] = key;
3010
+
3011
+ await config.update('keybindings', keybindings, vscode.ConfigurationTarget.Global);
3012
+ this.customKeybindings.set(command, { command: `elsabro.${command}`, key });
3013
+ }
3014
+
3015
+ public async resetKeybindings(): Promise<void> {
3016
+ const config = vscode.workspace.getConfiguration('elsabro');
3017
+ await config.update('keybindings', undefined, vscode.ConfigurationTarget.Global);
3018
+ this.customKeybindings.clear();
3019
+ }
3020
+
3021
+ public getDefaultKeybindings(): KeybindingConfig[] {
3022
+ return KeybindingManager.DEFAULT_KEYBINDINGS;
3023
+ }
3024
+
3025
+ public showKeybindingsQuickPick(): void {
3026
+ const items = KeybindingManager.DEFAULT_KEYBINDINGS.map((kb) => ({
3027
+ label: kb.command.replace('elsabro.', ''),
3028
+ description: this.getKeybinding(kb.command.replace('elsabro.', '')),
3029
+ detail: `When: ${kb.when || 'always'}`,
3030
+ command: kb.command,
3031
+ }));
3032
+
3033
+ vscode.window.showQuickPick(items, {
3034
+ placeHolder: 'ELSABRO Keybindings',
3035
+ }).then((selected) => {
3036
+ if (selected) {
3037
+ this.promptForNewKeybinding(selected.command);
3038
+ }
3039
+ });
3040
+ }
3041
+
3042
+ private async promptForNewKeybinding(command: string): Promise<void> {
3043
+ const input = await vscode.window.showInputBox({
3044
+ prompt: `Enter new keybinding for ${command}`,
3045
+ placeHolder: 'e.g., ctrl+shift+k',
3046
+ });
3047
+
3048
+ if (input) {
3049
+ await this.setCustomKeybinding(command.replace('elsabro.', ''), input);
3050
+ vscode.window.showInformationMessage(
3051
+ `Keybinding updated. Reload to apply changes.`
3052
+ );
3053
+ }
3054
+ }
3055
+ }
3056
+ ```
3057
+
3058
+ ---
3059
+
3060
+ ## 9. CLI Commands
3061
+
3062
+ ### 9.1 /elsabro:ide Commands
3063
+
3064
+ ```
3065
+ +------------------------------------------------------------------+
3066
+ | /elsabro:ide CLI Commands |
3067
+ +------------------------------------------------------------------+
3068
+
3069
+ USAGE: /elsabro:ide <command> [options]
3070
+
3071
+ COMMANDS:
3072
+
3073
+ install [ide] Install ELSABRO extension/plugin
3074
+ Options:
3075
+ --ide=vscode|jetbrains|all (default: all)
3076
+ --version=<version> (default: latest)
3077
+ --channel=stable|beta|nightly (default: stable)
3078
+
3079
+ configure Configure IDE integration
3080
+ Options:
3081
+ --server=<url> (WebSocket server URL)
3082
+ --auto-connect (enable auto-connect)
3083
+ --annotations (enable inline annotations)
3084
+ --codelens (enable CodeLens)
3085
+
3086
+ shortcuts Manage keyboard shortcuts
3087
+ Options:
3088
+ --list (show all shortcuts)
3089
+ --reset (reset to defaults)
3090
+ --export=<file> (export to file)
3091
+ --import=<file> (import from file)
3092
+
3093
+ sync Sync settings across IDEs
3094
+ Options:
3095
+ --push (push local settings)
3096
+ --pull (pull remote settings)
3097
+ --diff (show differences)
3098
+
3099
+ status Show integration status
3100
+ Options:
3101
+ --verbose (detailed output)
3102
+
3103
+ uninstall [ide] Remove ELSABRO extension/plugin
3104
+
3105
+ EXAMPLES:
3106
+
3107
+ # Install for all IDEs
3108
+ /elsabro:ide install
3109
+
3110
+ # Install only VSCode extension
3111
+ /elsabro:ide install --ide=vscode
3112
+
3113
+ # Configure server URL
3114
+ /elsabro:ide configure --server=ws://localhost:3847
3115
+
3116
+ # List all shortcuts
3117
+ /elsabro:ide shortcuts --list
3118
+
3119
+ # Sync settings
3120
+ /elsabro:ide sync --push
3121
+ ```
3122
+
3123
+ ### 9.2 CLI Implementation
3124
+
3125
+ ```typescript
3126
+ // cli/commands/ide.ts
3127
+ import { Command } from 'commander';
3128
+ import { spawn } from 'child_process';
3129
+ import * as fs from 'fs';
3130
+ import * as path from 'path';
3131
+ import * as os from 'os';
3132
+
3133
+ interface IDEConfig {
3134
+ vscode: {
3135
+ extensionPath: string;
3136
+ settingsPath: string;
3137
+ };
3138
+ jetbrains: {
3139
+ pluginPath: string;
3140
+ settingsPath: string;
3141
+ };
3142
+ }
3143
+
3144
+ const IDE_CONFIG: IDEConfig = {
3145
+ vscode: {
3146
+ extensionPath: path.join(os.homedir(), '.vscode', 'extensions'),
3147
+ settingsPath: path.join(os.homedir(), '.vscode', 'settings.json'),
3148
+ },
3149
+ jetbrains: {
3150
+ pluginPath: path.join(os.homedir(), '.config', 'JetBrains'),
3151
+ settingsPath: path.join(os.homedir(), '.config', 'JetBrains', 'elsabro.json'),
3152
+ },
3153
+ };
3154
+
3155
+ export function createIDECommand(): Command {
3156
+ const ide = new Command('ide')
3157
+ .description('Manage IDE integrations');
3158
+
3159
+ // Install command
3160
+ ide
3161
+ .command('install')
3162
+ .description('Install ELSABRO extension/plugin')
3163
+ .option('--ide <type>', 'IDE type (vscode|jetbrains|all)', 'all')
3164
+ .option('--version <version>', 'Version to install', 'latest')
3165
+ .option('--channel <channel>', 'Release channel', 'stable')
3166
+ .action(async (options) => {
3167
+ console.log(`Installing ELSABRO for ${options.ide}...`);
3168
+
3169
+ if (options.ide === 'all' || options.ide === 'vscode') {
3170
+ await installVSCodeExtension(options.version, options.channel);
3171
+ }
3172
+
3173
+ if (options.ide === 'all' || options.ide === 'jetbrains') {
3174
+ await installJetBrainsPlugin(options.version, options.channel);
3175
+ }
3176
+
3177
+ console.log('Installation complete!');
3178
+ });
3179
+
3180
+ // Configure command
3181
+ ide
3182
+ .command('configure')
3183
+ .description('Configure IDE integration')
3184
+ .option('--server <url>', 'WebSocket server URL')
3185
+ .option('--auto-connect', 'Enable auto-connect')
3186
+ .option('--annotations', 'Enable inline annotations')
3187
+ .option('--codelens', 'Enable CodeLens')
3188
+ .action(async (options) => {
3189
+ const config: any = {};
3190
+
3191
+ if (options.server) config['elsabro.serverUrl'] = options.server;
3192
+ if (options.autoConnect) config['elsabro.autoConnect'] = true;
3193
+ if (options.annotations) config['elsabro.showInlineAnnotations'] = true;
3194
+ if (options.codelens) config['elsabro.enableCodeLens'] = true;
3195
+
3196
+ await updateVSCodeSettings(config);
3197
+ await updateJetBrainsSettings(config);
3198
+
3199
+ console.log('Configuration updated!');
3200
+ });
3201
+
3202
+ // Shortcuts command
3203
+ ide
3204
+ .command('shortcuts')
3205
+ .description('Manage keyboard shortcuts')
3206
+ .option('--list', 'Show all shortcuts')
3207
+ .option('--reset', 'Reset to defaults')
3208
+ .option('--export <file>', 'Export to file')
3209
+ .option('--import <file>', 'Import from file')
3210
+ .action(async (options) => {
3211
+ if (options.list) {
3212
+ listShortcuts();
3213
+ } else if (options.reset) {
3214
+ await resetShortcuts();
3215
+ console.log('Shortcuts reset to defaults');
3216
+ } else if (options.export) {
3217
+ await exportShortcuts(options.export);
3218
+ console.log(`Shortcuts exported to ${options.export}`);
3219
+ } else if (options.import) {
3220
+ await importShortcuts(options.import);
3221
+ console.log(`Shortcuts imported from ${options.import}`);
3222
+ }
3223
+ });
3224
+
3225
+ // Sync command
3226
+ ide
3227
+ .command('sync')
3228
+ .description('Sync settings across IDEs')
3229
+ .option('--push', 'Push local settings')
3230
+ .option('--pull', 'Pull remote settings')
3231
+ .option('--diff', 'Show differences')
3232
+ .action(async (options) => {
3233
+ if (options.push) {
3234
+ await syncPush();
3235
+ console.log('Settings pushed to cloud');
3236
+ } else if (options.pull) {
3237
+ await syncPull();
3238
+ console.log('Settings pulled from cloud');
3239
+ } else if (options.diff) {
3240
+ const diff = await syncDiff();
3241
+ console.log('Settings differences:');
3242
+ console.log(JSON.stringify(diff, null, 2));
3243
+ }
3244
+ });
3245
+
3246
+ // Status command
3247
+ ide
3248
+ .command('status')
3249
+ .description('Show integration status')
3250
+ .option('--verbose', 'Detailed output')
3251
+ .action(async (options) => {
3252
+ const status = await getIntegrationStatus();
3253
+
3254
+ console.log('\nELSABRO IDE Integration Status\n');
3255
+ console.log('VSCode Extension:', status.vscode.installed ? 'Installed' : 'Not installed');
3256
+ if (status.vscode.installed) {
3257
+ console.log(' Version:', status.vscode.version);
3258
+ console.log(' Connected:', status.vscode.connected ? 'Yes' : 'No');
3259
+ }
3260
+
3261
+ console.log('\nJetBrains Plugin:', status.jetbrains.installed ? 'Installed' : 'Not installed');
3262
+ if (status.jetbrains.installed) {
3263
+ console.log(' Version:', status.jetbrains.version);
3264
+ console.log(' Connected:', status.jetbrains.connected ? 'Yes' : 'No');
3265
+ }
3266
+
3267
+ if (options.verbose) {
3268
+ console.log('\nDetailed configuration:');
3269
+ console.log(JSON.stringify(status, null, 2));
3270
+ }
3271
+ });
3272
+
3273
+ // Uninstall command
3274
+ ide
3275
+ .command('uninstall')
3276
+ .description('Remove ELSABRO extension/plugin')
3277
+ .argument('[ide]', 'IDE type', 'all')
3278
+ .action(async (ide) => {
3279
+ if (ide === 'all' || ide === 'vscode') {
3280
+ await uninstallVSCodeExtension();
3281
+ console.log('VSCode extension uninstalled');
3282
+ }
3283
+
3284
+ if (ide === 'all' || ide === 'jetbrains') {
3285
+ await uninstallJetBrainsPlugin();
3286
+ console.log('JetBrains plugin uninstalled');
3287
+ }
3288
+ });
3289
+
3290
+ return ide;
3291
+ }
3292
+
3293
+ // Helper functions
3294
+ async function installVSCodeExtension(version: string, channel: string): Promise<void> {
3295
+ const extensionId = channel === 'stable'
3296
+ ? 'elsabro.elsabro-vscode'
3297
+ : `elsabro.elsabro-vscode-${channel}`;
3298
+
3299
+ return new Promise((resolve, reject) => {
3300
+ const proc = spawn('code', ['--install-extension', extensionId], {
3301
+ stdio: 'inherit',
3302
+ });
3303
+
3304
+ proc.on('close', (code) => {
3305
+ if (code === 0) resolve();
3306
+ else reject(new Error(`Installation failed with code ${code}`));
3307
+ });
3308
+ });
3309
+ }
3310
+
3311
+ async function installJetBrainsPlugin(version: string, channel: string): Promise<void> {
3312
+ // JetBrains plugin installation logic
3313
+ console.log('Please install ELSABRO plugin from JetBrains Marketplace');
3314
+ console.log('Search for "ELSABRO AI Orchestration" in your IDE plugins');
3315
+ }
3316
+
3317
+ async function updateVSCodeSettings(config: Record<string, any>): Promise<void> {
3318
+ const settingsPath = IDE_CONFIG.vscode.settingsPath;
3319
+ let settings: Record<string, any> = {};
3320
+
3321
+ if (fs.existsSync(settingsPath)) {
3322
+ const content = fs.readFileSync(settingsPath, 'utf-8');
3323
+ settings = JSON.parse(content);
3324
+ }
3325
+
3326
+ Object.assign(settings, config);
3327
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
3328
+ }
3329
+
3330
+ async function updateJetBrainsSettings(config: Record<string, any>): Promise<void> {
3331
+ const settingsPath = IDE_CONFIG.jetbrains.settingsPath;
3332
+ const dir = path.dirname(settingsPath);
3333
+
3334
+ if (!fs.existsSync(dir)) {
3335
+ fs.mkdirSync(dir, { recursive: true });
3336
+ }
3337
+
3338
+ fs.writeFileSync(settingsPath, JSON.stringify(config, null, 2));
3339
+ }
3340
+
3341
+ function listShortcuts(): void {
3342
+ console.log('\nELSABRO Keyboard Shortcuts\n');
3343
+ console.log('Action VSCode JetBrains');
3344
+ console.log('----------------------------------------------------------------');
3345
+ console.log('Run Agent Ctrl+Shift+E R Ctrl+Shift+E R');
3346
+ console.log('Pause Agent Ctrl+Shift+E P Ctrl+Shift+E P');
3347
+ console.log('Cancel Task Ctrl+Shift+E C Ctrl+Shift+E C');
3348
+ console.log('Show Panel Ctrl+Shift+E E Ctrl+Shift+E E');
3349
+ console.log('Generate Tests Ctrl+Shift+E T Ctrl+Shift+E T');
3350
+ console.log('Generate Docs Ctrl+Shift+E D Ctrl+Shift+E D');
3351
+ console.log('AI Refactor Ctrl+Shift+E F Ctrl+Shift+E F');
3352
+ console.log('Quick Actions Ctrl+Shift+E A Ctrl+Shift+E A');
3353
+ console.log('\nNote: On macOS, use Cmd instead of Ctrl');
3354
+ }
3355
+
3356
+ async function resetShortcuts(): Promise<void> {
3357
+ // Reset shortcuts implementation
3358
+ }
3359
+
3360
+ async function exportShortcuts(file: string): Promise<void> {
3361
+ // Export shortcuts implementation
3362
+ }
3363
+
3364
+ async function importShortcuts(file: string): Promise<void> {
3365
+ // Import shortcuts implementation
3366
+ }
3367
+
3368
+ async function syncPush(): Promise<void> {
3369
+ // Sync push implementation
3370
+ }
3371
+
3372
+ async function syncPull(): Promise<void> {
3373
+ // Sync pull implementation
3374
+ }
3375
+
3376
+ async function syncDiff(): Promise<any> {
3377
+ // Sync diff implementation
3378
+ return {};
3379
+ }
3380
+
3381
+ async function getIntegrationStatus(): Promise<any> {
3382
+ return {
3383
+ vscode: {
3384
+ installed: true,
3385
+ version: '3.7.0',
3386
+ connected: false,
3387
+ },
3388
+ jetbrains: {
3389
+ installed: false,
3390
+ version: null,
3391
+ connected: false,
3392
+ },
3393
+ };
3394
+ }
3395
+
3396
+ async function uninstallVSCodeExtension(): Promise<void> {
3397
+ return new Promise((resolve, reject) => {
3398
+ const proc = spawn('code', ['--uninstall-extension', 'elsabro.elsabro-vscode'], {
3399
+ stdio: 'inherit',
3400
+ });
3401
+
3402
+ proc.on('close', (code) => {
3403
+ if (code === 0) resolve();
3404
+ else reject(new Error(`Uninstallation failed with code ${code}`));
3405
+ });
3406
+ });
3407
+ }
3408
+
3409
+ async function uninstallJetBrainsPlugin(): Promise<void> {
3410
+ // JetBrains plugin uninstallation logic
3411
+ }
3412
+ ```
3413
+
3414
+ ---
3415
+
3416
+ ## Diagrams de Arquitectura
3417
+
3418
+ ### Flujo de Comunicacion
3419
+
3420
+ ```
3421
+ +------------------------------------------------------------------+
3422
+ | Communication Flow Diagram |
3423
+ +------------------------------------------------------------------+
3424
+
3425
+ +------------------+
3426
+ | Developer |
3427
+ +--------+---------+
3428
+ |
3429
+ +--------------+--------------+
3430
+ | |
3431
+ +-------v-------+ +-------v-------+
3432
+ | VSCode | | JetBrains |
3433
+ | Extension | | Plugin |
3434
+ +-------+-------+ +-------+-------+
3435
+ | |
3436
+ | +----------------+ |
3437
+ +---->| LSP Server |<-----+
3438
+ | (Optional Hub) |
3439
+ +-------+--------+
3440
+ |
3441
+ +-------v--------+
3442
+ | WebSocket Conn |
3443
+ +-------+--------+
3444
+ |
3445
+ +-------v--------+
3446
+ | ELSABRO Server |
3447
+ +-------+--------+
3448
+ |
3449
+ +---------------------+---------------------+
3450
+ | | |
3451
+ +-------v-------+ +-------v-------+ +-------v-------+
3452
+ | Agents | | Tasks | | Logs |
3453
+ +---------------+ +---------------+ +---------------+
3454
+ ```
3455
+
3456
+ ### Estado de Sesion
3457
+
3458
+ ```
3459
+ +------------------------------------------------------------------+
3460
+ | Session State Machine |
3461
+ +------------------------------------------------------------------+
3462
+
3463
+ +-------------+
3464
+ | DISCONNECTED|<-----------+
3465
+ +------+------+ |
3466
+ | |
3467
+ | connect() | disconnect() / error
3468
+ v |
3469
+ +------+------+ |
3470
+ | CONNECTING |------------+
3471
+ +------+------+
3472
+ |
3473
+ | connected
3474
+ v
3475
+ +------+------+
3476
+ | CONNECTED |<-----------+
3477
+ +------+------+ |
3478
+ | |
3479
+ | runAgent() | taskComplete()
3480
+ v |
3481
+ +------+------+ |
3482
+ | RUNNING |------------+
3483
+ +------+------+
3484
+ |
3485
+ | pause()
3486
+ v
3487
+ +------+------+
3488
+ | PAUSED |
3489
+ +-------------+
3490
+ |
3491
+ | resume() / cancel()
3492
+ v
3493
+ (back to CONNECTED or RUNNING)
3494
+ ```
3495
+
3496
+ ---
3497
+
3498
+ ## Changelog
3499
+
3500
+ | Version | Fecha | Cambios |
3501
+ |---------|-------|---------|
3502
+ | 3.7.0 | 2025-02 | Initial IDE integrations release |
3503
+ | 3.6.0 | 2025-01 | Core agent improvements |
3504
+ | 3.5.0 | 2024-12 | WebSocket protocol updates |
3505
+
3506
+ ---
3507
+
3508
+ ## Referencias
3509
+
3510
+ - [VSCode Extension API](https://code.visualstudio.com/api)
3511
+ - [IntelliJ Platform SDK](https://plugins.jetbrains.com/docs/intellij/)
3512
+ - [Language Server Protocol](https://microsoft.github.io/language-server-protocol/)
3513
+ - [ELSABRO Core Documentation](/references/core-api.md)