Skip to content

refactor(agent_tools): consolidate tools with unified read operations#1

Merged
larryro merged 1 commit into
mainfrom
code-cleanup
Dec 2, 2025
Merged

refactor(agent_tools): consolidate tools with unified read operations#1
larryro merged 1 commit into
mainfrom
code-cleanup

Conversation

@larryro

@larryro larryro commented Dec 2, 2025

Copy link
Copy Markdown
Collaborator

… 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

Summary by CodeRabbit

  • New Features

    • Consolidated customer operations under unified customer_read tool with get_by_id, get_by_email, and list operations
    • Consolidated product operations under unified product_read tool with get_by_id and list operations
    • Added unified web_read tool combining fetch and search operations
    • Enhanced workflow tools with new operations including list, structure, examples, and history retrieval
  • Improvements

    • Renamed RAG knowledge tool to RAG write for clarity

✏️ Tip: You can customize this high-level summary in your review settings.

… 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
@coderabbitai

coderabbitai Bot commented Dec 2, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This 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 fetch_url and web_search with web_read), customer tools (consolidating customer_search, list_customers, and related operations into customer_read), product tools (consolidating product_get and list_products into product_read), and workflow tools (consolidating get_workflow_structure, list_available_actions, and search_workflow_examples into workflow_read). Additionally, the PR removes workflow processing record tools, refactors the tool registry with new naming conventions, updates agent configurations, and modifies predefined workflows to use the new unified tools.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Tool registry mapping verification: Ensure all old tool references are correctly mapped to new tool names and operations in tool_registry.ts and that no tools are inadvertently missing from the registry.
  • Helper function implementations: Review the new modular helper functions (read_customer_by_id.ts, read_product_by_id.ts, read_workflow_structure.ts, etc.) for consistency in parameter handling, error management, and field selection logic.
  • Type definitions across modules: Verify that newly introduced type files (types.ts in customer, product, crawler, and workflow helpers) define compatible shapes used by their respective helper functions and main tool files.
  • Predefined workflow tool references: Confirm that all predefined workflows (general_product_recommendation.ts, loopi_product_recommendation.ts) correctly use the new unified tool names and operation arguments instead of removed single-purpose tools.
  • Web/crawler service URL resolution: Ensure the new get_crawler_service_url.ts and get_search_service_url.ts helpers correctly fallback through variables, environment, and defaults.
  • Tool operation routing logic: Verify that the unified tools (web_read_tool.ts, customer_read_tool.ts, product_read_tool.ts, workflow_read_tool.ts) correctly dispatch to their respective helper functions based on operation arguments.
  • Agent configuration updates: Check that create_chat_agent.ts, create_workflow_agent.ts, and workflow_assistant_agent.ts have been consistently updated with the new default tool sets.
  • Workflow processing records removal impact: Confirm that removal of find_unprocessed_entities.ts, find_unprocessed_open_conversation.ts, and mark_entity_processed.ts does not leave orphaned references elsewhere in the codebase.

Possibly related PRs

  • talecorp/poc2#30: Introduces agent-oriented workflow primitives and tool registry changes that align with the unified tool consolidation pattern in this PR.
  • tale-project/poc2#497: Makes overlapping code-level changes to crawler/web tools, file/document tooling, and TOOL_REGISTRY entries.
  • talecorp/poc2#185: Refactors the Convex tool registry and tool-loading surface (TOOL_REGISTRY/TOOL_REGISTRY_MAP) affecting the same tool infrastructure layer.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 names

The change to use a single workflow_read tool in defaultWorkflowTools (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, and search_workflow_examples as standalone tools, even though these are now workflow_read operations.
  • Shows an example LLM config with tools: ['customer_search', 'product_get', 'list_products', 'rag_search'], which doesn’t match the new customer_read/product_read tooling.

To avoid the agent attempting to call non‑existent tools, those instructional references and examples should be updated to:

  • Use workflow_read with appropriate operation values.
  • Replace customer_search/product_get/list_products with customer_read/product_read in 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 unified product_read tool with operations:

  • Line 231: "Use product_get to get full details" → should be product_read with operation = 'get_by_id'
  • Line 236: "call list_products to browse" → should be product_read with operation = '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 to list_products in confidence scoring rules.

Line 319 mentions "Browsed products (from list_products)" but should reference product_read with operation = '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 relevance
services/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_NAMES and TOOL_REGISTRY may cause confusion during maintenance. For example, 'generate_file' appears at position 6 in TOOL_NAMES but generateFileTool is at position 12 in TOOL_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)

📥 Commits

Reviewing files that changed from the base of the PR and between 74b2a88 and 03e5b6c.

⛔ Files ignored due to path filters (1)
  • services/platform/convex/_generated/api.d.ts is 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.ts
  • services/platform/convex/agent_tools/convex_tools/products/helpers/types.ts
  • services/platform/convex/agent_tools/convex_tools/customers/helpers/types.ts
  • services/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.ts
  • services/platform/convex/agent_tools/convex_tools/workflows/helpers/types.ts
  • services/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.ts
  • services/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.ts
  • services/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.ts
  • services/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.ts
  • services/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.ts
  • services/platform/convex/agent_tools/convex_tools/crawler/helpers/types.ts
  • services/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.ts
  • services/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: Conditional providerOptions inclusion looks good; confirm external expectations

Using hasMaxTokens to only include providerOptions when opts.maxTokens is a number cleans up the config and avoids passing maxOutputTokens: undefined. The only behavior change is that when maxTokens is omitted, providerOptions is now absent instead of present-with-undefined. Please confirm that Agent (from @convex-dev/agent) and the underlying OpenAI provider do not rely on providerOptions always 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 verify responseBody doesn'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 responseBody before 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: organizationId is extracted for logging, not for query scoping.

