Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions apps/sim/app/api/webhooks/agentmail/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,24 @@ async function createRejectedTask(
* Format: "username@domain.com" or "Display Name <username@domain.com>"
*/
function extractSenderEmail(from: string): string {
const match = from.match(/<([^>]+)>/)
return (match?.[1] || from).toLowerCase().trim()
const openBracket = from.indexOf('<')
const closeBracket = from.indexOf('>', openBracket + 1)
if (openBracket !== -1 && closeBracket !== -1) {
return from
.substring(openBracket + 1, closeBracket)
.toLowerCase()
.trim()
}
return from.toLowerCase().trim()
}

function extractDisplayName(from: string): string | null {
const match = from.match(/^(.+?)\s*</)
return match?.[1]?.trim().replace(/^"|"$/g, '') || null
const openBracket = from.indexOf('<')
if (openBracket <= 0) return null
const name = from.substring(0, openBracket).trim()
if (!name) return null
if (name.startsWith('"') && name.endsWith('"')) {
return name.slice(1, -1) || null
}
return name
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import type { ContentBlock, OptionItem, SubagentName, ToolCallData } from '../../types'
import { SUBAGENT_LABELS } from '../../types'
import { SUBAGENT_LABELS, TOOL_UI_METADATA } from '../../types'
import type { AgentGroupItem } from './components'
import { AgentGroup, ChatContent, CircleStop, Options, PendingTagIndicator } from './components'

Expand Down Expand Up @@ -47,7 +47,10 @@ function toToolData(tc: NonNullable<ContentBlock['toolCall']>): ToolCallData {
return {
id: tc.id,
toolName: tc.name,
displayTitle: tc.displayTitle || formatToolName(tc.name),
displayTitle:
tc.displayTitle ||
TOOL_UI_METADATA[tc.name as keyof typeof TOOL_UI_METADATA]?.title ||
formatToolName(tc.name),
status: tc.status,
result: tc.result,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export function EmbeddedWorkflowActions({ workspaceId, workflowId }: EmbeddedWor
</Button>
</Tooltip.Trigger>
<Tooltip.Content side='bottom'>
<p>{isExecuting ? 'Stop' : 'Run'}</p>
<p>{isExecuting ? 'Stop' : 'Run workflow'}</p>
</Tooltip.Content>
</Tooltip.Root>
</>
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/connectors/google-docs/google-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ function buildQuery(sourceConfig: Record<string, unknown>): string {

const folderId = sourceConfig.folderId as string | undefined
if (folderId?.trim()) {
parts.push(`'${folderId.trim().replace(/'/g, "\\'")}' in parents`)
parts.push(`'${folderId.trim().replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' in parents`)
}

return parts.join(' and ')
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/connectors/google-drive/google-drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ function buildQuery(sourceConfig: Record<string, unknown>): string {

const folderId = sourceConfig.folderId as string | undefined
if (folderId?.trim()) {
parts.push(`'${folderId.trim().replace(/'/g, "\\'")}' in parents`)
parts.push(`'${folderId.trim().replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' in parents`)
}

const fileType = (sourceConfig.fileType as string) || 'all'
Expand Down
65 changes: 53 additions & 12 deletions apps/sim/lib/mothership/inbox/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,67 @@ function isForwardedEmail(subject: string | null, body: string | null): boolean
return false
}

/**
* Repeatedly applies a regex replacement until the string stabilises.
* Prevents incomplete sanitization from nested/overlapping patterns
* like `<scr<script>ipt>`.
*/
export function replaceUntilStable(
input: string,
pattern: RegExp,
replacement: string,
maxIterations = 100
): string {
let prev = input
let next = prev.replace(pattern, replacement)
let iterations = 0
while (next !== prev && iterations++ < maxIterations) {
prev = next
next = prev.replace(pattern, replacement)
}
return next
}

const HTML_ENTITY_MAP: Record<string, string> = {
'&nbsp;': ' ',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&#39;': "'",
}

/**
* Decodes known HTML entities in a single pass to avoid double-unescaping.
* A two-step decode (e.g. `&amp;` -> `&` then `&lt;` -> `<`) would turn
* `&amp;lt;` into `<`, which is incorrect.
*/
function decodeHtmlEntities(text: string): string {
return text.replace(/&(?:nbsp|amp|lt|gt|quot|#39);/g, (match) => HTML_ENTITY_MAP[match] ?? match)
}

/**
* Basic HTML to text extraction.
*/
function extractTextFromHtml(html: string | null): string | null {
if (!html) return null

return html
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
let text = html

text = decodeHtmlEntities(text)

text = replaceUntilStable(text, /<style[^>]*>[\s\S]*?<\/style\s*>/gi, '')
text = replaceUntilStable(text, /<script[^>]*>[\s\S]*?<\/script\s*>/gi, '')

text = text
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p>/gi, '\n\n')
.replace(/<\/div>/gi, '\n')
.replace(/<\/li>/gi, '\n')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/\n{3,}/g, '\n\n')
.trim()

text = replaceUntilStable(text, /<[^>]+>/g, '')

text = text.replace(/\n{3,}/g, '\n\n').trim()

return text
}
5 changes: 4 additions & 1 deletion apps/sim/lib/mothership/inbox/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger'
import { marked } from 'marked'
import { getBaseUrl } from '@/lib/core/utils/urls'
import * as agentmail from '@/lib/mothership/inbox/agentmail-client'
import { replaceUntilStable } from '@/lib/mothership/inbox/format'
import type { InboxTask } from '@/lib/mothership/inbox/types'

const logger = createLogger('InboxResponse')
Expand Down Expand Up @@ -82,7 +83,9 @@ const EMAIL_STYLES = `
function stripRawHtml(text: string): string {
return text
.split(/(```[\s\S]*?```)/g)
.map((segment, i) => (i % 2 === 0 ? segment.replace(/<\/?[a-z][^>]*>/gi, '') : segment))
.map((segment, i) =>
i % 2 === 0 ? replaceUntilStable(segment, /<\/?[a-z][^>]*>/gi, '') : segment
)
.join('')
}

Expand Down
Loading