@agent-native/core-corpus 0.204.0 → 0.204.1-nightly-20261008034100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
  4. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +3 -3
  5. package/corpus/templates/analytics/AGENTS.md +2 -2
  6. package/corpus/templates/analytics/actions/bigquery.ts +16 -3
  7. package/corpus/templates/analytics/actions/mutate-dashboard.ts +9 -5
  8. package/corpus/templates/analytics/actions/search-bigquery-schema.ts +31 -150
  9. package/corpus/templates/analytics/changelog/2026-10-07-analytics-answers-now-acknowledge-when-reference-catalogs-ar.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-10-07-analytics-chat-follows-short-follow-ups-like-what-about-emea.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-10-07-analytics-chat-no-longer-stays-on-running-after-the-agent-finishes.md +6 -0
  12. package/corpus/templates/analytics/changelog/2026-10-07-analytics-chat-stops-retrying-an-unconnected-source.md +6 -0
  13. package/corpus/templates/analytics/changelog/2026-10-07-analytics-thumbs-down-has-reason-chips-and-copy-details.md +6 -0
  14. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +1 -0
  15. package/corpus/templates/analytics/server/lib/analytics-agent-context.ts +84 -13
  16. package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +104 -13
  17. package/corpus/templates/analytics/server/lib/bigquery-schema-recovery.ts +263 -0
  18. package/corpus/templates/analytics/server/lib/bigquery.ts +158 -0
  19. package/corpus/templates/analytics/server/lib/real-data-actions.ts +169 -25
  20. package/corpus/templates/analytics/server/plugins/agent-chat.ts +26 -11
  21. package/corpus/templates/chat/app/components/chat/ChatRouteContent.tsx +8 -1
  22. package/corpus/templates/content/actions/_preview-document-draft.ts +29 -0
  23. package/corpus/templates/content/actions/get-document.ts +29 -1
  24. package/corpus/templates/content/actions/get-preview-document-draft.ts +2 -21
  25. package/corpus/templates/content/actions/record-live-body-parity.ts +69 -0
  26. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +4 -0
  27. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +43 -1
  28. package/corpus/templates/content/app/components/editor/live-body-parity.ts +77 -0
  29. package/corpus/templates/content/app/hooks/use-documents.ts +69 -20
  30. package/corpus/templates/content/app/lib/page-open-reads.ts +4 -2
  31. package/corpus/templates/content/changelog/2026-10-06-pages-you-open-from-home-appear-without-waiting-on-the-unsav.md +6 -0
  32. package/corpus/templates/content/docs/product/capabilities/content.author.document-editor.md +3 -1
  33. package/corpus/templates/content/e2e/convergence-summary.ts +21 -9
  34. package/corpus/templates/content/e2e/helpers.ts +70 -14
  35. package/corpus/templates/content/parity/matrix.md +1 -1
  36. package/corpus/templates/content/parity/matrix.ts +2 -1
  37. package/corpus/templates/content/server/plugins/feature-flags.ts +5 -0
  38. package/corpus/templates/content/shared/feature-flags.ts +17 -0
  39. package/corpus/templates/content/shared/live-body.ts +98 -0
  40. package/corpus/templates/dispatch/.agents/skills/automations/SKILL.md +8 -1
  41. package/corpus/templates/dispatch/.agents/skills/recurring-jobs/SKILL.md +3 -1
  42. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 11386
35
+ - template files: 11398
@@ -36,7 +36,7 @@ Analytics result or direct the user to an Extensions page.
36
36
 
37
37
  ### Step 1: Understand the Question (catalog-first, clarify-first)
38
38
 
39
- Orient before gathering data. Consult the injected `<data-dictionary>` and data-source status first to see which sources are configured and which one owns each fact, then settle scope:
39
+ Orient before gathering data. Consult the preloaded `<resource scope="analytics-catalog">` references (else one `search-analytics-query-catalog`) and data-source status first to see which sources are configured and which one owns each fact, then settle scope:
40
40
 
41
41
  - What is being analyzed? (deals, users, campaigns, errors, etc.)
