Agent Charts
This commit is contained in:
+290
-7
@@ -129,15 +129,163 @@ export const TOOL_DEFS = [
|
||||
type: 'function',
|
||||
name: 'open_chart',
|
||||
tier: 'core',
|
||||
description: 'Navigate the desktop UI to a chart (optional pause near timestamp ms).',
|
||||
description:
|
||||
'Open one chart on the Charts wall (scroll + focus). Prefer show_charts when pinning multiple or changing window/type.',
|
||||
parameters: params(
|
||||
{
|
||||
chart: { type: 'string', description: 'Chart id' },
|
||||
ts: { type: 'number', description: 'Event time ms' },
|
||||
ts: { type: 'number', description: 'Event time ms (pauses near event)' },
|
||||
pin: { type: 'boolean', description: 'Pin chart to board (default true)' },
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Chart type: line|area|stacked|bar|multibar|pie',
|
||||
enum: ['line', 'area', 'stacked', 'bar', 'multibar', 'pie'],
|
||||
},
|
||||
preset: {
|
||||
type: 'string',
|
||||
description: 'Time preset e.g. 5m|15m|1h|6h|24h',
|
||||
},
|
||||
},
|
||||
['chart']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'show_charts',
|
||||
tier: 'core',
|
||||
description:
|
||||
'PRIMARY live chart tool. Opens Charts view, pins charts to the board, sets time window/type, optional focus and related panel. Use for "show me graphs", "plot CPU", multi-chart boards.',
|
||||
parameters: params(
|
||||
{
|
||||
charts: {
|
||||
type: 'string',
|
||||
description: 'Comma/space separated chart ids (max 24), e.g. system.cpu,system.ram',
|
||||
},
|
||||
pin: {
|
||||
type: 'boolean',
|
||||
description: 'Pin charts to board (default true)',
|
||||
},
|
||||
boardOnly: {
|
||||
type: 'boolean',
|
||||
description: 'Show only pinned board (default true when pinning)',
|
||||
},
|
||||
focus: { type: 'string', description: 'Chart id to focus/expand' },
|
||||
ts: { type: 'number', description: 'Event time ms for focus' },
|
||||
preset: {
|
||||
type: 'string',
|
||||
description: 'Time preset: 5m|15m|30m|1h|3h|6h|12h|24h',
|
||||
},
|
||||
seconds: {
|
||||
type: 'number',
|
||||
description: 'Custom lookback window seconds (alternative to preset)',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'Apply chart type to all listed charts',
|
||||
enum: ['line', 'area', 'stacked', 'bar', 'multibar', 'pie'],
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
description: 'Charts wall search filter text',
|
||||
},
|
||||
related: {
|
||||
type: 'boolean',
|
||||
description: 'Open related-charts panel for focus chart',
|
||||
},
|
||||
playing: {
|
||||
type: 'boolean',
|
||||
description: 'Live play (true) or pause (false)',
|
||||
},
|
||||
},
|
||||
['charts']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'pin_charts',
|
||||
tier: 'core',
|
||||
description: 'Pin or unpin charts on the Charts board without changing focus.',
|
||||
parameters: params(
|
||||
{
|
||||
charts: {
|
||||
type: 'string',
|
||||
description: 'Comma/space separated chart ids',
|
||||
},
|
||||
pin: {
|
||||
type: 'boolean',
|
||||
description: 'true=pin (default), false=unpin',
|
||||
},
|
||||
},
|
||||
['charts']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'set_chart_type',
|
||||
tier: 'core',
|
||||
description: 'Change visualization for a chart: line, area, stacked, bar, multibar, pie.',
|
||||
parameters: params(
|
||||
{
|
||||
chart: { type: 'string', description: 'Chart id' },
|
||||
mode: {
|
||||
type: 'string',
|
||||
description: 'line|area|stacked|bar|multibar|pie',
|
||||
enum: ['line', 'area', 'stacked', 'bar', 'multibar', 'pie'],
|
||||
},
|
||||
},
|
||||
['chart', 'mode']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'set_time_window',
|
||||
tier: 'core',
|
||||
description: 'Set Charts wall time window (preset or seconds) and play/pause live.',
|
||||
parameters: params({
|
||||
preset: {
|
||||
type: 'string',
|
||||
description: '5m|15m|30m|1h|3h|6h|12h|24h',
|
||||
},
|
||||
seconds: { type: 'number', description: 'Lookback seconds' },
|
||||
playing: { type: 'boolean', description: 'Live updates on/off' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'filter_charts',
|
||||
tier: 'core',
|
||||
description: 'Filter the Charts wall by free text (same as the search box).',
|
||||
parameters: params(
|
||||
{
|
||||
q: { type: 'string', description: 'Filter query (empty clears)' },
|
||||
},
|
||||
['q']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'show_related_ui',
|
||||
tier: 'core',
|
||||
description: 'Open the Related charts panel for a seed chart on the Charts wall.',
|
||||
parameters: params(
|
||||
{
|
||||
chart: { type: 'string', description: 'Seed chart id' },
|
||||
},
|
||||
['chart']
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'run_correlations',
|
||||
tier: 'deep',
|
||||
description:
|
||||
'Run Metric Correlations around a chart or timestamp and show results on the Charts wall.',
|
||||
parameters: params({
|
||||
chart: { type: 'string', description: 'Optional seed chart id' },
|
||||
ts: { type: 'number', description: 'Center timestamp ms (default now)' },
|
||||
window: { type: 'number', description: 'Highlight window seconds' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
name: 'open_view',
|
||||
@@ -423,8 +571,19 @@ export const TOOL_DEFS = [
|
||||
},
|
||||
]
|
||||
|
||||
/** Tools that work without an agent connection. */
|
||||
const LOCAL_TOOLS = new Set(['open_view', 'open_chart', 'local_knowledge'])
|
||||
/** Tools that work without an agent connection (desktop UI / local RAG). */
|
||||
const LOCAL_TOOLS = new Set([
|
||||
'open_view',
|
||||
'open_chart',
|
||||
'show_charts',
|
||||
'pin_charts',
|
||||
'set_chart_type',
|
||||
'set_time_window',
|
||||
'filter_charts',
|
||||
'show_related_ui',
|
||||
'run_correlations',
|
||||
'local_knowledge',
|
||||
])
|
||||
|
||||
/** Operator write tools. */
|
||||
const WRITE_TOOLS = new Set(
|
||||
@@ -473,12 +632,32 @@ function wireDefs(defs) {
|
||||
* getRole: () => string,
|
||||
* isConnected: () => boolean,
|
||||
* getCatalog?: () => Record<string, object>,
|
||||
* onOpenChart?: (chartId: string, ts?: number) => void,
|
||||
* onOpenChart?: (chartId: string, ts?: number, opts?: object) => void,
|
||||
* onOpenView?: (view: string) => void,
|
||||
* charts?: {
|
||||
* showCharts?: (opts: object) => object,
|
||||
* setPinned?: (ids: string|string[], opts?: object) => object,
|
||||
* setChartMode?: (id: string, mode: string) => object,
|
||||
* setTimeWindow?: (opts: object) => object,
|
||||
* setFilter?: (q: string) => object,
|
||||
* showRelated?: (id: string) => Promise<void>|void,
|
||||
* openFocus?: (id: string) => void,
|
||||
* correlateAround?: (ts: number, opts?: object) => void,
|
||||
* runMetricCorrelations?: (opts?: object) => Promise<any>,
|
||||
* scrollToChart?: (id: string) => void,
|
||||
* },
|
||||
* confirmAction?: (message: string) => boolean|Promise<boolean>,
|
||||
* }} deps
|
||||
*/
|
||||
export function createToolRunner(deps) {
|
||||
function parseChartList(raw) {
|
||||
if (Array.isArray(raw)) return raw.map(String).filter(Boolean)
|
||||
return String(raw || '')
|
||||
.split(/[\s,]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {object} args
|
||||
@@ -494,6 +673,7 @@ export function createToolRunner(deps) {
|
||||
}
|
||||
}
|
||||
const req = (m, a) => deps.manager.request(m, a || {})
|
||||
const chartsApi = deps.charts || {}
|
||||
|
||||
try {
|
||||
switch (name) {
|
||||
@@ -616,8 +796,111 @@ export function createToolRunner(deps) {
|
||||
return { context: ctx || 'No matching local knowledge.', q: args.q }
|
||||
}
|
||||
case 'open_chart': {
|
||||
deps.onOpenChart?.(String(args.chart), args.ts)
|
||||
return { ok: true, opened: args.chart }
|
||||
const chart = String(args.chart || args.id || '')
|
||||
if (!chart) return { error: 'chart required' }
|
||||
deps.onOpenView?.('charts')
|
||||
if (chartsApi.showCharts) {
|
||||
return chartsApi.showCharts({
|
||||
charts: [chart],
|
||||
pin: args.pin !== false,
|
||||
boardOnly: args.pin !== false,
|
||||
focus: chart,
|
||||
ts: args.ts,
|
||||
mode: args.mode,
|
||||
preset: args.preset,
|
||||
openFocus: true,
|
||||
})
|
||||
}
|
||||
deps.onOpenChart?.(chart, args.ts, {
|
||||
pin: args.pin !== false,
|
||||
mode: args.mode,
|
||||
preset: args.preset,
|
||||
})
|
||||
return { ok: true, opened: chart }
|
||||
}
|
||||
case 'show_charts': {
|
||||
const list = parseChartList(args.charts || args.chart || args.ids)
|
||||
if (!list.length) return { error: 'charts required' }
|
||||
deps.onOpenView?.('charts')
|
||||
if (!chartsApi.showCharts) {
|
||||
// Fallback: open first chart only
|
||||
deps.onOpenChart?.(list[0], args.ts)
|
||||
return { ok: true, charts: list, partial: true }
|
||||
}
|
||||
return chartsApi.showCharts({
|
||||
charts: list,
|
||||
pin: args.pin !== false,
|
||||
boardOnly: args.boardOnly != null ? Boolean(args.boardOnly) : args.pin !== false,
|
||||
focus: args.focus || list[0],
|
||||
ts: args.ts,
|
||||
preset: args.preset,
|
||||
seconds: args.seconds,
|
||||
mode: args.mode,
|
||||
filter: args.filter,
|
||||
related: Boolean(args.related),
|
||||
playing: args.playing,
|
||||
openFocus: list.length === 1,
|
||||
})
|
||||
}
|
||||
case 'pin_charts': {
|
||||
const list = parseChartList(args.charts || args.chart)
|
||||
if (!list.length) return { error: 'charts required' }
|
||||
deps.onOpenView?.('charts')
|
||||
if (!chartsApi.setPinned) return { error: 'charts API unavailable' }
|
||||
return chartsApi.setPinned(list, {
|
||||
pin: args.pin !== false && args.pin !== 'false',
|
||||
})
|
||||
}
|
||||
case 'set_chart_type': {
|
||||
const chart = String(args.chart || args.id || '')
|
||||
const mode = String(args.mode || args.type || 'line')
|
||||
if (!chart) return { error: 'chart required' }
|
||||
deps.onOpenView?.('charts')
|
||||
if (!chartsApi.setChartMode) return { error: 'charts API unavailable' }
|
||||
return chartsApi.setChartMode(chart, mode)
|
||||
}
|
||||
case 'set_time_window': {
|
||||
deps.onOpenView?.('charts')
|
||||
if (!chartsApi.setTimeWindow) return { error: 'charts API unavailable' }
|
||||
return chartsApi.setTimeWindow({
|
||||
preset: args.preset,
|
||||
seconds: args.seconds ?? args.after,
|
||||
playing: args.playing,
|
||||
})
|
||||
}
|
||||
case 'filter_charts': {
|
||||
deps.onOpenView?.('charts')
|
||||
if (!chartsApi.setFilter) return { error: 'charts API unavailable' }
|
||||
return chartsApi.setFilter(args.q != null ? args.q : args.filter || '')
|
||||
}
|
||||
case 'show_related_ui': {
|
||||
const chart = String(args.chart || args.id || '')
|
||||
if (!chart) return { error: 'chart required' }
|
||||
deps.onOpenView?.('charts')
|
||||
chartsApi.scrollToChart?.(chart)
|
||||
await chartsApi.showRelated?.(chart)
|
||||
return { ok: true, chart, related: true }
|
||||
}
|
||||
case 'run_correlations': {
|
||||
deps.onOpenView?.('charts')
|
||||
const ts =
|
||||
args.ts != null ? Number(args.ts) : Date.now()
|
||||
if (chartsApi.correlateAround) {
|
||||
chartsApi.correlateAround(ts, {
|
||||
chart: args.chart,
|
||||
window: args.window,
|
||||
})
|
||||
return { ok: true, ts, chart: args.chart || null }
|
||||
}
|
||||
if (chartsApi.runMetricCorrelations) {
|
||||
const res = await chartsApi.runMetricCorrelations({
|
||||
chart: args.chart,
|
||||
ts,
|
||||
window: args.window,
|
||||
})
|
||||
return { ok: true, ...(res || {}) }
|
||||
}
|
||||
return { error: 'correlations API unavailable' }
|
||||
}
|
||||
case 'open_view': {
|
||||
deps.onOpenView?.(String(args.view || 'overview'))
|
||||
|
||||
Reference in New Issue
Block a user