feat(wait): Async toggle, chained-wait resume fix, execution status API (#4514)

* fix(wait): poll partially_resumed rows so chained waits resume

The chained-pause flow leaves a row in 'partially_resumed' status (wait1 done, wait2 still waiting). The poll's WHERE filter only matched 'paused', so wait2 was never picked up. Include 'partially_resumed' in the filter.

* feat(wait): make in-process threshold env-overridable for local testing

Adds WAIT_INPROCESS_MAX_MS env var (default 300000ms = 5 min). Lower it locally (e.g. 5000) to exercise the suspend/cron-resume path with short waits.

* feat(wait): add Suspend Workflow toggle, restore 5-min in-process default

* fix(wait): address bot review — setNextResumeAt + suspend unit default

- setNextResumeAt now matches paused OR partially_resumed; otherwise the
  cron poller can't null nextResumeAt after dispatching a chained-wait
  row, so it keeps reappearing in every poll batch until execution ends
  (flagged by both greptile and bugbot)
- Suspend mode now defaults missing timeUnitLong to 'minutes' instead of
  falling back to 'seconds' and immediately erroring (flagged by bugbot)

* improvement(wait): hint the wait-amount cap on the input

Restores the pre-#4331 description on the Wait Amount field so the limit is visible before submit instead of only at runtime. Mentions both the 5 min default and the 30 day cap with Suspend Workflow.

* refactor(wait): rename Suspend Workflow toggle to Async

* fix(wait): reword cap errors to say 'async mode'

* feat(workflows): add GET /workflows/[id]/executions/[executionId] status endpoint

Normalized status (pending|running|paused|completed|failed|cancelled)
across workflowExecutionLogs and pausedExecutions in a single response.
Surfaces paused-state details (resumeAt, pauseKind, blockedOnBlockId)
when a row exists in pausedExecutions, and the error string for failed
runs. finalOutput is opt-in via ?includeOutput=true.

* feat(workflows): support ?selectedOutputs= on execution status endpoint

Returns per-block outputs filtered by selectedOutputs paths (same
shape as the execute endpoint). Reads from executionData.traceSpans,
walks children recursively, and resolves dot-paths into each block's
output. Bare blockId returns the full output.

* fix(wait): drop dead tooltip prop on Async switch

Switch sub-blocks return null from renderLabel (sub-block.tsx:238), so
the tooltip never reached the user. The trade-off explanation already
lives in longDescription and bestPractices. Flagged by bugbot.

* docs(api): document GET /workflows/[id]/executions/[executionId]

Adds the WorkflowExecutionStatus schema and the getWorkflowExecution
operation to the OpenAPI spec, including completed/paused/failed
response examples and the includeOutput + selectedOutputs query params.
Registers the page in the Workflows section of the API reference.

* docs(api): tighten getWorkflowExecution descriptions
This commit is contained in:
Theodore Li
2026-05-15 22:09:06 -04:00
committed by GitHub
parent f76e8e6dcc
commit fffb87901d
10 changed files with 720 additions and 38 deletions
@@ -1,3 +1,10 @@
{
"pages": ["executeWorkflow", "cancelExecution", "listWorkflows", "getWorkflow", "getJobStatus"]
"pages": [
"executeWorkflow",
"getWorkflowExecution",
"cancelExecution",
"listWorkflows",
"getWorkflow",
"getJobStatus"
]
}
+287
View File
@@ -169,6 +169,158 @@
}
}
},
"/api/workflows/{id}/executions/{executionId}": {
"get": {
"operationId": "getWorkflowExecution",
"summary": "Get Execution Status",
"description": "Get the current status of a workflow execution. Returns the run's lifecycle state (`running`, `paused`, `completed`, `failed`, etc.), timing, error, and optionally per-block outputs. Designed for polling \u2014 works for any execution, including ones that pause and resume.",
"tags": ["Workflows"],
"x-codeSamples": [
{
"id": "curl",
"label": "cURL",
"lang": "bash",
"source": "curl \\\n \"https://www.sim.ai/api/workflows/{id}/executions/{executionId}\" \\\n -H \"X-API-Key: YOUR_API_KEY\""
},
{
"id": "curl-with-outputs",
"label": "cURL (with block outputs)",
"lang": "bash",
"source": "curl \\\n \"https://www.sim.ai/api/workflows/{id}/executions/{executionId}?selectedOutputs=blockId,blockId.field&includeOutput=true\" \\\n -H \"X-API-Key: YOUR_API_KEY\""
}
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The unique identifier of the workflow.",
"schema": {
"type": "string",
"example": "wf_1a2b3c4d5e"
}
},
{
"name": "executionId",
"in": "path",
"required": true,
"description": "The unique identifier of the execution.",
"schema": {
"type": "string",
"example": "exec_9f8e7d6c5b"
}
},
{
"name": "includeOutput",
"in": "query",
"required": false,
"description": "When `true` and the execution has `status: completed`, include the workflow's final output in the response.",
"schema": {
"type": "string",
"enum": ["true", "false"]
}
},
{
"name": "selectedOutputs",
"in": "query",
"required": false,
"description": "Comma-separated block-output selectors. A bare `blockId` returns that block's full output; a dot-path like `blockId.field` or `blockId.nested.path` returns just that value. Results are returned in the `blockOutputs` map keyed by the selector string.",
"schema": {
"type": "string",
"example": "c1b90bce-8a82-42a5-b6a5-5762846c2eaf,c1b90bce-8a82-42a5-b6a5-5762846c2eaf.waitDuration"
}
}
],
"responses": {
"200": {
"description": "Execution status returned.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WorkflowExecutionStatus"
},
"examples": {
"completed": {
"summary": "Completed run",
"value": {
"executionId": "9254f1c9-5a11-4a12-91e3-8065293f3609",
"workflowId": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7",
"status": "completed",
"trigger": "api",
"level": "info",
"startedAt": "2026-05-15T19:43:12.189Z",
"endedAt": "2026-05-15T19:45:45.224Z",
"totalDurationMs": 153035,
"paused": null,
"cost": {
"total": 0.005
},
"error": null,
"finalOutput": null,
"blockOutputs": null
}
},
"paused": {
"summary": "Currently paused run",
"value": {
"executionId": "772749f6-ee81-414c-a2c3-671549dd62b8",
"workflowId": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7",
"status": "paused",
"trigger": "manual",
"level": "info",
"startedAt": "2026-05-15T22:25:57.178Z",
"endedAt": "2026-05-15T22:25:57.215Z",
"totalDurationMs": 1,
"paused": {
"pausedAt": "2026-05-15T22:25:57.216Z",
"resumeAt": "2026-05-16T18:25:57.200Z",
"pauseKind": "time",
"blockedOnBlockId": "c1b90bce-8a82-42a5-b6a5-5762846c2eaf",
"pausedExecutionId": "438bf05b-bd3c-4011-b78e-b19c112eeb66",
"pausePointCount": 1,
"resumedCount": 0
},
"cost": {
"total": 0.005
},
"error": null,
"finalOutput": null,
"blockOutputs": null
}
},
"failed": {
"summary": "Failed run",
"value": {
"executionId": "3ccfdeed-a63c-4e86-98e2-8bec723bca52",
"workflowId": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7",
"status": "failed",
"trigger": "api",
"level": "error",
"startedAt": "2026-05-15T22:24:50.991Z",
"endedAt": "2026-05-15T22:24:50.999Z",
"totalDurationMs": 2,
"paused": null,
"cost": {
"total": 0.005
},
"error": "Wait 1: Wait time exceeds maximum of 5 minutes; enable async mode to wait up to 30 days",
"finalOutput": null,
"blockOutputs": null
}
}
}
}
}
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"404": {
"$ref": "#/components/responses/NotFound"
}
}
}
},
"/api/workflows/{id}/executions/{executionId}/cancel": {
"post": {
"operationId": "cancelExecution",
@@ -5831,6 +5983,141 @@
}
}
},
"WorkflowExecutionStatus": {
"type": "object",
"description": "Current status of a workflow execution.",
"properties": {
"executionId": {
"type": "string",
"description": "The unique identifier of the execution.",
"example": "9254f1c9-5a11-4a12-91e3-8065293f3609"
},
"workflowId": {
"type": "string",
"description": "The unique identifier of the workflow.",
"example": "81f661e1-d704-4861-b5c1-5bb3cf57e6a7"
},
"status": {
"type": "string",
"enum": ["pending", "running", "paused", "completed", "failed", "cancelled"],
"description": "Current normalized lifecycle status. `paused` is set when a row exists in pausedExecutions with status `paused` or `partially_resumed`; otherwise the workflowExecutionLogs row's status field is used.",
"example": "completed"
},
"trigger": {
"type": "string",
"enum": ["api", "manual", "schedule", "webhook", "chat"],
"description": "What triggered the execution.",
"example": "api"
},
"level": {
"type": "string",
"enum": ["info", "warning", "error"],
"description": "Log level of the execution.",
"example": "info"
},
"startedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp when execution started.",
"example": "2026-05-15T19:43:12.189Z"
},
"endedAt": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "ISO 8601 timestamp when execution ended. Null while the run is in flight.",
"example": "2026-05-15T19:45:45.224Z"
},
"totalDurationMs": {
"type": "integer",
"nullable": true,
"description": "Total duration of the execution in milliseconds. Null while the run is in flight.",
"example": 153035
},
"paused": {
"type": "object",
"nullable": true,
"description": "Pause-state details. Present only when status is `paused`.",
"properties": {
"pausedAt": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp when the workflow was paused.",
"example": "2026-05-15T22:25:57.216Z"
},
"resumeAt": {
"type": "string",
"format": "date-time",
"nullable": true,
"description": "Earliest scheduled resume time across active pause points. Null for human-only pauses.",
"example": "2026-05-16T18:25:57.200Z"
},
"pauseKind": {
"type": "string",
"enum": ["time", "human"],
"nullable": true,
"description": "What kind of pause the workflow is waiting on.",
"example": "time"
},
"blockedOnBlockId": {
"type": "string",
"nullable": true,
"description": "The block currently blocking resume.",
"example": "c1b90bce-8a82-42a5-b6a5-5762846c2eaf"
},
"pausedExecutionId": {
"type": "string",
"description": "ID of the paused-execution row, useful for cross-referencing with the human-in-the-loop endpoints.",
"example": "438bf05b-bd3c-4011-b78e-b19c112eeb66"
},
"pausePointCount": {
"type": "integer",
"description": "Total number of pause points recorded for this execution.",
"example": 1
},
"resumedCount": {
"type": "integer",
"description": "Number of pause points already resumed.",
"example": 0
}
}
},
"cost": {
"type": "object",
"nullable": true,
"description": "Cost summary. Detailed token / model breakdown lives on the /v1/logs detail endpoint.",
"properties": {
"total": {
"type": "number",
"description": "Total cost in USD.",
"example": 0.005
}
}
},
"error": {
"type": "string",
"nullable": true,
"description": "Error message. Present only when status is `failed`.",
"example": null
},
"finalOutput": {
"type": "object",
"nullable": true,
"description": "The workflow's final output. Returned only when ?includeOutput=true AND status is `completed`.",
"example": null
},
"blockOutputs": {
"type": "object",
"nullable": true,
"description": "Per-block outputs keyed by the selector string. Returned only when `?selectedOutputs` is set.",
"additionalProperties": true,
"example": {
"c1b90bce-8a82-42a5-b6a5-5762846c2eaf.waitDuration": 60000,
"c1b90bce-8a82-42a5-b6a5-5762846c2eaf.status": "completed"
}
}
}
},
"AuditLogEntry": {
"type": "object",
"description": "An enterprise audit log entry recording an action taken in the workspace.",