42
42
  - What time range?
@@ -34,7 +34,8 @@ lock and contract-metric rules before querying the warehouse.
34
34
 
35
35
  Orient before fanning out:
36
36
 
37
- 1. Read the injected `<data-dictionary>` and check data-source status to see
37
+ 1. Read the preloaded `<resource scope="analytics-catalog">` references (else
38
+ one `search-analytics-query-catalog`) and check data-source status to see
38
39
  which sources are configured and what each one actually holds.
39
40
  2. Map each fact in the question to the one source that owns it. Write a tiny
40
41
  plan: "identities + emails from HubSpot, usage from BigQuery, first-party
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: dashboard-management
3
3
  description: >-
4
- Create, edit, organize, or share Analytics dashboards and panels: sources, layout, mutate-dashboard edits and verification. Use before any dashboard or panel change.
4
+ Create, lay out, organize, or share Analytics dashboards and panels: sources, placement, folders, verification. Use to create a dashboard or to move, reorder, or lay out panels; a small edit of one existing panel needs no skill.
5
5
  ---
6
6
 
7
7
  # Dashboard Management
@@ -117,9 +117,9 @@ an ordinary activity scan.
117
117
 
118
118
  When the user asks for a dashboard:
119
119
 
120
- 1. Read the injected `<data-dictionary>` block first (catalog-first). If relevant entries exist, use their `table`, `columns`, `queryTemplate`, and gotchas verbatim.
120
+ 1. Start from the preloaded `<resource scope="analytics-catalog">` references (catalog-first), else one `search-analytics-query-catalog`. If a relevant entry exists, use its `table`, `columns`, `queryTemplate`, and gotchas verbatim.
121
121
  2. If a metric definition, date range, or grain is ambiguous and the choice would change the panel's numbers, use the `ask-question` clarifying tool once before building. Skip it when the dictionary or the user already settled it.
