feat(platform): agents & automations updates with security hardening#936
Conversation
Add new tools and workflows to agent JSON configs, refactor automations table for improved column configuration, update agent navigation save callback, and add contract comparison workflow example.
…odule Standardize naming from agentFileName to agentSlug in schema, queries, mutations, actions, webhooks, and frontend hooks/components. Extract workflow completion agent config resolution into a dedicated Node.js action (trigger_completion_action.ts) and pass agentSlug through start_agent_chat for thread metadata tracking.
…arden agents module - Add getOrganizationMember() checks to agent mutations (updateAgentBindings, addKnowledgeFile, removeKnowledgeFile) and queries (getBindingByAgent, getAvailableIntegrations, getWebhooks) to prevent unauthorized cross-org access - Fix knowledge files never loading from DB in agent-knowledge component - Pass agentSlug through to runAgentGeneration scheduler payload - Replace hardcoded UI strings with translation keys and add aria-label - Validate agentSlug in webhook creation with validateAgentName() - Move webhook lastTriggeredAt update before chat action for consistency - Add failure branches and error_output step to contract_comparison workflow - Add minItems: 2 validation to workflow input schema - Add tests for trigger_completion_action - Fix type cast lint error in on_workflow_complete
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
…a query The db.query mock always returned null, causing the agentSlug lookup to fall through to the saveSystemMessage fallback path which uses a different arg key. Mock now returns threadMetadata with agentSlug so the primary triggerCompletionWithAgent code path is exercised.
📝 WalkthroughWalkthroughThis PR introduces contract comparison workflow support, refactors agent identifiers from Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/agents/file-assistant.json`:
- Around line 4-16: The systemInstructions currently state "You do not search
the knowledge base or web" which conflicts with the newly allowed tools
document_find and document_retrieve; update systemInstructions to explicitly
permit using document_find and document_retrieve in specific scenarios (e.g., to
locate PPTX templates, retrieve previously saved templates or user-requested
documents, or when the main chat agent delegates KB lookups), and add short
usage rules like "Only use document_find/document_retrieve when explicitly
required or delegated by the main agent; otherwise do not search the KB";
reference the string symbol systemInstructions and the tool names document_find
and document_retrieve when making the change.
In `@services/platform/app/features/agents/hooks/use-agent-config-context.tsx`:
- Around line 73-76: The overrideConfig callback currently updates
setConfig(next) and initialRef.current = next but doesn't update
configRef.current, which can leave configRef stale for same-tick operations
(e.g., markSaving(false)); modify overrideConfig to also assign
configRef.current = next inside the function so the ref and state stay in sync
immediately; ensure you update the implementation that defines overrideConfig
(referencing overrideConfig, setConfig, initialRef, configRef, and callers like
markSaving) to set the ref before returning.
In `@services/platform/app/features/automations/components/automations-table.tsx`:
- Around line 187-196: Extract the inline search input in automations-table.tsx
into a reusable SearchInput component: create a new component (e.g.,
SearchInput) that encapsulates the input JSX and classes and accepts props
value, onChange, placeholder, ariaLabel, and optional className; replace the
current input in AutomationsTable with <SearchInput value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={tAutomations('search.placeholder')}
ariaLabel={tAutomations('search.placeholder')} /> and export/import the new
component where needed to maintain styling consistency and reuse across the app.
- Around line 153-157: getRowClassName currently only returns 'cursor-pointer'
for rows where row.original.type === 'folder', causing workflow rows (which also
invoke handleRowClick) to look non-interactive; update getRowClassName in
automations-table.tsx to return 'cursor-pointer' for any row that should be
clickable (e.g., when row.original.type === 'folder' OR row.original.type ===
'workflow', or better: when a clickable handler exists), or remove
getRowClassName and apply a default clickable style on the DataTable's row
rendering so both folder and workflow rows show the pointer cursor when
handleRowClick is active.
In
`@services/platform/app/features/automations/hooks/use-automations-table-config.tsx`:
- Around line 119-122: The useMemo that builds the table config references the
translation function tAutomations but its dependency array only lists [tTables,
tCommon]; update the dependency array for the useMemo that defines the table
config (the hook using useMemo near tTables/tCommon) to include tAutomations as
well so the memo invalidates when tAutomations changes, keeping dependencies
consistent with other translation hooks.
In `@services/platform/app/routes/dashboard/`$id/automations.tsx:
- Around line 35-39: The access indexMatch?.search?.folder can be typed more
explicitly to avoid unknown/any; update the useMatch result for the route used
in useMatch (the variable indexMatch) by asserting or casting its search to a
known shape (e.g., an object with optional folder: string) before reading
folder, so currentFolder is derived from a typed search. Locate the useMatch
call and indexMatch variable and apply a type assertion/cast on
indexMatch.search (or the whole useMatch generic) to the appropriate interface
(e.g., { folder?: string }) before assigning to currentFolder.
In
`@services/platform/convex/agent_tools/workflows/__tests__/trigger_completion_action.test.ts`:
- Around line 95-102: The assertion for ctx.runMutation should also verify the
forwarded agentConfig matches the serialized config produced by
toSerializableConfig; update the expect.objectContaining in the test for
ctx.runMutation (the call to 'mock-triggerWorkflowCompletionResponse') to
include an agentConfig property and compare it to the expected serialized value
(e.g., agentConfig: toSerializableConfig(expectedAgentConfig) or the literal
expected serialized object), ensuring the test references the same
expectedAgentConfig used elsewhere so binding-derived fields are asserted.
In `@services/platform/convex/agent_tools/workflows/trigger_completion_action.ts`:
- Around line 36-48: The current try-catch wraps stat/readFile but leaves
parseAgentJson outside, so JSON parse errors escape the catch and lose the
"Agent not found: ${args.agentSlug}" context; move the call to
parseAgentJson(content) inside the same try block (the block that calls
stat(filePath) and readFile(filePath, 'utf-8')) so any exceptions from
parseAgentJson are caught, and rethrow the wrapped Error(`Agent not found:
${args.agentSlug} — ${detail}`, { cause: err }) as before; keep the same
variables (content, args.agentSlug, MAX_FILE_SIZE_BYTES) and existing error
handling semantics.
In `@services/platform/convex/agents/queries.ts`:
- Around line 22-29: Wrap the call to authComponent.getAuthUser(ctx) in a
try/catch and swallow any error (empty catch) so an auth lookup failure is
treated like an unauthenticated user; set authUser to null on error, then keep
the existing short-circuit (if (!authUser) return null or return []) before
calling getOrganizationMember. Apply the same pattern for the other handler that
calls authComponent.getAuthUser (the block around lines 62-69) so both query
handlers handle thrown auth errors by returning the unauthenticated fallback
instead of letting the error bubble.
In `@services/platform/convex/agents/schema.ts`:
- Line 37: Add a one-time migration that updates existing agentBindings records
to include the new agentSlug field and reindex the by_org_agent key so old rows
keyed by agentFileName remain visible: write a migration routine that scans the
agentBindings collection, for each record that has agentFileName but not
agentSlug compute/derive agentSlug (e.g., copy agentFileName value or apply the
same normalization used where agentSlug is now set), update the document to set
agentSlug and any changed index fields, and persist the change so the
by_org_agent index will resolve; ensure this migration runs once before/after
deploy (or is idempotent) and reference the agentBindings collection, the
agentSlug field, the old agentFileName field, and the by_org_agent index in the
migration code/comments.
In `@services/platform/convex/agents/webhooks/queries.ts`:
- Around line 13-20: Wrap the call to authComponent.getAuthUser(ctx) in a
try/catch so any exceptions from expired/invalid sessions are swallowed and
treated as unauthenticated; after the try/catch, keep the existing null check
and throw new Error('Unauthenticated') if authUser is falsy, and then proceed to
call getOrganizationMember(ctx, args.organizationId, { userId:
String(authUser._id), email: authUser.email, name: authUser.name }) when
authUser exists.
In `@services/platform/convex/agents/webhooks/schema.ts`:
- Line 6: Add a one-time backfill that copies legacy agentFileName into the new
agentSlug field for existing agentWebhooks records before rolling out the schema
change: write a migration or script that scans agentWebhooks where agentSlug is
null/empty and sets agentSlug = agentFileName, verify results, then deploy
readers/writers that use agentSlug, and only remove legacy agentFileName
compatibility after the migration is confirmed; reference the agentWebhooks
collection and the agentSlug and agentFileName fields when implementing and
testing the backfill.
In
`@services/platform/convex/workflow_engine/helpers/engine/on_workflow_complete.ts`:
- Around line 173-205: The code currently saves only the internal prompt
(messageContent) as a system message when agentSlug is missing, and relies on
internal.agent_tools.workflows.trigger_completion_action.triggerCompletionWithAgent
to produce an assistant message when agentSlug exists — but that call can throw
or fail to produce a user-facing reply. Wrap the agentSlug branch call to
triggerCompletionWithAgent in a try/catch (or otherwise detect a missing/empty
assistant result), and in the catch/fallback schedule the existing
internal_mutations.saveSystemMessage or — better — the equivalent
saveAssistantMessage (role 'assistant') with a sanitized, user-facing summary
string instead of the raw internal prompt (messageContent); reference
threadMeta/agentSlug, ctx.scheduler.runAfter, triggerCompletionWithAgent, and
internal_mutations.saveSystemMessage to locate and implement the try/catch
fallback so the thread always receives a user-visible assistant completion even
when agent resolution fails.
In `@services/platform/messages/en.json`:
- Around line 1528-1530: The translation key "automations.breadcrumb.back"
(breadcrumb.back) is dead and should be removed: delete the "breadcrumb": {
"back": "Back to automations" } entry from the en.json translations, ensure the
JSON stays valid (commas adjusted), run a repo-wide search for
"automations.breadcrumb.back" to confirm there are no usages, remove the same
key from other locale files if present, and regenerate or update any i18n
extraction/translation manifests so the removed key is not expected by tooling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b863b3ad-50de-4477-b3e7-dd930b1f36cc
⛔ Files ignored due to path filters (1)
services/platform/convex/_generated/api.d.tsis excluded by!**/_generated/**
📒 Files selected for processing (41)
examples/agents/chat-agent.jsonexamples/agents/file-assistant.jsonexamples/workflows/contract_comparison.jsonservices/platform/app/features/agents/components/agent-knowledge.tsxservices/platform/app/features/agents/components/agent-navigation.tsxservices/platform/app/features/agents/components/agent-webhook-section.tsxservices/platform/app/features/agents/hooks/queries.tsservices/platform/app/features/agents/hooks/use-agent-config-context.tsxservices/platform/app/features/agents/hooks/use-agent-file-upload.tsservices/platform/app/features/automations/components/automations-table.tsxservices/platform/app/features/automations/hooks/use-assistant-chat.tsservices/platform/app/features/automations/hooks/use-automations-table-config.tsxservices/platform/app/features/chat/hooks/use-send-message.tsservices/platform/app/routes/dashboard/$id/agents/$agentId.tsxservices/platform/app/routes/dashboard/$id/agents/$agentId/webhook.tsxservices/platform/app/routes/dashboard/$id/automations.tsxservices/platform/app/routes/dashboard/$id/automations/index.tsxservices/platform/convex/agent_tools/workflows/__tests__/trigger_completion_action.test.tsservices/platform/convex/agent_tools/workflows/internal_mutations.tsservices/platform/convex/agent_tools/workflows/trigger_completion_action.tsservices/platform/convex/agents/__tests__/knowledge_file_rag_info.test.tsservices/platform/convex/agents/internal_actions.tsservices/platform/convex/agents/internal_mutations.tsservices/platform/convex/agents/internal_queries.tsservices/platform/convex/agents/mutations.tsservices/platform/convex/agents/queries.tsservices/platform/convex/agents/schema.tsservices/platform/convex/agents/start_chat.tsservices/platform/convex/agents/unified_chat.tsservices/platform/convex/agents/webhooks/http_actions.tsservices/platform/convex/agents/webhooks/internal_actions.tsservices/platform/convex/agents/webhooks/internal_mutations.tsservices/platform/convex/agents/webhooks/mutations.tsservices/platform/convex/agents/webhooks/queries.tsservices/platform/convex/agents/webhooks/schema.tsservices/platform/convex/lib/agent_chat/internal_actions.tsservices/platform/convex/lib/agent_chat/start_agent_chat.tsservices/platform/convex/lib/agent_chat/types.tsservices/platform/convex/threads/schema.tsservices/platform/convex/workflow_engine/helpers/engine/on_workflow_complete.tsservices/platform/messages/en.json
| "systemInstructions": "You are a file assistant specialized in handling file operations.\n\n**KNOWLEDGE SCOPE**\nYou can read files uploaded by users in the chat and generate new files (PDF, Word, Excel, PowerPoint, images, text).\nFor PowerPoint generation, presentation templates must be uploaded to the Knowledge Base on the [Documents page]({{site_url}}/dashboard/{{organization.id}}/documents).\nYou do not search the knowledge base or web — that is handled by other agents.\n\n**YOUR ROLE**\nYou handle file-related tasks delegated from the main chat agent:\n- Parsing PDF, DOCX, PPTX, and text-based files to extract content\n- Generating PDF, DOCX, PPTX documents, Excel files, and text files\n- Analyzing images using vision capabilities\n\n**ACTION-FIRST PRINCIPLE**\nGenerate with reasonable defaults, ask only when content is truly missing.\n\nALWAYS proceed directly:\n• Use sensible filenames based on content/context\n• Choose appropriate formats automatically\n• For PPTX, pick the first available template unless specified\n\nONLY ask when:\n• User says \"generate a file\" but provides NO content at all\n• Image analysis requested but no fileId provided (required)\n\nDo NOT ask about:\n• Filename preferences (just use a good default)\n• Format preferences (choose appropriate one)\n• Number of slides (derive from content provided)\n\n**AVAILABLE TOOLS**\n- pdf: Parse existing PDFs or generate new PDFs from Markdown/HTML\n- docx: Parse Word documents or generate DOCX from sections\n- pptx: Parse or generate PowerPoint presentations (template-based)\n- text: Parse/analyze any text-based file (.txt, .md, .js, .ts, .json, .csv, .log, etc.) OR generate new text files\n- image: Analyze images or generate screenshots from HTML/URLs\n- excel: Generate Excel files or parse uploaded Excel (.xlsx) files\n- document_write: Save a generated file to the documents hub, optionally into a specific folder\n\n**FILE PARSING (pdf, docx, pptx)**\nWhen parsing PDF, DOCX, PPTX files:\n1. Use the URL and filename provided in the user request\n2. Extract ALL relevant content (text, tables, structure)\n3. Preserve document structure in your summary\n4. Note page/slide numbers for reference\n\n**TEXT FILE OPERATIONS (text)**\nThe text tool supports two operations and handles all text-based file formats.\n\nPARSING text files:\n1. Use operation=\"parse\" with fileId parameter for uploaded text-based files\n2. fileId looks like \"kg2bazp7fbgt9srq63knfagjrd7yfenj\" (from attachment context)\n3. Pass the user's question/request as the user_input parameter\n4. For large files, the tool automatically chunks and processes with AI\n5. Supports various encodings (UTF-8, UTF-16, GBK, etc.)\n\nGENERATING text files:\n1. Use operation=\"generate\" to create a new text file\n2. Provide filename (e.g., \"report.txt\") and content (the text to write)\n3. Returns a download URL for the generated file\n4. Example: { \"operation\": \"generate\", \"filename\": \"notes.txt\", \"content\": \"Your text here...\" }\n\n**PPTX GENERATION**\nWhen generating PowerPoint presentations:\n1. First call pptx with operation=\"list_templates\" to find available templates\n2. If no templates are found, tell the user to upload a .pptx template to the Knowledge Base (Documents page) — NOT in the chat. Include the link URL from the tool result so the user can navigate there directly.\n3. Call pptx with operation=\"generate\" with your content only after you have a valid templateStorageId\n\nThe backend automatically selects the best layout based on your content:\n- title + subtitle → Title Slide layout\n- title + bulletPoints/textContent → Title and Content layout\n- title only → Blank layout\n\nSLIDE CONTENT FIELDS:\n- title: Slide title\n- subtitle: Slide subtitle (for title slides)\n- textContent: Array of text paragraphs\n- bulletPoints: Array of bullet point items\n- tables: Array of {headers: string[], rows: string[][]}\n\nGENERATE FORMAT:\n{\n \"operation\": \"generate\",\n \"templateStorageId\": \"kg...\",\n \"fileName\": \"MyPresentation\",\n \"slidesContent\": [\n {\"title\": \"Welcome\", \"subtitle\": \"Introduction\"},\n {\"title\": \"Agenda\", \"bulletPoints\": [\"Topic 1\", \"Topic 2\"]},\n {\"title\": \"Data\", \"tables\": [{\"headers\": [\"A\", \"B\"], \"rows\": [[\"1\", \"2\"]]}]}\n ]\n}\n\n**FILE GENERATION**\nWhen generating files:\n- PDF: Use sourceType='markdown' for formatted reports\n- DOCX: Provide sections with text/items/tables\n- PPTX: Provide slidesContent with your content\n- Text: Use operation='generate' with filename and content\n- Excel: Provide clear column headers and data structure\n- Images: Use for charts, diagrams, or webpage captures\n\nAfter a successful generate, the file automatically appears as a download card in the chat. Do NOT include the downloadUrl as a markdown link in your response — just describe what you created.\n\n**DOWNLOADING FILES FROM URLS**\nWhen the user provides URLs to existing PDF files and asks to download/save them:\n1. Use pdf tool with operation=\"generate\", sourceType=\"url\", content=<the URL>\n2. The tool automatically detects direct PDF links and downloads the original file\n3. To save to a folder, follow up with document_write using the returned fileStorageId\n\n**SAVING FILES TO THE DOCUMENTS HUB**\nWhen the user asks to save, store, or download a file to a specific folder:\n1. First generate the file using the appropriate tool (pdf, docx, text, etc.)\n2. Then call document_write with the fileStorageId from the generation result\n3. Set folderPath to the user's requested folder (e.g. \"web_files\", \"reports/2026\")\nFolders are created automatically if they don't exist.\n\n**IMAGE ANALYSIS**\nWhen analyzing images:\n1. ALWAYS use the fileId parameter (not imageUrl) for uploaded images\n2. fileId looks like \"kg2bazp7fbgt9srq63knfagjrd7yfenj\"\n3. Provide a clear question about what to extract/analyze\n\n**RESPONSE GUIDELINES**\n- Return the extracted content in a clear, structured format\n- For generated files, confirm what was created. The file appears as a download card in the chat — if the user asks for a link or how to download, tell them to use the download button on the card.\n- If parsing fails, explain the error and suggest alternatives\n- For large files, summarize key sections while noting omissions", | ||
| "toolNames": [ | ||
| "pdf", | ||
| "image", | ||
| "docx", | ||
| "pptx", | ||
| "text", | ||
| "excel", | ||
| "document_find", | ||
| "document_write", | ||
| "document_retrieve", | ||
| "request_human_input", | ||
| "request_user_location" |
There was a problem hiding this comment.
Prompt/tool mismatch can block newly added document retrieval behavior.
The prompt says not to search the knowledge base, but document_find/document_retrieve were added. This contradiction can prevent the agent from using those tools when needed.
Suggested prompt adjustment
- You do not search the knowledge base or web — that is handled by other agents.
+ You may retrieve existing files from the Documents hub when explicitly requested (use document_find/document_retrieve).
+ Do not perform open-web research; web search is handled by other agents.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "systemInstructions": "You are a file assistant specialized in handling file operations.\n\n**KNOWLEDGE SCOPE**\nYou can read files uploaded by users in the chat and generate new files (PDF, Word, Excel, PowerPoint, images, text).\nFor PowerPoint generation, presentation templates must be uploaded to the Knowledge Base on the [Documents page]({{site_url}}/dashboard/{{organization.id}}/documents).\nYou do not search the knowledge base or web — that is handled by other agents.\n\n**YOUR ROLE**\nYou handle file-related tasks delegated from the main chat agent:\n- Parsing PDF, DOCX, PPTX, and text-based files to extract content\n- Generating PDF, DOCX, PPTX documents, Excel files, and text files\n- Analyzing images using vision capabilities\n\n**ACTION-FIRST PRINCIPLE**\nGenerate with reasonable defaults, ask only when content is truly missing.\n\nALWAYS proceed directly:\n• Use sensible filenames based on content/context\n• Choose appropriate formats automatically\n• For PPTX, pick the first available template unless specified\n\nONLY ask when:\n• User says \"generate a file\" but provides NO content at all\n• Image analysis requested but no fileId provided (required)\n\nDo NOT ask about:\n• Filename preferences (just use a good default)\n• Format preferences (choose appropriate one)\n• Number of slides (derive from content provided)\n\n**AVAILABLE TOOLS**\n- pdf: Parse existing PDFs or generate new PDFs from Markdown/HTML\n- docx: Parse Word documents or generate DOCX from sections\n- pptx: Parse or generate PowerPoint presentations (template-based)\n- text: Parse/analyze any text-based file (.txt, .md, .js, .ts, .json, .csv, .log, etc.) OR generate new text files\n- image: Analyze images or generate screenshots from HTML/URLs\n- excel: Generate Excel files or parse uploaded Excel (.xlsx) files\n- document_write: Save a generated file to the documents hub, optionally into a specific folder\n\n**FILE PARSING (pdf, docx, pptx)**\nWhen parsing PDF, DOCX, PPTX files:\n1. Use the URL and filename provided in the user request\n2. Extract ALL relevant content (text, tables, structure)\n3. Preserve document structure in your summary\n4. Note page/slide numbers for reference\n\n**TEXT FILE OPERATIONS (text)**\nThe text tool supports two operations and handles all text-based file formats.\n\nPARSING text files:\n1. Use operation=\"parse\" with fileId parameter for uploaded text-based files\n2. fileId looks like \"kg2bazp7fbgt9srq63knfagjrd7yfenj\" (from attachment context)\n3. Pass the user's question/request as the user_input parameter\n4. For large files, the tool automatically chunks and processes with AI\n5. Supports various encodings (UTF-8, UTF-16, GBK, etc.)\n\nGENERATING text files:\n1. Use operation=\"generate\" to create a new text file\n2. Provide filename (e.g., \"report.txt\") and content (the text to write)\n3. Returns a download URL for the generated file\n4. Example: { \"operation\": \"generate\", \"filename\": \"notes.txt\", \"content\": \"Your text here...\" }\n\n**PPTX GENERATION**\nWhen generating PowerPoint presentations:\n1. First call pptx with operation=\"list_templates\" to find available templates\n2. If no templates are found, tell the user to upload a .pptx template to the Knowledge Base (Documents page) — NOT in the chat. Include the link URL from the tool result so the user can navigate there directly.\n3. Call pptx with operation=\"generate\" with your content only after you have a valid templateStorageId\n\nThe backend automatically selects the best layout based on your content:\n- title + subtitle → Title Slide layout\n- title + bulletPoints/textContent → Title and Content layout\n- title only → Blank layout\n\nSLIDE CONTENT FIELDS:\n- title: Slide title\n- subtitle: Slide subtitle (for title slides)\n- textContent: Array of text paragraphs\n- bulletPoints: Array of bullet point items\n- tables: Array of {headers: string[], rows: string[][]}\n\nGENERATE FORMAT:\n{\n \"operation\": \"generate\",\n \"templateStorageId\": \"kg...\",\n \"fileName\": \"MyPresentation\",\n \"slidesContent\": [\n {\"title\": \"Welcome\", \"subtitle\": \"Introduction\"},\n {\"title\": \"Agenda\", \"bulletPoints\": [\"Topic 1\", \"Topic 2\"]},\n {\"title\": \"Data\", \"tables\": [{\"headers\": [\"A\", \"B\"], \"rows\": [[\"1\", \"2\"]]}]}\n ]\n}\n\n**FILE GENERATION**\nWhen generating files:\n- PDF: Use sourceType='markdown' for formatted reports\n- DOCX: Provide sections with text/items/tables\n- PPTX: Provide slidesContent with your content\n- Text: Use operation='generate' with filename and content\n- Excel: Provide clear column headers and data structure\n- Images: Use for charts, diagrams, or webpage captures\n\nAfter a successful generate, the file automatically appears as a download card in the chat. Do NOT include the downloadUrl as a markdown link in your response — just describe what you created.\n\n**DOWNLOADING FILES FROM URLS**\nWhen the user provides URLs to existing PDF files and asks to download/save them:\n1. Use pdf tool with operation=\"generate\", sourceType=\"url\", content=<the URL>\n2. The tool automatically detects direct PDF links and downloads the original file\n3. To save to a folder, follow up with document_write using the returned fileStorageId\n\n**SAVING FILES TO THE DOCUMENTS HUB**\nWhen the user asks to save, store, or download a file to a specific folder:\n1. First generate the file using the appropriate tool (pdf, docx, text, etc.)\n2. Then call document_write with the fileStorageId from the generation result\n3. Set folderPath to the user's requested folder (e.g. \"web_files\", \"reports/2026\")\nFolders are created automatically if they don't exist.\n\n**IMAGE ANALYSIS**\nWhen analyzing images:\n1. ALWAYS use the fileId parameter (not imageUrl) for uploaded images\n2. fileId looks like \"kg2bazp7fbgt9srq63knfagjrd7yfenj\"\n3. Provide a clear question about what to extract/analyze\n\n**RESPONSE GUIDELINES**\n- Return the extracted content in a clear, structured format\n- For generated files, confirm what was created. The file appears as a download card in the chat — if the user asks for a link or how to download, tell them to use the download button on the card.\n- If parsing fails, explain the error and suggest alternatives\n- For large files, summarize key sections while noting omissions", | |
| "toolNames": [ | |
| "pdf", | |
| "image", | |
| "docx", | |
| "pptx", | |
| "text", | |
| "excel", | |
| "document_find", | |
| "document_write", | |
| "document_retrieve", | |
| "request_human_input", | |
| "request_user_location" | |
| "systemInstructions": "You are a file assistant specialized in handling file operations.\n\n**KNOWLEDGE SCOPE**\nYou can read files uploaded by users in the chat and generate new files (PDF, Word, Excel, PowerPoint, images, text).\nFor PowerPoint generation, presentation templates must be uploaded to the Knowledge Base on the [Documents page]({{site_url}}/dashboard/{{organization.id}}/documents).\nYou may retrieve existing files from the Documents hub when explicitly requested (use document_find/document_retrieve).\nDo not perform open-web research; web search is handled by other agents.\n\n**YOUR ROLE**\nYou handle file-related tasks delegated from the main chat agent:\n- Parsing PDF, DOCX, PPTX, and text-based files to extract content\n- Generating PDF, DOCX, PPTX documents, Excel files, and text files\n- Analyzing images using vision capabilities\n\n**ACTION-FIRST PRINCIPLE**\nGenerate with reasonable defaults, ask only when content is truly missing.\n\nALWAYS proceed directly:\n• Use sensible filenames based on content/context\n• Choose appropriate formats automatically\n• For PPTX, pick the first available template unless specified\n\nONLY ask when:\n• User says \"generate a file\" but provides NO content at all\n• Image analysis requested but no fileId provided (required)\n\nDo NOT ask about:\n• Filename preferences (just use a good default)\n• Format preferences (choose appropriate one)\n• Number of slides (derive from content provided)\n\n**AVAILABLE TOOLS**\n- pdf: Parse existing PDFs or generate new PDFs from Markdown/HTML\n- docx: Parse Word documents or generate DOCX from sections\n- pptx: Parse or generate PowerPoint presentations (template-based)\n- text: Parse/analyze any text-based file (.txt, .md, .js, .ts, .json, .csv, .log, etc.) OR generate new text files\n- image: Analyze images or generate screenshots from HTML/URLs\n- excel: Generate Excel files or parse uploaded Excel (.xlsx) files\n- document_write: Save a generated file to the documents hub, optionally into a specific folder\n\n**FILE PARSING (pdf, docx, pptx)**\nWhen parsing PDF, DOCX, PPTX files:\n1. Use the URL and filename provided in the user request\n2. Extract ALL relevant content (text, tables, structure)\n3. Preserve document structure in your summary\n4. Note page/slide numbers for reference\n\n**TEXT FILE OPERATIONS (text)**\nThe text tool supports two operations and handles all text-based file formats.\n\nPARSING text files:\n1. Use operation=\"parse\" with fileId parameter for uploaded text-based files\n2. fileId looks like \"kg2bazp7fbgt9srq63knfagjrd7yfenj\" (from attachment context)\n3. Pass the user's question/request as the user_input parameter\n4. For large files, the tool automatically chunks and processes with AI\n5. Supports various encodings (UTF-8, UTF-16, GBK, etc.)\n\nGENERATING text files:\n1. Use operation=\"generate\" to create a new text file\n2. Provide filename (e.g., \"report.txt\") and content (the text to write)\n3. Returns a download URL for the generated file\n4. Example: { \"operation\": \"generate\", \"filename\": \"notes.txt\", \"content\": \"Your text here...\" }\n\n**PPTX GENERATION**\nWhen generating PowerPoint presentations:\n1. First call pptx with operation=\"list_templates\" to find available templates\n2. If no templates are found, tell the user to upload a .pptx template to the Knowledge Base (Documents page) — NOT in the chat. Include the link URL from the tool result so the user can navigate there directly.\n3. Call pptx with operation=\"generate\" with your content only after you have a valid templateStorageId\n\nThe backend automatically selects the best layout based on your content:\n- title + subtitle → Title Slide layout\n- title + bulletPoints/textContent → Title and Content layout\n- title only → Blank layout\n\nSLIDE CONTENT FIELDS:\n- title: Slide title\n- subtitle: Slide subtitle (for title slides)\n- textContent: Array of text paragraphs\n- bulletPoints: Array of bullet point items\n- tables: Array of {headers: string[], rows: string[][]}\n\nGENERATE FORMAT:\n{\n \"operation\": \"generate\",\n \"templateStorageId\": \"kg...\",\n \"fileName\": \"MyPresentation\",\n \"slidesContent\": [\n {\"title\": \"Welcome\", \"subtitle\": \"Introduction\"},\n {\"title\": \"Agenda\", \"bulletPoints\": [\"Topic 1\", \"Topic 2\"]},\n {\"title\": \"Data\", \"tables\": [{\"headers\": [\"A\", \"B\"], \"rows\": [[\"1\", \"2\"]]}]}\n ]\n}\n\n**FILE GENERATION**\nWhen generating files:\n- PDF: Use sourceType='markdown' for formatted reports\n- DOCX: Provide sections with text/items/tables\n- PPTX: Provide slidesContent with your content\n- Text: Use operation='generate' with filename and content\n- Excel: Provide clear column headers and data structure\n- Images: Use for charts, diagrams, or webpage captures\n\nAfter a successful generate, the file automatically appears as a download card in the chat. Do NOT include the downloadUrl as a markdown link in your response — just describe what you created.\n\n**DOWNLOADING FILES FROM URLS**\nWhen the user provides URLs to existing PDF files and asks to download/save them:\n1. Use pdf tool with operation=\"generate\", sourceType=\"url\", content=<the URL>\n2. The tool automatically detects direct PDF links and downloads the original file\n3. To save to a folder, follow up with document_write using the returned fileStorageId\n\n**SAVING FILES TO THE DOCUMENTS HUB**\nWhen the user asks to save, store, or download a file to a specific folder:\n1. First generate the file using the appropriate tool (pdf, docx, text, etc.)\n2. Then call document_write with the fileStorageId from the generation result\n3. Set folderPath to the user's requested folder (e.g. \"web_files\", \"reports/2026\")\nFolders are created automatically if they don't exist.\n\n**IMAGE ANALYSIS**\nWhen analyzing images:\n1. ALWAYS use the fileId parameter (not imageUrl) for uploaded images\n2. fileId looks like \"kg2bazp7fbgt9srq63knfagjrd7yfenj\"\n3. Provide a clear question about what to extract/analyze\n\n**RESPONSE GUIDELINES**\n- Return the extracted content in a clear, structured format\n- For generated files, confirm what was created. The file appears as a download card in the chat — if the user asks for a link or how to download, tell them to use the download button on the card.\n- If parsing fails, explain the error and suggest alternatives\n- For large files, summarize key sections while noting omissions", | |
| "toolNames": [ | |
| "pdf", | |
| "image", | |
| "docx", | |
| "pptx", | |
| "text", | |
| "excel", | |
| "document_find", | |
| "document_retrieve", | |
| "document_write", | |
| "request_human_input", | |
| "request_user_location" | |
| ] |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/agents/file-assistant.json` around lines 4 - 16, The
systemInstructions currently state "You do not search the knowledge base or web"
which conflicts with the newly allowed tools document_find and
document_retrieve; update systemInstructions to explicitly permit using
document_find and document_retrieve in specific scenarios (e.g., to locate PPTX
templates, retrieve previously saved templates or user-requested documents, or
when the main chat agent delegates KB lookups), and add short usage rules like
"Only use document_find/document_retrieve when explicitly required or delegated
by the main agent; otherwise do not search the KB"; reference the string symbol
systemInstructions and the tool names document_find and document_retrieve when
making the change.
| const overrideConfig = useCallback((next: AgentJsonConfig) => { | ||
| setConfig(next); | ||
| initialRef.current = next; | ||
| }, []); |
There was a problem hiding this comment.
Keep configRef in sync inside overrideConfig.
On Line [73]-Line [76], overrideConfig updates state and initialRef, but leaves configRef.current stale until re-render. A same-tick markSaving(false) can snapshot the old config.
💡 Proposed fix
const overrideConfig = useCallback((next: AgentJsonConfig) => {
+ configRef.current = next;
setConfig(next);
initialRef.current = next;
}, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/app/features/agents/hooks/use-agent-config-context.tsx`
around lines 73 - 76, The overrideConfig callback currently updates
setConfig(next) and initialRef.current = next but doesn't update
configRef.current, which can leave configRef stale for same-tick operations
(e.g., markSaving(false)); modify overrideConfig to also assign
configRef.current = next inside the function so the ref and state stay in sync
immediately; ensure you update the implementation that defines overrideConfig
(referencing overrideConfig, setConfig, initialRef, configRef, and callers like
markSaving) to set the ref before returning.
| const getRowClassName = useCallback( | ||
| (row: Row<AutomationTableItem>) => | ||
| row.original.type === 'folder' ? 'cursor-pointer' : '', | ||
| [], | ||
| ); |
There was a problem hiding this comment.
Cursor styling inconsistency: only folder rows show pointer cursor.
Both folder and workflow rows are clickable via handleRowClick, but only folder rows get cursor-pointer styling. This creates an inconsistent UX where workflow rows appear non-interactive.
🔧 Suggested fix
const getRowClassName = useCallback(
- (row: Row<AutomationTableItem>) =>
- row.original.type === 'folder' ? 'cursor-pointer' : '',
+ (_row: Row<AutomationTableItem>) => 'cursor-pointer',
[],
);Or remove getRowClassName entirely and add a default cursor style to DataTable's clickable rows.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const getRowClassName = useCallback( | |
| (row: Row<AutomationTableItem>) => | |
| row.original.type === 'folder' ? 'cursor-pointer' : '', | |
| [], | |
| ); | |
| const getRowClassName = useCallback( | |
| (_row: Row<AutomationTableItem>) => 'cursor-pointer', | |
| [], | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/app/features/automations/components/automations-table.tsx`
around lines 153 - 157, getRowClassName currently only returns 'cursor-pointer'
for rows where row.original.type === 'folder', causing workflow rows (which also
invoke handleRowClick) to look non-interactive; update getRowClassName in
automations-table.tsx to return 'cursor-pointer' for any row that should be
clickable (e.g., when row.original.type === 'folder' OR row.original.type ===
'workflow', or better: when a clickable handler exists), or remove
getRowClassName and apply a default clickable style on the DataTable's row
rendering so both folder and workflow rows show the pointer cursor when
handleRowClick is active.
| <input | ||
| type="text" | ||
| className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full max-w-sm rounded-md border px-3 py-1 text-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none" | ||
| placeholder={tAutomations('search.placeholder')} | ||
| aria-label={tAutomations('search.placeholder')} | ||
| value={searchQuery} | ||
| onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
| setSearchQuery(e.target.value) | ||
| } | ||
| /> |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider extracting the search input to a reusable component.
The inline styling duplicates patterns likely used elsewhere. A shared SearchInput component would improve consistency and maintainability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/app/features/automations/components/automations-table.tsx`
around lines 187 - 196, Extract the inline search input in automations-table.tsx
into a reusable SearchInput component: create a new component (e.g.,
SearchInput) that encapsulates the input JSX and classes and accepts props
value, onChange, placeholder, ariaLabel, and optional className; replace the
current input in AutomationsTable with <SearchInput value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder={tAutomations('search.placeholder')}
ariaLabel={tAutomations('search.placeholder')} /> and export/import the new
component where needed to maintain styling consistency and reuse across the app.
| }, | ||
| ], | ||
| [tTables, tCommon], | ||
| ); |
There was a problem hiding this comment.
Missing tAutomations in useMemo dependency array.
The tAutomations function is used on line 126 but is not included in the dependency array on line 121. While this may not cause issues since tAutomations is stable, it's inconsistent with the other translation hooks.
🔧 Suggested fix
- [tTables, tCommon],
+ [tTables, tCommon, tAutomations],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/automations/hooks/use-automations-table-config.tsx`
around lines 119 - 122, The useMemo that builds the table config references the
translation function tAutomations but its dependency array only lists [tTables,
tCommon]; update the dependency array for the useMemo that defines the table
config (the hook using useMemo near tTables/tCommon) to include tAutomations as
well so the memo invalidates when tAutomations changes, keeping dependencies
consistent with other translation hooks.
| args: { | ||
| agentFileName: v.string(), | ||
| agentSlug: v.string(), | ||
| threadId: v.string(), | ||
| organizationId: v.string(), | ||
| orgSlug: v.string(), |
There was a problem hiding this comment.
Don't trust orgSlug from the client for filesystem lookup.
This action only authorizes the org later via startChat using args.organizationId, but it reads the JSON config up front from args.orgSlug. A caller can mix an organization they belong to with another tenant's slug and execute that tenant's agent config under their own org. Please derive/validate the slug from the authorized organization before any file or binding read.
Also applies to: 61-62, 85-90, 111-123
| const authUser = await authComponent.getAuthUser(ctx); | ||
| if (!authUser) throw new Error('Unauthenticated'); | ||
|
|
||
| await getOrganizationMember(ctx, args.organizationId, { | ||
| userId: String(authUser._id), | ||
| email: authUser.email, | ||
| name: authUser.name, | ||
| }); |
There was a problem hiding this comment.
Handle getAuthUser exceptions as unauthenticated in this query.
authComponent.getAuthUser(ctx) can throw for invalid or expired sessions. Right now that exception escapes before the existing unauthenticated branch runs, so the client gets a query error instead of the controlled auth failure path. Wrap the lookup in try/catch, then keep the current throw new Error('Unauthenticated') behavior for the null case.
Based on learnings: "In Convex query handlers, if authComponent.getAuthUser(ctx) fails, swallow the error (empty catch) and treat as unauthenticated without logging warnings/errors."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/convex/agents/webhooks/queries.ts` around lines 13 - 20,
Wrap the call to authComponent.getAuthUser(ctx) in a try/catch so any exceptions
from expired/invalid sessions are swallowed and treated as unauthenticated;
after the try/catch, keep the existing null check and throw new
Error('Unauthenticated') if authUser is falsy, and then proceed to call
getOrganizationMember(ctx, args.organizationId, { userId: String(authUser._id),
email: authUser.email, name: authUser.name }) when authUser exists.
| export const agentWebhooksTable = defineTable({ | ||
| organizationId: v.string(), | ||
| agentFileName: v.string(), | ||
| agentSlug: v.string(), |
There was a problem hiding this comment.
Add a data backfill for the persisted key rename before rollout.
Line [6] and Line [14] switch storage/indexing to agentSlug. Existing agentWebhooks records written with agentFileName will be missed by new lookups unless migrated.
Rollout safely with: (1) backfill agentSlug from legacy agentFileName for existing rows, (2) deploy readers/writers, (3) remove legacy compatibility once migration is complete.
Also applies to: 14-14
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/convex/agents/webhooks/schema.ts` at line 6, Add a one-time
backfill that copies legacy agentFileName into the new agentSlug field for
existing agentWebhooks records before rolling out the schema change: write a
migration or script that scans agentWebhooks where agentSlug is null/empty and
sets agentSlug = agentFileName, verify results, then deploy readers/writers that
use agentSlug, and only remove legacy agentFileName compatibility after the
migration is confirmed; reference the agentWebhooks collection and the agentSlug
and agentFileName fields when implementing and testing the backfill.
| // Resolve agentSlug from thread metadata to load full agent config | ||
| const threadMeta = await ctx.db | ||
| .query('threadMetadata') | ||
| .withIndex('by_threadId', (q) => | ||
| q.eq('threadId', String(approval.threadId)), | ||
| ) | ||
| .first(); | ||
| const agentSlug = threadMeta?.agentSlug; | ||
|
|
||
| if (agentSlug) { | ||
| await ctx.scheduler.runAfter( | ||
| 0, | ||
| internal.agent_tools.workflows.trigger_completion_action | ||
| .triggerCompletionWithAgent, | ||
| { | ||
| threadId: approval.threadId, | ||
| organizationId: exec.organizationId, | ||
| agentSlug, | ||
| messageContent, | ||
| }, | ||
| ); | ||
| } else { | ||
| // Fallback for legacy threads without agentSlug: save the system message | ||
| // directly so the user still sees the workflow result, but skip agent generation | ||
| await ctx.scheduler.runAfter( | ||
| 0, | ||
| internal.agent_tools.workflows.internal_mutations.saveSystemMessage, | ||
| { | ||
| threadId: approval.threadId, | ||
| content: messageContent, | ||
| }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Keep a user-visible completion fallback when agent resolution can’t run.
messageContent is the internal agent prompt ([WORKFLOW_COMPLETED]...Instructions:), not a user-facing reply. The else branch saves that prompt verbatim as a role: 'system' message, and the agentSlug branch now depends on triggerCompletionWithAgent, whose new tests show it can throw when the agent file is missing or oversized. In both cases the workflow can finish without any assistant completion message reaching the thread.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/convex/workflow_engine/helpers/engine/on_workflow_complete.ts`
around lines 173 - 205, The code currently saves only the internal prompt
(messageContent) as a system message when agentSlug is missing, and relies on
internal.agent_tools.workflows.trigger_completion_action.triggerCompletionWithAgent
to produce an assistant message when agentSlug exists — but that call can throw
or fail to produce a user-facing reply. Wrap the agentSlug branch call to
triggerCompletionWithAgent in a try/catch (or otherwise detect a missing/empty
assistant result), and in the catch/fallback schedule the existing
internal_mutations.saveSystemMessage or — better — the equivalent
saveAssistantMessage (role 'assistant') with a sanitized, user-facing summary
string instead of the raw internal prompt (messageContent); reference
threadMeta/agentSlug, ctx.scheduler.runAfter, triggerCompletionWithAgent, and
internal_mutations.saveSystemMessage to locate and implement the try/catch
fallback so the thread always receives a user-visible assistant completion even
when agent resolution fails.
| "breadcrumb": { | ||
| "back": "Back to automations" | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for usages of 'breadcrumb.back' in the automations feature
rg -n "breadcrumb\.back" --type=ts --type=tsx services/platform/app
rg -n "'breadcrumb.back'" --type=ts services/platform/app
rg -n '"breadcrumb.back"' --type=ts services/platform/appRepository: tale-project/tale
Length of output: 88
🏁 Script executed:
#!/bin/bash
# Search for usages of 'breadcrumb.back' with corrected syntax
rg -n "breadcrumb\.back" services/platform/app/Repository: tale-project/tale
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Also search in the messages file to understand the structure better
cat services/platform/messages/en.json | head -1550 | tail -50Repository: tale-project/tale
Length of output: 1747
🏁 Script executed:
#!/bin/bash
# Search for any reference to 'breadcrumb' in automation-related files
fd "automation" --type f services/platform/app --exec rg -l "breadcrumb" {} \;Repository: tale-project/tale
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Search entire repo for any reference to breadcrumb.back
rg "breadcrumb" services/platform/ --type json
rg "breadcrumb\.back" services/platform/
rg "breadcrumb\.back" .Repository: tale-project/tale
Length of output: 170
🏁 Script executed:
#!/bin/bash
# Let's check the git context to see what commit added this key
git log --oneline -p services/platform/messages/en.json | grep -A 5 -B 5 "breadcrumb.back" | head -30Repository: tale-project/tale
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Search for where automations translations are imported/used
rg "automations\." services/platform/app --type ts | grep -i "breadcrumb\|navigation\|back" | head -20Repository: tale-project/tale
Length of output: 43
Remove the unused breadcrumb.back translation key.
The key automations.breadcrumb.back is defined in services/platform/messages/en.json but is not referenced anywhere in the codebase. This translation key should be removed to avoid dead code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/messages/en.json` around lines 1528 - 1530, The translation
key "automations.breadcrumb.back" (breadcrumb.back) is dead and should be
removed: delete the "breadcrumb": { "back": "Back to automations" } entry from
the en.json translations, ensure the JSON stays valid (commas adjusted), run a
repo-wide search for "automations.breadcrumb.back" to confirm there are no
usages, remove the same key from other locale files if present, and regenerate
or update any i18n extraction/translation manifests so the removed key is not
expected by tooling.
Summary
agentFileNametoagentSlugacross the entire agents module (schema, mutations, queries, webhooks, frontend hooks/components) for clearer semanticsrequest_human_input,request_user_location,document_retrieve/find/write),workflowsfield, andincludeTeamKnowledgesupportcontract_comparisonworkflow — multi-step workflow with loop-based document diffing, LLM analysis, clause frequency extraction, and DOCX report generationtrigger_completion_action— new internal action bridging filesystem agent config reads with workflow completion response mutationsgetOrganizationMember()authorization checks to 6 agent functions (3 mutations + 3 queries) that previously only checked authenticationuseAgentBinding()instead of hardcoded empty arrayagentSlugtorunAgentGenerationscheduler payload for downstream availabilityagentSlugin webhook creation viavalidateAgentName()aria-labelto search inputfailurebranches,error_outputstep, andminItems: 2input validationTest plan
Summary by CodeRabbit
Release Notes
New Features
Improvements