mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
feat(settings): make the external-agent (MCP) connector setup findable in sidebar search
Production telemetry showed 1,437 installs on the external-agent surface but only 2 on the Pulse MCP adapter. Live verification proved the pulse_mcp telemetry surface attribution is honest (adapter tools/call records surface=pulse_mcp; header-less calls record agent_api), so the gap is real - but the funnel comparison is also misleading: agent_api counts any scoped API-token REST consumer (scripts, dashboards polling /api/config/nodes or /api/ai/patrol/findings), not BYO AI agents. Recorded that interpretation constraint on PulseIntelligenceExternalAgentEvidence. The genuine product gap: the in-app connector setup (Settings -> Pulse Intelligence -> Assistant -> External agents) was invisible to sidebar search. Searching 'mcp', 'claude', 'opencode', or 'connector' returned 'No settings found' because search matched only nav labels and header descriptions. - Add search-only keywords to SettingsNavItem and match them in filteredTabGroups; the Assistant item now carries mcp / model context protocol / external agent(s) / claude / opencode / connector / pulse-mcp. - Name external agent (MCP) connectors in the Assistant header description (EN/DE/ES kept in sync), so the page header states what the page contains. - Pin the search behavior with parameterized useSettingsAccess tests plus a settingsArchitecture source proof; record the keyword channel in the frontend-primitives contract (obligation 41) and the product-copy allowance in the security-privacy i18n extension point.
This commit is contained in:
@@ -2307,6 +2307,21 @@ default` instead of fusing provider and badge text such as
|
||||
`frontend-modern/src/components/shared/DiscoveryProvenanceMarker.tsx`, so
|
||||
operators can distinguish opt-in Discovery context from API-owned resource
|
||||
facts without reading a drawer-specific explanation.
|
||||
41. Keep settings sidebar search able to find pages by the vocabulary users
|
||||
actually type, not only by rendered copy. `SettingsNavItem.keywords` in
|
||||
`frontend-modern/src/components/Settings/settingsNavigationModel.ts` is the
|
||||
canonical search-only alias channel, matched alongside labels and header
|
||||
descriptions in
|
||||
`frontend-modern/src/components/Settings/useSettingsAccess.ts`; keywords
|
||||
are never rendered and must not become a second copy surface. The
|
||||
Assistant nav item must keep the external-agent connector aliases
|
||||
(`mcp`, `model context protocol`, `external agent`, `claude`, `opencode`,
|
||||
`connector`, `pulse-mcp`) so the pulse-mcp setup hosted on that page stays
|
||||
reachable from search, and the Assistant header description must continue
|
||||
to name external agent (MCP) connectors across locales. Proof lives in
|
||||
`frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts`
|
||||
and the parameterized search cases in
|
||||
`frontend-modern/src/components/Settings/__tests__/useSettingsAccess.test.tsx`.
|
||||
|
||||
## Current State
|
||||
|
||||
|
||||
@@ -299,7 +299,12 @@ the `white_label` branding entitlement.
|
||||
Pulse Intelligence Provider & Models, Patrol, Assistant, and Service Context
|
||||
settings labels may use the same localized catalog boundary, but those edits
|
||||
must stay product-settings copy only and must not change token scope names,
|
||||
preset ids, privacy disclosures, or security control terminology. Self-hosted
|
||||
preset ids, privacy disclosures, or security control terminology. The
|
||||
Assistant settings header naming external agent (MCP) connectors across
|
||||
locales is such product-settings copy: it describes what the page hosts and
|
||||
does not alter the scoped-token model, connector token handling, or any
|
||||
security/privacy disclosure. `MCP` stays untranslated as a protocol
|
||||
identifier. Self-hosted
|
||||
Plans & Billing header and navigation localization may share that same catalog
|
||||
boundary when it frames Pro setup as choosing Patrol autonomy; it must not
|
||||
alter API Access, authentication, privacy, or token-management terminology in
|
||||
|
||||
@@ -291,6 +291,22 @@ describe('settings architecture guardrails', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the external-agent (MCP) connector setup findable from sidebar search', () => {
|
||||
// The Assistant page hosts the pulse-mcp connector setup, but its label and
|
||||
// copy can't carry every term users search for. The nav item's search-only
|
||||
// keywords are the canonical bridge; losing them regresses "mcp"/"claude"
|
||||
// searches back to "No settings found".
|
||||
const assistantNavBlock = settingsNavCatalogSource.match(
|
||||
/id: 'system-ai-assistant',[\s\S]*?},/,
|
||||
);
|
||||
for (const keyword of ['mcp', 'external agent', 'claude', 'opencode', 'connector']) {
|
||||
expect(assistantNavBlock?.[0]).toContain(`'${keyword}'`);
|
||||
}
|
||||
expect(settingsHeaderMetaSource).toContain(
|
||||
'Configure Assistant chat behavior, chat action permissions, sessions, and external agent (MCP) connectors.',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps resource privacy route-backed instead of sidebar-promoted', () => {
|
||||
expect(settingsNavCatalogSource).toMatch(
|
||||
/id: 'security-data-handling',[\s\S]*label: 'Resource Privacy',[\s\S]*hideFromSidebar: true/,
|
||||
|
||||
+2
-1
@@ -149,7 +149,8 @@ describe('getSettingsHeaderMeta', () => {
|
||||
it('localizes system-ai-assistant to the English baseline (sibling only pinned es/de)', () => {
|
||||
expect(en['system-ai-assistant']).toEqual({
|
||||
title: 'Assistant',
|
||||
description: 'Configure Assistant chat behavior, chat action permissions, and sessions.',
|
||||
description:
|
||||
'Configure Assistant chat behavior, chat action permissions, sessions, and external agent (MCP) connectors.',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('settings localization catalog', () => {
|
||||
expect(spanishMeta['system-ai-assistant']).toEqual({
|
||||
title: 'Assistant',
|
||||
description:
|
||||
'Configura el comportamiento del chat, los permisos de acciones y las sesiones del Assistant.',
|
||||
'Configura el comportamiento del chat, los permisos de acciones, las sesiones y los conectores de agentes externos (MCP) del Assistant.',
|
||||
});
|
||||
expect(spanishMeta['system-billing']).toEqual({
|
||||
title: 'Planes y facturacion',
|
||||
@@ -99,7 +99,8 @@ describe('settings localization catalog', () => {
|
||||
});
|
||||
expect(getSettingsHeaderMeta('de')['system-ai-assistant']).toEqual({
|
||||
title: 'Assistant',
|
||||
description: 'Konfigurieren Sie Chatverhalten, Aktionsrechte und Sitzungen des Assistant.',
|
||||
description:
|
||||
'Konfigurieren Sie Chatverhalten, Aktionsrechte, Sitzungen und externe Agent-Verbindungen (MCP) des Assistant.',
|
||||
});
|
||||
expect(getSettingsHeaderMeta('de')['system-billing']).toEqual({
|
||||
title: 'Plaene & Abrechnung',
|
||||
|
||||
@@ -130,4 +130,28 @@ describe('useSettingsAccess', () => {
|
||||
expect(setActiveTabSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['mcp', 'claude', 'opencode', 'connector', 'external agent'])(
|
||||
'sidebar search %j surfaces the Assistant page hosting the external-agent connector setup',
|
||||
async (query) => {
|
||||
shouldHideSettingsNavItemMock.mockReturnValue(false);
|
||||
let access!: ReturnType<typeof useSettingsAccess>;
|
||||
render(() => {
|
||||
const [activeTab, setActiveTab] = createSignal('system-ai-assistant' as never);
|
||||
access = useSettingsAccess({
|
||||
activeTab,
|
||||
setActiveTab: setActiveTab as never,
|
||||
searchQuery: () => query,
|
||||
});
|
||||
return null;
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const matchedIds = access
|
||||
.filteredTabGroups()
|
||||
.flatMap((group) => group.items.map((item) => item.id));
|
||||
expect(matchedIds).toContain('system-ai-assistant');
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -42,7 +42,8 @@ export const SETTINGS_HEADER_META: SettingsHeaderMetaMap = {
|
||||
},
|
||||
'system-ai-assistant': {
|
||||
title: 'Assistant',
|
||||
description: 'Configure Assistant chat behavior, chat action permissions, and sessions.',
|
||||
description:
|
||||
'Configure Assistant chat behavior, chat action permissions, sessions, and external agent (MCP) connectors.',
|
||||
},
|
||||
'system-ai-discovery': {
|
||||
title: 'Service Context',
|
||||
|
||||
@@ -78,6 +78,16 @@ export const SETTINGS_NAV_GROUPS: SettingsNavGroup[] = [
|
||||
{
|
||||
id: 'system-ai-assistant',
|
||||
label: 'Assistant',
|
||||
keywords: [
|
||||
'mcp',
|
||||
'model context protocol',
|
||||
'external agent',
|
||||
'external agents',
|
||||
'claude',
|
||||
'opencode',
|
||||
'connector',
|
||||
'pulse-mcp',
|
||||
],
|
||||
icon: Terminal,
|
||||
iconProps: { strokeWidth: 2 },
|
||||
},
|
||||
|
||||
@@ -71,6 +71,10 @@ export type SettingsNavGroupId =
|
||||
export interface SettingsNavItem {
|
||||
id: SettingsTab;
|
||||
label: string;
|
||||
// Search-only aliases (never rendered). Lets sidebar search find a page by
|
||||
// terms its label/description can't carry, e.g. "mcp" or "claude" for the
|
||||
// Assistant page's external-agent connector setup.
|
||||
keywords?: string[];
|
||||
icon: Component<{ class?: string; strokeWidth?: number }>;
|
||||
iconProps?: { strokeWidth?: number };
|
||||
hideFromSidebar?: boolean;
|
||||
|
||||
@@ -144,7 +144,10 @@ export function useSettingsAccess({
|
||||
const matchLabel = item.label.toLowerCase().includes(q);
|
||||
const description = settingsHeaderMeta()[item.id]?.description?.toLowerCase() || '';
|
||||
const matchDesc = description.includes(q);
|
||||
return matchLabel || matchDesc;
|
||||
const matchKeyword = (item.keywords ?? []).some((keyword) =>
|
||||
keyword.toLowerCase().includes(q),
|
||||
);
|
||||
return matchLabel || matchDesc || matchKeyword;
|
||||
});
|
||||
return { ...group, items: filteredItems };
|
||||
})
|
||||
|
||||
@@ -403,7 +403,7 @@ export const DE_MESSAGE_OVERRIDES = {
|
||||
'Konfigurieren Sie Anbieter, Standardmodelle, Anbieterzustand, Budget und Nutzung fuer Pulse Intelligence.',
|
||||
'settings.header.systemAi.title': 'Anbieter & Modelle',
|
||||
'settings.header.systemAiAssistant.description':
|
||||
'Konfigurieren Sie Chatverhalten, Aktionsrechte und Sitzungen des Assistant.',
|
||||
'Konfigurieren Sie Chatverhalten, Aktionsrechte, Sitzungen und externe Agent-Verbindungen (MCP) des Assistant.',
|
||||
'settings.header.systemAiAssistant.title': 'Assistant',
|
||||
'settings.header.systemAiDiscovery.description':
|
||||
'Konfigurieren Sie den KI-gestuetzten Service-Kontext fuer Assistant und Patrol. Infrastruktur-Erkennung und Onboarding bleiben unter Infrastruktur.',
|
||||
|
||||
@@ -395,7 +395,7 @@ export const ES_MESSAGE_OVERRIDES = {
|
||||
'Configura proveedores, modelos predeterminados, salud de proveedores, presupuesto y uso para Pulse Intelligence.',
|
||||
'settings.header.systemAi.title': 'Proveedores y modelos',
|
||||
'settings.header.systemAiAssistant.description':
|
||||
'Configura el comportamiento del chat, los permisos de acciones y las sesiones del Assistant.',
|
||||
'Configura el comportamiento del chat, los permisos de acciones, las sesiones y los conectores de agentes externos (MCP) del Assistant.',
|
||||
'settings.header.systemAiAssistant.title': 'Assistant',
|
||||
'settings.header.systemAiDiscovery.description':
|
||||
'Configura el contexto de servicio asistido por IA que usan Assistant y Patrol. El descubrimiento y onboarding de infraestructura permanecen en Infraestructura.',
|
||||
|
||||
@@ -388,7 +388,7 @@ export const EN_MESSAGES = {
|
||||
'Configure providers, default models, provider health, budget, and usage for Pulse Intelligence.',
|
||||
'settings.header.systemAi.title': 'Provider & Models',
|
||||
'settings.header.systemAiAssistant.description':
|
||||
'Configure Assistant chat behavior, chat action permissions, and sessions.',
|
||||
'Configure Assistant chat behavior, chat action permissions, sessions, and external agent (MCP) connectors.',
|
||||
'settings.header.systemAiAssistant.title': 'Assistant',
|
||||
'settings.header.systemAiDiscovery.description':
|
||||
'Configure the model-backed service context Assistant and Patrol use. Infrastructure discovery and onboarding stay under Infrastructure.',
|
||||
|
||||
@@ -55,6 +55,14 @@ func PulseIntelligenceAssistantContextEvent(event config.AIUsageEventRecord) boo
|
||||
|
||||
// PulseIntelligenceExternalAgentEvidence is the content-free subset of
|
||||
// authenticated agent/MCP route activity used to prove external collaboration.
|
||||
//
|
||||
// Interpretation constraint: the agent_api surface counts any suitably scoped
|
||||
// API-token request to manifest-listed routes — including plain REST consumers
|
||||
// like scripts and dashboards hitting GET /api/config/nodes or
|
||||
// /api/ai/patrol/findings — so Used does NOT imply a BYO AI agent. Only
|
||||
// MCPAdapterUsed (the pulse_mcp surface header) certainly identifies an MCP
|
||||
// client. Do not read Used-vs-MCPAdapterUsed as an adoption funnel of the
|
||||
// same population.
|
||||
type PulseIntelligenceExternalAgentEvidence struct {
|
||||
Used bool
|
||||
MCPAdapterUsed bool
|
||||
|
||||
Reference in New Issue
Block a user