Skip to content

Dev#5410

Open
Sg312 wants to merge 36 commits into
stagingfrom
dev
Open

Dev#5410
Sg312 wants to merge 36 commits into
stagingfrom
dev

Conversation

@Sg312

@Sg312 Sg312 commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Brief description of what this PR does and why.

Fixes #(issue)

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

How has this been tested? What should reviewers focus on?

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 10, 2026 8:37pm

Request Review

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces three new read-only copilot tools (run_code, search_knowledge_base, query_user_table) and replaces the research subagent with a search subagent, wiring up all the catalog entries, runtime schemas, server-side handlers, UI display strings, and icon mappings. It also fixes a bug in getToolCallTerminalData where a failed tool call returning an empty-but-defined output ({}) was silently hiding its error from the model.

  • New read-only tools: run_code delegates to executeFunctionExecute after stripping write vectors (outputs, outputTable); search_knowledge_base and query_user_table wrap their mutable counterparts behind operation allowlists, with query_user_table adding an extra outputPath guard.
  • researchsearch: The Research subagent catalog entry and schema are removed; Search replaces it with a task-based parameter; display labels are updated throughout while the old research key is kept in TOOL_TITLES for backward compatibility with existing tool-call history.
  • tool-call-state bugfix: Failed calls that carry a non-null output now have the error merged into the returned terminal data so the model sees the failure reason on resume rather than a bare {}.

Confidence Score: 4/5

Safe to merge with the args nullability issue resolved — all other changes are additive registrations and a well-contained bug fix.

The core logic (read-only guards, tool registration, schema sync, bug fix in terminal data) is sound and well-commented. One concern is the args field typed as optional in both SearchKnowledgeBaseArgs and QueryUserTableArgs while the catalog schemas list args in required — if the model omits it, the handler forwards undefined into the underlying tool, where property accesses on args would throw at runtime.

apps/sim/lib/copilot/tools/server/knowledge/search-knowledge-base.ts and apps/sim/lib/copilot/tools/server/table/query-user-table.ts — both type args as optional at the TypeScript level while the JSON schema marks it required.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/tools/server/knowledge/search-knowledge-base.ts New read-only wrapper for knowledge_base; enforces operation allowlist but types args as optional while schema marks it required — downstream access on undefined args could throw.
apps/sim/lib/copilot/tools/server/table/query-user-table.ts New read-only wrapper for user_table with operation + outputPath guards; contains a redundant second outputPath check at the top-level params scope that cannot be reached given the typed schema.
apps/sim/lib/copilot/tools/handlers/run-code.ts New compute-only variant of function_execute; correctly strips output write vectors before delegating, with defense-in-depth noted in the catalog capabilities.
apps/sim/lib/copilot/request/tool-call-state.ts Bug fix ensuring failed tool calls always surface their error in terminal data even when a non-empty (but empty-object) output is present; logic and edge-case handling look correct.
apps/sim/lib/copilot/generated/tool-catalog-v1.ts Adds QueryUserTable, RunCode, Search, and SearchKnowledgeBase catalog entries; removes Research and replaces with Search subagent; all catalog entries and type unions are consistently updated.
apps/sim/lib/copilot/generated/tool-schemas-v1.ts Runtime schemas kept in sync with catalog additions; research schema removed, new schemas for query_user_table, run_code, search, search_knowledge_base are consistent with catalog definitions.
apps/sim/lib/copilot/tools/server/router.ts Both new server tool instances correctly registered in the server tool registry.
apps/sim/lib/copilot/tool-executor/register-handlers.ts executeRunCode handler properly imported and registered alongside RunCode catalog id.
apps/sim/lib/copilot/request/tools/executor.ts Research replaced by Search in LONG_RUNNING_TOOL_IDS; RunCode added alongside FunctionExecute.
apps/sim/app/workspace/[workspaceId]/home/components/message-content/utils.ts TOOL_ICONS updated to include run_code, search_knowledge_base, query_user_table, scout, and search icons; additions are complete and consistent.
apps/sim/app/workspace/[workspaceId]/home/types.ts SUBAGENT_LABELS extended with scout and search entries to match the new catalog subagent IDs.
apps/docs/components/workflow-preview/format-references.tsx Removes now-unnecessary biome-ignore comments for noArrayIndexKey — the root biome.json has this rule set to off globally, so the suppressions were already no-ops.
apps/sim/lib/copilot/tools/tool-display.ts TOOL_TITLES extended with run_code, query_user_table, search_knowledge_base, scout, and search display strings; consistent with catalog additions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Copilot Model Request] --> B{Tool Route}

    B -->|run_code| C[executeRunCode]
    C --> D{outputs / outputTable in params?}
    D -->|Yes| E[Return error: compute-only]
    D -->|No| F[executeFunctionExecute sandbox execution]

    B -->|search_knowledge_base| G[searchKnowledgeBaseServerTool]
    G --> H{operation in READ_OPERATIONS?}
    H -->|No| I[Return error: read-only]
    H -->|Yes| J[knowledgeBaseServerTool.execute]

    B -->|query_user_table| K[queryUserTableServerTool]
    K --> L{operation in READ_OPERATIONS?}
    L -->|No| M[Return error: read-only]
    L -->|Yes| N{outputPath present?}
    N -->|Yes| O[Return error: read-only]
    N -->|No| P[userTableServerTool.execute]

    B -->|search subagent| Q[Search Subagent replaces Research]

    F & J & P & Q --> R[Tool Result]
    R --> S[getToolCallTerminalData]
    S --> T{failed AND output defined?}
    T -->|Yes| U[Merge error into output for model context]
    T -->|No| V[Return output or error as-is]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Copilot Model Request] --> B{Tool Route}

    B -->|run_code| C[executeRunCode]
    C --> D{outputs / outputTable in params?}
    D -->|Yes| E[Return error: compute-only]
    D -->|No| F[executeFunctionExecute sandbox execution]

    B -->|search_knowledge_base| G[searchKnowledgeBaseServerTool]
    G --> H{operation in READ_OPERATIONS?}
    H -->|No| I[Return error: read-only]
    H -->|Yes| J[knowledgeBaseServerTool.execute]

    B -->|query_user_table| K[queryUserTableServerTool]
    K --> L{operation in READ_OPERATIONS?}
    L -->|No| M[Return error: read-only]
    L -->|Yes| N{outputPath present?}
    N -->|Yes| O[Return error: read-only]
    N -->|No| P[userTableServerTool.execute]

    B -->|search subagent| Q[Search Subagent replaces Research]

    F & J & P & Q --> R[Tool Result]
    R --> S[getToolCallTerminalData]
    S --> T{failed AND output defined?}
    T -->|Yes| U[Merge error into output for model context]
    T -->|No| V[Return output or error as-is]
