refactor(agent_tools): consolidate tools with unified read operations#1
Conversation
… and *_tool.ts naming - Consolidate customer tools into single customer_read_tool with operations: get_by_id, get_by_email, list - Consolidate product tools into single product_read_tool with operations: get_by_id, list - Consolidate workflow tools into workflow_read_tool with operations: list_available_actions, get_structure, search_examples - Consolidate crawler tools into web_read_tool with operations: fetch_url, web_search - Consolidate RAG tools: rag_search_tool and rag_write_tool - Rename all tool files to *_tool.ts suffix convention - Extract helper logic into helpers/ subdirectories with separate files - Remove deprecated standalone tools (list_customers, update_customer, list_products, etc.) - Update tool_registry.ts with new consolidated tool names - Update workflow prompts to use new tool names (product_read, customer_read, etc.) - Update create_workflow_agent.ts and generate_agent_response.ts for new tool names
📝 WalkthroughWalkthroughThis pull request comprehensively refactors the Convex agent tools infrastructure by consolidating multiple single-purpose tools into unified multi-operation tools and extracting common logic into reusable helper modules. The changes span crawler/web tools (replacing Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Comment |
There was a problem hiding this comment.
Actionable comments posted: 23
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
services/platform/convex/lib/create_workflow_agent.ts (1)
29-39: Default tools updated correctly; instructions still reference old tool namesThe change to use a single
workflow_readtool indefaultWorkflowTools(with a clarifying comment) cleanly reflects the new consolidated read surface; the wiring here looks good.However, further down in the same file, the long instructions string still:
- Refers to
get_workflow_structure,list_available_actions, andsearch_workflow_examplesas standalone tools, even though these are nowworkflow_readoperations.- Shows an example LLM config with
tools: ['customer_search', 'product_get', 'list_products', 'rag_search'], which doesn’t match the newcustomer_read/product_readtooling.To avoid the agent attempting to call non‑existent tools, those instructional references and examples should be updated to:
- Use
workflow_readwith appropriateoperationvalues.- Replace
customer_search/product_get/list_productswithcustomer_read/product_readin examples and narrative.This is important for keeping the agent’s mental model of available tools in sync with the actual registry.
services/platform/convex/predefined_workflows/loopi_product_recommendation.ts (2)
228-241: Inconsistent tool references in prompt text.Several lines still reference the old tool names (
list_products,product_get) instead of the new unifiedproduct_readtool with operations:
- Line 231: "Use product_get to get full details" → should be
product_readwithoperation = 'get_by_id'- Line 236: "call list_products to browse" → should be
product_readwithoperation = 'list'This inconsistency between the prompt instructions and the actual available tools (
product_read) will confuse the LLM.1. PRIORITY 1: Use relationship metadata from customer's current products - Look for relationships with confidence > 0.5 (lowered threshold for more options) - - Use product_get to get full details of related products + - Use product_read with operation = 'get_by_id' to get full details of related products - Prefer "Complementary", "Upgrade", and "Bundle" relationship types - CRITICAL: Verify stock > 0 before including any product 2. PRIORITY 2: MANDATORY - If relationship products are insufficient (< 5 recommendations) - - You MUST immediately call list_products to browse all available products + - You MUST immediately call product_read with operation = 'list' to browse all available products
313-319: Stale reference tolist_productsin confidence scoring rules.Line 319 mentions "Browsed products (from list_products)" but should reference
product_readwithoperation = 'list'for consistency.CONFIDENCE SCORING RULES: ... - - Browsed products (from list_products): Start at 0.5 and adjust based on relevance + - Browsed products (from product_read with operation = 'list'): Start at 0.5 and adjust based on relevanceservices/platform/convex/agent_tools/tool_registry.ts (1)
29-62: Align the order of TOOL_NAMES and TOOL_REGISTRY for better maintainability.While functionally correct, the ordering mismatch between
TOOL_NAMESandTOOL_REGISTRYmay cause confusion during maintenance. For example,'generate_file'appears at position 6 inTOOL_NAMESbutgenerateFileToolis at position 12 inTOOL_REGISTRY.Apply this diff to align the ordering:
export const TOOL_NAMES = [ 'customer_read', 'product_read', 'rag_search', 'rag_write', 'web_read', - 'generate_file', 'workflow_read', 'update_workflow_step', 'generate_workflow_from_description', 'save_workflow_definition', 'validate_workflow_definition', 'generate_excel', + 'generate_file', 'context_search', ] as const;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro (Legacy)
⛔ Files ignored due to path filters (1)
services/platform/convex/_generated/api.d.tsis excluded by!**/_generated/**
📒 Files selected for processing (53)
services/platform/convex/agent_tools/convex_tools/crawler/fetch_url.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_page_content.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_searxng_results.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/crawler/helpers/get_crawler_service_url.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/crawler/helpers/get_search_service_url.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/crawler/helpers/search_web.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/crawler/web_read_tool.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/crawler/web_search.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/customers/customer_read_tool.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/customers/customer_search.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_by_email.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_by_id.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_list.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/customers/list_customers.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/customers/update_customer.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/files/analyze_content.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_by_id.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_list.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/products/list_products.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/products/product_get.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/products/product_read_tool.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/products/update_product.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/rag/README.md(0 hunks)services/platform/convex/agent_tools/convex_tools/rag/rag_search_tool.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/rag/rag_write_tool.ts(5 hunks)services/platform/convex/agent_tools/convex_tools/workflow_processing_records/find_unprocessed_entities.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/workflow_processing_records/find_unprocessed_open_conversation.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/workflow_processing_records/mark_entity_processed.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/workflows/generate_workflow_from_description_tool.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/get_workflow_structure.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_active_version_steps.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_all_workflows.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_available_actions.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_version_history.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_workflow_examples.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_workflow_structure.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/list_available_actions.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/workflows/search_workflow_examples.ts(0 hunks)services/platform/convex/agent_tools/convex_tools/workflows/update_workflow_step_tool.ts(1 hunks)services/platform/convex/agent_tools/convex_tools/workflows/workflow_read_tool.ts(1 hunks)services/platform/convex/agent_tools/tool_registry.ts(3 hunks)services/platform/convex/lib/create_agent_config.ts(1 hunks)services/platform/convex/lib/create_chat_agent.ts(0 hunks)services/platform/convex/lib/create_workflow_agent.ts(1 hunks)services/platform/convex/model/chat_agent/generate_agent_response.ts(1 hunks)services/platform/convex/predefined_workflows/general_product_recommendation.ts(1 hunks)services/platform/convex/predefined_workflows/loopi_product_recommendation.ts(4 hunks)services/platform/convex/wf_definitions.ts(1 hunks)services/platform/convex/workflow_assistant_agent.ts(1 hunks)
💤 Files with no reviewable changes (17)
- services/platform/convex/agent_tools/convex_tools/products/update_product.ts
- services/platform/convex/agent_tools/convex_tools/workflows/list_available_actions.ts
- services/platform/convex/lib/create_chat_agent.ts
- services/platform/convex/agent_tools/convex_tools/crawler/fetch_url.ts
- services/platform/convex/agent_tools/convex_tools/workflow_processing_records/find_unprocessed_entities.ts
- services/platform/convex/agent_tools/convex_tools/workflow_processing_records/find_unprocessed_open_conversation.ts
- services/platform/convex/agent_tools/convex_tools/customers/list_customers.ts
- services/platform/convex/agent_tools/convex_tools/files/analyze_content.ts
- services/platform/convex/agent_tools/convex_tools/workflows/search_workflow_examples.ts
- services/platform/convex/agent_tools/convex_tools/rag/README.md
- services/platform/convex/agent_tools/convex_tools/workflow_processing_records/mark_entity_processed.ts
- services/platform/convex/agent_tools/convex_tools/products/list_products.ts
- services/platform/convex/agent_tools/convex_tools/customers/update_customer.ts
- services/platform/convex/agent_tools/convex_tools/workflows/get_workflow_structure.ts
- services/platform/convex/agent_tools/convex_tools/products/product_get.ts
- services/platform/convex/agent_tools/convex_tools/customers/customer_search.ts
- services/platform/convex/agent_tools/convex_tools/crawler/web_search.ts
🧰 Additional context used
🧠 Learnings (19)
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.ts : Use Id helper type from ./_generated/dataModel to type document ids (e.g., Id<'users'>)
Applied to files:
services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_by_id.tsservices/platform/convex/agent_tools/convex_tools/products/helpers/types.tsservices/platform/convex/agent_tools/convex_tools/customers/helpers/types.tsservices/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_by_id.ts
📚 Learning: 2025-07-20T08:40:24.693Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/ai.mdc:0-0
Timestamp: 2025-07-20T08:40:24.693Z
Learning: Applies to **/actions/*.ts : For text generation, use the `generateObject` or similar functions from the AI SDK
Applied to files:
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_available_actions.tsservices/platform/convex/agent_tools/convex_tools/workflows/helpers/types.tsservices/platform/convex/model/chat_agent/generate_agent_response.ts
📚 Learning: 2025-08-21T14:59:56.034Z
Learnt from: CR
Repo: talecorp/lanserhof PR: 0
File: .cursor/rules/ai.mdc:0-0
Timestamp: 2025-08-21T14:59:56.034Z
Learning: Applies to {**/actions/*.ts,**/app/api/**/*.ts,**/lib/**/*.ts} : Avoid custom AI wrappers that do not add meaningful functionality beyond the Vercel AI SDK
Applied to files:
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_available_actions.tsservices/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Always use the new Convex function syntax (query/mutation/action with args/returns/handler)
Applied to files:
services/platform/convex/agent_tools/convex_tools/rag/rag_search_tool.tsservices/platform/convex/wf_definitions.ts
📚 Learning: 2025-11-30T12:29:38.900Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-11-30T12:29:38.900Z
Learning: Applies to convex/*.ts : Split existing Convex modules that export multiple functions into separate files with one function per file, except for explicitly designated thin wrapper modules
Applied to files:
services/platform/convex/agent_tools/convex_tools/rag/rag_search_tool.ts
📚 Learning: 2025-10-11T11:46:02.452Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursorrules:0-0
Timestamp: 2025-10-11T11:46:02.452Z
Learning: Applies to convex/**/*.ts : In convex/ directory, exactly one exported function per file; file name and exported function name must be identical
Applied to files:
services/platform/convex/agent_tools/convex_tools/rag/rag_search_tool.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Use withSearchIndex and q.search for full-text search queries
Applied to files:
services/platform/convex/agent_tools/convex_tools/crawler/helpers/search_web.tsservices/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_searxng_results.ts
📚 Learning: 2025-08-21T15:03:10.828Z
Learnt from: CR
Repo: talecorp/lanserhof PR: 0
File: .cursor/rules/supabase.mdc:0-0
Timestamp: 2025-08-21T15:03:10.828Z
Learning: Applies to supabase/types.ts : Do not edit `types.ts`; it is generated by the script
Applied to files:
services/platform/convex/agent_tools/convex_tools/products/helpers/types.tsservices/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts
📚 Learning: 2025-11-30T12:29:38.900Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/workspace_rules.mdc:0-0
Timestamp: 2025-11-30T12:29:38.900Z
Learning: Applies to services/**/convex/*.ts : Thin wrapper API modules in services may export multiple Convex functions as thin wrappers that delegate to model helpers, must use snake_case file names and camelCase export names, and must not contain business logic
Applied to files:
services/platform/convex/workflow_assistant_agent.tsservices/platform/convex/agent_tools/convex_tools/crawler/helpers/types.tsservices/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts
📚 Learning: 2025-10-11T11:46:02.452Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursorrules:0-0
Timestamp: 2025-10-11T11:46:02.452Z
Learning: Applies to convex/**/*.ts : Convex file names must be snake_case and exported function names must be camelCase
Applied to files:
services/platform/convex/workflow_assistant_agent.ts
📚 Learning: 2025-07-20T08:40:24.693Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/ai.mdc:0-0
Timestamp: 2025-07-20T08:40:24.693Z
Learning: Applies to **/app/api/**/*.ts : For text generation, use the `generateObject` or similar functions from the AI SDK
Applied to files:
services/platform/convex/predefined_workflows/general_product_recommendation.tsservices/platform/convex/model/chat_agent/generate_agent_response.ts
📚 Learning: 2025-11-30T03:53:00.305Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.305Z
Learning: Applies to convex/**/*.ts : Use the `internal` object from `convex/_generated/api.ts` to call internal functions registered with `internalQuery`, `internalMutation`, or `internalAction`
Applied to files:
services/platform/convex/wf_definitions.ts
📚 Learning: 2025-11-30T03:53:00.305Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.305Z
Learning: Applies to convex/**/*.ts : Always use the new Convex function syntax with `query`, `mutation`, `internalQuery`, `internalMutation`, `action`, or `internalAction` with explicit `args`, `returns`, and `handler` properties
Applied to files:
services/platform/convex/wf_definitions.ts
📚 Learning: 2025-10-03T11:34:20.628Z
Learnt from: CR
Repo: talecorp/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-10-03T11:34:20.628Z
Learning: Applies to convex/**/*.{ts,js} : Register internal functions with internalQuery, internalMutation, and internalAction (imported from ./_generated/server)
Applied to files:
services/platform/convex/wf_definitions.ts
📚 Learning: 2025-11-30T03:53:00.305Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.305Z
Learning: Applies to convex/**/*.ts : Use the `api` object from `convex/_generated/api.ts` to call public functions registered with `query`, `mutation`, or `action`
Applied to files:
services/platform/convex/wf_definitions.ts
📚 Learning: 2025-11-30T03:53:00.305Z
Learnt from: CR
Repo: tale-project/poc2 PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-30T03:53:00.305Z
Learning: Applies to convex/**/*.ts : Define pagination using `paginationOptsValidator` with `numItems` and `cursor` properties, and use `.paginate()` on queries which returns objects with `page`, `isDone`, and `continueCursor` properties
Applied to files:
services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_list.ts
📚 Learning: 2025-07-19T15:30:00.886Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/core-rules.mdc:0-0
Timestamp: 2025-07-19T15:30:00.886Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Add TypeScript types where missing and create reusable patterns for similar issues
Applied to files:
services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts
📚 Learning: 2025-08-21T14:59:56.034Z
Learnt from: CR
Repo: talecorp/lanserhof PR: 0
File: .cursor/rules/ai.mdc:0-0
Timestamp: 2025-08-21T14:59:56.034Z
Learning: Applies to {**/actions/*.ts,**/app/api/**/*.ts,**/lib/**/*.ts} : For text generation, use `generateObject` (or equivalent SDK functions) with an SDK-provided model (e.g., `openrouter(...)`)
Applied to files:
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts
📚 Learning: 2025-07-20T08:40:24.693Z
Learnt from: CR
Repo: talecorp/poc PR: 0
File: .cursor/rules/ai.mdc:0-0
Timestamp: 2025-07-20T08:40:24.693Z
Learning: Applies to **/lib/**/*.ts : For text generation, use the `generateObject` or similar functions from the AI SDK
Applied to files:
services/platform/convex/model/chat_agent/generate_agent_response.ts
🧬 Code graph analysis (21)
services/platform/convex/agent_tools/convex_tools/crawler/web_read_tool.ts (3)
services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts (2)
WebReadFetchUrlResult(25-33)WebReadSearchResult(48-58)services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_page_content.ts (1)
fetchPageContent(16-103)services/platform/convex/agent_tools/convex_tools/crawler/helpers/search_web.ts (1)
searchWeb(27-85)
services/platform/convex/agent_tools/convex_tools/products/product_read_tool.ts (3)
services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts (2)
ProductReadGetByIdResult(2-5)ProductReadListResult(7-15)services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_by_id.ts (1)
readProductById(6-55)services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_list.ts (1)
readProductList(5-45)
services/platform/convex/agent_tools/convex_tools/workflows/workflow_read_tool.ts (7)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (6)
WorkflowReadListActionsResult(29-35)WorkflowReadGetStructureResult(37-41)WorkflowReadSearchExamplesResult(52-60)WorkflowReadListAllResult(72-78)WorkflowReadGetActiveVersionStepsResult(80-86)WorkflowReadListVersionHistoryResult(102-108)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_available_actions.ts (1)
readAvailableActions(4-53)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_workflow_structure.ts (1)
readWorkflowStructure(6-36)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_workflow_examples.ts (1)
readWorkflowExamples(6-92)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_active_version_steps.ts (1)
readActiveVersionSteps(14-75)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_version_history.ts (1)
readVersionHistory(18-103)services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_all_workflows.ts (1)
readAllWorkflows(11-85)
services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_page_content.ts (2)
services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts (2)
WebReadFetchUrlResult(25-33)FetchUrlsApiResponse(18-23)services/platform/convex/agent_tools/convex_tools/crawler/helpers/get_crawler_service_url.ts (1)
getCrawlerServiceUrl(7-15)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_workflow_structure.ts (1)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (1)
WorkflowReadGetStructureResult(37-41)
services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_by_id.ts (1)
services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts (2)
ProductReadGetByIdResult(2-5)defaultGetFields(18-28)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_available_actions.ts (1)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (1)
WorkflowReadListActionsResult(29-35)
services/platform/convex/lib/create_workflow_agent.ts (1)
services/platform/convex/agent_tools/tool_registry.ts (1)
ToolName(67-67)
services/platform/convex/agent_tools/convex_tools/crawler/helpers/search_web.ts (3)
services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts (1)
WebReadSearchResult(48-58)services/platform/convex/agent_tools/convex_tools/crawler/helpers/get_search_service_url.ts (1)
getSearchServiceUrl(7-15)services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_searxng_results.ts (1)
fetchSearXNGResults(13-80)
services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts (1)
services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts (2)
defaultGetFields(23-35)defaultListFields(37-46)
services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts (1)
services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts (2)
defaultGetFields(18-28)defaultListFields(30-38)
services/platform/convex/agent_tools/convex_tools/customers/customer_read_tool.ts (4)
services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts (3)
CustomerReadGetByIdResult(2-5)CustomerReadGetByEmailResult(7-10)CustomerReadListResult(12-20)services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_by_id.ts (1)
readCustomerById(6-58)services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_by_email.ts (1)
readCustomerByEmail(5-65)services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_list.ts (1)
readCustomerList(5-45)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_active_version_steps.ts (1)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (1)
WorkflowReadGetActiveVersionStepsResult(80-86)
services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_by_id.ts (1)
services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts (2)
CustomerReadGetByIdResult(2-5)defaultGetFields(23-35)
services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_list.ts (1)
services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts (2)
ProductReadListResult(7-15)defaultListFields(30-38)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_version_history.ts (1)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (2)
WorkflowReadListVersionHistoryResult(102-108)WorkflowVersionWithSteps(88-100)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_workflow_examples.ts (1)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (1)
WorkflowReadSearchExamplesResult(52-60)
services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_list.ts (1)
services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts (2)
CustomerReadListResult(12-20)defaultListFields(37-46)
services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_searxng_results.ts (1)
services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts (3)
SearchOptions(81-91)SearchResult(39-46)SearXNGResponse(74-79)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_all_workflows.ts (1)
services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (2)
WorkflowReadListAllResult(72-78)WorkflowSummary(62-70)
services/platform/convex/agent_tools/tool_registry.ts (5)
services/platform/convex/agent_tools/convex_tools/customers/customer_read_tool.ts (1)
customerReadTool(69-157)services/platform/convex/agent_tools/convex_tools/products/product_read_tool.ts (1)
productReadTool(59-116)services/platform/convex/agent_tools/convex_tools/rag/rag_search_tool.ts (1)
ragSearchTool(38-116)services/platform/convex/agent_tools/convex_tools/crawler/web_read_tool.ts (1)
webReadTool(85-139)services/platform/convex/agent_tools/convex_tools/workflows/workflow_read_tool.ts (1)
workflowReadTool(108-196)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build services/platform
🔇 Additional comments (35)
services/platform/convex/lib/create_agent_config.ts (1)
80-89: ConditionalproviderOptionsinclusion looks good; confirm external expectationsUsing
hasMaxTokensto only includeproviderOptionswhenopts.maxTokensis a number cleans up the config and avoids passingmaxOutputTokens: undefined. The only behavior change is that whenmaxTokensis omitted,providerOptionsis now absent instead of present-with-undefined. Please confirm thatAgent(from@convex-dev/agent) and the underlying OpenAI provider do not rely onproviderOptionsalways existing, but treat its absence the same as “no max tokens configured”.services/platform/convex/agent_tools/convex_tools/rag/rag_search_tool.ts (1)
117-117: LGTM!Trailing newline addition follows POSIX convention.
services/platform/convex/model/chat_agent/generate_agent_response.ts (1)
183-201: Improved error logging structure looks good, but verifyresponseBodydoesn't leak sensitive data.The sanitized error logging approach is a solid improvement for diagnosing API issues. However,
responseBody(line 199) could potentially contain sensitive data returned by the AI provider (e.g., echoed prompts, user content, or internal error details).Consider truncating or sanitizing
responseBodybefore logging:- responseBody: err?.responseBody ?? err?.data ?? err?.cause?.responseBody, + // Truncate response body to avoid logging large/sensitive payloads + responseBody: (() => { + const body = err?.responseBody ?? err?.data ?? err?.cause?.responseBody; + if (typeof body === 'string' && body.length > 500) { + return body.slice(0, 500) + '...[truncated]'; + } + return body; + })(),services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_by_id.ts (1)
10-20:organizationIdis extracted for logging, not for query scoping.The function extracts
organizationIdfrom context (line 10) and uses it in console logs (lines 12 and 25) for debugging and tracing purposes. ThegetProductByIdquery does not accept anorganizationIdparameter—it only takesproductId. If product scoping by organization is required, it must be enforced at the database or schema level withinProductsModel.getProductById, not in this helper function.Likely an incorrect or invalid review comment.
services/platform/convex/agent_tools/convex_tools/rag/rag_write_tool.ts (1)
30-32: Rename and logging alignment look correctTool name, description, and log labels are consistently updated to
rag_writewith no behavioral changes; handler logic, payload, and error handling remain sound.Also applies to: 73-77, 131-135, 144-147
services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts (1)
1-38: Product read result types and defaults are consistentThe shared result types and
default*Fieldsfollow the same pattern as the customer helpers and give a clear, centralized contract forproduct_readoperations. This looks good for the current refactor.services/platform/convex/predefined_workflows/general_product_recommendation.ts (1)
181-193: LLM tools updated correctly to useproduct_readSwitching the LLM step’s tools to
['product_read', 'rag_search']aligns this predefined workflow with the new consolidated product read tooling; no behavioral issues visible here.services/platform/convex/wf_definitions.ts (1)
110-122:getActiveVersioninternalQuery wiring is consistentThe new
getActiveVersioninternalQuery matches the existing patterns (args, return type, delegation to the model) and provides the expected wrapper for helpers likereadActiveVersionSteps.services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_list.ts (2)
5-17: LGTM on function signature and validation.The organizationId validation is appropriate, and the error message is clear. The dual cast pattern (
ctx as unknown as ActionCtxand separateorganizationIdextraction) matches the convention used in sibling helpers likereadProductList. Based on learnings, the pagination approach withpaginationOptscontainingnumItemsandcursorfollows Convex conventions.
23-34: Consider adding explicit type annotation for the query result.The inline type annotation for
resultworks, but since this pattern is repeated across helpers (read_product_list.tsuses the same structure), consider extracting a shared type for pagination query results to improve maintainability.// In a shared types file: export type PaginatedQueryResult<T> = { page: T[]; isDone: boolean; continueCursor: string | null; };⛔ Skipped due to learnings
Learnt from: CR Repo: tale-project/poc2 PR: 0 File: .cursor/rules/convex_rules.mdc:0-0 Timestamp: 2025-11-30T03:53:00.316Z Learning: Applies to convex/**/*.ts : Define pagination using `paginationOptsValidator` with `numItems` and `cursor` properties, and use `.paginate()` on queries which returns objects with `page`, `isDone`, and `continueCursor` propertiesLearnt from: CR Repo: talecorp/poc2 PR: 0 File: .cursor/rules/convex_rules.mdc:0-0 Timestamp: 2025-10-03T11:34:20.628Z Learning: Applies to convex/**/*.{ts,js} : Define paginated queries using paginationOptsValidator and .paginate(opts)Learnt from: CR Repo: tale-project/poc2 PR: 0 File: .cursor/rules/convex_rules.mdc:0-0 Timestamp: 2025-11-30T03:53:00.305Z Learning: Applies to convex/**/*.ts : When using `ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction` to call a function in the same file, specify a type annotation on the return value to work around TypeScript circularity limitationsLearnt from: larryro Repo: talecorp/tale PR: 1297 File: apps/app/app/[locale]/(protected)/merchant/activate-account/onboarding/stripe-embedded-account-onboarding.tsx:8-11 Timestamp: 2024-10-08T16:22:15.228Z Learning: In this project, prefer relying on TypeScript's type inference instead of adding explicit type annotations when they are not necessary.Learnt from: CR Repo: tale-project/poc2 PR: 0 File: .cursor/rules/convex_rules.mdc:0-0 Timestamp: 2025-11-30T03:53:00.305Z Learning: Applies to convex/**/*.ts : When using async iteration on queries, use `for await (const row of query)` syntax instead of `.collect()` or `.take(n)`Learnt from: CR Repo: talecorp/poc PR: 0 File: .cursorrules:0-0 Timestamp: 2025-07-19T15:29:09.401Z Learning: Applies to **/*.{ts,tsx} : Always use typed database queries with Supabase.services/platform/convex/agent_tools/convex_tools/crawler/helpers/get_search_service_url.ts (1)
7-15: LGTM!Clean and defensive implementation. The type check with truthiness guard (
typeof fromVariables === 'string' && fromVariables) correctly handles both undefined and empty string cases before falling back to environment variable or default.services/platform/convex/agent_tools/convex_tools/crawler/web_read_tool.ts (2)
21-83: LGTM on flat schema design.The comment explaining the rationale for avoiding
discriminatedUnion(OpenAI JSON Schema compatibility) is helpful. The schema correctly marks operation-specific fields as optional while providing clear descriptions about which fields are required for each operation.
110-137: Handler logic is correct with appropriate validation.The handler properly validates required fields before delegating to helpers:
fetch_urlrequiresurl(line 115)searchrequiresquery(line 124)All optional search parameters are correctly forwarded to
searchWeb. The explicitthrow new Errorfor missing required fields provides clear error messages for debugging.services/platform/convex/agent_tools/convex_tools/products/product_read_tool.ts (3)
21-57: LGTM on schema design.The flat object schema follows the same pattern as
web_read_tool.tsfor OpenAI compatibility. Thecursorfield correctly uses.nullable().optional()to support Convex pagination semantics where the cursor can benullfor the first page.
92-114: Handler logic is correct.Validation and delegation are properly implemented:
get_by_idvalidatesproductIdpresence before callingreadProductByIdlistpasses through pagination args toreadProductListThe return type union correctly reflects both possible result types.
116-116: Unnecessaryas constassertion.The
as constassertion on line 116 is redundant since the object is already typed asToolDefinition. This differs fromweb_read_tool.tswhich doesn't useas const. Consider removing for consistency.-} as const; +};⛔ Skipped due to learnings
Learnt from: CR Repo: tale-project/poc2 PR: 0 File: .cursor/rules/convex_rules.mdc:0-0 Timestamp: 2025-11-30T03:53:00.316Z Learning: Applies to convex/**/*.ts : Always use `as const` for string literals in discriminated union typesLearnt from: CR Repo: talecorp/poc2 PR: 0 File: .cursor/rules/convex_rules.mdc:0-0 Timestamp: 2025-10-03T11:34:20.628Z Learning: Applies to convex/**/*.ts : Use as const for string literals in discriminated unionsLearnt from: CR Repo: tale-project/poc2 PR: 0 File: .cursor/rules/convex_rules.mdc:0-0 Timestamp: 2025-11-30T03:53:00.316Z Learning: Applies to convex/**/*.ts : When using the `Array` type, always define arrays as `const array: Array<T> = [...]`Learnt from: larryro Repo: talecorp/tale PR: 1299 File: features/platform/rest-contracts/src/contracts/platform/organization/organization-contract.ts:19-21 Timestamp: 2024-10-08T16:22:15.228Z Learning: In TypeScript files like `organization-contract.ts`, avoid suggesting adding comments above the response schema when the code logic is very simple, as it may be unnecessary.Learnt from: larryro Repo: talecorp/tale PR: 1299 File: features/platform/rest-contracts/src/contracts/platform/organization/organization-contract.ts:19-21 Timestamp: 2024-10-07T12:01:14.503Z Learning: In TypeScript files like `organization-contract.ts`, avoid suggesting adding comments above the response schema when the code logic is very simple, as it may be unnecessary.Learnt from: CR Repo: talecorp/poc PR: 0 File: .cursor/rules/core-rules.mdc:0-0 Timestamp: 2025-07-19T15:30:00.886Z Learning: Applies to **/*.{ts,tsx,js,jsx} : Use enums or const objects for reused custom property valuesLearnt from: CR Repo: talecorp/lanserhof PR: 0 File: .cursor/rules/core-rules.mdc:0-0 Timestamp: 2025-08-21T15:01:09.405Z Learning: Applies to **/*.{ts,tsx,js} : Use enums or const objects for reused custom properties/valuesLearnt from: CR Repo: talecorp/poc PR: 0 File: .cursorrules:0-0 Timestamp: 2025-07-19T15:29:09.401Z Learning: Applies to **/*.{js,ts} : Use enums or const objects for custom properties that are reused.Learnt from: larryro Repo: talecorp/tale PR: 1297 File: apps/app/app/[locale]/(protected)/merchant/activate-account/onboarding/stripe-embedded-account-onboarding.tsx:8-11 Timestamp: 2024-10-08T16:22:15.228Z Learning: In this project, prefer relying on TypeScript's type inference instead of adding explicit type annotations when they are not necessary.Learnt from: larryro Repo: talecorp/tale PR: 1297 File: apps/app/app/[locale]/(protected)/merchant/merchant-layout-inner.tsx:18-27 Timestamp: 2024-10-08T16:22:15.228Z Learning: In TypeScript projects, avoid suggesting runtime type checks for variables that are already enforced by TypeScript's type system, unless there is a specific reason.services/platform/convex/workflow_assistant_agent.ts (1)
122-131: Tool consolidation is correct and complete.The updated tool list properly reflects the consolidated tools introduced by this PR:
workflow_readreplacesget_workflow_structure,list_available_actions, andsearch_workflow_examplesvia operationsget_structure,list_available_actions, andsearch_examplesrespectivelycustomer_readreplacescustomer_searchandlist_customersproduct_readreplacesproduct_getandlist_productsThe
workflow_readtool also provides additional operations (list_all,get_active_version_steps,list_version_history) beyond the minimum required coverage.services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts (1)
1-46: LGTM!The type definitions are well-structured with clear operation discriminators. The separation of
defaultGetFields(detailed) anddefaultListFields(summary) follows a sensible pattern consistent with the product helpers. UsingRecord<string, unknown>is appropriate given the dynamic field projection requirements.services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_page_content.ts (1)
83-95: LGTM - Well-structured response with useful metadata.The return structure includes all necessary information: operation type, success flag, content, and metadata with truncation details. This enables callers to handle truncated content appropriately.
services/platform/convex/agent_tools/convex_tools/workflows/workflow_read_tool.ts (1)
31-106: Well-designed flat schema for OpenAI compatibility.The decision to use a flat object schema instead of
discriminatedUnionis well-documented and addresses real compatibility issues with some LLM providers. The parameter descriptions clearly indicate which fields are required for each operation.services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_searxng_results.ts (1)
64-73: LGTM!The result mapping correctly handles optional fields with sensible defaults (
|| ''for strings) and preserves optional metadata likeengines,publishedDate, andcategory.services/platform/convex/predefined_workflows/loopi_product_recommendation.ts (1)
380-380: LGTM!The tools array correctly references only
product_read, aligning with the new unified tool structure.services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_available_actions.ts (2)
45-52: LGTM!The return structure correctly conforms to
WorkflowReadListActionsResultand provides comprehensive information including the catalog, total count, and usage guidance.
29-31: The logic is correct. Verification ofACTION_REFERENCEinaction_reference.tsconfirms that'operation'is included inrequiredParamsfor virtually all operations (e.g.,['operation', 'organizationId', 'name']), so the check will returntrueas intended. The flag accurately indicates whether an action's operations use the'operation'field.services/platform/convex/agent_tools/convex_tools/customers/helpers/read_customer_by_email.ts (1)
44-63: LGTM!The field projection logic correctly defaults to
defaultGetFields, ensures_idis always included, and filters out undefined keys before logging. This is consistent with the pattern inreadCustomerById.services/platform/convex/agent_tools/convex_tools/products/helpers/read_product_list.ts (1)
36-44: LGTM!The return structure correctly maps the query result to
ProductReadListResult, withhasMorederived from!isDoneand pagination cursor properly forwarded.services/platform/convex/agent_tools/convex_tools/customers/customer_read_tool.ts (1)
24-67: Well-designed flat schema for JSON Schema compatibility.The decision to use a flat object schema with an operation enum instead of
discriminatedUnionis pragmatic for OpenAI tool compatibility. The inline documentation clearly explains operation-specific field requirements.services/platform/convex/agent_tools/convex_tools/crawler/helpers/search_web.ts (1)
27-85: Implementation is correct and well-structured.The function properly handles context extraction, parameter defaults, and error logging. The delegation to
fetchSearXNGResultswith appropriate parameter mapping is clean.services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_active_version_steps.ts (1)
14-75: Clean implementation with consistent error handling pattern.The function properly validates context, handles missing workflow scenarios gracefully, and returns structured results instead of throwing errors. This aligns well with the other workflow helpers and provides clear feedback to callers.
services/platform/convex/agent_tools/convex_tools/workflows/helpers/read_version_history.ts (1)
18-53: Consistent structure with other workflow helpers.The validation of
organizationId, handling of empty results, and structured return format align well with the established patterns inread_active_version_steps.tsand other helpers.services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts (1)
1-91: Well-organized type definitions with clear separation of concerns.The types are cleanly structured into logical sections (fetch URL types, web search types, SearXNG API types). The distinction between internal types (
SearchResult,WebReadSearchResult) and external API types (SearXNGResult,SearXNGResponse) provides good abstraction boundaries.services/platform/convex/agent_tools/tool_registry.ts (2)
9-21: LGTM - Clean consolidation of tool imports.The imports correctly reference the new consolidated tool modules. The naming convention is consistent and paths follow the expected structure.
72-75: LGTM - Efficient tool lookup map.The derived
TOOL_REGISTRY_MAPprovides O(1) lookups by tool name, which is appropriate for runtime tool resolution.services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts (2)
3-27: LGTM - Well-structured foundational types.The building block types (
ActionOperationInfo,ActionInfo,CatalogEntry) are clearly defined with descriptive field names. These provide a solid foundation for the workflow operation result types.
1-1: LGTM - Clean type-only module.The file properly imports only the necessary
Doctype from the generated dataModel and maintains a clear separation of type definitions without any runtime code.
| try { | ||
| const controller = new AbortController(); | ||
| const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout | ||
|
|
||
| const response = await fetch(apiUrl, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify(payload), | ||
| signal: controller.signal, | ||
| }); | ||
|
|
||
| clearTimeout(timeoutId); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Timeout is not cleared on abort or error.
If the request times out (abort triggered) or throws before clearTimeout(timeoutId) on line 48, the timeout will continue running. While this is unlikely to cause issues in a serverless context, it's good practice to ensure cleanup.
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
- const response = await fetch(apiUrl, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(payload),
- signal: controller.signal,
- });
-
- clearTimeout(timeoutId);
+ let response: Response;
+ try {
+ response = await fetch(apiUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ signal: controller.signal,
+ });
+ } finally {
+ clearTimeout(timeoutId);
+ }📝 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.
| try { | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout | |
| const response = await fetch(apiUrl, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(payload), | |
| signal: controller.signal, | |
| }); | |
| clearTimeout(timeoutId); | |
| try { | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout | |
| let response: Response; | |
| try { | |
| response = await fetch(apiUrl, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(payload), | |
| signal: controller.signal, | |
| }); | |
| } finally { | |
| clearTimeout(timeoutId); | |
| } |
🤖 Prompt for AI Agents
In
services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_page_content.ts
around lines 35 to 48, the timeoutId is only cleared after a successful fetch so
if fetch throws or the AbortController triggers the timeout the timer may remain
active; move the clearTimeout(timeoutId) into a finally block (or add a
try/catch/finally) so clearTimeout is always called regardless of success or
error, ensuring proper cleanup of the timeout and avoiding leaked timers.
| totalResults: number; | ||
| suggestions: string[]; | ||
| }> { | ||
| const url = new URL(`${serviceUrl}/search`); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Potential URL construction error if serviceUrl is malformed.
If serviceUrl is empty, malformed, or doesn't include a protocol, new URL() will throw a TypeError. Consider adding validation or wrapping in try-catch for a more descriptive error message.
+ if (!serviceUrl) {
+ throw new Error('SearXNG service URL is required');
+ }
const url = new URL(`${serviceUrl}/search`);📝 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 url = new URL(`${serviceUrl}/search`); | |
| if (!serviceUrl) { | |
| throw new Error('SearXNG service URL is required'); | |
| } | |
| const url = new URL(`${serviceUrl}/search`); |
| const response = await fetch(url.toString(), { | ||
| headers: { Accept: 'application/json' }, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const errorText = await response.text(); | ||
| throw new Error(`SearXNG Search API error: ${response.status} ${errorText}`); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding a request timeout.
The fetch call has no timeout, which could cause the function to hang indefinitely if the SearXNG service is unresponsive. Consider using AbortController with a timeout for resilience.
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 30000);
+
const response = await fetch(url.toString(), {
headers: { Accept: 'application/json' },
+ signal: controller.signal,
});
+
+ clearTimeout(timeoutId);📝 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 response = await fetch(url.toString(), { | |
| headers: { Accept: 'application/json' }, | |
| }); | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| throw new Error(`SearXNG Search API error: ${response.status} ${errorText}`); | |
| } | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 30000); | |
| const response = await fetch(url.toString(), { | |
| headers: { Accept: 'application/json' }, | |
| signal: controller.signal, | |
| }); | |
| clearTimeout(timeoutId); | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| throw new Error(`SearXNG Search API error: ${response.status} ${errorText}`); | |
| } |
🤖 Prompt for AI Agents
In
services/platform/convex/agent_tools/convex_tools/crawler/helpers/fetch_searxng_results.ts
around lines 52 to 59, add a request timeout using AbortController: create an
AbortController before calling fetch, start a timer (e.g., 5–15s) that calls
controller.abort(), pass controller.signal to fetch, clear the timer after a
successful response, and catch the abort error to throw a clear timeout-specific
error before the existing response.ok handling.
| export function getCrawlerServiceUrl( | ||
| variables?: Record<string, unknown>, | ||
| ): string { | ||
| return ( | ||
| (variables?.crawlerServiceUrl as string) || | ||
| process.env.CRAWLER_URL || | ||
| 'http://localhost:8002' | ||
| ); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Unsafe type assertion may pass through non-string values.
If variables?.crawlerServiceUrl exists but is not a string (e.g., a number or object), the as string cast will not convert it, and the truthy check will pass, returning an invalid URL.
Consider adding a type guard:
export function getCrawlerServiceUrl(
variables?: Record<string, unknown>,
): string {
+ const fromVariables = variables?.crawlerServiceUrl;
return (
- (variables?.crawlerServiceUrl as string) ||
+ (typeof fromVariables === 'string' ? fromVariables : undefined) ||
process.env.CRAWLER_URL ||
'http://localhost:8002'
);
}📝 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.
| export function getCrawlerServiceUrl( | |
| variables?: Record<string, unknown>, | |
| ): string { | |
| return ( | |
| (variables?.crawlerServiceUrl as string) || | |
| process.env.CRAWLER_URL || | |
| 'http://localhost:8002' | |
| ); | |
| } | |
| export function getCrawlerServiceUrl( | |
| variables?: Record<string, unknown>, | |
| ): string { | |
| const fromVariables = variables?.crawlerServiceUrl; | |
| return ( | |
| (typeof fromVariables === 'string' ? fromVariables : undefined) || | |
| process.env.CRAWLER_URL || | |
| 'http://localhost:8002' | |
| ); | |
| } |
🤖 Prompt for AI Agents
In
services/platform/convex/agent_tools/convex_tools/crawler/helpers/get_crawler_service_url.ts
around lines 7 to 15, the function uses an unsafe `as string` cast which can
return non-string values (number/object) from `variables?.crawlerServiceUrl`;
replace the cast with a runtime type guard that checks typeof
variables?.crawlerServiceUrl === 'string' (and optionally validate non-empty),
return that value if true, otherwise fall back to process.env.CRAWLER_URL or the
default 'http://localhost:8002'; ensure no type assertions are used and update
the function signature/types if needed.
| language: args.language, | ||
| }); | ||
|
|
||
| const hasMore = pageData.items.length >= numResults; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
hasMore heuristic may yield false positives on exact page boundaries.
When exactly numResults items are returned, hasMore will be true even if this was the last page. This is a common pagination heuristic, but consider documenting this behavior or requesting numResults + 1 from the API to detect the boundary accurately.
🤖 Prompt for AI Agents
In
services/platform/convex/agent_tools/convex_tools/crawler/helpers/search_web.ts
around line 59, the hasMore calculation (const hasMore = pageData.items.length
>= numResults;) can produce false positives when a page returns exactly
numResults items; either request numResults + 1 from the API and set hasMore =
pageData.items.length > numResults (then trim the extra item before consuming
results), or explicitly document the current heuristic behavior in a comment and
keep the existing check if changing the request is not feasible.
| export async function readWorkflowStructure( | ||
| ctx: unknown, | ||
| args: { workflowId: string }, | ||
| ): Promise<WorkflowReadGetStructureResult> { | ||
| const actionCtx = ctx as ActionCtx; | ||
| const workflowId = args.workflowId as Id<'wfDefinitions'>; | ||
|
|
||
| const workflow = await actionCtx.runQuery( | ||
| internal.wf_definitions.getWorkflow, | ||
| { | ||
| wfDefinitionId: workflowId, | ||
| }, | ||
| ); | ||
|
|
||
| if (!workflow) { | ||
| return { operation: 'get_structure', workflow: null, steps: [] }; | ||
| } | ||
|
|
||
| const steps = await actionCtx.runQuery( | ||
| internal.wf_step_defs.listWorkflowSteps, | ||
| { | ||
| wfDefinitionId: workflowId, | ||
| }, | ||
| ); | ||
|
|
||
| return { | ||
| operation: 'get_structure', | ||
| workflow: workflow as Doc<'wfDefinitions'>, | ||
| steps: steps as Doc<'wfStepDefs'>[], | ||
| }; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding error handling and organizationId validation for consistency.
Other helpers in this PR (e.g., readActiveVersionSteps, readAllWorkflows) validate organizationId from context and wrap queries in try-catch blocks. This function lacks both patterns, which could lead to:
- Unhandled exceptions propagating to the caller
- Inconsistent behavior across workflow read operations
export async function readWorkflowStructure(
ctx: unknown,
args: { workflowId: string },
): Promise<WorkflowReadGetStructureResult> {
const actionCtx = ctx as ActionCtx;
const workflowId = args.workflowId as Id<'wfDefinitions'>;
+ try {
const workflow = await actionCtx.runQuery(
internal.wf_definitions.getWorkflow,
{
wfDefinitionId: workflowId,
},
);
if (!workflow) {
return { operation: 'get_structure', workflow: null, steps: [] };
}
const steps = await actionCtx.runQuery(
internal.wf_step_defs.listWorkflowSteps,
{
wfDefinitionId: workflowId,
},
);
return {
operation: 'get_structure',
workflow: workflow as Doc<'wfDefinitions'>,
steps: steps as Doc<'wfStepDefs'>[],
};
+ } catch (error) {
+ console.error('[tool:workflow_read] get_structure error', {
+ workflowId: args.workflowId,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ throw error;
+ }
}📝 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.
| export async function readWorkflowStructure( | |
| ctx: unknown, | |
| args: { workflowId: string }, | |
| ): Promise<WorkflowReadGetStructureResult> { | |
| const actionCtx = ctx as ActionCtx; | |
| const workflowId = args.workflowId as Id<'wfDefinitions'>; | |
| const workflow = await actionCtx.runQuery( | |
| internal.wf_definitions.getWorkflow, | |
| { | |
| wfDefinitionId: workflowId, | |
| }, | |
| ); | |
| if (!workflow) { | |
| return { operation: 'get_structure', workflow: null, steps: [] }; | |
| } | |
| const steps = await actionCtx.runQuery( | |
| internal.wf_step_defs.listWorkflowSteps, | |
| { | |
| wfDefinitionId: workflowId, | |
| }, | |
| ); | |
| return { | |
| operation: 'get_structure', | |
| workflow: workflow as Doc<'wfDefinitions'>, | |
| steps: steps as Doc<'wfStepDefs'>[], | |
| }; | |
| } | |
| export async function readWorkflowStructure( | |
| ctx: unknown, | |
| args: { workflowId: string }, | |
| ): Promise<WorkflowReadGetStructureResult> { | |
| const actionCtx = ctx as ActionCtx; | |
| const workflowId = args.workflowId as Id<'wfDefinitions'>; | |
| try { | |
| const workflow = await actionCtx.runQuery( | |
| internal.wf_definitions.getWorkflow, | |
| { | |
| wfDefinitionId: workflowId, | |
| }, | |
| ); | |
| if (!workflow) { | |
| return { operation: 'get_structure', workflow: null, steps: [] }; | |
| } | |
| const steps = await actionCtx.runQuery( | |
| internal.wf_step_defs.listWorkflowSteps, | |
| { | |
| wfDefinitionId: workflowId, | |
| }, | |
| ); | |
| return { | |
| operation: 'get_structure', | |
| workflow: workflow as Doc<'wfDefinitions'>, | |
| steps: steps as Doc<'wfStepDefs'>[], | |
| }; | |
| } catch (error) { | |
| console.error('[tool:workflow_read] get_structure error', { | |
| workflowId: args.workflowId, | |
| error: error instanceof Error ? error.message : String(error), | |
| }); | |
| throw error; | |
| } | |
| } |
| export type WorkflowReadListActionsResult = { | ||
| operation: 'list_available_actions'; | ||
| totalActions: number; | ||
| actions: ActionInfo[]; | ||
| catalogByActionType: Record<string, CatalogEntry>; | ||
| usage: string; | ||
| }; | ||
|
|
||
| export type WorkflowReadGetStructureResult = { | ||
| operation: 'get_structure'; | ||
| workflow: Doc<'wfDefinitions'> | null; | ||
| steps: Doc<'wfStepDefs'>[]; | ||
| }; | ||
|
|
||
| export type WorkflowExample = { | ||
| workflowId: string; | ||
| name: string; | ||
| description: string; | ||
| status: string; | ||
| stepCount: number; | ||
| steps: Doc<'wfStepDefs'>[]; | ||
| }; | ||
|
|
||
| export type WorkflowReadSearchExamplesResult = { | ||
| operation: 'search_examples'; | ||
| totalFound: number; | ||
| returned: number; | ||
| examples: WorkflowExample[]; | ||
| suggestion: string; | ||
| error?: string; | ||
| message?: string; | ||
| }; | ||
|
|
||
| export type WorkflowSummary = { | ||
| workflowId: string; | ||
| name: string; | ||
| description?: string; | ||
| status: string; | ||
| version: string; | ||
| versionNumber: number; | ||
| stepCount?: number; | ||
| }; | ||
|
|
||
| export type WorkflowReadListAllResult = { | ||
| operation: 'list_all'; | ||
| totalWorkflows: number; | ||
| workflows: WorkflowSummary[]; | ||
| message?: string; | ||
| error?: string; | ||
| }; | ||
|
|
||
| export type WorkflowReadGetActiveVersionStepsResult = { | ||
| operation: 'get_active_version_steps'; | ||
| workflow: Doc<'wfDefinitions'> | null; | ||
| steps: Doc<'wfStepDefs'>[]; | ||
| message?: string; | ||
| error?: string; | ||
| }; | ||
|
|
||
| export type WorkflowVersionWithSteps = { | ||
| workflowId: string; | ||
| name: string; | ||
| description?: string; | ||
| version: string; | ||
| versionNumber: number; | ||
| status: string; | ||
| publishedAt?: number; | ||
| publishedBy?: string; | ||
| changeLog?: string; | ||
| stepCount?: number; | ||
| steps?: Doc<'wfStepDefs'>[]; | ||
| }; | ||
|
|
||
| export type WorkflowReadListVersionHistoryResult = { | ||
| operation: 'list_version_history'; | ||
| totalVersions: number; | ||
| versions: WorkflowVersionWithSteps[]; | ||
| message?: string; | ||
| error?: string; | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding JSDoc comments to improve discoverability.
The result types are well-structured and use the discriminated union pattern effectively. However, adding JSDoc comments would help developers understand:
- When each operation type should be used
- The meaning of optional fields (especially
errorandmessage) - The relationship between these types and the workflow_read_tool operations
Example for one of the types:
/**
* Result from the 'list_available_actions' operation.
* Returns all available action types that can be used in workflow steps.
*
* @property operation - Discriminator for this result type
* @property totalActions - Total count of available action types
* @property actions - Detailed information about each action type
* @property catalogByActionType - Quick reference catalog organized by action type
* @property usage - Usage instructions for interpreting the results
*/
export type WorkflowReadListActionsResult = {
operation: 'list_available_actions';
totalActions: number;
actions: ActionInfo[];
catalogByActionType: Record<string, CatalogEntry>;
usage: string;
};🤖 Prompt for AI Agents
In services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts
around lines 29-108, the exported result types lack JSDoc which hurts
discoverability; add concise JSDoc blocks above each exported type describing
the operation discriminator, when the operation is returned, meanings of key
fields (call out optional fields like error and message and when they are set),
and the relationship to the corresponding workflow_read_tool operation (include
expected consumers/usage and any important invariants such as whether
steps/workflow can be null); keep comments short, follow the example in the
review (operation description, field descriptions, and any caveats), and ensure
consistent formatting across all types.
|
|
||
| LLM (AI agent): | ||
| { name: "Analyze Customer", systemPrompt: "You are a customer analyst...", userPrompt: "Analyze this customer data", temperature: 0.7, tools: ["product_search", "customer_get"] } | ||
| { name: "Analyze Customer", systemPrompt: "You are a customer analyst...", userPrompt: "Analyze this customer data", temperature: 0.7, tools: ["product_get", "customer_get"] } |
There was a problem hiding this comment.
Example uses legacy tool names instead of the new consolidated names.
The PR objective is to consolidate tools into product_read and customer_read, but this example still references product_get and customer_get. This inconsistency may confuse agents using this tool's description to understand available tools.
Update to use the new tool names:
-{ name: "Analyze Customer", systemPrompt: "You are a customer analyst...", userPrompt: "Analyze this customer data", temperature: 0.7, tools: ["product_get", "customer_get"] }
+{ name: "Analyze Customer", systemPrompt: "You are a customer analyst...", userPrompt: "Analyze this customer data", temperature: 0.7, tools: ["product_read", "customer_read"] }📝 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.
| { name: "Analyze Customer", systemPrompt: "You are a customer analyst...", userPrompt: "Analyze this customer data", temperature: 0.7, tools: ["product_get", "customer_get"] } | |
| { name: "Analyze Customer", systemPrompt: "You are a customer analyst...", userPrompt: "Analyze this customer data", temperature: 0.7, tools: ["product_read", "customer_read"] } |
🤖 Prompt for AI Agents
In
services/platform/convex/agent_tools/convex_tools/workflows/update_workflow_step_tool.ts
around line 102, the example uses legacy tool names "product_get" and
"customer_get"; update the example to use the consolidated tool names
"product_read" and "customer_read" instead (i.e., replace product_get ->
product_read and customer_get -> customer_read in the tools array), and verify
there are no other occurrences of the old names in this file.
| // operation === 'list_all' | ||
| return readAllWorkflows(ctx, { | ||
| status: args.status, | ||
| includeStepCount: args.includeStepCount, | ||
| }); | ||
| }, |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Add explicit check for 'list_all' operation to prevent silent fallthrough.
The current implementation uses list_all as a default fallthrough case. If a new operation is added to the enum but not handled in the if-chain, it would silently execute readAllWorkflows. Consider making this explicit:
- // operation === 'list_all'
- return readAllWorkflows(ctx, {
- status: args.status,
- includeStepCount: args.includeStepCount,
- });
+ if (args.operation === 'list_all') {
+ return readAllWorkflows(ctx, {
+ status: args.status,
+ includeStepCount: args.includeStepCount,
+ });
+ }
+
+ // Exhaustive check - TypeScript will error if a new operation is added but not handled
+ throw new Error(`Unknown operation: ${args.operation}`);📝 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.
| // operation === 'list_all' | |
| return readAllWorkflows(ctx, { | |
| status: args.status, | |
| includeStepCount: args.includeStepCount, | |
| }); | |
| }, | |
| if (args.operation === 'list_all') { | |
| return readAllWorkflows(ctx, { | |
| status: args.status, | |
| includeStepCount: args.includeStepCount, | |
| }); | |
| } | |
| // Exhaustive check - TypeScript will error if a new operation is added but not handled | |
| throw new Error(`Unknown operation: ${args.operation}`); | |
| }, |
🤖 Prompt for AI Agents
In
services/platform/convex/agent_tools/convex_tools/workflows/workflow_read_tool.ts
around lines 189 to 194, the code currently falls through to readAllWorkflows
for any unhandled operation, which can mask missing enum handling; replace the
implicit fallthrough with an explicit check for operation === 'list_all' and
return readAllWorkflows only in that branch, and add a final else or default
branch that throws or logs an error (e.g., throw new Error(`Unsupported workflow
operation: ${operation}`)) so unknown operations fail fast; alternatively
convert the if-chain to a switch over the operation enum and include a default
case that throws.
| EXAMPLE WORKFLOW: | ||
| - Found 1 product from relationships → Need 4 more | ||
| - Call list_products (no cursor, numItems: 100) | ||
| - Call product_read with operation = 'list' (no cursor, numItems: 100) | ||
| - Review products, find 3 with stock > 0 that fit | ||
| - Call product_get for each of those 3 products | ||
| - Add to recommendations (now have 4 total) | ||
| - Still need 1 more → Call list_products again with cursor from previous response | ||
| - Still need 1 more → Call product_read again with operation = 'list' and cursor from previous response | ||
| - Find 1 more product with stock > 0 | ||
| - Call product_get for it | ||
| - Add to recommendations (now have 5 total) → DONE |
There was a problem hiding this comment.
Stale tool reference in example workflow.
Line 292 still references product_get instead of product_read with operation = 'get_by_id'. The example should match the actual tool interface.
EXAMPLE WORKFLOW:
- Found 1 product from relationships → Need 4 more
- Call product_read with operation = 'list' (no cursor, numItems: 100)
- Review products, find 3 with stock > 0 that fit
-- Call product_get for each of those 3 products
+- Call product_read with operation = 'get_by_id' for each of those 3 products
- Add to recommendations (now have 4 total)
- Still need 1 more → Call product_read again with operation = 'list' and cursor from previous response
- Find 1 more product with stock > 0
-- Call product_get for it
+- Call product_read with operation = 'get_by_id' for it
- Add to recommendations (now have 5 total) → DONE🤖 Prompt for AI Agents
In services/platform/convex/predefined_workflows/loopi_product_recommendation.ts
around lines 288 to 297, the example workflow references the obsolete tool name
product_get; update that example to use product_read with operation =
'get_by_id' and the appropriate id parameter to match the actual tool interface
(replace the two occurrences of product_get in the example with product_read +
operation = 'get_by_id', and verify any parameter names and the cursor/list
usage in the surrounding example text are consistent with product_read's
signature).
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
…eout Round-2 v15 confirmed: /config unauthenticated, /openapi.json + /docs + /redoc unauthenticated, RAG container ran as root, default token baked into image ENV, strict-mode env name diverged across the wire, non-constant-time token compare, plus three SSRF-guard gaps. services/rag/app/auth.py - W7 #3: hmac.compare_digest replaces == on the bearer compare. Removes the dead-code EXEMPT_PATHS frozenset. services/rag/app/routers/health.py - W7 #1: split into public_router (`/`, `/health`) and protected_router (`/config`). main.py mounts the protected one under Depends(verify_internal_token). Old `router` re-export stays for backwards compat. services/rag/app/main.py - W7 #2: docs_url / redoc_url / openapi_url are None outside debug. - W7 #4: CORS allow_credentials flipped to False (bearer rides Authorization, never cookies). - W7 #1 wiring: mount health-public + health-protected separately. services/rag/app/config.py - W7 #8: require_custom_internal_token accepts BOTH RAG_REQUIRE_CUSTOM_INTERNAL_TOKEN and TALE_REQUIRE_CUSTOM_RAG_TOKEN via pydantic AliasChoices. services/rag/Dockerfile + services/convex/Dockerfile - W7 #5: RAG container runs as non-root (uid:gid 1001:1001 `app`). RAG ingests untrusted PDFs/DOCX through native parsers; biggest blast radius in the stack, now hardened. - W7 #6: removed RAG_INTERNAL_TOKEN=tale-rag-dev-only ENV bake from both runtime + scratch-squash stages and the matching bake in services/convex/Dockerfile. Operators MUST supply via env / compose / k8s secret. services/platform/convex/lib/helpers/rag_config.ts - W7 #9 F1: `redirect: 'manual'` on every ragFetch. - W7 #9 F2: added fc00::/7 (IPv6 ULA) to v6 blocklist (AWS IPv6 IMDSv2). - W7 #9 F3: strip trailing `.` before hostname blocklist lookup. - W7 #9 F4: re-validate URL per ragFetch invocation (DNS rebinding + env rotation mitigation). - W7 #9 F9: deleted path.startsWith('http') override branch (future- bypass foot-gun). services/platform/convex/agent_tools/rag/helpers/fetch_document_chunks.ts - W7 #10: pass timeoutMs=60_000 (default 10s was a regression). - Plus MAX_ITERATIONS=30 cap and "cursor did not advance" break to defend against an adversarial RAG response.
Round-2 W8 batch (six independent UI / docs items, all small). i18n keys (W8 #1) - Add governance.retentionPolicy.group.deletionBehavior.{title,description} to en/de/fr — previously missing in all three locales, retention editor fell back to the inline English string in every locale. - Add governance.retentionPolicy.chatFilterEvents.{title,description, placeholder,helper} to en/de/fr — previously the row title rendered the literal id "chatFilterEvents". useTranslation namespace (W8 #2) - use-data-classification-notice.ts now binds useTranslation('dataNotice') so the dataNotice.default fallback string is reachable; the prior default-namespace bind meant the DE/FR fallbacks were unreachable. UI delete copy (W8 #7) - en/de/fr.json deletePermanentMessage now matches the actual user-visible behavior (Trash + grace + retention) instead of saying "permanently deleted" while the code soft-trashes. Stale test (W8 #10) - delete_chat_thread.test.ts was asserting the legacy `{ status: 'deleted' }` patch shape; the helper now writes `{ status: 'trashed', statusChangedAt: <ms> }` in user-trash mode. Docs cron + maker-checker (W8 #8) - docs/{en,de,fr}/self-hosted/configuration/retention.md: cron 03:00 UTC → 04:00 UTC (matches crons.ts), plus a sibling note about the new 01:00 UTC effectReleasesOnly cron. - en docs: replace the non-existent `releaseLegalHold` reference with the correct dual-control flow names plus the new RELEASE_APPROVAL_MIN_DELAY_MS, ORG_HOLD_REQUIRES_DUAL_CONTROL, and closeLegalMatter fan-out behavior. - retention_cleanup.ts stale "03:00 UTC" comment fixed.
…— docs + openai.json honesty Closes round-5 findings #1, #2, #42, #43, #45. - Docs across en/de/fr no longer claim a browser `speechSynthesis` fallback. The code (use-voice-output-player.ts + tts/schema.ts) is explicitly provider-only — failed chunks are skipped silently. The "Provider vs browser fallback" section is replaced with a "When no provider is configured" section that states the actual behaviour: the personalization toggle is disabled and surfaces a Settings link. - Docs across en/de/fr correct the "no cron required" lie. The lifecycle text now reflects reality: a daily org-sweep cron is the primary GC, with opportunistic per-thread cleanup scheduled from the write path as a secondary trigger. - `examples/providers/openai.json` raises the `centsPerMillionCharacters` default from 200 to 1500 (tts-1 list rate, matches `providers.test.ts:81` and the docs' canonical example), and expands the description / de+fr i18n descriptions with the per-token-vs-per- character billing caveat that previously existed only in the English description. Operators on a non-English UI no longer miss the calibration warning. - The English doc consistently uses "Settings → AI providers" (matching the UI label) instead of "Settings > Providers". - `docs/fr/self-hosted/configuration/providers.md:319` anchor link to the FR attachments page is fixed (`#audio-and-video-transcription` was the English slug; the localized heading auto-slugifies to `#transcription-audio-et-video`). DE line 319 had the same English- slug bug, fixed alongside (DE auto-slugs to `#audio-und-video-transkription`).
…— docs + openai.json honesty Closes round-5 findings #1, #2, #42, #43, #45. - Docs across en/de/fr no longer claim a browser `speechSynthesis` fallback. The code (use-voice-output-player.ts + tts/schema.ts) is explicitly provider-only — failed chunks are skipped silently. The "Provider vs browser fallback" section is replaced with a "When no provider is configured" section that states the actual behaviour: the personalization toggle is disabled and surfaces a Settings link. - Docs across en/de/fr correct the "no cron required" lie. The lifecycle text now reflects reality: a daily org-sweep cron is the primary GC, with opportunistic per-thread cleanup scheduled from the write path as a secondary trigger. - `examples/providers/openai.json` raises the `centsPerMillionCharacters` default from 200 to 1500 (tts-1 list rate, matches `providers.test.ts:81` and the docs' canonical example), and expands the description / de+fr i18n descriptions with the per-token-vs-per- character billing caveat that previously existed only in the English description. Operators on a non-English UI no longer miss the calibration warning. - The English doc consistently uses "Settings → AI providers" (matching the UI label) instead of "Settings > Providers". - `docs/fr/self-hosted/configuration/providers.md:319` anchor link to the FR attachments page is fixed (`#audio-and-video-transcription` was the English slug; the localized heading auto-slugifies to `#transcription-audio-et-video`). DE line 319 had the same English- slug bug, fixed alongside (DE auto-slugs to `#audio-und-video-transkription`).
… proxy, spawner)
Container-side foundation for the `code_run` agent tool: an ephemeral
Python/Node sandbox the LLM can invoke to run code with arbitrary packages
and surface generated files (e.g. .pptx via python-pptx) as chat attachments.
Components:
- services/sandbox-runtime: lean Python 3.12 + Node 24 + uv image. Entrypoint
installs requested packages on demand (`--only-binary=:all:` for pip and
`--ignore-scripts` for npm by default — closes setup.py / postinstall ACE
vectors per R2.7), emits PHASE markers for the chat UI, then execs user
code at /workspace/code/main.{py,js}.
- services/sandbox-egress: tinyproxy sidecar on tale-sandbox-net (an
internal-only Docker bridge). Filters CONNECT host requests against an
allow-list (pypi.org, files.pythonhosted.org, registry.npmjs.org, github
package endpoints). Replaces the originally-planned iptables IP allow-list
which R1.3/R2.1 showed was unsafe due to shared Fastly/Cloudflare CDN IPs.
- services/sandbox: ~250 LOC Bun HTTP service. POST /v1/execute with
HMAC-signed body spawns one ephemeral container; POST /v1/cancel/:id
propagates AbortSignal as docker kill. Workspace is a per-call tmpfs
Docker volume (size=256m, hard ENOSPC cap per R2.2); pip/npm caches are
per-org named volumes (closes the R2.3 cross-tenant wheel-cache poison
vector). docker_args.ts is a pure builder with strict regex validation;
the #1 regression gate per R1.22 has 9 passing unit tests asserting the
argv shape and that user code never reaches argv.
- compose.yml: registers both services and the internal `sandbox` network
pinned to `tale-sandbox-net`. IPv6 disabled on the bridge to prevent
v4-allowlist bypass via v6 routes (R1.3).
- .commitlintrc.json: add `sandbox` scope.
Convex schema, executeCode action, code_run tool, CLI compose generator
work, and tests follow in M2 and M3.
Plan: /home/larry/.claude/plans/presentation-generation-from-prompts-delightful-aho.md
… heartbeat Three coupled fixes to the Convex side of the sandbox state machine that together close the failure modes round-2 verification confirmed: R2-B7 #1: `codeStorageId` was stored before `reserveSlotAndInsert` but the rollback set was constructed AFTER reservation. A QUOTA_EXCEEDED throw orphaned one `_storage` blob per rejected run. Catch the reserve error and `ctx.storage.delete()` the blob before rethrowing. R2-B7 #2: the 90-day audit GC dropped audit rows without touching their code/stdout/stderr storage blobs. Inline-delete those three blob types before the row delete (mutation contexts CAN call `ctx.storage.delete`, per `workflows/executions/delete_storage_blob.ts:20`). Watchdog reaps the same way so a stuck row doesn't sit on its blobs for 90 days. Output-file blobs are still owned by `fileMetadata` and not touched here. R2-B6 #1/#2/#3: `recoverStuckSandboxes` now caps each per-status scan at 200 rows so the mutation can't blow its doc-read budget mid-sweep (cron re-runs every 5 min and picks up the trailing rows). The heartbeat `setInterval` callback wraps the mutation call in try/catch+console.warn so a stalled heartbeat is visible rather than silently aging into a watchdog reap. Explicit `await tickHeartbeat()` between each `ctx.storage.store` keeps `heartbeatAt` fresh during multi-MB upload tails. Watchdog cutoff is now `max_timeout + 600s` so those upload tails fit inside the budget by construction.
… proxy, spawner)
Container-side foundation for the `code_run` agent tool: an ephemeral
Python/Node sandbox the LLM can invoke to run code with arbitrary packages
and surface generated files (e.g. .pptx via python-pptx) as chat attachments.
Components:
- services/sandbox-runtime: lean Python 3.12 + Node 24 + uv image. Entrypoint
installs requested packages on demand (`--only-binary=:all:` for pip and
`--ignore-scripts` for npm by default — closes setup.py / postinstall ACE
vectors per R2.7), emits PHASE markers for the chat UI, then execs user
code at /workspace/code/main.{py,js}.
- services/sandbox-egress: tinyproxy sidecar on tale-sandbox-net (an
internal-only Docker bridge). Filters CONNECT host requests against an
allow-list (pypi.org, files.pythonhosted.org, registry.npmjs.org, github
package endpoints). Replaces the originally-planned iptables IP allow-list
which R1.3/R2.1 showed was unsafe due to shared Fastly/Cloudflare CDN IPs.
- services/sandbox: ~250 LOC Bun HTTP service. POST /v1/execute with
HMAC-signed body spawns one ephemeral container; POST /v1/cancel/:id
propagates AbortSignal as docker kill. Workspace is a per-call tmpfs
Docker volume (size=256m, hard ENOSPC cap per R2.2); pip/npm caches are
per-org named volumes (closes the R2.3 cross-tenant wheel-cache poison
vector). docker_args.ts is a pure builder with strict regex validation;
the #1 regression gate per R1.22 has 9 passing unit tests asserting the
argv shape and that user code never reaches argv.
- compose.yml: registers both services and the internal `sandbox` network
pinned to `tale-sandbox-net`. IPv6 disabled on the bridge to prevent
v4-allowlist bypass via v6 routes (R1.3).
- .commitlintrc.json: add `sandbox` scope.
Convex schema, executeCode action, code_run tool, CLI compose generator
work, and tests follow in M2 and M3.
Plan: /home/larry/.claude/plans/presentation-generation-from-prompts-delightful-aho.md
… heartbeat Three coupled fixes to the Convex side of the sandbox state machine that together close the failure modes round-2 verification confirmed: R2-B7 #1: `codeStorageId` was stored before `reserveSlotAndInsert` but the rollback set was constructed AFTER reservation. A QUOTA_EXCEEDED throw orphaned one `_storage` blob per rejected run. Catch the reserve error and `ctx.storage.delete()` the blob before rethrowing. R2-B7 #2: the 90-day audit GC dropped audit rows without touching their code/stdout/stderr storage blobs. Inline-delete those three blob types before the row delete (mutation contexts CAN call `ctx.storage.delete`, per `workflows/executions/delete_storage_blob.ts:20`). Watchdog reaps the same way so a stuck row doesn't sit on its blobs for 90 days. Output-file blobs are still owned by `fileMetadata` and not touched here. R2-B6 #1/#2/#3: `recoverStuckSandboxes` now caps each per-status scan at 200 rows so the mutation can't blow its doc-read budget mid-sweep (cron re-runs every 5 min and picks up the trailing rows). The heartbeat `setInterval` callback wraps the mutation call in try/catch+console.warn so a stalled heartbeat is visible rather than silently aging into a watchdog reap. Explicit `await tickHeartbeat()` between each `ctx.storage.store` keeps `heartbeatAt` fresh during multi-MB upload tails. Watchdog cutoff is now `max_timeout + 600s` so those upload tails fit inside the budget by construction.
Mid-turn stdin steering went silent again for a sub-case the prior quiet-idle work missed: an agent that launches a background workflow and then calls TaskOutput(block=true) to wait is parked inside an in-flight main-level tool. Neither quiet-idle trigger fired — Trigger #1 requires inflightToolUses === 0 (the in-flight TaskOutput keeps it at 1) plus a text last-event, and Trigger #2 requires an inflight Task sub-agent. So the exec was never flagged idle, the queued message took the file/hook path, and no tool/Stop boundary fires during the block — it landed only when the block timed out (minutes), never the prompt ~5s over stdin. Add a third deliverable-idle posture: a background task pending while the only main-level tools in flight are blocking task-reads (TaskOutput), tracked in inflightWaitTools (mirrors inflightSubAgents). Also drop lastMainEventWasText from the background-task trigger — with inflightToolUses === 0 the last main event is necessarily a completed text OR tool-result, so the flag only excluded the tool-result case (the TaskOutput block=false blind spot); it stays for the stdin-confirm path. The three postures move into a pure quietIdleDecision helper so this logic — a delivery blind spot for the third time — gets direct unit coverage. Platform-only (hot-pushed; no image rebuild), but a session container still needs the existing /stdin daemon + stream-json adapter for stdin delivery; old containers fall back to file and stall through the block.
… and *_tool.ts naming
Summary by CodeRabbit
New Features
customer_readtool with get_by_id, get_by_email, and list operationsproduct_readtool with get_by_id and list operationsweb_readtool combining fetch and search operationsImprovements
✏️ Tip: You can customize this high-level summary in your review settings.