|
27 | 27 | * and is rendered in transcript order. |
28 | 28 | */ |
29 | 29 | import { evidenceRefSchema, evidenceSchema } from "./evidence.js"; |
| 30 | +import { runFiltersSchema } from "./run-filters.js"; |
30 | 31 | import { triggerUriSchema } from "./trigger-uri.js"; |
31 | 32 | import { z } from "zod"; |
32 | 33 |
|
@@ -150,6 +151,42 @@ export const diagnosisBlockBodySchema = z.object({ |
150 | 151 | // chart |
151 | 152 | // --------------------------------------------------------------------------- |
152 | 153 |
|
| 154 | +/** |
| 155 | + * A button under a chart. The chart answers "which task failed most"; the |
| 156 | + * actions are what to do about the winner — investigate it, or go look at its |
| 157 | + * runs. The card only *emits* the intent; the host decides whether to honour it, |
| 158 | + * the same rule every other intent follows. |
| 159 | + */ |
| 160 | +/** |
| 161 | + * A chart action's intent. Mirrors `agentIntentSchema`, but the navigate |
| 162 | + * `target` is a plain string at this boundary — the model can't always build a |
| 163 | + * canonical URI (the grammar embeds ids it doesn't hold), and a malformed |
| 164 | + * target must cost one button, not the whole tool call. The `render_view` |
| 165 | + * executor drops navigate actions whose target isn't a valid trigger:// URI. |
| 166 | + */ |
| 167 | +const chartActionIntentSchema = z.union([ |
| 168 | + z.object({ |
| 169 | + kind: z.literal("ask"), |
| 170 | + prompt: z.string().min(1), |
| 171 | + }), |
| 172 | + z.object({ |
| 173 | + kind: z.literal("navigate"), |
| 174 | + target: z.string().min(1), |
| 175 | + filters: runFiltersSchema.optional(), |
| 176 | + }), |
| 177 | +]); |
| 178 | + |
| 179 | +export const chartActionSchema = z.object({ |
| 180 | + label: z |
| 181 | + .string() |
| 182 | + .describe("The button text, naming the thing, e.g. 'Investigate send-order-receipt'."), |
| 183 | + intent: chartActionIntentSchema.describe( |
| 184 | + "What the button does. `ask` is the default and always works: phrase the user's own follow-up in their voice ('Investigate the send-order-receipt failures — why are they failing?'), and the click sends it as their next message. `navigate` takes them to the matching page — ONLY when you already hold a canonical `trigger://` URI for it (e.g. one a tool returned); an invalid target is silently dropped, so when in doubt use `ask`." |
| 185 | + ), |
| 186 | +}); |
| 187 | + |
| 188 | +export type ChartAction = z.infer<typeof chartActionSchema>; |
| 189 | + |
153 | 190 | // The chart block carries the TRQL query (not the rows): the panel runs it |
154 | 191 | // through the dashboard's own query execution + QueryResultsChart, so the chart |
155 | 192 | // is live and matches the Query page exactly. The agent describes the chart with |
@@ -197,6 +234,18 @@ export const chartBlockBodySchema = z.object({ |
197 | 234 | .enum(["sum", "avg", "count", "min", "max"]) |
198 | 235 | .optional() |
199 | 236 | .describe("How to combine values that share an x point. Default sum."), |
| 237 | + /** |
| 238 | + * Optional and capped at three: a chart that ranks things gets a way to act on |
| 239 | + * the winner. Older stored charts have no `actions` at all and must keep |
| 240 | + * parsing, so this is additive and never required. |
| 241 | + */ |
| 242 | + actions: z |
| 243 | + .array(chartActionSchema) |
| 244 | + .max(3) |
| 245 | + .optional() |
| 246 | + .describe( |
| 247 | + "Optional buttons under the chart, at most 2-3. After a ranking or failures chart, give the top item an 'Investigate <name>' ask action, and a navigate action to the page that shows it (its filtered runs list, its error, its queue) when you have the target." |
| 248 | + ), |
200 | 249 | }); |
201 | 250 |
|
202 | 251 | // --------------------------------------------------------------------------- |
|
0 commit comments