@vybestack/llxprt-ui 0.7.0-nightly.251211.5750c518a
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/PLAN-messages.md +681 -0
- package/PLAN.md +47 -0
- package/README.md +25 -0
- package/bun.lock +1024 -0
- package/dev-docs/ARCHITECTURE.md +178 -0
- package/dev-docs/CODE_ORGANIZATION.md +232 -0
- package/dev-docs/STANDARDS.md +235 -0
- package/dev-docs/UI_DESIGN.md +425 -0
- package/eslint.config.cjs +194 -0
- package/images/nui.png +0 -0
- package/llxprt.png +0 -0
- package/llxprt.svg +128 -0
- package/package.json +66 -0
- package/scripts/check-limits.ts +177 -0
- package/scripts/start.js +71 -0
- package/src/app.tsx +599 -0
- package/src/bootstrap.tsx +23 -0
- package/src/commands/AuthCommand.tsx +80 -0
- package/src/commands/ModelCommand.tsx +102 -0
- package/src/commands/ProviderCommand.tsx +103 -0
- package/src/commands/ThemeCommand.tsx +71 -0
- package/src/features/chat/history.ts +178 -0
- package/src/features/chat/index.ts +3 -0
- package/src/features/chat/persistentHistory.ts +102 -0
- package/src/features/chat/responder.ts +217 -0
- package/src/features/completion/completions.ts +161 -0
- package/src/features/completion/index.ts +3 -0
- package/src/features/completion/slash.test.ts +82 -0
- package/src/features/completion/slash.ts +248 -0
- package/src/features/completion/suggestions.test.ts +51 -0
- package/src/features/completion/suggestions.ts +112 -0
- package/src/features/config/configSession.test.ts +189 -0
- package/src/features/config/configSession.ts +179 -0
- package/src/features/config/index.ts +4 -0
- package/src/features/config/llxprtAdapter.integration.test.ts +202 -0
- package/src/features/config/llxprtAdapter.test.ts +139 -0
- package/src/features/config/llxprtAdapter.ts +257 -0
- package/src/features/config/llxprtCommands.test.ts +40 -0
- package/src/features/config/llxprtCommands.ts +35 -0
- package/src/features/config/llxprtConfig.test.ts +261 -0
- package/src/features/config/llxprtConfig.ts +418 -0
- package/src/features/theme/index.ts +2 -0
- package/src/features/theme/theme.test.ts +51 -0
- package/src/features/theme/theme.ts +105 -0
- package/src/features/theme/themeManager.ts +84 -0
- package/src/hooks/useAppCommands.ts +129 -0
- package/src/hooks/useApprovalKeyboard.ts +156 -0
- package/src/hooks/useChatStore.test.ts +112 -0
- package/src/hooks/useChatStore.ts +252 -0
- package/src/hooks/useInputManager.ts +99 -0
- package/src/hooks/useKeyboardHandlers.ts +130 -0
- package/src/hooks/useListNavigation.test.ts +166 -0
- package/src/hooks/useListNavigation.ts +62 -0
- package/src/hooks/usePersistentHistory.ts +94 -0
- package/src/hooks/useScrollManagement.ts +107 -0
- package/src/hooks/useSelectionClipboard.ts +48 -0
- package/src/hooks/useSessionManager.test.ts +85 -0
- package/src/hooks/useSessionManager.ts +101 -0
- package/src/hooks/useStreamingLifecycle.ts +71 -0
- package/src/hooks/useStreamingResponder.ts +401 -0
- package/src/hooks/useSuggestionSetup.ts +23 -0
- package/src/hooks/useToolApproval.test.ts +140 -0
- package/src/hooks/useToolApproval.ts +264 -0
- package/src/hooks/useToolScheduler.ts +432 -0
- package/src/index.ts +3 -0
- package/src/jsx.d.ts +11 -0
- package/src/lib/clipboard.ts +18 -0
- package/src/lib/logger.ts +107 -0
- package/src/lib/random.ts +5 -0
- package/src/main.tsx +13 -0
- package/src/test/mockTheme.ts +51 -0
- package/src/types/events.ts +87 -0
- package/src/types.ts +13 -0
- package/src/ui/components/ChatLayout.tsx +694 -0
- package/src/ui/components/CommandComponents.tsx +74 -0
- package/src/ui/components/DiffViewer.tsx +306 -0
- package/src/ui/components/FilterInput.test.ts +69 -0
- package/src/ui/components/FilterInput.tsx +62 -0
- package/src/ui/components/HeaderBar.tsx +137 -0
- package/src/ui/components/RadioSelect.test.ts +140 -0
- package/src/ui/components/RadioSelect.tsx +88 -0
- package/src/ui/components/SelectableList.test.ts +83 -0
- package/src/ui/components/SelectableList.tsx +35 -0
- package/src/ui/components/StatusBar.tsx +45 -0
- package/src/ui/components/SuggestionPanel.tsx +102 -0
- package/src/ui/components/messages/ModelMessage.tsx +14 -0
- package/src/ui/components/messages/SystemMessage.tsx +29 -0
- package/src/ui/components/messages/ThinkingMessage.tsx +14 -0
- package/src/ui/components/messages/UserMessage.tsx +26 -0
- package/src/ui/components/messages/index.ts +15 -0
- package/src/ui/components/messages/renderMessage.test.ts +49 -0
- package/src/ui/components/messages/renderMessage.tsx +43 -0
- package/src/ui/components/messages/types.test.ts +24 -0
- package/src/ui/components/messages/types.ts +36 -0
- package/src/ui/modals/AuthModal.tsx +106 -0
- package/src/ui/modals/ModalShell.tsx +60 -0
- package/src/ui/modals/SearchSelectModal.tsx +236 -0
- package/src/ui/modals/ThemeModal.tsx +204 -0
- package/src/ui/modals/ToolApprovalModal.test.ts +206 -0
- package/src/ui/modals/ToolApprovalModal.tsx +282 -0
- package/src/ui/modals/index.ts +20 -0
- package/src/ui/modals/modals.test.ts +26 -0
- package/src/ui/modals/types.ts +19 -0
- package/src/uicontext/Command.tsx +102 -0
- package/src/uicontext/Dialog.tsx +65 -0
- package/src/uicontext/index.ts +2 -0
- package/themes/ansi-light.json +59 -0
- package/themes/ansi.json +59 -0
- package/themes/atom-one-dark.json +59 -0
- package/themes/ayu-light.json +59 -0
- package/themes/ayu.json +59 -0
- package/themes/default-light.json +59 -0
- package/themes/default.json +59 -0
- package/themes/dracula.json +59 -0
- package/themes/github-dark.json +59 -0
- package/themes/github-light.json +59 -0
- package/themes/googlecode.json +59 -0
- package/themes/green-screen.json +59 -0
- package/themes/no-color.json +59 -0
- package/themes/shades-of-purple.json +59 -0
- package/themes/xcode.json +59 -0
- package/tsconfig.json +28 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { PendingApproval, UseToolApprovalResult } from './useToolApproval';
|
|
3
|
+
import type { ToolConfirmationType } from '../types/events';
|
|
4
|
+
|
|
5
|
+
describe('useToolApproval', () => {
|
|
6
|
+
describe('PendingApproval type', () => {
|
|
7
|
+
it('extends ToolApprovalDetails with correlationId', () => {
|
|
8
|
+
const approval: PendingApproval = {
|
|
9
|
+
callId: 'call-123',
|
|
10
|
+
toolName: 'write_file',
|
|
11
|
+
confirmationType: 'edit' as ToolConfirmationType,
|
|
12
|
+
question: 'Allow file write?',
|
|
13
|
+
preview: 'Writing to /path/to/file.ts',
|
|
14
|
+
params: { path: '/path/to/file.ts' },
|
|
15
|
+
canAllowAlways: true,
|
|
16
|
+
correlationId: 'corr-456',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
expect(approval.callId).toBe('call-123');
|
|
20
|
+
expect(approval.correlationId).toBe('corr-456');
|
|
21
|
+
expect(approval.toolName).toBe('write_file');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('supports all confirmation types', () => {
|
|
25
|
+
const confirmationTypes: ToolConfirmationType[] = [
|
|
26
|
+
'edit',
|
|
27
|
+
'exec',
|
|
28
|
+
'mcp',
|
|
29
|
+
'info',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const type of confirmationTypes) {
|
|
33
|
+
const approval: PendingApproval = {
|
|
34
|
+
callId: `call-${type}`,
|
|
35
|
+
toolName: `${type}_tool`,
|
|
36
|
+
confirmationType: type,
|
|
37
|
+
question: `Allow ${type}?`,
|
|
38
|
+
preview: `Preview for ${type}`,
|
|
39
|
+
params: {},
|
|
40
|
+
canAllowAlways: true,
|
|
41
|
+
correlationId: `corr-${type}`,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
expect(approval.confirmationType).toBe(type);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('UseToolApprovalResult type', () => {
|
|
50
|
+
it('has correct shape', () => {
|
|
51
|
+
const mockResult: UseToolApprovalResult = {
|
|
52
|
+
pendingApproval: null,
|
|
53
|
+
queueApproval: () => {},
|
|
54
|
+
queueApprovalFromScheduler: () => {},
|
|
55
|
+
handleDecision: () => {},
|
|
56
|
+
clearApproval: () => {},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
expect(mockResult.pendingApproval).toBeNull();
|
|
60
|
+
expect(typeof mockResult.queueApproval).toBe('function');
|
|
61
|
+
expect(typeof mockResult.queueApprovalFromScheduler).toBe('function');
|
|
62
|
+
expect(typeof mockResult.handleDecision).toBe('function');
|
|
63
|
+
expect(typeof mockResult.clearApproval).toBe('function');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('pendingApproval can be a PendingApproval object', () => {
|
|
67
|
+
const approval: PendingApproval = {
|
|
68
|
+
callId: 'call-789',
|
|
69
|
+
toolName: 'run_command',
|
|
70
|
+
confirmationType: 'exec' as ToolConfirmationType,
|
|
71
|
+
question: 'Allow command?',
|
|
72
|
+
preview: 'npm test',
|
|
73
|
+
params: { command: 'npm test' },
|
|
74
|
+
canAllowAlways: false,
|
|
75
|
+
correlationId: 'corr-789',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const mockResult: UseToolApprovalResult = {
|
|
79
|
+
pendingApproval: approval,
|
|
80
|
+
queueApproval: () => {},
|
|
81
|
+
queueApprovalFromScheduler: () => {},
|
|
82
|
+
handleDecision: () => {},
|
|
83
|
+
clearApproval: () => {},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
expect(mockResult.pendingApproval).toBe(approval);
|
|
87
|
+
expect(mockResult.pendingApproval?.toolName).toBe('run_command');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('queueApproval accepts PendingApproval', () => {
|
|
91
|
+
let queuedApproval: PendingApproval | null = null;
|
|
92
|
+
|
|
93
|
+
const mockResult: UseToolApprovalResult = {
|
|
94
|
+
pendingApproval: null,
|
|
95
|
+
queueApproval: (approval: PendingApproval) => {
|
|
96
|
+
queuedApproval = approval;
|
|
97
|
+
},
|
|
98
|
+
queueApprovalFromScheduler: () => {},
|
|
99
|
+
handleDecision: () => {},
|
|
100
|
+
clearApproval: () => {},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const approval: PendingApproval = {
|
|
104
|
+
callId: 'call-queue',
|
|
105
|
+
toolName: 'test_tool',
|
|
106
|
+
confirmationType: 'info' as ToolConfirmationType,
|
|
107
|
+
question: 'Allow?',
|
|
108
|
+
preview: 'Test',
|
|
109
|
+
params: {},
|
|
110
|
+
canAllowAlways: true,
|
|
111
|
+
correlationId: 'corr-queue',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
mockResult.queueApproval(approval);
|
|
115
|
+
|
|
116
|
+
expect(queuedApproval).toBe(approval);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('handleDecision receives callId and outcome', () => {
|
|
120
|
+
let receivedCallId: string | null = null;
|
|
121
|
+
let receivedOutcome: string | null = null;
|
|
122
|
+
|
|
123
|
+
const mockResult: UseToolApprovalResult = {
|
|
124
|
+
pendingApproval: null,
|
|
125
|
+
queueApproval: () => {},
|
|
126
|
+
queueApprovalFromScheduler: () => {},
|
|
127
|
+
handleDecision: (callId: string, outcome) => {
|
|
128
|
+
receivedCallId = callId;
|
|
129
|
+
receivedOutcome = outcome;
|
|
130
|
+
},
|
|
131
|
+
clearApproval: () => {},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
mockResult.handleDecision('call-decision', 'allow_always');
|
|
135
|
+
|
|
136
|
+
expect(receivedCallId).toBe('call-decision');
|
|
137
|
+
expect(receivedOutcome).toBe('allow_always');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { useCallback, useState, useRef, useEffect } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
ToolApprovalDetails,
|
|
4
|
+
ToolApprovalOutcome,
|
|
5
|
+
} from '../ui/modals/ToolApprovalModal';
|
|
6
|
+
import type { ToolCallConfirmationDetails } from '@vybestack/llxprt-code-core';
|
|
7
|
+
import { ToolConfirmationOutcome } from '@vybestack/llxprt-code-core';
|
|
8
|
+
import type { ToolConfirmationType } from '../types/events';
|
|
9
|
+
import { getLogger } from '../lib/logger';
|
|
10
|
+
|
|
11
|
+
const logger = getLogger('nui:tool-approval');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Function type for responding to tool confirmations
|
|
15
|
+
*/
|
|
16
|
+
export type RespondToConfirmationFn = (
|
|
17
|
+
callId: string,
|
|
18
|
+
outcome: ToolConfirmationOutcome,
|
|
19
|
+
) => void;
|
|
20
|
+
|
|
21
|
+
export interface PendingApproval extends ToolApprovalDetails {
|
|
22
|
+
readonly correlationId: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseToolApprovalResult {
|
|
26
|
+
readonly pendingApproval: PendingApproval | null;
|
|
27
|
+
readonly queueApproval: (approval: PendingApproval) => void;
|
|
28
|
+
readonly queueApprovalFromScheduler: (
|
|
29
|
+
callId: string,
|
|
30
|
+
toolName: string,
|
|
31
|
+
confirmationDetails: ToolCallConfirmationDetails,
|
|
32
|
+
) => void;
|
|
33
|
+
readonly handleDecision: (
|
|
34
|
+
callId: string,
|
|
35
|
+
outcome: ToolApprovalOutcome,
|
|
36
|
+
) => void;
|
|
37
|
+
readonly clearApproval: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mapOutcome(outcome: ToolApprovalOutcome): ToolConfirmationOutcome {
|
|
41
|
+
switch (outcome) {
|
|
42
|
+
case 'allow_once':
|
|
43
|
+
return ToolConfirmationOutcome.ProceedOnce;
|
|
44
|
+
case 'allow_always':
|
|
45
|
+
return ToolConfirmationOutcome.ProceedAlways;
|
|
46
|
+
case 'cancel':
|
|
47
|
+
return ToolConfirmationOutcome.Cancel;
|
|
48
|
+
default:
|
|
49
|
+
return ToolConfirmationOutcome.Cancel;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Map CoreToolScheduler confirmation type to UI confirmation type
|
|
55
|
+
*/
|
|
56
|
+
function mapConfirmationType(type: string): ToolConfirmationType {
|
|
57
|
+
switch (type) {
|
|
58
|
+
case 'edit':
|
|
59
|
+
return 'edit';
|
|
60
|
+
case 'exec':
|
|
61
|
+
return 'exec';
|
|
62
|
+
case 'mcp':
|
|
63
|
+
return 'mcp';
|
|
64
|
+
case 'info':
|
|
65
|
+
return 'info';
|
|
66
|
+
default:
|
|
67
|
+
return 'info';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get question based on confirmation type
|
|
73
|
+
*/
|
|
74
|
+
function getQuestionForType(details: ToolCallConfirmationDetails): string {
|
|
75
|
+
switch (details.type) {
|
|
76
|
+
case 'edit':
|
|
77
|
+
return 'Apply this change?';
|
|
78
|
+
case 'exec':
|
|
79
|
+
return `Allow execution of: '${details.rootCommand}'?`;
|
|
80
|
+
case 'mcp':
|
|
81
|
+
return `Allow execution of MCP tool "${details.toolName}" from server "${details.serverName}"?`;
|
|
82
|
+
case 'info':
|
|
83
|
+
return 'Do you want to proceed?';
|
|
84
|
+
default:
|
|
85
|
+
return 'Do you want to proceed?';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get preview string from confirmation details
|
|
91
|
+
*/
|
|
92
|
+
function getPreviewForType(details: ToolCallConfirmationDetails): string {
|
|
93
|
+
switch (details.type) {
|
|
94
|
+
case 'edit':
|
|
95
|
+
return details.fileDiff || `Edit: ${details.filePath}`;
|
|
96
|
+
case 'exec':
|
|
97
|
+
return details.command;
|
|
98
|
+
case 'mcp':
|
|
99
|
+
return `MCP Server: ${details.serverName}\nTool: ${details.toolDisplayName}`;
|
|
100
|
+
case 'info':
|
|
101
|
+
return details.prompt || '';
|
|
102
|
+
default:
|
|
103
|
+
return '';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function useToolApproval(
|
|
108
|
+
respondToConfirmation: RespondToConfirmationFn | null,
|
|
109
|
+
): UseToolApprovalResult {
|
|
110
|
+
const [pendingApproval, setPendingApproval] =
|
|
111
|
+
useState<PendingApproval | null>(null);
|
|
112
|
+
const approvalQueueRef = useRef<PendingApproval[]>([]);
|
|
113
|
+
|
|
114
|
+
// Ref to track current pendingApproval to avoid stale closures
|
|
115
|
+
const pendingApprovalRef = useRef<PendingApproval | null>(null);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
pendingApprovalRef.current = pendingApproval;
|
|
118
|
+
}, [pendingApproval]);
|
|
119
|
+
|
|
120
|
+
// Ref to track respondToConfirmation to avoid stale closures
|
|
121
|
+
const respondToConfirmationRef = useRef<RespondToConfirmationFn | null>(
|
|
122
|
+
respondToConfirmation,
|
|
123
|
+
);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
respondToConfirmationRef.current = respondToConfirmation;
|
|
126
|
+
}, [respondToConfirmation]);
|
|
127
|
+
|
|
128
|
+
const processNextApproval = useCallback(() => {
|
|
129
|
+
if (approvalQueueRef.current.length > 0) {
|
|
130
|
+
const next = approvalQueueRef.current.shift();
|
|
131
|
+
if (next) {
|
|
132
|
+
setPendingApproval(next);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
setPendingApproval(null);
|
|
136
|
+
}
|
|
137
|
+
}, []);
|
|
138
|
+
|
|
139
|
+
const queueApproval = useCallback(
|
|
140
|
+
(approval: PendingApproval) => {
|
|
141
|
+
approvalQueueRef.current.push(approval);
|
|
142
|
+
// If no current pending approval, show this one
|
|
143
|
+
if (pendingApproval === null) {
|
|
144
|
+
processNextApproval();
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[pendingApproval, processNextApproval],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Queue approval from CoreToolScheduler's waiting tool call
|
|
152
|
+
*/
|
|
153
|
+
const queueApprovalFromScheduler = useCallback(
|
|
154
|
+
(
|
|
155
|
+
callId: string,
|
|
156
|
+
toolName: string,
|
|
157
|
+
confirmationDetails: ToolCallConfirmationDetails,
|
|
158
|
+
) => {
|
|
159
|
+
const approval: PendingApproval = {
|
|
160
|
+
callId,
|
|
161
|
+
toolName,
|
|
162
|
+
confirmationType: mapConfirmationType(confirmationDetails.type),
|
|
163
|
+
question: getQuestionForType(confirmationDetails),
|
|
164
|
+
preview: getPreviewForType(confirmationDetails),
|
|
165
|
+
params: {}, // Params are embedded in confirmationDetails
|
|
166
|
+
canAllowAlways: true, // Can be refined based on policy
|
|
167
|
+
correlationId: String(
|
|
168
|
+
(confirmationDetails as { correlationId?: string }).correlationId ??
|
|
169
|
+
callId,
|
|
170
|
+
),
|
|
171
|
+
coreDetails: confirmationDetails,
|
|
172
|
+
};
|
|
173
|
+
queueApproval(approval);
|
|
174
|
+
},
|
|
175
|
+
[queueApproval],
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const handleDecision = useCallback(
|
|
179
|
+
(callId: string, outcome: ToolApprovalOutcome) => {
|
|
180
|
+
const currentApproval = pendingApprovalRef.current;
|
|
181
|
+
const confirmFn = respondToConfirmationRef.current;
|
|
182
|
+
logger.debug(
|
|
183
|
+
'handleDecision called',
|
|
184
|
+
'callId:',
|
|
185
|
+
callId,
|
|
186
|
+
'outcome:',
|
|
187
|
+
outcome,
|
|
188
|
+
'currentApproval:',
|
|
189
|
+
currentApproval?.callId,
|
|
190
|
+
'hasConfirmFn:',
|
|
191
|
+
!!confirmFn,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (!confirmFn) {
|
|
195
|
+
logger.warn('handleDecision: no respondToConfirmation function');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (!currentApproval) {
|
|
199
|
+
logger.warn('handleDecision: no currentApproval');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (currentApproval.callId !== callId) {
|
|
203
|
+
logger.warn(
|
|
204
|
+
'handleDecision: callId mismatch',
|
|
205
|
+
'expected:',
|
|
206
|
+
currentApproval.callId,
|
|
207
|
+
'got:',
|
|
208
|
+
callId,
|
|
209
|
+
);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const coreOutcome = mapOutcome(outcome);
|
|
215
|
+
logger.debug(
|
|
216
|
+
'Calling respondToConfirmation',
|
|
217
|
+
'callId:',
|
|
218
|
+
callId,
|
|
219
|
+
'outcome:',
|
|
220
|
+
coreOutcome,
|
|
221
|
+
);
|
|
222
|
+
confirmFn(callId, coreOutcome);
|
|
223
|
+
logger.debug('respondToConfirmation called successfully');
|
|
224
|
+
|
|
225
|
+
// Move to next approval in queue
|
|
226
|
+
processNextApproval();
|
|
227
|
+
} catch (err) {
|
|
228
|
+
logger.error('Error in handleDecision:', String(err));
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
[processNextApproval],
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const clearApproval = useCallback(() => {
|
|
235
|
+
// Cancel all pending approvals
|
|
236
|
+
const currentApproval = pendingApprovalRef.current;
|
|
237
|
+
const confirmFn = respondToConfirmationRef.current;
|
|
238
|
+
if (confirmFn && currentApproval) {
|
|
239
|
+
logger.debug(
|
|
240
|
+
'clearApproval: cancelling',
|
|
241
|
+
'callId:',
|
|
242
|
+
currentApproval.callId,
|
|
243
|
+
);
|
|
244
|
+
confirmFn(currentApproval.callId, ToolConfirmationOutcome.Cancel);
|
|
245
|
+
}
|
|
246
|
+
approvalQueueRef.current = [];
|
|
247
|
+
setPendingApproval(null);
|
|
248
|
+
}, []);
|
|
249
|
+
|
|
250
|
+
// Clean up on unmount
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
return () => {
|
|
253
|
+
approvalQueueRef.current = [];
|
|
254
|
+
};
|
|
255
|
+
}, []);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
pendingApproval,
|
|
259
|
+
queueApproval,
|
|
260
|
+
queueApprovalFromScheduler,
|
|
261
|
+
handleDecision,
|
|
262
|
+
clearApproval,
|
|
263
|
+
};
|
|
264
|
+
}
|