Custom Dashboards - Agent Driven Dashboards
This commit is contained in:
+56
-18
@@ -132,32 +132,70 @@ export const AGENT_SPECS = [
|
||||
]
|
||||
|
||||
/**
|
||||
* Whether this turn should fan out sub-agents.
|
||||
* Dynamic multi-agent gate — **single-agent by default**.
|
||||
* Only fan out when the user clearly wants a broad investigation (or multi-domain).
|
||||
*
|
||||
* @param {string} userText
|
||||
* @param {{ subAgents?: boolean }} prefs
|
||||
* @param {{ subAgents?: boolean, subAgentMode?: 'auto'|'always'|'never' }} prefs
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldUseSubAgents(userText, prefs = {}) {
|
||||
if (prefs.subAgents === false) return false
|
||||
const q = String(userText || '').toLowerCase()
|
||||
// Product how-tos: skip multi-agent (use local_knowledge only)
|
||||
if (prefs.subAgents === false || prefs.subAgentMode === 'never') return false
|
||||
if (prefs.subAgentMode === 'always' && prefs.subAgents !== false) {
|
||||
const q0 = String(userText || '').toLowerCase()
|
||||
// Still skip pure how-to / empty
|
||||
if (!q0.trim()) return false
|
||||
if (/how (do|to)|what is qvac|keyboard|time preset/.test(q0)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const q = String(userText || '').toLowerCase().trim()
|
||||
if (!q || q.length < 10) return false
|
||||
|
||||
// Product / how-to / chart-only / single tool asks → always single
|
||||
if (
|
||||
q.includes('how do') ||
|
||||
q.includes('how to') ||
|
||||
q.includes('what is qvac') ||
|
||||
q.includes('keyboard') ||
|
||||
q.includes('time preset')
|
||||
/how (do|to)|what is qvac|keyboard|time preset|retention work/.test(q)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// Default on for operational questions when feature enabled
|
||||
if (prefs.subAgents === true || prefs.subAgents == null) {
|
||||
return (
|
||||
/health|wrong|diagnos|investigat|summar|status|cpu|ram|memory|disk|alert|anomal|process|load|hot|spik|fleet|storage|retention|metric|chart|nginx|redis|docker|postgres|io\b/.test(
|
||||
q
|
||||
) || q.length < 4
|
||||
)
|
||||
// UI chart/dashboard building without investigation language
|
||||
if (
|
||||
/\b(show|plot|graph|open|pin|filter|dashboard)\b/.test(q) &&
|
||||
!/\b(investigat|diagnos|what'?s wrong|root cause|health check|full (scan|check))\b/.test(q)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
|
||||
// Explicit multi-agent / deep investigation
|
||||
if (
|
||||
/\b(multi[- ]?agent|investigat|investigation|diagnos(e|is|tic)?|what'?s wrong|whats wrong|what is wrong|root cause|deep dive|full (health|check|scan)|run (all|every) agent|spin up agent)\b/.test(
|
||||
q
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Broad host health summaries
|
||||
if (
|
||||
/\b(summarize host|host health|health (check|status|summary)|overall status|everything (wrong|ok)|whole (host|system))\b/.test(
|
||||
q
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Multi-domain operational question (3+ areas) → worth parallel specialists
|
||||
const domains = [
|
||||
/\b(cpu|load)\b/,
|
||||
/\b(ram|memory)\b/,
|
||||
/\b(disk|io|storage|iops)\b/,
|
||||
/\b(alert|anomal)/,
|
||||
/\b(process|pid|top)\b/,
|
||||
/\b(fleet|child peer)/,
|
||||
/\b(net|network)\b/,
|
||||
]
|
||||
const hits = domains.reduce((n, re) => n + (re.test(q) ? 1 : 0), 0)
|
||||
return hits >= 3
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -63,6 +63,7 @@ export function createQvacView(opts) {
|
||||
onOpenChart: opts.onOpenChart,
|
||||
onOpenView: opts.onOpenView,
|
||||
charts: opts.charts,
|
||||
dashboards: opts.dashboards,
|
||||
confirmAction: (msg) => {
|
||||
try {
|
||||
return typeof confirm === 'function' ? confirm(msg) : true
|
||||
|
||||
+5
-2
@@ -30,15 +30,17 @@ export function buildSystemPrompt(ctx = {}) {
|
||||
'- Do not claim cloud access; all inference is local via QVAC.',
|
||||
'- Prefer show_charts / open_chart / open_view when the user asks to see graphs or the UI.',
|
||||
multi
|
||||
? '- Multi-agent mode: specialist investigators may already have collected a briefing. Trust those facts; only call extra tools for gaps.'
|
||||
? '- Multi-agent is DYNAMIC: the host only fans out specialists for broad investigations (what\'s wrong, full health, multi-domain). Single questions use normal tools only.'
|
||||
: '',
|
||||
'',
|
||||
'Tool playbook (use these exact tool names and chart ids):',
|
||||
'- Host health / "what\'s wrong" / diagnose / investigate → investigate_host FIRST (one-shot findings).',
|
||||
' Lighter alternative: host_snapshot. Do NOT use md.health for general health.',
|
||||
'- "Show me a graph/chart/plot" → search_charts if needed, then show_charts with concrete chart ids.',
|
||||
' show_charts pins to the board, sets window (preset=5m|1h|…), optional mode=line|area|bar|pie, focus.',
|
||||
' show_charts pins to the Charts wall board, sets window (preset=5m|1h|…), optional mode=line|area|bar|pie.',
|
||||
' Example: show_charts charts=system.cpu,system.ram preset=15m mode=area boardOnly=true.',
|
||||
'- Custom Dashboard tab (saved boards) → create_dashboard name=… charts=system.cpu,system.ram',
|
||||
' then open_dashboard. Edit with add_dashboard_charts / remove_dashboard_charts / update_dashboard / list_dashboards.',
|
||||
'- Single chart focus → open_chart chart=system.cpu (optional pin, mode, preset, ts).',
|
||||
'- Pin/unpin board → pin_charts; change type → set_chart_type; window → set_time_window; search wall → filter_charts.',
|
||||
'- Related wall panel → show_related_ui; Metric Correlations UI → run_correlations.',
|
||||
@@ -69,6 +71,7 @@ export const SAMPLE_PROMPTS = [
|
||||
'Summarize host health right now',
|
||||
"What's wrong — investigate this host",
|
||||
'Show me live graphs for CPU and RAM (15m)',
|
||||
'Create a dashboard named SRE Core with CPU, RAM, load, and disk IO',
|
||||
'Plot disk / io charts as an area board',
|
||||
'Why might CPU be high?',
|
||||
'Show hot / spiking metrics and open them',
|
||||
|
||||
+180
-1
@@ -291,7 +291,7 @@ export const TOOL_DEFS = [
|
||||
name: 'open_view',
|
||||
tier: 'core',
|
||||
description:
|
||||
'Navigate desktop to a view: overview|charts|processes|alerts|logs|fleet|settings|qvac',
|
||||
'Navigate desktop to a view: overview|charts|dashboard|processes|alerts|logs|fleet|settings|qvac',
|
||||
parameters: params(
|
||||
{
|
||||
view: {
|
||||
@@ -300,6 +300,7 @@ export const TOOL_DEFS = [
|
||||
enum: [
|
||||
'overview',
|
||||
'charts',
|
||||
'dashboard',
|
||||
'processes',
|
||||
'alerts',
|
||||
'logs',
|
||||
@@ -313,6 +314,112 @@ export const TOOL_DEFS = [
|
||||
),
|
||||
},
|
||||
|
||||
// ── custom dashboards (user boards the agent can build/edit) ──────────
|
||||
{
|
||||
type: 'function',
|
||||
name: 'list_dashboards',
|
||||
tier: 'core',
|
||||
description: 'List custom dashboards (id, name, chart tiles).',
|
||||
parameters: params({}),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'create_dashboard',
|
||||
tier: 'core',
|
||||
description:
|
||||
'Create a custom live dashboard and open it. Pass name + charts (comma ids). Use for "build a dashboard with CPU and RAM".',
|
||||
parameters: params(
|
||||
{
|
||||
name: { type: 'string', description: 'Dashboard name' },
|
||||
description: { type: 'string', description: 'Optional description' },
|
||||
charts: {
|
||||
type: 'string',
|
||||
description: 'Comma/space chart ids e.g. system.cpu,system.ram,system.io',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Default tile mode line|area|bar|…',
|
||||
enum: ['line', 'area', 'stacked', 'bar', 'multibar', 'pie'],
|
||||
},
|
||||
},
|
||||
['name']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'update_dashboard',
|
||||
tier: 'core',
|
||||
description: 'Rename/update a dashboard (id optional = active). Can replace tiles via charts list.',
|
||||
parameters: params({
|
||||
id: { type: 'string', description: 'Dashboard id' },
|
||||
name: { type: 'string', description: 'New name' },
|
||||
description: { type: 'string', description: 'New description' },
|
||||
charts: {
|
||||
type: 'string',
|
||||
description: 'If set, replace all tiles with these chart ids',
|
||||
},
|
||||
mode: { type: 'string', description: 'Mode when replacing tiles' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'delete_dashboard',
|
||||
tier: 'core',
|
||||
description: 'Delete a custom dashboard by id.',
|
||||
parameters: params(
|
||||
{
|
||||
id: { type: 'string', description: 'Dashboard id' },
|
||||
},
|
||||
['id']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'add_dashboard_charts',
|
||||
tier: 'core',
|
||||
description: 'Add chart tiles to a dashboard (id optional = active board).',
|
||||
parameters: params(
|
||||
{
|
||||
id: { type: 'string', description: 'Dashboard id (default active)' },
|
||||
charts: {
|
||||
type: 'string',
|
||||
description: 'Comma/space chart ids to add',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Tile chart type',
|
||||
enum: ['line', 'area', 'stacked', 'bar', 'multibar', 'pie'],
|
||||
},
|
||||
},
|
||||
['charts']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'remove_dashboard_charts',
|
||||
tier: 'core',
|
||||
description: 'Remove chart tiles from a dashboard.',
|
||||
parameters: params(
|
||||
{
|
||||
id: { type: 'string', description: 'Dashboard id (default active)' },
|
||||
charts: {
|
||||
type: 'string',
|
||||
description: 'Comma/space chart ids to remove',
|
||||
},
|
||||
},
|
||||
['charts']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'open_dashboard',
|
||||
tier: 'core',
|
||||
description: 'Open the Dashboard tab and select a board by id (or active).',
|
||||
parameters: params({
|
||||
id: { type: 'string', description: 'Dashboard id (optional)' },
|
||||
}),
|
||||
},
|
||||
|
||||
// ── deep: investigation, fleet, storage, catalog ───────────────────
|
||||
{
|
||||
type: 'function',
|
||||
@@ -582,6 +689,13 @@ const LOCAL_TOOLS = new Set([
|
||||
'filter_charts',
|
||||
'show_related_ui',
|
||||
'run_correlations',
|
||||
'list_dashboards',
|
||||
'create_dashboard',
|
||||
'update_dashboard',
|
||||
'delete_dashboard',
|
||||
'add_dashboard_charts',
|
||||
'remove_dashboard_charts',
|
||||
'open_dashboard',
|
||||
'local_knowledge',
|
||||
])
|
||||
|
||||
@@ -646,6 +760,15 @@ function wireDefs(defs) {
|
||||
* runMetricCorrelations?: (opts?: object) => Promise<any>,
|
||||
* scrollToChart?: (id: string) => void,
|
||||
* },
|
||||
* dashboards?: {
|
||||
* list?: () => object,
|
||||
* create?: (a: object) => object,
|
||||
* update?: (a: object) => object,
|
||||
* remove?: (a: object) => object,
|
||||
* addCharts?: (a: object) => object,
|
||||
* removeCharts?: (a: object) => object,
|
||||
* open?: (a: object) => object,
|
||||
* },
|
||||
* confirmAction?: (message: string) => boolean|Promise<boolean>,
|
||||
* }} deps
|
||||
*/
|
||||
@@ -906,6 +1029,62 @@ export function createToolRunner(deps) {
|
||||
deps.onOpenView?.(String(args.view || 'overview'))
|
||||
return { ok: true, view: args.view }
|
||||
}
|
||||
case 'list_dashboards': {
|
||||
if (!deps.dashboards?.list) return { error: 'dashboards API unavailable' }
|
||||
return deps.dashboards.list()
|
||||
}
|
||||
case 'create_dashboard': {
|
||||
if (!deps.dashboards?.create) return { error: 'dashboards API unavailable' }
|
||||
const r = deps.dashboards.create({
|
||||
name: args.name,
|
||||
description: args.description,
|
||||
charts: args.charts || args.chart,
|
||||
mode: args.mode || 'area',
|
||||
tiles: args.tiles,
|
||||
})
|
||||
deps.onOpenView?.('dashboard')
|
||||
return r
|
||||
}
|
||||
case 'update_dashboard': {
|
||||
if (!deps.dashboards?.update) return { error: 'dashboards API unavailable' }
|
||||
return deps.dashboards.update({
|
||||
id: args.id || args.dashboardId,
|
||||
name: args.name,
|
||||
description: args.description,
|
||||
charts: args.charts,
|
||||
mode: args.mode,
|
||||
tiles: args.tiles,
|
||||
})
|
||||
}
|
||||
case 'delete_dashboard': {
|
||||
if (!deps.dashboards?.remove) return { error: 'dashboards API unavailable' }
|
||||
return deps.dashboards.remove({ id: args.id || args.dashboardId })
|
||||
}
|
||||
case 'add_dashboard_charts': {
|
||||
if (!deps.dashboards?.addCharts) return { error: 'dashboards API unavailable' }
|
||||
const r = deps.dashboards.addCharts({
|
||||
id: args.id || args.dashboardId,
|
||||
charts: args.charts || args.chart,
|
||||
mode: args.mode,
|
||||
replace: args.replace,
|
||||
})
|
||||
deps.onOpenView?.('dashboard')
|
||||
return r
|
||||
}
|
||||
case 'remove_dashboard_charts': {
|
||||
if (!deps.dashboards?.removeCharts) return { error: 'dashboards API unavailable' }
|
||||
return deps.dashboards.removeCharts({
|
||||
id: args.id || args.dashboardId,
|
||||
charts: args.charts || args.chart,
|
||||
})
|
||||
}
|
||||
case 'open_dashboard': {
|
||||
if (!deps.dashboards?.open) {
|
||||
deps.onOpenView?.('dashboard')
|
||||
return { ok: true, view: 'dashboard' }
|
||||
}
|
||||
return deps.dashboards.open({ id: args.id || args.dashboardId })
|
||||
}
|
||||
case 'silence_alert': {
|
||||
if (!args.confirmed) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user