Loading

Reviews (1): Last reviewed commit: "fix(copilot): failed tool calls must sur..." | Re-trigger Greptile

Comment on lines +42 to +48
if (params && 'outputPath' in (params as Record<string, unknown>)) {
return {
success: false,
message:
'query_user_table is read-only: outputPath (file export) is not available; digest the rows directly or route exports through the table agent',
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant top-level outputPath check

The check at lines 35–40 already guards against outputPath nested inside params.args. The second check at lines 42–48 looks for outputPath at the top level of params, but outputPath is not a valid top-level field in QueryUserTableArgs — the catalog schema only defines operation and args at this level. If the Go executor or the server framework normalises the payload into the typed shape before reaching this handler, the top-level path can never be reached, making this check dead code. If the raw payload could arrive unnormalised, a note explaining that assumption would help clarify intent.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +5 to +8
type SearchKnowledgeBaseArgs = {
operation: string
args?: Record<string, any>
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 args typed optional but required in schema

SearchKnowledgeBaseArgs.args is typed as optional (args?: Record<string, any>), yet the generated catalog entry at SearchKnowledgeBase.parameters.required includes 'args'. The same pattern exists in QueryUserTableArgs. If the LLM omits args, the handler forwards params with args: undefined to knowledgeBaseServerTool.execute, where downstream property accesses like params.args.knowledgeBaseId would throw. Aligning the TypeScript type with the schema (args: Record<string, any> without ?) makes the contract explicit and prevents a runtime crash on malformed calls.

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
OAuth reconnect and pending-draft upsert logic touch credential binding and authorization; fork performs quota-gated storage copies and reference rewrites that can leave partial failures if blobs fail.

Overview
Large feature batch across auth, copilot chat, and code execution.

OAuth2 authorize now supports optional credentialId for reconnect: admin checks, provider/workspace/type validation, Trello/Shopify blocked, and pending drafts that carry credentialId (plain connect explicitly nulls it on upsert). Connect drafts use shared defaultCredentialDisplayName with collision numbering against existing workspace OAuth credentials; reconnect drafts reuse the credential’s display name.

Mothership chat fork copies uploads born at or before the fork point: storage quota gate, physical blob copies with new ids/keys, transcript and resource reference rewrites, ghost resource cleanup, failed-copy row deletion + failedFileCopies in the response, and copilot-service fork cut aligned to the last kept user message.

<question> special tags add interactive single/multi-select cards (with recap pairing that hides the duplicate user bubble), plus parsing/streaming support. Thinking blocks are omitted from chat rendering; Branch in new chat fork is enabled for persisted assistant messages only.

Function execute returns 422 when sandbox mounts/exports are requested without E2B; overwrite exports get byte-compare unchanged warnings, size/sha256 receipts, and a leading newline on E2B result markers. Streamed workflow remove events evict workflows from the active React Query cache.

Smaller changes: credential reconnect link labels, sim_key placeholder handling, parseJsonArrayValue for KB sub-blocks, mothership execute drops user-skill tool wiring, minimal dev registry adds Slack/Gmail, docs drops biome array-index-key ignores.

Reviewed by Cursor Bugbot for commit 07af501. Bugbot is set up for automated code reviews on this repo. Configure here.

answersByIndex[index] = answers
hiddenUserByIndex[index + 1] = true
}
return { answersByIndex, hiddenUserByIndex }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last tag wins pairing

Medium Severity

When one assistant turn contains more than one question segment, pairing uses only the last tag’s schema via parseLastQuestionTag, but every QuestionDisplay on that turn receives the same questionAnswers. Earlier cards can show another batch’s prompts/answers or blank recap lines.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fcfacf9. Configure here.

setStep(next)
const prefill = customByStep[next] ?? ''
setFreeText(prefill)
setFreeTextEditing(prefill.trim().length > 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stepper ignores committed custom text

Medium Severity

In multi-step question cards, goToStep calls commitCustom() but reads the prefilled “Something else” value from stale customByStep instead of the array commitCustom returns. After moving forward and back, earlier typed custom answers can disappear from the input even though they were just committed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8b6c2df. Configure here.

if (!lines[i].startsWith(prefix)) return null
answers.push(lines[i].slice(prefix.length))
}
return answers

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiline answers break pairing

Medium Severity

Question answers are serialized with newlines between questions, but each answer is not escaped. A multiline “Something else” value adds extra lines, so parseQuestionAnswerMessage treats them as more questions, pairing fails, the user bubble stays visible, and the card never shows the answered recap after reload.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 040dea9. Configure here.

const { copied, failed, failedCopyIds } = await executeChatFileBlobCopies(result.blobTasks, {
userId,
workspaceId: parent.workspaceId ?? undefined,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quota checked before copy completes

Low Severity

Forking checks storage quota from planned byte totals, then commits the chat and copies blobs afterward. Concurrent forks or other uploads can consume quota between the check and incrementStorageUsage, so copies can succeed even when the workspace is already at or over its limit.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 040dea9. Configure here.

Comment thread apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts
Stop the mothership from adopting a workspace user-skill on its own:

- Remove the load_user_skill tool and its three payload callers (chat
  payload, mothership execute route, inbox executor); delete
  lib/mothership/skills.ts + its test. Skills no longer autoload as the
  agent's own instructions.
- Rename the workspace "## Skills" inventory to "## Agent Block Skills
  — NOT FOR YOU" with a one-line guardrail so a skill's description
  (e.g. "respond like a pirate") is not treated as an instruction.
  Skills reach the model as behavior only via explicit /-attach.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 6 total unresolved issues (including 4 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 56c7af1. Configure here.

)
}

if (data.length === 0 || phase === 'dismissed') return null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dismissed question card reappears

Medium Severity

Dismissing a question card only updates local React state (phase === 'dismissed'). After a reload or remount, that state resets to active, so the card shows again even though the user already dismissed it and may have continued the chat another way.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 56c7af1. Configure here.

Comment thread apps/sim/app/api/mothership/chats/[chatId]/fork/route.ts
…top it clearing them (#5546)

* fix(copilot): persist KB tag subblocks as JSON strings from edit_workflow

The edit_workflow tool normalizes array-with-id subblocks (via
normalizeArrayWithIds) but only re-stringifies the keys listed in
JSON_STRING_SUBBLOCK_KEYS. `tagFilters` (knowledge-tag-filters) and
`documentTags` (document-tag-entry) were missing, so agent-authored tag
filters were stored as raw JSON arrays while those UI components read
their value with JSON.parse (expecting a string). The result: an agent
edit to a Knowledge block's tag filter persisted correctly but rendered
as an empty filter in the editor (JSON.parse on an array throws -> []).

- Add `tagFilters` and `documentTags` to JSON_STRING_SUBBLOCK_KEYS so
  edit_workflow stores them in the same shape the UI writes.
- Make both components' parsers tolerate an already-parsed array on read,
  self-healing values already persisted in the broken (array) shape.

Search execution was unaffected (parseTagFilters accepts arrays), so the
value was never lost — only the editor render and round-trip were broken.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(copilot): expose KB tag definitions in VFS meta.json

Surface each knowledge base's defined tags (displayName -> tagSlot) inline in
its meta.json via serializeKBMeta, loaded in one batched query
(loadKbTagDefinitions), so the agent can bind a knowledge-tag filter to a real
tag slot instead of guessing a tag name it cannot otherwise see.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(copilot): stringify KB tag subblocks on the nested-node edit path

The nested-node merge path normalized array-with-id subblocks but never
re-serialized the JSON_STRING_SUBBLOCK_KEYS, so editing a block nested in a
loop/parallel container still persisted tagFilters/documentTags (and
conditions/routes) as raw arrays -- the exact shape the subblock components
cannot JSON.parse.

Route all four write paths through a single normalizeSubblockValue helper so
the normalize and re-stringify steps cannot drift apart again, and extract the
duplicated string-or-array read logic into parseJsonArrayValue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(copilot): tighten subblock serialization helpers

Derive KbTagDefinitionSummary from the canonical TagDefinition instead of
restating its fields, make parseJsonArrayValue generic so callers drop their
`as T[]` casts, and unexport the three builders helpers that no longer have
consumers outside the module now that normalizeSubblockValue fronts them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(copilot): stop stripping tagFilters/documentTags from the agent's workflow view

sanitizeForCopilot dropped `tagFilters` and `documentTags` from the workflow state the
agent reads (workflows/{name}/state.json), while edit_workflow is allowed to write both.
The field was therefore write-only: on a follow-up edit the agent read back an absent
field, concluded no filter was set, and cleared the user's tag filter.

The redaction was introduced for workflow *export* (#1628) and is already enforced there
by sanitizeWorkflowForSharing's key list. The duplicate in the copilot-only
sanitizeSubBlocks was redundant for export and destructive for the agent. Removes it and
pins the contract with a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(copilot): reject malformed KB tag values instead of clearing the filter

`knowledge-tag-filters` and `document-tag-entry` had no arm in the
`edit_workflow` input validator, so they fell through to the pass-through
default. Any non-array value the agent supplied -- a double-encoded JSON
string, an object, an unparseable string -- reached `normalizeSubblockValue`,
where `normalizeArrayWithIds` coerces unparseable input to `[]`. The write
path then persisted `"[]"` over the tag filter the user had configured.

`condition-input` and `router-input` already guard against exactly this and
return an actionable error to the model. Extend that arm to cover the two KB
subblock types. It keys on subblock type, so the unrelated `tagFilters`
short-input on the Algolia block is unaffected. `null`/`undefined` and empty
arrays still clear the field, so intentional clears keep working.

Also wrap `loadKbTagDefinitions` in try/catch. Tag definitions are an optional
meta.json enrichment, but the query ran inside the top-level `Promise.all`, so
a transient failure would reject the entire workspace VFS materialize and
leave the agent unable to read any file. Now it degrades to a meta.json
without tag definitions, matching the sibling materializers.

Adds regression tests for both, plus the first tests for
`parseJsonArrayValue`, the helper that keeps pre-fix raw-array rows readable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(copilot): collapse duplicate JSON-array parsing in edit-workflow builders

`normalizeArrayWithIds` and `normalizeConditionRouterIds` each hand-rolled the
same "accept a raw array or the JSON string these subblocks persist" parse.
Extract `parseJsonArray`, which returns null when the value is neither, so each
caller keeps its own distinct fallback: `[]` for the former, the untouched
original value for the latter.

Behavior-preserving. An empty array is truthy, so `[]` and `"[]"` still parse
through rather than hitting either fallback.

`validation.ts` has a third copy, but `builders.ts` already imports from it, so
sharing the helper across the two would introduce an import cycle. Left as is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(copilot): specify tag name and legal operators in KB meta.json

`tagDefinitions` exposed `displayName`, but a `tagFilters` entry must carry the
key `tagName`. An entry written with `displayName` passes validation and
persists, then filters nothing -- a silent failure. Rename the field at the
serializer boundary; the DB column is untouched.

Also emit the operators legal for each tag's `fieldType`, reusing
`getOperatorsForFieldType`. `between` is valid for number and date but not for
text or boolean, and the agent has no way to infer that. An unrecognized
fieldType yields an empty list rather than throwing.

Still unspecified, and deliberately out of scope: a filter entry's value key is
`tagValue` (but `value` on documentTags), and `between` needs `valueTo`. Those
describe the subblock entry shape, not the knowledge base, so meta.json is the
wrong place for them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(copilot): pass a nullish subblock clear through instead of serializing "[]"

`validateValueForSubBlockType` accepts null as an explicit clear, but
`normalizeSubblockValue` then ran it through `normalizeArrayWithIds`, which
coerces any non-array to `[]`, and persisted the string "[]".

No data is lost either way -- "[]" and an absent field both mean "no filters".
But it left the field present when the caller asked for it to be unset, so
`sanitizeForCopilot` showed the agent an empty filter rather than an absent
one, contradicting the absent-means-unset invariant the sanitizer documents.
It also made Algolia's `if (params.tagFilters)` see a set value, since "[]" is
truthy.

An explicitly empty array still serializes to "[]" -- clearing with a value is
distinct from clearing by omission.

Reported by Cursor Bugbot on #5546.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants