diff --git a/services/platform/convex/_generated/api.d.ts b/services/platform/convex/_generated/api.d.ts index b4b47790f8..304ed5e9ae 100644 --- a/services/platform/convex/_generated/api.d.ts +++ b/services/platform/convex/_generated/api.d.ts @@ -347,7 +347,6 @@ import type * as model_wf_definitions_update_draft from "../model/wf_definitions import type * as model_wf_definitions_update_workflow from "../model/wf_definitions/update_workflow.js"; import type * as model_wf_definitions_update_workflow_status from "../model/wf_definitions/update_workflow_status.js"; import type * as model_wf_executions_complete_execution from "../model/wf_executions/complete_execution.js"; -import type * as model_wf_executions_create_execution from "../model/wf_executions/create_execution.js"; import type * as model_wf_executions_fail_execution from "../model/wf_executions/fail_execution.js"; import type * as model_wf_executions_get_execution from "../model/wf_executions/get_execution.js"; import type * as model_wf_executions_get_execution_step_journal from "../model/wf_executions/get_execution_step_journal.js"; @@ -458,65 +457,65 @@ import type * as wf_executions from "../wf_executions.js"; import type * as wf_step_defs from "../wf_step_defs.js"; import type * as workflow_actions_action_registry from "../workflow/actions/action_registry.js"; import type * as workflow_actions_approval_approval_action from "../workflow/actions/approval/approval_action.js"; -import type * as workflow_actions_approval_create_approval from "../workflow/actions/approval/create_approval.js"; -import type * as workflow_actions_approval_get_approval from "../workflow/actions/approval/get_approval.js"; -import type * as workflow_actions_approval_get_approval_history from "../workflow/actions/approval/get_approval_history.js"; -import type * as workflow_actions_approval_list_approvals_for_execution from "../workflow/actions/approval/list_approvals_for_execution.js"; -import type * as workflow_actions_approval_list_pending_approvals from "../workflow/actions/approval/list_pending_approvals.js"; -import type * as workflow_actions_approval_list_pending_approvals_for_execution from "../workflow/actions/approval/list_pending_approvals_for_execution.js"; -import type * as workflow_actions_approval_types from "../workflow/actions/approval/types.js"; -import type * as workflow_actions_approval_update_approval_status from "../workflow/actions/approval/update_approval_status.js"; +import type * as workflow_actions_approval_helpers_create_approval from "../workflow/actions/approval/helpers/create_approval.js"; +import type * as workflow_actions_approval_helpers_get_approval from "../workflow/actions/approval/helpers/get_approval.js"; +import type * as workflow_actions_approval_helpers_get_approval_history from "../workflow/actions/approval/helpers/get_approval_history.js"; +import type * as workflow_actions_approval_helpers_list_approvals_for_execution from "../workflow/actions/approval/helpers/list_approvals_for_execution.js"; +import type * as workflow_actions_approval_helpers_list_pending_approvals from "../workflow/actions/approval/helpers/list_pending_approvals.js"; +import type * as workflow_actions_approval_helpers_list_pending_approvals_for_execution from "../workflow/actions/approval/helpers/list_pending_approvals_for_execution.js"; +import type * as workflow_actions_approval_helpers_types from "../workflow/actions/approval/helpers/types.js"; +import type * as workflow_actions_approval_helpers_update_approval_status from "../workflow/actions/approval/helpers/update_approval_status.js"; import type * as workflow_actions_conversation_conversation_action from "../workflow/actions/conversation/conversation_action.js"; -import type * as workflow_actions_conversation_create_conversation from "../workflow/actions/conversation/create_conversation.js"; -import type * as workflow_actions_conversation_create_conversation_from_email from "../workflow/actions/conversation/create_conversation_from_email.js"; -import type * as workflow_actions_conversation_create_conversation_from_sent_email from "../workflow/actions/conversation/create_conversation_from_sent_email.js"; -import type * as workflow_actions_conversation_get_conversation_by_id from "../workflow/actions/conversation/get_conversation_by_id.js"; import type * as workflow_actions_conversation_helpers_add_message_to_conversation from "../workflow/actions/conversation/helpers/add_message_to_conversation.js"; import type * as workflow_actions_conversation_helpers_build_conversation_metadata from "../workflow/actions/conversation/helpers/build_conversation_metadata.js"; import type * as workflow_actions_conversation_helpers_build_email_metadata from "../workflow/actions/conversation/helpers/build_email_metadata.js"; import type * as workflow_actions_conversation_helpers_build_initial_message from "../workflow/actions/conversation/helpers/build_initial_message.js"; import type * as workflow_actions_conversation_helpers_check_conversation_exists from "../workflow/actions/conversation/helpers/check_conversation_exists.js"; import type * as workflow_actions_conversation_helpers_check_message_exists from "../workflow/actions/conversation/helpers/check_message_exists.js"; +import type * as workflow_actions_conversation_helpers_create_conversation from "../workflow/actions/conversation/helpers/create_conversation.js"; +import type * as workflow_actions_conversation_helpers_create_conversation_from_email from "../workflow/actions/conversation/helpers/create_conversation_from_email.js"; +import type * as workflow_actions_conversation_helpers_create_conversation_from_sent_email from "../workflow/actions/conversation/helpers/create_conversation_from_sent_email.js"; import type * as workflow_actions_conversation_helpers_find_or_create_customer_from_email from "../workflow/actions/conversation/helpers/find_or_create_customer_from_email.js"; import type * as workflow_actions_conversation_helpers_find_related_conversation from "../workflow/actions/conversation/helpers/find_related_conversation.js"; +import type * as workflow_actions_conversation_helpers_get_conversation_by_id from "../workflow/actions/conversation/helpers/get_conversation_by_id.js"; +import type * as workflow_actions_conversation_helpers_query_conversation_messages from "../workflow/actions/conversation/helpers/query_conversation_messages.js"; +import type * as workflow_actions_conversation_helpers_query_conversations from "../workflow/actions/conversation/helpers/query_conversations.js"; +import type * as workflow_actions_conversation_helpers_query_latest_message_by_delivery_state from "../workflow/actions/conversation/helpers/query_latest_message_by_delivery_state.js"; +import type * as workflow_actions_conversation_helpers_types from "../workflow/actions/conversation/helpers/types.js"; +import type * as workflow_actions_conversation_helpers_update_conversations from "../workflow/actions/conversation/helpers/update_conversations.js"; import type * as workflow_actions_conversation_helpers_update_message from "../workflow/actions/conversation/helpers/update_message.js"; -import type * as workflow_actions_conversation_query_conversation_messages from "../workflow/actions/conversation/query_conversation_messages.js"; -import type * as workflow_actions_conversation_query_conversations from "../workflow/actions/conversation/query_conversations.js"; -import type * as workflow_actions_conversation_query_latest_message_by_delivery_state from "../workflow/actions/conversation/query_latest_message_by_delivery_state.js"; -import type * as workflow_actions_conversation_types from "../workflow/actions/conversation/types.js"; -import type * as workflow_actions_conversation_update_conversations from "../workflow/actions/conversation/update_conversations.js"; import type * as workflow_actions_crawler_crawler_action from "../workflow/actions/crawler/crawler_action.js"; -import type * as workflow_actions_crawler_types from "../workflow/actions/crawler/types.js"; +import type * as workflow_actions_crawler_helpers_types from "../workflow/actions/crawler/helpers/types.js"; import type * as workflow_actions_customer_customer_action from "../workflow/actions/customer/customer_action.js"; import type * as workflow_actions_document_document_action from "../workflow/actions/document/document_action.js"; import type * as workflow_actions_email_provider_email_provider_action from "../workflow/actions/email_provider/email_provider_action.js"; -import type * as workflow_actions_imap_get_imap_credentials from "../workflow/actions/imap/get_imap_credentials.js"; +import type * as workflow_actions_imap_helpers_get_imap_credentials from "../workflow/actions/imap/helpers/get_imap_credentials.js"; +import type * as workflow_actions_imap_helpers_types from "../workflow/actions/imap/helpers/types.js"; import type * as workflow_actions_imap_imap_action from "../workflow/actions/imap/imap_action.js"; -import type * as workflow_actions_imap_types from "../workflow/actions/imap/types.js"; -import type * as workflow_actions_integration_build_secrets_from_integration from "../workflow/actions/integration/build_secrets_from_integration.js"; +import type * as workflow_actions_integration_helpers_build_secrets_from_integration from "../workflow/actions/integration/helpers/build_secrets_from_integration.js"; import type * as workflow_actions_integration_integration_action from "../workflow/actions/integration/integration_action.js"; import type * as workflow_actions_onedrive_onedrive_action from "../workflow/actions/onedrive/onedrive_action.js"; import type * as workflow_actions_product_product_action from "../workflow/actions/product/product_action.js"; -import type * as workflow_actions_rag_get_document_info from "../workflow/actions/rag/get_document_info.js"; -import type * as workflow_actions_rag_get_rag_config from "../workflow/actions/rag/get_rag_config.js"; +import type * as workflow_actions_rag_helpers_get_document_info from "../workflow/actions/rag/helpers/get_document_info.js"; +import type * as workflow_actions_rag_helpers_get_rag_config from "../workflow/actions/rag/helpers/get_rag_config.js"; +import type * as workflow_actions_rag_helpers_types from "../workflow/actions/rag/helpers/types.js"; +import type * as workflow_actions_rag_helpers_upload_file_direct from "../workflow/actions/rag/helpers/upload_file_direct.js"; +import type * as workflow_actions_rag_helpers_upload_text_document from "../workflow/actions/rag/helpers/upload_text_document.js"; import type * as workflow_actions_rag_rag_action from "../workflow/actions/rag/rag_action.js"; -import type * as workflow_actions_rag_types from "../workflow/actions/rag/types.js"; -import type * as workflow_actions_rag_upload_file_direct from "../workflow/actions/rag/upload_file_direct.js"; -import type * as workflow_actions_rag_upload_text_document from "../workflow/actions/rag/upload_text_document.js"; import type * as workflow_actions_set_variables_action from "../workflow/actions/set_variables_action.js"; import type * as workflow_actions_tone_of_voice_tone_of_voice_action from "../workflow/actions/tone_of_voice/tone_of_voice_action.js"; -import type * as workflow_actions_website_types from "../workflow/actions/website/types.js"; +import type * as workflow_actions_website_helpers_types from "../workflow/actions/website/helpers/types.js"; import type * as workflow_actions_website_website_action from "../workflow/actions/website/website_action.js"; -import type * as workflow_actions_websitePages_types from "../workflow/actions/websitePages/types.js"; +import type * as workflow_actions_websitePages_helpers_types from "../workflow/actions/websitePages/helpers/types.js"; import type * as workflow_actions_websitePages_websitePages_action from "../workflow/actions/websitePages/websitePages_action.js"; -import type * as workflow_actions_workflow_types from "../workflow/actions/workflow/types.js"; -import type * as workflow_actions_workflow_upload_workflows from "../workflow/actions/workflow/upload_workflows.js"; +import type * as workflow_actions_workflow_helpers_types from "../workflow/actions/workflow/helpers/types.js"; +import type * as workflow_actions_workflow_helpers_upload_workflows from "../workflow/actions/workflow/helpers/upload_workflows.js"; import type * as workflow_actions_workflow_workflow_action from "../workflow/actions/workflow/workflow_action.js"; -import type * as workflow_actions_workflow_processing_records_find_product_recommendation_by_status from "../workflow/actions/workflow_processing_records/find_product_recommendation_by_status.js"; -import type * as workflow_actions_workflow_processing_records_find_unprocessed from "../workflow/actions/workflow_processing_records/find_unprocessed.js"; -import type * as workflow_actions_workflow_processing_records_find_unprocessed_open_conversation from "../workflow/actions/workflow_processing_records/find_unprocessed_open_conversation.js"; -import type * as workflow_actions_workflow_processing_records_record_processed from "../workflow/actions/workflow_processing_records/record_processed.js"; -import type * as workflow_actions_workflow_processing_records_types from "../workflow/actions/workflow_processing_records/types.js"; +import type * as workflow_actions_workflow_processing_records_helpers_find_product_recommendation_by_status from "../workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.js"; +import type * as workflow_actions_workflow_processing_records_helpers_find_unprocessed from "../workflow/actions/workflow_processing_records/helpers/find_unprocessed.js"; +import type * as workflow_actions_workflow_processing_records_helpers_find_unprocessed_open_conversation from "../workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.js"; +import type * as workflow_actions_workflow_processing_records_helpers_record_processed from "../workflow/actions/workflow_processing_records/helpers/record_processed.js"; +import type * as workflow_actions_workflow_processing_records_helpers_types from "../workflow/actions/workflow_processing_records/helpers/types.js"; import type * as workflow_actions_workflow_processing_records_workflow_processing_records_action from "../workflow/actions/workflow_processing_records/workflow_processing_records_action.js"; import type * as workflow_engine from "../workflow/engine.js"; import type * as workflow_helpers_data_source_database_workflow_data_source from "../workflow/helpers/data_source/database_workflow_data_source.js"; @@ -938,7 +937,6 @@ declare const fullApi: ApiFromModules<{ "model/wf_definitions/update_workflow": typeof model_wf_definitions_update_workflow; "model/wf_definitions/update_workflow_status": typeof model_wf_definitions_update_workflow_status; "model/wf_executions/complete_execution": typeof model_wf_executions_complete_execution; - "model/wf_executions/create_execution": typeof model_wf_executions_create_execution; "model/wf_executions/fail_execution": typeof model_wf_executions_fail_execution; "model/wf_executions/get_execution": typeof model_wf_executions_get_execution; "model/wf_executions/get_execution_step_journal": typeof model_wf_executions_get_execution_step_journal; @@ -1049,65 +1047,65 @@ declare const fullApi: ApiFromModules<{ wf_step_defs: typeof wf_step_defs; "workflow/actions/action_registry": typeof workflow_actions_action_registry; "workflow/actions/approval/approval_action": typeof workflow_actions_approval_approval_action; - "workflow/actions/approval/create_approval": typeof workflow_actions_approval_create_approval; - "workflow/actions/approval/get_approval": typeof workflow_actions_approval_get_approval; - "workflow/actions/approval/get_approval_history": typeof workflow_actions_approval_get_approval_history; - "workflow/actions/approval/list_approvals_for_execution": typeof workflow_actions_approval_list_approvals_for_execution; - "workflow/actions/approval/list_pending_approvals": typeof workflow_actions_approval_list_pending_approvals; - "workflow/actions/approval/list_pending_approvals_for_execution": typeof workflow_actions_approval_list_pending_approvals_for_execution; - "workflow/actions/approval/types": typeof workflow_actions_approval_types; - "workflow/actions/approval/update_approval_status": typeof workflow_actions_approval_update_approval_status; + "workflow/actions/approval/helpers/create_approval": typeof workflow_actions_approval_helpers_create_approval; + "workflow/actions/approval/helpers/get_approval": typeof workflow_actions_approval_helpers_get_approval; + "workflow/actions/approval/helpers/get_approval_history": typeof workflow_actions_approval_helpers_get_approval_history; + "workflow/actions/approval/helpers/list_approvals_for_execution": typeof workflow_actions_approval_helpers_list_approvals_for_execution; + "workflow/actions/approval/helpers/list_pending_approvals": typeof workflow_actions_approval_helpers_list_pending_approvals; + "workflow/actions/approval/helpers/list_pending_approvals_for_execution": typeof workflow_actions_approval_helpers_list_pending_approvals_for_execution; + "workflow/actions/approval/helpers/types": typeof workflow_actions_approval_helpers_types; + "workflow/actions/approval/helpers/update_approval_status": typeof workflow_actions_approval_helpers_update_approval_status; "workflow/actions/conversation/conversation_action": typeof workflow_actions_conversation_conversation_action; - "workflow/actions/conversation/create_conversation": typeof workflow_actions_conversation_create_conversation; - "workflow/actions/conversation/create_conversation_from_email": typeof workflow_actions_conversation_create_conversation_from_email; - "workflow/actions/conversation/create_conversation_from_sent_email": typeof workflow_actions_conversation_create_conversation_from_sent_email; - "workflow/actions/conversation/get_conversation_by_id": typeof workflow_actions_conversation_get_conversation_by_id; "workflow/actions/conversation/helpers/add_message_to_conversation": typeof workflow_actions_conversation_helpers_add_message_to_conversation; "workflow/actions/conversation/helpers/build_conversation_metadata": typeof workflow_actions_conversation_helpers_build_conversation_metadata; "workflow/actions/conversation/helpers/build_email_metadata": typeof workflow_actions_conversation_helpers_build_email_metadata; "workflow/actions/conversation/helpers/build_initial_message": typeof workflow_actions_conversation_helpers_build_initial_message; "workflow/actions/conversation/helpers/check_conversation_exists": typeof workflow_actions_conversation_helpers_check_conversation_exists; "workflow/actions/conversation/helpers/check_message_exists": typeof workflow_actions_conversation_helpers_check_message_exists; + "workflow/actions/conversation/helpers/create_conversation": typeof workflow_actions_conversation_helpers_create_conversation; + "workflow/actions/conversation/helpers/create_conversation_from_email": typeof workflow_actions_conversation_helpers_create_conversation_from_email; + "workflow/actions/conversation/helpers/create_conversation_from_sent_email": typeof workflow_actions_conversation_helpers_create_conversation_from_sent_email; "workflow/actions/conversation/helpers/find_or_create_customer_from_email": typeof workflow_actions_conversation_helpers_find_or_create_customer_from_email; "workflow/actions/conversation/helpers/find_related_conversation": typeof workflow_actions_conversation_helpers_find_related_conversation; + "workflow/actions/conversation/helpers/get_conversation_by_id": typeof workflow_actions_conversation_helpers_get_conversation_by_id; + "workflow/actions/conversation/helpers/query_conversation_messages": typeof workflow_actions_conversation_helpers_query_conversation_messages; + "workflow/actions/conversation/helpers/query_conversations": typeof workflow_actions_conversation_helpers_query_conversations; + "workflow/actions/conversation/helpers/query_latest_message_by_delivery_state": typeof workflow_actions_conversation_helpers_query_latest_message_by_delivery_state; + "workflow/actions/conversation/helpers/types": typeof workflow_actions_conversation_helpers_types; + "workflow/actions/conversation/helpers/update_conversations": typeof workflow_actions_conversation_helpers_update_conversations; "workflow/actions/conversation/helpers/update_message": typeof workflow_actions_conversation_helpers_update_message; - "workflow/actions/conversation/query_conversation_messages": typeof workflow_actions_conversation_query_conversation_messages; - "workflow/actions/conversation/query_conversations": typeof workflow_actions_conversation_query_conversations; - "workflow/actions/conversation/query_latest_message_by_delivery_state": typeof workflow_actions_conversation_query_latest_message_by_delivery_state; - "workflow/actions/conversation/types": typeof workflow_actions_conversation_types; - "workflow/actions/conversation/update_conversations": typeof workflow_actions_conversation_update_conversations; "workflow/actions/crawler/crawler_action": typeof workflow_actions_crawler_crawler_action; - "workflow/actions/crawler/types": typeof workflow_actions_crawler_types; + "workflow/actions/crawler/helpers/types": typeof workflow_actions_crawler_helpers_types; "workflow/actions/customer/customer_action": typeof workflow_actions_customer_customer_action; "workflow/actions/document/document_action": typeof workflow_actions_document_document_action; "workflow/actions/email_provider/email_provider_action": typeof workflow_actions_email_provider_email_provider_action; - "workflow/actions/imap/get_imap_credentials": typeof workflow_actions_imap_get_imap_credentials; + "workflow/actions/imap/helpers/get_imap_credentials": typeof workflow_actions_imap_helpers_get_imap_credentials; + "workflow/actions/imap/helpers/types": typeof workflow_actions_imap_helpers_types; "workflow/actions/imap/imap_action": typeof workflow_actions_imap_imap_action; - "workflow/actions/imap/types": typeof workflow_actions_imap_types; - "workflow/actions/integration/build_secrets_from_integration": typeof workflow_actions_integration_build_secrets_from_integration; + "workflow/actions/integration/helpers/build_secrets_from_integration": typeof workflow_actions_integration_helpers_build_secrets_from_integration; "workflow/actions/integration/integration_action": typeof workflow_actions_integration_integration_action; "workflow/actions/onedrive/onedrive_action": typeof workflow_actions_onedrive_onedrive_action; "workflow/actions/product/product_action": typeof workflow_actions_product_product_action; - "workflow/actions/rag/get_document_info": typeof workflow_actions_rag_get_document_info; - "workflow/actions/rag/get_rag_config": typeof workflow_actions_rag_get_rag_config; + "workflow/actions/rag/helpers/get_document_info": typeof workflow_actions_rag_helpers_get_document_info; + "workflow/actions/rag/helpers/get_rag_config": typeof workflow_actions_rag_helpers_get_rag_config; + "workflow/actions/rag/helpers/types": typeof workflow_actions_rag_helpers_types; + "workflow/actions/rag/helpers/upload_file_direct": typeof workflow_actions_rag_helpers_upload_file_direct; + "workflow/actions/rag/helpers/upload_text_document": typeof workflow_actions_rag_helpers_upload_text_document; "workflow/actions/rag/rag_action": typeof workflow_actions_rag_rag_action; - "workflow/actions/rag/types": typeof workflow_actions_rag_types; - "workflow/actions/rag/upload_file_direct": typeof workflow_actions_rag_upload_file_direct; - "workflow/actions/rag/upload_text_document": typeof workflow_actions_rag_upload_text_document; "workflow/actions/set_variables_action": typeof workflow_actions_set_variables_action; "workflow/actions/tone_of_voice/tone_of_voice_action": typeof workflow_actions_tone_of_voice_tone_of_voice_action; - "workflow/actions/website/types": typeof workflow_actions_website_types; + "workflow/actions/website/helpers/types": typeof workflow_actions_website_helpers_types; "workflow/actions/website/website_action": typeof workflow_actions_website_website_action; - "workflow/actions/websitePages/types": typeof workflow_actions_websitePages_types; + "workflow/actions/websitePages/helpers/types": typeof workflow_actions_websitePages_helpers_types; "workflow/actions/websitePages/websitePages_action": typeof workflow_actions_websitePages_websitePages_action; - "workflow/actions/workflow/types": typeof workflow_actions_workflow_types; - "workflow/actions/workflow/upload_workflows": typeof workflow_actions_workflow_upload_workflows; + "workflow/actions/workflow/helpers/types": typeof workflow_actions_workflow_helpers_types; + "workflow/actions/workflow/helpers/upload_workflows": typeof workflow_actions_workflow_helpers_upload_workflows; "workflow/actions/workflow/workflow_action": typeof workflow_actions_workflow_workflow_action; - "workflow/actions/workflow_processing_records/find_product_recommendation_by_status": typeof workflow_actions_workflow_processing_records_find_product_recommendation_by_status; - "workflow/actions/workflow_processing_records/find_unprocessed": typeof workflow_actions_workflow_processing_records_find_unprocessed; - "workflow/actions/workflow_processing_records/find_unprocessed_open_conversation": typeof workflow_actions_workflow_processing_records_find_unprocessed_open_conversation; - "workflow/actions/workflow_processing_records/record_processed": typeof workflow_actions_workflow_processing_records_record_processed; - "workflow/actions/workflow_processing_records/types": typeof workflow_actions_workflow_processing_records_types; + "workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status": typeof workflow_actions_workflow_processing_records_helpers_find_product_recommendation_by_status; + "workflow/actions/workflow_processing_records/helpers/find_unprocessed": typeof workflow_actions_workflow_processing_records_helpers_find_unprocessed; + "workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation": typeof workflow_actions_workflow_processing_records_helpers_find_unprocessed_open_conversation; + "workflow/actions/workflow_processing_records/helpers/record_processed": typeof workflow_actions_workflow_processing_records_helpers_record_processed; + "workflow/actions/workflow_processing_records/helpers/types": typeof workflow_actions_workflow_processing_records_helpers_types; "workflow/actions/workflow_processing_records/workflow_processing_records_action": typeof workflow_actions_workflow_processing_records_workflow_processing_records_action; "workflow/engine": typeof workflow_engine; "workflow/helpers/data_source/database_workflow_data_source": typeof workflow_helpers_data_source_database_workflow_data_source; diff --git a/services/platform/convex/agent_tools/convex_tools/workflows/action_reference.ts b/services/platform/convex/agent_tools/convex_tools/workflows/action_reference.ts index a8a1e41f47..ce6de0a7dc 100644 --- a/services/platform/convex/agent_tools/convex_tools/workflows/action_reference.ts +++ b/services/platform/convex/agent_tools/convex_tools/workflows/action_reference.ts @@ -429,13 +429,13 @@ export const ACTION_REFERENCE: ActionReference[] = [ 'operation', 'organizationId', 'tableName', - 'documentId', - 'workflowId', - 'documentCreationTime', + 'recordId', + 'wfDefinitionId', + 'recordCreationTime', ], optionalParams: ['metadata'], example: - '{ action: "workflow_processing_records", parameters: { operation: "record_processed", organizationId: "{{organizationId}}", tableName: "customers", documentId: "{{customerId}}", workflowId: "{{workflowId}}", documentCreationTime: {{customer._creationTime}} } }', + '{ action: "workflow_processing_records", parameters: { operation: "record_processed", organizationId: "{{organizationId}}", tableName: "customers", recordId: "{{customerId}}", wfDefinitionId: "{{wfDefinitionId}}", recordCreationTime: {{customer._creationTime}} } }', }, ], }, diff --git a/services/platform/convex/lib/create_workflow_agent.ts b/services/platform/convex/lib/create_workflow_agent.ts index b5e1b1d352..d387abb417 100644 --- a/services/platform/convex/lib/create_workflow_agent.ts +++ b/services/platform/convex/lib/create_workflow_agent.ts @@ -280,9 +280,9 @@ REMINDER: All of these are action steps with stepType: "action". The "type" fiel operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', - documentId: '{{entityId}}', - documentCreationTime: '{{entity._creationTime}}', + wfDefinitionId: '{{wfDefinitionId}}', + recordId: '{{entityId}}', + recordCreationTime: '{{entity._creationTime}}', metadata: { processedAt: '{{now}}' } } } diff --git a/services/platform/convex/lib/variables/jexl_instance.ts b/services/platform/convex/lib/variables/jexl_instance.ts index ef786387a7..5043b09dc2 100644 --- a/services/platform/convex/lib/variables/jexl_instance.ts +++ b/services/platform/convex/lib/variables/jexl_instance.ts @@ -46,7 +46,13 @@ jexlInstance.addTransform('join', (arr: unknown[], separator = ',') => { }); // Add custom transforms for type conversion -jexlInstance.addTransform('string', (val: unknown) => toString(val)); +jexlInstance.addTransform('string', (val: unknown) => { + // For objects and arrays, use JSON.stringify to get proper string representation + if (val !== null && typeof val === 'object') { + return JSON.stringify(val); + } + return toString(val); +}); jexlInstance.addTransform('number', (val: unknown) => toNumber(val)); jexlInstance.addTransform('boolean', (val: unknown) => Boolean(val)); diff --git a/services/platform/convex/model/organizations/save_default_workflows.ts b/services/platform/convex/model/organizations/save_default_workflows.ts index 94b88d6982..86fcaa18c8 100644 --- a/services/platform/convex/model/organizations/save_default_workflows.ts +++ b/services/platform/convex/model/organizations/save_default_workflows.ts @@ -26,11 +26,11 @@ interface SaveDefaultWorkflowsArgs { * Save default workflows for a new organization * * This includes: - * - Document RAG Sync (scheduled every hour) + * - Document RAG Sync (scheduled every 20 minutes) * - OneDrive Sync (scheduled every hour) - * - Product RAG Sync (scheduled every hour) - * - Website Pages RAG Sync (scheduled every hour) - * - Customer RAG Sync (scheduled every hour) + * - Product RAG Sync (scheduled every 20 minutes) + * - Website Pages RAG Sync (scheduled every 20 minutes) + * - Customer RAG Sync (scheduled every 20 minutes) */ export async function saveDefaultWorkflows( ctx: ActionCtx, @@ -46,7 +46,7 @@ export async function saveDefaultWorkflows( const workflowsToSave = [ { workflow: documentRagSync, - schedule: '0 */1 * * *', // Every hour + schedule: '*/20 * * * *', // Every 20 minutes timezone: 'UTC', }, { @@ -56,17 +56,17 @@ export async function saveDefaultWorkflows( }, { workflow: productRagSync, - schedule: '0 */1 * * *', // Every hour + schedule: '*/20 * * * *', // Every 20 minutes timezone: 'UTC', }, { workflow: websitePagesRagSync, - schedule: '0 */1 * * *', // Every hour + schedule: '*/20 * * * *', // Every 20 minutes timezone: 'UTC', }, { workflow: customerRagSync, - schedule: '0 */1 * * *', // Every hour + schedule: '*/20 * * * *', // Every 20 minutes timezone: 'UTC', }, { diff --git a/services/platform/convex/model/wf_executions/create_execution.ts b/services/platform/convex/model/wf_executions/create_execution.ts deleted file mode 100644 index 9f0b252072..0000000000 --- a/services/platform/convex/model/wf_executions/create_execution.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Create execution record - */ - -import type { MutationCtx } from '../../_generated/server'; -import type { Id } from '../../_generated/dataModel'; -// Inline serialization removed. Variables are initialized empty at creation; actual values set from actions. -import { snakeCase } from 'lodash'; -import type { CreateExecutionArgs, ExecutionVariables } from './types'; - -import { createDebugLog } from '../../lib/debug_log'; - -const debugLog = createDebugLog('DEBUG_WORKFLOW', '[Workflow]'); - -export async function createExecution( - ctx: MutationCtx, - args: CreateExecutionArgs, -): Promise> { - // Initialize variables with input data and include organizationId for action steps - const variables: ExecutionVariables = { - ...(typeof args.input === 'object' && args.input !== null - ? args.input - : {}), - organizationId: args.organizationId, - }; - - // Generate workflowSlug from workflow name using snake_case - // This creates a stable identifier for tracking processed entities - const workflowSlug = args.workflowName - ? snakeCase(args.workflowName) - : undefined; - - debugLog('createExecution Creating execution', { - workflowName: args.workflowName, - workflowSlug, - organizationId: args.organizationId, - }); - - // Initialize with empty variables; they will be set via actions using storage-backed serialization - const serialized = JSON.stringify({}); - - return await ctx.db.insert('wfExecutions', { - organizationId: args.organizationId, - wfDefinitionId: args.wfDefinitionId, - status: 'running', - currentStepSlug: '', - startedAt: Date.now(), - updatedAt: Date.now(), - variables: serialized, - // No variablesStorageId at creation time (large payloads should be set from an action later) - input: args.input, - triggeredBy: args.triggeredBy, - triggerData: args.triggerData, - // Store potentially deeply nested configs as JSON strings to avoid Convex nesting limits - workflowConfig: args.workflowConfig - ? JSON.stringify(args.workflowConfig) - : JSON.stringify({}), - stepsConfig: args.stepsConfig - ? JSON.stringify(args.stepsConfig) - : JSON.stringify({}), - workflowSlug, - }); -} diff --git a/services/platform/convex/model/wf_executions/index.ts b/services/platform/convex/model/wf_executions/index.ts index db5cc5ca4c..d73b88b061 100644 --- a/services/platform/convex/model/wf_executions/index.ts +++ b/services/platform/convex/model/wf_executions/index.ts @@ -7,7 +7,6 @@ export * from './types'; // Export business logic functions -export { createExecution } from './create_execution'; export { getExecution } from './get_execution'; export { getRawExecution } from './get_raw_execution'; export { listExecutions } from './list_executions'; diff --git a/services/platform/convex/model/wf_executions/types.ts b/services/platform/convex/model/wf_executions/types.ts index aa9db6e2b6..90f9706e52 100644 --- a/services/platform/convex/model/wf_executions/types.ts +++ b/services/platform/convex/model/wf_executions/types.ts @@ -23,17 +23,6 @@ export interface DeserializedWorkflowExecution variables: ExecutionVariables; } -export interface CreateExecutionArgs { - organizationId: string; - wfDefinitionId: Doc<'wfDefinitions'>['_id']; - input?: unknown; - triggeredBy: string; - triggerData?: unknown; - workflowConfig?: unknown; - stepsConfig?: Record; - workflowName?: string; -} - export interface UpdateExecutionStatusArgs { executionId: Doc<'wfExecutions'>['_id']; status: string; @@ -102,17 +91,6 @@ export interface ListExecutionsArgs { // Convex Validators // ============================================================================= -export const createExecutionArgsValidator = { - organizationId: v.string(), - wfDefinitionId: v.id('wfDefinitions'), - input: v.optional(v.any()), - triggeredBy: v.string(), - triggerData: v.optional(v.any()), - workflowConfig: v.optional(v.any()), - stepsConfig: v.optional(v.record(v.string(), v.any())), - workflowName: v.optional(v.string()), -}; - export const updateExecutionStatusArgsValidator = { executionId: v.id('wfExecutions'), status: v.string(), diff --git a/services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts b/services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts index 588a5b4571..26984d03d8 100644 --- a/services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts +++ b/services/platform/convex/model/workflow_processing_records/find_product_recommendation_by_status.ts @@ -10,7 +10,7 @@ import { findUnprocessedWithCustomQuery } from './helpers/find_unprocessed_with_ export interface FindProductRecommendationByStatusArgs { organizationId: string; - workflowId: string; + wfDefinitionId: string; backoffHours: number; // Number of hours to look back for processing records status: 'pending' | 'approved' | 'rejected'; } @@ -35,7 +35,7 @@ export async function findProductRecommendationByStatus( ctx: QueryCtx, args: FindProductRecommendationByStatusArgs, ): Promise { - const { organizationId, workflowId, backoffHours, status } = args; + const { organizationId, wfDefinitionId, backoffHours, status } = args; // Calculate cutoff timestamp from backoffHours const cutoffDate = new Date(); @@ -45,7 +45,7 @@ export async function findProductRecommendationByStatus( const result = await findUnprocessedWithCustomQuery>(ctx, { organizationId, tableName: 'approvals', - workflowId, + wfDefinitionId, cutoffTimestamp, // Build query with the specified status @@ -78,4 +78,3 @@ export async function findProductRecommendationByStatus( count: result.count, }; } - diff --git a/services/platform/convex/model/workflow_processing_records/find_unprocessed.ts b/services/platform/convex/model/workflow_processing_records/find_unprocessed.ts index 46ca4d12b9..41d709d988 100644 --- a/services/platform/convex/model/workflow_processing_records/find_unprocessed.ts +++ b/services/platform/convex/model/workflow_processing_records/find_unprocessed.ts @@ -36,7 +36,7 @@ import { findUnprocessedWithCustomQuery } from './helpers/find_unprocessed_with_ export interface FindUnprocessedArgs { organizationId: string; tableName: TableName; - workflowId: string; + wfDefinitionId: string; backoffHours: number; // Number of hours to look back for processing records limit?: number; } @@ -56,7 +56,8 @@ export async function findUnprocessed( ctx: QueryCtx, args: FindUnprocessedArgs, ): Promise { - const { organizationId, tableName, workflowId, backoffHours, limit } = args; + const { organizationId, tableName, wfDefinitionId, backoffHours, limit } = + args; // Calculate cutoff timestamp from backoffHours const cutoffDate = new Date(); @@ -67,7 +68,7 @@ export async function findUnprocessed( const result = await findUnprocessedWithCustomQuery(ctx, { organizationId, tableName, - workflowId, + wfDefinitionId, cutoffTimestamp, limit, diff --git a/services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts b/services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts index 3f4abdebbb..50d35b6e4f 100644 --- a/services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts +++ b/services/platform/convex/model/workflow_processing_records/find_unprocessed_open_conversation.ts @@ -11,7 +11,7 @@ import { getLatestConversationMessage } from './helpers/get_latest_conversation_ export interface FindUnprocessedOpenConversationArgs { organizationId: string; - workflowId: string; + wfDefinitionId: string; backoffHours: number; // Number of hours to look back for processing records } @@ -38,7 +38,7 @@ export async function findUnprocessedOpenConversation( ctx: QueryCtx, args: FindUnprocessedOpenConversationArgs, ): Promise { - const { organizationId, workflowId, backoffHours } = args; + const { organizationId, wfDefinitionId, backoffHours } = args; // Calculate cutoff timestamp from backoffHours const cutoffDate = new Date(); @@ -50,7 +50,7 @@ export async function findUnprocessedOpenConversation( { organizationId, tableName: 'conversations', - workflowId, + wfDefinitionId, cutoffTimestamp, // Hook 1: Build your custom query with the right index diff --git a/services/platform/convex/model/workflow_processing_records/helpers/find_unprocessed_with_custom_query.ts b/services/platform/convex/model/workflow_processing_records/helpers/find_unprocessed_with_custom_query.ts index 0196d9cec6..2fe14c5218 100644 --- a/services/platform/convex/model/workflow_processing_records/helpers/find_unprocessed_with_custom_query.ts +++ b/services/platform/convex/model/workflow_processing_records/helpers/find_unprocessed_with_custom_query.ts @@ -17,7 +17,7 @@ * const result = await findUnprocessedWithCustomQuery(ctx, { * organizationId, * tableName: 'conversations', - * workflowId, + * wfDefinitionId, * cutoffTimestamp, * limit: 10, * buildQuery: (resumeFrom) => { @@ -65,7 +65,7 @@ export async function findUnprocessedWithCustomQuery( ctx: QueryCtx, args: FindUnprocessedWithCustomQueryArgs, ): Promise> { - const { organizationId, tableName, workflowId } = args; + const { organizationId, tableName, wfDefinitionId } = args; const limitVal = args.limit ?? 1; if (limitVal < 1 || limitVal > 10) { @@ -78,7 +78,7 @@ export async function findUnprocessedWithCustomQuery( const resumeFrom = await getLatestProcessedCreationTime(ctx, { organizationId, tableName, - workflowId, + wfDefinitionId, }); // First, try starting from the latest processed creation time (resumeFrom) diff --git a/services/platform/convex/model/workflow_processing_records/helpers/get_latest_processed_creation_time.ts b/services/platform/convex/model/workflow_processing_records/helpers/get_latest_processed_creation_time.ts index 3c61f1a198..bb241b29a3 100644 --- a/services/platform/convex/model/workflow_processing_records/helpers/get_latest_processed_creation_time.ts +++ b/services/platform/convex/model/workflow_processing_records/helpers/get_latest_processed_creation_time.ts @@ -1,5 +1,5 @@ /** - * Get the _creationTime of the latest processed document for a workflow. + * Get the _creationTime of the latest processed record for a workflow. * * This is useful for resume optimization - you can start your query from this point * instead of scanning from the beginning. @@ -10,7 +10,7 @@ * const resumeFrom = await getLatestProcessedCreationTime(ctx, { * organizationId, * tableName: 'conversations', - * workflowId + * wfDefinitionId * }); * * // Build query with resume point @@ -35,26 +35,26 @@ import { TableName } from '../types'; export interface GetLatestProcessedCreationTimeArgs { organizationId: string; tableName: TableName; - workflowId: string; + wfDefinitionId: string; } export async function getLatestProcessedCreationTime( ctx: QueryCtx, args: GetLatestProcessedCreationTimeArgs, ): Promise { - const { organizationId, tableName, workflowId } = args; + const { organizationId, tableName, wfDefinitionId } = args; - // Get the latest processed document for this workflow+table + // Get the latest processed record for this workflow+table const latestProcessed = await ctx.db .query('workflowProcessingRecords') - .withIndex('by_org_table_workflow_creationTime', (q) => + .withIndex('by_org_table_wfDefinition_creationTime', (q) => q .eq('organizationId', organizationId) .eq('tableName', tableName) - .eq('workflowId', workflowId), + .eq('wfDefinitionId', wfDefinitionId), ) .order('desc') .first(); - return latestProcessed?.documentCreationTime ?? null; + return latestProcessed?.recordCreationTime ?? null; } diff --git a/services/platform/convex/model/workflow_processing_records/helpers/is_document_processed.ts b/services/platform/convex/model/workflow_processing_records/helpers/is_document_processed.ts index 255afa8047..36faefa903 100644 --- a/services/platform/convex/model/workflow_processing_records/helpers/is_document_processed.ts +++ b/services/platform/convex/model/workflow_processing_records/helpers/is_document_processed.ts @@ -1,8 +1,8 @@ /** - * Check if a document has been processed by a workflow since the cutoff timestamp. + * Check if a record has been processed by a workflow since the cutoff timestamp. * * This helper allows you to write custom index queries and then check processing status - * for individual documents during iteration. + * for individual records during iteration. * * @example * ```typescript @@ -14,15 +14,15 @@ * ) * ) { * // Check if processed - * const isProcessed = await isDocumentProcessed(ctx, { + * const isProcessed = await isRecordProcessed(ctx, { * tableName: 'conversations', - * documentId: String(conv._id), - * workflowId, + * recordId: String(conv._id), + * wfDefinitionId, * cutoffTimestamp * }); * * if (!isProcessed) { - * // Process this document + * // Process this record * } * } * ``` @@ -31,33 +31,33 @@ import { QueryCtx } from '../../../_generated/server'; import { TableName } from '../types'; -export interface IsDocumentProcessedArgs { +export interface IsRecordProcessedArgs { tableName: TableName; - documentId: string; - workflowId: string; + recordId: string; + wfDefinitionId: string; cutoffTimestamp: string; // ISO date string } -export async function isDocumentProcessed( +export async function isRecordProcessed( ctx: QueryCtx, - args: IsDocumentProcessedArgs, + args: IsRecordProcessedArgs, ): Promise { - const { tableName, documentId, workflowId, cutoffTimestamp } = args; + const { tableName, recordId, wfDefinitionId, cutoffTimestamp } = args; // Convert cutoffTimestamp to milliseconds const cutoffMs = new Date(cutoffTimestamp).getTime(); - // Check if this document has been processed since the cutoff + // Check if this record has been processed since the cutoff const processedRecord = await ctx.db .query('workflowProcessingRecords') - .withIndex('by_document', (q) => + .withIndex('by_record', (q) => q .eq('tableName', tableName) - .eq('documentId', documentId) - .eq('workflowId', workflowId), + .eq('recordId', recordId) + .eq('wfDefinitionId', wfDefinitionId), ) .first(); - // Document is processed if a record exists and was processed after cutoff + // Record is processed if a record exists and was processed after cutoff return processedRecord !== null && processedRecord.processedAt >= cutoffMs; } diff --git a/services/platform/convex/model/workflow_processing_records/helpers/run_query.ts b/services/platform/convex/model/workflow_processing_records/helpers/run_query.ts index 416d383107..b1c8e7d93c 100644 --- a/services/platform/convex/model/workflow_processing_records/helpers/run_query.ts +++ b/services/platform/convex/model/workflow_processing_records/helpers/run_query.ts @@ -1,15 +1,15 @@ import { QueryCtx } from '../../../_generated/server'; import { FindUnprocessedWithCustomQueryArgs } from '../types'; -import { isDocumentProcessed } from './is_document_processed'; +import { isRecordProcessed } from './is_document_processed'; /** * Shared helper to run a workflow-processing query starting from a given - * resume point and return matching documents. + * resume point and return matching records. * * This encapsulates the common logic of: * - Calling the caller-provided `buildQuery(startFrom)` * - Iterating candidates in index order - * - Skipping already-processed documents (respecting `cutoffTimestamp`) + * - Skipping already-processed records (respecting `cutoffTimestamp`) * - Applying the optional `additionalFilter` */ export async function runQuery( @@ -20,7 +20,7 @@ export async function runQuery( ): Promise { const { tableName, - workflowId, + wfDefinitionId, cutoffTimestamp, buildQuery, additionalFilter, @@ -32,10 +32,10 @@ export async function runQuery( for await (const doc of candidateIter) { const docId = String((doc as any)._id); - const processed = await isDocumentProcessed(ctx, { + const processed = await isRecordProcessed(ctx, { tableName, - documentId: docId, - workflowId, + recordId: docId, + wfDefinitionId, cutoffTimestamp, }); diff --git a/services/platform/convex/model/workflow_processing_records/index.ts b/services/platform/convex/model/workflow_processing_records/index.ts index 32761385c0..e261a9c9c9 100644 --- a/services/platform/convex/model/workflow_processing_records/index.ts +++ b/services/platform/convex/model/workflow_processing_records/index.ts @@ -35,8 +35,8 @@ export type { } from './find_product_recommendation_by_status'; // Helper functions for custom queries -export { isDocumentProcessed } from './helpers/is_document_processed'; -export type { IsDocumentProcessedArgs } from './helpers/is_document_processed'; +export { isRecordProcessed } from './helpers/is_document_processed'; +export type { IsRecordProcessedArgs } from './helpers/is_document_processed'; export { getLatestProcessedCreationTime } from './helpers/get_latest_processed_creation_time'; export type { GetLatestProcessedCreationTimeArgs } from './helpers/get_latest_processed_creation_time'; diff --git a/services/platform/convex/model/workflow_processing_records/record_processed.ts b/services/platform/convex/model/workflow_processing_records/record_processed.ts index 6a6a2fbacb..279f9a7dbb 100644 --- a/services/platform/convex/model/workflow_processing_records/record_processed.ts +++ b/services/platform/convex/model/workflow_processing_records/record_processed.ts @@ -10,9 +10,9 @@ import { TableName } from './types'; export interface RecordProcessedArgs { organizationId: string; tableName: TableName; - documentId: string; - workflowId: string; - documentCreationTime: number; + recordId: string; + wfDefinitionId: string; + recordCreationTime: number; metadata?: unknown; } @@ -23,20 +23,20 @@ export async function recordProcessed( const { organizationId, tableName, - documentId, - workflowId, - documentCreationTime, + recordId, + wfDefinitionId, + recordCreationTime, metadata, } = args; - // Check if this document has already been recorded for this workflow + // Check if this record has already been recorded for this workflow const existing = await ctx.db .query('workflowProcessingRecords') - .withIndex('by_document', (q) => + .withIndex('by_record', (q) => q .eq('tableName', tableName) - .eq('documentId', documentId) - .eq('workflowId', workflowId), + .eq('recordId', recordId) + .eq('wfDefinitionId', wfDefinitionId), ) .first(); @@ -53,9 +53,9 @@ export async function recordProcessed( return await ctx.db.insert('workflowProcessingRecords', { organizationId, tableName, - documentId, - workflowId, - documentCreationTime, + recordId, + wfDefinitionId, + recordCreationTime, processedAt: Date.now(), metadata, }); diff --git a/services/platform/convex/model/workflow_processing_records/types.ts b/services/platform/convex/model/workflow_processing_records/types.ts index 92fc4bd645..ceb8145399 100644 --- a/services/platform/convex/model/workflow_processing_records/types.ts +++ b/services/platform/convex/model/workflow_processing_records/types.ts @@ -21,7 +21,7 @@ export type TableName = export interface FindUnprocessedWithCustomQueryArgs { organizationId: string; tableName: TableName; - workflowId: string; + wfDefinitionId: string; cutoffTimestamp: string; limit?: number; diff --git a/services/platform/convex/node_only/imap/lib/build_email_message.ts b/services/platform/convex/node_only/imap/lib/build_email_message.ts index a9bebc7595..da0f93c889 100644 --- a/services/platform/convex/node_only/imap/lib/build_email_message.ts +++ b/services/platform/convex/node_only/imap/lib/build_email_message.ts @@ -1,7 +1,7 @@ 'use node'; import type { ParsedMail } from 'mailparser'; -import type { EmailMessage } from '../../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../../workflow/actions/imap/helpers/types'; import extractAddresses from './addresses'; import parseHeaders from './parse_headers'; diff --git a/services/platform/convex/node_only/imap/lib/collect_thread_message_ids.ts b/services/platform/convex/node_only/imap/lib/collect_thread_message_ids.ts index 5751a4fa2d..49a940d200 100644 --- a/services/platform/convex/node_only/imap/lib/collect_thread_message_ids.ts +++ b/services/platform/convex/node_only/imap/lib/collect_thread_message_ids.ts @@ -1,6 +1,6 @@ 'use node'; -import type { EmailMessage } from '../../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../../workflow/actions/imap/helpers/types'; import extractThreadMessageIds from './extract_thread_message_ids'; export default function collectThreadMessageIds( diff --git a/services/platform/convex/node_only/imap/lib/fetch_and_parse_message.ts b/services/platform/convex/node_only/imap/lib/fetch_and_parse_message.ts index 1f4b372dc1..83bce42bc4 100644 --- a/services/platform/convex/node_only/imap/lib/fetch_and_parse_message.ts +++ b/services/platform/convex/node_only/imap/lib/fetch_and_parse_message.ts @@ -2,7 +2,7 @@ import type { ImapFlow } from 'imapflow'; import { simpleParser } from 'mailparser'; -import type { EmailMessage } from '../../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../../workflow/actions/imap/helpers/types'; import { createDebugLog } from '../../../lib/debug_log'; import buildEmailMessage from './build_email_message'; diff --git a/services/platform/convex/node_only/imap/lib/fetch_email_by_uid.ts b/services/platform/convex/node_only/imap/lib/fetch_email_by_uid.ts index f307c7c18c..6c0b8afdef 100644 --- a/services/platform/convex/node_only/imap/lib/fetch_email_by_uid.ts +++ b/services/platform/convex/node_only/imap/lib/fetch_email_by_uid.ts @@ -3,7 +3,7 @@ import type { ImapFlow } from 'imapflow'; import { simpleParser, type ParsedMail } from 'mailparser'; import extractAddresses from './addresses'; -import type { EmailMessage } from '../../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../../workflow/actions/imap/helpers/types'; export interface FetchOptions { includeAttachments: boolean; diff --git a/services/platform/convex/node_only/imap/lib/fetch_messages_from_search_results.ts b/services/platform/convex/node_only/imap/lib/fetch_messages_from_search_results.ts index 5ab5329eb3..e9069be439 100644 --- a/services/platform/convex/node_only/imap/lib/fetch_messages_from_search_results.ts +++ b/services/platform/convex/node_only/imap/lib/fetch_messages_from_search_results.ts @@ -1,7 +1,7 @@ 'use node'; import type { ImapFlow } from 'imapflow'; -import type { EmailMessage } from '../../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../../workflow/actions/imap/helpers/types'; import type { SearchResult } from './find_message_in_folders'; import fetchAndParseMessage from './fetch_and_parse_message'; diff --git a/services/platform/convex/node_only/imap/lib/find_root_message.ts b/services/platform/convex/node_only/imap/lib/find_root_message.ts index 5cd9cb58f3..1af8ac36d0 100644 --- a/services/platform/convex/node_only/imap/lib/find_root_message.ts +++ b/services/platform/convex/node_only/imap/lib/find_root_message.ts @@ -1,6 +1,6 @@ 'use node'; -import type { EmailMessage } from '../../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../../workflow/actions/imap/helpers/types'; import { createDebugLog } from '../../../lib/debug_log'; diff --git a/services/platform/convex/node_only/imap/lib/search_thread_messages.ts b/services/platform/convex/node_only/imap/lib/search_thread_messages.ts index cbbd7fdb26..f3f5d6e8ed 100644 --- a/services/platform/convex/node_only/imap/lib/search_thread_messages.ts +++ b/services/platform/convex/node_only/imap/lib/search_thread_messages.ts @@ -1,7 +1,7 @@ 'use node'; import type { ImapFlow } from 'imapflow'; -import type { EmailMessage } from '../../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../../workflow/actions/imap/helpers/types'; import { createDebugLog } from '../../../lib/debug_log'; import collectThreadMessageIds from './collect_thread_message_ids'; diff --git a/services/platform/convex/node_only/imap/retrieve_imap_emails.ts b/services/platform/convex/node_only/imap/retrieve_imap_emails.ts index ce048d3a90..ca55c5b3da 100644 --- a/services/platform/convex/node_only/imap/retrieve_imap_emails.ts +++ b/services/platform/convex/node_only/imap/retrieve_imap_emails.ts @@ -3,7 +3,7 @@ import { ImapFlow } from 'imapflow'; import { internalAction } from '../../_generated/server'; import { v } from 'convex/values'; -import type { EmailMessage } from '../../workflow/actions/imap/types'; +import type { EmailMessage } from '../../workflow/actions/imap/helpers/types'; import { createDebugLog } from '../../lib/debug_log'; import computeUidToFetch from './lib/compute_uids_to_fetch'; diff --git a/services/platform/convex/predefined_workflows/conversation_auto_reply.ts b/services/platform/convex/predefined_workflows/conversation_auto_reply.ts index 720f34d8a4..c04e9bf1cc 100644 --- a/services/platform/convex/predefined_workflows/conversation_auto_reply.ts +++ b/services/platform/convex/predefined_workflows/conversation_auto_reply.ts @@ -66,7 +66,7 @@ export const conversationAutoReplyWorkflow = { parameters: { operation: 'find_unprocessed_open_conversation', organizationId: '{{organizationId}}', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -439,9 +439,9 @@ Task: As John, generate a warm and professional reply in markdown format that ad operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'conversations', - documentId: '{{currentConversationId}}', - workflowId: '{{workflowId}}', - documentCreationTime: '{{currentConversationCreationTime}}', + recordId: '{{currentConversationId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', + recordCreationTime: '{{currentConversationCreationTime}}', metadata: { needsReply: '{{steps.check_needs_reply.output.data.needs_reply}}', urgency: '{{steps.check_needs_reply.output.data.urgency}}', diff --git a/services/platform/convex/predefined_workflows/customer_rag_sync.ts b/services/platform/convex/predefined_workflows/customer_rag_sync.ts index 0ea6f85a82..ad7f69a51b 100644 --- a/services/platform/convex/predefined_workflows/customer_rag_sync.ts +++ b/services/platform/convex/predefined_workflows/customer_rag_sync.ts @@ -53,7 +53,7 @@ export const customerRagSyncWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -88,12 +88,13 @@ export const customerRagSyncWorkflow = { type: 'rag', parameters: { operation: 'upload_text', + recordId: + '{{steps.find_unprocessed_customer.output.data.documents[0]._id}}', content: '{{steps.find_unprocessed_customer.output.data.documents[0]|string}}', metadata: { _id: '{{steps.find_unprocessed_customer.output.data.documents[0]._id}}', }, - timeout: 600000, // 10 minutes for text upload }, }, nextSteps: { @@ -113,11 +114,11 @@ export const customerRagSyncWorkflow = { operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'customers', - documentId: + recordId: '{{steps.find_unprocessed_customer.output.data.documents[0]._id}}', - documentCreationTime: + recordCreationTime: '{{steps.find_unprocessed_customer.output.data.documents[0]._creationTime}}', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', }, }, nextSteps: { diff --git a/services/platform/convex/predefined_workflows/document_rag_sync.ts b/services/platform/convex/predefined_workflows/document_rag_sync.ts index 68d0d4a523..f37083e712 100644 --- a/services/platform/convex/predefined_workflows/document_rag_sync.ts +++ b/services/platform/convex/predefined_workflows/document_rag_sync.ts @@ -52,7 +52,7 @@ export const documentRagSyncWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'documents', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -87,14 +87,8 @@ export const documentRagSyncWorkflow = { type: 'rag', parameters: { operation: 'upload_document', - documentId: + recordId: '{{steps.find_unprocessed_document.output.data.documents[0]._id}}', - organizationId: '{{organizationId}}', - includeMetadata: '{{includeMetadata}}', - // With async RAG ingestion, this timeout only covers the HTTP request - // to enqueue ingestion plus Convex storage download + upload. Keep - // this comfortably below the overall workflow timeout. - timeout: 120000, }, }, nextSteps: { @@ -136,11 +130,11 @@ export const documentRagSyncWorkflow = { operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'documents', - documentId: + recordId: '{{steps.find_unprocessed_document.output.data.documents[0]._id}}', - documentCreationTime: + recordCreationTime: '{{steps.find_unprocessed_document.output.data.documents[0]._creationTime}}', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', }, }, nextSteps: { diff --git a/services/platform/convex/predefined_workflows/general_customer_status_assessment.ts b/services/platform/convex/predefined_workflows/general_customer_status_assessment.ts index cc8620d939..3e77ab0f31 100644 --- a/services/platform/convex/predefined_workflows/general_customer_status_assessment.ts +++ b/services/platform/convex/predefined_workflows/general_customer_status_assessment.ts @@ -35,7 +35,7 @@ export const generalCustomerStatusAssessmentWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -203,9 +203,9 @@ Return ONLY JSON in the exact shape described in the system prompt.`, operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', - documentId: '{{currentCustomerId}}', - documentCreationTime: '{{currentCustomer._creationTime}}', + wfDefinitionId: '{{rootWfDefinitionId}}', + recordId: '{{currentCustomerId}}', + recordCreationTime: '{{currentCustomer._creationTime}}', metadata: { processedAt: '{{now}}', statusUpdated: true, diff --git a/services/platform/convex/predefined_workflows/general_product_recommendation.ts b/services/platform/convex/predefined_workflows/general_product_recommendation.ts index 5ac0fdfc3b..15a03185ae 100644 --- a/services/platform/convex/predefined_workflows/general_product_recommendation.ts +++ b/services/platform/convex/predefined_workflows/general_product_recommendation.ts @@ -37,7 +37,7 @@ export const generalProductRecommendationWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -251,7 +251,7 @@ Example of a VALID response (do NOT explain it, just return JSON like this): '{{steps.hydrate_recommendations.output.data.recommendations}}', summary: '{{steps.generate_recommendations.output.data.summary}}', generatedAt: '{{now}}', - workflowId: '{{workflowId}}', + workflowId: '{{rootWfDefinitionId}}', }, }, }, @@ -268,9 +268,9 @@ Example of a VALID response (do NOT explain it, just return JSON like this): operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', - documentId: '{{currentCustomerId}}', - documentCreationTime: '{{currentCustomer._creationTime}}', + wfDefinitionId: '{{rootWfDefinitionId}}', + recordId: '{{currentCustomerId}}', + recordCreationTime: '{{currentCustomer._creationTime}}', metadata: { recommendationsGenerated: '{{steps.hydrate_recommendations.output.data.recommendations|length}}', diff --git a/services/platform/convex/predefined_workflows/loopi_customer_status_assessment.ts b/services/platform/convex/predefined_workflows/loopi_customer_status_assessment.ts index 987db6891e..306d0e99fe 100644 --- a/services/platform/convex/predefined_workflows/loopi_customer_status_assessment.ts +++ b/services/platform/convex/predefined_workflows/loopi_customer_status_assessment.ts @@ -78,7 +78,7 @@ export const loopiCustomerStatusAssessmentWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -219,9 +219,9 @@ Analyze the subscription data and provide your determination strictly as JSON wi operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', - documentId: '{{currentCustomerId}}', - documentCreationTime: '{{currentCustomer._creationTime}}', + wfDefinitionId: '{{rootWfDefinitionId}}', + recordId: '{{currentCustomerId}}', + recordCreationTime: '{{currentCustomer._creationTime}}', metadata: { processedAt: '{{now}}', statusUpdated: true, diff --git a/services/platform/convex/predefined_workflows/loopi_product_recommendation.ts b/services/platform/convex/predefined_workflows/loopi_product_recommendation.ts index 4878e8cfe6..d2ea9df559 100644 --- a/services/platform/convex/predefined_workflows/loopi_product_recommendation.ts +++ b/services/platform/convex/predefined_workflows/loopi_product_recommendation.ts @@ -75,7 +75,7 @@ export const loopiProductRecommendationWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -453,7 +453,7 @@ DO NOT use the same confidence score for all recommendations.`, '{{steps.hydrate_recommendations.output.data.recommendations}}', summary: '{{steps.generate_recommendations.output.data.summary}}', generatedAt: '{{now}}', - workflowId: '{{workflowId}}', + workflowId: '{{rootWfDefinitionId}}', }, }, }, @@ -474,9 +474,9 @@ DO NOT use the same confidence score for all recommendations.`, operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'customers', - workflowId: '{{workflowId}}', - documentId: '{{currentCustomerId}}', - documentCreationTime: '{{currentCustomer._creationTime}}', + wfDefinitionId: '{{rootWfDefinitionId}}', + recordId: '{{currentCustomerId}}', + recordCreationTime: '{{currentCustomer._creationTime}}', metadata: { recommendationsGenerated: '{{steps.hydrate_recommendations.output.data.recommendations|length}}', diff --git a/services/platform/convex/predefined_workflows/onedrive_sync.ts b/services/platform/convex/predefined_workflows/onedrive_sync.ts index c4c1e7cc23..1542bae35d 100644 --- a/services/platform/convex/predefined_workflows/onedrive_sync.ts +++ b/services/platform/convex/predefined_workflows/onedrive_sync.ts @@ -62,7 +62,7 @@ export const onedriveSyncWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'onedriveSyncConfigs', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -334,10 +334,10 @@ export const onedriveSyncWorkflow = { operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'onedriveSyncConfigs', - documentId: '{{steps.find_sync_config.output.data.documents[0]._id}}', - documentCreationTime: + recordId: '{{steps.find_sync_config.output.data.documents[0]._id}}', + recordCreationTime: '{{steps.find_sync_config.output.data.documents[0]._creationTime}}', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', }, }, nextSteps: { diff --git a/services/platform/convex/predefined_workflows/product_rag_sync.ts b/services/platform/convex/predefined_workflows/product_rag_sync.ts index 3a9f63fc84..f24be34229 100644 --- a/services/platform/convex/predefined_workflows/product_rag_sync.ts +++ b/services/platform/convex/predefined_workflows/product_rag_sync.ts @@ -53,7 +53,7 @@ export const productRagSyncWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'products', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -88,12 +88,13 @@ export const productRagSyncWorkflow = { type: 'rag', parameters: { operation: 'upload_text', + recordId: + '{{steps.find_unprocessed_product.output.data.documents[0]._id}}', content: '{{steps.find_unprocessed_product.output.data.documents[0]|string}}', metadata: { _id: '{{steps.find_unprocessed_product.output.data.documents[0]._id}}', }, - timeout: 600000, // 10 minutes for text upload }, }, nextSteps: { @@ -113,11 +114,11 @@ export const productRagSyncWorkflow = { operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'products', - documentId: + recordId: '{{steps.find_unprocessed_product.output.data.documents[0]._id}}', - documentCreationTime: + recordCreationTime: '{{steps.find_unprocessed_product.output.data.documents[0]._creationTime}}', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', }, }, nextSteps: { diff --git a/services/platform/convex/predefined_workflows/product_recommendation_email.ts b/services/platform/convex/predefined_workflows/product_recommendation_email.ts index e2a9adaf6e..74ed008ad2 100644 --- a/services/platform/convex/predefined_workflows/product_recommendation_email.ts +++ b/services/platform/convex/predefined_workflows/product_recommendation_email.ts @@ -81,7 +81,7 @@ export const productRecommendationEmailWorkflow = { parameters: { operation: 'find_product_recommendation_by_status', organizationId: '{{organizationId}}', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', status: 'approved', }, @@ -320,9 +320,9 @@ Return only the JSON object with "subject", "body", and "preview".`, operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'approvals', - workflowId: '{{workflowId}}', - documentId: '{{currentApprovalId}}', - documentCreationTime: '{{currentApproval._creationTime}}', + wfDefinitionId: '{{rootWfDefinitionId}}', + recordId: '{{currentApprovalId}}', + recordCreationTime: '{{currentApproval._creationTime}}', metadata: { emailGenerated: true, conversationId: diff --git a/services/platform/convex/predefined_workflows/product_relationship_analysis.ts b/services/platform/convex/predefined_workflows/product_relationship_analysis.ts index 60d03f68e1..2afddaea01 100644 --- a/services/platform/convex/predefined_workflows/product_relationship_analysis.ts +++ b/services/platform/convex/predefined_workflows/product_relationship_analysis.ts @@ -68,7 +68,7 @@ export const productRelationshipAnalysisWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'products', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -474,9 +474,9 @@ BIDIRECTIONAL RELATIONSHIPS: operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'products', - workflowId: '{{workflowId}}', - documentId: '{{sourceProductId}}', - documentCreationTime: '{{sourceProduct._creationTime}}', + wfDefinitionId: '{{rootWfDefinitionId}}', + recordId: '{{sourceProductId}}', + recordCreationTime: '{{sourceProduct._creationTime}}', metadata: { relationshipsFound: '{{steps.analyze_product_relationships.output.data.relationships|length}}', diff --git a/services/platform/convex/predefined_workflows/website_pages_rag_sync.ts b/services/platform/convex/predefined_workflows/website_pages_rag_sync.ts index 5432af479e..a9de6cdd2d 100644 --- a/services/platform/convex/predefined_workflows/website_pages_rag_sync.ts +++ b/services/platform/convex/predefined_workflows/website_pages_rag_sync.ts @@ -47,7 +47,7 @@ export const websitePagesRagSyncWorkflow = { operation: 'find_unprocessed', organizationId: '{{organizationId}}', tableName: 'websitePages', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', backoffHours: '{{backoffHours}}', }, }, @@ -78,6 +78,8 @@ export const websitePagesRagSyncWorkflow = { type: 'rag', parameters: { operation: 'upload_text', + recordId: + '{{steps.find_unprocessed_page.output.data.documents[0]._id}}', content: '{{steps.find_unprocessed_page.output.data.documents[0]|string}}', metadata: { @@ -85,9 +87,6 @@ export const websitePagesRagSyncWorkflow = { '{{steps.find_unprocessed_page.output.data.documents[0].websiteId}}', _id: '{{steps.find_unprocessed_page.output.data.documents[0]._id}}', }, - // Async ingestion means this timeout only covers the HTTP request to - // enqueue the page content, not full cognee processing. - timeout: 120000, }, }, nextSteps: { @@ -105,11 +104,11 @@ export const websitePagesRagSyncWorkflow = { operation: 'record_processed', organizationId: '{{organizationId}}', tableName: 'websitePages', - documentId: + recordId: '{{steps.find_unprocessed_page.output.data.documents[0]._id}}', - documentCreationTime: + recordCreationTime: '{{steps.find_unprocessed_page.output.data.documents[0]._creationTime}}', - workflowId: '{{workflowId}}', + wfDefinitionId: '{{rootWfDefinitionId}}', }, }, nextSteps: { diff --git a/services/platform/convex/schema.ts b/services/platform/convex/schema.ts index 622cf9bc8c..25b09abc95 100644 --- a/services/platform/convex/schema.ts +++ b/services/platform/convex/schema.ts @@ -640,6 +640,7 @@ export default defineSchema({ wfExecutions: defineTable({ organizationId: v.string(), // Better Auth organization ID wfDefinitionId: v.union(v.id('wfDefinitions'), v.string(), v.null()), // references workflow template, inline identifier, or null for inline workflows + rootWfDefinitionId: v.optional(v.id('wfDefinitions')), // Root version of the workflow family (from wfDefinitions.rootVersionId) // Version tracking (for audit and debugging) workflowSlug: v.optional(v.string()), // Stable workflow identifier (snake_case of name) for entity tracking @@ -798,35 +799,35 @@ export default defineSchema({ workflowProcessingRecords: defineTable({ organizationId: v.string(), // Better Auth organization ID tableName: v.string(), // 'customers' | 'products' | 'subscriptions' | etc. - documentId: v.string(), // The _id of the processed document (stored as string for flexibility) - workflowId: v.string(), // Workflow identifier (workflowSlug or workflowId) + recordId: v.string(), // The _id of the processed record (stored as string for flexibility) + wfDefinitionId: v.string(), // Workflow definition identifier (workflowSlug or wfDefinitionId) - // Document metadata at time of processing - documentCreationTime: v.number(), // _creationTime of the processed document + // Record metadata at time of processing + recordCreationTime: v.number(), // _creationTime of the processed record // Processing metadata - processedAt: v.number(), // When this document was processed + processedAt: v.number(), // When this record was processed metadata: v.optional(v.any()), }) - .index('by_org_table_workflow', [ + .index('by_org_table_wfDefinition', [ 'organizationId', 'tableName', - 'workflowId', + 'wfDefinitionId', ]) - .index('by_org_table_workflow_creationTime', [ + .index('by_org_table_wfDefinition_creationTime', [ 'organizationId', 'tableName', - 'workflowId', - 'documentCreationTime', + 'wfDefinitionId', + 'recordCreationTime', ]) - .index('by_org_table_workflow_processedAt', [ + .index('by_org_table_wfDefinition_processedAt', [ 'organizationId', 'tableName', - 'workflowId', + 'wfDefinitionId', 'processedAt', ]) - .index('by_document', ['tableName', 'documentId', 'workflowId']), + .index('by_record', ['tableName', 'recordId', 'wfDefinitionId']), // Message Metadata (for tracking LLM usage, tokens, model info) messageMetadata: defineTable({ diff --git a/services/platform/convex/workflow/actions/approval/approval_action.ts b/services/platform/convex/workflow/actions/approval/approval_action.ts index 1d707d2e55..7250c2a9df 100644 --- a/services/platform/convex/workflow/actions/approval/approval_action.ts +++ b/services/platform/convex/workflow/actions/approval/approval_action.ts @@ -12,13 +12,13 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; import type { Id } from '../../../_generated/dataModel'; -import { createApproval } from './create_approval'; -import { updateApprovalStatus } from './update_approval_status'; -import { getApproval } from './get_approval'; -import { listPendingApprovals } from './list_pending_approvals'; -import { listApprovalsForExecution } from './list_approvals_for_execution'; -import { listPendingApprovalsForExecution } from './list_pending_approvals_for_execution'; -import { getApprovalHistory } from './get_approval_history'; +import { createApproval } from './helpers/create_approval'; +import { updateApprovalStatus } from './helpers/update_approval_status'; +import { getApproval } from './helpers/get_approval'; +import { listPendingApprovals } from './helpers/list_pending_approvals'; +import { listApprovalsForExecution } from './helpers/list_approvals_for_execution'; +import { listPendingApprovalsForExecution } from './helpers/list_pending_approvals_for_execution'; +import { getApprovalHistory } from './helpers/get_approval_history'; export const approvalAction: ActionDefinition<{ operation: diff --git a/services/platform/convex/workflow/actions/approval/create_approval.ts b/services/platform/convex/workflow/actions/approval/helpers/create_approval.ts similarity index 80% rename from services/platform/convex/workflow/actions/approval/create_approval.ts rename to services/platform/convex/workflow/actions/approval/helpers/create_approval.ts index 24bffa9e50..b3c18ec381 100644 --- a/services/platform/convex/workflow/actions/approval/create_approval.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/create_approval.ts @@ -1,8 +1,8 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { CreateApprovalResult, ApprovalPriority } from './types'; -import type { ApprovalResourceType } from '../../../model/approvals/types'; +import type { ApprovalResourceType } from '../../../../model/approvals/types'; export async function createApproval( ctx: ActionCtx, diff --git a/services/platform/convex/workflow/actions/approval/get_approval.ts b/services/platform/convex/workflow/actions/approval/helpers/get_approval.ts similarity index 67% rename from services/platform/convex/workflow/actions/approval/get_approval.ts rename to services/platform/convex/workflow/actions/approval/helpers/get_approval.ts index 280226d2d1..f72d7e9884 100644 --- a/services/platform/convex/workflow/actions/approval/get_approval.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/get_approval.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { GetApprovalResult } from './types'; export async function getApproval( diff --git a/services/platform/convex/workflow/actions/approval/get_approval_history.ts b/services/platform/convex/workflow/actions/approval/helpers/get_approval_history.ts similarity index 81% rename from services/platform/convex/workflow/actions/approval/get_approval_history.ts rename to services/platform/convex/workflow/actions/approval/helpers/get_approval_history.ts index 504b800148..d5bdeeea98 100644 --- a/services/platform/convex/workflow/actions/approval/get_approval_history.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/get_approval_history.ts @@ -1,7 +1,7 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; import type { ListApprovalsResult } from './types'; -import type { ApprovalResourceType } from '../../../model/approvals/types'; +import type { ApprovalResourceType } from '../../../../model/approvals/types'; const VALID_RESOURCE_TYPES: ApprovalResourceType[] = [ 'conversations', diff --git a/services/platform/convex/workflow/actions/approval/list_approvals_for_execution.ts b/services/platform/convex/workflow/actions/approval/helpers/list_approvals_for_execution.ts similarity index 72% rename from services/platform/convex/workflow/actions/approval/list_approvals_for_execution.ts rename to services/platform/convex/workflow/actions/approval/helpers/list_approvals_for_execution.ts index 8ffad6fde3..dc26c16803 100644 --- a/services/platform/convex/workflow/actions/approval/list_approvals_for_execution.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/list_approvals_for_execution.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { ListApprovalsResult } from './types'; export async function listApprovalsForExecution( @@ -22,4 +22,3 @@ export async function listApprovalsForExecution( count: approvals.length, }; } - diff --git a/services/platform/convex/workflow/actions/approval/list_pending_approvals.ts b/services/platform/convex/workflow/actions/approval/helpers/list_pending_approvals.ts similarity index 83% rename from services/platform/convex/workflow/actions/approval/list_pending_approvals.ts rename to services/platform/convex/workflow/actions/approval/helpers/list_pending_approvals.ts index 051a0be3b1..2015984f6a 100644 --- a/services/platform/convex/workflow/actions/approval/list_pending_approvals.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/list_pending_approvals.ts @@ -1,7 +1,7 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; import type { ListApprovalsResult } from './types'; -import type { ApprovalResourceType } from '../../../model/approvals/types'; +import type { ApprovalResourceType } from '../../../../model/approvals/types'; const VALID_RESOURCE_TYPES: ApprovalResourceType[] = [ 'conversations', diff --git a/services/platform/convex/workflow/actions/approval/list_pending_approvals_for_execution.ts b/services/platform/convex/workflow/actions/approval/helpers/list_pending_approvals_for_execution.ts similarity index 73% rename from services/platform/convex/workflow/actions/approval/list_pending_approvals_for_execution.ts rename to services/platform/convex/workflow/actions/approval/helpers/list_pending_approvals_for_execution.ts index 8c800341e9..3eeea9f155 100644 --- a/services/platform/convex/workflow/actions/approval/list_pending_approvals_for_execution.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/list_pending_approvals_for_execution.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { ListApprovalsResult } from './types'; export async function listPendingApprovalsForExecution( @@ -22,4 +22,3 @@ export async function listPendingApprovalsForExecution( count: approvals.length, }; } - diff --git a/services/platform/convex/workflow/actions/approval/types.ts b/services/platform/convex/workflow/actions/approval/helpers/types.ts similarity index 91% rename from services/platform/convex/workflow/actions/approval/types.ts rename to services/platform/convex/workflow/actions/approval/helpers/types.ts index b6137c3288..d00df67255 100644 --- a/services/platform/convex/workflow/actions/approval/types.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/types.ts @@ -1,5 +1,5 @@ -import type { Id } from '../../../_generated/dataModel'; -import type { ApprovalResourceType } from '../../../model/approvals/types'; +import type { Id } from '../../../../_generated/dataModel'; +import type { ApprovalResourceType } from '../../../../model/approvals/types'; export type ApprovalStatus = 'pending' | 'approved' | 'rejected'; export type ApprovalPriority = 'low' | 'medium' | 'high' | 'urgent'; diff --git a/services/platform/convex/workflow/actions/approval/update_approval_status.ts b/services/platform/convex/workflow/actions/approval/helpers/update_approval_status.ts similarity index 78% rename from services/platform/convex/workflow/actions/approval/update_approval_status.ts rename to services/platform/convex/workflow/actions/approval/helpers/update_approval_status.ts index 6086ce4591..f14ad56f02 100644 --- a/services/platform/convex/workflow/actions/approval/update_approval_status.ts +++ b/services/platform/convex/workflow/actions/approval/helpers/update_approval_status.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { UpdateApprovalStatusResult, ApprovalStatus } from './types'; export async function updateApprovalStatus( diff --git a/services/platform/convex/workflow/actions/conversation/conversation_action.ts b/services/platform/convex/workflow/actions/conversation/conversation_action.ts index 8a11525fd0..1362baf27e 100644 --- a/services/platform/convex/workflow/actions/conversation/conversation_action.ts +++ b/services/platform/convex/workflow/actions/conversation/conversation_action.ts @@ -13,15 +13,15 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; import type { Id } from '../../../_generated/dataModel'; -import { createConversation } from './create_conversation'; -import { getConversationById } from './get_conversation_by_id'; -import { queryConversations } from './query_conversations'; -import { queryConversationMessages } from './query_conversation_messages'; -import { queryLatestMessageByDeliveryState } from './query_latest_message_by_delivery_state'; -import { updateConversations } from './update_conversations'; -import { createConversationFromEmail } from './create_conversation_from_email'; -import { createConversationFromSentEmail } from './create_conversation_from_sent_email'; -import type { ConversationStatus } from './types'; +import { createConversation } from './helpers/create_conversation'; +import { getConversationById } from './helpers/get_conversation_by_id'; +import { queryConversations } from './helpers/query_conversations'; +import { queryConversationMessages } from './helpers/query_conversation_messages'; +import { queryLatestMessageByDeliveryState } from './helpers/query_latest_message_by_delivery_state'; +import { updateConversations } from './helpers/update_conversations'; +import { createConversationFromEmail } from './helpers/create_conversation_from_email'; +import { createConversationFromSentEmail } from './helpers/create_conversation_from_sent_email'; +import type { ConversationStatus } from './helpers/types'; export const conversationAction: ActionDefinition<{ operation: diff --git a/services/platform/convex/workflow/actions/conversation/helpers/add_message_to_conversation.ts b/services/platform/convex/workflow/actions/conversation/helpers/add_message_to_conversation.ts index ec8ab1f13e..4204d1fb02 100644 --- a/services/platform/convex/workflow/actions/conversation/helpers/add_message_to_conversation.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/add_message_to_conversation.ts @@ -1,7 +1,7 @@ import type { ActionCtx } from '../../../../_generated/server'; import { internal } from '../../../../_generated/api'; import type { Id } from '../../../../_generated/dataModel'; -import type { EmailType } from '../types'; +import type { EmailType } from './types'; import { buildEmailMetadata } from './build_email_metadata'; /** diff --git a/services/platform/convex/workflow/actions/conversation/helpers/build_conversation_metadata.ts b/services/platform/convex/workflow/actions/conversation/helpers/build_conversation_metadata.ts index 0f7ab2135b..af14e453be 100644 --- a/services/platform/convex/workflow/actions/conversation/helpers/build_conversation_metadata.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/build_conversation_metadata.ts @@ -1,4 +1,4 @@ -import type { EmailType } from '../types'; +import type { EmailType } from './types'; /** * Build conversation metadata object diff --git a/services/platform/convex/workflow/actions/conversation/helpers/build_email_metadata.ts b/services/platform/convex/workflow/actions/conversation/helpers/build_email_metadata.ts index deeaa19a09..8281504320 100644 --- a/services/platform/convex/workflow/actions/conversation/helpers/build_email_metadata.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/build_email_metadata.ts @@ -1,4 +1,4 @@ -import type { EmailType } from '../types'; +import type { EmailType } from './types'; /** * Build rich metadata object for email message diff --git a/services/platform/convex/workflow/actions/conversation/helpers/build_initial_message.ts b/services/platform/convex/workflow/actions/conversation/helpers/build_initial_message.ts index 56b1ab5571..6c81fe10e7 100644 --- a/services/platform/convex/workflow/actions/conversation/helpers/build_initial_message.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/build_initial_message.ts @@ -1,4 +1,4 @@ -import type { EmailType } from '../types'; +import type { EmailType } from './types'; import { buildEmailMetadata } from './build_email_metadata'; /** diff --git a/services/platform/convex/workflow/actions/conversation/create_conversation.ts b/services/platform/convex/workflow/actions/conversation/helpers/create_conversation.ts similarity index 85% rename from services/platform/convex/workflow/actions/conversation/create_conversation.ts rename to services/platform/convex/workflow/actions/conversation/helpers/create_conversation.ts index ec1818a620..b2fe18304e 100644 --- a/services/platform/convex/workflow/actions/conversation/create_conversation.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/create_conversation.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { ConversationStatus } from './types'; export async function createConversation( @@ -39,3 +39,4 @@ export async function createConversation( timestamp: Date.now(), }; } + diff --git a/services/platform/convex/workflow/actions/conversation/create_conversation_from_email.ts b/services/platform/convex/workflow/actions/conversation/helpers/create_conversation_from_email.ts similarity index 91% rename from services/platform/convex/workflow/actions/conversation/create_conversation_from_email.ts rename to services/platform/convex/workflow/actions/conversation/helpers/create_conversation_from_email.ts index 22623bcd80..e1096c6908 100644 --- a/services/platform/convex/workflow/actions/conversation/create_conversation_from_email.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/create_conversation_from_email.ts @@ -1,16 +1,16 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { EmailType, ConversationStatus } from './types'; -import { checkMessageExists } from './helpers/check_message_exists'; -import { checkConversationExists } from './helpers/check_conversation_exists'; -import { findOrCreateCustomerFromEmail } from './helpers/find_or_create_customer_from_email'; -import { addMessageToConversation } from './helpers/add_message_to_conversation'; -import { updateMessage } from './helpers/update_message'; -import { buildInitialMessage } from './helpers/build_initial_message'; -import { buildConversationMetadata } from './helpers/build_conversation_metadata'; - -import { createDebugLog } from '../../../lib/debug_log'; +import { checkMessageExists } from './check_message_exists'; +import { checkConversationExists } from './check_conversation_exists'; +import { findOrCreateCustomerFromEmail } from './find_or_create_customer_from_email'; +import { addMessageToConversation } from './add_message_to_conversation'; +import { updateMessage } from './update_message'; +import { buildInitialMessage } from './build_initial_message'; +import { buildConversationMetadata } from './build_conversation_metadata'; + +import { createDebugLog } from '../../../../lib/debug_log'; const debugLog = createDebugLog('DEBUG_CONVERSATIONS', '[Conversations]'); diff --git a/services/platform/convex/workflow/actions/conversation/create_conversation_from_sent_email.ts b/services/platform/convex/workflow/actions/conversation/helpers/create_conversation_from_sent_email.ts similarity index 87% rename from services/platform/convex/workflow/actions/conversation/create_conversation_from_sent_email.ts rename to services/platform/convex/workflow/actions/conversation/helpers/create_conversation_from_sent_email.ts index d76206590a..74e8954128 100644 --- a/services/platform/convex/workflow/actions/conversation/create_conversation_from_sent_email.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/create_conversation_from_sent_email.ts @@ -1,16 +1,16 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { EmailType, ConversationStatus } from './types'; -import { checkMessageExists } from './helpers/check_message_exists'; -import { findOrCreateCustomerFromEmail } from './helpers/find_or_create_customer_from_email'; -import { findRelatedConversation } from './helpers/find_related_conversation'; -import { addMessageToConversation } from './helpers/add_message_to_conversation'; -import { updateMessage } from './helpers/update_message'; -import { buildInitialMessage } from './helpers/build_initial_message'; -import { buildConversationMetadata } from './helpers/build_conversation_metadata'; +import { checkMessageExists } from './check_message_exists'; +import { findOrCreateCustomerFromEmail } from './find_or_create_customer_from_email'; +import { findRelatedConversation } from './find_related_conversation'; +import { addMessageToConversation } from './add_message_to_conversation'; +import { updateMessage } from './update_message'; +import { buildInitialMessage } from './build_initial_message'; +import { buildConversationMetadata } from './build_conversation_metadata'; -import { createDebugLog } from '../../../lib/debug_log'; +import { createDebugLog } from '../../../../lib/debug_log'; const debugLog = createDebugLog('DEBUG_CONVERSATIONS', '[Conversations]'); @@ -47,6 +47,26 @@ export async function createConversationFromSentEmail( rootEmail.messageId, ); + // Helper: does an address list include a given email? + const listIncludes = ( + list: Array<{ address: string; name?: string }> | undefined, + addr: string | undefined, + ) => + !!addr && + !!list?.some((x) => x.address?.toLowerCase() === addr.toLowerCase()); + + // Collect all unique addresses from the thread + const allAddresses = new Set(); + for (const email of emailsArray) { + if (email.from?.[0]?.address) + allAddresses.add(email.from[0].address.toLowerCase()); + if (email.to?.[0]?.address) + allAddresses.add(email.to[0].address.toLowerCase()); + } + + // Prefer explicit accountEmail if provided (most reliable) + const explicitAgent = params.accountEmail?.toLowerCase(); + if (existing) { debugLog( 'create_from_sent_email Root conversation already exists:', @@ -77,24 +97,6 @@ export async function createConversationFromSentEmail( continue; } - // Determine customer email (reuse logic from below) - const explicitAgent = params.accountEmail?.toLowerCase(); - const listIncludes = ( - list: Array<{ address: string; name?: string }> | undefined, - addr: string | undefined, - ) => - !!addr && - !!list?.some((x) => x.address?.toLowerCase() === addr.toLowerCase()); - - // Collect all unique addresses from the thread - const allAddresses = new Set(); - for (const threadEmail of emailsArray) { - if (threadEmail.from?.[0]?.address) - allAddresses.add(threadEmail.from[0].address.toLowerCase()); - if (threadEmail.to?.[0]?.address) - allAddresses.add(threadEmail.to[0].address.toLowerCase()); - } - // Determine customer email let customerEmail: string | undefined; let accountEmailLower = explicitAgent; @@ -164,27 +166,6 @@ export async function createConversationFromSentEmail( }; } - // Determine the customer email from the thread - // Prefer explicit accountEmail if provided (most reliable) - const explicitAgent = params.accountEmail?.toLowerCase(); - - // Helper: does an address list include a given email? - const listIncludes = ( - list: Array<{ address: string; name?: string }> | undefined, - addr: string | undefined, - ) => - !!addr && - !!list?.some((x) => x.address?.toLowerCase() === addr.toLowerCase()); - - // Collect all unique addresses from the thread - const allAddresses = new Set(); - for (const email of emailsArray) { - if (email.from?.[0]?.address) - allAddresses.add(email.from[0].address.toLowerCase()); - if (email.to?.[0]?.address) - allAddresses.add(email.to[0].address.toLowerCase()); - } - // Determine agent and customer let accountEmailLower = explicitAgent; let customerEmail: string | undefined; diff --git a/services/platform/convex/workflow/actions/conversation/helpers/find_or_create_customer_from_email.ts b/services/platform/convex/workflow/actions/conversation/helpers/find_or_create_customer_from_email.ts index 4df1cd22b3..45400c7ecb 100644 --- a/services/platform/convex/workflow/actions/conversation/helpers/find_or_create_customer_from_email.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/find_or_create_customer_from_email.ts @@ -1,7 +1,7 @@ import type { ActionCtx } from '../../../../_generated/server'; import { internal } from '../../../../_generated/api'; import type { Id } from '../../../../_generated/dataModel'; -import type { EmailType } from '../types'; +import type { EmailType } from './types'; /** * Find or create customer based on email direction diff --git a/services/platform/convex/workflow/actions/conversation/helpers/find_related_conversation.ts b/services/platform/convex/workflow/actions/conversation/helpers/find_related_conversation.ts index b0a09ef1a7..abdcdda6da 100644 --- a/services/platform/convex/workflow/actions/conversation/helpers/find_related_conversation.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/find_related_conversation.ts @@ -1,6 +1,6 @@ import type { ActionCtx } from '../../../../_generated/server'; import type { Id } from '../../../../_generated/dataModel'; -import type { EmailType } from '../types'; +import type { EmailType } from './types'; import { checkMessageExists } from './check_message_exists'; /** diff --git a/services/platform/convex/workflow/actions/conversation/get_conversation_by_id.ts b/services/platform/convex/workflow/actions/conversation/helpers/get_conversation_by_id.ts similarity index 69% rename from services/platform/convex/workflow/actions/conversation/get_conversation_by_id.ts rename to services/platform/convex/workflow/actions/conversation/helpers/get_conversation_by_id.ts index 10c0bb535c..f4af96c8a5 100644 --- a/services/platform/convex/workflow/actions/conversation/get_conversation_by_id.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/get_conversation_by_id.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; export async function getConversationById( ctx: ActionCtx, diff --git a/services/platform/convex/workflow/actions/conversation/query_conversation_messages.ts b/services/platform/convex/workflow/actions/conversation/helpers/query_conversation_messages.ts similarity index 82% rename from services/platform/convex/workflow/actions/conversation/query_conversation_messages.ts rename to services/platform/convex/workflow/actions/conversation/helpers/query_conversation_messages.ts index cb49e765cc..9f7d2861ee 100644 --- a/services/platform/convex/workflow/actions/conversation/query_conversation_messages.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/query_conversation_messages.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { QueryResult } from './types'; export async function queryConversationMessages( @@ -36,3 +36,4 @@ export async function queryConversationMessages( timestamp: Date.now(), }; } + diff --git a/services/platform/convex/workflow/actions/conversation/query_conversations.ts b/services/platform/convex/workflow/actions/conversation/helpers/query_conversations.ts similarity index 84% rename from services/platform/convex/workflow/actions/conversation/query_conversations.ts rename to services/platform/convex/workflow/actions/conversation/helpers/query_conversations.ts index 0663890eaa..f91d616b3d 100644 --- a/services/platform/convex/workflow/actions/conversation/query_conversations.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/query_conversations.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { QueryResult, ConversationStatus } from './types'; export async function queryConversations( @@ -40,3 +40,4 @@ export async function queryConversations( timestamp: Date.now(), }; } + diff --git a/services/platform/convex/workflow/actions/conversation/query_latest_message_by_delivery_state.ts b/services/platform/convex/workflow/actions/conversation/helpers/query_latest_message_by_delivery_state.ts similarity index 80% rename from services/platform/convex/workflow/actions/conversation/query_latest_message_by_delivery_state.ts rename to services/platform/convex/workflow/actions/conversation/helpers/query_latest_message_by_delivery_state.ts index 1edd432119..8777bdfdf1 100644 --- a/services/platform/convex/workflow/actions/conversation/query_latest_message_by_delivery_state.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/query_latest_message_by_delivery_state.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import type { Id } from '../../../_generated/dataModel'; -import { internal } from '../../../_generated/api'; +import type { ActionCtx } from '../../../../_generated/server'; +import type { Id } from '../../../../_generated/dataModel'; +import { internal } from '../../../../_generated/api'; export async function queryLatestMessageByDeliveryState( ctx: ActionCtx, @@ -29,3 +29,4 @@ export async function queryLatestMessageByDeliveryState( timestamp: Date.now(), }; } + diff --git a/services/platform/convex/workflow/actions/conversation/types.ts b/services/platform/convex/workflow/actions/conversation/helpers/types.ts similarity index 93% rename from services/platform/convex/workflow/actions/conversation/types.ts rename to services/platform/convex/workflow/actions/conversation/helpers/types.ts index c1a333e0a6..f97914d818 100644 --- a/services/platform/convex/workflow/actions/conversation/types.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/types.ts @@ -1,4 +1,4 @@ -import type { Id } from '../../../_generated/dataModel'; +import type { Id } from '../../../../_generated/dataModel'; export type UpdateConversationsResult = { success: boolean; diff --git a/services/platform/convex/workflow/actions/conversation/update_conversations.ts b/services/platform/convex/workflow/actions/conversation/helpers/update_conversations.ts similarity index 82% rename from services/platform/convex/workflow/actions/conversation/update_conversations.ts rename to services/platform/convex/workflow/actions/conversation/helpers/update_conversations.ts index d7d54a6530..5e7a9df0ec 100644 --- a/services/platform/convex/workflow/actions/conversation/update_conversations.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/update_conversations.ts @@ -1,6 +1,6 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; import type { UpdateConversationsResult, ConversationStatus } from './types'; export async function updateConversations( @@ -32,3 +32,4 @@ export async function updateConversations( timestamp: Date.now(), }; } + diff --git a/services/platform/convex/workflow/actions/conversation/helpers/update_message.ts b/services/platform/convex/workflow/actions/conversation/helpers/update_message.ts index 4400bf46f8..5577be44ec 100644 --- a/services/platform/convex/workflow/actions/conversation/helpers/update_message.ts +++ b/services/platform/convex/workflow/actions/conversation/helpers/update_message.ts @@ -1,7 +1,7 @@ import type { ActionCtx } from '../../../../_generated/server'; import { internal } from '../../../../_generated/api'; import type { Id } from '../../../../_generated/dataModel'; -import type { EmailType } from '../types'; +import type { EmailType } from './types'; import { buildEmailMetadata } from './build_email_metadata'; /** diff --git a/services/platform/convex/workflow/actions/crawler/crawler_action.ts b/services/platform/convex/workflow/actions/crawler/crawler_action.ts index f7435fae81..11d7badd06 100644 --- a/services/platform/convex/workflow/actions/crawler/crawler_action.ts +++ b/services/platform/convex/workflow/actions/crawler/crawler_action.ts @@ -5,7 +5,7 @@ import type { CrawlerResult, DiscoverUrlsResult, FetchUrlsResult, -} from './types'; +} from './helpers/types'; import { createDebugLog } from '../../../lib/debug_log'; diff --git a/services/platform/convex/workflow/actions/crawler/types.ts b/services/platform/convex/workflow/actions/crawler/helpers/types.ts similarity index 99% rename from services/platform/convex/workflow/actions/crawler/types.ts rename to services/platform/convex/workflow/actions/crawler/helpers/types.ts index 906cd4e064..4077c683ab 100644 --- a/services/platform/convex/workflow/actions/crawler/types.ts +++ b/services/platform/convex/workflow/actions/crawler/helpers/types.ts @@ -53,3 +53,4 @@ export interface FetchUrlsResult { structured_data?: Record; }>; } + diff --git a/services/platform/convex/workflow/actions/imap/README.md b/services/platform/convex/workflow/actions/imap/README.md deleted file mode 100644 index 75b085232b..0000000000 --- a/services/platform/convex/workflow/actions/imap/README.md +++ /dev/null @@ -1,309 +0,0 @@ -# IMAP Email Retrieval Action - -This action allows you to retrieve emails from any IMAP-compatible email server (Gmail, Outlook, Yahoo, etc.) within your workflows. - -## Features - -- **Multiple Operations**: List, search, or get specific emails -- **Flexible Authentication**: Support for standard IMAP credentials -- **Advanced Search**: Filter emails by sender, recipient, subject, date, flags, etc. -- **Attachment Support**: Optionally include attachment metadata -- **HTML Parsing**: Optionally parse HTML content -- **Mark as Read**: Optionally mark retrieved emails as seen -- **Pagination**: Support for offset and limit to handle large mailboxes - -## Configuration - -### Credentials - -You can provide IMAP credentials in two ways: - -1. **In action parameters** (for testing or single-use): - -```typescript -{ - type: 'imap', - config: { - parameters: { - host: 'imap.gmail.com', - port: 993, - secure: true, - username: 'user@gmail.com', - password: 'app-password', - operation: 'list', - }, - }, -} -``` - -2. **In workflow variables** (recommended for reusable workflows): - -```typescript -{ - workflowConfig: { - config: { - variables: { - imapHost: 'imap.gmail.com', - imapPort: 993, - imapSecure: true, - imapUsername: 'user@gmail.com', - imapPassword: 'app-password', - }, - }, - }, -} -``` - -### Common IMAP Servers - -| Provider | Host | Port | Secure | -| ------------------ | --------------------- | ---- | ------ | -| Gmail | imap.gmail.com | 993 | true | -| Outlook/Office 365 | outlook.office365.com | 993 | true | -| Yahoo | imap.mail.yahoo.com | 993 | true | -| iCloud | imap.mail.me.com | 993 | true | - -**Note for Gmail**: You need to use an [App Password](https://support.google.com/accounts/answer/185833) instead of your regular password. - -## Operations - -### 1. List Emails - -Retrieve the most recent emails from a mailbox: - -```typescript -{ - type: 'imap', - config: { - parameters: { - operation: 'list', - mailbox: 'INBOX', // Optional, defaults to 'INBOX' - limit: 50, // Optional, defaults to 50 - offset: 0, // Optional, defaults to 0 - markAsSeen: false, // Optional, defaults to false - includeAttachments: true, // Optional, defaults to false - parseHtml: true, // Optional, defaults to false - }, - }, -} -``` - -### 2. Search Emails - -Search for emails matching specific criteria: - -```typescript -{ - type: 'imap', - config: { - parameters: { - operation: 'search', - searchCriteria: { - from: 'sender@example.com', // Optional - to: 'recipient@example.com', // Optional - subject: 'Invoice', // Optional - since: '2024-01-01', // Optional, ISO date string - before: '2024-12-31', // Optional, ISO date string - unseen: true, // Optional, only unread emails - flagged: false, // Optional - }, - limit: 100, - markAsSeen: false, - includeAttachments: true, - }, - }, -} -``` - -### 3. Get Specific Email - -Retrieve a specific email by UID: - -```typescript -{ - type: 'imap', - config: { - parameters: { - operation: 'get', - uid: 12345, // Required for 'get' operation - markAsSeen: true, - includeAttachments: true, - parseHtml: true, - }, - }, -} -``` - -## Response Format - -The action returns an object with the following structure: - -```typescript -{ - success: true, - operation: 'list' | 'search' | 'get', - mailbox: 'INBOX', - messages: [ - { - uid: 12345, - messageId: '', - from: [{ name: 'John Doe', address: 'john@example.com' }], - to: [{ name: 'Jane Smith', address: 'jane@example.com' }], - cc: [{ name: 'Bob', address: 'bob@example.com' }], // Optional - bcc: [], // Optional - subject: 'Important Email', - date: '2024-01-15T10:30:00.000Z', - text: 'Plain text content...', - html: '...', // Only if parseHtml: true - attachments: [ // Only if includeAttachments: true - { - filename: 'document.pdf', - contentType: 'application/pdf', - size: 102400, - }, - ], - flags: ['\\Seen', '\\Flagged'], - headers: { // Optional - 'x-custom-header': 'value', - }, - }, - ], - count: 1, - duration: 1234, // milliseconds - timestamp: 1705315800000, -} -``` - -## Example Workflows - -### Example 1: Process Unread Emails - -```typescript -{ - workflowConfig: { - name: 'Process Unread Emails', - workflowType: 'predefined', - config: { - variables: { - imapHost: 'imap.gmail.com', - imapPort: 993, - imapSecure: true, - imapUsername: 'support@company.com', - imapPassword: '${secrets.GMAIL_APP_PASSWORD}', - }, - }, - }, - steps: [ - { - id: 'trigger', - type: 'trigger', - config: { type: 'manual' }, - }, - { - id: 'fetch_unread', - type: 'action', - config: { - type: 'imap', - parameters: { - operation: 'search', - searchCriteria: { - unseen: true, - }, - limit: 20, - markAsSeen: true, - includeAttachments: true, - }, - }, - }, - { - id: 'process_emails', - type: 'loop', - config: { - items: '${fetch_unread.messages}', - itemVariable: 'email', - }, - }, - // ... process each email - ], -} -``` - -### Example 2: Search for Invoices - -```typescript -{ - id: 'search_invoices', - type: 'action', - config: { - type: 'imap', - parameters: { - operation: 'search', - searchCriteria: { - from: 'billing@vendor.com', - subject: 'Invoice', - since: '2024-01-01', - unseen: true, - }, - limit: 50, - includeAttachments: true, - }, - }, -} -``` - -## Error Handling - -The action will throw an error if: - -- IMAP credentials are missing or invalid -- Connection to the IMAP server fails -- The specified mailbox doesn't exist -- Required parameters are missing (e.g., `uid` for 'get' operation) -- Search criteria are missing for 'search' operation - -## Security Best Practices - -1. **Use App Passwords**: For Gmail and other providers, use app-specific passwords instead of your main account password -2. **Store Credentials Securely**: Use workflow secrets for passwords: `${secrets.IMAP_PASSWORD}` -3. **Limit Access**: Use dedicated email accounts for automation with minimal permissions -4. **Enable 2FA**: Enable two-factor authentication on your email account -5. **Monitor Usage**: Regularly review which workflows have access to email credentials - -## Performance Considerations - -- **Limit Results**: Use the `limit` parameter to avoid retrieving too many emails at once -- **Use Search**: Use the `search` operation with specific criteria instead of listing all emails -- **Pagination**: Use `offset` and `limit` for pagination when processing large mailboxes -- **Mark as Seen**: Consider using `markAsSeen: true` to avoid processing the same emails multiple times -- **Timeout**: Adjust the `timeout` parameter if you're experiencing connection issues (default: 30000ms) - -## Troubleshooting - -### Connection Timeout - -- Increase the `timeout` parameter -- Check your network connectivity -- Verify the IMAP server address and port - -### Authentication Failed - -- Verify your username and password -- For Gmail, ensure you're using an App Password -- Check if IMAP is enabled in your email account settings - -### No Emails Returned - -- Verify the mailbox name (case-sensitive) -- Check your search criteria -- Ensure there are emails matching your criteria - -## File Structure - -``` -imap/ -├── imap_action.ts # Main action definition -├── types.ts # TypeScript interfaces -├── get_imap_credentials.ts # Credential helper -├── retrieve_imap_emails.ts # IMAP client implementation -└── README.md # This file -``` diff --git a/services/platform/convex/workflow/actions/imap/get_imap_credentials.ts b/services/platform/convex/workflow/actions/imap/helpers/get_imap_credentials.ts similarity index 99% rename from services/platform/convex/workflow/actions/imap/get_imap_credentials.ts rename to services/platform/convex/workflow/actions/imap/helpers/get_imap_credentials.ts index 7e78906bf4..0ed40350af 100644 --- a/services/platform/convex/workflow/actions/imap/get_imap_credentials.ts +++ b/services/platform/convex/workflow/actions/imap/helpers/get_imap_credentials.ts @@ -69,3 +69,4 @@ export function getImapCredentials( accessToken, }; } + diff --git a/services/platform/convex/workflow/actions/imap/types.ts b/services/platform/convex/workflow/actions/imap/helpers/types.ts similarity index 99% rename from services/platform/convex/workflow/actions/imap/types.ts rename to services/platform/convex/workflow/actions/imap/helpers/types.ts index 845567651b..907e65e795 100644 --- a/services/platform/convex/workflow/actions/imap/types.ts +++ b/services/platform/convex/workflow/actions/imap/helpers/types.ts @@ -58,3 +58,4 @@ export interface ImapActionResult { duration: number; timestamp: number; } + diff --git a/services/platform/convex/workflow/actions/imap/imap_action.ts b/services/platform/convex/workflow/actions/imap/imap_action.ts index c4388f4087..f3221ed8a8 100644 --- a/services/platform/convex/workflow/actions/imap/imap_action.ts +++ b/services/platform/convex/workflow/actions/imap/imap_action.ts @@ -1,7 +1,7 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; -import type { ImapActionParams, ImapActionResult } from './types'; -import { getImapCredentials } from './get_imap_credentials'; +import type { ImapActionParams, ImapActionResult } from './helpers/types'; +import { getImapCredentials } from './helpers/get_imap_credentials'; import { internal } from '../../../_generated/api'; export const imapAction: ActionDefinition = { diff --git a/services/platform/convex/workflow/actions/integration/build_secrets_from_integration.ts b/services/platform/convex/workflow/actions/integration/helpers/build_secrets_from_integration.ts similarity index 86% rename from services/platform/convex/workflow/actions/integration/build_secrets_from_integration.ts rename to services/platform/convex/workflow/actions/integration/helpers/build_secrets_from_integration.ts index fe00d18765..a2c5d6519e 100644 --- a/services/platform/convex/workflow/actions/integration/build_secrets_from_integration.ts +++ b/services/platform/convex/workflow/actions/integration/helpers/build_secrets_from_integration.ts @@ -3,9 +3,9 @@ * Maps secretBindings to actual decrypted values */ -import type { Doc } from '../../../_generated/dataModel'; -import type { ActionDefinition } from '../../helpers/nodes/action/types'; -import { internal } from '../../../_generated/api'; +import type { Doc } from '../../../../_generated/dataModel'; +import type { ActionDefinition } from '../../../helpers/nodes/action/types'; +import { internal } from '../../../../_generated/api'; export async function buildSecretsFromIntegration( ctx: Parameters['execute']>[0], diff --git a/services/platform/convex/workflow/actions/integration/integration_action.ts b/services/platform/convex/workflow/actions/integration/integration_action.ts index e294a83573..00e70db66d 100644 --- a/services/platform/convex/workflow/actions/integration/integration_action.ts +++ b/services/platform/convex/workflow/actions/integration/integration_action.ts @@ -15,7 +15,7 @@ import { internal } from '../../../_generated/api'; import type { Doc } from '../../../_generated/dataModel'; import type { IntegrationExecutionResult } from '../../../node_only/integration_sandbox/types'; import { getPredefinedIntegration } from '../../../predefined_integrations'; -import { buildSecretsFromIntegration } from './build_secrets_from_integration'; +import { buildSecretsFromIntegration } from './helpers/build_secrets_from_integration'; import { createDebugLog } from '../../../lib/debug_log'; diff --git a/services/platform/convex/workflow/actions/product/README.md b/services/platform/convex/workflow/actions/product/README.md deleted file mode 100644 index 91d94927ef..0000000000 --- a/services/platform/convex/workflow/actions/product/README.md +++ /dev/null @@ -1,258 +0,0 @@ -# Product Action - -Product-specific workflow actions for safe, specialized operations on product data. - -## Operations - -### 1. create -Create a new product. - -**Required Parameters:** -- `organizationId`: Organization ID -- `name`: Product name - -**Optional Parameters:** -- `description`: Product description -- `imageUrl`: Product image URL -- `stock`: Stock quantity -- `price`: Product price -- `currency`: Currency code -- `category`: Product category -- `tags`: Array of tags -- `status`: Product status (active, inactive, draft, archived) -- `externalId`: External system ID -- `metadata`: Additional metadata - -**Example:** -```typescript -{ - operation: 'create', - organizationId: 'org_123', - name: 'Premium Laptop', - price: 1299, - status: 'active' -} -``` - -### 2. get_by_id -Get a product by its ID. - -**Required Parameters:** -- `productId`: Product ID - -**Example:** -```typescript -{ - operation: 'get_by_id', - productId: 'prod_123' -} -``` - -### 3. query -Query products with pagination and filtering. - -**Required Parameters:** -- `organizationId`: Organization ID -- `paginationOpts`: Pagination options - - `numItems`: Number of items per page - - `cursor`: Pagination cursor (null for first page) - -**Optional Parameters:** -- `externalId`: Filter by external ID -- `status`: Filter by status -- `category`: Filter by category - -**Example:** -```typescript -{ - operation: 'query', - organizationId: 'org_123', - status: 'active', - paginationOpts: { - numItems: 10, - cursor: null - } -} -``` - -### 4. update -Update products with flexible filtering. - -**Required Parameters:** -- Either `productId` OR `organizationId` -- `updates`: Object containing fields to update - -**Optional Parameters:** -- `externalId`: Filter by external ID -- `status`: Filter by status -- `category`: Filter by category - -**Example:** -```typescript -{ - operation: 'update', - productId: 'prod_123', - updates: { - price: 1499, - status: 'active' - } -} -``` - -### 5. filter -Filter products using JEXL expressions. This operation loops through all products in the organization and evaluates the expression against each product. Products that match (expression evaluates to true) are returned. - -**Required Parameters:** -- `organizationId`: Organization ID -- `expression`: JEXL expression to evaluate - -**How it works:** -1. Loops through all products using cursor-based iteration -2. Evaluates the JEXL expression with each product as context -3. Returns products where the expression evaluates to true - -**Expression Context:** -The expression has access to all product fields: -- `_id`: Product ID -- `_creationTime`: Creation timestamp -- `organizationId`: Organization ID -- `name`: Product name -- `description`: Product description -- `imageUrl`: Product image URL -- `stock`: Stock quantity -- `price`: Product price -- `currency`: Currency code -- `category`: Product category -- `tags`: Array of tags -- `status`: Product status -- `externalId`: External system ID -- `metadata`: Additional metadata (can access nested fields) - -**Examples:** - -Filter by price: -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: 'price > 100' -} -``` - -Filter by status: -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: 'status == "active"' -} -``` - -Filter by multiple conditions: -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: 'price > 50 && price < 200 && status == "active"' -} -``` - -Filter by metadata field: -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: 'metadata.featured == true' -} -``` - -Filter by nested metadata: -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: 'metadata.shopify && metadata.shopify.status == "active"' -} -``` - -Filter by tags (array contains): -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: 'tags && tags.includes("premium")' -} -``` - -Filter by stock availability: -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: 'stock > 0 && stock < 10' -} -``` - -Complex filter with logical operators: -```typescript -{ - operation: 'filter', - organizationId: 'org_123', - expression: '(category == "Electronics" || category == "Furniture") && price > 100' -} -``` - -**Return Value:** -```typescript -{ - operation: 'filter', - products: [...], // Array of matching products - count: 5, // Number of matching products - timestamp: 1234567890 -} -``` - -**Performance Notes:** -- Uses cursor-based iteration for efficient processing -- Processes products one at a time to avoid memory issues -- Continues processing even if one product fails evaluation -- Errors are logged but don't stop the entire operation - -## JEXL Expression Syntax - -The filter operation uses JEXL (JavaScript Expression Language) for safe expression evaluation. JEXL supports: - -- **Comparisons**: `>`, `<`, `>=`, `<=`, `==`, `!=` -- **Logical operators**: `&&`, `||`, `!` -- **Math operations**: `+`, `-`, `*`, `/`, `%` -- **Ternary operator**: `condition ? true_value : false_value` -- **Array operations**: `array.includes(value)`, `array.length` -- **Object access**: `object.field`, `object.nested.field` -- **Null checks**: `field != null`, `field != undefined` - -See the [JEXL documentation](../../../../../../lib/variables/DESIGN.md) for more details. - -## Best Practices - -1. **Use indexes when possible**: For simple queries, prefer the `query` operation which uses Convex indexes for better performance. - -2. **Use filter for complex conditions**: Use the `filter` operation when you need to evaluate complex expressions that can't be expressed with simple index queries. - -3. **Test expressions**: Test your JEXL expressions with a small dataset first to ensure they work as expected. - -4. **Handle null values**: Always check for null/undefined when accessing optional fields: - ```typescript - expression: 'metadata && metadata.field == "value"' - ``` - -5. **Use meaningful variable names**: Store filter results in workflow variables with descriptive names: - ```typescript - steps.filter_expensive_products.output.products - ``` - -## See Also - -- [JEXL Expression Guide](../../../../../../lib/variables/DESIGN.md) -- [Product Filter Example Workflow](../../../../../../workflows/examples/product-filter-example.ts) -- [Workflow Developer Guide](../../../../../../docs/workflows/developer-guide.md) - diff --git a/services/platform/convex/workflow/actions/rag/README.md b/services/platform/convex/workflow/actions/rag/README.md deleted file mode 100644 index 2b9d05964b..0000000000 --- a/services/platform/convex/workflow/actions/rag/README.md +++ /dev/null @@ -1,260 +0,0 @@ -# RAG Action - Document Upload to Cognee - -This action uploads documents from the Convex database to the cognee RAG (Retrieval-Augmented Generation) service for semantic search and retrieval. - -## Overview - -The RAG action enables workflows to: - -- Read documents from the Convex `documents` table -- Handle both text content and file storage references -- Upload documents to the cognee RAG service -- Track upload status and results - -## Configuration - -### Environment Variables - -Set these in your `.env` file: - -```bash -# RAG Service URL (default: http://localhost:8001) -RAG_URL=http://localhost:8001 - -# Optional: RAG API Key (if authentication is required) -RAG_API_KEY=your-api-key-here -``` - -### Workflow Variables - -You can also configure RAG settings in workflow variables: - -```typescript -{ - ragServiceUrl: 'http://localhost:8001', - ragApiKey: 'optional-api-key', -} -``` - -## Usage - -### Basic Example - -```typescript -{ - type: 'rag', - config: { - parameters: { - operation: 'upload_document', - documentId: 'doc_123', - organizationId: 'org_456', - }, - }, -} -``` - -### With Custom RAG Service URL - -```typescript -{ - type: 'rag', - config: { - parameters: { - operation: 'upload_document', - documentId: 'doc_123', - organizationId: 'org_456', - ragServiceUrl: 'https://rag.example.com', - }, - }, -} -``` - -### With Advanced Options - -```typescript -{ - type: 'rag', - config: { - parameters: { - operation: 'upload_document', - documentId: 'doc_123', - organizationId: 'org_456', - forceReupload: true, // Re-upload even if already uploaded - includeMetadata: true, // Include document metadata - timeout: 60000, // 60 second timeout - }, - }, -} -``` - -## Parameters - -| Parameter | Type | Required | Description | -| ----------------- | ------- | -------- | --------------------------------------------------- | -| `operation` | string | Yes | Must be `'upload_document'` | -| `documentId` | string | Yes | ID of the document to upload | -| `organizationId` | string | Yes | Organization ID for context | -| `ragServiceUrl` | string | No | RAG service URL (overrides env var) | -| `forceReupload` | boolean | No | Re-upload even if already uploaded (default: false) | -| `includeMetadata` | boolean | No | Include document metadata (default: true) | -| `timeout` | number | No | Request timeout in milliseconds (default: 30000) | - -## Return Value - -The action returns a result object: - -```typescript -{ - success: boolean; - documentId: string; - ragDocumentId?: string; // ID assigned by RAG service - chunksCreated?: number; // Number of chunks created - processingTimeMs?: number; // Time spent uploading - documentType?: 'text' | 'file'; // Type of document uploaded - executionTimeMs?: number; // Total execution time - error?: string; // Error message if failed - timestamp: number; // Timestamp of upload -} -``` - -## Document Types - -### Text Documents - -Documents with a `content` field are uploaded as text: - -```typescript -{ - title: 'My Document', - kind: 'note', - content: 'This is the document content...', - metadata: { /* ... */ } -} -``` - -### File Documents - -Documents with a `fileId` field are uploaded as files: - -```typescript -{ - title: 'my-file.pdf', - kind: 'file', - fileId: 'storage_id_123', - metadata: { - contentType: 'application/pdf', - size: 1024000, - /* ... */ - } -} -``` - -## Metadata - -The action automatically includes the following metadata when uploading: - -```typescript -{ - documentId: string; // Convex document ID - organizationId: string; // Organization ID - title?: string; // Document title - kind?: string; // Document kind - sourceProvider?: string; // Source provider (e.g., 'upload', 'onedrive') - uploadedAt: string; // ISO timestamp -} -``` - -## Error Handling - -The action handles various error scenarios: - -- **Document not found**: Returns error if document doesn't exist -- **Missing content**: Returns error if document has neither content nor fileId -- **File download failed**: Returns error if file URL is invalid or file is deleted -- **RAG service unavailable**: Retries up to 3 times with exponential backoff -- **Network timeout**: Respects the configured timeout parameter - -## Workflow Example - -Here's a complete workflow that uploads documents to RAG: - -```typescript -export default { - workflowConfig: { - name: 'Upload Documents to RAG', - workflowType: 'predefined', - config: { - variables: { - ragServiceUrl: 'http://localhost:8001', - }, - }, - }, - steps: [ - { - id: 'trigger', - type: 'trigger', - config: { triggerType: 'manual' }, - }, - { - id: 'upload_to_rag', - type: 'action', - config: { - actionType: 'rag', - parameters: { - operation: 'upload_document', - documentId: '${trigger.documentId}', - organizationId: '${trigger.organizationId}', - }, - }, - }, - ], -}; -``` - -## Troubleshooting - -### RAG Service URL not configured - -**Error**: "RAG service URL not configured" - -**Solution**: Set `RAG_URL` environment variable or provide `ragServiceUrl` in action parameters. - -### Document not found - -**Error**: "Document not found" - -**Solution**: Verify the `documentId` is correct and the document exists in the database. - -### File URL not available - -**Error**: "File URL not available for document" - -**Solution**: The file may have been deleted from storage. Check if the file still exists in Convex storage. - -### RAG service connection failed - -**Error**: "RAG service error: 502 Bad Gateway" - -**Solution**: Verify the RAG service is running and accessible at the configured URL. - -## Implementation Details - -### File Structure - -``` -rag/ -├── rag_action.ts # Main action definition -├── types.ts # TypeScript interfaces -├── rag_client.ts # HTTP client for RAG service -├── get_rag_config.ts # Configuration helper -├── read_document.ts # Document reading logic -└── README.md # This file -``` - -### Key Features - -- **Retry Logic**: Automatic retries with exponential backoff for transient failures -- **File Handling**: Seamless handling of both text and file documents -- **Metadata Enrichment**: Automatic metadata inclusion for better RAG context -- **Error Tracking**: Detailed error messages for debugging -- **Performance Metrics**: Tracks processing and execution times diff --git a/services/platform/convex/workflow/actions/rag/get_document_info.ts b/services/platform/convex/workflow/actions/rag/helpers/get_document_info.ts similarity index 59% rename from services/platform/convex/workflow/actions/rag/get_document_info.ts rename to services/platform/convex/workflow/actions/rag/helpers/get_document_info.ts index 32b8da20cd..a983eb281c 100644 --- a/services/platform/convex/workflow/actions/rag/get_document_info.ts +++ b/services/platform/convex/workflow/actions/rag/helpers/get_document_info.ts @@ -1,7 +1,7 @@ import type { RagActionParams } from './types'; -import type { ActionCtx } from '../../../_generated/server'; -import { api, internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; +import type { ActionCtx } from '../../../../_generated/server'; +import { api, internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; /** * Document information without downloading content @@ -25,22 +25,25 @@ export async function getDocumentInfo( ctx: ActionCtx, params: Extract, ): Promise { - if (!params.documentId) { - throw new Error('documentId is required'); + if (!params.recordId) { + throw new Error('recordId is required'); } const getDocumentResult = await ctx.runQuery?.( internal.documents.getDocumentById, { - documentId: params.documentId as Id<'documents'>, + documentId: params.recordId as Id<'documents'>, }, ); if (!getDocumentResult) { - throw new Error(`Document not found: ${params.documentId}`); + throw new Error(`Document not found: ${params.recordId}`); } const document = getDocumentResult as Record; + const baseMetadata = + (document.metadata as Record | undefined) || {}; + const organizationId = document.organizationId as string | undefined; // Check if document has file content if (document.fileId) { @@ -49,24 +52,23 @@ export async function getDocumentInfo( }); if (!fileUrl) { - throw new Error( - `File URL not available for document ${params.documentId}`, - ); + throw new Error(`File URL not available for document ${params.recordId}`); } - const metadata = (document.metadata as Record) || {}; return { type: 'file', fileUrl: fileUrl as string, filename: - (metadata.name as string) || (document.title as string) || 'document', + (baseMetadata.name as string) || + (document.title as string) || + 'document', contentType: - (metadata.contentType as string) || 'application/octet-stream', + (baseMetadata.contentType as string) || 'application/octet-stream', metadata: { - documentId: params.documentId, - organizationId: params.organizationId, + recordId: params.recordId, + organizationId, title: document.title as string, - ...metadata, + ...baseMetadata, }, }; } @@ -77,13 +79,14 @@ export async function getDocumentInfo( type: 'text', content: document.content as string, metadata: { - documentId: params.documentId, - organizationId: params.organizationId, + recordId: params.recordId, + organizationId, title: document.title as string, - ...((document.metadata as Record) || {}), + ...baseMetadata, }, }; } - throw new Error(`Document has no content or file: ${params.documentId}`); + throw new Error(`Document has no content or file: ${params.recordId}`); } + diff --git a/services/platform/convex/workflow/actions/rag/get_rag_config.ts b/services/platform/convex/workflow/actions/rag/helpers/get_rag_config.ts similarity index 99% rename from services/platform/convex/workflow/actions/rag/get_rag_config.ts rename to services/platform/convex/workflow/actions/rag/helpers/get_rag_config.ts index 05205bcf52..f7800ac5f7 100644 --- a/services/platform/convex/workflow/actions/rag/get_rag_config.ts +++ b/services/platform/convex/workflow/actions/rag/helpers/get_rag_config.ts @@ -13,3 +13,4 @@ export function getRagConfig(): RagConfig { return { serviceUrl }; } + diff --git a/services/platform/convex/workflow/actions/rag/types.ts b/services/platform/convex/workflow/actions/rag/helpers/types.ts similarity index 84% rename from services/platform/convex/workflow/actions/rag/types.ts rename to services/platform/convex/workflow/actions/rag/helpers/types.ts index 93348b7ff4..797f38872f 100644 --- a/services/platform/convex/workflow/actions/rag/types.ts +++ b/services/platform/convex/workflow/actions/rag/helpers/types.ts @@ -4,17 +4,14 @@ export type RagActionParams = | { operation: 'upload_document'; - documentId: string; - organizationId: string; - forceReupload?: boolean; - includeMetadata?: boolean; - timeout?: number; + recordId: string; } | { operation: 'upload_text'; + /** Optional for upload_text; when provided, also used as endpoint document_id. */ + recordId?: string; content: string; metadata: Record; - timeout?: number; }; /** @@ -34,8 +31,8 @@ export interface DocumentContent { export interface RagUploadResult { /** Whether the request to RAG succeeded (enqueue + basic validation). */ success: boolean; - /** Caller-level document identifier (usually from metadata.documentId). */ - documentId: string; + /** Caller-level record identifier (usually from metadata.recordId). */ + recordId: string; /** Identifier returned by the RAG service for the ingested/queued document. */ ragDocumentId?: string; /** Number of chunks created. For async ingestion this will typically be 0. */ @@ -55,3 +52,4 @@ export interface RagUploadResult { /** Background job identifier if provided by the RAG service. */ jobId?: string; } + diff --git a/services/platform/convex/workflow/actions/rag/upload_file_direct.ts b/services/platform/convex/workflow/actions/rag/helpers/upload_file_direct.ts similarity index 77% rename from services/platform/convex/workflow/actions/rag/upload_file_direct.ts rename to services/platform/convex/workflow/actions/rag/helpers/upload_file_direct.ts index 2326a2775e..0d9c066db7 100644 --- a/services/platform/convex/workflow/actions/rag/upload_file_direct.ts +++ b/services/platform/convex/workflow/actions/rag/helpers/upload_file_direct.ts @@ -1,5 +1,14 @@ import type { RagUploadResult } from './types'; +export interface UploadFileDirectArgs { + ragServiceUrl: string; + fileUrl: string; + filename: string; + contentType: string; + metadata?: Record; + timeoutMs?: number; +} + /** * Upload file document to RAG service by downloading from URL and uploading directly * @@ -7,14 +16,14 @@ import type { RagUploadResult } from './types'; * to the RAG service using multipart/form-data, which is more reliable than * having the RAG service fetch from a URL. */ -export async function uploadFileDirect( - ragServiceUrl: string, - fileUrl: string, - filename: string, - contentType: string, - metadata?: Record, - timeout = 30000, -): Promise { +export async function uploadFileDirect({ + ragServiceUrl, + fileUrl, + filename, + contentType, + metadata, + timeoutMs = 30000, +}: UploadFileDirectArgs): Promise { const startTime = Date.now(); // Step 1: Download file from Convex storage @@ -42,16 +51,16 @@ export async function uploadFileDirect( formData.append('metadata', JSON.stringify(enrichedMetadata)); } - // Add document_id if provided in metadata - if (metadata?.documentId) { - formData.append('document_id', metadata.documentId as string); + // Add document_id if provided in metadata (mapped from recordId) + if (metadata?.recordId) { + formData.append('document_id', metadata.recordId as string); } // Step 3: Upload to RAG service const url = `${ragServiceUrl}/api/v1/documents/upload`; const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeout); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); const response = await fetch(url, { method: 'POST', @@ -74,7 +83,7 @@ export async function uploadFileDirect( return { success: (result.success as boolean | undefined) ?? true, - documentId: (metadata?.documentId as string) || ragDocumentId || 'unknown', + recordId: (metadata?.recordId as string) || ragDocumentId || 'unknown', ragDocumentId, chunksCreated: (result.chunks_created as number) || 0, processingTimeMs: Date.now() - startTime, @@ -83,3 +92,4 @@ export async function uploadFileDirect( jobId, }; } + diff --git a/services/platform/convex/workflow/actions/rag/upload_text_document.ts b/services/platform/convex/workflow/actions/rag/helpers/upload_text_document.ts similarity index 64% rename from services/platform/convex/workflow/actions/rag/upload_text_document.ts rename to services/platform/convex/workflow/actions/rag/helpers/upload_text_document.ts index e5e182df5f..1a450e4dcd 100644 --- a/services/platform/convex/workflow/actions/rag/upload_text_document.ts +++ b/services/platform/convex/workflow/actions/rag/helpers/upload_text_document.ts @@ -1,25 +1,40 @@ import type { RagUploadResult } from './types'; +export interface UploadTextDocumentArgs { + ragServiceUrl: string; + content: string; + metadata?: Record; + /** Optional logical record identifier; sent as `document_id` to the RAG endpoint. */ + recordId?: string; + timeoutMs?: number; +} + /** * Upload text document to RAG service */ -export async function uploadTextDocument( - ragServiceUrl: string, - content: string, - metadata?: Record, - timeout = 30000, -): Promise { +export async function uploadTextDocument({ + ragServiceUrl, + content, + metadata, + recordId, + timeoutMs = 30000, +}: UploadTextDocumentArgs): Promise { const startTime = Date.now(); const url = `${ragServiceUrl}/api/v1/documents`; - const payload = { + const payload: Record = { content, content_type: 'text', metadata: metadata || {}, }; + // If a logical recordId is provided, also use it as the endpoint document_id + if (recordId) { + payload.document_id = recordId; + } + const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeout); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); const response = await fetch(url, { method: 'POST', @@ -45,7 +60,8 @@ export async function uploadTextDocument( return { success: (result.success as boolean | undefined) ?? true, - documentId: (metadata?.documentId as string) || ragDocumentId || 'unknown', + recordId: + recordId || (metadata?.recordId as string) || ragDocumentId || 'unknown', ragDocumentId, chunksCreated: (result.chunks_created as number) || 0, processingTimeMs: Date.now() - startTime, @@ -54,3 +70,4 @@ export async function uploadTextDocument( jobId, }; } + diff --git a/services/platform/convex/workflow/actions/rag/rag_action.ts b/services/platform/convex/workflow/actions/rag/rag_action.ts index f059e39956..d3848c432b 100644 --- a/services/platform/convex/workflow/actions/rag/rag_action.ts +++ b/services/platform/convex/workflow/actions/rag/rag_action.ts @@ -1,10 +1,10 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; -import type { RagActionParams, RagUploadResult } from './types'; -import { getRagConfig } from './get_rag_config'; -import { getDocumentInfo } from './get_document_info'; -import { uploadTextDocument } from './upload_text_document'; -import { uploadFileDirect } from './upload_file_direct'; +import type { RagActionParams, RagUploadResult } from './helpers/types'; +import { getRagConfig } from './helpers/get_rag_config'; +import { getDocumentInfo } from './helpers/get_document_info'; +import { uploadTextDocument } from './helpers/upload_text_document'; +import { uploadFileDirect } from './helpers/upload_file_direct'; export const ragAction: ActionDefinition = { type: 'rag', @@ -15,17 +15,13 @@ export const ragAction: ActionDefinition = { parametersValidator: v.union( v.object({ operation: v.literal('upload_document'), - documentId: v.string(), - organizationId: v.string(), - forceReupload: v.optional(v.boolean()), - includeMetadata: v.optional(v.boolean()), - timeout: v.optional(v.number()), + recordId: v.string(), }), v.object({ operation: v.literal('upload_text'), + recordId: v.optional(v.string()), content: v.string(), metadata: v.any(), - timeout: v.optional(v.number()), }), ), @@ -41,13 +37,13 @@ export const ragAction: ActionDefinition = { let documentType: string; if (processedParams.operation === 'upload_text') { - // Direct text upload - uploadResult = await uploadTextDocument( - ragConfig.serviceUrl, - processedParams.content, - processedParams.metadata, - processedParams.timeout || 30000, - ); + // Direct text upload; metadata is passed through as provided + uploadResult = await uploadTextDocument({ + ragServiceUrl: ragConfig.serviceUrl, + content: processedParams.content, + metadata: processedParams.metadata as Record, + recordId: processedParams.recordId, + }); documentType = 'text'; } else { // Document upload (from documents table) @@ -55,26 +51,20 @@ export const ragAction: ActionDefinition = { if (documentInfo.type === 'text') { // Upload text content directly - uploadResult = await uploadTextDocument( - ragConfig.serviceUrl, - documentInfo.content as string, - processedParams.includeMetadata !== false - ? documentInfo.metadata - : undefined, - processedParams.timeout || 30000, - ); + uploadResult = await uploadTextDocument({ + ragServiceUrl: ragConfig.serviceUrl, + content: documentInfo.content as string, + metadata: documentInfo.metadata, + }); } else { // Upload file directly by downloading from storage and uploading to RAG - uploadResult = await uploadFileDirect( - ragConfig.serviceUrl, - documentInfo.fileUrl as string, - documentInfo.filename || 'document', - documentInfo.contentType || 'application/octet-stream', - processedParams.includeMetadata !== false - ? documentInfo.metadata - : undefined, - processedParams.timeout || 30000, - ); + uploadResult = await uploadFileDirect({ + ragServiceUrl: ragConfig.serviceUrl, + fileUrl: documentInfo.fileUrl as string, + filename: documentInfo.filename || 'document', + contentType: documentInfo.contentType || 'application/octet-stream', + metadata: documentInfo.metadata, + }); } documentType = documentInfo.type; } diff --git a/services/platform/convex/workflow/actions/website/types.ts b/services/platform/convex/workflow/actions/website/helpers/types.ts similarity index 100% rename from services/platform/convex/workflow/actions/website/types.ts rename to services/platform/convex/workflow/actions/website/helpers/types.ts diff --git a/services/platform/convex/workflow/actions/website/website_action.ts b/services/platform/convex/workflow/actions/website/website_action.ts index 55a6690e25..cfdcdac235 100644 --- a/services/platform/convex/workflow/actions/website/website_action.ts +++ b/services/platform/convex/workflow/actions/website/website_action.ts @@ -1,6 +1,6 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; -import type { WebsiteActionParams } from './types'; +import type { WebsiteActionParams } from './helpers/types'; import { internal } from '../../../_generated/api'; import type { Id } from '../../../_generated/dataModel'; diff --git a/services/platform/convex/workflow/actions/websitePages/types.ts b/services/platform/convex/workflow/actions/websitePages/helpers/types.ts similarity index 99% rename from services/platform/convex/workflow/actions/websitePages/types.ts rename to services/platform/convex/workflow/actions/websitePages/helpers/types.ts index 0f0807181d..958c1c2d36 100644 --- a/services/platform/convex/workflow/actions/websitePages/types.ts +++ b/services/platform/convex/workflow/actions/websitePages/helpers/types.ts @@ -27,3 +27,4 @@ export interface WebsitePagesActionResult { success: boolean; timestamp: number; } + diff --git a/services/platform/convex/workflow/actions/websitePages/websitePages_action.ts b/services/platform/convex/workflow/actions/websitePages/websitePages_action.ts index e5c199edac..29dbe94579 100644 --- a/services/platform/convex/workflow/actions/websitePages/websitePages_action.ts +++ b/services/platform/convex/workflow/actions/websitePages/websitePages_action.ts @@ -1,6 +1,6 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; -import type { WebsitePagesActionParams } from './types'; +import type { WebsitePagesActionParams } from './helpers/types'; import { internal } from '../../../_generated/api'; import type { Id } from '../../../_generated/dataModel'; diff --git a/services/platform/convex/workflow/actions/workflow/types.ts b/services/platform/convex/workflow/actions/workflow/helpers/types.ts similarity index 99% rename from services/platform/convex/workflow/actions/workflow/types.ts rename to services/platform/convex/workflow/actions/workflow/helpers/types.ts index 35b0f69678..d7dfa44d5a 100644 --- a/services/platform/convex/workflow/actions/workflow/types.ts +++ b/services/platform/convex/workflow/actions/workflow/helpers/types.ts @@ -23,3 +23,4 @@ export interface UploadAllWorkflowsResult { }>; executionTimeMs?: number; } + diff --git a/services/platform/convex/workflow/actions/workflow/upload_workflows.ts b/services/platform/convex/workflow/actions/workflow/helpers/upload_workflows.ts similarity index 84% rename from services/platform/convex/workflow/actions/workflow/upload_workflows.ts rename to services/platform/convex/workflow/actions/workflow/helpers/upload_workflows.ts index cc0167d1ae..ea1ce0c4c4 100644 --- a/services/platform/convex/workflow/actions/workflow/upload_workflows.ts +++ b/services/platform/convex/workflow/actions/workflow/helpers/upload_workflows.ts @@ -1,9 +1,9 @@ import type { UploadAllWorkflowsResult } from './types'; -import * as workflowExports from '../../../predefined_workflows'; -import { getRagConfig } from '../rag/get_rag_config'; -import { uploadTextDocument } from '../rag/upload_text_document'; +import * as workflowExports from '../../../../predefined_workflows'; +import { getRagConfig } from '../../rag/helpers/get_rag_config'; +import { uploadTextDocument } from '../../rag/helpers/upload_text_document'; -import { createDebugLog } from '../../../lib/debug_log'; +import { createDebugLog } from '../../../../lib/debug_log'; const debugLog = createDebugLog('DEBUG_WORKFLOW', '[Workflow]'); @@ -50,11 +50,11 @@ export async function uploadAllWorkflows( (workflowConfig?.workflowType as string) || 'predefined'; // Upload to RAG service - const result = await uploadTextDocument( - ragConfig.serviceUrl, + const result = await uploadTextDocument({ + ragServiceUrl: ragConfig.serviceUrl, content, - { - documentId: `workflow_${key}`, + metadata: { + recordId: `workflow_${key}`, workflowKey: key, workflowName: workflowName, workflowDescription: workflowDescription, @@ -63,8 +63,8 @@ export async function uploadAllWorkflows( organizationId: organizationId, uploadedAt: Date.now(), }, - timeout, - ); + timeoutMs: timeout, + }); debugLog( `Workflow RAG Upload ✓ Successfully uploaded: ${key} (${result.chunksCreated} chunks)`, @@ -89,3 +89,4 @@ export async function uploadAllWorkflows( details, }; } + diff --git a/services/platform/convex/workflow/actions/workflow/workflow_action.ts b/services/platform/convex/workflow/actions/workflow/workflow_action.ts index 05af9bf26e..2c67356463 100644 --- a/services/platform/convex/workflow/actions/workflow/workflow_action.ts +++ b/services/platform/convex/workflow/actions/workflow/workflow_action.ts @@ -1,7 +1,7 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; -import type { WorkflowActionParams } from './types'; -import { uploadAllWorkflows } from './upload_workflows'; +import type { WorkflowActionParams } from './helpers/types'; +import { uploadAllWorkflows } from './helpers/upload_workflows'; export const workflowAction: ActionDefinition = { type: 'workflow', diff --git a/services/platform/convex/workflow/actions/workflow_processing_records/find_product_recommendation_by_status.ts b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts similarity index 73% rename from services/platform/convex/workflow/actions/workflow_processing_records/find_product_recommendation_by_status.ts rename to services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts index da55283091..6b44a80852 100644 --- a/services/platform/convex/workflow/actions/workflow_processing_records/find_product_recommendation_by_status.ts +++ b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_product_recommendation_by_status.ts @@ -1,9 +1,9 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; import type { FindUnprocessedResult } from './types'; -import { createDebugLog } from '../../../lib/debug_log'; +import { createDebugLog } from '../../../../lib/debug_log'; const debugLog = createDebugLog('DEBUG_WORKFLOW', '[Workflow]'); @@ -11,7 +11,7 @@ export async function findProductRecommendationByStatus( ctx: ActionCtx, params: { organizationId: string; - workflowId: string; + wfDefinitionId: string; backoffHours: number; status: 'pending' | 'approved' | 'rejected'; }, @@ -21,7 +21,7 @@ export async function findProductRecommendationByStatus( internal.workflow_processing_records.findProductRecommendationByStatus, { organizationId: params.organizationId, - workflowId: params.workflowId, + wfDefinitionId: params.wfDefinitionId, backoffHours: params.backoffHours, status: params.status, }, diff --git a/services/platform/convex/workflow/actions/workflow_processing_records/find_unprocessed.ts b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts similarity index 74% rename from services/platform/convex/workflow/actions/workflow_processing_records/find_unprocessed.ts rename to services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts index 25b1086c8e..bd0a795da2 100644 --- a/services/platform/convex/workflow/actions/workflow_processing_records/find_unprocessed.ts +++ b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed.ts @@ -1,5 +1,5 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; import type { TableName, FindUnprocessedResult } from './types'; @@ -8,7 +8,7 @@ export async function findUnprocessed( params: { organizationId: string; tableName: TableName; - workflowId: string; + wfDefinitionId: string; backoffHours: number; }, ): Promise { @@ -17,7 +17,7 @@ export async function findUnprocessed( { organizationId: params.organizationId, tableName: params.tableName, - workflowId: params.workflowId, + wfDefinitionId: params.wfDefinitionId, backoffHours: params.backoffHours, }, ); diff --git a/services/platform/convex/workflow/actions/workflow_processing_records/find_unprocessed_open_conversation.ts b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts similarity index 73% rename from services/platform/convex/workflow/actions/workflow_processing_records/find_unprocessed_open_conversation.ts rename to services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts index ec5ad1634a..2b98ee5551 100644 --- a/services/platform/convex/workflow/actions/workflow_processing_records/find_unprocessed_open_conversation.ts +++ b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/find_unprocessed_open_conversation.ts @@ -1,5 +1,5 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; import type { FindUnprocessedResult } from './types'; @@ -7,7 +7,7 @@ export async function findUnprocessedOpenConversation( ctx: ActionCtx, params: { organizationId: string; - workflowId: string; + wfDefinitionId: string; backoffHours: number; }, ): Promise { @@ -15,7 +15,7 @@ export async function findUnprocessedOpenConversation( internal.workflow_processing_records.findUnprocessedOpenConversation, { organizationId: params.organizationId, - workflowId: params.workflowId, + wfDefinitionId: params.wfDefinitionId, backoffHours: params.backoffHours, }, ); diff --git a/services/platform/convex/workflow/actions/workflow_processing_records/helpers/record_processed.ts b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/record_processed.ts new file mode 100644 index 0000000000..b0712f6110 --- /dev/null +++ b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/record_processed.ts @@ -0,0 +1,36 @@ +import type { ActionCtx } from '../../../../_generated/server'; +import { internal } from '../../../../_generated/api'; +import type { Id } from '../../../../_generated/dataModel'; +import type { TableName, RecordProcessedResult } from './types'; + +export async function recordProcessed( + ctx: ActionCtx, + params: { + organizationId: string; + tableName: TableName; + recordId: string; + wfDefinitionId: string; + recordCreationTime: number; + metadata?: unknown; + }, +): Promise { + const processingRecordId: Id<'workflowProcessingRecords'> = + await ctx.runMutation( + internal.workflow_processing_records.recordProcessed, + { + organizationId: params.organizationId, + tableName: params.tableName, + recordId: params.recordId, + wfDefinitionId: params.wfDefinitionId, + recordCreationTime: params.recordCreationTime, + metadata: params.metadata, + }, + ); + + return { + operation: 'record_processed', + recordId: processingRecordId, + success: true, + timestamp: Date.now(), + }; +} diff --git a/services/platform/convex/workflow/actions/workflow_processing_records/types.ts b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts similarity index 87% rename from services/platform/convex/workflow/actions/workflow_processing_records/types.ts rename to services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts index e63bc20068..5698febdd4 100644 --- a/services/platform/convex/workflow/actions/workflow_processing_records/types.ts +++ b/services/platform/convex/workflow/actions/workflow_processing_records/helpers/types.ts @@ -1,4 +1,4 @@ -import type { Id } from '../../../_generated/dataModel'; +import type { Id } from '../../../../_generated/dataModel'; export type TableName = | 'customers' @@ -21,3 +21,4 @@ export type RecordProcessedResult = { success: boolean; timestamp: number; }; + diff --git a/services/platform/convex/workflow/actions/workflow_processing_records/record_processed.ts b/services/platform/convex/workflow/actions/workflow_processing_records/record_processed.ts deleted file mode 100644 index f2af761ba4..0000000000 --- a/services/platform/convex/workflow/actions/workflow_processing_records/record_processed.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { ActionCtx } from '../../../_generated/server'; -import { internal } from '../../../_generated/api'; -import type { Id } from '../../../_generated/dataModel'; -import type { TableName, RecordProcessedResult } from './types'; - -export async function recordProcessed( - ctx: ActionCtx, - params: { - organizationId: string; - tableName: TableName; - documentId: string; - workflowId: string; - documentCreationTime: number; - metadata?: unknown; - }, -): Promise { - const recordId: Id<'workflowProcessingRecords'> = await ctx.runMutation( - internal.workflow_processing_records.recordProcessed, - { - organizationId: params.organizationId, - tableName: params.tableName, - documentId: params.documentId, - workflowId: params.workflowId, - documentCreationTime: params.documentCreationTime, - metadata: params.metadata, - }, - ); - - return { - operation: 'record_processed', - recordId, - success: true, - timestamp: Date.now(), - }; -} diff --git a/services/platform/convex/workflow/actions/workflow_processing_records/workflow_processing_records_action.ts b/services/platform/convex/workflow/actions/workflow_processing_records/workflow_processing_records_action.ts index eb9286ab5c..9e79c29a0c 100644 --- a/services/platform/convex/workflow/actions/workflow_processing_records/workflow_processing_records_action.ts +++ b/services/platform/convex/workflow/actions/workflow_processing_records/workflow_processing_records_action.ts @@ -12,11 +12,11 @@ import { v } from 'convex/values'; import type { ActionDefinition } from '../../helpers/nodes/action/types'; -import { findUnprocessed } from './find_unprocessed'; -import { findUnprocessedOpenConversation } from './find_unprocessed_open_conversation'; -import { findProductRecommendationByStatus } from './find_product_recommendation_by_status'; -import { recordProcessed } from './record_processed'; -import type { TableName } from './types'; +import { findUnprocessed } from './helpers/find_unprocessed'; +import { findUnprocessedOpenConversation } from './helpers/find_unprocessed_open_conversation'; +import { findProductRecommendationByStatus } from './helpers/find_product_recommendation_by_status'; +import { recordProcessed } from './helpers/record_processed'; +import type { TableName } from './helpers/types'; export const workflowProcessingRecordsAction: ActionDefinition<{ operation: @@ -26,11 +26,11 @@ export const workflowProcessingRecordsAction: ActionDefinition<{ | 'record_processed'; organizationId: string; tableName?: TableName; - workflowId: string; + wfDefinitionId: string; backoffHours?: number; status?: 'pending' | 'approved' | 'rejected'; - documentId?: string; - documentCreationTime?: number; + recordId?: string; + recordCreationTime?: number; metadata?: unknown; }> = { type: 'workflow_processing_records', @@ -57,7 +57,7 @@ export const workflowProcessingRecordsAction: ActionDefinition<{ v.literal('exampleMessages'), ), ), - workflowId: v.string(), + wfDefinitionId: v.string(), backoffHours: v.optional(v.number()), status: v.optional( v.union( @@ -66,8 +66,8 @@ export const workflowProcessingRecordsAction: ActionDefinition<{ v.literal('rejected'), ), ), - documentId: v.optional(v.string()), - documentCreationTime: v.optional(v.number()), + recordId: v.optional(v.string()), + recordCreationTime: v.optional(v.number()), metadata: v.optional(v.any()), }), @@ -88,7 +88,7 @@ export const workflowProcessingRecordsAction: ActionDefinition<{ return await findUnprocessed(ctx, { organizationId: params.organizationId, tableName: params.tableName, - workflowId: params.workflowId, + wfDefinitionId: params.wfDefinitionId, backoffHours: params.backoffHours, }); } @@ -102,7 +102,7 @@ export const workflowProcessingRecordsAction: ActionDefinition<{ return await findUnprocessedOpenConversation(ctx, { organizationId: params.organizationId, - workflowId: params.workflowId, + wfDefinitionId: params.wfDefinitionId, backoffHours: params.backoffHours, }); } @@ -121,7 +121,7 @@ export const workflowProcessingRecordsAction: ActionDefinition<{ return await findProductRecommendationByStatus(ctx, { organizationId: params.organizationId, - workflowId: params.workflowId, + wfDefinitionId: params.wfDefinitionId, backoffHours: params.backoffHours, status: params.status, }); @@ -133,23 +133,23 @@ export const workflowProcessingRecordsAction: ActionDefinition<{ 'record_processed operation requires tableName parameter', ); } - if (!params.documentId) { + if (!params.recordId) { throw new Error( - 'record_processed operation requires documentId parameter', + 'record_processed operation requires recordId parameter', ); } - if (params.documentCreationTime === undefined) { + if (params.recordCreationTime === undefined) { throw new Error( - 'record_processed operation requires documentCreationTime parameter', + 'record_processed operation requires recordCreationTime parameter', ); } return await recordProcessed(ctx, { organizationId: params.organizationId, tableName: params.tableName, - documentId: params.documentId, - workflowId: params.workflowId, - documentCreationTime: params.documentCreationTime, + recordId: params.recordId, + wfDefinitionId: params.wfDefinitionId, + recordCreationTime: params.recordCreationTime, metadata: params.metadata, }); } diff --git a/services/platform/convex/workflow/helpers/engine/start_workflow_handler.ts b/services/platform/convex/workflow/helpers/engine/start_workflow_handler.ts index b14f8821a1..753cb03852 100644 --- a/services/platform/convex/workflow/helpers/engine/start_workflow_handler.ts +++ b/services/platform/convex/workflow/helpers/engine/start_workflow_handler.ts @@ -25,9 +25,9 @@ export async function handleStartWorkflow( args: StartWorkflowArgs, workflowManager: WorkflowManager, ): Promise> { - // Validate database workflow - const workflow = await ctx.db.get(args.wfDefinitionId); - if (!workflow) { + // Validate database workflow definition + const wfDefinition = await ctx.db.get(args.wfDefinitionId); + if (!wfDefinition) { throw new Error('Workflow definition not found'); } @@ -43,20 +43,25 @@ export async function handleStartWorkflow( throw new Error('No steps defined for workflow'); } - const workflowName = workflow.name; + const workflowName = wfDefinition.name; // Generate workflowSlug from workflow name const workflowSlug = snakeCase(workflowName); + // Get rootWfDefinitionId: use wfDefinition's rootVersionId, or fall back to its own _id + const rootWfDefinitionId = wfDefinition.rootVersionId ?? wfDefinition._id; + debugLog('startWorkflow Creating execution', { workflowName, workflowSlug, + rootWfDefinitionId, }); // Pre-create execution record with pending status const executionId: Id<'wfExecutions'> = await ctx.db.insert('wfExecutions', { organizationId: args.organizationId, wfDefinitionId: args.wfDefinitionId, + rootWfDefinitionId, status: 'pending', currentStepSlug: '', input: args.input || {}, diff --git a/services/platform/convex/workflow/helpers/step_execution/initialize_execution_variables.ts b/services/platform/convex/workflow/helpers/step_execution/initialize_execution_variables.ts index 911e088f86..d48fdf7e6b 100644 --- a/services/platform/convex/workflow/helpers/step_execution/initialize_execution_variables.ts +++ b/services/platform/convex/workflow/helpers/step_execution/initialize_execution_variables.ts @@ -21,12 +21,15 @@ export async function initializeExecutionVariables( args: InitializeVariablesArgs, workflowConfig: WorkflowConfig, ): Promise> { - // Determine workflowId: prefer wfDefinitionId, fallback to workflowSlug for inline workflows - const workflowId = execution.wfDefinitionId ?? execution.workflowSlug; + // Determine wfDefinitionId: prefer wfDefinitionId, fallback to workflowSlug for inline workflows + const wfDefinitionId = execution.wfDefinitionId ?? execution.workflowSlug; + // rootWfDefinitionId: root version of the workflow family (for tracking across versions) + const rootWfDefinitionId = execution.rootWfDefinitionId; let fullVariables: Record = { organizationId: args.organizationId, - workflowId, // Auto-inject wfDefinitionId or workflowSlug as workflowId + wfDefinitionId, // Auto-inject wfDefinitionId or workflowSlug + rootWfDefinitionId, // Auto-inject root workflow definition ID }; // Use execution variables as the base, or initialize if empty @@ -34,7 +37,8 @@ export async function initializeExecutionVariables( fullVariables = { ...(execution.variables as Record), organizationId: args.organizationId, - workflowId, // Auto-inject wfDefinitionId or workflowSlug as workflowId + wfDefinitionId, // Auto-inject wfDefinitionId or workflowSlug + rootWfDefinitionId, // Auto-inject root workflow definition ID }; } else { // Initialize execution variables for the first time @@ -42,7 +46,8 @@ export async function initializeExecutionVariables( ...((args.resumeVariables ?? args.initialInput) || {}), ...(workflowConfig?.config?.variables ?? {}), organizationId: args.organizationId, - workflowId, // Auto-inject wfDefinitionId or workflowSlug as workflowId + wfDefinitionId, // Auto-inject wfDefinitionId or workflowSlug + rootWfDefinitionId, // Auto-inject root workflow definition ID }; // Handle secrets decryption for initial setup diff --git a/services/platform/convex/workflow/helpers/step_execution/types.ts b/services/platform/convex/workflow/helpers/step_execution/types.ts index 90000c1605..6382b76554 100644 --- a/services/platform/convex/workflow/helpers/step_execution/types.ts +++ b/services/platform/convex/workflow/helpers/step_execution/types.ts @@ -18,6 +18,7 @@ export interface StepDefinition { export interface ExecutionData { _id: Id<'wfExecutions'>; wfDefinitionId?: Id<'wfDefinitions'> | null; + rootWfDefinitionId?: Id<'wfDefinitions'>; // Root version of the workflow family stepsConfig?: Record; workflowConfig?: WorkflowConfig; variables?: Record; diff --git a/services/platform/convex/workflow_processing_records.ts b/services/platform/convex/workflow_processing_records.ts index a9db76b1fb..5405ab9183 100644 --- a/services/platform/convex/workflow_processing_records.ts +++ b/services/platform/convex/workflow_processing_records.ts @@ -29,22 +29,22 @@ import * as WorkflowProcessingRecordsModel from './model/workflow_processing_rec // ============================================================================= /** - * Find unprocessed documents in a table for a specific workflow. + * Find unprocessed records in a table for a specific workflow. * * Algorithm: - * 1. Get the latest processed document (by documentCreationTime) - * 2. Get the latest document in the target table (by _creationTime) + * 1. Get the latest processed record (by recordCreationTime) + * 2. Get the latest record in the target table (by _creationTime) * 3. If latest processed == latest in table OR no processing history exists: - * - Start from the earliest unprocessed document + * - Start from the earliest unprocessed record * 4. Otherwise: - * - Continue from where we left off (documents with _creationTime > last processed) + * - Continue from where we left off (records with _creationTime > last processed) * * This approach: - * - Avoids scanning all documents + * - Avoids scanning all records * - Uses indexes efficiently * - Tracks processing progress per workflow per table * - Can resume from where it left off - * - Always returns exactly one document (limit defaults to 1) + * - Always returns exactly one record (limit defaults to 1) */ export const findUnprocessed = internalQuery({ args: { @@ -59,7 +59,7 @@ export const findUnprocessed = internalQuery({ v.literal('websitePages'), v.literal('exampleMessages'), ), - workflowId: v.string(), + wfDefinitionId: v.string(), backoffHours: v.number(), limit: v.optional(v.number()), }, @@ -81,7 +81,7 @@ export const findUnprocessed = internalQuery({ export const findUnprocessedOpenConversation = internalQuery({ args: { organizationId: v.string(), - workflowId: v.string(), + wfDefinitionId: v.string(), backoffHours: v.number(), }, returns: v.object({ @@ -105,7 +105,7 @@ export const findUnprocessedOpenConversation = internalQuery({ export const findProductRecommendationByStatus = internalQuery({ args: { organizationId: v.string(), - workflowId: v.string(), + wfDefinitionId: v.string(), backoffHours: v.number(), status: v.union( v.literal('pending'), @@ -142,9 +142,9 @@ export const recordProcessed = internalMutation({ v.literal('websitePages'), v.literal('exampleMessages'), ), - documentId: v.string(), - workflowId: v.string(), - documentCreationTime: v.number(), + recordId: v.string(), + wfDefinitionId: v.string(), + recordCreationTime: v.number(), metadata: v.optional(v.any()), }, returns: v.id('workflowProcessingRecords'),