Skip to content

Commit ee1965d

Browse files
Merge branch 'simstudioai:main' into fix/sub-block-tooltip-rendering
2 parents f21b57b + f8f3758 commit ee1965d

File tree

88 files changed

+1400
-521
lines changed

Some content is hidden

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

88 files changed

+1400
-521
lines changed

apps/docs/content/docs/en/blocks/human-in-the-loop.mdx

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Defines the fields approvers fill in when responding. This data becomes availabl
7878
}
7979
```
8080

81-
Access resume data in downstream blocks using `<blockId.resumeInput.fieldName>`.
81+
Access resume data in downstream blocks using `<blockId.fieldName>`.
8282

8383
## Approval Methods
8484

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

96-
Programmatically resume workflows using the resume endpoint. The `contextId` is available from the block's `resumeEndpoint` output or from the paused execution detail.
96+
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.
9797

9898
```bash
9999
POST /api/resume/{workflowId}/{executionId}/{contextId}
100100
Content-Type: application/json
101+
X-API-Key: your-api-key
101102

102103
{
103104
"input": {
@@ -107,23 +108,44 @@ Access resume data in downstream blocks using `<blockId.resumeInput.fieldName>`.
107108
}
108109
```
109110

110-
The response includes a new `executionId` for the resumed execution:
111+
The resume endpoint automatically respects the execution mode used in the original execute call:
112+
113+
- **Sync mode** (default) — The response waits for the remaining workflow to complete and returns the full result:
114+
115+
```json
116+
{
117+
"success": true,
118+
"status": "completed",
119+
"executionId": "<resumeExecutionId>",
120+
"output": { ... },
121+
"metadata": { "duration": 1234, "startTime": "...", "endTime": "..." }
122+
}
123+
```
124+
125+
If the resumed workflow hits another HITL block, the response returns `"status": "paused"` with new `_resume` URLs in the output.
126+
127+
- **Stream mode** (`stream: true` on the original execute call) — The resume response streams SSE events with `selectedOutputs` chunks, just like the initial execution.
128+
129+
- **Async mode** (`X-Execution-Mode: async` on the original execute call) — The resume dispatches execution to a background worker and returns immediately with `202`:
111130

112131
```json
113132
{
114133
"status": "started",
115134
"executionId": "<resumeExecutionId>",
116-
"message": "Resume execution started."
135+
"message": "Resume execution started asynchronously."
117136
}
118137
```
119138

120-
To poll execution progress after resuming, connect to the SSE stream:
139+
#### Polling execution status
140+
141+
To check on a paused execution or poll for completion after an async resume:
121142

122143
```bash
123-
GET /api/workflows/{workflowId}/executions/{resumeExecutionId}/stream
144+
GET /api/resume/{workflowId}/{executionId}
145+
X-API-Key: your-api-key
124146
```
125147

126-
Build custom approval UIs or integrate with existing systems.
148+
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.
127149
</Tab>
128150
<Tab>
129151
### Webhook
@@ -132,6 +154,53 @@ Access resume data in downstream blocks using `<blockId.resumeInput.fieldName>`.
132154
</Tab>
133155
</Tabs>
134156

157+
## API Execute Behavior
158+
159+
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:
160+
161+
<Tabs items={['Sync (JSON)', 'Stream (SSE)', 'Async']}>
162+
<Tab>
163+
The response includes the full pause data with resume URLs:
164+
165+
```json
166+
{
167+
"success": true,
168+
"executionId": "<executionId>",
169+
"output": {
170+
"data": {
171+
"operation": "human",
172+
"_resume": {
173+
"apiUrl": "/api/resume/{workflowId}/{executionId}/{contextId}",
174+
"uiUrl": "/resume/{workflowId}/{executionId}",
175+
"contextId": "<contextId>",
176+
"executionId": "<executionId>",
177+
"workflowId": "<workflowId>"
178+
}
179+
}
180+
}
181+
}
182+
```
183+
</Tab>
184+
<Tab>
185+
Blocks before the HITL stream their `selectedOutputs` normally. When execution pauses, the final SSE event includes `status: "paused"` and the `_resume` data:
186+
187+
```
188+
data: {"blockId":"agent1","chunk":"streamed content..."}
189+
data: {"event":"final","data":{"success":true,"output":{...,"_resume":{...}},"status":"paused"}}
190+
data: "[DONE]"
191+
```
192+
193+
On resume, blocks after the HITL stream their `selectedOutputs` the same way.
194+
195+
<Callout type="info">
196+
HITL blocks are automatically excluded from the `selectedOutputs` dropdown since their data is always included in the pause response.
197+
</Callout>
198+
</Tab>
199+
<Tab>
200+
Returns `202` immediately. Use the polling endpoint to check when the execution pauses.
201+
</Tab>
202+
</Tabs>
203+
135204
## Common Use Cases
136205

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

166-
Access using `<blockId.resumeInput.fieldName>`.
235+
Access using `<blockId.fieldName>`.
167236

168237
## Example
169238

@@ -187,7 +256,7 @@ Access using `<blockId.resumeInput.fieldName>`.
187256
**Downstream Usage:**
188257
```javascript
189258
// Condition block
190-
<approval1.resumeInput.approved> === true
259+
<approval1.approved> === true
191260
```
192261
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>`.
193262

@@ -204,7 +273,7 @@ The example below shows an approval portal as seen by an approver after the work
204273
<FAQ items={[
205274
{ 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." },
206275
{ 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." },
207-
{ 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>." },
276+
{ 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>." },
208277
{ 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." },
209278
{ 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." },
210279
{ 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." },

apps/docs/content/docs/en/tools/athena.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ Retrieve the results of a completed Athena query execution
113113
| `awsAccessKeyId` | string | Yes | AWS access key ID |
114114
| `awsSecretAccessKey` | string | Yes | AWS secret access key |
115115
| `queryExecutionId` | string | Yes | Query execution ID to get results for |
116-
| `maxResults` | number | No | Maximum number of rows to return \(1-1000\) |
116+
| `maxResults` | number | No | Maximum number of rows to return \(1-999\) |
117117
| `nextToken` | string | No | Pagination token from a previous request |
118118

119119
#### Output

apps/docs/content/docs/en/tools/cloudwatch.mdx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
1010
color="linear-gradient(45deg, #B0084D 0%, #FF4F8B 100%)"
1111
/>
1212

13+
{/* MANUAL-CONTENT-START:intro */}
14+
[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.
15+
16+
With the CloudWatch integration, you can:
17+
18+
- **Query Logs (Insights)**: Run CloudWatch Log Insights queries against one or more log groups to analyze log data with a powerful query language
19+
- **Describe Log Groups**: List available CloudWatch log groups in your account, optionally filtered by name prefix
20+
- **Get Log Events**: Retrieve log events from a specific log stream within a log group
21+
- **Describe Log Streams**: List log streams within a log group, ordered by last event time or filtered by name prefix
22+
- **List Metrics**: Browse available CloudWatch metrics, optionally filtered by namespace, metric name, or recent activity
23+
- **Get Metric Statistics**: Retrieve statistical data for a metric over a specified time range with configurable granularity
24+
- **Publish Metric**: Publish custom metric data points to CloudWatch for your own application monitoring
25+
- **Describe Alarms**: List and filter CloudWatch alarms by name prefix, state, or alarm type
26+
27+
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.
28+
{/* MANUAL-CONTENT-END */}
29+
30+
1331
## Usage Instructions
1432

1533
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.
@@ -155,6 +173,34 @@ Get statistics for a CloudWatch metric over a time range
155173
| `label` | string | Metric label |
156174
| `datapoints` | array | Datapoints with timestamp and statistics values |
157175

176+
### `cloudwatch_put_metric_data`
177+
178+
Publish a custom metric data point to CloudWatch
179+
180+
#### Input
181+
182+
| Parameter | Type | Required | Description |
183+
| --------- | ---- | -------- | ----------- |
184+
| `awsRegion` | string | Yes | AWS region \(e.g., us-east-1\) |
185+
| `awsAccessKeyId` | string | Yes | AWS access key ID |
186+
| `awsSecretAccessKey` | string | Yes | AWS secret access key |
187+
| `namespace` | string | Yes | Metric namespace \(e.g., Custom/MyApp\) |
188+
| `metricName` | string | Yes | Name of the metric |
189+
| `value` | number | Yes | Metric value to publish |
190+
| `unit` | string | No | Unit of the metric \(e.g., Count, Seconds, Bytes\) |
191+
| `dimensions` | string | No | JSON string of dimension name/value pairs |
192+
193+
#### Output
194+
195+
| Parameter | Type | Description |
196+
| --------- | ---- | ----------- |
197+
| `success` | boolean | Whether the metric was published successfully |
198+
| `namespace` | string | Metric namespace |
199+
| `metricName` | string | Metric name |
200+
| `value` | number | Published metric value |
201+
| `unit` | string | Metric unit |
202+
| `timestamp` | string | Timestamp when the metric was published |
203+
158204
### `cloudwatch_describe_alarms`
159205

160206
List and filter CloudWatch alarms

apps/docs/content/docs/en/tools/jira_service_management.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,11 @@ Create a new service request in Jira Service Management
113113
| `cloudId` | string | No | Jira Cloud ID for the instance |
114114
| `serviceDeskId` | string | Yes | Service Desk ID \(e.g., "1", "2"\) |
115115
| `requestTypeId` | string | Yes | Request Type ID \(e.g., "10", "15"\) |
116-
| `summary` | string | Yes | Summary/title for the service request |
116+
| `summary` | string | No | Summary/title for the service request \(required unless using Form Answers\) |
117117
| `description` | string | No | Description for the service request |
118118
| `raiseOnBehalfOf` | string | No | Account ID of customer to raise request on behalf of |
119119
| `requestFieldValues` | json | No | Request field values as key-value pairs \(overrides summary/description if provided\) |
120+
| `formAnswers` | json | No | Form answers for form-based request types \(e.g., \{"summary": \{"text": "Title"\}, "customfield_10010": \{"choices": \["10320"\]\}\}\) |
120121
| `requestParticipants` | string | No | Comma-separated account IDs to add as request participants |
121122
| `channel` | string | No | Channel the request originates from \(e.g., portal, email\) |
122123

apps/sim/app/(landing)/integrations/data/integrations.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2044,12 +2044,16 @@
20442044
"name": "Get Metric Statistics",
20452045
"description": "Get statistics for a CloudWatch metric over a time range"
20462046
},
2047+
{
2048+
"name": "Publish Metric",
2049+
"description": "Publish a custom metric data point to CloudWatch"
2050+
},
20472051
{
20482052
"name": "Describe Alarms",
20492053
"description": "List and filter CloudWatch alarms"
20502054
}
20512055
],
2052-
"operationCount": 7,
2056+
"operationCount": 8,
20532057
"triggers": [],
20542058
"triggerCount": 0,
20552059
"authType": "none",

apps/sim/app/api/auth/socket-token/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ export async function POST() {
2323

2424
return NextResponse.json({ token: response.token })
2525
} catch (error) {
26+
// better-auth's sessionMiddleware throws APIError("UNAUTHORIZED") with no message
27+
// when the session is missing/expired — surface this as a 401, not a 500.
28+
if (
29+
error instanceof Error &&
30+
('statusCode' in error || 'status' in error) &&
31+
((error as Record<string, unknown>).statusCode === 401 ||
32+
(error as Record<string, unknown>).status === 'UNAUTHORIZED')
33+
) {
34+
logger.warn('Socket token request with invalid/expired session')
35+
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
36+
}
37+
2638
logger.error('Failed to generate socket token', {
2739
error: error instanceof Error ? error.message : String(error),
2840
stack: error instanceof Error ? error.stack : undefined,

apps/sim/app/api/chat/[identifier]/route.test.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ vi.mock('@/lib/workflows/streaming/streaming', () => ({
140140
createStreamingResponse: vi.fn().mockImplementation(async () => createMockStream()),
141141
}))
142142

143+
vi.mock('@/lib/workflows/executor/execute-workflow', () => ({
144+
executeWorkflow: vi.fn().mockResolvedValue({ success: true, output: {} }),
145+
}))
146+
143147
vi.mock('@/lib/core/utils/sse', () => ({
144148
SSE_HEADERS: {
145149
'Content-Type': 'text/event-stream',
@@ -410,14 +414,7 @@ describe('Chat Identifier API Route', () => {
410414

411415
expect(createStreamingResponse).toHaveBeenCalledWith(
412416
expect.objectContaining({
413-
workflow: expect.objectContaining({
414-
id: 'workflow-id',
415-
userId: 'user-id',
416-
}),
417-
input: expect.objectContaining({
418-
input: 'Hello world',
419-
conversationId: 'conv-123',
420-
}),
417+
executeFn: expect.any(Function),
421418
streamConfig: expect.objectContaining({
422419
isSecureMode: true,
423420
workflowTriggerType: 'chat',
@@ -494,9 +491,9 @@ describe('Chat Identifier API Route', () => {
494491

495492
expect(createStreamingResponse).toHaveBeenCalledWith(
496493
expect.objectContaining({
497-
input: expect.objectContaining({
498-
input: 'Hello world',
499-
conversationId: 'test-conversation-123',
494+
executeFn: expect.any(Function),
495+
streamConfig: expect.objectContaining({
496+
workflowTriggerType: 'chat',
500497
}),
501498
})
502499
)
@@ -510,9 +507,7 @@ describe('Chat Identifier API Route', () => {
510507

511508
expect(createStreamingResponse).toHaveBeenCalledWith(
512509
expect.objectContaining({
513-
input: expect.objectContaining({
514-
input: 'Hello world',
515-
}),
510+
executeFn: expect.any(Function),
516511
})
517512
)
518513
})

apps/sim/app/api/chat/[identifier]/route.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export async function POST(
199199
}
200200

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

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

253254
const stream = await createStreamingResponse({
254255
requestId,
255-
workflow: workflowForExecution,
256-
input: workflowInput,
257-
executingUserId: workspaceOwnerId,
258256
streamConfig: {
259257
selectedOutputs,
260258
isSecureMode: true,
261259
workflowTriggerType: 'chat',
262260
},
263261
executionId,
262+
executeFn: async ({ onStream, onBlockComplete, abortSignal }) =>
263+
executeWorkflow(
264+
workflowForExecution,
265+
requestId,
266+
workflowInput,
267+
workspaceOwnerId,
268+
{
269+
enabled: true,
270+
selectedOutputs,
271+
isSecureMode: true,
272+
workflowTriggerType: 'chat',
273+
onStream,
274+
onBlockComplete,
275+
skipLoggingComplete: true,
276+
abortSignal,
277+
executionMode: 'stream',
278+
},
279+
executionId
280+
),
264281
})
265282

266283
const streamResponse = new NextResponse(stream, {

0 commit comments

Comments
 (0)