The function extracts organizationId from context (line 10) and uses it in console logs (lines 12 and 25) for debugging and tracing purposes. The getProductById query does not accept an organizationId parameter—it only takes productId. If product scoping by organization is required, it must be enforced at the database or schema level within ProductsModel.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 correct

Tool name, description, and log labels are consistently updated to rag_write with 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 consistent

The shared result types and default*Fields follow the same pattern as the customer helpers and give a clear, centralized contract for product_read operations. This looks good for the current refactor.

services/platform/convex/predefined_workflows/general_product_recommendation.ts (1)

181-193: LLM tools updated correctly to use product_read

Switching 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: getActiveVersion internalQuery wiring is consistent

The new getActiveVersion internalQuery matches the existing patterns (args, return type, delegation to the model) and provides the expected wrapper for helpers like readActiveVersionSteps.

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 ActionCtx and separate organizationId extraction) matches the convention used in sibling helpers like readProductList. Based on learnings, the pagination approach with paginationOpts containing numItems and cursor follows Convex conventions.


23-34: Consider adding explicit type annotation for the query result.

The inline type annotation for result works, but since this pattern is repeated across helpers (read_product_list.ts uses 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` properties
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} : 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 limitations
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: 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_url requires url (line 115)
  • search requires query (line 124)

All optional search parameters are correctly forwarded to searchWeb. The explicit throw new Error for 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.ts for OpenAI compatibility. The cursor field correctly uses .nullable().optional() to support Convex pagination semantics where the cursor can be null for the first page.


92-114: Handler logic is correct.

Validation and delegation are properly implemented:

  • get_by_id validates productId presence before calling readProductById
  • list passes through pagination args to readProductList

The return type union correctly reflects both possible result types.


116-116: Unnecessary as const assertion.

The as const assertion on line 116 is redundant since the object is already typed as ToolDefinition. This differs from web_read_tool.ts which doesn't use as 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 types
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 as const for string literals in discriminated unions
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 : 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 values
Learnt 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/values
Learnt 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_read replaces get_workflow_structure, list_available_actions, and search_workflow_examples via operations get_structure, list_available_actions, and search_examples respectively
  • customer_read replaces customer_search and list_customers
  • product_read replaces product_get and list_products

The workflow_read tool 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) and defaultListFields (summary) follows a sensible pattern consistent with the product helpers. Using Record<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 discriminatedUnion is 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 like engines, publishedDate, and category.

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 WorkflowReadListActionsResult and provides comprehensive information including the catalog, total count, and usage guidance.


29-31: The logic is correct. Verification of ACTION_REFERENCE in action_reference.ts confirms that 'operation' is included in requiredParams for virtually all operations (e.g., ['operation', 'organizationId', 'name']), so the check will return true as 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 _id is always included, and filters out undefined keys before logging. This is consistent with the pattern in readCustomerById.

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, with hasMore derived from !isDone and 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 discriminatedUnion is 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 fetchSearXNGResults with 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 in read_active_version_steps.ts and 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_MAP provides 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 Doc type from the generated dataModel and maintains a clear separation of type definitions without any runtime code.

Comment on lines +35 to +48
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
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`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
const url = new URL(`${serviceUrl}/search`);
if (!serviceUrl) {
throw new Error('SearXNG service URL is required');
}
const url = new URL(`${serviceUrl}/search`);

Comment on lines +52 to +59
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}`);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
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.

Comment on lines +7 to +15
export function getCrawlerServiceUrl(
variables?: Record<string, unknown>,
): string {
return (
(variables?.crawlerServiceUrl as string) ||
process.env.CRAWLER_URL ||
'http://localhost:8002'
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Comment on lines +6 to +36
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'>[],
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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:

  1. Unhandled exceptions propagating to the caller
  2. 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.

Suggested change
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;
}
}

Comment on lines +29 to +108
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;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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 error and message)
  • 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"] }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
{ 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.

Comment on lines +189 to +194
// operation === 'list_all'
return readAllWorkflows(ctx, {
status: args.status,
includeStepCount: args.includeStepCount,
});
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 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.

Suggested change
// 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.

Comment on lines 288 to 297
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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).

@larryro larryro merged commit 96811a5 into main Dec 2, 2025
2 checks passed
@larryro larryro deleted the code-cleanup branch December 2, 2025 08:12
@coderabbitai coderabbitai Bot mentioned this pull request Dec 4, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 11, 2025
larryro added a commit that referenced this pull request May 7, 2026
…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.
larryro added a commit that referenced this pull request May 7, 2026
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.
larryro added a commit that referenced this pull request May 8, 2026
…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.
larryro added a commit that referenced this pull request May 8, 2026
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.
larryro added a commit that referenced this pull request May 8, 2026
…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.
larryro added a commit that referenced this pull request May 8, 2026
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.
larryro added a commit that referenced this pull request May 9, 2026
…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.
larryro added a commit that referenced this pull request May 9, 2026
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.
larryro added a commit that referenced this pull request May 17, 2026
…— 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`).
larryro added a commit that referenced this pull request May 17, 2026
…— 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`).
larryro added a commit that referenced this pull request May 20, 2026
… 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
larryro added a commit that referenced this pull request May 21, 2026
… 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.
larryro added a commit that referenced this pull request May 24, 2026
… 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
larryro added a commit that referenced this pull request May 24, 2026
… 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.
larryro added a commit that referenced this pull request Jun 13, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant