Compare commits

..

27 Commits

Author SHA1 Message Date
Waleed
5e8c843241 v0.5.59: a2a support, documentation 2026-01-13 13:21:21 -08:00
Waleed
4be420311c fix(a2a): removed deployment constraint for redeploying a2a workflows (#2796)
* fix(a2a): removed deployment constraint for redeploying a2a workflows

* updated A2A tab copy state

* consolidated trigger types const
2026-01-13 13:19:57 -08:00
Waleed
7bf3d73ee6 v0.5.58: export folders, new tools, permissions groups enhancements 2026-01-13 00:56:59 -08:00
Vikhyath Mondreti
7ffc11a738 v0.5.57: subagents, context menu improvements, bug fixes 2026-01-11 11:38:40 -08:00
Waleed
be578e2ed7 v0.5.56: batch operations, access control and permission groups, billing fixes 2026-01-10 00:31:34 -08:00
Waleed
f415e5edc4 v0.5.55: polling groups, bedrock provider, devcontainer fixes, workflow preview enhancements 2026-01-08 23:36:56 -08:00
Waleed
13a6e6c3fa v0.5.54: seo, model blacklist, helm chart updates, fireflies integration, autoconnect improvements, billing fixes 2026-01-07 16:09:45 -08:00
Waleed
f5ab7f21ae v0.5.53: hotkey improvements, added redis fallback, fixes for workflow tool 2026-01-06 23:34:52 -08:00
Waleed
bfb6fffe38 v0.5.52: new port-based router block, combobox expression and variable support 2026-01-06 16:14:10 -08:00
Waleed
4fbec0a43f v0.5.51: triggers, kb, condition block improvements, supabase and grain integration updates 2026-01-06 14:26:46 -08:00
Waleed
585f5e365b v0.5.50: import improvements, ui upgrades, kb styling and performance improvements 2026-01-05 00:35:55 -08:00
Waleed
3792bdd252 v0.5.49: hitl improvements, new email styles, imap trigger, logs context menu (#2672)
* feat(logs-context-menu): consolidated logs utils and types, added logs record context menu (#2659)

* feat(email): welcome email; improvement(emails): ui/ux (#2658)

* feat(email): welcome email; improvement(emails): ui/ux

* improvement(emails): links, accounts, preview

* refactor(emails): file structure and wrapper components

* added envvar for personal emails sent, added isHosted gate

* fixed failing tests, added env mock

* fix: removed comment

---------

Co-authored-by: waleed <walif6@gmail.com>

* fix(logging): hitl + trigger dev crash protection (#2664)

* hitl gaps

* deal with trigger worker crashes

* cleanup import strcuture

* feat(imap): added support for imap trigger (#2663)

* feat(tools): added support for imap trigger

* feat(imap): added parity, tested

* ack PR comments

* final cleanup

* feat(i18n): update translations (#2665)

Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>

* fix(grain): updated grain trigger to auto-establish trigger (#2666)

Co-authored-by: aadamgough <adam@sim.ai>

* feat(admin): routes to manage deployments (#2667)

* feat(admin): routes to manage deployments

* fix naming fo deployed by

* feat(time-picker): added timepicker emcn component, added to playground, added searchable prop for dropdown, added more timezones for schedule, updated license and notice date (#2668)

* feat(time-picker): added timepicker emcn component, added to playground, added searchable prop for dropdown, added more timezones for schedule, updated license and notice date

* removed unused params, cleaned up redundant utils

* improvement(invite): aligned styling (#2669)

* improvement(invite): aligned with rest of app

* fix(invite): error handling

* fix: addressed comments

---------

Co-authored-by: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
Co-authored-by: waleedlatif1 <waleedlatif1@users.noreply.github.com>
Co-authored-by: Adam Gough <77861281+aadamgough@users.noreply.github.com>
Co-authored-by: aadamgough <adam@sim.ai>
2026-01-03 13:19:18 -08:00
Waleed
eb5d1f3e5b v0.5.48: copy-paste workflow blocks, docs updates, mcp tool fixes 2025-12-31 18:00:04 -08:00
Waleed
54ab82c8dd v0.5.47: deploy workflow as mcp, kb chunks tokenizer, UI improvements, jira service management tools 2025-12-30 23:18:58 -08:00
Waleed
f895bf469b v0.5.46: build improvements, greptile, light mode improvements 2025-12-29 02:17:52 -08:00
Waleed
dd3209af06 v0.5.45: light mode fixes, realtime usage indicator, docker build improvements 2025-12-27 19:57:42 -08:00
Waleed
b6ba3b50a7 v0.5.44: keyboard shortcuts, autolayout, light mode, byok, testing improvements 2025-12-26 21:25:19 -08:00
Waleed
b304233062 v0.5.43: export logs, circleback, grain, vertex, code hygiene, schedule improvements 2025-12-23 19:19:18 -08:00
Vikhyath Mondreti
57e4b49bd6 v0.5.42: fix memory migration 2025-12-23 01:24:54 -08:00
Vikhyath Mondreti
e12dd204ed v0.5.41: memory fixes, copilot improvements, knowledgebase improvements, LLM providers standardization 2025-12-23 00:15:18 -08:00
Vikhyath Mondreti
3d9d9cbc54 v0.5.40: supabase ops to allow non-public schemas, jira uuid 2025-12-21 22:28:05 -08:00
Waleed
0f4ec962ad v0.5.39: notion, workflow variables fixes 2025-12-20 20:44:00 -08:00
Waleed
4827866f9a v0.5.38: snap to grid, copilot ux improvements, billing line items 2025-12-20 17:24:38 -08:00
Waleed
3e697d9ed9 v0.5.37: redaction utils consolidation, logs updates, autoconnect improvements, additional kb tag types 2025-12-19 22:31:55 -08:00
Martin Yankov
4431a1a484 fix(helm): add custom egress rules to realtime network policy (#2481)
The realtime service network policy was missing the custom egress rules section
that allows configuration of additional egress rules via values.yaml. This caused
the realtime pods to be unable to connect to external databases (e.g., PostgreSQL
on port 5432) when using external database configurations.

The app network policy already had this section, but the realtime network policy
was missing it, creating an inconsistency and preventing the realtime service
from accessing external databases configured via networkPolicy.egress values.

This fix adds the same custom egress rules template section to the realtime
network policy, matching the app network policy behavior and allowing users to
configure database connectivity via values.yaml.
2025-12-19 18:59:08 -08:00
Waleed
4d1a9a3f22 v0.5.36: hitl improvements, opengraph, slack fixes, one-click unsubscribe, auth checks, new db indexes 2025-12-19 01:27:49 -08:00
Vikhyath Mondreti
eb07a080fb v0.5.35: helm updates, copilot improvements, 404 for docs, salesforce fixes, subflow resize clamping 2025-12-18 16:23:19 -08:00
50 changed files with 1068 additions and 609 deletions

View File

@@ -552,53 +552,6 @@ All fields automatically have:
- `mode: 'trigger'` - Only shown in trigger mode
- `condition: { field: 'selectedTriggerId', value: triggerId }` - Only shown when this trigger is selected
## Trigger Outputs & Webhook Input Formatting
### Important: Two Sources of Truth
There are two related but separate concerns:
1. **Trigger `outputs`** - Schema/contract defining what fields SHOULD be available. Used by UI for tag dropdown.
2. **`formatWebhookInput`** - Implementation that transforms raw webhook payload into actual data. Located in `apps/sim/lib/webhooks/utils.server.ts`.
**These MUST be aligned.** The fields returned by `formatWebhookInput` should match what's defined in trigger `outputs`. If they differ:
- Tag dropdown shows fields that don't exist (broken variable resolution)
- Or actual data has fields not shown in dropdown (users can't discover them)
### When to Add a formatWebhookInput Handler
- **Simple providers**: If the raw webhook payload structure already matches your outputs, you don't need a handler. The generic fallback returns `body` directly.
- **Complex providers**: If you need to transform, flatten, extract nested data, compute fields, or handle conditional logic, add a handler.
### Adding a Handler
In `apps/sim/lib/webhooks/utils.server.ts`, add a handler block:
```typescript
if (foundWebhook.provider === '{service}') {
// Transform raw webhook body to match trigger outputs
return {
eventType: body.type,
resourceId: body.data?.id || '',
timestamp: body.created_at,
resource: body.data,
}
}
```
**Key rules:**
- Return fields that match your trigger `outputs` definition exactly
- No wrapper objects like `webhook: { data: ... }` or `{service}: { ... }`
- No duplication (don't spread body AND add individual fields)
- Use `null` for missing optional data, not empty objects with empty strings
### Verify Alignment
Run the alignment checker:
```bash
bunx scripts/check-trigger-alignment.ts {service}
```
## Trigger Outputs
Trigger outputs use the same schema as block outputs (NOT tool outputs).
@@ -696,11 +649,6 @@ export const {service}WebhookTrigger: TriggerConfig = {
- [ ] Added `delete{Service}Webhook` function to `provider-subscriptions.ts`
- [ ] Added provider to `cleanupExternalWebhook` function
### Webhook Input Formatting
- [ ] Added handler in `apps/sim/lib/webhooks/utils.server.ts` (if custom formatting needed)
- [ ] Handler returns fields matching trigger `outputs` exactly
- [ ] Run `bunx scripts/check-trigger-alignment.ts {service}` to verify alignment
### Testing
- [ ] Run `bun run type-check` to verify no TypeScript errors
- [ ] Restart dev server to pick up new triggers

View File

@@ -384,7 +384,7 @@ async function handleMessageSend(
headers,
body: JSON.stringify({
...workflowInput,
triggerType: 'api',
triggerType: 'a2a',
...(useInternalAuth && { workflowId: agent.workflowId }),
}),
signal: AbortSignal.timeout(A2A_DEFAULT_TIMEOUT),
@@ -613,7 +613,7 @@ async function handleMessageStream(
headers,
body: JSON.stringify({
...workflowInput,
triggerType: 'api',
triggerType: 'a2a',
stream: true,
...(useInternalAuth && { workflowId: agent.workflowId }),
}),

View File

@@ -27,7 +27,7 @@ import { ExecutionSnapshot } from '@/executor/execution/snapshot'
import type { ExecutionMetadata, IterationContext } from '@/executor/execution/types'
import type { StreamingExecution } from '@/executor/types'
import { Serializer } from '@/serializer'
import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types'
import { CORE_TRIGGER_TYPES, type CoreTriggerType } from '@/stores/logs/filters/types'
const logger = createLogger('WorkflowExecuteAPI')
@@ -109,7 +109,7 @@ type AsyncExecutionParams = {
workflowId: string
userId: string
input: any
triggerType: 'api' | 'webhook' | 'schedule' | 'manual' | 'chat' | 'mcp'
triggerType: CoreTriggerType
}
/**
@@ -253,17 +253,9 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
})
const executionId = uuidv4()
type LoggingTriggerType = 'api' | 'webhook' | 'schedule' | 'manual' | 'chat' | 'mcp'
let loggingTriggerType: LoggingTriggerType = 'manual'
if (
triggerType === 'api' ||
triggerType === 'chat' ||
triggerType === 'webhook' ||
triggerType === 'schedule' ||
triggerType === 'manual' ||
triggerType === 'mcp'
) {
loggingTriggerType = triggerType as LoggingTriggerType
let loggingTriggerType: CoreTriggerType = 'manual'
if (CORE_TRIGGER_TYPES.includes(triggerType as CoreTriggerType)) {
loggingTriggerType = triggerType as CoreTriggerType
}
const loggingSession = new LoggingSession(
workflowId,

View File

@@ -72,6 +72,7 @@ const TRIGGER_VARIANT_MAP: Record<string, React.ComponentProps<typeof Badge>['va
schedule: 'green',
chat: 'purple',
webhook: 'orange',
a2a: 'teal',
}
interface StatusBadgeProps {

View File

@@ -204,7 +204,8 @@ export function A2aDeploy({
const [skillTags, setSkillTags] = useState<string[]>([])
const [language, setLanguage] = useState<CodeLanguage>('curl')
const [useStreamingExample, setUseStreamingExample] = useState(false)
const [copied, setCopied] = useState(false)
const [urlCopied, setUrlCopied] = useState(false)
const [codeCopied, setCodeCopied] = useState(false)
useEffect(() => {
if (existingAgent) {
@@ -451,7 +452,7 @@ export function A2aDeploy({
}
try {
if (!isDeployed && onDeployWorkflow) {
if ((!isDeployed || workflowNeedsRedeployment) && onDeployWorkflow) {
await onDeployWorkflow()
}
@@ -475,6 +476,7 @@ export function A2aDeploy({
}, [
existingAgent,
isDeployed,
workflowNeedsRedeployment,
onDeployWorkflow,
name,
description,
@@ -643,8 +645,8 @@ console.log(data);`
const handleCopyCommand = useCallback(() => {
navigator.clipboard.writeText(getCurlCommand())
setCopied(true)
setTimeout(() => setCopied(false), 2000)
setCodeCopied(true)
setTimeout(() => setCodeCopied(false), 2000)
}, [getCurlCommand])
if (isLoading) {
@@ -702,12 +704,12 @@ console.log(data);`
type='button'
onClick={() => {
navigator.clipboard.writeText(endpoint)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
setUrlCopied(true)
setTimeout(() => setUrlCopied(false), 2000)
}}
className='-translate-y-1/2 absolute top-1/2 right-2'
>
{copied ? (
{urlCopied ? (
<Check className='h-3 w-3 text-[var(--brand-tertiary-2)]' />
) : (
<Clipboard className='h-3 w-3 text-[var(--text-tertiary)]' />
@@ -715,7 +717,7 @@ console.log(data);`
</button>
</Tooltip.Trigger>
<Tooltip.Content>
<span>{copied ? 'Copied' : 'Copy'}</span>
<span>{urlCopied ? 'Copied' : 'Copy'}</span>
</Tooltip.Content>
</Tooltip.Root>
</div>
@@ -871,11 +873,15 @@ console.log(data);`
aria-label='Copy command'
className='!p-1.5 -my-1.5'
>
{copied ? <Check className='h-3 w-3' /> : <Clipboard className='h-3 w-3' />}
{codeCopied ? (
<Check className='h-3 w-3' />
) : (
<Clipboard className='h-3 w-3' />
)}
</Button>
</Tooltip.Trigger>
<Tooltip.Content>
<span>{copied ? 'Copied' : 'Copy'}</span>
<span>{codeCopied ? 'Copied' : 'Copy'}</span>
</Tooltip.Content>
</Tooltip.Root>
</div>

View File

@@ -2319,6 +2319,8 @@ const WorkflowContent = React.memo(() => {
/**
* Handles connection drag end. Detects if the edge was dropped over a block
* and automatically creates a connection to that block's target handle.
* Only creates a connection if ReactFlow didn't already handle it (e.g., when
* dropping on the block body instead of a handle).
*/
const onConnectEnd = useCallback(
(event: MouseEvent | TouchEvent) => {
@@ -2340,14 +2342,25 @@ const WorkflowContent = React.memo(() => {
// Find node under cursor
const targetNode = findNodeAtPosition(flowPosition)
// Create connection if valid target found
// Create connection if valid target found AND edge doesn't already exist
// ReactFlow's onConnect fires first when dropping on a handle, so we check
// if that connection already exists to avoid creating duplicates.
// IMPORTANT: We must read directly from the store (not React state) because
// the store update from ReactFlow's onConnect may not have triggered a
// React re-render yet when this callback runs (typically 1-2ms later).
if (targetNode && targetNode.id !== source.nodeId) {
onConnect({
source: source.nodeId,
sourceHandle: source.handleId,
target: targetNode.id,
targetHandle: 'target',
})
const currentEdges = useWorkflowStore.getState().edges
const edgeAlreadyExists = currentEdges.some(
(e) => e.source === source.nodeId && e.target === targetNode.id
)
if (!edgeAlreadyExists) {
onConnect({
source: source.nodeId,
sourceHandle: source.handleId,
target: targetNode.id,
targetHandle: 'target',
})
}
}
connectionSourceRef.current = null

View File

@@ -10,6 +10,7 @@ import { getWorkflowById } from '@/lib/workflows/utils'
import { ExecutionSnapshot } from '@/executor/execution/snapshot'
import type { ExecutionMetadata } from '@/executor/execution/types'
import type { ExecutionResult } from '@/executor/types'
import type { CoreTriggerType } from '@/stores/logs/filters/types'
const logger = createLogger('TriggerWorkflowExecution')
@@ -17,7 +18,7 @@ export type WorkflowExecutionPayload = {
workflowId: string
userId: string
input?: any
triggerType?: 'api' | 'webhook' | 'schedule' | 'manual' | 'chat' | 'mcp'
triggerType?: CoreTriggerType
metadata?: Record<string, any>
}

View File

@@ -313,26 +313,6 @@ export const getBlock = (type: string): BlockConfig | undefined => {
return registry[normalized]
}
export const getLatestBlock = (baseType: string): BlockConfig | undefined => {
const normalized = baseType.replace(/-/g, '_')
const versionedKeys = Object.keys(registry).filter((key) => {
const match = key.match(new RegExp(`^${normalized}_v(\\d+)$`))
return match !== null
})
if (versionedKeys.length > 0) {
const sorted = versionedKeys.sort((a, b) => {
const versionA = Number.parseInt(a.match(/_v(\d+)$/)?.[1] || '0', 10)
const versionB = Number.parseInt(b.match(/_v(\d+)$/)?.[1] || '0', 10)
return versionB - versionA
})
return registry[sorted[0]]
}
return registry[normalized]
}
export const getBlockByToolName = (toolName: string): BlockConfig | undefined => {
return Object.values(registry).find((block) => block.tools?.access?.includes(toolName))
}

View File

@@ -25,6 +25,7 @@ const badgeVariants = cva(
orange: `${STATUS_BASE} bg-[#fed7aa] text-[#c2410c] dark:bg-[rgba(249,115,22,0.2)] dark:text-[#fdba74]`,
amber: `${STATUS_BASE} bg-[#fde68a] text-[#a16207] dark:bg-[rgba(245,158,11,0.2)] dark:text-[#fcd34d]`,
teal: `${STATUS_BASE} bg-[#99f6e4] text-[#0f766e] dark:bg-[rgba(20,184,166,0.2)] dark:text-[#5eead4]`,
cyan: `${STATUS_BASE} bg-[#a5f3fc] text-[#0e7490] dark:bg-[rgba(14,165,233,0.2)] dark:text-[#7dd3fc]`,
'gray-secondary': `${STATUS_BASE} bg-[var(--surface-4)] text-[var(--text-secondary)]`,
},
size: {
@@ -51,6 +52,7 @@ const STATUS_VARIANTS = [
'orange',
'amber',
'teal',
'cyan',
'gray-secondary',
] as const
@@ -84,7 +86,7 @@ export interface BadgeProps
* Supports two categories of variants:
* - **Bordered**: `default`, `outline` - traditional badges with borders
* - **Status colors**: `green`, `red`, `gray`, `blue`, `blue-secondary`, `purple`,
* `orange`, `amber`, `teal`, `gray-secondary` - borderless colored badges
* `orange`, `amber`, `teal`, `cyan`, `gray-secondary` - borderless colored badges
*
* Status color variants can display a dot indicator via the `dot` prop.
* All variants support an optional `icon` prop for leading icons.

View File

@@ -378,10 +378,21 @@ function buildManualTriggerOutput(
}
function buildIntegrationTriggerOutput(
_finalInput: unknown,
finalInput: unknown,
workflowInput: unknown
): NormalizedBlockOutput {
return isPlainObject(workflowInput) ? (workflowInput as NormalizedBlockOutput) : {}
const base: NormalizedBlockOutput = isPlainObject(workflowInput)
? ({ ...(workflowInput as Record<string, unknown>) } as NormalizedBlockOutput)
: {}
if (isPlainObject(finalInput)) {
Object.assign(base, finalInput as Record<string, unknown>)
base.input = { ...(finalInput as Record<string, unknown>) }
} else {
base.input = finalInput
}
return mergeFilesIntoOutput(base, workflowInput)
}
function extractSubBlocks(block: SerializedBlock): Record<string, unknown> | undefined {

View File

@@ -1,5 +1,6 @@
import { createLogger } from '@sim/logger'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { CoreTriggerType } from '@/stores/logs/filters/types'
const logger = createLogger('NotificationQueries')
@@ -18,7 +19,7 @@ export const notificationKeys = {
type NotificationType = 'webhook' | 'email' | 'slack'
type LogLevel = 'info' | 'error'
type TriggerType = 'api' | 'webhook' | 'schedule' | 'manual' | 'chat' | 'mcp'
type TriggerType = CoreTriggerType
type AlertRuleType =
| 'consecutive_failures'

View File

@@ -78,12 +78,16 @@ export interface A2AFile {
export function extractFileContent(message: Message): A2AFile[] {
return message.parts
.filter((part): part is FilePart => part.kind === 'file')
.map((part) => ({
name: part.file.name,
mimeType: part.file.mimeType,
...('uri' in part.file ? { uri: part.file.uri } : {}),
...('bytes' in part.file ? { bytes: part.file.bytes } : {}),
}))
.map((part) => {
const file = part.file as unknown as Record<string, unknown>
const uri = (file.url as string) || (file.uri as string)
return {
name: file.name as string | undefined,
mimeType: file.mimeType as string | undefined,
...(uri ? { uri } : {}),
...(file.bytes ? { bytes: file.bytes as string } : {}),
}
})
}
export interface ExecutionFileInput {

View File

@@ -1,15 +1,8 @@
import { env } from '@/lib/core/config/env'
import type { CoreTriggerType } from '@/stores/logs/filters/types'
import type { TokenBucketConfig } from './storage'
export type TriggerType =
| 'api'
| 'webhook'
| 'schedule'
| 'manual'
| 'chat'
| 'mcp'
| 'form'
| 'api-endpoint'
export type TriggerType = CoreTriggerType | 'form' | 'api-endpoint'
export type RateLimitCounterType = 'sync' | 'async' | 'api-endpoint'

View File

@@ -7,6 +7,7 @@ import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
import { RateLimiter } from '@/lib/core/rate-limiter/rate-limiter'
import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils'
import type { CoreTriggerType } from '@/stores/logs/filters/types'
const logger = createLogger('ExecutionPreprocessing')
@@ -108,7 +109,7 @@ export interface PreprocessExecutionOptions {
// Required fields
workflowId: string
userId: string // The authenticated user ID
triggerType: 'manual' | 'api' | 'webhook' | 'schedule' | 'chat' | 'mcp' | 'form'
triggerType: CoreTriggerType | 'form'
executionId: string
requestId: string

View File

@@ -1,4 +1,4 @@
import { getLatestBlock } from '@/blocks/registry'
import { getBlock } from '@/blocks/registry'
import { getAllTriggers } from '@/triggers'
export interface TriggerOption {
@@ -38,6 +38,7 @@ export function getTriggerOptions(): TriggerOption[] {
{ value: 'form', label: 'Form', color: '#06b6d4' },
{ value: 'webhook', label: 'Webhook', color: '#ea580c' },
{ value: 'mcp', label: 'MCP', color: '#dc2626' },
{ value: 'a2a', label: 'A2A', color: '#14b8a6' },
]
for (const trigger of triggers) {
@@ -48,13 +49,22 @@ export function getTriggerOptions(): TriggerOption[] {
continue
}
const block = getLatestBlock(provider)
const block = getBlock(provider)
providerMap.set(provider, {
value: provider,
label: block?.name || formatProviderName(provider),
color: block?.bgColor || '#6b7280',
})
if (block) {
providerMap.set(provider, {
value: provider,
label: block.name, // Use block's display name (e.g., "Slack", "GitHub")
color: block.bgColor || '#6b7280', // Use block's hex color, fallback to gray
})
} else {
const label = formatProviderName(provider)
providerMap.set(provider, {
value: provider,
label,
color: '#6b7280', // gray fallback
})
}
}
const integrationOptions = Array.from(providerMap.values()).sort((a, b) =>

File diff suppressed because it is too large Load Diff

View File

@@ -2290,7 +2290,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
webhookId: { value: null },
},
}),
@@ -2302,7 +2302,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
webhookId: { value: 'wh_123456' },
},
}),
@@ -2318,7 +2318,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
triggerPath: { value: '' },
},
}),
@@ -2330,7 +2330,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
triggerPath: { value: '/api/webhooks/abc123' },
},
}),
@@ -2346,7 +2346,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
webhookId: { value: null },
triggerPath: { value: '' },
},
@@ -2359,7 +2359,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
webhookId: { value: 'wh_123456' },
triggerPath: { value: '/api/webhooks/abc123' },
},
@@ -2371,18 +2371,14 @@ describe('hasWorkflowChanged', () => {
})
it.concurrent(
'should detect change when actual config differs but runtime metadata also differs',
'should detect change when triggerConfig differs but runtime metadata also differs',
() => {
// Test that when a real config field changes along with runtime metadata,
// the change is still detected. Using 'model' as the config field since
// triggerConfig is now excluded from comparison (individual trigger fields
// are compared separately).
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
webhookId: { value: null },
},
}),
@@ -2394,7 +2390,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4o' },
triggerConfig: { value: { event: 'pull_request' } },
webhookId: { value: 'wh_123456' },
},
}),
@@ -2406,12 +2402,8 @@ describe('hasWorkflowChanged', () => {
)
it.concurrent(
'should not detect change when triggerConfig differs (individual fields compared separately)',
'should not detect change when runtime metadata is added to current state',
() => {
// triggerConfig is excluded from comparison because:
// 1. Individual trigger fields are stored as separate subblocks and compared individually
// 2. The client populates triggerConfig with default values from trigger definitions,
// which aren't present in the deployed state, causing false positive change detection
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1', {
@@ -2428,36 +2420,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
triggerConfig: { value: { event: 'pull_request', extraField: true } },
},
}),
},
})
expect(hasWorkflowChanged(currentState, deployedState)).toBe(false)
}
)
it.concurrent(
'should not detect change when runtime metadata is added to current state',
() => {
const deployedState = createWorkflowState({
blocks: {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
},
}),
},
})
const currentState = createWorkflowState({
blocks: {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
webhookId: { value: 'wh_123456' },
triggerPath: { value: '/api/webhooks/abc123' },
},
@@ -2477,7 +2440,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
webhookId: { value: 'wh_old123' },
triggerPath: { value: '/api/webhooks/old' },
},
@@ -2490,7 +2453,7 @@ describe('hasWorkflowChanged', () => {
block1: createBlock('block1', {
type: 'starter',
subBlocks: {
model: { value: 'gpt-4' },
triggerConfig: { value: { event: 'push' } },
},
}),
},

View File

@@ -174,7 +174,15 @@ export type TimeRange =
export type LogLevel = 'error' | 'info' | 'running' | 'pending' | 'all' | (string & {})
/** Core trigger types for workflow execution */
export const CORE_TRIGGER_TYPES = ['manual', 'api', 'schedule', 'chat', 'webhook', 'mcp'] as const
export const CORE_TRIGGER_TYPES = [
'manual',
'api',
'schedule',
'chat',
'webhook',
'mcp',
'a2a',
] as const
export type CoreTriggerType = (typeof CORE_TRIGGER_TYPES)[number]

View File

@@ -96,3 +96,23 @@ export function buildMeetingOutputs(): Record<string, TriggerOutput> {
},
} as Record<string, TriggerOutput>
}
/**
* Build output schema for generic webhook events
*/
export function buildGenericOutputs(): Record<string, TriggerOutput> {
return {
payload: {
type: 'object',
description: 'Raw webhook payload',
},
headers: {
type: 'object',
description: 'Request headers',
},
timestamp: {
type: 'string',
description: 'ISO8601 received timestamp',
},
} as Record<string, TriggerOutput>
}

View File

@@ -1,6 +1,6 @@
import { CirclebackIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
import { buildMeetingOutputs, circlebackSetupInstructions, circlebackTriggerOptions } from './utils'
import { buildGenericOutputs, circlebackSetupInstructions, circlebackTriggerOptions } from './utils'
export const circlebackWebhookTrigger: TriggerConfig = {
id: 'circleback_webhook',
@@ -74,7 +74,7 @@ export const circlebackWebhookTrigger: TriggerConfig = {
},
],
outputs: buildMeetingOutputs(),
outputs: buildGenericOutputs(),
webhook: {
method: 'POST',

View File

@@ -31,14 +31,8 @@ export const TRIGGER_PERSISTED_SUBBLOCK_IDS: string[] = [
/**
* Trigger-related subblock IDs that represent runtime metadata. They should remain
* in the workflow state but must not be modified or cleared by diff operations.
*
* Note: 'triggerConfig' is included because it's an aggregate of individual trigger
* field subblocks. Those individual fields are compared separately, so comparing
* triggerConfig would be redundant. Additionally, the client populates triggerConfig
* with default values from the trigger definition on load, which aren't present in
* the deployed state, causing false positive change detection.
*/
export const TRIGGER_RUNTIME_SUBBLOCK_IDS: string[] = ['webhookId', 'triggerPath', 'triggerConfig']
export const TRIGGER_RUNTIME_SUBBLOCK_IDS: string[] = ['webhookId', 'triggerPath']
/**
* Maximum number of consecutive failures before a trigger (schedule/webhook) is auto-disabled.

View File

@@ -116,11 +116,6 @@ export const githubIssueClosedTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description:
'GitHub event type from X-GitHub-Event header (e.g., issues, pull_request, push)',
},
action: {
type: 'string',
description: 'Action performed (opened, closed, reopened, edited, etc.)',

View File

@@ -117,10 +117,6 @@ export const githubIssueCommentTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., issue_comment)',
},
action: {
type: 'string',
description: 'Action performed (created, edited, deleted)',

View File

@@ -137,11 +137,6 @@ export const githubIssueOpenedTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description:
'GitHub event type from X-GitHub-Event header (e.g., issues, pull_request, push)',
},
action: {
type: 'string',
description: 'Action performed (opened, closed, reopened, edited, etc.)',

View File

@@ -117,10 +117,6 @@ export const githubPRClosedTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., pull_request)',
},
action: {
type: 'string',
description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)',

View File

@@ -117,10 +117,6 @@ export const githubPRCommentTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., issue_comment)',
},
action: {
type: 'string',
description: 'Action performed (created, edited, deleted)',

View File

@@ -116,10 +116,6 @@ export const githubPRMergedTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., pull_request)',
},
action: {
type: 'string',
description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)',

View File

@@ -116,10 +116,6 @@ export const githubPROpenedTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., pull_request)',
},
action: {
type: 'string',
description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)',

View File

@@ -117,10 +117,6 @@ export const githubPRReviewedTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., pull_request_review)',
},
action: {
type: 'string',
description: 'Action performed (submitted, edited, dismissed)',

View File

@@ -116,14 +116,6 @@ export const githubPushTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., push)',
},
branch: {
type: 'string',
description: 'Branch name derived from ref (e.g., main from refs/heads/main)',
},
ref: {
type: 'string',
description: 'Git reference that was pushed (e.g., refs/heads/main)',

View File

@@ -116,10 +116,6 @@ export const githubReleasePublishedTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., release)',
},
action: {
type: 'string',
description:

View File

@@ -117,10 +117,6 @@ export const githubWorkflowRunTrigger: TriggerConfig = {
],
outputs: {
event_type: {
type: 'string',
description: 'GitHub event type from X-GitHub-Event header (e.g., workflow_run)',
},
action: {
type: 'string',
description: 'Action performed (requested, in_progress, completed)',

View File

@@ -265,6 +265,11 @@ function buildBaseWebhookOutputs(): Record<string, TriggerOutput> {
},
},
},
webhook: {
type: 'json',
description: 'Webhook metadata including provider, path, and raw payload',
},
}
}

View File

@@ -1,7 +1,7 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildEmailBouncedOutputs,
buildActivityOutputs,
buildLemlistExtraFields,
lemlistSetupInstructions,
lemlistTriggerOptions,
@@ -27,7 +27,7 @@ export const lemlistEmailBouncedTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_email_bounced'),
}),
outputs: buildEmailBouncedOutputs(),
outputs: buildActivityOutputs(),
webhook: {
method: 'POST',

View File

@@ -1,7 +1,7 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildEmailClickedOutputs,
buildActivityOutputs,
buildLemlistExtraFields,
lemlistSetupInstructions,
lemlistTriggerOptions,
@@ -27,7 +27,7 @@ export const lemlistEmailClickedTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_email_clicked'),
}),
outputs: buildEmailClickedOutputs(),
outputs: buildActivityOutputs(),
webhook: {
method: 'POST',

View File

@@ -1,7 +1,7 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildEmailOpenedOutputs,
buildActivityOutputs,
buildLemlistExtraFields,
lemlistSetupInstructions,
lemlistTriggerOptions,
@@ -27,7 +27,7 @@ export const lemlistEmailOpenedTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_email_opened'),
}),
outputs: buildEmailOpenedOutputs(),
outputs: buildActivityOutputs(),
webhook: {
method: 'POST',

View File

@@ -1,7 +1,7 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildEmailRepliedOutputs,
buildEmailReplyOutputs,
buildLemlistExtraFields,
lemlistSetupInstructions,
lemlistTriggerOptions,
@@ -30,7 +30,7 @@ export const lemlistEmailRepliedTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_email_replied'),
}),
outputs: buildEmailRepliedOutputs(),
outputs: buildEmailReplyOutputs(),
webhook: {
method: 'POST',

View File

@@ -1,7 +1,7 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildEmailSentOutputs,
buildActivityOutputs,
buildLemlistExtraFields,
lemlistSetupInstructions,
lemlistTriggerOptions,
@@ -27,7 +27,7 @@ export const lemlistEmailSentTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_email_sent'),
}),
outputs: buildEmailSentOutputs(),
outputs: buildActivityOutputs(),
webhook: {
method: 'POST',

View File

@@ -1,7 +1,7 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildInterestOutputs,
buildActivityOutputs,
buildLemlistExtraFields,
lemlistSetupInstructions,
lemlistTriggerOptions,
@@ -27,7 +27,7 @@ export const lemlistInterestedTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_interested'),
}),
outputs: buildInterestOutputs(),
outputs: buildActivityOutputs(),
webhook: {
method: 'POST',

View File

@@ -2,7 +2,7 @@ import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildLemlistExtraFields,
buildLinkedInRepliedOutputs,
buildLinkedInReplyOutputs,
lemlistSetupInstructions,
lemlistTriggerOptions,
} from '@/triggers/lemlist/utils'
@@ -27,7 +27,7 @@ export const lemlistLinkedInRepliedTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_linkedin_replied'),
}),
outputs: buildLinkedInRepliedOutputs(),
outputs: buildLinkedInReplyOutputs(),
webhook: {
method: 'POST',

View File

@@ -1,7 +1,7 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildInterestOutputs,
buildActivityOutputs,
buildLemlistExtraFields,
lemlistSetupInstructions,
lemlistTriggerOptions,
@@ -27,7 +27,7 @@ export const lemlistNotInterestedTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_not_interested'),
}),
outputs: buildInterestOutputs(),
outputs: buildActivityOutputs(),
webhook: {
method: 'POST',

View File

@@ -66,254 +66,203 @@ export function buildLemlistExtraFields(triggerId: string) {
}
/**
* Core fields present in ALL Lemlist webhook payloads
* See: https://help.lemlist.com/en/articles/9423940-use-the-api-to-list-activity-types
* Base activity outputs shared across all Lemlist triggers
*/
const coreOutputs = {
_id: {
type: 'string',
description: 'Unique activity identifier',
},
type: {
type: 'string',
description: 'Activity type (e.g., emailsSent, emailsReplied)',
},
createdAt: {
type: 'string',
description: 'Activity creation timestamp (ISO 8601)',
},
teamId: {
type: 'string',
description: 'Lemlist team identifier',
},
leadId: {
type: 'string',
description: 'Lead identifier',
},
campaignId: {
type: 'string',
description: 'Campaign identifier',
},
campaignName: {
type: 'string',
description: 'Campaign name',
},
} as const
/**
* Lead fields present in webhook payloads
*/
const leadOutputs = {
email: {
type: 'string',
description: 'Lead email address',
},
firstName: {
type: 'string',
description: 'Lead first name',
},
lastName: {
type: 'string',
description: 'Lead last name',
},
companyName: {
type: 'string',
description: 'Lead company name',
},
linkedinUrl: {
type: 'string',
description: 'Lead LinkedIn profile URL',
},
} as const
/**
* Sequence/campaign tracking fields for email activities
*/
const sequenceOutputs = {
sequenceId: {
type: 'string',
description: 'Sequence identifier',
},
sequenceStep: {
type: 'number',
description: 'Current step in the sequence (0-indexed)',
},
totalSequenceStep: {
type: 'number',
description: 'Total number of steps in the sequence',
},
isFirst: {
type: 'boolean',
description: 'Whether this is the first activity of this type for this step',
},
} as const
/**
* Sender information fields
*/
const senderOutputs = {
sendUserId: {
type: 'string',
description: 'Sender user identifier',
},
sendUserEmail: {
type: 'string',
description: 'Sender email address',
},
sendUserName: {
type: 'string',
description: 'Sender display name',
},
} as const
/**
* Email content fields
*/
const emailContentOutputs = {
subject: {
type: 'string',
description: 'Email subject line',
},
text: {
type: 'string',
description: 'Email body content (HTML)',
},
messageId: {
type: 'string',
description: 'Email message ID (RFC 2822 format)',
},
emailId: {
type: 'string',
description: 'Lemlist email identifier',
},
} as const
/**
* Build outputs for email sent events
*/
export function buildEmailSentOutputs(): Record<string, TriggerOutput> {
function buildBaseActivityOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
...senderOutputs,
...emailContentOutputs,
} as Record<string, TriggerOutput>
type: {
type: 'string',
description: 'Activity type (emailsReplied, linkedinReplied, interested, emailsOpened, etc.)',
},
_id: {
type: 'string',
description: 'Unique activity identifier',
},
leadId: {
type: 'string',
description: 'Associated lead ID',
},
campaignId: {
type: 'string',
description: 'Campaign ID',
},
campaignName: {
type: 'string',
description: 'Campaign name',
},
sequenceId: {
type: 'string',
description: 'Sequence ID within the campaign',
},
stepId: {
type: 'string',
description: 'Step ID that triggered this activity',
},
createdAt: {
type: 'string',
description: 'When the activity occurred (ISO 8601)',
},
}
}
/**
* Build outputs for email replied events
* Lead outputs - information about the lead
*/
export function buildEmailRepliedOutputs(): Record<string, TriggerOutput> {
function buildLeadOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
...senderOutputs,
...emailContentOutputs,
} as Record<string, TriggerOutput>
lead: {
_id: {
type: 'string',
description: 'Lead unique identifier',
},
email: {
type: 'string',
description: 'Lead email address',
},
firstName: {
type: 'string',
description: 'Lead first name',
},
lastName: {
type: 'string',
description: 'Lead last name',
},
companyName: {
type: 'string',
description: 'Lead company name',
},
phone: {
type: 'string',
description: 'Lead phone number',
},
linkedinUrl: {
type: 'string',
description: 'Lead LinkedIn profile URL',
},
picture: {
type: 'string',
description: 'Lead profile picture URL',
},
icebreaker: {
type: 'string',
description: 'Personalized icebreaker text',
},
timezone: {
type: 'string',
description: 'Lead timezone (e.g., America/New_York)',
},
isUnsubscribed: {
type: 'boolean',
description: 'Whether the lead is unsubscribed',
},
},
}
}
/**
* Build outputs for email opened events
* Standard activity outputs (activity + lead data)
*/
export function buildEmailOpenedOutputs(): Record<string, TriggerOutput> {
export function buildActivityOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
...senderOutputs,
...buildBaseActivityOutputs(),
...buildLeadOutputs(),
webhook: {
type: 'json',
description: 'Full webhook payload with all activity-specific data',
},
}
}
/**
* Email-specific outputs (includes message content for replies)
*/
export function buildEmailReplyOutputs(): Record<string, TriggerOutput> {
return {
...buildBaseActivityOutputs(),
...buildLeadOutputs(),
messageId: {
type: 'string',
description: 'Email message ID that was opened',
description: 'Email message ID',
},
} as Record<string, TriggerOutput>
}
/**
* Build outputs for email clicked events
*/
export function buildEmailClickedOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
...senderOutputs,
messageId: {
subject: {
type: 'string',
description: 'Email message ID containing the clicked link',
description: 'Email subject line',
},
clickedUrl: {
type: 'string',
description: 'URL that was clicked',
},
} as Record<string, TriggerOutput>
}
/**
* Build outputs for email bounced events
*/
export function buildEmailBouncedOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
...senderOutputs,
messageId: {
type: 'string',
description: 'Email message ID that bounced',
},
errorMessage: {
type: 'string',
description: 'Bounce error message',
},
} as Record<string, TriggerOutput>
}
/**
* Build outputs for LinkedIn replied events
*/
export function buildLinkedInRepliedOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
text: {
type: 'string',
description: 'LinkedIn message content',
description: 'Email reply text content',
},
} as Record<string, TriggerOutput>
html: {
type: 'string',
description: 'Email reply HTML content',
},
sentAt: {
type: 'string',
description: 'When the reply was sent',
},
webhook: {
type: 'json',
description: 'Full webhook payload with all email data',
},
}
}
/**
* Build outputs for interested/not interested events
* LinkedIn-specific outputs (includes message content)
*/
export function buildInterestOutputs(): Record<string, TriggerOutput> {
export function buildLinkedInReplyOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
} as Record<string, TriggerOutput>
...buildBaseActivityOutputs(),
...buildLeadOutputs(),
messageId: {
type: 'string',
description: 'LinkedIn message ID',
},
text: {
type: 'string',
description: 'LinkedIn message text content',
},
sentAt: {
type: 'string',
description: 'When the message was sent',
},
webhook: {
type: 'json',
description: 'Full webhook payload with all LinkedIn data',
},
}
}
/**
* Build outputs for generic webhook (all events)
* Includes all possible fields across event types
* All outputs for generic webhook (activity + lead + all possible fields)
*/
export function buildLemlistOutputs(): Record<string, TriggerOutput> {
export function buildAllOutputs(): Record<string, TriggerOutput> {
return {
...coreOutputs,
...leadOutputs,
...sequenceOutputs,
...senderOutputs,
...emailContentOutputs,
clickedUrl: {
...buildBaseActivityOutputs(),
...buildLeadOutputs(),
messageId: {
type: 'string',
description: 'URL that was clicked (for emailsClicked events)',
description: 'Message ID (for email/LinkedIn events)',
},
errorMessage: {
subject: {
type: 'string',
description: 'Error message (for bounce/failed events)',
description: 'Email subject (for email events)',
},
} as Record<string, TriggerOutput>
text: {
type: 'string',
description: 'Message text content',
},
html: {
type: 'string',
description: 'Message HTML content (for email events)',
},
sentAt: {
type: 'string',
description: 'When the message was sent',
},
webhook: {
type: 'json',
description: 'Full webhook payload with all data',
},
}
}

View File

@@ -1,8 +1,8 @@
import { LemlistIcon } from '@/components/icons'
import { buildTriggerSubBlocks } from '@/triggers'
import {
buildAllOutputs,
buildLemlistExtraFields,
buildLemlistOutputs,
lemlistSetupInstructions,
lemlistTriggerOptions,
} from '@/triggers/lemlist/utils'
@@ -27,7 +27,7 @@ export const lemlistWebhookTrigger: TriggerConfig = {
extraFields: buildLemlistExtraFields('lemlist_webhook'),
}),
outputs: buildLemlistOutputs(),
outputs: buildAllOutputs(),
webhook: {
method: 'POST',

View File

@@ -110,7 +110,6 @@ export const telegramWebhookTrigger: TriggerConfig = {
},
sender: {
id: { type: 'number', description: 'Sender user ID' },
username: { type: 'string', description: 'Sender username (if available)' },
firstName: { type: 'string', description: 'Sender first name' },
lastName: { type: 'string', description: 'Sender last name' },
languageCode: { type: 'string', description: 'Sender language code (if available)' },

View File

@@ -136,8 +136,6 @@ export const typeformWebhookTrigger: TriggerConfig = {
'Array of respondent answers (only includes answered questions). Each answer contains type, value, and field reference.',
},
definition: {
description:
'Form definition (only included when "Include Form Definition" is enabled in trigger settings)',
id: {
type: 'string',
description: 'Form ID',

View File

@@ -96,6 +96,10 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
type: 'string',
description: 'The site ID where the event occurred',
},
workspaceId: {
type: 'string',
description: 'The workspace ID where the event occurred',
},
collectionId: {
type: 'string',
description: 'The collection ID where the item was changed',

View File

@@ -109,6 +109,10 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
type: 'string',
description: 'The site ID where the event occurred',
},
workspaceId: {
type: 'string',
description: 'The workspace ID where the event occurred',
},
collectionId: {
type: 'string',
description: 'The collection ID where the item was created',

View File

@@ -97,6 +97,10 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
type: 'string',
description: 'The site ID where the event occurred',
},
workspaceId: {
type: 'string',
description: 'The workspace ID where the event occurred',
},
collectionId: {
type: 'string',
description: 'The collection ID where the item was deleted',

View File

@@ -76,9 +76,9 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
type: 'string',
description: 'The site ID where the form was submitted',
},
formId: {
workspaceId: {
type: 'string',
description: 'The form ID',
description: 'The workspace ID where the event occurred',
},
name: {
type: 'string',

View File

@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "simstudio",