@vybestack/llxprt-code 0.10.0-nightly.260724.a20e30f14 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ Get started immediately with powerful LLM options:
23
23
  # Your Claude Pro / Max subscription
24
24
  /auth anthropic enable
25
25
  /provider anthropic
26
- /model claude-opus-4-8
26
+ /model claude-opus-5
27
27
 
28
28
  # Your ChatGPT Plus / Pro subscription (Codex)
29
29
  /auth codex enable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vybestack/llxprt-code",
3
- "version": "0.10.0-nightly.260724.a20e30f14",
3
+ "version": "0.10.0",
4
4
  "description": "LLxprt Code",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -44,24 +44,24 @@
44
44
  "!**/__snapshots__/**"
45
45
  ],
46
46
  "config": {
47
- "sandboxImageUri": "ghcr.io/vybestack/llxprt-code/sandbox:0.10.0-nightly.260724.a20e30f14"
47
+ "sandboxImageUri": "ghcr.io/vybestack/llxprt-code/sandbox:0.10.0"
48
48
  },
49
49
  "dependencies": {
50
- "@vybestack/llxprt-code-storage": "0.10.0-nightly.260724.a20e30f14",
50
+ "@vybestack/llxprt-code-storage": "0.10.0",
51
51
  "@agentclientprotocol/sdk": "^0.14.1",
52
52
  "@anthropic-ai/sdk": "^0.55.1",
53
53
  "@dqbd/tiktoken": "^1.0.21",
54
54
  "@iarna/toml": "^2.2.5",
55
55
  "@modelcontextprotocol/sdk": "^1.25.2",
56
- "@vybestack/llxprt-code-auth": "0.10.0-nightly.260724.a20e30f14",
57
- "@vybestack/llxprt-code-core": "0.10.0-nightly.260724.a20e30f14",
58
- "@vybestack/llxprt-code-ide-integration": "0.10.0-nightly.260724.a20e30f14",
59
- "@vybestack/llxprt-code-mcp": "0.10.0-nightly.260724.a20e30f14",
60
- "@vybestack/llxprt-code-providers": "0.10.0-nightly.260724.a20e30f14",
61
- "@vybestack/llxprt-code-agents": "0.10.0-nightly.260724.a20e30f14",
62
- "@vybestack/llxprt-code-settings": "0.10.0-nightly.260724.a20e30f14",
63
- "@vybestack/llxprt-code-telemetry": "0.10.0-nightly.260724.a20e30f14",
64
- "@vybestack/llxprt-code-tools": "0.10.0-nightly.260724.a20e30f14",
56
+ "@vybestack/llxprt-code-auth": "0.10.0",
57
+ "@vybestack/llxprt-code-core": "0.10.0",
58
+ "@vybestack/llxprt-code-ide-integration": "0.10.0",
59
+ "@vybestack/llxprt-code-mcp": "0.10.0",
60
+ "@vybestack/llxprt-code-providers": "0.10.0",
61
+ "@vybestack/llxprt-code-agents": "0.10.0",
62
+ "@vybestack/llxprt-code-settings": "0.10.0",
63
+ "@vybestack/llxprt-code-telemetry": "0.10.0",
64
+ "@vybestack/llxprt-code-tools": "0.10.0",
65
65
  "ansi-regex": "^6.2.2",
66
66
  "bun": "1.3.14",
67
67
  "chalk": "^5.3.0",
@@ -1,3 +1,3 @@
1
1
  {
2
- "commit": "a20e30f14"
2
+ "commit": "2d228fb17"
3
3
  }
@@ -370,6 +370,7 @@ function buildUIActionsParams(r: HookResults) {
370
370
  closeProfileDetailDialog: d.closeProfileDetailDialog,
371
371
  loadProfileFromDetail: d.loadProfileFromDetail,
372
372
  deleteProfileFromDetail: d.deleteProfileFromDetail,
373
+ deleteProfileFromList: d.deleteProfileFromList,
373
374
  setProfileAsDefault: d.setProfileAsDefault,
374
375
  openProfileEditor: d.openProfileEditor,
375
376
  closeProfileEditor: d.closeProfileEditor,
@@ -79,7 +79,7 @@ function formatPolicyOutput(
79
79
  (a, b) => (b.priority ?? 0) - (a.priority ?? 0),
80
80
  );
81
81
 
82
- const lines: string[] = ['Active Policy Rules:', ''];
82
+ const lines: string[] = ['Configured Policy Rules:', ''];
83
83
 
84
84
  const tierBands = new Map<string, typeof sortedRules>();
85
85
 
@@ -136,7 +136,7 @@ function formatPolicyOutput(
136
136
  }
137
137
 
138
138
  /**
139
- * Handle /policies command — displays active policy rules.
139
+ * Handle /policies command — displays configured policy rules.
140
140
  * Prefers the agent.policy facade; falls back to config.getPolicyEngine()
141
141
  * when the agent is null (tracked migration debt).
142
142
  */
@@ -180,7 +180,7 @@ function handlePoliciesCommand(
180
180
 
181
181
  export const policiesCommand: SlashCommand = {
182
182
  name: 'policies',
183
- description: 'display active policy rules and their priorities',
183
+ description: 'display configured policy rules and their priorities',
184
184
  kind: CommandKind.BUILT_IN,
185
185
  autoExecute: true,
186
186
  action: (context: CommandContext, args: string): MessageActionReturn | void =>
@@ -416,6 +416,7 @@ function renderProfileListDialogView(
416
416
  onSelect={uiActions.loadProfileFromDetail}
417
417
  onClose={uiActions.closeProfileListDialog}
418
418
  onViewDetail={uiActions.viewProfileDetail}
419
+ onDelete={uiActions.deleteProfileFromList}
419
420
  isLoading={uiState.profileDialogLoading}
420
421
  defaultProfileName={uiState.defaultProfileName ?? undefined}
421
422
  activeProfileName={uiState.activeProfileName ?? undefined}
@@ -13,6 +13,7 @@ export const PROVIDER_OPTIONS: ProviderOption[] = [
13
13
  needsBaseUrl: false,
14
14
  supportsOAuth: true,
15
15
  knownModels: [
16
+ 'claude-opus-5',
16
17
  'claude-sonnet-5',
17
18
  'claude-sonnet-4-5-20250929',
18
19
  'claude-haiku-4-5-20251001',
@@ -27,6 +27,7 @@ interface ProfileListDialogProps {
27
27
  onSelect: (profileName: string) => void;
28
28
  onClose: () => void;
29
29
  onViewDetail: (profileName: string) => void;
30
+ onDelete: (profileName: string) => void;
30
31
  isLoading?: boolean;
31
32
  defaultProfileName?: string;
32
33
  activeProfileName?: string;
@@ -102,7 +103,6 @@ const ProfileItem: React.FC<{
102
103
 
103
104
  function handleSearchModeKeys(
104
105
  key: { name?: string; sequence?: string; ctrl?: boolean; meta?: boolean },
105
- isNarrow: boolean,
106
106
  setSearchTerm: React.Dispatch<React.SetStateAction<string>>,
107
107
  setIsSearching: React.Dispatch<React.SetStateAction<boolean>>,
108
108
  onViewDetail: (name: string) => void,
@@ -111,15 +111,11 @@ function handleSearchModeKeys(
111
111
  ): void {
112
112
  if (key.name === 'return') {
113
113
  if (filteredProfiles.length > 0) {
114
- if (isNarrow) {
115
- onViewDetail(filteredProfiles[selectedIndex].name);
116
- return;
117
- }
118
- setIsSearching(false);
114
+ onViewDetail(filteredProfiles[selectedIndex].name);
119
115
  }
120
116
  return;
121
117
  }
122
- if (key.name === 'tab' && !isNarrow) {
118
+ if (key.name === 'tab') {
123
119
  setIsSearching(false);
124
120
  return;
125
121
  }
@@ -151,6 +147,7 @@ function handleNavModeKeys(
151
147
  onViewDetail: (name: string) => void,
152
148
  onSelect: (name: string) => void,
153
149
  setIsSearching: React.Dispatch<React.SetStateAction<boolean>>,
150
+ requestDelete: (name: string) => void,
154
151
  columns: number,
155
152
  ): void {
156
153
  if (key.name === 'return' && filteredProfiles.length > 0) {
@@ -162,10 +159,14 @@ function handleNavModeKeys(
162
159
  return;
163
160
  }
164
161
  if (filteredProfiles.length === 0) return;
165
- if (key.sequence === 'l' && filteredProfiles.length > 0) {
162
+ if (key.sequence === 'l') {
166
163
  onSelect(filteredProfiles[index].name);
167
164
  return;
168
165
  }
166
+ if (key.sequence === 'd') {
167
+ requestDelete(filteredProfiles[index].name);
168
+ return;
169
+ }
169
170
  if (key.name === 'left') move(-1);
170
171
  else if (key.name === 'right') move(1);
171
172
  else if (key.name === 'up') move(-columns);
@@ -175,11 +176,49 @@ function handleNavModeKeys(
175
176
  else if (key.sequence === 'h') move(-1);
176
177
  }
177
178
 
179
+ const DeleteConfirmationBanner: React.FC<{
180
+ confirmDeleteName: string | null;
181
+ activeProfileName?: string;
182
+ defaultProfileName?: string;
183
+ }> = ({ confirmDeleteName, activeProfileName, defaultProfileName }) => {
184
+ if (!confirmDeleteName) {
185
+ return null;
186
+ }
187
+
188
+ const isActive = confirmDeleteName === activeProfileName;
189
+ const isDefault = confirmDeleteName === defaultProfileName;
190
+ const qualifiers = [
191
+ isActive ? 'active' : null,
192
+ isDefault ? 'default' : null,
193
+ ].filter(Boolean);
194
+ const qualifierText =
195
+ qualifiers.length > 0 ? ` (${qualifiers.join(', ')})` : '';
196
+
197
+ return (
198
+ <Box marginTop={1}>
199
+ <Text color={SemanticColors.status.warning}>
200
+ Delete profile &apos;{confirmDeleteName}&apos;{qualifierText}? Press y
201
+ to confirm, n or Esc to cancel.
202
+ </Text>
203
+ </Box>
204
+ );
205
+ };
206
+
178
207
  const NarrowContent: React.FC<{
179
208
  searchTerm: string;
180
209
  filteredProfiles: ProfileListItem[];
181
210
  grid: React.ReactNode[];
182
- }> = ({ searchTerm, filteredProfiles, grid }) => (
211
+ confirmDeleteName: string | null;
212
+ activeProfileName?: string;
213
+ defaultProfileName?: string;
214
+ }> = ({
215
+ searchTerm,
216
+ filteredProfiles,
217
+ grid,
218
+ confirmDeleteName,
219
+ activeProfileName,
220
+ defaultProfileName,
221
+ }) => (
183
222
  <Box flexDirection="column">
184
223
  <Text bold color={SemanticColors.text.primary}>
185
224
  Profiles
@@ -193,7 +232,8 @@ const NarrowContent: React.FC<{
193
232
  </Box>
194
233
 
195
234
  <Text color={SemanticColors.text.secondary}>
196
- Type to filter, Enter for details, Esc to cancel
235
+ Type to filter, Tab to navigate ([d] Delete), Enter for details, Esc to
236
+ cancel
197
237
  </Text>
198
238
 
199
239
  <Text color={SemanticColors.text.secondary}>
@@ -209,6 +249,12 @@ const NarrowContent: React.FC<{
209
249
  </Text>
210
250
  </Box>
211
251
  )}
252
+
253
+ <DeleteConfirmationBanner
254
+ confirmDeleteName={confirmDeleteName}
255
+ activeProfileName={activeProfileName}
256
+ defaultProfileName={defaultProfileName}
257
+ />
212
258
  </Box>
213
259
  );
214
260
 
@@ -241,19 +287,20 @@ const WideSelectionDetail: React.FC<{
241
287
  index: number;
242
288
  isSearching: boolean;
243
289
  }> = ({ filteredProfiles, index, isSearching }) => {
244
- if (filteredProfiles.length === 0 || isSearching) return null;
290
+ if (isSearching || index < 0 || index >= filteredProfiles.length) {
291
+ return null;
292
+ }
293
+ const selected = filteredProfiles[index];
245
294
 
246
295
  return (
247
296
  <Box marginTop={1}>
248
297
  <Text color={SemanticColors.text.secondary}>
249
- Selected: {filteredProfiles[index].name}
250
- {filteredProfiles[index].provider != null && (
298
+ Selected: {selected.name}
299
+ {selected.provider != null && (
251
300
  <Text color={SemanticColors.text.secondary}>
252
301
  {' '}
253
- ({filteredProfiles[index].provider}
254
- {filteredProfiles[index].model != null &&
255
- ` / ${filteredProfiles[index].model}`}
256
- )
302
+ ({selected.provider}
303
+ {selected.model != null && ` / ${selected.model}`})
257
304
  </Text>
258
305
  )}
259
306
  </Text>
@@ -267,7 +314,19 @@ const WideContent: React.FC<{
267
314
  filteredProfiles: ProfileListItem[];
268
315
  index: number;
269
316
  grid: React.ReactNode[];
270
- }> = ({ isSearching, searchTerm, filteredProfiles, index, grid }) => (
317
+ confirmDeleteName: string | null;
318
+ activeProfileName?: string;
319
+ defaultProfileName?: string;
320
+ }> = ({
321
+ isSearching,
322
+ searchTerm,
323
+ filteredProfiles,
324
+ index,
325
+ grid,
326
+ confirmDeleteName,
327
+ activeProfileName,
328
+ defaultProfileName,
329
+ }) => (
271
330
  <Box flexDirection="column">
272
331
  <Text bold color={SemanticColors.text.primary}>
273
332
  Profile List
@@ -301,10 +360,16 @@ const WideContent: React.FC<{
301
360
  </Text>
302
361
  </Box>
303
362
 
363
+ <DeleteConfirmationBanner
364
+ confirmDeleteName={confirmDeleteName}
365
+ activeProfileName={activeProfileName}
366
+ defaultProfileName={defaultProfileName}
367
+ />
368
+
304
369
  <Box marginTop={1} />
305
370
 
306
371
  <Text color={SemanticColors.text.secondary}>
307
- Controls: ↑↓←→ Navigate [Enter] Details [l] Load [Esc] Close
372
+ Controls: ↑↓←→ Navigate [Enter] Details [l] Load [d] Delete [Esc] Close
308
373
  </Text>
309
374
  </Box>
310
375
  );
@@ -374,9 +439,28 @@ const EmptyState: React.FC = () => (
374
439
  </Box>
375
440
  );
376
441
 
442
+ function handleDeleteConfirmKeys(
443
+ key: { name?: string; sequence?: string },
444
+ confirmDeleteName: string,
445
+ setConfirmDeleteName: React.Dispatch<React.SetStateAction<string | null>>,
446
+ onDelete: (name: string) => void,
447
+ ): void {
448
+ if (key.name === 'escape') {
449
+ setConfirmDeleteName(null);
450
+ return;
451
+ }
452
+ if (key.sequence === 'y' || key.sequence === 'Y') {
453
+ setConfirmDeleteName(null);
454
+ onDelete(confirmDeleteName);
455
+ return;
456
+ }
457
+ if (key.sequence === 'n' || key.sequence === 'N') {
458
+ setConfirmDeleteName(null);
459
+ }
460
+ }
461
+
377
462
  function useListKeypress(
378
463
  isSearching: boolean,
379
- isNarrow: boolean,
380
464
  searchTerm: string,
381
465
  setSearchTerm: React.Dispatch<React.SetStateAction<string>>,
382
466
  setIsSearching: React.Dispatch<React.SetStateAction<boolean>>,
@@ -385,12 +469,25 @@ function useListKeypress(
385
469
  filteredProfiles: ProfileListItem[],
386
470
  move: (delta: number) => void,
387
471
  onSelect: (name: string) => void,
472
+ onDelete: (name: string) => void,
388
473
  columns: number,
389
474
  onClose: () => void,
390
475
  isLoading: boolean,
476
+ confirmDeleteName: string | null,
477
+ setConfirmDeleteName: React.Dispatch<React.SetStateAction<string | null>>,
391
478
  ) {
392
479
  const handleKeypress = useCallback(
393
480
  (key: Parameters<Parameters<typeof useKeypress>[0]>[0]) => {
481
+ if (confirmDeleteName !== null) {
482
+ handleDeleteConfirmKeys(
483
+ key,
484
+ confirmDeleteName,
485
+ setConfirmDeleteName,
486
+ onDelete,
487
+ );
488
+ return;
489
+ }
490
+
394
491
  if (key.name === 'escape') {
395
492
  if (isSearching && searchTerm.length > 0) {
396
493
  setSearchTerm('');
@@ -399,10 +496,9 @@ function useListKeypress(
399
496
  }
400
497
  return;
401
498
  }
402
- if (isSearching || isNarrow) {
499
+ if (isSearching) {
403
500
  handleSearchModeKeys(
404
501
  key,
405
- isNarrow,
406
502
  setSearchTerm,
407
503
  setIsSearching,
408
504
  onViewDetail,
@@ -418,16 +514,19 @@ function useListKeypress(
418
514
  onViewDetail,
419
515
  onSelect,
420
516
  setIsSearching,
517
+ setConfirmDeleteName,
421
518
  columns,
422
519
  );
423
520
  }
424
521
  },
425
522
  [
523
+ confirmDeleteName,
524
+ setConfirmDeleteName,
525
+ onDelete,
426
526
  isSearching,
427
527
  searchTerm,
428
528
  setSearchTerm,
429
529
  onClose,
430
- isNarrow,
431
530
  setIsSearching,
432
531
  onViewDetail,
433
532
  index,
@@ -489,6 +588,9 @@ const ProfileListBody: React.FC<{
489
588
  filteredProfiles: ProfileListItem[];
490
589
  index: number;
491
590
  grid: React.ReactNode[];
591
+ confirmDeleteName: string | null;
592
+ activeProfileName?: string;
593
+ defaultProfileName?: string;
492
594
  }> = ({
493
595
  isNarrow,
494
596
  width,
@@ -497,6 +599,9 @@ const ProfileListBody: React.FC<{
497
599
  filteredProfiles,
498
600
  index,
499
601
  grid,
602
+ confirmDeleteName,
603
+ activeProfileName,
604
+ defaultProfileName,
500
605
  }) => {
501
606
  if (isNarrow) {
502
607
  return (
@@ -505,6 +610,9 @@ const ProfileListBody: React.FC<{
505
610
  searchTerm={searchTerm}
506
611
  filteredProfiles={filteredProfiles}
507
612
  grid={grid}
613
+ confirmDeleteName={confirmDeleteName}
614
+ activeProfileName={activeProfileName}
615
+ defaultProfileName={defaultProfileName}
508
616
  />
509
617
  </Box>
510
618
  );
@@ -524,31 +632,73 @@ const ProfileListBody: React.FC<{
524
632
  filteredProfiles={filteredProfiles}
525
633
  index={index}
526
634
  grid={grid}
635
+ confirmDeleteName={confirmDeleteName}
636
+ activeProfileName={activeProfileName}
637
+ defaultProfileName={defaultProfileName}
527
638
  />
528
639
  </Box>
529
640
  );
530
641
  };
531
642
 
532
- export const ProfileListDialog: React.FC<ProfileListDialogProps> = ({
533
- profiles,
534
- onSelect,
535
- onClose,
536
- onViewDetail,
537
- isLoading = false,
538
- defaultProfileName,
539
- activeProfileName,
540
- }) => {
643
+ function useConfirmDeleteClear(
644
+ confirmDeleteName: string | null,
645
+ filteredProfiles: ProfileListItem[],
646
+ setConfirmDeleteName: React.Dispatch<React.SetStateAction<string | null>>,
647
+ ): void {
648
+ useEffect(() => {
649
+ if (
650
+ confirmDeleteName !== null &&
651
+ !filteredProfiles.some((profile) => profile.name === confirmDeleteName)
652
+ ) {
653
+ setConfirmDeleteName(null);
654
+ }
655
+ }, [confirmDeleteName, filteredProfiles, setConfirmDeleteName]);
656
+ }
657
+
658
+ function useProfileListMove(
659
+ index: number,
660
+ filteredLength: number,
661
+ setIndex: React.Dispatch<React.SetStateAction<number>>,
662
+ ) {
663
+ return useCallback(
664
+ (delta: number) => {
665
+ if (filteredLength === 0) {
666
+ setIndex(0);
667
+ return;
668
+ }
669
+ let next = index + delta;
670
+ if (next < 0) next = 0;
671
+ if (next >= filteredLength) next = filteredLength - 1;
672
+ setIndex(next);
673
+ },
674
+ [index, filteredLength, setIndex],
675
+ );
676
+ }
677
+
678
+ function useProfileListController(opts: {
679
+ profiles: ProfileListItem[];
680
+ onSelect: (profileName: string) => void;
681
+ onClose: () => void;
682
+ onViewDetail: (profileName: string) => void;
683
+ onDelete: (profileName: string) => void;
684
+ isLoading: boolean;
685
+ activeProfileName?: string;
686
+ defaultProfileName?: string;
687
+ }) {
541
688
  const { isNarrow, isWide, width } = useResponsive();
542
689
  const [searchTerm, setSearchTerm] = useState('');
543
690
  const [isSearching, setIsSearching] = useState(true);
544
691
  const [index, setIndex] = useState(0);
692
+ const [confirmDeleteName, setConfirmDeleteName] = useState<string | null>(
693
+ null,
694
+ );
545
695
 
546
696
  const filteredProfiles = useMemo(
547
697
  () =>
548
- profiles.filter((p) =>
698
+ opts.profiles.filter((p) =>
549
699
  p.name.toLowerCase().includes(searchTerm.toLowerCase()),
550
700
  ),
551
- [profiles, searchTerm],
701
+ [opts.profiles, searchTerm],
552
702
  );
553
703
 
554
704
  const { columns, colWidth, rows } = useListLayout(
@@ -558,62 +708,82 @@ export const ProfileListDialog: React.FC<ProfileListDialogProps> = ({
558
708
  );
559
709
 
560
710
  useProfileListIndexBounds(searchTerm, filteredProfiles, setIndex);
561
-
562
- const move = useCallback(
563
- (delta: number) => {
564
- if (filteredProfiles.length === 0) {
565
- setIndex(0);
566
- return;
567
- }
568
- let next = index + delta;
569
- if (next < 0) next = 0;
570
- if (next >= filteredProfiles.length) next = filteredProfiles.length - 1;
571
- setIndex(next);
572
- },
573
- [index, filteredProfiles.length],
711
+ useConfirmDeleteClear(
712
+ confirmDeleteName,
713
+ filteredProfiles,
714
+ setConfirmDeleteName,
574
715
  );
575
716
 
717
+ const move = useProfileListMove(index, filteredProfiles.length, setIndex);
718
+
576
719
  useListKeypress(
577
720
  isSearching,
578
- isNarrow,
579
721
  searchTerm,
580
722
  setSearchTerm,
581
723
  setIsSearching,
582
- onViewDetail,
724
+ opts.onViewDetail,
583
725
  index,
584
726
  filteredProfiles,
585
727
  move,
586
- onSelect,
728
+ opts.onSelect,
729
+ opts.onDelete,
587
730
  columns,
588
- onClose,
589
- isLoading,
731
+ opts.onClose,
732
+ opts.isLoading,
733
+ confirmDeleteName,
734
+ setConfirmDeleteName,
590
735
  );
591
736
 
592
- const grid = buildGrid(
737
+ return {
738
+ isNarrow,
739
+ width,
740
+ isSearching,
741
+ searchTerm,
593
742
  filteredProfiles,
594
- rows,
595
- columns,
596
743
  index,
597
- isSearching,
598
- isNarrow,
599
- isWide,
600
- activeProfileName,
601
- defaultProfileName,
602
- colWidth,
603
- );
744
+ confirmDeleteName,
745
+ grid: buildGrid(
746
+ filteredProfiles,
747
+ rows,
748
+ columns,
749
+ index,
750
+ isSearching,
751
+ isNarrow,
752
+ isWide,
753
+ opts.activeProfileName,
754
+ opts.defaultProfileName,
755
+ colWidth,
756
+ ),
757
+ };
758
+ }
604
759
 
605
- if (isLoading) return <LoadingState />;
606
- if (profiles.length === 0) return <EmptyState />;
760
+ export const ProfileListDialog: React.FC<ProfileListDialogProps> = (props) => {
761
+ const controller = useProfileListController({
762
+ profiles: props.profiles,
763
+ onSelect: props.onSelect,
764
+ onClose: props.onClose,
765
+ onViewDetail: props.onViewDetail,
766
+ onDelete: props.onDelete,
767
+ isLoading: props.isLoading === true,
768
+ activeProfileName: props.activeProfileName,
769
+ defaultProfileName: props.defaultProfileName,
770
+ });
771
+
772
+ if (props.isLoading === true) return <LoadingState />;
773
+ if (props.profiles.length === 0) return <EmptyState />;
607
774
 
608
775
  return (
609
776
  <ProfileListBody
610
- isNarrow={isNarrow}
611
- width={width}
612
- isSearching={isSearching}
613
- searchTerm={searchTerm}
614
- filteredProfiles={filteredProfiles}
615
- index={index}
616
- grid={grid}
777
+ isNarrow={controller.isNarrow}
778
+ width={controller.width}
779
+ isSearching={controller.isSearching}
780
+ searchTerm={controller.searchTerm}
781
+ filteredProfiles={controller.filteredProfiles}
782
+ index={controller.index}
783
+ grid={controller.grid}
784
+ confirmDeleteName={controller.confirmDeleteName}
785
+ activeProfileName={props.activeProfileName}
786
+ defaultProfileName={props.defaultProfileName}
617
787
  />
618
788
  );
619
789
  };
@@ -60,8 +60,7 @@ export const INFORMATIVE_TIPS = [
60
60
  'Exclude env vars from the context (settings.json)...',
61
61
  'Configure a custom command for filing bug reports (settings.json)...',
62
62
  'Enable or disable telemetry collection (/settings)...',
63
- 'Send telemetry data to a local file or GCP (settings.json)...',
64
- 'Configure the OTLP endpoint for telemetry (settings.json)...',
63
+ 'Write telemetry to a local file (settings.json)...',
65
64
  'Choose whether to log prompt content (settings.json)...',
66
65
  'Enable AI-powered prompt completion while typing (/settings)...',
67
66
  'Enable debug logging of keystrokes to the console (/settings)...',
@@ -77,6 +77,7 @@ export interface UIActionsParams {
77
77
  closeProfileDetailDialog: () => void;
78
78
  loadProfileFromDetail: (profileName: string) => void;
79
79
  deleteProfileFromDetail: (profileName: string) => void;
80
+ deleteProfileFromList: (profileName: string) => void;
80
81
  setProfileAsDefault: (profileName: string) => void;
81
82
  openProfileEditor: (profileName: string, openedDirectly?: boolean) => void;
82
83
  closeProfileEditor: () => void;
@@ -240,6 +241,7 @@ export function buildUIActions(params: UIActionsParams): UIActions {
240
241
  closeProfileDetailDialog: params.closeProfileDetailDialog,
241
242
  loadProfileFromDetail: params.loadProfileFromDetail,
242
243
  deleteProfileFromDetail: params.deleteProfileFromDetail,
244
+ deleteProfileFromList: params.deleteProfileFromList,
243
245
  setProfileAsDefault: params.setProfileAsDefault,
244
246
  openProfileEditor: params.openProfileEditor,
245
247
  closeProfileEditor: params.closeProfileEditor,
@@ -381,6 +381,7 @@ function useDialogsProfiles(p: AppDialogsParams) {
381
381
  closeProfileDetailDialog: profileMgmt.closeDetailDialog,
382
382
  loadProfileFromDetail: profileMgmt.loadProfile,
383
383
  deleteProfileFromDetail: profileMgmt.deleteProfile,
384
+ deleteProfileFromList: profileMgmt.deleteProfileFromList,
384
385
  setProfileAsDefault: profileMgmt.setDefault,
385
386
  openProfileEditor: profileMgmt.openEditor,
386
387
  closeProfileEditor: profileMgmt.closeEditor,
@@ -80,6 +80,7 @@ export interface UIActions {
80
80
  closeProfileDetailDialog: () => void;
81
81
  loadProfileFromDetail: (profileName: string) => void;
82
82
  deleteProfileFromDetail: (profileName: string) => void;
83
+ deleteProfileFromList: (profileName: string) => void;
83
84
  setProfileAsDefault: (profileName: string) => void;
84
85
  openProfileEditor: (profileName: string, openedDirectly?: boolean) => void;
85
86
  closeProfileEditor: () => void;
@@ -153,52 +153,60 @@ function useProfileLoader(
153
153
  setDefaultProfileName: React.Dispatch<React.SetStateAction<string | null>>,
154
154
  setActiveProfileName: React.Dispatch<React.SetStateAction<string | null>>,
155
155
  ) {
156
- return useCallback(async () => {
157
- setIsLoading(true);
158
- setProfileError(null);
159
-
160
- try {
161
- const profileNames = await runtime.listSavedProfiles();
162
- const profileItems = await fetchProfileItems(runtime, profileNames);
163
- setProfiles(profileItems);
164
-
165
- try {
166
- const services = runtime.getCliRuntimeServices();
167
- const defaultName = services.settingsService.get('defaultProfile') as
168
- | string
169
- | null;
170
- setDefaultProfileName(defaultName ?? null);
171
- } catch {
172
- // Ignore errors getting default profile
156
+ return useCallback(
157
+ async (options?: { showLoading?: boolean }) => {
158
+ const showLoading = options?.showLoading !== false;
159
+ if (showLoading) {
160
+ setIsLoading(true);
173
161
  }
162
+ setProfileError(null);
174
163
 
175
164
  try {
176
- const diagnostics = runtime.getRuntimeDiagnosticsSnapshot();
177
- setActiveProfileName(diagnostics.profileName);
178
- } catch {
179
- // Ignore errors getting active profile
165
+ const profileNames = await runtime.listSavedProfiles();
166
+ const profileItems = await fetchProfileItems(runtime, profileNames);
167
+ setProfiles(profileItems);
168
+
169
+ try {
170
+ const services = runtime.getCliRuntimeServices();
171
+ const defaultName = services.settingsService.get('defaultProfile') as
172
+ | string
173
+ | null;
174
+ setDefaultProfileName(defaultName ?? null);
175
+ } catch {
176
+ // Ignore errors getting default profile
177
+ }
178
+
179
+ try {
180
+ const diagnostics = runtime.getRuntimeDiagnosticsSnapshot();
181
+ setActiveProfileName(diagnostics.profileName);
182
+ } catch {
183
+ // Ignore errors getting active profile
184
+ }
185
+ } catch (error) {
186
+ setProfileError(
187
+ error instanceof Error ? error.message : 'Failed to load profiles',
188
+ );
189
+ addMessage({
190
+ type: MessageType.ERROR,
191
+ content: `Failed to load profiles: ${error instanceof Error ? error.message : 'Unknown error'}`,
192
+ timestamp: new Date(),
193
+ });
194
+ } finally {
195
+ if (showLoading) {
196
+ setIsLoading(false);
197
+ }
180
198
  }
181
- } catch (error) {
182
- setProfileError(
183
- error instanceof Error ? error.message : 'Failed to load profiles',
184
- );
185
- addMessage({
186
- type: MessageType.ERROR,
187
- content: `Failed to load profiles: ${error instanceof Error ? error.message : 'Unknown error'}`,
188
- timestamp: new Date(),
189
- });
190
- } finally {
191
- setIsLoading(false);
192
- }
193
- }, [
194
- runtime,
195
- addMessage,
196
- setProfiles,
197
- setIsLoading,
198
- setProfileError,
199
- setDefaultProfileName,
200
- setActiveProfileName,
201
- ]);
199
+ },
200
+ [
201
+ runtime,
202
+ addMessage,
203
+ setProfiles,
204
+ setIsLoading,
205
+ setProfileError,
206
+ setDefaultProfileName,
207
+ setActiveProfileName,
208
+ ],
209
+ );
202
210
  }
203
211
 
204
212
  function useListDialogActions(
@@ -330,10 +338,12 @@ function useLoadProfileAction(
330
338
 
331
339
  function useDeleteProfileAction(
332
340
  addMessage: AddMessageFn,
333
- appDispatch: ReturnType<typeof useAppDispatch>,
341
+ appDispatch: ReturnType<typeof useAppDispatch> | null,
334
342
  runtime: ReturnType<typeof useRuntimeApi>,
335
- loadProfiles: () => Promise<void>,
343
+ loadProfiles: (options?: { showLoading?: boolean }) => Promise<void>,
344
+ options?: { fromList?: boolean },
336
345
  ) {
346
+ const fromList = options?.fromList === true;
337
347
  return useCallback(
338
348
  async (profileName: string) => {
339
349
  try {
@@ -343,9 +353,14 @@ function useDeleteProfileAction(
343
353
  content: `Profile '${profileName}' deleted`,
344
354
  timestamp: new Date(),
345
355
  });
346
- appDispatch({ type: 'CLOSE_DIALOG', payload: 'profileDetail' });
347
- appDispatch({ type: 'OPEN_DIALOG', payload: 'profileList' });
348
- await loadProfiles();
356
+ if (!fromList && appDispatch !== null) {
357
+ appDispatch({ type: 'CLOSE_DIALOG', payload: 'profileDetail' });
358
+ appDispatch({ type: 'OPEN_DIALOG', payload: 'profileList' });
359
+ await loadProfiles();
360
+ } else {
361
+ // Refresh in place without the loading flash so selection can clamp.
362
+ await loadProfiles({ showLoading: false });
363
+ }
349
364
  } catch (error) {
350
365
  addMessage({
351
366
  type: MessageType.ERROR,
@@ -354,7 +369,7 @@ function useDeleteProfileAction(
354
369
  });
355
370
  }
356
371
  },
357
- [addMessage, appDispatch, runtime, loadProfiles],
372
+ [addMessage, appDispatch, runtime, loadProfiles, fromList],
358
373
  );
359
374
  }
360
375
 
@@ -509,7 +524,7 @@ function useProfileDispatchActions(
509
524
  addMessage: AddMessageFn,
510
525
  appDispatch: ReturnType<typeof useAppDispatch>,
511
526
  runtime: ReturnType<typeof useRuntimeApi>,
512
- loadProfiles: () => Promise<void>,
527
+ loadProfiles: (options?: { showLoading?: boolean }) => Promise<void>,
513
528
  dataStates: ReturnType<typeof useProfileDataStates>,
514
529
  viewProfileDetail: (name: string, direct: boolean) => Promise<void>,
515
530
  ) {
@@ -525,6 +540,13 @@ function useProfileDispatchActions(
525
540
  runtime,
526
541
  loadProfiles,
527
542
  );
543
+ const deleteProfileFromList = useDeleteProfileAction(
544
+ addMessage,
545
+ null,
546
+ runtime,
547
+ loadProfiles,
548
+ { fromList: true },
549
+ );
528
550
  const setDefault = useSetDefaultAction(
529
551
  addMessage,
530
552
  runtime,
@@ -560,6 +582,7 @@ function useProfileDispatchActions(
560
582
  return {
561
583
  loadProfile,
562
584
  deleteProfile,
585
+ deleteProfileFromList,
563
586
  setDefault,
564
587
  openEditor,
565
588
  closeEditor,
@@ -567,6 +590,24 @@ function useProfileDispatchActions(
567
590
  };
568
591
  }
569
592
 
593
+ function buildProfileManagementResult<TActions extends object>(
594
+ dialogStates: ReturnType<typeof useProfileDialogStates>,
595
+ dataStates: ReturnType<typeof useProfileDataStates>,
596
+ actions: TActions,
597
+ ) {
598
+ return {
599
+ ...dialogStates,
600
+ profiles: dataStates.profiles,
601
+ isLoading: dataStates.isLoading,
602
+ selectedProfileName: dataStates.selectedProfileName,
603
+ selectedProfile: dataStates.selectedProfile,
604
+ defaultProfileName: dataStates.defaultProfileName,
605
+ activeProfileName: dataStates.activeProfileName,
606
+ profileError: dataStates.profileError,
607
+ ...actions,
608
+ };
609
+ }
610
+
570
611
  export const useProfileManagement = ({
571
612
  addMessage,
572
613
  appState,
@@ -578,7 +619,6 @@ export const useProfileManagement = ({
578
619
  const dataStates = useProfileDataStates();
579
620
  const { setActiveProfileName } = dataStates;
580
621
 
581
- // Initialize activeProfileName on mount from runtime diagnostics
582
622
  useEffect(() => {
583
623
  try {
584
624
  const diagnostics = runtime.getRuntimeDiagnosticsSnapshot();
@@ -615,14 +655,7 @@ export const useProfileManagement = ({
615
655
  dataStates.detailOpenedDirectly,
616
656
  loadProfiles,
617
657
  );
618
- const {
619
- loadProfile,
620
- deleteProfile,
621
- setDefault,
622
- openEditor,
623
- closeEditor,
624
- saveProfile,
625
- } = useProfileDispatchActions(
658
+ const dispatchActions = useProfileDispatchActions(
626
659
  addMessage,
627
660
  appDispatch,
628
661
  runtime,
@@ -631,33 +664,11 @@ export const useProfileManagement = ({
631
664
  viewProfileDetail,
632
665
  );
633
666
 
634
- return {
635
- // Dialog states
636
- ...dialogStates,
637
-
638
- // Data
639
- profiles: dataStates.profiles,
640
- isLoading: dataStates.isLoading,
641
- selectedProfileName: dataStates.selectedProfileName,
642
- selectedProfile: dataStates.selectedProfile,
643
- defaultProfileName: dataStates.defaultProfileName,
644
- activeProfileName: dataStates.activeProfileName,
645
- profileError: dataStates.profileError,
646
-
647
- // List dialog actions
667
+ return buildProfileManagementResult(dialogStates, dataStates, {
648
668
  openListDialog,
649
669
  closeListDialog,
650
-
651
- // Detail dialog actions
652
670
  viewProfileDetail,
653
671
  closeDetailDialog,
654
- loadProfile,
655
- deleteProfile,
656
- setDefault,
657
-
658
- // Editor actions
659
- openEditor,
660
- closeEditor,
661
- saveProfile,
662
- };
672
+ ...dispatchActions,
673
+ });
663
674
  };