122
- 3. If a metric is not documented, do not guess column names. Ask for the table/columns or introspect the provider schema, then propose a dictionary entry with `save-data-dictionary-entry`.
122
+ 3. If a metric is not documented, do not guess column names and do not ask the user for them. Find the table and columns with `search-bigquery-schema` (or the provider's own schema action), then propose a dictionary entry with `save-data-dictionary-entry`.
123
123
  4. Build a complete `SqlDashboardConfig` with `name` and `panels`. Optionally set top-level `columns` (1–6, default 2) to control how many grid columns the panels before any section use.
124
124
  5. Every panel needs `id`, `title`, `source`, `chartType`, `width`, and `sql`. `width` is the number of grid columns the panel spans (1..6, clamped to the active section's column count). Section panels skip `source` and `sql` and may set their own `columns` (1–6) to override the dashboard default for the panels following the section. Extension panels (`chartType: "extension"`) also skip `source` and `sql`; use `config.extensionId` for ordinary author-selected shared embeds. Use `config.extensionSlotId` only when the user explicitly asks for a personal/per-viewer slot (see "Embedding An Extension As A Panel").
125
125
  6. Persist with `update-dashboard` (load it with `tool-search` — it is not on the
@@ -4,13 +4,13 @@ Analytics owns sources, queries, charts, and dashboards; dashboards are canonica
4
4
 
5
5
  ## Skills
6
6
 
7
- Search skills with `rg --hidden --follow`; read the exact linked guide before deeper work. App: `.agents/skills/data-querying/SKILL.md` (SQL, results, `/chart`), `.agents/skills/bigquery/SKILL.md`, `.agents/skills/hubspot/SKILL.md`, `.agents/skills/gong/SKILL.md`, `.agents/skills/prometheus/SKILL.md`, `.agents/skills/account-health/SKILL.md`, `.agents/skills/cross-source-analysis/SKILL.md`, `.agents/skills/dashboard-management/SKILL.md`, `.agents/skills/adhoc-analysis/SKILL.md`, `.agents/skills/analysis-workspace/SKILL.md`, `.agents/skills/provider-api/SKILL.md`, `.agents/skills/data-programs/SKILL.md`, `.agents/skills/creative-context/SKILL.md`, `.agents/skills/admin-surfaces/SKILL.md`, `.agents/skills/custom-blocks/SKILL.md` (extension panels), `.agents/skills/incident-investigation/SKILL.md`. Shared: `.agents/skills/actions/SKILL.md`, `.agents/skills/adding-a-feature/SKILL.md`, `.agents/skills/agent-native-docs/SKILL.md`, `.agents/skills/agent-native-toolkit/SKILL.md`, `.agents/skills/client-side-routing/SKILL.md`, `.agents/skills/context-awareness/SKILL.md`, `.agents/skills/customizing-agent-native/SKILL.md`, `.agents/skills/delegate-to-agent/SKILL.md`, `.agents/skills/external-agents/SKILL.md`, `.agents/skills/frontend-design/SKILL.md`, `.agents/skills/performance/SKILL.md`, `.agents/skills/portability/SKILL.md`, `.agents/skills/real-time-sync/SKILL.md`, `.agents/skills/reliable-mutations/SKILL.md`, `.agents/skills/secrets/SKILL.md`, `.agents/skills/security/SKILL.md`, `.agents/skills/self-modifying-code/SKILL.md`, `.agents/skills/shadcn-ui/SKILL.md`, `.agents/skills/sharing/SKILL.md`, `.agents/skills/storing-data/SKILL.md`, `.agents/skills/turn-into-skill/SKILL.md`, `.agents/skills/workspace-conventions/SKILL.md`.
7
+ The skills summary lists every skill; find more with `rg --hidden --follow` in `.agents/skills/<name>/SKILL.md` and read one with `pnpm action docs-search --slug "skill-<name>"` before deeper work.
8
8
 
9
9
  Use local docs only (no web research): `pnpm action docs-search --query "<topic>"` and `pnpm action docs-search --slug "<slug>"`. Source examples: `pnpm action source-search --query "<pattern>"` or `pnpm action source-search --path <path>`.
10
10
 
11
11
  ## Data questions
12
12
 
13
- - Edit a panel on the open dashboard with `get-sql-dashboard` (`panelIds`), then `mutate-dashboard`. A dashboard edit is done only when `mutate-dashboard` returns `verified: true`. `noRenderAffected: true` means no chart changed. On `verified: false`, an error, or "the change isn't visible", follow `nextStep` and call `inspect-dashboard-panel` before saying anything; "Applied N ops", a raw `bigquery` result, or no warning banner is not proof.
13
+ - Edit a panel on the open dashboard with `get-sql-dashboard` (`panelIds`), then `mutate-dashboard`; no skill read is needed (read `dashboard-management` to create a dashboard or move, reorder, or lay out panels). A dashboard edit is done only when `mutate-dashboard` returns `verified: true`. `noRenderAffected: true` means no chart changed. On `verified: false`, an error, or "the change isn't visible", follow `nextStep` and call `inspect-dashboard-panel` before saying anything; "Applied N ops", a raw `bigquery` result, or no warning banner is not proof.
14
14
  - Start from the closest query example (a preloaded reference, else one `search-analytics-query-catalog`); to build or clone a dashboard from another, call `search-dashboard-references`, then inspect matches with `get-sql-dashboard` or `get-explorer-dashboard` by `kind`. A match is context, not live data.
15
15
  - Use one bounded SQL or server-side `run-code` call for lists, filters, counts, or cohorts. If the catalog misses, make one discovery pass (`list-data-dictionary`, `search-bigquery-schema`, `data-source-status`) before querying; do not fan out per item or add unasked breakdowns.
16
16
  - Give a concise, evidence-backed answer with source, window, filters, sample size, join method, and caveats. Label figures “Unverified” if no live query ran; never cite the public `demo` source as real evidence unless asked.
@@ -5,6 +5,7 @@ import { track } from "@agent-native/core/tracking";
5
5
  import { z } from "zod";
6
6
 
7
7
  import { runQuery } from "../server/lib/bigquery";
8
+ import { recoverFromSchemaMiss } from "../server/lib/bigquery-schema-recovery";
8
9
 
9
10
  function extractBigQueryMessage(message: string): string {
10
11
  const jsonStart = message.indexOf("{");
@@ -122,7 +123,7 @@ function stopForRepeatedBigQueryQuery(): never {
122
123
 
123
124
  export default defineAction({
124
125
  description:
125
- "Query the user-configured BigQuery data warehouse. Use this when the user asks for warehouse SQL, BigQuery, or a data-dictionary metric/table that lives in BigQuery. If the user names a provider action such as Jira or Pylon, use that provider action first and do not use BigQuery unless the user explicitly asks for a warehouse copy. For a named customer or organization ID, resolve the canonical CRM/contract identity first and verify the returned rows carry the same customer and org/root-org identifiers. For account health, distinguish completed-month usage from current partial snapshots, contract metrics from similarly named platform metrics, total distinct contracted users from DAU/WAU, and actual usage from contracted capacity. Pass standard SQL via the `sql` arg. Do NOT use `db-query` for warehouse data (it only reaches the app's own SQL database). If a query fails with a schema or SQL error (unknown dataset/table/column, syntax), treat it as a normal debugging signal: inspect the real schema with `search-bigquery-schema` (or query INFORMATION_SCHEMA), correct the query based on the error, and run it again — a few corrective attempts are expected. Surface the error to the user only if it still fails after a few attempts or is non-recoverable (missing credentials, permission, quota). Never rerun identical failing SQL, and never substitute made-up numbers for data you could not query.",
126
+ "Query the user-configured BigQuery data warehouse. Use this when the user asks for warehouse SQL, BigQuery, or a data-dictionary metric/table that lives in BigQuery. If the user names a provider action such as Jira or Pylon, use that provider action first and do not use BigQuery unless the user explicitly asks for a warehouse copy. For a named customer or organization ID, resolve the canonical CRM/contract identity first and verify the returned rows carry the same customer and org/root-org identifiers. For account health, distinguish completed-month usage from current partial snapshots, contract metrics from similarly named platform metrics, total distinct contracted users from DAU/WAU, and actual usage from contracted capacity. Pass standard SQL via the `sql` arg. Do NOT use `db-query` for warehouse data (it only reaches the app's own SQL database). If a query fails with a schema or SQL error (unknown dataset/table/column, syntax), treat it as a normal debugging signal: use the `didYouMean` and `columns` the failure often carries, else inspect the real schema with `search-bigquery-schema` (or query INFORMATION_SCHEMA), correct the query based on the error, and run it again — a few corrective attempts are expected. Surface the error to the user only if it still fails after a few attempts or is non-recoverable (missing credentials, permission, quota). Never rerun identical failing SQL, and never substitute made-up numbers for data you could not query.",
126
127
  schema: z.object({
127
128
  sql: z.string().describe("SQL query to execute"),
128
129
  }),
@@ -172,11 +173,23 @@ export default defineAction({
172
173
  };
173
174
  }
174
175
  if (/BigQuery (API|poll) error/i.test(msg)) {
176
+ const message = extractBigQueryMessage(msg);
177
+ const recovery = await recoverFromSchemaMiss(
178
+ args.sql,
179
+ message,
180
+ context?.signal,
181
+ );
182
+ const foundSchema = Boolean(
183
+ recovery?.columns?.length || recovery?.didYouMeanTables?.length,
184
+ );
175
185
  return {
176
186
  error: "bigquery_query_failed",
177
- message: extractBigQueryMessage(msg),
187
+ message,
178
188
  recoverable: true,
179
- hint: "Likely a schema mismatch (wrong dataset, table, or column) or a SQL issue. Use search-bigquery-schema to get the exact datasets/tables/columns (or query INFORMATION_SCHEMA), correct the SQL based on this error, and run it again. Change the query based on the error — do not rerun identical SQL — and never substitute made-up numbers for data you could not query.",
189
+ hint: foundSchema
190
+ ? "The warehouse schema for this failure is below: correct the SQL using the exact names in `didYouMean`, `didYouMeanTables`, or `columns` and run it again. Do not rerun identical SQL, and never substitute made-up numbers for data you could not query."
191
+ : "Likely a schema mismatch (wrong dataset, table, or column) or a SQL issue. Use search-bigquery-schema to get the exact datasets/tables/columns (or query INFORMATION_SCHEMA), correct the SQL based on this error, and run it again. Change the query based on the error — do not rerun identical SQL — and never substitute made-up numbers for data you could not query.",
192
+ ...recovery,
180
193
  };
181
194
  }
182
195
  throw err;
@@ -202,8 +202,12 @@ function nonEmptyOperations(
202
202
  }
203
203
 
204
204
  const apiHelp =
205
- "Compact script form of `operations`: JSON-literal calls on `dashboard` only (quote object keys; no variables, loops, or imports). Use it for short layout or config edits; the dashboard-management skill lists the methods. " +
206
- `Examples: ${DASHBOARD_MUTATION_EXAMPLES[0]} ${DASHBOARD_MUTATION_EXAMPLES[2]}`;
205
+ "Compact script form of `operations`: JSON-literal calls on `dashboard` only (quote object keys; no variables, loops, or imports). Use it for short layout or config edits; call with only `returnTypes: true` to list every method. " +
206
+ `Examples: ${[0, 2, 3, 4, 7].map((index) => DASHBOARD_MUTATION_EXAMPLES[index]).join(" ")}`;
207
+
208
+ const operationsHelp =
209
+ "Edits applied atomically in one save, by panel id. " +
210
+ 'Examples: {"op":"updatePanel","panelId":"top-referrers","patch":{"title":"Top Referrers by Domain","width":2,"config":{"yFormatter":"percent"}}} {"op":"movePanels","panelIds":["dau","wau"],"position":"top"} {"op":"setFilterDefault","filterId":"emailFilter","value":"exclude_builder"}';
207
211
 
208
212
  const dryRunHelp = "Validate and verify without saving.";
209
213
  const allowEmptyResultHelp =
@@ -215,7 +219,7 @@ const agentInputSchema = z.object({
215
219
  .array(mutationOperationSchema)
216
220
  .max(MAX_DASHBOARD_MUTATION_OPERATIONS)
217
221
  .optional()
218
- .describe("Edits applied atomically in one save, by panel id."),
222
+ .describe(operationsHelp),
219
223
  code: z
220
224
  .string()
221
225
  .max(MAX_DASHBOARD_MUTATION_CODE_LENGTH)
@@ -434,7 +438,7 @@ function helpResult() {
434
438
  export default defineAction({
435
439
  description:
436
440
  "Edit a SQL dashboard in ONE atomic save: move, insert, duplicate, remove, and edit panels by id (title, SQL, width, config), patch dashboard fields, or set filter defaults. Pass typed `operations`; `code` is a compact script form of the same edits. " +
437
- "Place a panel in a visible row with nextToPanelId or rowNumber. First-party panel SQL must bind to a dashboard time filter (config.timeScope). Use `compose-dashboard` for catalog metrics and keep large SQL out of `code`. Read the existing panels with `get-sql-dashboard` first and match their chart types, widths, and config. The dashboard-management skill owns placement, time-scope, and config rules. " +
441
+ "Place a panel in a visible row with nextToPanelId or rowNumber. First-party panel SQL must bind to a dashboard time filter (config.timeScope). For catalog metrics load `compose-dashboard` with `tool-search`, and keep large SQL out of `code`. Read the existing panels with `get-sql-dashboard` first and match their chart types, widths, and config. Small edits of existing panels need no skill; the dashboard-management skill owns new-panel placement, time-scope, and config rules. " +
438
442
  "Before saving, agent calls run every changed panel the way the dashboard page does; a panel that would show 'No data', drop configured columns, or fail is refused with the reason and nothing is written. " +
439
443
  "The result's `verified` flag and per-panel `verification` (or `unverified` reasons) are the proof the edit renders: report only what they show, and on `verified:false`, an error, or a user report that nothing changed, call `inspect-dashboard-panel` before saying anything about the chart.",
440
444
  schema: z.object({
@@ -449,7 +453,7 @@ export default defineAction({
449
453
  .optional()
450
454
  .describe(apiHelp),
451
455
  operations: operationsInputSchema.describe(
452
- "Edits applied atomically in one save, by panel id. Native callers pass an array; shell/legacy callers may pass a JSON string.",
456
+ `${operationsHelp} Native callers pass an array; shell/legacy callers may pass a JSON string.`,
453
457
  ),
454
458
  dryRun: z.boolean().optional().describe(dryRunHelp),
455
459
  allowEmptyResult: z.boolean().optional().describe(allowEmptyResultHelp),
@@ -1,18 +1,17 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
2
  import { z } from "zod";
3
3
 
4
- import { getBigQueryProjectId } from "../server/lib/bigquery";
5
- import { getAccessToken } from "../server/lib/gcloud";
4
+ import {
5
+ bigQueryGet,
6
+ flattenBigQueryFields,
7
+ getBigQueryProjectId,
8
+ getBigQueryTableMetadata,
9
+ listBigQueryTables,
10
+ type BigQueryTableMetadata,
11
+ type BigQueryTableSummary,
12
+ } from "../server/lib/bigquery";
6
13
  import { cliBoolean } from "./schema-helpers";
7
14
 
8
- interface BigQueryField {
9
- name: string;
10
- type?: string;
11
- mode?: string;
12
- description?: string;
13
- fields?: BigQueryField[];
14
- }
15
-
16
15
  interface DatasetListResponse {
17
16
  datasets?: Array<{
18
17
  datasetReference?: { projectId?: string; datasetId?: string };
@@ -22,45 +21,6 @@ interface DatasetListResponse {
22
21
  }>;
23
22
  }
24
23
 
25
- interface TableListResponse {
26
- tables?: Array<{
27
- tableReference?: {
28
- projectId?: string;
29
- datasetId?: string;
30
- tableId?: string;
31
- };
32
- type?: string;
33
- friendlyName?: string;
34
- labels?: Record<string, string>;
35
- }>;
36
- }
37
-
38
- interface BigQueryTableSummary {
39
- projectId?: string;
40
- datasetId?: string;
41
- tableId?: string;
42
- type?: string;
43
- friendlyName?: string;
44
- labels?: Record<string, string>;
45
- }
46
-
47
- interface TableMetadata {
48
- tableReference?: {
49
- projectId?: string;
50
- datasetId?: string;
51
- tableId?: string;
52
- };
53
- friendlyName?: string;
54
- description?: string;
55
- type?: string;
56
- location?: string;
57
- numRows?: string;
58
- numBytes?: string;
59
- timePartitioning?: unknown;
60
- clustering?: unknown;
61
- schema?: { fields?: BigQueryField[] };
62
- }
63
-
64
24
  const PROJECT_RE = /^[A-Za-z][A-Za-z0-9-]{4,61}[A-Za-z0-9]$/;
65
25
  const ID_RE = /^[A-Za-z0-9_]+$/;
66
26
  const GLOBAL_SEARCH_DATASET_LIMIT = 100;
@@ -116,67 +76,7 @@ function parseTableRef(
116
76
  );
117
77
  }
118
78
 
119
- async function bigQueryGet<T>(url: string): Promise<T> {
120
- const token = await getAccessToken();
121
- const res = await fetch(url, {
122
- headers: {
123
- Authorization: `Bearer ${token}`,
124
- "Content-Type": "application/json",
125
- },
126
- });
127
-
128
- if (!res.ok) {
129
- const text = await res.text();
130
- let detail = "";
131
- try {
132
- const parsed = JSON.parse(text) as { error?: { message?: string } };
133
- detail = parsed.error?.message ?? "";
134
- } catch {
135
- // Fall through to include the raw response body below.
136
- }
137
- if (detail) {
138
- throw new Error(detail);
139
- }
140
- throw new Error(
141
- `BigQuery metadata request failed (${res.status}): ${text}`,
142
- );
143
- }
144
-
145
- return (await res.json()) as T;
146
- }
147
-
148
- function flattenFields(
149
- fields: BigQueryField[] | undefined,
150
- prefix = "",
151
- ): Array<{
152
- name: string;
153
- type?: string;
154
- mode?: string;
155
- description?: string;
156
- }> {
157
- if (!fields?.length) return [];
158
- const columns: Array<{
159
- name: string;
160
- type?: string;
161
- mode?: string;
162
- description?: string;
163
- }> = [];
164
-
165
- for (const field of fields) {
166
- const name = prefix ? `${prefix}.${field.name}` : field.name;
167
- columns.push({
168
- name,
169
- type: field.type,
170
- mode: field.mode,
171
- description: field.description,
172
- });
173
- columns.push(...flattenFields(field.fields, name));
174
- }
175
-
176
- return columns;
177
- }
178
-
179
- function compactTable(meta: TableMetadata, includeColumns: boolean) {
79
+ function compactTable(meta: BigQueryTableMetadata, includeColumns: boolean) {
180
80
  const ref = meta.tableReference ?? {};
181
81
  return {
182
82
  projectId: ref.projectId,
@@ -190,11 +90,13 @@ function compactTable(meta: TableMetadata, includeColumns: boolean) {
190
90
  numBytes: meta.numBytes ? Number(meta.numBytes) : undefined,
191
91
  timePartitioning: meta.timePartitioning,
192
92
  clustering: meta.clustering,
193
- columns: includeColumns ? flattenFields(meta.schema?.fields) : undefined,
93
+ columns: includeColumns
94
+ ? flattenBigQueryFields(meta.schema?.fields)
95
+ : undefined,
194
96
  };
195
97
  }
196
98
 
197
- function matchesSearch(meta: TableMetadata, search: string): boolean {
99
+ function matchesSearch(meta: BigQueryTableMetadata, search: string): boolean {
198
100
  const terms = search
199
101
  .toLowerCase()
200
102
  .replace(/[._-]+/g, " ")
@@ -208,7 +110,7 @@ function matchesSearch(meta: TableMetadata, search: string): boolean {
208
110
  ref.tableId,
209
111
  meta.friendlyName,
210
112
  meta.description,
211
- ...flattenFields(meta.schema?.fields).flatMap((column) => [
113
+ ...flattenBigQueryFields(meta.schema?.fields).flatMap((column) => [
212
114
  column.name,
213
115
  column.type,
214
116
  column.description,
@@ -247,22 +149,6 @@ async function listDatasets(projectId: string, limit: number, search: string) {
247
149
  .slice(0, limit);
248
150
  }
249
151
 
250
- async function listTables(projectId: string, datasetId: string, limit: number) {
251
- const url = new URL(
252
- `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetId}/tables`,
253
- );
254
- url.searchParams.set("maxResults", String(Math.min(limit, 1000)));
255
- const result = await bigQueryGet<TableListResponse>(url.toString());
256
- return (result.tables ?? []).map((table) => ({
257
- projectId: table.tableReference?.projectId,
258
- datasetId: table.tableReference?.datasetId,
259
- tableId: table.tableReference?.tableId,
260
- type: table.type,
261
- friendlyName: table.friendlyName,
262
- labels: table.labels,
263
- }));
264
- }
265
-
266
152
  async function searchAcrossDatasets(
267
153
  projectId: string,
268
154
  search: string,
@@ -291,7 +177,7 @@ async function searchAcrossDatasets(
291
177
 
292
178
  datasetsScanned += 1;
293
179
  const remaining = GLOBAL_SEARCH_TABLE_LIMIT - tables.length;
294
- const listed = await listTables(
180
+ const listed = await listBigQueryTables(
295
181
  projectId,
296
182
  datasetId,
297
183
  Math.min(remaining, GLOBAL_SEARCH_TABLE_LIMIT),
@@ -321,11 +207,11 @@ async function searchAcrossDatasets(
321
207
  const settled = await Promise.allSettled(
322
208
  batch.map(async (table) => {
323
209
  if (!table.datasetId || !table.tableId) return null;
324
- const metadata = await getTableMetadata(
210
+ const metadata = await getBigQueryTableMetadata({
325
211
  projectId,
326
- table.datasetId,
327
- table.tableId,
328
- );
212
+ datasetId: table.datasetId,
213
+ tableId: table.tableId,
214
+ });
329
215
  return matchesSearch(metadata, search)
330
216
  ? compactTable(metadata, true)
331
217
  : null;
@@ -366,15 +252,6 @@ async function searchAcrossDatasets(
366
252
  };
367
253
  }
368
254
 
369
- async function getTableMetadata(
370
- projectId: string,
371
- datasetId: string,
372
- tableId: string,
373
- ) {
374
- const url = `https://bigquery.googleapis.com/bigquery/v2/projects/${projectId}/datasets/${datasetId}/tables/${tableId}`;
375
- return await bigQueryGet<TableMetadata>(url);
376
- }
377
-
378
255
  export default defineAction({
379
256
  description:
380
257
  "Search or describe BigQuery metadata for the configured warehouse. Use before writing SQL when the data dictionary does not already name the dataset, table, and columns. With no args, lists datasets. With search and no dataset, searches accessible tables and columns across the configured project so the user does not need to provide internal table names. With dataset, lists tables. With dataset + table or a dataset.table value, returns columns for that table.",
@@ -417,11 +294,7 @@ export default defineAction({
417
294
 
418
295
  if (args.table) {
419
296
  const ref = parseTableRef(configuredProjectId, args.dataset, args.table);
420
- const meta = await getTableMetadata(
421
- ref.projectId,
422
- ref.datasetId,
423
- ref.tableId,
424
- );
297
+ const meta = await getBigQueryTableMetadata(ref);
425
298
  return {
426
299
  mode: "table",
427
300
  table: compactTable(meta, true),
@@ -442,7 +315,11 @@ export default defineAction({
442
315
  }
443
316
 
444
317
  const datasetId = assertIdentifier("dataset", args.dataset);
445
- const tables = await listTables(configuredProjectId, datasetId, limit);
318
+ const tables = await listBigQueryTables(
319
+ configuredProjectId,
320
+ datasetId,
321
+ limit,
322
+ );
446
323
  const includeColumns = args.includeColumns === true || !!search;
447
324
 
448
325
  if (!includeColumns) {
@@ -469,7 +346,11 @@ export default defineAction({
469
346
  const metadata = await Promise.all(
470
347
  tables.slice(0, Math.min(tables.length, limit)).map((table) => {
471
348
  const tableId = table.tableId ?? "";
472
- return getTableMetadata(configuredProjectId, datasetId, tableId);
349
+ return getBigQueryTableMetadata({
350
+ projectId: configuredProjectId,
351
+ datasetId,
352
+ tableId,
353
+ });
473
354
  }),
474
355
  );
475
356
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-07
4
+ ---
5
+
6
+ Analytics now treats chart filter changes as data requests and reports when reference catalogs are incomplete.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-07
4
+ ---
5
+
6
+ Analytics chat follows short follow-ups like "what about EMEA?" and recovers from an unknown BigQuery column in one step
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-07
4
+ ---
5
+
6
+ Analytics chat no longer stays on "running" after the agent has finished; the thread reloads so the change is visible without asking again
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-07
4
+ ---
5
+
6
+ Analytics chat stops retrying a data source that is not connected and says who can connect it
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-07
4
+ ---
5
+
6
+ Thumbs-down on a chat answer now offers quick reasons and a Copy details button that copies the run id and thread link for a report
@@ -2,6 +2,7 @@ export const INITIAL_TOOL_NAMES = [
2
2
  "describe-workspace-apps",
3
3
  "call-agent",
4
4
  "view-screen",
5
+ "ask-question",
5
6
  "data-source-status",
6
7
  "list-analyses",
7
8
  "get-analysis",