-
Notifications
You must be signed in to change notification settings - Fork 4
feat(platform): Outlook email sync cursor and normalization #815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,10 +19,14 @@ import type { ConversationStatus, ConversationPriority } from './helpers/types'; | |
| import { | ||
| jsonRecordValidator, | ||
| jsonValueValidator, | ||
| } from '../../../lib/validators/json'; | ||
| } from '../../../lib/shared/schemas/utils/json_value'; | ||
| import { createConversation } from './helpers/create_conversation'; | ||
| import { createConversationFromEmail } from './helpers/create_conversation_from_email'; | ||
| import { createConversationFromSentEmail } from './helpers/create_conversation_from_sent_email'; | ||
| import { | ||
| getEmailSyncCursor, | ||
| updateEmailSyncCursor, | ||
| } from './helpers/email_sync_cursor'; | ||
| import { queryConversationMessages } from './helpers/query_conversation_messages'; | ||
| import { queryLatestMessageByDeliveryState } from './helpers/query_latest_message_by_delivery_state'; | ||
| import { updateConversations } from './helpers/update_conversations'; | ||
|
|
@@ -113,12 +117,21 @@ type ConversationActionParams = | |
| priority?: ConversationPriority; | ||
| type?: string; | ||
| integrationName?: string; | ||
| } | ||
| | { | ||
| operation: 'get_email_sync_cursor'; | ||
| integrationName: string; | ||
| } | ||
| | { | ||
| operation: 'update_email_sync_cursor'; | ||
| integrationName: string; | ||
| cursor: Record<string, unknown>; | ||
|
Comment on lines
+121
to
+128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C3 '\bnextCursor\b|\bemailSyncCursor\b|\bget_email_sync_cursor\b|\bupdate_email_sync_cursor\b'Repository: tale-project/tale Length of output: 15642 🏁 Script executed: #!/bin/bash
# Search for jsonValueValidator and other validators to understand available options
rg -n 'jsonValueValidator|jsonRecordValidator' --type tsRepository: tale-project/tale Length of output: 32225 🏁 Script executed: #!/bin/bash
# Search for IMAP and other integration connectors that might produce cursors
fd -e ts -path '*/integrations/*' -o -path '*/imap/*' | head -20Repository: tale-project/tale Length of output: 229 🏁 Script executed: #!/bin/bash
# Look for cursor or nextCursor definitions in IMAP-related files
find . -type f -name "*imap*" \( -name "*.ts" -o -name "*.json" \) | head -20Repository: tale-project/tale Length of output: 43 🏁 Script executed: #!/bin/bash
# Search for IMAP integration files
find . -type f -name "*imap*" | grep -E '\.(ts|js|json)$'Repository: tale-project/tale Length of output: 43 🏁 Script executed: #!/bin/bash
# Look at the metadata handling precedent mentioned
sed -n '70,85p' services/platform/convex/integrations/internal_mutations.tsRepository: tale-project/tale Length of output: 563 🏁 Script executed: #!/bin/bash
# Search for all cursor/nextCursor producers in integrations
rg -n 'nextCursor.*=' examples/integrations/ --type ts -B2 -A2Repository: tale-project/tale Length of output: 519 🏁 Script executed: #!/bin/bash
# Search for IMAP files in services/platform
find services/platform -type f -name "*imap*" 2>/dev/null | head -20Repository: tale-project/tale Length of output: 43 🏁 Script executed: #!/bin/bash
# Check the updateEmailSyncCursor implementation to see if it validates cursor at runtime
rg -n 'updateEmailSyncCursor' services/platform/convex/workflow_engine/action_defs/conversation/helpers/ -A 15Repository: tale-project/tale Length of output: 2002 🏁 Script executed: #!/bin/bash
# Verify the json validator definitions to understand their scope
cat services/platform/convex/lib/shared/schemas/utils/json_value.tsRepository: tale-project/tale Length of output: 877 Use The cursor is documented as "provider-opaque" but the validator restricts it to objects. This contradicts the semantic intent and locks the API to one shape. For consistency with how provider metadata is handled elsewhere (see Also applies to: 207-217 🤖 Prompt for AI Agents |
||
| }; | ||
|
|
||
| export const conversationAction: ActionDefinition<ConversationActionParams> = { | ||
| type: 'conversation', | ||
| title: 'Conversation Operation', | ||
| description: `Execute conversation-specific operations (create, get_by_id, query_messages, query_latest_message_by_delivery_state, update, create_from_email, create_from_sent_email). organizationId is automatically read from workflow context variables. | ||
| description: `Execute conversation-specific operations (create, get_by_id, query_messages, query_latest_message_by_delivery_state, update, create_from_email, create_from_sent_email, get_email_sync_cursor, update_email_sync_cursor). organizationId is automatically read from workflow context variables. | ||
|
|
||
| FOR EMAIL WORKFLOWS: | ||
| When creating outbound email conversations, include these fields in the metadata object: | ||
|
|
@@ -191,6 +204,17 @@ See 'product_recommendation_email' predefined workflow for complete example.`, | |
| type: v.optional(v.string()), | ||
| integrationName: v.optional(v.string()), | ||
| }), | ||
| // get_email_sync_cursor: Read provider-opaque sync cursor from integration metadata | ||
| v.object({ | ||
| operation: v.literal('get_email_sync_cursor'), | ||
| integrationName: v.string(), | ||
| }), | ||
| // update_email_sync_cursor: Write provider-opaque sync cursor to integration metadata | ||
| v.object({ | ||
| operation: v.literal('update_email_sync_cursor'), | ||
| integrationName: v.string(), | ||
| cursor: jsonRecordValidator, | ||
| }), | ||
| ), | ||
| async execute(ctx, params, variables) { | ||
| // Read and validate organizationId from workflow context variables | ||
|
|
@@ -281,6 +305,22 @@ See 'product_recommendation_email' predefined workflow for complete example.`, | |
| }); | ||
| } | ||
|
|
||
| case 'get_email_sync_cursor': { | ||
| return await getEmailSyncCursor(ctx, { | ||
| organizationId, | ||
| integrationName: params.integrationName, | ||
| }); | ||
| } | ||
|
|
||
| case 'update_email_sync_cursor': { | ||
| await updateEmailSyncCursor(ctx, { | ||
| organizationId, | ||
| integrationName: params.integrationName, | ||
| cursor: params.cursor, | ||
| }); | ||
| return { success: true }; | ||
| } | ||
|
|
||
| default: | ||
| throw new Error( | ||
| `Unsupported conversation operation: ${(params as { operation: string }).operation}`, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't silently replace non-object metadata.
createIntegrationstill accepts arbitrary JSON metadata, so an existing scalar or array value is legal here. Falling back to{}means the first cursor update erases that stored value and replaces it with an object. Fail fast here, or narrow integrations metadata to records before merging.Proposed guard
🤖 Prompt for AI Agents