agentuity-vscode 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +475 -2
- package/src/core/cliClient.ts +477 -0
- package/src/core/index.ts +1 -0
- package/src/core/readonlyDocument.ts +11 -0
- package/src/core/sandboxManager.ts +483 -0
- package/src/extension.ts +14 -0
- package/src/features/chat/agentTools.ts +254 -1
- package/src/features/sandboxExplorer/index.ts +1375 -0
- package/src/features/sandboxExplorer/sandboxTreeData.ts +803 -0
- package/src/features/sandboxExplorer/statusBar.ts +383 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import * as vscode from 'vscode';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { getCliClient, CliClient, type SandboxInfo } from './cliClient';
|
|
7
|
+
|
|
8
|
+
/** Default remote path for sandbox file operations */
|
|
9
|
+
export const DEFAULT_SANDBOX_PATH = CliClient.SANDBOX_HOME;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Represents a sandbox linked to the current workspace.
|
|
13
|
+
*/
|
|
14
|
+
export interface LinkedSandbox {
|
|
15
|
+
sandboxId: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
linkedAt: string;
|
|
18
|
+
lastSyncedAt?: string;
|
|
19
|
+
remotePath: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for syncing files to a sandbox.
|
|
24
|
+
*/
|
|
25
|
+
export interface SyncOptions {
|
|
26
|
+
remotePath?: string;
|
|
27
|
+
clean?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Result of a sync operation.
|
|
32
|
+
*/
|
|
33
|
+
export interface SyncResult {
|
|
34
|
+
filesUploaded: number;
|
|
35
|
+
bytesTransferred: number;
|
|
36
|
+
duration: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const LINKED_SANDBOXES_KEY = 'agentuity.linkedSandboxes';
|
|
40
|
+
|
|
41
|
+
let _sandboxManager: SandboxManager | undefined;
|
|
42
|
+
const _onLinkedSandboxesChanged = new vscode.EventEmitter<LinkedSandbox[]>();
|
|
43
|
+
export const onLinkedSandboxesChanged = _onLinkedSandboxesChanged.event;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Manages sandbox linking and file synchronization for workspaces.
|
|
47
|
+
*/
|
|
48
|
+
export class SandboxManager {
|
|
49
|
+
private context: vscode.ExtensionContext;
|
|
50
|
+
private cliClient: CliClient;
|
|
51
|
+
|
|
52
|
+
constructor(context: vscode.ExtensionContext) {
|
|
53
|
+
this.context = context;
|
|
54
|
+
this.cliClient = getCliClient();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get all sandboxes linked to the current workspace.
|
|
59
|
+
*/
|
|
60
|
+
getLinkedSandboxes(): LinkedSandbox[] {
|
|
61
|
+
const workspaceKey = this.getWorkspaceKey();
|
|
62
|
+
if (!workspaceKey) return [];
|
|
63
|
+
|
|
64
|
+
const allLinked = this.context.workspaceState.get<Record<string, LinkedSandbox[]>>(
|
|
65
|
+
LINKED_SANDBOXES_KEY,
|
|
66
|
+
{}
|
|
67
|
+
);
|
|
68
|
+
return allLinked[workspaceKey] || [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Link a sandbox to the current workspace.
|
|
73
|
+
*/
|
|
74
|
+
async linkSandbox(
|
|
75
|
+
sandboxId: string,
|
|
76
|
+
options: { name?: string; remotePath?: string } = {}
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const workspaceKey = this.getWorkspaceKey();
|
|
79
|
+
if (!workspaceKey) {
|
|
80
|
+
throw new Error('No workspace folder open');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Verify sandbox exists
|
|
84
|
+
const result = await this.cliClient.sandboxGet(sandboxId);
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
throw new Error(`Failed to verify sandbox: ${result.error}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const allLinked = this.context.workspaceState.get<Record<string, LinkedSandbox[]>>(
|
|
90
|
+
LINKED_SANDBOXES_KEY,
|
|
91
|
+
{}
|
|
92
|
+
);
|
|
93
|
+
const workspaceLinks = allLinked[workspaceKey] || [];
|
|
94
|
+
|
|
95
|
+
// Check if already linked
|
|
96
|
+
const existingIndex = workspaceLinks.findIndex((l) => l.sandboxId === sandboxId);
|
|
97
|
+
if (existingIndex >= 0) {
|
|
98
|
+
// Update existing link
|
|
99
|
+
workspaceLinks[existingIndex] = {
|
|
100
|
+
...workspaceLinks[existingIndex],
|
|
101
|
+
name: options.name ?? workspaceLinks[existingIndex].name,
|
|
102
|
+
remotePath: options.remotePath ?? workspaceLinks[existingIndex].remotePath,
|
|
103
|
+
};
|
|
104
|
+
} else {
|
|
105
|
+
// Add new link
|
|
106
|
+
workspaceLinks.push({
|
|
107
|
+
sandboxId,
|
|
108
|
+
name: options.name,
|
|
109
|
+
linkedAt: new Date().toISOString(),
|
|
110
|
+
remotePath: options.remotePath ?? DEFAULT_SANDBOX_PATH,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
allLinked[workspaceKey] = workspaceLinks;
|
|
115
|
+
await this.context.workspaceState.update(LINKED_SANDBOXES_KEY, allLinked);
|
|
116
|
+
_onLinkedSandboxesChanged.fire(workspaceLinks);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Unlink a sandbox from the current workspace.
|
|
121
|
+
*/
|
|
122
|
+
async unlinkSandbox(sandboxId: string): Promise<void> {
|
|
123
|
+
const workspaceKey = this.getWorkspaceKey();
|
|
124
|
+
if (!workspaceKey) return;
|
|
125
|
+
|
|
126
|
+
const allLinked = this.context.workspaceState.get<Record<string, LinkedSandbox[]>>(
|
|
127
|
+
LINKED_SANDBOXES_KEY,
|
|
128
|
+
{}
|
|
129
|
+
);
|
|
130
|
+
const workspaceLinks = allLinked[workspaceKey] || [];
|
|
131
|
+
|
|
132
|
+
const filtered = workspaceLinks.filter((l) => l.sandboxId !== sandboxId);
|
|
133
|
+
allLinked[workspaceKey] = filtered;
|
|
134
|
+
await this.context.workspaceState.update(LINKED_SANDBOXES_KEY, allLinked);
|
|
135
|
+
_onLinkedSandboxesChanged.fire(filtered);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if a sandbox is linked to the current workspace.
|
|
140
|
+
*/
|
|
141
|
+
isLinked(sandboxId: string): boolean {
|
|
142
|
+
return this.getLinkedSandboxes().some((l) => l.sandboxId === sandboxId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get linked sandbox info by ID.
|
|
147
|
+
*/
|
|
148
|
+
getLinkedSandbox(sandboxId: string): LinkedSandbox | undefined {
|
|
149
|
+
return this.getLinkedSandboxes().find((l) => l.sandboxId === sandboxId);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Sync workspace files to a sandbox, respecting .gitignore.
|
|
154
|
+
*/
|
|
155
|
+
async syncToSandbox(sandboxId: string, options: SyncOptions = {}): Promise<SyncResult> {
|
|
156
|
+
const workspaceFolder = this.getWorkspaceFolder();
|
|
157
|
+
if (!workspaceFolder) {
|
|
158
|
+
throw new Error('No workspace folder open');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const remotePath = options.remotePath ?? DEFAULT_SANDBOX_PATH;
|
|
162
|
+
const startTime = Date.now();
|
|
163
|
+
|
|
164
|
+
// Get files to sync (respecting .gitignore)
|
|
165
|
+
const files = await this.getFilesToSync(workspaceFolder);
|
|
166
|
+
if (files.length === 0) {
|
|
167
|
+
return { filesUploaded: 0, bytesTransferred: 0, duration: Date.now() - startTime };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Create tar.gz archive
|
|
171
|
+
const archivePath = await this.createSyncArchive(files, workspaceFolder.uri.fsPath);
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
// Get archive size
|
|
175
|
+
const stats = fs.statSync(archivePath);
|
|
176
|
+
const bytesTransferred = stats.size;
|
|
177
|
+
|
|
178
|
+
// Upload and extract
|
|
179
|
+
const uploadResult = await this.cliClient.sandboxUpload(sandboxId, archivePath, remotePath);
|
|
180
|
+
if (!uploadResult.success) {
|
|
181
|
+
throw new Error(`Failed to upload files: ${uploadResult.error}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Update last synced time
|
|
185
|
+
await this.updateLastSynced(sandboxId);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
filesUploaded: files.length,
|
|
189
|
+
bytesTransferred,
|
|
190
|
+
duration: Date.now() - startTime,
|
|
191
|
+
};
|
|
192
|
+
} finally {
|
|
193
|
+
// Clean up temp archive
|
|
194
|
+
try {
|
|
195
|
+
fs.unlinkSync(archivePath);
|
|
196
|
+
} catch {
|
|
197
|
+
// Ignore cleanup errors
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Download files from a sandbox to a local path.
|
|
204
|
+
*/
|
|
205
|
+
async downloadFromSandbox(
|
|
206
|
+
sandboxId: string,
|
|
207
|
+
remotePath: string,
|
|
208
|
+
localPath: string
|
|
209
|
+
): Promise<void> {
|
|
210
|
+
const result = await this.cliClient.sandboxCpFromSandbox(sandboxId, remotePath, localPath, true);
|
|
211
|
+
if (!result.success) {
|
|
212
|
+
throw new Error(`Failed to download files: ${result.error}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get the list of files to sync, respecting .gitignore and default exclusions.
|
|
218
|
+
*/
|
|
219
|
+
private async getFilesToSync(workspaceFolder: vscode.WorkspaceFolder): Promise<string[]> {
|
|
220
|
+
const rootPath = workspaceFolder.uri.fsPath;
|
|
221
|
+
|
|
222
|
+
// Get default exclusions from settings
|
|
223
|
+
const config = vscode.workspace.getConfiguration('agentuity');
|
|
224
|
+
const defaultExclusions = config.get<string[]>('sandbox.syncExclusions', [
|
|
225
|
+
'.git',
|
|
226
|
+
'node_modules',
|
|
227
|
+
'.agentuity',
|
|
228
|
+
'dist',
|
|
229
|
+
'build',
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
// Use git ls-files if in a git repo, otherwise walk directory
|
|
233
|
+
const isGitRepo = fs.existsSync(path.join(rootPath, '.git'));
|
|
234
|
+
|
|
235
|
+
if (isGitRepo) {
|
|
236
|
+
return this.getGitTrackedFiles(rootPath, defaultExclusions);
|
|
237
|
+
} else {
|
|
238
|
+
return this.walkDirectory(rootPath, defaultExclusions);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get files tracked by git (respects .gitignore automatically).
|
|
244
|
+
*/
|
|
245
|
+
private getGitTrackedFiles(rootPath: string, additionalExclusions: string[]): Promise<string[]> {
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
// Use git ls-files to get all tracked and untracked (but not ignored) files
|
|
248
|
+
const child = spawn('git', ['ls-files', '-co', '--exclude-standard'], {
|
|
249
|
+
cwd: rootPath,
|
|
250
|
+
shell: true,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
let stdout = '';
|
|
254
|
+
let stderr = '';
|
|
255
|
+
|
|
256
|
+
child.stdout?.on('data', (data: Buffer) => {
|
|
257
|
+
stdout += data.toString();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
child.stderr?.on('data', (data: Buffer) => {
|
|
261
|
+
stderr += data.toString();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
child.on('error', (err) => {
|
|
265
|
+
reject(new Error(`Git command failed: ${err.message}`));
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
child.on('close', (code) => {
|
|
269
|
+
if (code !== 0) {
|
|
270
|
+
reject(new Error(`Git command failed: ${stderr}`));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const files = stdout
|
|
275
|
+
.trim()
|
|
276
|
+
.split('\n')
|
|
277
|
+
.filter((f) => f.length > 0)
|
|
278
|
+
.filter((f) => {
|
|
279
|
+
// Apply additional exclusions
|
|
280
|
+
for (const exclusion of additionalExclusions) {
|
|
281
|
+
if (f.startsWith(exclusion + '/') || f === exclusion) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return true;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
resolve(files);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Walk directory manually (for non-git projects).
|
|
295
|
+
*/
|
|
296
|
+
private walkDirectory(rootPath: string, exclusions: string[]): Promise<string[]> {
|
|
297
|
+
return new Promise((resolve) => {
|
|
298
|
+
const files: string[] = [];
|
|
299
|
+
|
|
300
|
+
const walk = (dir: string, relativePath: string = '') => {
|
|
301
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
302
|
+
|
|
303
|
+
for (const entry of entries) {
|
|
304
|
+
const entryRelPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
305
|
+
|
|
306
|
+
// Check exclusions
|
|
307
|
+
let excluded = false;
|
|
308
|
+
for (const exclusion of exclusions) {
|
|
309
|
+
if (entryRelPath.startsWith(exclusion + '/') || entryRelPath === exclusion) {
|
|
310
|
+
excluded = true;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (excluded) continue;
|
|
315
|
+
|
|
316
|
+
const fullPath = path.join(dir, entry.name);
|
|
317
|
+
|
|
318
|
+
if (entry.isDirectory()) {
|
|
319
|
+
walk(fullPath, entryRelPath);
|
|
320
|
+
} else if (entry.isFile()) {
|
|
321
|
+
files.push(entryRelPath);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
walk(rootPath);
|
|
327
|
+
resolve(files);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Create a tar.gz archive of the specified files.
|
|
333
|
+
*/
|
|
334
|
+
private createSyncArchive(files: string[], rootPath: string): Promise<string> {
|
|
335
|
+
return new Promise((resolve, reject) => {
|
|
336
|
+
const tmpDir = os.tmpdir();
|
|
337
|
+
const archiveName = `agentuity-sync-${Date.now()}.tar.gz`;
|
|
338
|
+
const archivePath = path.join(tmpDir, archiveName);
|
|
339
|
+
|
|
340
|
+
// Write file list to a temp file for tar
|
|
341
|
+
const fileListPath = path.join(tmpDir, `agentuity-files-${Date.now()}.txt`);
|
|
342
|
+
fs.writeFileSync(fileListPath, files.join('\n'));
|
|
343
|
+
|
|
344
|
+
// Create tar.gz using tar command
|
|
345
|
+
const child = spawn('tar', ['-czf', archivePath, '-T', fileListPath], {
|
|
346
|
+
cwd: rootPath,
|
|
347
|
+
shell: true,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
let stderr = '';
|
|
351
|
+
|
|
352
|
+
child.stderr?.on('data', (data: Buffer) => {
|
|
353
|
+
stderr += data.toString();
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
child.on('error', (err) => {
|
|
357
|
+
try {
|
|
358
|
+
fs.unlinkSync(fileListPath);
|
|
359
|
+
} catch {
|
|
360
|
+
// Ignore
|
|
361
|
+
}
|
|
362
|
+
reject(new Error(`Failed to create archive: ${err.message}`));
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
child.on('close', (code) => {
|
|
366
|
+
try {
|
|
367
|
+
fs.unlinkSync(fileListPath);
|
|
368
|
+
} catch {
|
|
369
|
+
// Ignore
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (code !== 0) {
|
|
373
|
+
reject(new Error(`Failed to create archive: ${stderr}`));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
resolve(archivePath);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Update the last synced timestamp for a linked sandbox.
|
|
384
|
+
*/
|
|
385
|
+
private async updateLastSynced(sandboxId: string): Promise<void> {
|
|
386
|
+
const workspaceKey = this.getWorkspaceKey();
|
|
387
|
+
if (!workspaceKey) return;
|
|
388
|
+
|
|
389
|
+
const allLinked = this.context.workspaceState.get<Record<string, LinkedSandbox[]>>(
|
|
390
|
+
LINKED_SANDBOXES_KEY,
|
|
391
|
+
{}
|
|
392
|
+
);
|
|
393
|
+
const workspaceLinks = allLinked[workspaceKey] || [];
|
|
394
|
+
|
|
395
|
+
const linkIndex = workspaceLinks.findIndex((l) => l.sandboxId === sandboxId);
|
|
396
|
+
if (linkIndex >= 0) {
|
|
397
|
+
workspaceLinks[linkIndex].lastSyncedAt = new Date().toISOString();
|
|
398
|
+
allLinked[workspaceKey] = workspaceLinks;
|
|
399
|
+
await this.context.workspaceState.update(LINKED_SANDBOXES_KEY, allLinked);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get a unique key for the current workspace.
|
|
405
|
+
*/
|
|
406
|
+
private getWorkspaceKey(): string | undefined {
|
|
407
|
+
const folder = this.getWorkspaceFolder();
|
|
408
|
+
return folder?.uri.fsPath;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Get the current workspace folder.
|
|
413
|
+
*/
|
|
414
|
+
private getWorkspaceFolder(): vscode.WorkspaceFolder | undefined {
|
|
415
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
416
|
+
return folders?.[0];
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Refresh sandbox info for all linked sandboxes.
|
|
421
|
+
* Returns info about which sandboxes are still valid.
|
|
422
|
+
*/
|
|
423
|
+
async refreshLinkedSandboxes(): Promise<Map<string, SandboxInfo | null>> {
|
|
424
|
+
const linked = this.getLinkedSandboxes();
|
|
425
|
+
const results = new Map<string, SandboxInfo | null>();
|
|
426
|
+
|
|
427
|
+
for (const link of linked) {
|
|
428
|
+
const result = await this.cliClient.sandboxGet(link.sandboxId);
|
|
429
|
+
if (result.success && result.data) {
|
|
430
|
+
results.set(link.sandboxId, result.data);
|
|
431
|
+
} else {
|
|
432
|
+
results.set(link.sandboxId, null);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return results;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
dispose(): void {
|
|
440
|
+
// Nothing to dispose currently
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Initialize the sandbox manager.
|
|
446
|
+
*/
|
|
447
|
+
export function initSandboxManager(context: vscode.ExtensionContext): SandboxManager {
|
|
448
|
+
if (!_sandboxManager) {
|
|
449
|
+
_sandboxManager = new SandboxManager(context);
|
|
450
|
+
}
|
|
451
|
+
return _sandboxManager;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get the sandbox manager instance.
|
|
456
|
+
*/
|
|
457
|
+
export function getSandboxManager(): SandboxManager {
|
|
458
|
+
if (!_sandboxManager) {
|
|
459
|
+
throw new Error('SandboxManager not initialized. Call initSandboxManager first.');
|
|
460
|
+
}
|
|
461
|
+
return _sandboxManager;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Dispose the sandbox manager.
|
|
466
|
+
*/
|
|
467
|
+
export function disposeSandboxManager(): void {
|
|
468
|
+
if (_sandboxManager) {
|
|
469
|
+
_sandboxManager.dispose();
|
|
470
|
+
_sandboxManager = undefined;
|
|
471
|
+
}
|
|
472
|
+
_onLinkedSandboxesChanged.dispose();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Format bytes to human-readable string.
|
|
477
|
+
*/
|
|
478
|
+
export function formatBytes(bytes: number): string {
|
|
479
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
480
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
481
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
482
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
483
|
+
}
|
package/src/extension.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { registerReadonlyDocumentProvider } from './core/readonlyDocument';
|
|
|
15
15
|
import { registerAgentExplorer } from './features/agentExplorer';
|
|
16
16
|
import { registerDataExplorer } from './features/dataExplorer';
|
|
17
17
|
import { registerDeploymentExplorer } from './features/deploymentExplorer';
|
|
18
|
+
import { registerSandboxExplorer } from './features/sandboxExplorer';
|
|
19
|
+
import { disposeSandboxManager } from './core/sandboxManager';
|
|
18
20
|
import { registerDevServerCommands } from './features/devServer';
|
|
19
21
|
import { registerWorkbenchCommands } from './features/workbench';
|
|
20
22
|
import {
|
|
@@ -62,11 +64,13 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|
|
62
64
|
const agentProvider = registerAgentExplorer(context);
|
|
63
65
|
const dataProvider = registerDataExplorer(context);
|
|
64
66
|
const deploymentProvider = registerDeploymentExplorer(context);
|
|
67
|
+
const sandboxProvider = registerSandboxExplorer(context);
|
|
65
68
|
|
|
66
69
|
registerRefreshCommands(context, {
|
|
67
70
|
agents: agentProvider,
|
|
68
71
|
data: dataProvider,
|
|
69
72
|
deployments: deploymentProvider,
|
|
73
|
+
sandboxes: sandboxProvider,
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
context.subscriptions.push({
|
|
@@ -74,6 +78,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|
|
74
78
|
agentProvider.dispose();
|
|
75
79
|
dataProvider.dispose();
|
|
76
80
|
deploymentProvider.dispose();
|
|
81
|
+
sandboxProvider.dispose();
|
|
77
82
|
},
|
|
78
83
|
});
|
|
79
84
|
|
|
@@ -252,6 +257,7 @@ function registerRefreshCommands(
|
|
|
252
257
|
agents: ReturnType<typeof registerAgentExplorer>;
|
|
253
258
|
data: ReturnType<typeof registerDataExplorer>;
|
|
254
259
|
deployments: ReturnType<typeof registerDeploymentExplorer>;
|
|
260
|
+
sandboxes: ReturnType<typeof registerSandboxExplorer>;
|
|
255
261
|
}
|
|
256
262
|
): void {
|
|
257
263
|
context.subscriptions.push(
|
|
@@ -261,6 +267,7 @@ function registerRefreshCommands(
|
|
|
261
267
|
providers.agents.forceRefresh();
|
|
262
268
|
providers.data.refresh();
|
|
263
269
|
providers.deployments.forceRefresh();
|
|
270
|
+
providers.sandboxes.forceRefresh();
|
|
264
271
|
vscode.window.showInformationMessage('Agentuity refreshed');
|
|
265
272
|
})
|
|
266
273
|
);
|
|
@@ -282,11 +289,18 @@ function registerRefreshCommands(
|
|
|
282
289
|
providers.data.refresh();
|
|
283
290
|
})
|
|
284
291
|
);
|
|
292
|
+
|
|
293
|
+
context.subscriptions.push(
|
|
294
|
+
vscode.commands.registerCommand('agentuity.sandbox.refresh', () => {
|
|
295
|
+
void providers.sandboxes.forceRefresh();
|
|
296
|
+
})
|
|
297
|
+
);
|
|
285
298
|
}
|
|
286
299
|
|
|
287
300
|
export function deactivate(): void {
|
|
288
301
|
disposeCliClient();
|
|
289
302
|
disposeAuth();
|
|
290
303
|
disposeProject();
|
|
304
|
+
disposeSandboxManager();
|
|
291
305
|
disposeLogger();
|
|
292
306
|
}
|