agentuity-vscode 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/cliClient.ts +110 -147
- package/src/core/sandboxManager.ts +46 -5
- package/src/features/chat/agentTools.ts +71 -3
- package/src/features/sandboxExplorer/index.ts +163 -37
- package/src/features/sandboxExplorer/sandboxTreeData.ts +66 -22
- package/src/features/sandboxExplorer/statusBar.ts +15 -2
package/package.json
CHANGED
package/src/core/cliClient.ts
CHANGED
|
@@ -103,19 +103,6 @@ export class CliClient {
|
|
|
103
103
|
return args;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
/**
|
|
107
|
-
* Append --region flag to args using region from agentuity.json.
|
|
108
|
-
* Used for commands that require region but don't accept --dir.
|
|
109
|
-
* The --region flag is a subcommand option, so it must come after the command.
|
|
110
|
-
*/
|
|
111
|
-
private withRegion(args: string[]): string[] {
|
|
112
|
-
const region = this.getProjectRegion();
|
|
113
|
-
if (region) {
|
|
114
|
-
return [...args, '--region', region];
|
|
115
|
-
}
|
|
116
|
-
return args;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
106
|
/**
|
|
120
107
|
* Get the environment variables for CLI execution.
|
|
121
108
|
* Sets TERM_PROGRAM=vscode to ensure CLI disables interactive mode.
|
|
@@ -329,15 +316,14 @@ export class CliClient {
|
|
|
329
316
|
return this.exec<string>(['ai', 'prompt', 'llm'], { format: 'text' });
|
|
330
317
|
}
|
|
331
318
|
|
|
332
|
-
// Database methods (require region - pass --region from agentuity.json)
|
|
333
319
|
async listDatabases(): Promise<CliResult<DbListResponse>> {
|
|
334
|
-
return this.exec<DbListResponse>(
|
|
320
|
+
return this.exec<DbListResponse>(['cloud', 'db', 'list'], {
|
|
335
321
|
format: 'json',
|
|
336
322
|
});
|
|
337
323
|
}
|
|
338
324
|
|
|
339
325
|
async getDatabase(name: string): Promise<CliResult<DbInfo>> {
|
|
340
|
-
return this.exec<DbInfo>(
|
|
326
|
+
return this.exec<DbInfo>(['cloud', 'db', 'get', name], {
|
|
341
327
|
format: 'json',
|
|
342
328
|
});
|
|
343
329
|
}
|
|
@@ -356,12 +342,11 @@ export class CliClient {
|
|
|
356
342
|
if (opts?.sessionId) {
|
|
357
343
|
args.push('--session-id', opts.sessionId);
|
|
358
344
|
}
|
|
359
|
-
return this.exec<DbQueryLog[]>(
|
|
345
|
+
return this.exec<DbQueryLog[]>(args, { format: 'json', timeout: 60000 });
|
|
360
346
|
}
|
|
361
347
|
|
|
362
|
-
// Storage methods (require region - pass --region from agentuity.json)
|
|
363
348
|
async listStorageBuckets(): Promise<CliResult<StorageListResponse>> {
|
|
364
|
-
return this.exec<StorageListResponse>(
|
|
349
|
+
return this.exec<StorageListResponse>(['cloud', 'storage', 'list'], {
|
|
365
350
|
format: 'json',
|
|
366
351
|
});
|
|
367
352
|
}
|
|
@@ -374,7 +359,7 @@ export class CliClient {
|
|
|
374
359
|
if (prefix) {
|
|
375
360
|
args.push(prefix);
|
|
376
361
|
}
|
|
377
|
-
return this.exec<StorageListResponse>(
|
|
362
|
+
return this.exec<StorageListResponse>(args, { format: 'json' });
|
|
378
363
|
}
|
|
379
364
|
|
|
380
365
|
async getStorageFileMetadata(
|
|
@@ -382,7 +367,7 @@ export class CliClient {
|
|
|
382
367
|
filename: string
|
|
383
368
|
): Promise<CliResult<StorageFileMetadataResponse>> {
|
|
384
369
|
return this.exec<StorageFileMetadataResponse>(
|
|
385
|
-
|
|
370
|
+
['cloud', 'storage', 'download', bucket, filename, '--metadata'],
|
|
386
371
|
{ format: 'json' }
|
|
387
372
|
);
|
|
388
373
|
}
|
|
@@ -509,17 +494,6 @@ export class CliClient {
|
|
|
509
494
|
|
|
510
495
|
// ==================== Sandbox Methods ====================
|
|
511
496
|
|
|
512
|
-
/** Default region for sandbox operations when no agentuity.json is present */
|
|
513
|
-
private readonly defaultSandboxRegion = 'usc';
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Get the region for sandbox operations.
|
|
517
|
-
* Uses region from agentuity.json if present, otherwise falls back to default.
|
|
518
|
-
*/
|
|
519
|
-
getSandboxRegion(): string {
|
|
520
|
-
return this.getProjectRegion() ?? this.defaultSandboxRegion;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
497
|
/** Default home path in sandboxes */
|
|
524
498
|
static readonly SANDBOX_HOME = '/home/agentuity';
|
|
525
499
|
|
|
@@ -527,8 +501,23 @@ export class CliClient {
|
|
|
527
501
|
* Create a new sandbox.
|
|
528
502
|
*/
|
|
529
503
|
async sandboxCreate(options: SandboxCreateOptions = {}): Promise<CliResult<SandboxInfo>> {
|
|
530
|
-
const
|
|
504
|
+
const region = this.getProjectRegion() ?? 'usc';
|
|
505
|
+
const args = ['cloud', 'sandbox', 'create', '--region', region];
|
|
531
506
|
|
|
507
|
+
// New runtime/name/description options
|
|
508
|
+
if (options.runtime) {
|
|
509
|
+
args.push('--runtime', options.runtime);
|
|
510
|
+
}
|
|
511
|
+
if (options.runtimeId) {
|
|
512
|
+
args.push('--runtime-id', options.runtimeId);
|
|
513
|
+
}
|
|
514
|
+
if (options.name) {
|
|
515
|
+
args.push('--name', options.name);
|
|
516
|
+
}
|
|
517
|
+
if (options.description) {
|
|
518
|
+
args.push('--description', options.description);
|
|
519
|
+
}
|
|
520
|
+
// Existing options
|
|
532
521
|
if (options.memory) {
|
|
533
522
|
args.push('--memory', options.memory);
|
|
534
523
|
}
|
|
@@ -569,11 +558,31 @@ export class CliClient {
|
|
|
569
558
|
return this.exec<SandboxInfo>(args, { format: 'json', timeout: 120000 });
|
|
570
559
|
}
|
|
571
560
|
|
|
561
|
+
/**
|
|
562
|
+
* List available sandbox runtimes.
|
|
563
|
+
*/
|
|
564
|
+
async sandboxRuntimeList(
|
|
565
|
+
params: SandboxRuntimeListParams = {}
|
|
566
|
+
): Promise<CliResult<SandboxRuntimeListResponse>> {
|
|
567
|
+
const args = ['cloud', 'sandbox', 'runtime', 'list'];
|
|
568
|
+
|
|
569
|
+
if (params.limit !== undefined) {
|
|
570
|
+
args.push('--limit', String(params.limit));
|
|
571
|
+
}
|
|
572
|
+
if (params.offset !== undefined) {
|
|
573
|
+
args.push('--offset', String(params.offset));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return this.exec<SandboxRuntimeListResponse>(args, { format: 'json' });
|
|
577
|
+
}
|
|
578
|
+
|
|
572
579
|
/**
|
|
573
580
|
* List sandboxes with optional filtering.
|
|
581
|
+
* Runs from home directory to list all sandboxes since sandbox create
|
|
582
|
+
* doesn't currently support project association.
|
|
574
583
|
*/
|
|
575
584
|
async sandboxList(filter: SandboxListFilter = {}): Promise<CliResult<SandboxInfo[]>> {
|
|
576
|
-
const args = ['cloud', 'sandbox', 'list'
|
|
585
|
+
const args = ['cloud', 'sandbox', 'list'];
|
|
577
586
|
|
|
578
587
|
if (filter.status) {
|
|
579
588
|
args.push('--status', filter.status);
|
|
@@ -588,9 +597,12 @@ export class CliClient {
|
|
|
588
597
|
args.push('--offset', String(filter.offset));
|
|
589
598
|
}
|
|
590
599
|
|
|
591
|
-
//
|
|
600
|
+
// Run from home directory to list all sandboxes
|
|
601
|
+
// CLI filters by project when run from project dir, but sandbox create
|
|
602
|
+
// doesn't support project association yet
|
|
592
603
|
const result = await this.exec<{ sandboxes: SandboxInfo[]; total: number }>(args, {
|
|
593
604
|
format: 'json',
|
|
605
|
+
cwd: os.homedir(),
|
|
594
606
|
});
|
|
595
607
|
if (result.success && result.data) {
|
|
596
608
|
return { success: true, data: result.data.sandboxes || [], exitCode: result.exitCode };
|
|
@@ -602,28 +614,16 @@ export class CliClient {
|
|
|
602
614
|
* Get detailed information about a sandbox.
|
|
603
615
|
*/
|
|
604
616
|
async sandboxGet(sandboxId: string): Promise<CliResult<SandboxInfo>> {
|
|
605
|
-
return this.exec<SandboxInfo>(
|
|
606
|
-
['cloud', 'sandbox', 'get', sandboxId, '--region', this.getSandboxRegion()],
|
|
607
|
-
{ format: 'json' }
|
|
608
|
-
);
|
|
617
|
+
return this.exec<SandboxInfo>(['cloud', 'sandbox', 'get', sandboxId], { format: 'json' });
|
|
609
618
|
}
|
|
610
619
|
|
|
611
620
|
/**
|
|
612
621
|
* Delete a sandbox.
|
|
613
622
|
*/
|
|
614
623
|
async sandboxDelete(sandboxId: string): Promise<CliResult<void>> {
|
|
615
|
-
return this.exec<void>(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
'sandbox',
|
|
619
|
-
'delete',
|
|
620
|
-
sandboxId,
|
|
621
|
-
'--confirm',
|
|
622
|
-
'--region',
|
|
623
|
-
this.getSandboxRegion(),
|
|
624
|
-
],
|
|
625
|
-
{ format: 'json' }
|
|
626
|
-
);
|
|
624
|
+
return this.exec<void>(['cloud', 'sandbox', 'delete', sandboxId, '--confirm'], {
|
|
625
|
+
format: 'json',
|
|
626
|
+
});
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
/**
|
|
@@ -635,7 +635,7 @@ export class CliClient {
|
|
|
635
635
|
command: string[],
|
|
636
636
|
options: SandboxExecOptions = {}
|
|
637
637
|
): Promise<CliResult<ExecutionInfo>> {
|
|
638
|
-
const args = ['cloud', 'sandbox', 'exec', sandboxId
|
|
638
|
+
const args = ['cloud', 'sandbox', 'exec', sandboxId];
|
|
639
639
|
|
|
640
640
|
if (options.timeout) {
|
|
641
641
|
args.push('--timeout', String(options.timeout));
|
|
@@ -655,13 +655,11 @@ export class CliClient {
|
|
|
655
655
|
*/
|
|
656
656
|
async sandboxLs(sandboxId: string, remotePath?: string): Promise<CliResult<SandboxFileInfo[]>> {
|
|
657
657
|
const args = ['cloud', 'sandbox', 'files', sandboxId];
|
|
658
|
-
// Only add path if specified (omit for root listing)
|
|
659
658
|
if (remotePath) {
|
|
660
659
|
args.push(remotePath);
|
|
661
660
|
}
|
|
662
|
-
args.push('-l'
|
|
661
|
+
args.push('-l');
|
|
663
662
|
|
|
664
|
-
// CLI returns { files: [...], total: N }, extract the array and add name from path
|
|
665
663
|
const result = await this.exec<{
|
|
666
664
|
files: Array<Omit<SandboxFileInfo, 'name'>>;
|
|
667
665
|
total: number;
|
|
@@ -669,7 +667,7 @@ export class CliClient {
|
|
|
669
667
|
if (result.success && result.data) {
|
|
670
668
|
const files = (result.data.files || []).map((f) => ({
|
|
671
669
|
...f,
|
|
672
|
-
name: f.path.split('/').pop() || f.path,
|
|
670
|
+
name: f.path.split('/').pop() || f.path,
|
|
673
671
|
}));
|
|
674
672
|
return { success: true, data: files, exitCode: result.exitCode };
|
|
675
673
|
}
|
|
@@ -685,7 +683,7 @@ export class CliClient {
|
|
|
685
683
|
remotePath: string,
|
|
686
684
|
recursive = false
|
|
687
685
|
): Promise<CliResult<SandboxCpResult>> {
|
|
688
|
-
const args = ['cloud', 'sandbox', 'cp'
|
|
686
|
+
const args = ['cloud', 'sandbox', 'cp'];
|
|
689
687
|
if (recursive) {
|
|
690
688
|
args.push('-r');
|
|
691
689
|
}
|
|
@@ -702,7 +700,7 @@ export class CliClient {
|
|
|
702
700
|
localPath: string,
|
|
703
701
|
recursive = false
|
|
704
702
|
): Promise<CliResult<SandboxCpResult>> {
|
|
705
|
-
const args = ['cloud', 'sandbox', 'cp'
|
|
703
|
+
const args = ['cloud', 'sandbox', 'cp'];
|
|
706
704
|
if (recursive) {
|
|
707
705
|
args.push('-r');
|
|
708
706
|
}
|
|
@@ -718,15 +716,7 @@ export class CliClient {
|
|
|
718
716
|
archivePath: string,
|
|
719
717
|
destPath?: string
|
|
720
718
|
): Promise<CliResult<void>> {
|
|
721
|
-
const args = [
|
|
722
|
-
'cloud',
|
|
723
|
-
'sandbox',
|
|
724
|
-
'upload',
|
|
725
|
-
sandboxId,
|
|
726
|
-
archivePath,
|
|
727
|
-
'--region',
|
|
728
|
-
this.getSandboxRegion(),
|
|
729
|
-
];
|
|
719
|
+
const args = ['cloud', 'sandbox', 'upload', sandboxId, archivePath];
|
|
730
720
|
if (destPath) {
|
|
731
721
|
args.push('--path', destPath);
|
|
732
722
|
}
|
|
@@ -741,15 +731,7 @@ export class CliClient {
|
|
|
741
731
|
outputPath: string,
|
|
742
732
|
sourcePath?: string
|
|
743
733
|
): Promise<CliResult<void>> {
|
|
744
|
-
const args = [
|
|
745
|
-
'cloud',
|
|
746
|
-
'sandbox',
|
|
747
|
-
'download',
|
|
748
|
-
sandboxId,
|
|
749
|
-
outputPath,
|
|
750
|
-
'--region',
|
|
751
|
-
this.getSandboxRegion(),
|
|
752
|
-
];
|
|
734
|
+
const args = ['cloud', 'sandbox', 'download', sandboxId, outputPath];
|
|
753
735
|
if (sourcePath) {
|
|
754
736
|
args.push('--path', sourcePath);
|
|
755
737
|
}
|
|
@@ -764,15 +746,7 @@ export class CliClient {
|
|
|
764
746
|
remotePath: string,
|
|
765
747
|
recursive = false
|
|
766
748
|
): Promise<CliResult<void>> {
|
|
767
|
-
const args = [
|
|
768
|
-
'cloud',
|
|
769
|
-
'sandbox',
|
|
770
|
-
'mkdir',
|
|
771
|
-
sandboxId,
|
|
772
|
-
remotePath,
|
|
773
|
-
'--region',
|
|
774
|
-
this.getSandboxRegion(),
|
|
775
|
-
];
|
|
749
|
+
const args = ['cloud', 'sandbox', 'mkdir', sandboxId, remotePath];
|
|
776
750
|
if (recursive) {
|
|
777
751
|
args.push('-p');
|
|
778
752
|
}
|
|
@@ -783,10 +757,7 @@ export class CliClient {
|
|
|
783
757
|
* Remove a file from a sandbox.
|
|
784
758
|
*/
|
|
785
759
|
async sandboxRm(sandboxId: string, remotePath: string): Promise<CliResult<void>> {
|
|
786
|
-
return this.exec<void>(
|
|
787
|
-
['cloud', 'sandbox', 'rm', sandboxId, remotePath, '--region', this.getSandboxRegion()],
|
|
788
|
-
{ format: 'json' }
|
|
789
|
-
);
|
|
760
|
+
return this.exec<void>(['cloud', 'sandbox', 'rm', sandboxId, remotePath], { format: 'json' });
|
|
790
761
|
}
|
|
791
762
|
|
|
792
763
|
/**
|
|
@@ -797,15 +768,7 @@ export class CliClient {
|
|
|
797
768
|
remotePath: string,
|
|
798
769
|
recursive = false
|
|
799
770
|
): Promise<CliResult<void>> {
|
|
800
|
-
const args = [
|
|
801
|
-
'cloud',
|
|
802
|
-
'sandbox',
|
|
803
|
-
'rmdir',
|
|
804
|
-
sandboxId,
|
|
805
|
-
remotePath,
|
|
806
|
-
'--region',
|
|
807
|
-
this.getSandboxRegion(),
|
|
808
|
-
];
|
|
771
|
+
const args = ['cloud', 'sandbox', 'rmdir', sandboxId, remotePath];
|
|
809
772
|
if (recursive) {
|
|
810
773
|
args.push('-r');
|
|
811
774
|
}
|
|
@@ -819,7 +782,7 @@ export class CliClient {
|
|
|
819
782
|
sandboxId: string,
|
|
820
783
|
vars: Record<string, string>
|
|
821
784
|
): Promise<CliResult<SandboxEnvResult>> {
|
|
822
|
-
const args = ['cloud', 'sandbox', 'env', sandboxId
|
|
785
|
+
const args = ['cloud', 'sandbox', 'env', sandboxId];
|
|
823
786
|
for (const [key, value] of Object.entries(vars)) {
|
|
824
787
|
args.push(`${key}=${value}`);
|
|
825
788
|
}
|
|
@@ -833,7 +796,7 @@ export class CliClient {
|
|
|
833
796
|
sandboxId: string,
|
|
834
797
|
varNames: string[]
|
|
835
798
|
): Promise<CliResult<SandboxEnvResult>> {
|
|
836
|
-
const args = ['cloud', 'sandbox', 'env', sandboxId
|
|
799
|
+
const args = ['cloud', 'sandbox', 'env', sandboxId];
|
|
837
800
|
for (const name of varNames) {
|
|
838
801
|
args.push('--delete', name);
|
|
839
802
|
}
|
|
@@ -844,10 +807,9 @@ export class CliClient {
|
|
|
844
807
|
* Get environment variables from a sandbox.
|
|
845
808
|
*/
|
|
846
809
|
async sandboxEnvGet(sandboxId: string): Promise<CliResult<SandboxEnvResult>> {
|
|
847
|
-
return this.exec<SandboxEnvResult>(
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
);
|
|
810
|
+
return this.exec<SandboxEnvResult>(['cloud', 'sandbox', 'env', sandboxId], {
|
|
811
|
+
format: 'json',
|
|
812
|
+
});
|
|
851
813
|
}
|
|
852
814
|
|
|
853
815
|
// ==================== Snapshot Methods ====================
|
|
@@ -856,15 +818,7 @@ export class CliClient {
|
|
|
856
818
|
* Create a snapshot of a sandbox.
|
|
857
819
|
*/
|
|
858
820
|
async snapshotCreate(sandboxId: string, tag?: string): Promise<CliResult<SnapshotInfo>> {
|
|
859
|
-
const args = [
|
|
860
|
-
'cloud',
|
|
861
|
-
'sandbox',
|
|
862
|
-
'snapshot',
|
|
863
|
-
'create',
|
|
864
|
-
sandboxId,
|
|
865
|
-
'--region',
|
|
866
|
-
this.getSandboxRegion(),
|
|
867
|
-
];
|
|
821
|
+
const args = ['cloud', 'sandbox', 'snapshot', 'create', sandboxId];
|
|
868
822
|
if (tag) {
|
|
869
823
|
args.push('--tag', tag);
|
|
870
824
|
}
|
|
@@ -875,11 +829,10 @@ export class CliClient {
|
|
|
875
829
|
* List snapshots with optional sandbox filter.
|
|
876
830
|
*/
|
|
877
831
|
async snapshotList(sandboxId?: string): Promise<CliResult<SnapshotInfo[]>> {
|
|
878
|
-
const args = ['cloud', 'sandbox', 'snapshot', 'list'
|
|
832
|
+
const args = ['cloud', 'sandbox', 'snapshot', 'list'];
|
|
879
833
|
if (sandboxId) {
|
|
880
834
|
args.push('--sandbox', sandboxId);
|
|
881
835
|
}
|
|
882
|
-
// CLI returns { snapshots: [], total: N }
|
|
883
836
|
const result = await this.exec<{ snapshots: SnapshotInfo[]; total: number }>(args, {
|
|
884
837
|
format: 'json',
|
|
885
838
|
});
|
|
@@ -893,44 +846,25 @@ export class CliClient {
|
|
|
893
846
|
* Get detailed information about a snapshot.
|
|
894
847
|
*/
|
|
895
848
|
async snapshotGet(snapshotId: string): Promise<CliResult<SnapshotInfo>> {
|
|
896
|
-
return this.exec<SnapshotInfo>(
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
);
|
|
849
|
+
return this.exec<SnapshotInfo>(['cloud', 'sandbox', 'snapshot', 'get', snapshotId], {
|
|
850
|
+
format: 'json',
|
|
851
|
+
});
|
|
900
852
|
}
|
|
901
853
|
|
|
902
854
|
/**
|
|
903
855
|
* Delete a snapshot.
|
|
904
856
|
*/
|
|
905
857
|
async snapshotDelete(snapshotId: string): Promise<CliResult<void>> {
|
|
906
|
-
return this.exec<void>(
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
'sandbox',
|
|
910
|
-
'snapshot',
|
|
911
|
-
'delete',
|
|
912
|
-
snapshotId,
|
|
913
|
-
'--confirm',
|
|
914
|
-
'--region',
|
|
915
|
-
this.getSandboxRegion(),
|
|
916
|
-
],
|
|
917
|
-
{ format: 'json' }
|
|
918
|
-
);
|
|
858
|
+
return this.exec<void>(['cloud', 'sandbox', 'snapshot', 'delete', snapshotId, '--confirm'], {
|
|
859
|
+
format: 'json',
|
|
860
|
+
});
|
|
919
861
|
}
|
|
920
862
|
|
|
921
863
|
/**
|
|
922
864
|
* Tag or untag a snapshot.
|
|
923
865
|
*/
|
|
924
866
|
async snapshotTag(snapshotId: string, tag: string | null): Promise<CliResult<void>> {
|
|
925
|
-
const args = [
|
|
926
|
-
'cloud',
|
|
927
|
-
'sandbox',
|
|
928
|
-
'snapshot',
|
|
929
|
-
'tag',
|
|
930
|
-
snapshotId,
|
|
931
|
-
'--region',
|
|
932
|
-
this.getSandboxRegion(),
|
|
933
|
-
];
|
|
867
|
+
const args = ['cloud', 'sandbox', 'snapshot', 'tag', snapshotId];
|
|
934
868
|
if (tag === null) {
|
|
935
869
|
args.push('--clear');
|
|
936
870
|
} else {
|
|
@@ -945,9 +879,8 @@ export class CliClient {
|
|
|
945
879
|
* List executions for a sandbox.
|
|
946
880
|
*/
|
|
947
881
|
async executionList(sandboxId: string): Promise<CliResult<ExecutionInfo[]>> {
|
|
948
|
-
// CLI returns { executions: [] }
|
|
949
882
|
const result = await this.exec<{ executions: ExecutionInfo[] }>(
|
|
950
|
-
['cloud', 'sandbox', 'execution', 'list', sandboxId
|
|
883
|
+
['cloud', 'sandbox', 'execution', 'list', sandboxId],
|
|
951
884
|
{ format: 'json' }
|
|
952
885
|
);
|
|
953
886
|
if (result.success && result.data) {
|
|
@@ -960,10 +893,9 @@ export class CliClient {
|
|
|
960
893
|
* Get detailed information about an execution.
|
|
961
894
|
*/
|
|
962
895
|
async executionGet(executionId: string): Promise<CliResult<ExecutionInfo>> {
|
|
963
|
-
return this.exec<ExecutionInfo>(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
);
|
|
896
|
+
return this.exec<ExecutionInfo>(['cloud', 'sandbox', 'execution', 'get', executionId], {
|
|
897
|
+
format: 'json',
|
|
898
|
+
});
|
|
967
899
|
}
|
|
968
900
|
|
|
969
901
|
dispose(): void {
|
|
@@ -1270,9 +1202,20 @@ export interface SandboxInfo {
|
|
|
1270
1202
|
resources?: SandboxResources;
|
|
1271
1203
|
stdoutStreamUrl?: string;
|
|
1272
1204
|
stderrStreamUrl?: string;
|
|
1205
|
+
// New fields from sandbox improvements
|
|
1206
|
+
name?: string;
|
|
1207
|
+
description?: string;
|
|
1208
|
+
runtimeId?: string;
|
|
1209
|
+
runtimeName?: string;
|
|
1273
1210
|
}
|
|
1274
1211
|
|
|
1275
1212
|
export interface SandboxCreateOptions {
|
|
1213
|
+
// New fields from sandbox improvements
|
|
1214
|
+
runtime?: string;
|
|
1215
|
+
runtimeId?: string;
|
|
1216
|
+
name?: string;
|
|
1217
|
+
description?: string;
|
|
1218
|
+
// Existing fields
|
|
1276
1219
|
memory?: string;
|
|
1277
1220
|
cpu?: string;
|
|
1278
1221
|
disk?: string;
|
|
@@ -1341,6 +1284,26 @@ export interface SandboxEnvResult {
|
|
|
1341
1284
|
env: Record<string, string>;
|
|
1342
1285
|
}
|
|
1343
1286
|
|
|
1287
|
+
// Sandbox runtime types
|
|
1288
|
+
export interface SandboxRuntime {
|
|
1289
|
+
id: string;
|
|
1290
|
+
name: string;
|
|
1291
|
+
description?: string;
|
|
1292
|
+
iconUrl?: string;
|
|
1293
|
+
url?: string;
|
|
1294
|
+
tags?: string[];
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
export interface SandboxRuntimeListParams {
|
|
1298
|
+
limit?: number;
|
|
1299
|
+
offset?: number;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
export interface SandboxRuntimeListResponse {
|
|
1303
|
+
runtimes: SandboxRuntime[];
|
|
1304
|
+
total: number;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1344
1307
|
// Singleton
|
|
1345
1308
|
let _cliClient: CliClient | undefined;
|
|
1346
1309
|
|
|
@@ -17,6 +17,11 @@ export interface LinkedSandbox {
|
|
|
17
17
|
linkedAt: string;
|
|
18
18
|
lastSyncedAt?: string;
|
|
19
19
|
remotePath: string;
|
|
20
|
+
// New fields from sandbox improvements
|
|
21
|
+
description?: string;
|
|
22
|
+
runtimeId?: string;
|
|
23
|
+
runtimeName?: string;
|
|
24
|
+
region?: string;
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
/**
|
|
@@ -80,11 +85,12 @@ export class SandboxManager {
|
|
|
80
85
|
throw new Error('No workspace folder open');
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
// Verify sandbox exists
|
|
88
|
+
// Verify sandbox exists and get its info
|
|
84
89
|
const result = await this.cliClient.sandboxGet(sandboxId);
|
|
85
|
-
if (!result.success) {
|
|
90
|
+
if (!result.success || !result.data) {
|
|
86
91
|
throw new Error(`Failed to verify sandbox: ${result.error}`);
|
|
87
92
|
}
|
|
93
|
+
const sandboxInfo = result.data;
|
|
88
94
|
|
|
89
95
|
const allLinked = this.context.workspaceState.get<Record<string, LinkedSandbox[]>>(
|
|
90
96
|
LINKED_SANDBOXES_KEY,
|
|
@@ -100,6 +106,7 @@ export class SandboxManager {
|
|
|
100
106
|
...workspaceLinks[existingIndex],
|
|
101
107
|
name: options.name ?? workspaceLinks[existingIndex].name,
|
|
102
108
|
remotePath: options.remotePath ?? workspaceLinks[existingIndex].remotePath,
|
|
109
|
+
region: sandboxInfo.region ?? workspaceLinks[existingIndex].region,
|
|
103
110
|
};
|
|
104
111
|
} else {
|
|
105
112
|
// Add new link
|
|
@@ -108,6 +115,7 @@ export class SandboxManager {
|
|
|
108
115
|
name: options.name,
|
|
109
116
|
linkedAt: new Date().toISOString(),
|
|
110
117
|
remotePath: options.remotePath ?? DEFAULT_SANDBOX_PATH,
|
|
118
|
+
region: sandboxInfo.region,
|
|
111
119
|
});
|
|
112
120
|
}
|
|
113
121
|
|
|
@@ -428,20 +436,53 @@ export class SandboxManager {
|
|
|
428
436
|
/**
|
|
429
437
|
* Refresh sandbox info for all linked sandboxes.
|
|
430
438
|
* Returns info about which sandboxes are still valid.
|
|
439
|
+
* Also updates linked sandbox metadata with new fields from the API.
|
|
431
440
|
*/
|
|
432
441
|
async refreshLinkedSandboxes(): Promise<Map<string, SandboxInfo | null>> {
|
|
433
|
-
const
|
|
442
|
+
const workspaceKey = this.getWorkspaceKey();
|
|
443
|
+
if (!workspaceKey) {
|
|
444
|
+
return new Map();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const allLinked = this.context.workspaceState.get<Record<string, LinkedSandbox[]>>(
|
|
448
|
+
LINKED_SANDBOXES_KEY,
|
|
449
|
+
{}
|
|
450
|
+
);
|
|
451
|
+
const workspaceLinks = allLinked[workspaceKey] || [];
|
|
434
452
|
const results = new Map<string, SandboxInfo | null>();
|
|
453
|
+
let needsUpdate = false;
|
|
435
454
|
|
|
436
|
-
for (const link of
|
|
455
|
+
for (const link of workspaceLinks) {
|
|
437
456
|
const result = await this.cliClient.sandboxGet(link.sandboxId);
|
|
438
457
|
if (result.success && result.data) {
|
|
439
|
-
|
|
458
|
+
const info = result.data;
|
|
459
|
+
results.set(link.sandboxId, info);
|
|
460
|
+
|
|
461
|
+
// Update linked sandbox with new fields from API
|
|
462
|
+
if (
|
|
463
|
+
info.name !== link.name ||
|
|
464
|
+
info.description !== link.description ||
|
|
465
|
+
info.runtimeId !== link.runtimeId ||
|
|
466
|
+
info.runtimeName !== link.runtimeName
|
|
467
|
+
) {
|
|
468
|
+
link.name = info.name ?? link.name;
|
|
469
|
+
link.description = info.description;
|
|
470
|
+
link.runtimeId = info.runtimeId;
|
|
471
|
+
link.runtimeName = info.runtimeName;
|
|
472
|
+
needsUpdate = true;
|
|
473
|
+
}
|
|
440
474
|
} else {
|
|
441
475
|
results.set(link.sandboxId, null);
|
|
442
476
|
}
|
|
443
477
|
}
|
|
444
478
|
|
|
479
|
+
// Persist updated metadata if changed
|
|
480
|
+
if (needsUpdate) {
|
|
481
|
+
allLinked[workspaceKey] = workspaceLinks;
|
|
482
|
+
await this.context.workspaceState.update(LINKED_SANDBOXES_KEY, allLinked);
|
|
483
|
+
_onLinkedSandboxesChanged.fire(workspaceLinks);
|
|
484
|
+
}
|
|
485
|
+
|
|
445
486
|
return results;
|
|
446
487
|
}
|
|
447
488
|
|
|
@@ -507,7 +507,11 @@ export class ListSandboxesTool implements vscode.LanguageModelTool<ListSandboxes
|
|
|
507
507
|
|
|
508
508
|
const sandboxes = result.data.map((s) => ({
|
|
509
509
|
id: s.sandboxId,
|
|
510
|
+
name: s.name,
|
|
511
|
+
description: s.description,
|
|
510
512
|
status: s.status,
|
|
513
|
+
runtime: s.runtimeName ?? s.runtimeId,
|
|
514
|
+
runtimeId: s.runtimeId,
|
|
511
515
|
region: s.region,
|
|
512
516
|
createdAt: s.createdAt,
|
|
513
517
|
resources: s.resources,
|
|
@@ -529,12 +533,57 @@ export class ListSandboxesTool implements vscode.LanguageModelTool<ListSandboxes
|
|
|
529
533
|
}
|
|
530
534
|
}
|
|
531
535
|
|
|
536
|
+
export interface ListSandboxRuntimesInput {
|
|
537
|
+
limit?: number;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export class ListSandboxRuntimesTool implements vscode.LanguageModelTool<ListSandboxRuntimesInput> {
|
|
541
|
+
async invoke(
|
|
542
|
+
options: vscode.LanguageModelToolInvocationOptions<ListSandboxRuntimesInput>,
|
|
543
|
+
_token: vscode.CancellationToken
|
|
544
|
+
): Promise<vscode.LanguageModelToolResult> {
|
|
545
|
+
const cli = getCliClient();
|
|
546
|
+
const limit = options.input.limit ?? 50;
|
|
547
|
+
const result = await cli.sandboxRuntimeList({ limit });
|
|
548
|
+
|
|
549
|
+
if (!result.success || !result.data) {
|
|
550
|
+
throw new Error(`Failed to list runtimes: ${result.error || 'Unknown error'}`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const output = result.data.runtimes.map((rt) => ({
|
|
554
|
+
id: rt.id,
|
|
555
|
+
name: rt.name,
|
|
556
|
+
description: rt.description,
|
|
557
|
+
tags: rt.tags,
|
|
558
|
+
url: rt.url,
|
|
559
|
+
}));
|
|
560
|
+
|
|
561
|
+
return new vscode.LanguageModelToolResult([
|
|
562
|
+
new vscode.LanguageModelTextPart(JSON.stringify(output, null, 2)),
|
|
563
|
+
]);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async prepareInvocation(
|
|
567
|
+
_options: vscode.LanguageModelToolInvocationPrepareOptions<ListSandboxRuntimesInput>,
|
|
568
|
+
_token: vscode.CancellationToken
|
|
569
|
+
): Promise<vscode.PreparedToolInvocation> {
|
|
570
|
+
return {
|
|
571
|
+
invocationMessage: 'Listing sandbox runtimes...',
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
532
576
|
export interface CreateSandboxInput {
|
|
533
577
|
memory?: string;
|
|
534
578
|
cpu?: string;
|
|
535
579
|
network?: boolean;
|
|
536
580
|
snapshot?: string;
|
|
537
581
|
dependencies?: string[];
|
|
582
|
+
// New fields
|
|
583
|
+
runtime?: string;
|
|
584
|
+
runtimeId?: string;
|
|
585
|
+
name?: string;
|
|
586
|
+
description?: string;
|
|
538
587
|
}
|
|
539
588
|
|
|
540
589
|
export class CreateSandboxTool implements vscode.LanguageModelTool<CreateSandboxInput> {
|
|
@@ -549,6 +598,10 @@ export class CreateSandboxTool implements vscode.LanguageModelTool<CreateSandbox
|
|
|
549
598
|
network: options.input.network,
|
|
550
599
|
snapshot: options.input.snapshot,
|
|
551
600
|
dependencies: options.input.dependencies,
|
|
601
|
+
runtime: options.input.runtime,
|
|
602
|
+
runtimeId: options.input.runtimeId,
|
|
603
|
+
name: options.input.name,
|
|
604
|
+
description: options.input.description,
|
|
552
605
|
};
|
|
553
606
|
|
|
554
607
|
const result = await cli.sandboxCreate(createOptions);
|
|
@@ -557,10 +610,20 @@ export class CreateSandboxTool implements vscode.LanguageModelTool<CreateSandbox
|
|
|
557
610
|
throw new Error(`Failed to create sandbox: ${result.error || 'Unknown error'}`);
|
|
558
611
|
}
|
|
559
612
|
|
|
613
|
+
const info = result.data;
|
|
614
|
+
const lines = [
|
|
615
|
+
'Sandbox created successfully!',
|
|
616
|
+
'',
|
|
617
|
+
`ID: ${info.sandboxId}`,
|
|
618
|
+
info.name ? `Name: ${info.name}` : '',
|
|
619
|
+
info.description ? `Description: ${info.description}` : '',
|
|
620
|
+
`Runtime: ${info.runtimeName ?? info.runtimeId ?? 'bun:1'}`,
|
|
621
|
+
`Status: ${info.status}`,
|
|
622
|
+
`Region: ${info.region}`,
|
|
623
|
+
].filter(Boolean);
|
|
624
|
+
|
|
560
625
|
return new vscode.LanguageModelToolResult([
|
|
561
|
-
new vscode.LanguageModelTextPart(
|
|
562
|
-
`Sandbox created successfully!\n\nID: ${result.data.sandboxId}\nStatus: ${result.data.status}\nRegion: ${result.data.region}`
|
|
563
|
-
),
|
|
626
|
+
new vscode.LanguageModelTextPart(lines.join('\n')),
|
|
564
627
|
]);
|
|
565
628
|
}
|
|
566
629
|
|
|
@@ -680,6 +743,7 @@ export class ExecuteInSandboxTool implements vscode.LanguageModelTool<ExecuteInS
|
|
|
680
743
|
export interface CreateSnapshotInput {
|
|
681
744
|
sandboxId: string;
|
|
682
745
|
tag?: string;
|
|
746
|
+
region?: string;
|
|
683
747
|
}
|
|
684
748
|
|
|
685
749
|
export class CreateSnapshotTool implements vscode.LanguageModelTool<CreateSnapshotInput> {
|
|
@@ -759,6 +823,10 @@ export function registerAgentTools(context: vscode.ExtensionContext): void {
|
|
|
759
823
|
vscode.lm.registerTool('agentuity_list_sandboxes', new ListSandboxesTool())
|
|
760
824
|
);
|
|
761
825
|
|
|
826
|
+
context.subscriptions.push(
|
|
827
|
+
vscode.lm.registerTool('agentuity_list_sandbox_runtimes', new ListSandboxRuntimesTool())
|
|
828
|
+
);
|
|
829
|
+
|
|
762
830
|
context.subscriptions.push(
|
|
763
831
|
vscode.lm.registerTool('agentuity_create_sandbox', new CreateSandboxTool())
|
|
764
832
|
);
|
|
@@ -230,7 +230,9 @@ function registerCommands(
|
|
|
230
230
|
context.subscriptions.push(
|
|
231
231
|
vscode.commands.registerCommand(
|
|
232
232
|
'agentuity.sandbox.exec',
|
|
233
|
-
async (
|
|
233
|
+
async (
|
|
234
|
+
itemOrOptions?: SandboxTreeItem | { sandboxId: string; command?: string }
|
|
235
|
+
) => {
|
|
234
236
|
let sandboxId: string | undefined;
|
|
235
237
|
let command: string | undefined;
|
|
236
238
|
|
|
@@ -455,6 +457,59 @@ function registerCommands(
|
|
|
455
457
|
|
|
456
458
|
// ==================== Command Implementations ====================
|
|
457
459
|
|
|
460
|
+
interface RuntimePickItem extends vscode.QuickPickItem {
|
|
461
|
+
runtime?: string;
|
|
462
|
+
runtimeId?: string;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Show a quick pick to select a sandbox runtime.
|
|
467
|
+
* Returns undefined if cancelled, or the selected runtime info.
|
|
468
|
+
*/
|
|
469
|
+
async function pickSandboxRuntime(): Promise<{ runtime?: string; runtimeId?: string } | undefined> {
|
|
470
|
+
const cli = getCliClient();
|
|
471
|
+
|
|
472
|
+
// Show progress while loading runtimes
|
|
473
|
+
const result = await vscode.window.withProgress(
|
|
474
|
+
{
|
|
475
|
+
location: vscode.ProgressLocation.Notification,
|
|
476
|
+
title: 'Loading runtimes...',
|
|
477
|
+
cancellable: false,
|
|
478
|
+
},
|
|
479
|
+
async () => cli.sandboxRuntimeList({ limit: 50 })
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
if (!result.success || !result.data || !result.data.runtimes.length) {
|
|
483
|
+
// Fall back to default runtime if listing fails
|
|
484
|
+
const choice = await vscode.window.showQuickPick<RuntimePickItem>(
|
|
485
|
+
[
|
|
486
|
+
{ label: 'Use default runtime (base:latest)', runtime: undefined },
|
|
487
|
+
{ label: 'Cancel', runtime: '__cancel__' },
|
|
488
|
+
],
|
|
489
|
+
{ placeHolder: 'Runtime selection (runtime list unavailable)' }
|
|
490
|
+
);
|
|
491
|
+
if (!choice || choice.runtime === '__cancel__') {
|
|
492
|
+
return undefined;
|
|
493
|
+
}
|
|
494
|
+
return {}; // no runtime specified => CLI default
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const items: RuntimePickItem[] = result.data.runtimes.map((rt) => ({
|
|
498
|
+
label: rt.name,
|
|
499
|
+
description: rt.description,
|
|
500
|
+
detail: rt.tags?.length ? rt.tags.join(', ') : undefined,
|
|
501
|
+
runtime: rt.name,
|
|
502
|
+
runtimeId: rt.id,
|
|
503
|
+
}));
|
|
504
|
+
|
|
505
|
+
const picked = await vscode.window.showQuickPick(items, {
|
|
506
|
+
placeHolder: 'Select a sandbox runtime',
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
if (!picked) return undefined;
|
|
510
|
+
return { runtime: picked.runtime, runtimeId: picked.runtimeId };
|
|
511
|
+
}
|
|
512
|
+
|
|
458
513
|
async function createSandbox(provider: SandboxTreeDataProvider): Promise<void> {
|
|
459
514
|
const config = vscode.workspace.getConfiguration('agentuity');
|
|
460
515
|
const defaultMemory = config.get<string>('sandbox.defaultMemory', '512Mi');
|
|
@@ -464,8 +519,8 @@ async function createSandbox(provider: SandboxTreeDataProvider): Promise<void> {
|
|
|
464
519
|
// Quick pick for basic vs advanced
|
|
465
520
|
const mode = await vscode.window.showQuickPick(
|
|
466
521
|
[
|
|
467
|
-
{ label: 'Quick Create', description: '
|
|
468
|
-
{ label: 'Custom', description: 'Configure resources and options' },
|
|
522
|
+
{ label: 'Quick Create', description: 'bun:1 runtime with default settings' },
|
|
523
|
+
{ label: 'Custom', description: 'Configure runtime, resources and options' },
|
|
469
524
|
],
|
|
470
525
|
{ placeHolder: 'How do you want to create the sandbox?' }
|
|
471
526
|
);
|
|
@@ -475,6 +530,28 @@ async function createSandbox(provider: SandboxTreeDataProvider): Promise<void> {
|
|
|
475
530
|
let options: SandboxCreateOptions = {};
|
|
476
531
|
|
|
477
532
|
if (mode.label === 'Custom') {
|
|
533
|
+
// Name (optional)
|
|
534
|
+
const name = await vscode.window.showInputBox({
|
|
535
|
+
prompt: 'Sandbox name (optional)',
|
|
536
|
+
placeHolder: 'e.g., my-feature-env',
|
|
537
|
+
});
|
|
538
|
+
if (name === undefined) return;
|
|
539
|
+
options.name = name || undefined;
|
|
540
|
+
|
|
541
|
+
// Description (optional)
|
|
542
|
+
const description = await vscode.window.showInputBox({
|
|
543
|
+
prompt: 'Sandbox description (optional)',
|
|
544
|
+
placeHolder: 'e.g., Sandbox for feature-xyz integration tests',
|
|
545
|
+
});
|
|
546
|
+
if (description === undefined) return;
|
|
547
|
+
options.description = description || undefined;
|
|
548
|
+
|
|
549
|
+
// Runtime selection
|
|
550
|
+
const runtimeSelection = await pickSandboxRuntime();
|
|
551
|
+
if (runtimeSelection === undefined) return;
|
|
552
|
+
options.runtime = runtimeSelection.runtime;
|
|
553
|
+
options.runtimeId = runtimeSelection.runtimeId;
|
|
554
|
+
|
|
478
555
|
// Memory
|
|
479
556
|
const memory = await vscode.window.showInputBox({
|
|
480
557
|
prompt: 'Memory limit',
|
|
@@ -513,7 +590,9 @@ async function createSandbox(provider: SandboxTreeDataProvider): Promise<void> {
|
|
|
513
590
|
options.dependencies = deps.split(/\s+/).filter(Boolean);
|
|
514
591
|
}
|
|
515
592
|
} else {
|
|
593
|
+
// Quick create uses bun:1 runtime
|
|
516
594
|
options = {
|
|
595
|
+
runtime: 'bun:1',
|
|
517
596
|
memory: defaultMemory,
|
|
518
597
|
cpu: defaultCpu,
|
|
519
598
|
network: defaultNetwork,
|
|
@@ -531,7 +610,12 @@ async function createSandbox(provider: SandboxTreeDataProvider): Promise<void> {
|
|
|
531
610
|
const result = await cli.sandboxCreate(options);
|
|
532
611
|
|
|
533
612
|
if (result.success && result.data) {
|
|
534
|
-
|
|
613
|
+
const info = result.data;
|
|
614
|
+
const displayName = info.name || info.sandboxId.slice(0, 12);
|
|
615
|
+
const runtimeDisplay = info.runtimeName ?? info.runtimeId ?? 'bun:1';
|
|
616
|
+
vscode.window.showInformationMessage(
|
|
617
|
+
`Sandbox "${displayName}" created with runtime ${runtimeDisplay}`
|
|
618
|
+
);
|
|
535
619
|
await provider.forceRefresh();
|
|
536
620
|
} else {
|
|
537
621
|
vscode.window.showErrorMessage(`Failed to create sandbox: ${result.error}`);
|
|
@@ -544,6 +628,23 @@ async function createSandboxFromSnapshot(
|
|
|
544
628
|
snapshotId: string,
|
|
545
629
|
provider: SandboxTreeDataProvider
|
|
546
630
|
): Promise<void> {
|
|
631
|
+
// Optional: prompt for name/description when creating from snapshot
|
|
632
|
+
const name = await vscode.window.showInputBox({
|
|
633
|
+
prompt: 'Sandbox name (optional)',
|
|
634
|
+
placeHolder: 'e.g., my-feature-env',
|
|
635
|
+
});
|
|
636
|
+
if (name === undefined) return;
|
|
637
|
+
|
|
638
|
+
const description = await vscode.window.showInputBox({
|
|
639
|
+
prompt: 'Sandbox description (optional)',
|
|
640
|
+
placeHolder: 'e.g., Restored from snapshot for testing',
|
|
641
|
+
});
|
|
642
|
+
if (description === undefined) return;
|
|
643
|
+
|
|
644
|
+
// Runtime selection
|
|
645
|
+
const runtimeSelection = await pickSandboxRuntime();
|
|
646
|
+
if (runtimeSelection === undefined) return;
|
|
647
|
+
|
|
547
648
|
await vscode.window.withProgress(
|
|
548
649
|
{
|
|
549
650
|
location: vscode.ProgressLocation.Notification,
|
|
@@ -552,10 +653,21 @@ async function createSandboxFromSnapshot(
|
|
|
552
653
|
},
|
|
553
654
|
async () => {
|
|
554
655
|
const cli = getCliClient();
|
|
555
|
-
const result = await cli.sandboxCreate({
|
|
656
|
+
const result = await cli.sandboxCreate({
|
|
657
|
+
snapshot: snapshotId,
|
|
658
|
+
name: name || undefined,
|
|
659
|
+
description: description || undefined,
|
|
660
|
+
runtime: runtimeSelection.runtime,
|
|
661
|
+
runtimeId: runtimeSelection.runtimeId,
|
|
662
|
+
});
|
|
556
663
|
|
|
557
664
|
if (result.success && result.data) {
|
|
558
|
-
|
|
665
|
+
const info = result.data;
|
|
666
|
+
const displayName = info.name || info.sandboxId.slice(0, 12);
|
|
667
|
+
const runtimeDisplay = info.runtimeName ?? info.runtimeId ?? 'bun:1';
|
|
668
|
+
vscode.window.showInformationMessage(
|
|
669
|
+
`Sandbox "${displayName}" created from snapshot with runtime ${runtimeDisplay}`
|
|
670
|
+
);
|
|
559
671
|
await provider.forceRefresh();
|
|
560
672
|
} else {
|
|
561
673
|
vscode.window.showErrorMessage(`Failed to create sandbox: ${result.error}`);
|
|
@@ -702,9 +814,7 @@ function executeInTerminal(sandboxId: string, command: string): void {
|
|
|
702
814
|
}
|
|
703
815
|
|
|
704
816
|
terminal.show();
|
|
705
|
-
terminal.sendText(
|
|
706
|
-
`${cliPath} cloud sandbox exec ${sandboxId} --region ${cli.getSandboxRegion()} -- ${command}`
|
|
707
|
-
);
|
|
817
|
+
terminal.sendText(`${cliPath} cloud sandbox exec ${sandboxId} -- ${command}`);
|
|
708
818
|
}
|
|
709
819
|
|
|
710
820
|
function disposeTerminals(): void {
|
|
@@ -723,22 +833,19 @@ async function viewSandboxFile(sandboxId: string, filePath: string): Promise<voi
|
|
|
723
833
|
},
|
|
724
834
|
async () => {
|
|
725
835
|
const cli = getCliClient();
|
|
726
|
-
// Use a stable temp directory for sandbox files
|
|
727
836
|
const sandboxTmpDir = path.join(os.tmpdir(), 'agentuity-sandbox', sandboxId.slice(0, 12));
|
|
728
837
|
fs.mkdirSync(sandboxTmpDir, { recursive: true });
|
|
729
838
|
|
|
730
839
|
const fileName = path.basename(filePath);
|
|
731
840
|
const localPath = path.join(sandboxTmpDir, fileName);
|
|
732
841
|
|
|
733
|
-
// Build full remote path under sandbox home
|
|
734
842
|
const fullRemotePath = filePath.startsWith('/')
|
|
735
843
|
? filePath
|
|
736
844
|
: `${CliClient.SANDBOX_HOME}/${filePath}`;
|
|
737
845
|
|
|
738
|
-
const result = await cli.sandboxCpFromSandbox(sandboxId, fullRemotePath, localPath);
|
|
846
|
+
const result = await cli.sandboxCpFromSandbox(sandboxId, fullRemotePath, localPath, false);
|
|
739
847
|
|
|
740
848
|
if (result.success) {
|
|
741
|
-
// Track this file for save-back
|
|
742
849
|
sandboxFileMap.set(localPath, {
|
|
743
850
|
sandboxId,
|
|
744
851
|
remotePath: fullRemotePath,
|
|
@@ -760,7 +867,7 @@ async function uploadSavedFile(
|
|
|
760
867
|
provider: SandboxTreeDataProvider
|
|
761
868
|
): Promise<void> {
|
|
762
869
|
const cli = getCliClient();
|
|
763
|
-
const result = await cli.sandboxCpToSandbox(sandboxId, localPath, remotePath);
|
|
870
|
+
const result = await cli.sandboxCpToSandbox(sandboxId, localPath, remotePath, false);
|
|
764
871
|
|
|
765
872
|
if (result.success) {
|
|
766
873
|
vscode.window.showInformationMessage(`Saved to sandbox: ${path.basename(remotePath)}`);
|
|
@@ -1057,8 +1164,7 @@ async function viewEnv(sandboxId: string): Promise<void> {
|
|
|
1057
1164
|
},
|
|
1058
1165
|
async () => {
|
|
1059
1166
|
const cli = getCliClient();
|
|
1060
|
-
|
|
1061
|
-
const result = await cli.sandboxExec(sandboxId, ['env']);
|
|
1167
|
+
const result = await cli.sandboxExec(sandboxId, ['env'], {});
|
|
1062
1168
|
|
|
1063
1169
|
if (result.success && result.data) {
|
|
1064
1170
|
const content = result.data.output || '(no environment variables)';
|
|
@@ -1332,32 +1438,42 @@ async function downloadFile(url: string, destPath: string): Promise<void> {
|
|
|
1332
1438
|
}
|
|
1333
1439
|
|
|
1334
1440
|
async function uploadToSandbox(uri: vscode.Uri): Promise<void> {
|
|
1335
|
-
const
|
|
1441
|
+
const cli = getCliClient();
|
|
1336
1442
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1443
|
+
// Fetch all sandboxes
|
|
1444
|
+
const listResult = await vscode.window.withProgress(
|
|
1445
|
+
{
|
|
1446
|
+
location: vscode.ProgressLocation.Notification,
|
|
1447
|
+
title: 'Loading sandboxes...',
|
|
1448
|
+
cancellable: false,
|
|
1449
|
+
},
|
|
1450
|
+
async () => cli.sandboxList()
|
|
1451
|
+
);
|
|
1452
|
+
|
|
1453
|
+
if (!listResult.success || !listResult.data || listResult.data.length === 0) {
|
|
1454
|
+
vscode.window.showWarningMessage('No sandboxes available. Create a sandbox first.');
|
|
1339
1455
|
return;
|
|
1340
1456
|
}
|
|
1341
1457
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1458
|
+
const sandboxes = listResult.data;
|
|
1459
|
+
|
|
1460
|
+
// Pick a sandbox
|
|
1461
|
+
const picked = await vscode.window.showQuickPick(
|
|
1462
|
+
sandboxes.map((s) => ({
|
|
1463
|
+
label: s.name || s.sandboxId.slice(0, 12),
|
|
1464
|
+
description: `${s.status} · ${s.runtimeName ?? s.runtimeId ?? 'base'}`,
|
|
1465
|
+
detail: s.sandboxId,
|
|
1466
|
+
sandbox: s,
|
|
1467
|
+
})),
|
|
1468
|
+
{ placeHolder: 'Select sandbox to upload to' }
|
|
1469
|
+
);
|
|
1470
|
+
|
|
1471
|
+
if (!picked) return;
|
|
1472
|
+
const selectedSandbox = picked.sandbox;
|
|
1357
1473
|
|
|
1358
1474
|
const remotePath = await vscode.window.showInputBox({
|
|
1359
1475
|
prompt: 'Remote path',
|
|
1360
|
-
value:
|
|
1476
|
+
value: DEFAULT_SANDBOX_PATH,
|
|
1361
1477
|
});
|
|
1362
1478
|
|
|
1363
1479
|
if (!remotePath) return;
|
|
@@ -1369,11 +1485,21 @@ async function uploadToSandbox(uri: vscode.Uri): Promise<void> {
|
|
|
1369
1485
|
cancellable: false,
|
|
1370
1486
|
},
|
|
1371
1487
|
async () => {
|
|
1372
|
-
const cli = getCliClient();
|
|
1373
1488
|
const stats = await vscode.workspace.fs.stat(uri);
|
|
1374
1489
|
const isDir = stats.type === vscode.FileType.Directory;
|
|
1375
1490
|
|
|
1376
|
-
|
|
1491
|
+
// Build full remote path including filename
|
|
1492
|
+
const fileName = path.basename(uri.fsPath);
|
|
1493
|
+
const fullRemotePath = remotePath.endsWith('/')
|
|
1494
|
+
? `${remotePath}${fileName}`
|
|
1495
|
+
: `${remotePath}/${fileName}`;
|
|
1496
|
+
|
|
1497
|
+
const result = await cli.sandboxCpToSandbox(
|
|
1498
|
+
selectedSandbox.sandboxId,
|
|
1499
|
+
uri.fsPath,
|
|
1500
|
+
fullRemotePath,
|
|
1501
|
+
isDir
|
|
1502
|
+
);
|
|
1377
1503
|
|
|
1378
1504
|
if (result.success) {
|
|
1379
1505
|
vscode.window.showInformationMessage(`Uploaded to ${remotePath}`);
|
|
@@ -39,7 +39,8 @@ export class SandboxTreeItem extends vscode.TreeItem {
|
|
|
39
39
|
public readonly parentSandboxId?: string,
|
|
40
40
|
public readonly categoryType?: 'files' | 'snapshots' | 'executions',
|
|
41
41
|
public readonly linkedData?: LinkedSandbox,
|
|
42
|
-
public readonly filePath?: string
|
|
42
|
+
public readonly filePath?: string,
|
|
43
|
+
public readonly region?: string
|
|
43
44
|
) {
|
|
44
45
|
super(label, collapsibleState);
|
|
45
46
|
this.setupItem();
|
|
@@ -92,9 +93,14 @@ export class SandboxTreeItem extends vscode.TreeItem {
|
|
|
92
93
|
}
|
|
93
94
|
this.contextValue = contextValue;
|
|
94
95
|
|
|
95
|
-
// Set description
|
|
96
|
+
// Set description with status and runtime
|
|
96
97
|
const statusLabel = status.charAt(0).toUpperCase() + status.slice(1);
|
|
97
|
-
|
|
98
|
+
const runtimeLabel = this.sandboxData.runtimeName ?? this.sandboxData.runtimeId ?? 'bun:1';
|
|
99
|
+
let desc = `${statusLabel} · ${runtimeLabel}`;
|
|
100
|
+
if (isLinked) {
|
|
101
|
+
desc += ' [linked]';
|
|
102
|
+
}
|
|
103
|
+
this.description = desc;
|
|
98
104
|
|
|
99
105
|
// Set tooltip
|
|
100
106
|
this.tooltip = this.formatSandboxTooltip();
|
|
@@ -135,12 +141,27 @@ export class SandboxTreeItem extends vscode.TreeItem {
|
|
|
135
141
|
private formatSandboxTooltip(): string {
|
|
136
142
|
if (!this.sandboxData) return '';
|
|
137
143
|
|
|
138
|
-
const lines = [
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
`
|
|
143
|
-
|
|
144
|
+
const lines: string[] = [];
|
|
145
|
+
|
|
146
|
+
// Name and description first if present
|
|
147
|
+
if (this.sandboxData.name) {
|
|
148
|
+
lines.push(`Name: ${this.sandboxData.name}`);
|
|
149
|
+
}
|
|
150
|
+
if (this.sandboxData.description) {
|
|
151
|
+
lines.push(`Description: ${this.sandboxData.description}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
lines.push(`ID: ${this.sandboxData.sandboxId}`);
|
|
155
|
+
lines.push(`Status: ${this.sandboxData.status}`);
|
|
156
|
+
|
|
157
|
+
// Runtime info
|
|
158
|
+
const runtimeDisplay = this.sandboxData.runtimeName ?? this.sandboxData.runtimeId ?? 'bun:1';
|
|
159
|
+
lines.push(`Runtime: ${runtimeDisplay}`);
|
|
160
|
+
|
|
161
|
+
if (this.sandboxData.region) {
|
|
162
|
+
lines.push(`Region: ${this.sandboxData.region}`);
|
|
163
|
+
}
|
|
164
|
+
lines.push(`Created: ${new Date(this.sandboxData.createdAt).toLocaleString()}`);
|
|
144
165
|
|
|
145
166
|
if (this.sandboxData.resources) {
|
|
146
167
|
const r = this.sandboxData.resources;
|
|
@@ -382,20 +403,21 @@ export class SandboxTreeDataProvider implements vscode.TreeDataProvider<SandboxT
|
|
|
382
403
|
|
|
383
404
|
// Category children
|
|
384
405
|
if (element.itemType === 'category' && element.parentSandboxId) {
|
|
406
|
+
const region = element.sandboxData?.region;
|
|
385
407
|
switch (element.categoryType) {
|
|
386
408
|
case 'files':
|
|
387
409
|
// Pass undefined for root listing (CLI defaults to sandbox home)
|
|
388
|
-
return this.getFilesChildren(element.parentSandboxId, undefined);
|
|
410
|
+
return this.getFilesChildren(element.parentSandboxId, undefined, region);
|
|
389
411
|
case 'snapshots':
|
|
390
|
-
return this.getSnapshotsChildren(element.parentSandboxId);
|
|
412
|
+
return this.getSnapshotsChildren(element.parentSandboxId, region);
|
|
391
413
|
case 'executions':
|
|
392
|
-
return this.getExecutionsChildren(element.parentSandboxId);
|
|
414
|
+
return this.getExecutionsChildren(element.parentSandboxId, region);
|
|
393
415
|
}
|
|
394
416
|
}
|
|
395
417
|
|
|
396
418
|
// Directory children
|
|
397
419
|
if (element.itemType === 'directory' && element.parentSandboxId && element.filePath) {
|
|
398
|
-
return this.getFilesChildren(element.parentSandboxId, element.filePath);
|
|
420
|
+
return this.getFilesChildren(element.parentSandboxId, element.filePath, element.region);
|
|
399
421
|
}
|
|
400
422
|
|
|
401
423
|
// Snapshot children (files from snapshot get)
|
|
@@ -469,7 +491,11 @@ export class SandboxTreeDataProvider implements vscode.TreeDataProvider<SandboxT
|
|
|
469
491
|
// Add sandbox items
|
|
470
492
|
for (const sandbox of this.sandboxes) {
|
|
471
493
|
const linked = linkedSandboxes.find((l) => l.sandboxId === sandbox.sandboxId);
|
|
472
|
-
|
|
494
|
+
// Prefer server-side name, then linked alias, then short ID
|
|
495
|
+
const displayName =
|
|
496
|
+
sandbox.name && sandbox.name.trim().length > 0
|
|
497
|
+
? sandbox.name
|
|
498
|
+
: linked?.name || sandbox.sandboxId.slice(0, 12);
|
|
473
499
|
|
|
474
500
|
items.push(
|
|
475
501
|
new SandboxTreeItem(
|
|
@@ -552,15 +578,18 @@ export class SandboxTreeDataProvider implements vscode.TreeDataProvider<SandboxT
|
|
|
552
578
|
];
|
|
553
579
|
}
|
|
554
580
|
|
|
555
|
-
private async getFilesChildren(
|
|
581
|
+
private async getFilesChildren(
|
|
582
|
+
sandboxId: string,
|
|
583
|
+
dirPath?: string,
|
|
584
|
+
region?: string
|
|
585
|
+
): Promise<SandboxTreeItem[]> {
|
|
556
586
|
// Always fetch from root to get full file list, then filter
|
|
557
587
|
const cacheKey = `${sandboxId}:root`;
|
|
558
588
|
|
|
559
589
|
// Check cache first - always cache from root
|
|
560
590
|
if (!this.filesCache.has(cacheKey)) {
|
|
561
591
|
const cli = getCliClient();
|
|
562
|
-
|
|
563
|
-
const result = await cli.sandboxLs(sandboxId);
|
|
592
|
+
const result = await cli.sandboxLs(sandboxId, undefined);
|
|
564
593
|
|
|
565
594
|
if (result.success && result.data) {
|
|
566
595
|
this.filesCache.set(cacheKey, result.data);
|
|
@@ -621,12 +650,16 @@ export class SandboxTreeDataProvider implements vscode.TreeDataProvider<SandboxT
|
|
|
621
650
|
sandboxId,
|
|
622
651
|
undefined,
|
|
623
652
|
undefined,
|
|
624
|
-
file.path // Use the full path from CLI
|
|
653
|
+
file.path, // Use the full path from CLI
|
|
654
|
+
region // Pass region to child items
|
|
625
655
|
);
|
|
626
656
|
});
|
|
627
657
|
}
|
|
628
658
|
|
|
629
|
-
private async getSnapshotsChildren(
|
|
659
|
+
private async getSnapshotsChildren(
|
|
660
|
+
sandboxId: string,
|
|
661
|
+
region?: string
|
|
662
|
+
): Promise<SandboxTreeItem[]> {
|
|
630
663
|
if (!this.snapshotsCache.has(sandboxId)) {
|
|
631
664
|
const cli = getCliClient();
|
|
632
665
|
const result = await cli.snapshotList(sandboxId);
|
|
@@ -663,7 +696,11 @@ export class SandboxTreeDataProvider implements vscode.TreeDataProvider<SandboxT
|
|
|
663
696
|
undefined,
|
|
664
697
|
snap,
|
|
665
698
|
undefined,
|
|
666
|
-
sandboxId
|
|
699
|
+
sandboxId,
|
|
700
|
+
undefined,
|
|
701
|
+
undefined,
|
|
702
|
+
undefined,
|
|
703
|
+
region
|
|
667
704
|
)
|
|
668
705
|
);
|
|
669
706
|
}
|
|
@@ -720,7 +757,10 @@ export class SandboxTreeDataProvider implements vscode.TreeDataProvider<SandboxT
|
|
|
720
757
|
});
|
|
721
758
|
}
|
|
722
759
|
|
|
723
|
-
private async getExecutionsChildren(
|
|
760
|
+
private async getExecutionsChildren(
|
|
761
|
+
sandboxId: string,
|
|
762
|
+
region?: string
|
|
763
|
+
): Promise<SandboxTreeItem[]> {
|
|
724
764
|
if (!this.executionsCache.has(sandboxId)) {
|
|
725
765
|
const cli = getCliClient();
|
|
726
766
|
const result = await cli.executionList(sandboxId);
|
|
@@ -756,7 +796,11 @@ export class SandboxTreeDataProvider implements vscode.TreeDataProvider<SandboxT
|
|
|
756
796
|
undefined,
|
|
757
797
|
undefined,
|
|
758
798
|
exec,
|
|
759
|
-
sandboxId
|
|
799
|
+
sandboxId,
|
|
800
|
+
undefined,
|
|
801
|
+
undefined,
|
|
802
|
+
undefined,
|
|
803
|
+
region
|
|
760
804
|
)
|
|
761
805
|
);
|
|
762
806
|
}
|
|
@@ -40,8 +40,20 @@ export function updateStatusBar(): void {
|
|
|
40
40
|
} else if (linked.length === 1) {
|
|
41
41
|
const sandbox = linked[0];
|
|
42
42
|
const name = sandbox.name || sandbox.sandboxId.slice(0, 8);
|
|
43
|
+
const runtime = sandbox.runtimeName ?? sandbox.runtimeId ?? 'bun:1';
|
|
43
44
|
statusBarItem.text = `$(vm) ${name}`;
|
|
44
|
-
|
|
45
|
+
|
|
46
|
+
const tooltipLines = [
|
|
47
|
+
`Sandbox: ${sandbox.sandboxId}`,
|
|
48
|
+
`Runtime: ${runtime}`,
|
|
49
|
+
`Linked: ${new Date(sandbox.linkedAt).toLocaleDateString()}`,
|
|
50
|
+
`Last sync: ${sandbox.lastSyncedAt ? new Date(sandbox.lastSyncedAt).toLocaleString() : 'Never'}`,
|
|
51
|
+
];
|
|
52
|
+
if (sandbox.description) {
|
|
53
|
+
tooltipLines.unshift(`Description: ${sandbox.description}`);
|
|
54
|
+
}
|
|
55
|
+
tooltipLines.push('', 'Click for sandbox options');
|
|
56
|
+
statusBarItem.tooltip = tooltipLines.join('\n');
|
|
45
57
|
statusBarItem.backgroundColor = undefined;
|
|
46
58
|
} else {
|
|
47
59
|
statusBarItem.text = `$(vm) ${linked.length} Sandboxes`;
|
|
@@ -125,9 +137,10 @@ async function showSandboxQuickPick(): Promise<void> {
|
|
|
125
137
|
|
|
126
138
|
for (const sandbox of linked) {
|
|
127
139
|
const name = sandbox.name || sandbox.sandboxId.slice(0, 8);
|
|
140
|
+
const runtime = sandbox.runtimeName ?? sandbox.runtimeId ?? 'bun:1';
|
|
128
141
|
items.push({
|
|
129
142
|
label: `$(vm) ${name}`,
|
|
130
|
-
description: sandbox.sandboxId,
|
|
143
|
+
description: `${runtime} · ${sandbox.sandboxId.slice(0, 8)}`,
|
|
131
144
|
detail: sandbox.lastSyncedAt
|
|
132
145
|
? `Last synced: ${new Date(sandbox.lastSyncedAt).toLocaleString()}`
|
|
133
146
|
: 'Never synced',
|