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 CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "agentuity-vscode",
3
3
  "displayName": "Agentuity VSCode Extension",
4
4
  "description": "Build, deploy, and manage AI agents with Agentuity",
5
- "version": "0.1.9",
5
+ "version": "0.1.11",
6
6
  "publisher": "agentuity",
7
7
  "license": "Apache-2.0",
8
8
  "repository": {
@@ -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>(this.withRegion(['cloud', 'db', 'list']), {
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>(this.withRegion(['cloud', 'db', 'get', name]), {
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[]>(this.withRegion(args), { format: 'json', timeout: 60000 });
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>(this.withRegion(['cloud', 'storage', 'list']), {
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>(this.withRegion(args), { format: 'json' });
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
- this.withRegion(['cloud', 'storage', 'download', bucket, filename, '--metadata']),
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 args = ['cloud', 'sandbox', 'create', '--region', this.getSandboxRegion()];
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', '--region', this.getSandboxRegion()];
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
- // CLI returns { sandboxes: [...], total: N }, extract the array
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
- 'cloud',
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, '--region', this.getSandboxRegion()];
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', '--region', this.getSandboxRegion());
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, // Extract filename from 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', '--region', this.getSandboxRegion()];
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', '--region', this.getSandboxRegion()];
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, '--region', this.getSandboxRegion()];
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, '--region', this.getSandboxRegion()];
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
- ['cloud', 'sandbox', 'env', sandboxId, '--region', this.getSandboxRegion()],
849
- { format: 'json' }
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', '--region', this.getSandboxRegion()];
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
- ['cloud', 'sandbox', 'snapshot', 'get', snapshotId, '--region', this.getSandboxRegion()],
898
- { format: 'json' }
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
- 'cloud',
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, '--region', this.getSandboxRegion()],
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
- ['cloud', 'sandbox', 'execution', 'get', executionId, '--region', this.getSandboxRegion()],
965
- { format: 'json' }
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 linked = this.getLinkedSandboxes();
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 linked) {
455
+ for (const link of workspaceLinks) {
437
456
  const result = await this.cliClient.sandboxGet(link.sandboxId);
438
457
  if (result.success && result.data) {
439
- results.set(link.sandboxId, result.data);
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 (itemOrOptions?: SandboxTreeItem | { sandboxId: string; command?: string }) => {
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: 'Use default settings' },
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
- vscode.window.showInformationMessage(`Sandbox created: ${result.data.sandboxId}`);
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({ snapshot: snapshotId });
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
- vscode.window.showInformationMessage(`Sandbox created: ${result.data.sandboxId}`);
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
- // Use exec to run 'env' command to get actual runtime environment
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 linked = getSandboxManager().getLinkedSandboxes();
1441
+ const cli = getCliClient();
1336
1442
 
1337
- if (linked.length === 0) {
1338
- vscode.window.showWarningMessage('No sandbox linked. Link a sandbox first.');
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
- let sandboxId: string;
1343
- if (linked.length === 1) {
1344
- sandboxId = linked[0].sandboxId;
1345
- } else {
1346
- const picked = await vscode.window.showQuickPick(
1347
- linked.map((l) => ({
1348
- label: l.name || l.sandboxId,
1349
- description: l.sandboxId,
1350
- sandboxId: l.sandboxId,
1351
- })),
1352
- { placeHolder: 'Select sandbox to upload to' }
1353
- );
1354
- if (!picked) return;
1355
- sandboxId = picked.sandboxId;
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: linked.find((l) => l.sandboxId === sandboxId)?.remotePath || DEFAULT_SANDBOX_PATH,
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
- const result = await cli.sandboxCpToSandbox(sandboxId, uri.fsPath, remotePath, isDir);
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
- this.description = isLinked ? `${statusLabel} [linked]` : statusLabel;
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
- `ID: ${this.sandboxData.sandboxId}`,
140
- `Status: ${this.sandboxData.status}`,
141
- `Region: ${this.sandboxData.region}`,
142
- `Created: ${new Date(this.sandboxData.createdAt).toLocaleString()}`,
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
- const displayName = linked?.name || sandbox.sandboxId;
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(sandboxId: string, dirPath?: string): Promise<SandboxTreeItem[]> {
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
- // Always fetch from root (no path) to get complete file list
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(sandboxId: string): Promise<SandboxTreeItem[]> {
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(sandboxId: string): Promise<SandboxTreeItem[]> {
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
- statusBarItem.tooltip = `Sandbox: ${sandbox.sandboxId}\nLinked: ${new Date(sandbox.linkedAt).toLocaleDateString()}\nLast sync: ${sandbox.lastSyncedAt ? new Date(sandbox.lastSyncedAt).toLocaleString() : 'Never'}\n\nClick for sandbox options`;
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',