agentuity-vscode 0.0.86

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.
@@ -0,0 +1,258 @@
1
+ import * as vscode from 'vscode';
2
+ import { spawn, exec, type ChildProcess } from 'child_process';
3
+ import { getCurrentProject } from '../../core/project';
4
+ import { getCliClient } from '../../core/cliClient';
5
+
6
+ export type DevServerState = 'stopped' | 'starting' | 'running' | 'error';
7
+
8
+ export class DevServerManager {
9
+ private process: ChildProcess | undefined;
10
+ private state: DevServerState = 'stopped';
11
+ private outputChannel: vscode.OutputChannel;
12
+ private statusBarItem: vscode.StatusBarItem;
13
+ private startupTimeoutId: NodeJS.Timeout | undefined;
14
+ private hasReceivedOutput = false;
15
+
16
+ private _onStateChanged = new vscode.EventEmitter<DevServerState>();
17
+ readonly onStateChanged = this._onStateChanged.event;
18
+
19
+ constructor() {
20
+ this.outputChannel = vscode.window.createOutputChannel('Agentuity Dev Server');
21
+ this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
22
+ this.updateStatusBar();
23
+ }
24
+
25
+ private setState(state: DevServerState): void {
26
+ this.state = state;
27
+ this.updateStatusBar();
28
+ this._onStateChanged.fire(state);
29
+ }
30
+
31
+ private updateStatusBar(): void {
32
+ switch (this.state) {
33
+ case 'stopped':
34
+ this.statusBarItem.text = '$(debug-stop) Agentuity: Stopped';
35
+ this.statusBarItem.backgroundColor = undefined;
36
+ this.statusBarItem.command = 'agentuity.dev.start';
37
+ this.statusBarItem.tooltip = 'Click to start dev server';
38
+ break;
39
+ case 'starting':
40
+ this.statusBarItem.text = '$(loading~spin) Agentuity: Starting...';
41
+ this.statusBarItem.backgroundColor = new vscode.ThemeColor(
42
+ 'statusBarItem.warningBackground'
43
+ );
44
+ this.statusBarItem.command = undefined;
45
+ this.statusBarItem.tooltip = 'Dev server is starting';
46
+ break;
47
+ case 'running':
48
+ this.statusBarItem.text = '$(debug-start) Agentuity: Running';
49
+ this.statusBarItem.backgroundColor = new vscode.ThemeColor(
50
+ 'statusBarItem.prominentBackground'
51
+ );
52
+ this.statusBarItem.command = 'agentuity.dev.stop';
53
+ this.statusBarItem.tooltip = 'Click to stop dev server';
54
+ break;
55
+ case 'error':
56
+ this.statusBarItem.text = '$(error) Agentuity: Error';
57
+ this.statusBarItem.backgroundColor = new vscode.ThemeColor(
58
+ 'statusBarItem.errorBackground'
59
+ );
60
+ this.statusBarItem.command = 'agentuity.dev.showLogs';
61
+ this.statusBarItem.tooltip = 'Click to view logs';
62
+ break;
63
+ }
64
+ this.statusBarItem.show();
65
+ }
66
+
67
+ getState(): DevServerState {
68
+ return this.state;
69
+ }
70
+
71
+ async start(): Promise<void> {
72
+ if (this.state === 'running' || this.state === 'starting') {
73
+ vscode.window.showWarningMessage('Dev server is already running');
74
+ return;
75
+ }
76
+
77
+ const project = getCurrentProject();
78
+ if (!project) {
79
+ vscode.window.showErrorMessage('No Agentuity project found');
80
+ return;
81
+ }
82
+
83
+ this.setState('starting');
84
+ this.outputChannel.clear();
85
+ this.outputChannel.show(true);
86
+ this.hasReceivedOutput = false;
87
+
88
+ const cli = getCliClient();
89
+ const cliPath = cli.getCliPath();
90
+ const env = cli.getCliEnv();
91
+
92
+ try {
93
+ this.process = spawn(cliPath, ['dev'], {
94
+ cwd: project.rootPath,
95
+ shell: true,
96
+ env,
97
+ // On Unix, create a new process group so we can kill the entire tree
98
+ detached: process.platform !== 'win32',
99
+ });
100
+
101
+ this.process.stdout?.on('data', (data: Buffer) => {
102
+ const text = data.toString();
103
+ this.outputChannel.append(text);
104
+ this.hasReceivedOutput = true;
105
+
106
+ // Detect ready signals from the dev server
107
+ if (text.includes('listening') || text.includes('started') || text.includes('ready')) {
108
+ this.clearStartupTimeout();
109
+ this.setState('running');
110
+ }
111
+ });
112
+
113
+ this.process.stderr?.on('data', (data: Buffer) => {
114
+ this.outputChannel.append(data.toString());
115
+ this.hasReceivedOutput = true;
116
+ });
117
+
118
+ this.process.on('error', (err: Error) => {
119
+ this.clearStartupTimeout();
120
+ this.outputChannel.appendLine(`Error: ${err.message}`);
121
+ this.setState('error');
122
+ this.process = undefined;
123
+ });
124
+
125
+ this.process.on('close', (code: number | null) => {
126
+ this.clearStartupTimeout();
127
+ this.outputChannel.appendLine(`\nDev server exited with code ${code}`);
128
+ if (this.state !== 'stopped') {
129
+ this.setState(code === 0 ? 'stopped' : 'error');
130
+ }
131
+ this.process = undefined;
132
+ });
133
+
134
+ // Timeout: if no ready signal after 10s, check if we got any output
135
+ this.startupTimeoutId = setTimeout(() => {
136
+ if (this.state === 'starting') {
137
+ if (this.hasReceivedOutput) {
138
+ // Got output but no ready signal - assume running
139
+ this.setState('running');
140
+ } else {
141
+ // No output at all - likely failed to start
142
+ this.setState('error');
143
+ void vscode.window
144
+ .showErrorMessage(
145
+ 'Dev server failed to start. No output received.',
146
+ 'View Logs'
147
+ )
148
+ .then((action) => {
149
+ if (action === 'View Logs') {
150
+ this.showLogs();
151
+ }
152
+ });
153
+ }
154
+ }
155
+ }, 10000);
156
+ } catch (err) {
157
+ const message = err instanceof Error ? err.message : 'Unknown error';
158
+ this.outputChannel.appendLine(`Failed to start: ${message}`);
159
+ this.setState('error');
160
+ }
161
+ }
162
+
163
+ private clearStartupTimeout(): void {
164
+ if (this.startupTimeoutId) {
165
+ clearTimeout(this.startupTimeoutId);
166
+ this.startupTimeoutId = undefined;
167
+ }
168
+ }
169
+
170
+ async stop(): Promise<void> {
171
+ this.clearStartupTimeout();
172
+
173
+ if (!this.process) {
174
+ this.setState('stopped');
175
+ return;
176
+ }
177
+
178
+ this.outputChannel.appendLine('\nStopping dev server...');
179
+ this.setState('stopped');
180
+
181
+ const pid = this.process.pid;
182
+ if (pid) {
183
+ // Kill the entire process tree, not just the shell
184
+ await this.killProcessTree(pid);
185
+ }
186
+
187
+ this.process = undefined;
188
+ }
189
+
190
+ private async killProcessTree(pid: number): Promise<void> {
191
+ return new Promise((resolve) => {
192
+ if (process.platform === 'win32') {
193
+ // Windows: use taskkill to kill process tree
194
+ exec(`taskkill /pid ${pid} /T /F`, () => resolve());
195
+ } else {
196
+ // Unix: kill the process group (negative pid)
197
+ try {
198
+ process.kill(-pid, 'SIGTERM');
199
+ } catch {
200
+ // Process group might not exist, try killing just the pid
201
+ try {
202
+ process.kill(pid, 'SIGTERM');
203
+ } catch {
204
+ // Process already dead
205
+ }
206
+ }
207
+
208
+ // Force kill after timeout
209
+ setTimeout(() => {
210
+ try {
211
+ process.kill(-pid, 'SIGKILL');
212
+ } catch {
213
+ try {
214
+ process.kill(pid, 'SIGKILL');
215
+ } catch {
216
+ // Already dead
217
+ }
218
+ }
219
+ resolve();
220
+ }, 2000);
221
+ }
222
+ });
223
+ }
224
+
225
+ async restart(): Promise<void> {
226
+ await this.stop();
227
+ await new Promise((resolve) => setTimeout(resolve, 1000));
228
+ await this.start();
229
+ }
230
+
231
+ showLogs(): void {
232
+ this.outputChannel.show();
233
+ }
234
+
235
+ dispose(): void {
236
+ this.clearStartupTimeout();
237
+ this.stop();
238
+ this.outputChannel.dispose();
239
+ this.statusBarItem.dispose();
240
+ this._onStateChanged.dispose();
241
+ }
242
+ }
243
+
244
+ let _devServerManager: DevServerManager | undefined;
245
+
246
+ export function getDevServerManager(): DevServerManager {
247
+ if (!_devServerManager) {
248
+ _devServerManager = new DevServerManager();
249
+ }
250
+ return _devServerManager;
251
+ }
252
+
253
+ export function disposeDevServerManager(): void {
254
+ if (_devServerManager) {
255
+ _devServerManager.dispose();
256
+ _devServerManager = undefined;
257
+ }
258
+ }
@@ -0,0 +1,52 @@
1
+ import * as vscode from 'vscode';
2
+ import { getDevServerManager, disposeDevServerManager } from './devServerManager';
3
+ import { requireProject } from '../../core/project';
4
+ import { requireAuth } from '../../core/auth';
5
+
6
+ export function registerDevServerCommands(context: vscode.ExtensionContext): void {
7
+ const manager = getDevServerManager();
8
+
9
+ void vscode.commands.executeCommand(
10
+ 'setContext',
11
+ 'agentuity.devServerRunning',
12
+ manager.getState() === 'running'
13
+ );
14
+
15
+ manager.onStateChanged((state) => {
16
+ void vscode.commands.executeCommand('setContext', 'agentuity.devServerRunning', state === 'running');
17
+ });
18
+
19
+ context.subscriptions.push(
20
+ vscode.commands.registerCommand('agentuity.dev.start', async () => {
21
+ if (!(await requireAuth()) || !requireProject()) {
22
+ return;
23
+ }
24
+ await manager.start();
25
+ })
26
+ );
27
+
28
+ context.subscriptions.push(
29
+ vscode.commands.registerCommand('agentuity.dev.stop', async () => {
30
+ await manager.stop();
31
+ })
32
+ );
33
+
34
+ context.subscriptions.push(
35
+ vscode.commands.registerCommand('agentuity.dev.restart', async () => {
36
+ if (!(await requireAuth()) || !requireProject()) {
37
+ return;
38
+ }
39
+ await manager.restart();
40
+ })
41
+ );
42
+
43
+ context.subscriptions.push(
44
+ vscode.commands.registerCommand('agentuity.dev.showLogs', () => {
45
+ manager.showLogs();
46
+ })
47
+ );
48
+
49
+ context.subscriptions.push({ dispose: () => disposeDevServerManager() });
50
+ }
51
+
52
+ export { getDevServerManager, type DevServerState } from './devServerManager';
@@ -0,0 +1,19 @@
1
+ import * as vscode from 'vscode';
2
+ import { getCurrentProject } from '../../core/project';
3
+
4
+ const WORKBENCH_BASE_URL = 'https://app.agentuity.com';
5
+
6
+ export function registerWorkbenchCommands(context: vscode.ExtensionContext): void {
7
+ context.subscriptions.push(
8
+ vscode.commands.registerCommand('agentuity.workbench.open', async () => {
9
+ const project = getCurrentProject();
10
+
11
+ let url = WORKBENCH_BASE_URL;
12
+ if (project) {
13
+ url = `${WORKBENCH_BASE_URL}/projects/${project.projectId}`;
14
+ }
15
+
16
+ await vscode.env.openExternal(vscode.Uri.parse(url));
17
+ })
18
+ );
19
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "CommonJS",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "node",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "outDir": "./dist",
16
+ "rootDir": "./src"
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }