Skip to content

Commit f2b1c73

Browse files
authored
v0.3.6: advanced mode for blocks, async api + bg tasks, trigger blocks
2 parents 06b1d82 + b923c24 commit f2b1c73

File tree

401 files changed

+19668
-3087
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

401 files changed

+19668
-3087
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ bun run dev:sockets
147147
- **Docs**: [Fumadocs](https://fumadocs.vercel.app/)
148148
- **Monorepo**: [Turborepo](https://turborepo.org/)
149149
- **Realtime**: [Socket.io](https://socket.io/)
150+
- **Background Jobs**: [Trigger.dev](https://trigger.dev/)
150151

151152
## Contributing
152153

apps/docs/content/docs/blocks/meta.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
"agent",
55
"api",
66
"condition",
7-
"function",
87
"evaluator",
9-
"router",
10-
"response",
11-
"workflow",
8+
"function",
129
"loop",
13-
"parallel"
10+
"parallel",
11+
"response",
12+
"router",
13+
"webhook_trigger",
14+
"workflow"
1415
]
1516
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
title: Webhook Trigger
3+
description: Trigger workflow execution from external webhooks
4+
---
5+
6+
import { Callout } from 'fumadocs-ui/components/callout'
7+
import { Step, Steps } from 'fumadocs-ui/components/steps'
8+
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
9+
import { Card, Cards } from 'fumadocs-ui/components/card'
10+
import { ThemeImage } from '@/components/ui/theme-image'
11+
12+
The Webhook Trigger block allows external services to trigger your workflow execution through HTTP webhooks. Unlike starter blocks, webhook triggers are pure input sources that start workflows without requiring manual intervention.
13+
14+
<ThemeImage
15+
lightSrc="/static/light/webhooktrigger-light.png"
16+
darkSrc="/static/dark/webhooktrigger-dark.png"
17+
alt="Webhook Trigger Block"
18+
width={350}
19+
height={175}
20+
/>
21+
22+
<Callout>
23+
Webhook triggers cannot receive incoming connections and do not expose webhook data to the workflow. They serve as pure execution triggers.
24+
</Callout>
25+
26+
## Overview
27+
28+
The Webhook Trigger block enables you to:
29+
30+
<Steps>
31+
<Step>
32+
<strong>Receive external triggers</strong>: Accept HTTP requests from external services
33+
</Step>
34+
<Step>
35+
<strong>Support multiple providers</strong>: Handle webhooks from Slack, Gmail, GitHub, and more
36+
</Step>
37+
<Step>
38+
<strong>Start workflows automatically</strong>: Execute workflows without manual intervention
39+
</Step>
40+
<Step>
41+
<strong>Provide secure endpoints</strong>: Generate unique webhook URLs for each trigger
42+
</Step>
43+
</Steps>
44+
45+
## How It Works
46+
47+
The Webhook Trigger block operates as a pure input source:
48+
49+
1. **Generate Endpoint** - Creates a unique webhook URL when configured
50+
2. **Receive Request** - Accepts HTTP POST requests from external services
51+
3. **Trigger Execution** - Starts the workflow when a valid request is received
52+
53+
## Configuration Options
54+
55+
### Webhook Provider
56+
57+
Choose from supported service providers:
58+
59+
<Cards>
60+
<Card title="Slack" href="#">
61+
Receive events from Slack apps and bots
62+
</Card>
63+
<Card title="Gmail" href="#">
64+
Handle email-based triggers and notifications
65+
</Card>
66+
<Card title="Airtable" href="#">
67+
Respond to database changes
68+
</Card>
69+
<Card title="Telegram" href="#">
70+
Process bot messages and updates
71+
</Card>
72+
<Card title="WhatsApp" href="#">
73+
Handle messaging events
74+
</Card>
75+
<Card title="GitHub" href="#">
76+
Process repository events and pull requests
77+
</Card>
78+
<Card title="Discord" href="#">
79+
Respond to Discord server events
80+
</Card>
81+
<Card title="Stripe" href="#">
82+
Handle payment and subscription events
83+
</Card>
84+
</Cards>
85+
86+
### Generic Webhooks
87+
88+
For custom integrations or services not listed above, use the **Generic** provider. This option accepts HTTP POST requests from any client and provides flexible authentication options:
89+
90+
- **Optional Authentication** - Configure Bearer token or custom header authentication
91+
- **IP Restrictions** - Limit access to specific IP addresses
92+
- **Request Deduplication** - Automatic duplicate request detection using content hashing
93+
- **Flexible Headers** - Support for custom authentication header names
94+
95+
The Generic provider is ideal for internal services, custom applications, or third-party tools that need to trigger workflows via standard HTTP requests.
96+
97+
### Webhook Configuration
98+
99+
Configure provider-specific settings:
100+
101+
- **Webhook URL** - Automatically generated unique endpoint
102+
- **Provider Settings** - Authentication and validation options
103+
- **Security** - Built-in rate limiting and provider-specific authentication
104+
105+
## Best Practices
106+
107+
- **Use unique webhook URLs** for each integration to maintain security
108+
- **Configure proper authentication** when supported by the provider
109+
- **Keep workflows independent** of webhook payload structure
110+
- **Test webhook endpoints** before deploying to production
111+
- **Monitor webhook delivery** through provider dashboards
112+
113+
34.4 KB
Loading
35.8 KB
Loading

apps/sim/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,5 @@ next-env.d.ts
5050

5151
# Uploads
5252
/uploads
53+
54+
.trigger

apps/sim/app/api/chat/utils.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,38 @@ import type { NextResponse } from 'next/server'
77
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
88
import { env } from '@/lib/env'
99

10+
// Mock all the problematic imports that cause timeouts
11+
vi.mock('@/db', () => ({
12+
db: {
13+
select: vi.fn(),
14+
update: vi.fn(),
15+
},
16+
}))
17+
18+
vi.mock('@/lib/utils', () => ({
19+
decryptSecret: vi.fn().mockResolvedValue({ decrypted: 'test-secret' }),
20+
}))
21+
22+
vi.mock('@/lib/logs/enhanced-logging-session', () => ({
23+
EnhancedLoggingSession: vi.fn().mockImplementation(() => ({
24+
safeStart: vi.fn().mockResolvedValue(undefined),
25+
safeComplete: vi.fn().mockResolvedValue(undefined),
26+
safeCompleteWithError: vi.fn().mockResolvedValue(undefined),
27+
})),
28+
}))
29+
30+
vi.mock('@/executor', () => ({
31+
Executor: vi.fn(),
32+
}))
33+
34+
vi.mock('@/serializer', () => ({
35+
Serializer: vi.fn(),
36+
}))
37+
38+
vi.mock('@/stores/workflows/server-utils', () => ({
39+
mergeSubblockState: vi.fn().mockReturnValue({}),
40+
}))
41+
1042
describe('Chat API Utils', () => {
1143
beforeEach(() => {
1244
vi.resetModules()
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { runs } from '@trigger.dev/sdk/v3'
2+
import { eq } from 'drizzle-orm'
3+
import { type NextRequest, NextResponse } from 'next/server'
4+
import { getSession } from '@/lib/auth'
5+
import { createLogger } from '@/lib/logs/console-logger'
6+
import { db } from '@/db'
7+
import { apiKey as apiKeyTable } from '@/db/schema'
8+
import { createErrorResponse } from '../../workflows/utils'
9+
10+
const logger = createLogger('TaskStatusAPI')
11+
12+
export async function GET(
13+
request: NextRequest,
14+
{ params }: { params: Promise<{ jobId: string }> }
15+
) {
16+
const { jobId: taskId } = await params
17+
const requestId = crypto.randomUUID().slice(0, 8)
18+
19+
try {
20+
logger.debug(`[${requestId}] Getting status for task: ${taskId}`)
21+
22+
// Try session auth first (for web UI)
23+
const session = await getSession()
24+
let authenticatedUserId: string | null = session?.user?.id || null
25+
26+
if (!authenticatedUserId) {
27+
const apiKeyHeader = request.headers.get('x-api-key')
28+
if (apiKeyHeader) {
29+
const [apiKeyRecord] = await db
30+
.select({ userId: apiKeyTable.userId })
31+
.from(apiKeyTable)
32+
.where(eq(apiKeyTable.key, apiKeyHeader))
33+
.limit(1)
34+
35+
if (apiKeyRecord) {
36+
authenticatedUserId = apiKeyRecord.userId
37+
}
38+
}
39+
}
40+
41+
if (!authenticatedUserId) {
42+
return createErrorResponse('Authentication required', 401)
43+
}
44+
45+
// Fetch task status from Trigger.dev
46+
const run = await runs.retrieve(taskId)
47+
48+
logger.debug(`[${requestId}] Task ${taskId} status: ${run.status}`)
49+
50+
// Map Trigger.dev status to our format
51+
const statusMap = {
52+
QUEUED: 'queued',
53+
WAITING_FOR_DEPLOY: 'queued',
54+
EXECUTING: 'processing',
55+
RESCHEDULED: 'processing',
56+
FROZEN: 'processing',
57+
COMPLETED: 'completed',
58+
CANCELED: 'cancelled',
59+
FAILED: 'failed',
60+
CRASHED: 'failed',
61+
INTERRUPTED: 'failed',
62+
SYSTEM_FAILURE: 'failed',
63+
EXPIRED: 'failed',
64+
} as const
65+
66+
const mappedStatus = statusMap[run.status as keyof typeof statusMap] || 'unknown'
67+
68+
// Build response based on status
69+
const response: any = {
70+
success: true,
71+
taskId,
72+
status: mappedStatus,
73+
metadata: {
74+
startedAt: run.startedAt,
75+
},
76+
}
77+
78+
// Add completion details if finished
79+
if (mappedStatus === 'completed') {
80+
response.output = run.output // This contains the workflow execution results
81+
response.metadata.completedAt = run.finishedAt
82+
response.metadata.duration = run.durationMs
83+
}
84+
85+
// Add error details if failed
86+
if (mappedStatus === 'failed') {
87+
response.error = run.error
88+
response.metadata.completedAt = run.finishedAt
89+
response.metadata.duration = run.durationMs
90+
}
91+
92+
// Add progress info if still processing
93+
if (mappedStatus === 'processing' || mappedStatus === 'queued') {
94+
response.estimatedDuration = 180000 // 3 minutes max from our config
95+
}
96+
97+
return NextResponse.json(response)
98+
} catch (error: any) {
99+
logger.error(`[${requestId}] Error fetching task status:`, error)
100+
101+
if (error.message?.includes('not found') || error.status === 404) {
102+
return createErrorResponse('Task not found', 404)
103+
}
104+
105+
return createErrorResponse('Failed to fetch task status', 500)
106+
}
107+
}
108+
109+
// TODO: Implement task cancellation via Trigger.dev API if needed
110+
// export async function DELETE() { ... }

apps/sim/app/api/schedules/[id]/route.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,29 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
141141
})
142142
}
143143

144+
if (action === 'disable' || (body.status && body.status === 'disabled')) {
145+
if (schedule.status === 'disabled') {
146+
return NextResponse.json({ message: 'Schedule is already disabled' }, { status: 200 })
147+
}
148+
149+
const now = new Date()
150+
151+
await db
152+
.update(workflowSchedule)
153+
.set({
154+
status: 'disabled',
155+
updatedAt: now,
156+
nextRunAt: null, // Clear next run time when disabled
157+
})
158+
.where(eq(workflowSchedule.id, scheduleId))
159+
160+
logger.info(`[${requestId}] Disabled schedule: ${scheduleId}`)
161+
162+
return NextResponse.json({
163+
message: 'Schedule disabled successfully',
164+
})
165+
}
166+
144167
logger.warn(`[${requestId}] Unsupported update action for schedule: ${scheduleId}`)
145168
return NextResponse.json({ error: 'Unsupported update action' }, { status: 400 })
146169
} catch (error) {

0 commit comments

Comments
 (0)