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.
- package/README.md +668 -20
- package/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- 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)
|