@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 +1 -1
- package/package.json +12 -12
- package/src/generated/git-commit.json +1 -1
- package/src/ui/AppContainerRuntime.tsx +1 -0
- package/src/ui/commands/policiesCommand.ts +3 -3
- package/src/ui/components/DialogManager.tsx +1 -0
- package/src/ui/components/ProfileCreateWizard/constants.ts +1 -0
- package/src/ui/components/ProfileListDialog.tsx +241 -71
- package/src/ui/constants/tips.ts +1 -2
- package/src/ui/containers/AppContainer/builders/buildUIActions.ts +2 -0
- package/src/ui/containers/AppContainer/hooks/useAppDialogs.ts +1 -0
- package/src/ui/contexts/UIActionsContext.tsx +1 -0
- package/src/ui/hooks/useProfileManagement.ts +94 -83
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-
|
|
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
|
|
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
|
|
47
|
+
"sandboxImageUri": "ghcr.io/vybestack/llxprt-code/sandbox:0.10.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@vybestack/llxprt-code-storage": "0.10.0
|
|
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
|
|
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
|
|
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",
|
|
@@ -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[] = ['
|
|
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
|
|
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
|
|
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}
|
|
@@ -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
|
-
|
|
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'
|
|
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'
|
|
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 '{confirmDeleteName}'{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
|
-
|
|
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
|
|
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 (
|
|
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: {
|
|
250
|
-
{
|
|
298
|
+
Selected: {selected.name}
|
|
299
|
+
{selected.provider != null && (
|
|
251
300
|
<Text color={SemanticColors.text.secondary}>
|
|
252
301
|
{' '}
|
|
253
|
-
({
|
|
254
|
-
{
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
737
|
+
return {
|
|
738
|
+
isNarrow,
|
|
739
|
+
width,
|
|
740
|
+
isSearching,
|
|
741
|
+
searchTerm,
|
|
593
742
|
filteredProfiles,
|
|
594
|
-
rows,
|
|
595
|
-
columns,
|
|
596
743
|
index,
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
606
|
-
|
|
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
|
};
|
package/src/ui/constants/tips.ts
CHANGED
|
@@ -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
|
-
'
|
|
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(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
setDefault,
|
|
657
|
-
|
|
658
|
-
// Editor actions
|
|
659
|
-
openEditor,
|
|
660
|
-
closeEditor,
|
|
661
|
-
saveProfile,
|
|
662
|
-
};
|
|
672
|
+
...dispatchActions,
|
|
673
|
+
});
|
|
663
674
|
};
|