fix(tables): make the agent tool picker version-aware and enrich the v2 query - #7328
Conversation
…v2 query The Agent block's tool picker gated on a hardcoded `CORE_AGENT_TOOL_TYPES` set naming `'table'` with no version resolution. Both Table blocks are `category: 'blocks'`, so that set is their only route into the picker — meaning `table_v2` would never appear there, even once it ships and the toolbar has moved to it. Store base types and match after stripping the `_vN` suffix so a cutover never has to re-list a block here. The existing `hideFromToolbar` guard already excludes superseded versions (file v1-v4) and unrevealed preview blocks, so broadening `file_v5` to `file` admits nothing new. `BUILT_IN_TOOL_TYPES` had the same gap and would have filed v2 under Integrations. `table_query_rows_v2` also shipped with no `toolEnrichment` while all seven other table tools have one, so an agent using it never saw the table's column names and had to guess field values. The enrichment module only knew v1's MongoDB grammar, so adding the id to the existing sets would have taught the wrong syntax — v2 gets its own branch emitting predicate examples, `order` rather than `sort`, and cursor paging guidance. Unlike v1, a v2 query with no filter is valid and returns every row, so the new branch deliberately does not push `filter` into `required`. The shared bulk tools stay on `$eq`: the rows route accepts either grammar via `resolveBulkFilter`, and enrichment is keyed on tool id alone, so it cannot tell which block invoked it.
…ichment wiring Review findings. The operator list the v2 surfaces advertise omitted `ncontains`, which `FILTER_OPS` declares and `sql.ts` compiles to a negated ILIKE. An agent reading the tool schema therefore never knew the operator existed. The list is spelled out verbatim in several places and had already drifted from its source of truth on arrival; deriving it from `FILTER_OPS` is the real fix and is left as follow-up. The `toolEnrichment` block this branch adds had no test, which is the same gap that let v2 ship without enrichment in the first place — a refactor could silently drop it again, and a typo in the tool-id literal degrades to the generic description with no failure. Pin both halves: the tool declares the wiring, and the enricher answers in predicate grammar with `filter` left optional. Also cover the two `v2PredicateExample` arms an all-numeric or all-text table takes, and assert the empty-order case the previous test only executed without checking.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThe PR makes agent tool discovery version-aware and adds schema-aware enrichment for the v2 table query tool.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/table/llm/enrichment.ts | Adds v2-specific, schema-aware query guidance and completes the prior select-operator fix across tool and parameter descriptions. |
| apps/sim/lib/internal/table/read-schema.ts | Preserves select cardinality so enrichment can distinguish single-select and multi-select operator contracts. |
| apps/sim/lib/table/column-types/select.ts | Exports shared v2 select-operator allowlists used by both validation and LLM guidance. |
| apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/utils.ts | Resolves versioned core block types by base name while retaining toolbar visibility checks. |
| apps/sim/blocks/blocks/table_v2.ts | Updates user-facing query guidance to describe select-column restrictions accurately. |
| apps/sim/tools/table/query_rows_v2.ts | Wires table-schema enrichment into the v2 query tool contract. |
Reviews (5): Last reviewed commit: "fix(tables): cover all four pattern oper..." | Re-trigger Greptile
There was a problem hiding this comment.
All reported issues were addressed across 7 files
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Fix all with cubic | Re-trigger cubic
…accepts Greptile and cubic both flagged this independently, and they were right. A select column accepts only a subset of the filter operators — eq/ne/in/nin when single, contains/ncontains when multi — and `buildFilterConditions` THROWS on anything else rather than returning no rows. The new v2 enrichment advertised the full operator list for every column and, worse, asserted "there are no array columns - for substring matching use ilike with *x*". A multi-select cell holds a list, so that sentence is false and it steers the model straight into a rejected query on exactly the columns it describes. The enricher could not tell single from multi because `readTableSchemaAsExecutor` dropped `multiple` when projecting the summary. Carry it through — for select columns only, where it means something — and annotate each select column in the description with the operators it actually takes. The block's bestPractices carried the same false claim, so the model reached it a second way even when enrichment was absent. Corrected there too. Also regenerate the tool metadata bundle, which is what CI was failing on: the ncontains commit changed the v2 tool description without regenerating the derived artifact.
|
@cubic review |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 12 files
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Fix all with cubic | Re-trigger cubic
… sets Round 2 review. Both bots were right, and the second finding corrected me. The first fix annotated the column list but left the `filter` PARAMETER description advertising every operator for every column. That description is what a model reads when deciding the filter's shape, so the restriction has to appear there too — it now names the restricted columns inline. More importantly, the annotation itself was wrong: I omitted isNull and isNotNull. I had traced the v2 grammar through `predicateToFilter` to the `$`-prefixed sets in `column-types/select.ts`, which genuinely lack them — but the v2 query path never goes through that conversion. It compiles predicates directly in `fieldPredicate`, which gates on a SEPARATE pair of `FilterOp` sets that do allow both. Two allowlists for one concept is what made a hand-copied list wrong, so the v2 sets move next to their v1 siblings in the column-type registry — where per-type knowledge belongs — and the enrichment now derives its text from the exact sets the validator gates on. It can no longer drift. Same correction applied to the block's bestPractices, which carried the short list too.
Follows the gmail_v2 / github_v2 / confluence_v2 / cursor_v2 cutovers: v1 is renamed "(Legacy)", hidden from discovery, and points at its successor; v2 drops the preview gate. Both edits must land together — the registry check fails a sunset block whose replacedBy is still preview, verified by splitting them locally. v1's `triggers.enabled` deliberately stays true. Webhook execution gates on it at runtime rather than on discovery, so flipping it would break every deployed v1 table-trigger workflow. Both versions host the same `table_new_row` trigger and dispatch is provider-keyed, so hiding v1 changes nothing for triggers. No BLOCK_META_REGISTRY entry is needed — meta coverage is required only for `category: 'tools'` blocks and both Table blocks are `category: 'blocks'`. Docs regenerate from v2 now that v1 is skipped as a source, so the generated page moves to the predicate grammar and cursor pagination. The hand-written regions did not, and are updated here: the operator reference and combining examples, the workflow guide's field walkthrough, and the pagination advice — which previously told readers to advance an offset while looping on nextCursor, mixing both versions. Verified in the browser: the toolbar yields a `table_v2` block whose Query Rows shows Cursor/Order; the Agent tool picker stores `type: table_v2, toolId: table_query_rows_v2` and groups it under built-ins; and a pre-existing v1 block still renders its own Offset/Sort fields behind an amber legacy badge.
Review findings, all consequences of v2 becoming the docs source. `BlockPreview` looks the block type up in a hand-maintained map and renders nothing when it misses, so pointing the trigger page at `table_v2` silently deleted its hero. Move the entry to the new type; the trigger config it displays is identical across both versions. The docs generator extracts `longDescription` with a single-literal regex, so v2's `+`-concatenated string published as its first fragment alone — the page lost every mention of the predicate grammar and cursor pagination. Join it into one literal. v1's was a single literal, which is why this only surfaced now. The hand-written operator table called `contains` case-sensitive. It compiles to ILIKE, as do `startsWith` and `endsWith`; the pre-cutover page had this right, so the rewrite regressed it. Restore the qualifiers and document `ncontains` alongside `contains`. Drop `'table'` from BUILT_IN_TOOL_TYPES: it now only ever reaches blocks that already passed `isAgentToolBlock`, which excludes hidden ones, so the entry is dead. Matches the `file`/`file_v5` precedent the sibling test already asserts.
Regenerate the two artifacts the `ncontains` commit left stale: the tool metadata bundle and the generated Table integration page both still published the operator list without it, so `bun run tool-metadata:check` failed at HEAD. Point the remaining docs previews at `table_v2`. `BLOCK_DISPLAY_WORKFLOWS` already moved, but the two hand-written table example workflows and the OutputBundle on the workflows guide still named the v1 type, so every table example in the docs described the block the toolbar no longer yields. They render identically today only because both types share an icon-map entry. The workflows guide told readers the log shows "the filter and sort it sent"; v2 sends `order`. Document the `sunset` step in the three skills that describe the v1 -> v2 cutover. All three stopped at `(Legacy)` + `hideFromToolbar`, but `check-block-registry` fails a legacy block with no `replacedBy`, and the amber badge and its click-to-upgrade action read from that field - following the procedure verbatim produced a build failure. Also record the ordering constraint this cutover hit: the v1 `sunset` edit and the v2 `preview` removal must land together, since the check rejects a `replacedBy` that is still preview. Left alone: the academy video previews still show v1 labels. They mirror recorded footage, so correcting the label without a re-record would only make the still disagree with the video it claims to depict.
The regenerated page reverted to the "there are no array columns" claim that the select-operator fix corrected. Restore the allowed-operator table.
Greptile: convert the v1 lifecycle comment to TSDoc. The repo rule is TSDoc-only, and none of the four reference cutover blocks comment here at all, so the `//` form was not following a local convention either. The note is worth keeping as declaration documentation — it records why `triggers.enabled` must stay true. Cubic: fix a link to `#tips`, an anchor that does not exist on the page. The pagination guidance lives under Variations.
Review finding. The add-integration cutover steps said to add `sunset.replacedBy` without stating the precondition, so an author following them while v2 was still preview-gated would get a `check-block-registry` failure — the exact constraint this cutover hit. The sibling add-block and add-block-preview skills already spell this out; this brings the third in line.
|
@greptile review |
|
@cubic review |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 15 files
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Fix all with cubic | Re-trigger cubic
…richment # Conflicts: # apps/sim/tools/generated/tool-metadata.ts
Review finding. Instruction 5 hardcoded `{"field":"name",...}` while every
other example in the block is derived from the table's own columns. An
unknown field is rejected outright — `Unknown filter column "name". It is
not a column on this table.` — so on a table without that column the
example invited the model to write a query that cannot run.
Derived from a real text column now, and dropped entirely when the table
has none rather than falling back to a placeholder.
|
@greptile review |
|
@cubic review |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
The amber legacy badge and its click-to-upgrade action already mark a sunset block in the UI, so the suffix restated it — a placed v1 block rendered a "Table (Legacy)" type tag next to a "legacy" badge saying the same thing. The four earlier cutovers (gmail, github, confluence, cursor) carry the suffix, but the most recent one does not: the slack_v2 GA left v1 named "Slack" and leaned on the badge. Following that.
There was a problem hiding this comment.
All reported issues were addressed across 15 files
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Fix all with cubic | Re-trigger cubic
…ional Review findings, both in the instruction text. The wildcard rule named only like/ilike, but nlike and nilike route through the same `buildPatternClause`. That matters more than an omission: the builder ESCAPES `%` before translating `*`, so a model reaching for `%` on a negative pattern gets a literal-percent match and silently wrong rows rather than an error. The rule now names all four and says never to use `%`. Instructions 1 and 11 together pushed the model to invent a predicate for a question that carries no condition — "the 5 most recent rows" is answered with order and limit, and v2 accepts an omitted filter. Both now say so.
|
@greptile review |
|
@cubic review |
@mzxchandra I have started the AI code review. It will take a few minutes to complete. |
…richment # Conflicts: # apps/sim/tools/generated/tool-metadata.ts
There was a problem hiding this comment.
All reported issues were addressed
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Fix all with cubic | Re-trigger cubic
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
feat(tables): GA table_v2 and mark the v1 Table block legacy
Summary
Two prerequisites for the Table v1 → v2 cutover (#follow-up PR), shipped first because they are independently safe:
table_v2is stillpreview: trueon this branch, so nothing here is user-visible yet.1. The Agent block's tool picker was version-blind. It gated on a hardcoded set naming
'table'with no version resolution:Both Table blocks are
category: 'blocks', so that set is their only route into the picker.table_v2would therefore never appear there — even after it ships and the toolbar has moved to it. The set now stores base types and matches after stripping the_vNsuffix, so a cutover never has to remember this file again.Broadening
'file_v5'→'file'admits nothing new:file,file_v2,file_v3,file_v4all carryhideFromToolbar: trueand are caught by the first guard. Same for unrevealedpreviewblocks, whichgetAllBlocks()projects into clones carrying that flag.2.
table_query_rows_v2shipped with notoolEnrichment, while all seven other table tools have one. Enrichment is what injects the table's real column names into the LLM's tool schema — without it an agent using the v2 query tool has to guess field names.lib/table/llm/enrichment.tsonly knew v1's MongoDB grammar, so adding the id to the existing sets would have taught the wrong syntax; v2 gets its own branch emitting predicate examples,orderrather thansort, and cursor-paging guidance.Unlike v1, a v2 query with no filter is valid and returns every row, so the new branch deliberately does not push
filterintorequired.The shared bulk tools (
table_update_rows_by_filter,table_delete_rows_by_filter) stay on$eq. The rows route accepts either grammar viaresolveBulkFilter, and enrichment is keyed on tool id alone so it cannot tell which block invoked it — one grammar has to win, and$eqworks for both.Type of Change
Testing
Full suite green: 2,776 tests across the affected areas (
blocks/,lib/table/,tools/table/,tools/schema-enrichers,tool-input/), plusturbo run type-check,lint:check, andcheck-block-registry.tson this branch alone.New coverage:
tool-input.test.ts— base-type match, superseded-version exclusion, a versioned block whose base is unlisted (memory_v2), and everytools-category block regardless of version.enrichment.test.ts(new) — v2 teaches predicate grammar and never$eq/offset;filterstays optional; both single-column arms of the example builder; v1 regression guard thattable_query_rowsstill forcesfilterrequired and still teaches$eq.schema-enrichers.test.ts— thetoolEnrichmentwiring itself. This is the gap that let v2 ship unenriched in the first place, and a typo in the tool-id literal degrades silently to the generic description, so it is now pinned.Reviewers should focus on whether the
stripVersionSuffixbroadening inisAgentToolBlockcan admit any block that should not be agent-callable. I enumerated every registered block whose stripped base lands in the set; net picker delta is zero on this branch.Verified in the browser with
PREVIEW_BLOCKS=table_v2set locally: the Table tool appears in the picker under built-ins, offers v2's Cursor field rather than v1's Offset, and disappears again when the flag is unset.Checklist
Notes
ncontainswas missing from the operator list every v2 surface advertises, althoughFILTER_OPSdeclares it andsql.tscompiles it to a negated ILIKE. Added. That list is now spelled out verbatim in four places and had already drifted from its source of truth on arrival — deriving it fromFILTER_OPSis the real fix and is worth a follow-up.