mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-24 05:28:46 +02:00
feat: show MCP probe progress in approval pill
- Thread mcpProbingServerIds from workspace state through App → ChatPane → InlineApprovalPill - Show animated spinner and 'probing…' label on pill button while any MCP server is being probed - Show per-server probing indicator in popover: spinning icon, 'probing…' badge, hidden toggle - Render approval pill during probing even when no MCP tools are known yet Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -333,6 +333,7 @@ export default function App() {
|
||||
});
|
||||
}}
|
||||
availableModels={availableModels}
|
||||
mcpProbingServerIds={workspace.mcpProbingServerIds}
|
||||
pattern={patternForSession}
|
||||
project={projectForSession}
|
||||
runtimeTools={sidecarCapabilities?.runtimeTools}
|
||||
|
||||
@@ -41,6 +41,7 @@ interface ChatPaneProps {
|
||||
session: SessionRecord;
|
||||
availableModels: ReadonlyArray<ModelDefinition>;
|
||||
toolingSettings: WorkspaceToolingSettings;
|
||||
mcpProbingServerIds?: string[];
|
||||
runtimeTools?: ReadonlyArray<RuntimeToolDefinition>;
|
||||
sessionUsage?: SessionUsageState;
|
||||
onSend: (content: string, attachments?: ChatMessageAttachment[], messageMode?: MessageMode) => Promise<void>;
|
||||
@@ -65,6 +66,7 @@ export function ChatPane({
|
||||
session,
|
||||
availableModels,
|
||||
toolingSettings,
|
||||
mcpProbingServerIds,
|
||||
runtimeTools,
|
||||
sessionUsage,
|
||||
onSend,
|
||||
@@ -143,6 +145,8 @@ export function ChatPane({
|
||||
}
|
||||
return counted.size;
|
||||
}, [approvalTools, effectiveAutoApproved, toolingSettings]);
|
||||
const isProbingMcp = (mcpProbingServerIds?.length ?? 0) > 0;
|
||||
const hasApprovalContent = approvalTools.length > 0 || isProbingMcp;
|
||||
|
||||
useEffect(() => {
|
||||
transcriptRef.current?.scrollTo({
|
||||
@@ -487,13 +491,14 @@ export function ChatPane({
|
||||
selection={toolSelection}
|
||||
/>
|
||||
)}
|
||||
{hasToolCallApproval && onUpdateSessionApprovalSettings && approvalTools.length > 0 && (
|
||||
{hasToolCallApproval && onUpdateSessionApprovalSettings && hasApprovalContent && (
|
||||
<InlineApprovalPill
|
||||
approvalTools={approvalTools}
|
||||
disabled={isComposerDisabled}
|
||||
effectiveAutoApproved={effectiveAutoApproved}
|
||||
effectiveAutoApprovedCount={effectiveAutoApprovedCount}
|
||||
isOverridden={isApprovalOverridden}
|
||||
mcpProbingServerIds={mcpProbingServerIds}
|
||||
onUpdate={onUpdateSessionApprovalSettings}
|
||||
toolingSettings={toolingSettings}
|
||||
/>
|
||||
@@ -543,13 +548,14 @@ export function ChatPane({
|
||||
selection={toolSelection}
|
||||
/>
|
||||
)}
|
||||
{hasToolCallApproval && onUpdateSessionApprovalSettings && approvalTools.length > 0 && (
|
||||
{hasToolCallApproval && onUpdateSessionApprovalSettings && hasApprovalContent && (
|
||||
<InlineApprovalPill
|
||||
approvalTools={approvalTools}
|
||||
disabled={isComposerDisabled}
|
||||
effectiveAutoApproved={effectiveAutoApproved}
|
||||
effectiveAutoApprovedCount={effectiveAutoApprovedCount}
|
||||
isOverridden={isApprovalOverridden}
|
||||
mcpProbingServerIds={mcpProbingServerIds}
|
||||
onUpdate={onUpdateSessionApprovalSettings}
|
||||
toolingSettings={toolingSettings}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ChevronDown, ChevronRight, Minus, Search, Sparkles } from 'lucide-react';
|
||||
import { ChevronDown, ChevronRight, Loader2, Minus, Search, Sparkles } from 'lucide-react';
|
||||
|
||||
import { ProviderIcon } from '@renderer/components/ProviderIcons';
|
||||
import { PopoverToggleRow } from '@renderer/components/ui';
|
||||
@@ -347,6 +347,7 @@ export function InlineApprovalPill({
|
||||
effectiveAutoApprovedCount,
|
||||
isOverridden,
|
||||
disabled,
|
||||
mcpProbingServerIds,
|
||||
onUpdate,
|
||||
}: {
|
||||
approvalTools: ApprovalToolDefinition[];
|
||||
@@ -355,6 +356,7 @@ export function InlineApprovalPill({
|
||||
effectiveAutoApprovedCount: number;
|
||||
isOverridden: boolean;
|
||||
disabled: boolean;
|
||||
mcpProbingServerIds?: string[];
|
||||
onUpdate: (settings: { autoApprovedToolNames?: string[] }) => void;
|
||||
}){
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -362,6 +364,12 @@ export function InlineApprovalPill({
|
||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||
const ref = useClickOutside<HTMLDivElement>(() => { setOpen(false); setSearch(''); }, open);
|
||||
|
||||
const probingSet = useMemo(
|
||||
() => new Set(mcpProbingServerIds ?? []),
|
||||
[mcpProbingServerIds],
|
||||
);
|
||||
const isProbingAny = probingSet.size > 0;
|
||||
|
||||
const groups = useMemo(
|
||||
() => groupApprovalToolsByProvider(approvalTools, toolingSettings),
|
||||
[approvalTools, toolingSettings],
|
||||
@@ -439,6 +447,12 @@ export function InlineApprovalPill({
|
||||
return 'none';
|
||||
}
|
||||
|
||||
function isGroupProbing(group: ApprovalToolGroup): boolean {
|
||||
if (group.kind !== 'mcp') return false;
|
||||
const serverId = group.id.replace(/^mcp:/, '');
|
||||
return probingSet.has(serverId);
|
||||
}
|
||||
|
||||
function toggleExpanded(groupId: string) {
|
||||
setExpandedGroups((prev) => {
|
||||
const next = new Set(prev);
|
||||
@@ -472,8 +486,15 @@ export function InlineApprovalPill({
|
||||
onClick={() => setOpen(!open)}
|
||||
type="button"
|
||||
>
|
||||
<ShieldCheck className="size-2.5" />
|
||||
<span>{effectiveAutoApprovedCount}/{totalItemCount} auto-approved</span>
|
||||
{isProbingAny ? (
|
||||
<Loader2 className="size-2.5 animate-spin" aria-label="Probing MCP servers" />
|
||||
) : (
|
||||
<ShieldCheck className="size-2.5" />
|
||||
)}
|
||||
<span>
|
||||
{effectiveAutoApprovedCount}/{totalItemCount} auto-approved
|
||||
{isProbingAny && <span className="text-zinc-500"> · probing…</span>}
|
||||
</span>
|
||||
<ChevronDown className={`size-2.5 transition ${open ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
|
||||
@@ -525,6 +546,7 @@ export function InlineApprovalPill({
|
||||
const isBuiltin = group.kind === 'builtin';
|
||||
const isCollapsible = !isBuiltin;
|
||||
const expanded = isBuiltin || isGroupExpanded(group.id);
|
||||
const probing = isGroupProbing(group);
|
||||
const groupState = isGroupApproved(group);
|
||||
const allApproved = groupState === 'all';
|
||||
const someApproved = groupState === 'some';
|
||||
@@ -547,20 +569,30 @@ export function InlineApprovalPill({
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
{group.tools.length > 0 ? (
|
||||
{probing ? (
|
||||
<Loader2 className="size-3 shrink-0 animate-spin text-indigo-400" aria-label="Probing server" />
|
||||
) : group.tools.length > 0 ? (
|
||||
<ChevronRight className={`size-3 shrink-0 text-zinc-600 transition ${expanded ? 'rotate-90' : ''}`} />
|
||||
) : (
|
||||
<Server className="size-3 shrink-0 text-zinc-600" />
|
||||
)}
|
||||
<span className="min-w-0 flex-1 truncate text-[12px] font-medium text-zinc-300">{group.label}</span>
|
||||
<span className="shrink-0 rounded-full bg-zinc-800/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-zinc-500">
|
||||
{approvedLabel}
|
||||
</span>
|
||||
<GroupToggle
|
||||
allApproved={allApproved}
|
||||
someApproved={someApproved}
|
||||
onToggle={(e) => { e.stopPropagation(); toggleGroup(group); }}
|
||||
/>
|
||||
{probing ? (
|
||||
<span className="shrink-0 rounded-full bg-indigo-500/10 px-1.5 py-px text-[9px] font-medium text-indigo-400">
|
||||
probing…
|
||||
</span>
|
||||
) : (
|
||||
<span className="shrink-0 rounded-full bg-zinc-800/80 px-1.5 py-px text-[9px] font-medium tabular-nums text-zinc-500">
|
||||
{approvedLabel}
|
||||
</span>
|
||||
)}
|
||||
{!probing && (
|
||||
<GroupToggle
|
||||
allApproved={allApproved}
|
||||
someApproved={someApproved}
|
||||
onToggle={(e) => { e.stopPropagation(); toggleGroup(group); }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user