Start, stop and pause flows, and show what their nodes print

Flows can now be taken off the engine and put back. Stopped state lives in a
runtime.json beside the flow, not in the flow document: the canvas autosaves
that document, so a stopped flow would otherwise start itself again on the
next edit. A stopped flow gets no subscriptions, schedules or webhooks, its
nodes are skipped by the scheduler, and running it answers 409. Pausing holds
a flow's nodes while its values keep arriving, so the canvas still shows what
is coming in.

Node code is user code and print is how it says things, so stdout is teed
through a contextvar sink active only during a node execution — one event per
execution, capped, so a chatty node cannot outrun the stream. A node that
fails sends its traceback the same way, trimmed to the author's own frames.
The dock gains a logs panel and a pause control; the dashboard replaces its
placeholder with what is running, stopped or failing; the edge inspector can
send the last message again.

Single-stepping is deferred and noted: the scheduler keeps no progress between
calls, so a step button would re-run the same node rather than advance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Melvin Strobl
2026-08-15 23:35:08 +02:00
co-authored by Claude Fable 5
parent 606ab3c423
commit 7344eac262
29 changed files with 1410 additions and 48 deletions
+20
View File
@@ -166,6 +166,16 @@ export const FlowDetailSchema = {
type: 'boolean',
title: 'Has Draft',
default: false
},
enabled: {
type: 'boolean',
title: 'Enabled',
default: true
},
paused: {
type: 'boolean',
title: 'Paused',
default: false
}
},
type: 'object',
@@ -266,6 +276,16 @@ export const FlowSummarySchema = {
type: 'boolean',
title: 'Has Draft',
default: false
},
enabled: {
type: 'boolean',
title: 'Enabled',
default: true
},
paused: {
type: 'boolean',
title: 'Paused',
default: false
}
},
type: 'object',
+85 -1
View File
@@ -3,7 +3,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
import type { FlowsReadFlowsResponse, FlowsReadNodeTypesResponse, FlowsReadFlowData, FlowsReadFlowResponse, FlowsSaveFlowData, FlowsSaveFlowResponse, FlowsDeleteFlowData, FlowsDeleteFlowResponse, FlowsPublishFlowData, FlowsPublishFlowResponse, FlowsDiscardDraftData, FlowsDiscardDraftResponse, FlowsRenameFlowData, FlowsRenameFlowResponse, FlowsReadNodeSourceData, FlowsReadNodeSourceResponse, FlowsSaveNodeSourceData, FlowsSaveNodeSourceResponse, FlowsValidateFlowData, FlowsValidateFlowResponse, FlowsRunFlowData, FlowsRunFlowResponse, FlowsTriggerNodeData, FlowsTriggerNodeResponse, FlowsReadFlowStateData, FlowsReadFlowStateResponse, FlowsReadMessageHistoryData, FlowsReadMessageHistoryResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, PrivateCreateUserData, PrivateCreateUserResponse, SecretsReadSecretsResponse, SecretsSaveSecretData, SecretsSaveSecretResponse, SecretsDeleteSecretData, SecretsDeleteSecretResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse } from './types.gen';
import type { FlowsReadFlowsResponse, FlowsReadNodeTypesResponse, FlowsReadFlowData, FlowsReadFlowResponse, FlowsSaveFlowData, FlowsSaveFlowResponse, FlowsDeleteFlowData, FlowsDeleteFlowResponse, FlowsPublishFlowData, FlowsPublishFlowResponse, FlowsDiscardDraftData, FlowsDiscardDraftResponse, FlowsRenameFlowData, FlowsRenameFlowResponse, FlowsReadNodeSourceData, FlowsReadNodeSourceResponse, FlowsSaveNodeSourceData, FlowsSaveNodeSourceResponse, FlowsStartFlowData, FlowsStartFlowResponse, FlowsStopFlowData, FlowsStopFlowResponse, FlowsPauseFlowData, FlowsPauseFlowResponse, FlowsResumeFlowData, FlowsResumeFlowResponse, FlowsValidateFlowData, FlowsValidateFlowResponse, FlowsRunFlowData, FlowsRunFlowResponse, FlowsTriggerNodeData, FlowsTriggerNodeResponse, FlowsReadFlowStateData, FlowsReadFlowStateResponse, FlowsReadMessageHistoryData, FlowsReadMessageHistoryResponse, LoginLoginAccessTokenData, LoginLoginAccessTokenResponse, LoginTestTokenResponse, LoginRecoverPasswordData, LoginRecoverPasswordResponse, LoginResetPasswordData, LoginResetPasswordResponse, LoginRecoverPasswordHtmlContentData, LoginRecoverPasswordHtmlContentResponse, PrivateCreateUserData, PrivateCreateUserResponse, SecretsReadSecretsResponse, SecretsSaveSecretData, SecretsSaveSecretResponse, SecretsDeleteSecretData, SecretsDeleteSecretResponse, UsersReadUsersData, UsersReadUsersResponse, UsersCreateUserData, UsersCreateUserResponse, UsersReadUserMeResponse, UsersDeleteUserMeResponse, UsersUpdateUserMeData, UsersUpdateUserMeResponse, UsersUpdatePasswordMeData, UsersUpdatePasswordMeResponse, UsersRegisterUserData, UsersRegisterUserResponse, UsersReadUserByIdData, UsersReadUserByIdResponse, UsersUpdateUserData, UsersUpdateUserResponse, UsersDeleteUserData, UsersDeleteUserResponse, UtilsTestEmailData, UtilsTestEmailResponse, UtilsHealthCheckResponse } from './types.gen';
export class FlowsService {
/**
@@ -225,6 +225,90 @@ export class FlowsService {
});
}
/**
* Start Flow
* Let the engine run this flow again.
* @param data The data for the request.
* @param data.name
* @returns FlowDetail Successful Response
* @throws ApiError
*/
public static startFlow(data: FlowsStartFlowData): CancelablePromise<FlowsStartFlowResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/flows/{name}/start',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Stop Flow
* Take this flow off the engine: no subscriptions, schedules or webhooks.
* @param data The data for the request.
* @param data.name
* @returns FlowDetail Successful Response
* @throws ApiError
*/
public static stopFlow(data: FlowsStopFlowData): CancelablePromise<FlowsStopFlowResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/flows/{name}/stop',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Pause Flow
* Hold the flow's nodes so its messages can be stepped through.
* @param data The data for the request.
* @param data.name
* @returns Message Successful Response
* @throws ApiError
*/
public static pauseFlow(data: FlowsPauseFlowData): CancelablePromise<FlowsPauseFlowResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/flows/{name}/pause',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Resume Flow
* Let the flow carry on, running whatever was held back.
* @param data The data for the request.
* @param data.name
* @returns Message Successful Response
* @throws ApiError
*/
public static resumeFlow(data: FlowsResumeFlowData): CancelablePromise<FlowsResumeFlowResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/flows/{name}/resume',
path: {
name: data.name
},
errors: {
422: 'Validation Error'
}
});
}
/**
* Validate Flow
* Report what would keep this flow from running.
+28
View File
@@ -51,6 +51,8 @@ export type FlowDetail = {
nodes?: Array<NodeStatusPublic>;
issues?: Array<ValidationIssue>;
has_draft?: boolean;
enabled?: boolean;
paused?: boolean;
};
/**
@@ -87,6 +89,8 @@ export type FlowSummary = {
node_count?: number;
error_count?: number;
has_draft?: boolean;
enabled?: boolean;
paused?: boolean;
};
/**
@@ -382,6 +386,30 @@ export type FlowsSaveNodeSourceData = {
export type FlowsSaveNodeSourceResponse = (NodeStatusPublic);
export type FlowsStartFlowData = {
name: string;
};
export type FlowsStartFlowResponse = (FlowDetail);
export type FlowsStopFlowData = {
name: string;
};
export type FlowsStopFlowResponse = (FlowDetail);
export type FlowsPauseFlowData = {
name: string;
};
export type FlowsPauseFlowResponse = (Message);
export type FlowsResumeFlowData = {
name: string;
};
export type FlowsResumeFlowResponse = (Message);
export type FlowsValidateFlowData = {
name: string;
};