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
91 changes: 80 additions & 11 deletions apps/docs/content/docs/en/blocks/human-in-the-loop.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Defines the fields approvers fill in when responding. This data becomes availabl
}
```

Access resume data in downstream blocks using `<blockId.resumeInput.fieldName>`.
Access resume data in downstream blocks using `<blockId.fieldName>`.

## Approval Methods

Expand All @@ -93,11 +93,12 @@ Access resume data in downstream blocks using `<blockId.resumeInput.fieldName>`.
<Tab>
### REST API

Programmatically resume workflows using the resume endpoint. The `contextId` is available from the block's `resumeEndpoint` output or from the paused execution detail.
Programmatically resume workflows using the resume endpoint. The `contextId` is available from the block's `resumeEndpoint` output or from the `_resume` object in the paused execution response.

```bash
POST /api/resume/{workflowId}/{executionId}/{contextId}
Content-Type: application/json
X-API-Key: your-api-key

{
"input": {
Expand All @@ -107,23 +108,44 @@ Access resume data in downstream blocks using `<blockId.resumeInput.fieldName>`.
}
```

The response includes a new `executionId` for the resumed execution:
The resume endpoint automatically respects the execution mode used in the original execute call:

- **Sync mode** (default) — The response waits for the remaining workflow to complete and returns the full result:

```json
{
"success": true,
"status": "completed",
"executionId": "<resumeExecutionId>",
"output": { ... },
"metadata": { "duration": 1234, "startTime": "...", "endTime": "..." }
}
```

If the resumed workflow hits another HITL block, the response returns `"status": "paused"` with new `_resume` URLs in the output.

- **Stream mode** (`stream: true` on the original execute call) — The resume response streams SSE events with `selectedOutputs` chunks, just like the initial execution.

- **Async mode** (`X-Execution-Mode: async` on the original execute call) — The resume dispatches execution to a background worker and returns immediately with `202`:

```json
{
"status": "started",
"executionId": "<resumeExecutionId>",
"message": "Resume execution started."
"message": "Resume execution started asynchronously."
}
```

To poll execution progress after resuming, connect to the SSE stream:
#### Polling execution status

To check on a paused execution or poll for completion after an async resume:

```bash
GET /api/workflows/{workflowId}/executions/{resumeExecutionId}/stream
GET /api/resume/{workflowId}/{executionId}
X-API-Key: your-api-key
```

Build custom approval UIs or integrate with existing systems.
Returns the full paused execution detail with all pause points, their statuses, and resume links. Returns `404` when the execution has completed and is no longer paused.
</Tab>
<Tab>
### Webhook
Expand All @@ -132,6 +154,53 @@ Access resume data in downstream blocks using `<blockId.resumeInput.fieldName>`.
</Tab>
</Tabs>

## API Execute Behavior

When triggering a workflow via the execute API (`POST /api/workflows/{id}/execute`), HITL blocks cause the execution to pause and return the `_resume` data in the response:

<Tabs items={['Sync (JSON)', 'Stream (SSE)', 'Async']}>
<Tab>
The response includes the full pause data with resume URLs:

```json
{
"success": true,
"executionId": "<executionId>",
"output": {
"data": {
"operation": "human",
"_resume": {
"apiUrl": "/api/resume/{workflowId}/{executionId}/{contextId}",
"uiUrl": "/resume/{workflowId}/{executionId}",
"contextId": "<contextId>",
"executionId": "<executionId>",
"workflowId": "<workflowId>"
}
}
}
}
```
</Tab>
<Tab>
Blocks before the HITL stream their `selectedOutputs` normally. When execution pauses, the final SSE event includes `status: "paused"` and the `_resume` data:

```
data: {"blockId":"agent1","chunk":"streamed content..."}
data: {"event":"final","data":{"success":true,"output":{...,"_resume":{...}},"status":"paused"}}
data: "[DONE]"
```

On resume, blocks after the HITL stream their `selectedOutputs` the same way.

<Callout type="info">
HITL blocks are automatically excluded from the `selectedOutputs` dropdown since their data is always included in the pause response.
</Callout>
</Tab>
<Tab>
Returns `202` immediately. Use the polling endpoint to check when the execution pauses.
</Tab>
</Tabs>

## Common Use Cases

**Content Approval** - Review AI-generated content before publishing
Expand Down Expand Up @@ -161,9 +230,9 @@ Agent (Generate) → Human in the Loop (QA) → Gmail (Send)
**`response`** - Display data shown to the approver (json)
**`submission`** - Form submission data from the approver (json)
**`submittedAt`** - ISO timestamp when the workflow was resumed
**`resumeInput.*`** - All fields defined in Resume Form become available after the workflow resumes
**`<fieldName>`** - All fields defined in Resume Form become available at the top level after the workflow resumes

Access using `<blockId.resumeInput.fieldName>`.
Access using `<blockId.fieldName>`.

## Example

Expand All @@ -187,7 +256,7 @@ Access using `<blockId.resumeInput.fieldName>`.
**Downstream Usage:**
```javascript
// Condition block
<approval1.resumeInput.approved> === true
<approval1.approved> === true
```
The example below shows an approval portal as seen by an approver after the workflow is paused. Approvers can review the data and provide inputs as a part of the workflow resumption. The approval portal can be accessed directly via the unique URL, `<blockId.url>`.

Expand All @@ -204,7 +273,7 @@ The example below shows an approval portal as seen by an approver after the work
<FAQ items={[
{ question: "How long does the workflow stay paused?", answer: "The workflow pauses indefinitely until a human provides input through the approval portal, the REST API, or a webhook. There is no automatic timeout — it will wait until someone responds." },
{ question: "What notification channels can I use to alert approvers?", answer: "You can configure notifications through Slack, Gmail, Microsoft Teams, SMS (via Twilio), or custom webhooks. Include the approval URL in your notification message so approvers can access the portal directly." },
{ question: "How do I access the approver's input in downstream blocks?", answer: "Use the syntax <blockId.resumeInput.fieldName> to reference specific fields from the resume form. For example, if your block ID is 'approval1' and the form has an 'approved' field, use <approval1.resumeInput.approved>." },
{ question: "How do I access the approver's input in downstream blocks?", answer: "Use the syntax <blockId.fieldName> to reference specific fields from the resume form. For example, if your block name is 'approval1' and the form has an 'approved' field, use <approval1.approved>." },
{ question: "Can I chain multiple Human in the Loop blocks for multi-stage approvals?", answer: "Yes. You can place multiple Human in the Loop blocks in sequence to create multi-stage approval workflows. Each block pauses independently and can have its own notification configuration and resume form fields." },
{ question: "Can I resume the workflow programmatically without the portal?", answer: "Yes. Each block exposes a resume API endpoint that you can call with a POST request containing the form data as JSON. This lets you build custom approval UIs or integrate with existing systems like Jira or ServiceNow." },
{ question: "What outputs are available after the workflow resumes?", answer: "The block outputs include the approval portal URL, the resume API endpoint URL, the display data shown to the approver, the form submission data, the raw resume input, and an ISO timestamp of when the workflow was resumed." },
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/en/tools/athena.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Retrieve the results of a completed Athena query execution
| `awsAccessKeyId` | string | Yes | AWS access key ID |
| `awsSecretAccessKey` | string | Yes | AWS secret access key |
| `queryExecutionId` | string | Yes | Query execution ID to get results for |
| `maxResults` | number | No | Maximum number of rows to return \(1-1000\) |
| `maxResults` | number | No | Maximum number of rows to return \(1-999\) |
| `nextToken` | string | No | Pagination token from a previous request |

#### Output
Expand Down
46 changes: 46 additions & 0 deletions apps/docs/content/docs/en/tools/cloudwatch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="linear-gradient(45deg, #B0084D 0%, #FF4F8B 100%)"
/>

{/* MANUAL-CONTENT-START:intro */}
[AWS CloudWatch](https://aws.amazon.com/cloudwatch/) is a monitoring and observability service that provides data and actionable insights for AWS resources, applications, and services. CloudWatch collects monitoring and operational data in the form of logs, metrics, and events, giving you a unified view of your AWS environment.

With the CloudWatch integration, you can:

- **Query Logs (Insights)**: Run CloudWatch Log Insights queries against one or more log groups to analyze log data with a powerful query language
- **Describe Log Groups**: List available CloudWatch log groups in your account, optionally filtered by name prefix
- **Get Log Events**: Retrieve log events from a specific log stream within a log group
- **Describe Log Streams**: List log streams within a log group, ordered by last event time or filtered by name prefix
- **List Metrics**: Browse available CloudWatch metrics, optionally filtered by namespace, metric name, or recent activity
- **Get Metric Statistics**: Retrieve statistical data for a metric over a specified time range with configurable granularity
- **Publish Metric**: Publish custom metric data points to CloudWatch for your own application monitoring
- **Describe Alarms**: List and filter CloudWatch alarms by name prefix, state, or alarm type

In Sim, the CloudWatch integration enables your agents to monitor AWS infrastructure, analyze application logs, track custom metrics, and respond to alarm states as part of automated DevOps and SRE workflows. This is especially powerful when combined with other AWS integrations like CloudFormation and SNS for end-to-end infrastructure management.
{/* MANUAL-CONTENT-END */}


## Usage Instructions

Integrate AWS CloudWatch into workflows. Run Log Insights queries, list log groups, retrieve log events, list and get metrics, and monitor alarms. Requires AWS access key and secret access key.
Expand Down Expand Up @@ -155,6 +173,34 @@ Get statistics for a CloudWatch metric over a time range
| `label` | string | Metric label |
| `datapoints` | array | Datapoints with timestamp and statistics values |

### `cloudwatch_put_metric_data`

Publish a custom metric data point to CloudWatch

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) |
| `awsAccessKeyId` | string | Yes | AWS access key ID |
| `awsSecretAccessKey` | string | Yes | AWS secret access key |
| `namespace` | string | Yes | Metric namespace \(e.g., Custom/MyApp\) |
| `metricName` | string | Yes | Name of the metric |
| `value` | number | Yes | Metric value to publish |
| `unit` | string | No | Unit of the metric \(e.g., Count, Seconds, Bytes\) |
| `dimensions` | string | No | JSON string of dimension name/value pairs |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the metric was published successfully |
| `namespace` | string | Metric namespace |
| `metricName` | string | Metric name |
| `value` | number | Published metric value |
| `unit` | string | Metric unit |
| `timestamp` | string | Timestamp when the metric was published |

### `cloudwatch_describe_alarms`

List and filter CloudWatch alarms
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/content/docs/en/tools/jira_service_management.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,11 @@ Create a new service request in Jira Service Management
| `cloudId` | string | No | Jira Cloud ID for the instance |
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
| `requestTypeId` | string | Yes | Request Type ID \(e.g., "10", "15"\) |
| `summary` | string | Yes | Summary/title for the service request |
| `summary` | string | No | Summary/title for the service request \(required unless using Form Answers\) |
| `description` | string | No | Description for the service request |
| `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of |
| `requestFieldValues` | json | No | Request field values as key-value pairs \(overrides summary/description if provided\) |
| `formAnswers` | json | No | Form answers for form-based request types \(e.g., \{"summary": \{"text": "Title"\}, "customfield_10010": \{"choices": \["10320"\]\}\}\) |
| `requestParticipants` | string | No | Comma-separated account IDs to add as request participants |
| `channel` | string | No | Channel the request originates from \(e.g., portal, email\) |

Expand Down
6 changes: 5 additions & 1 deletion apps/sim/app/(landing)/integrations/data/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -2044,12 +2044,16 @@
"name": "Get Metric Statistics",
"description": "Get statistics for a CloudWatch metric over a time range"
},
{
"name": "Publish Metric",
"description": "Publish a custom metric data point to CloudWatch"
},
{
"name": "Describe Alarms",
"description": "List and filter CloudWatch alarms"
}
],
"operationCount": 7,
"operationCount": 8,
"triggers": [],
"triggerCount": 0,
"authType": "none",
Expand Down
12 changes: 12 additions & 0 deletions apps/sim/app/api/auth/socket-token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ export async function POST() {

return NextResponse.json({ token: response.token })
} catch (error) {
// better-auth's sessionMiddleware throws APIError("UNAUTHORIZED") with no message
// when the session is missing/expired — surface this as a 401, not a 500.
if (
error instanceof Error &&
('statusCode' in error || 'status' in error) &&
((error as Record<string, unknown>).statusCode === 401 ||
(error as Record<string, unknown>).status === 'UNAUTHORIZED')
) {
logger.warn('Socket token request with invalid/expired session')
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}

logger.error('Failed to generate socket token', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
Expand Down
23 changes: 9 additions & 14 deletions apps/sim/app/api/chat/[identifier]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ vi.mock('@/lib/workflows/streaming/streaming', () => ({
createStreamingResponse: vi.fn().mockImplementation(async () => createMockStream()),
}))

vi.mock('@/lib/workflows/executor/execute-workflow', () => ({
executeWorkflow: vi.fn().mockResolvedValue({ success: true, output: {} }),
}))

vi.mock('@/lib/core/utils/sse', () => ({
SSE_HEADERS: {
'Content-Type': 'text/event-stream',
Expand Down Expand Up @@ -410,14 +414,7 @@ describe('Chat Identifier API Route', () => {

expect(createStreamingResponse).toHaveBeenCalledWith(
expect.objectContaining({
workflow: expect.objectContaining({
id: 'workflow-id',
userId: 'user-id',
}),
input: expect.objectContaining({
input: 'Hello world',
conversationId: 'conv-123',
}),
executeFn: expect.any(Function),
streamConfig: expect.objectContaining({
isSecureMode: true,
workflowTriggerType: 'chat',
Expand Down Expand Up @@ -494,9 +491,9 @@ describe('Chat Identifier API Route', () => {

expect(createStreamingResponse).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
input: 'Hello world',
conversationId: 'test-conversation-123',
executeFn: expect.any(Function),
streamConfig: expect.objectContaining({
workflowTriggerType: 'chat',
}),
})
)
Expand All @@ -510,9 +507,7 @@ describe('Chat Identifier API Route', () => {

expect(createStreamingResponse).toHaveBeenCalledWith(
expect.objectContaining({
input: expect.objectContaining({
input: 'Hello world',
}),
executeFn: expect.any(Function),
})
)
})
Expand Down
23 changes: 20 additions & 3 deletions apps/sim/app/api/chat/[identifier]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ export async function POST(
}

const { createStreamingResponse } = await import('@/lib/workflows/streaming/streaming')
const { executeWorkflow } = await import('@/lib/workflows/executor/execute-workflow')
const { SSE_HEADERS } = await import('@/lib/core/utils/sse')

const workflowInput: any = { input, conversationId }
Expand Down Expand Up @@ -252,15 +253,31 @@ export async function POST(

const stream = await createStreamingResponse({
requestId,
workflow: workflowForExecution,
input: workflowInput,
executingUserId: workspaceOwnerId,
streamConfig: {
selectedOutputs,
isSecureMode: true,
workflowTriggerType: 'chat',
},
executionId,
executeFn: async ({ onStream, onBlockComplete, abortSignal }) =>
executeWorkflow(
workflowForExecution,
requestId,
workflowInput,
workspaceOwnerId,
{
enabled: true,
selectedOutputs,
isSecureMode: true,
workflowTriggerType: 'chat',
onStream,
onBlockComplete,
skipLoggingComplete: true,
abortSignal,
executionMode: 'stream',
},
executionId
),
})

const streamResponse = new NextResponse(stream, {
Expand Down
Loading
Loading