UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

370 lines 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FlowsApi = void 0; const v4_1 = require("zod/v4"); const CodeGenerationOptions_1 = require("../models/CodeGenerationOptions"); const CreateDonobuFlow_1 = require("../models/CreateDonobuFlow"); const FlowMetadata_1 = require("../models/FlowMetadata"); /** * API controller for managing Donobu flows throughout their lifecycle. * * The FlowsApi provides endpoints for creating, querying, controlling, and managing * Donobu flows. It handles flow metadata operations, code generation, project export, * and runtime control operations like pause/resume/cancel. * * @remarks * This class serves as the HTTP API layer for the {@link DonobuFlowsManager}, translating * REST requests into appropriate manager method calls. All methods are designed to be * used as Express.js route handlers. * * Flow States: * - UNSTARTED: Flow created but not yet initialized * - INITIALIZING: Setting up browser context and initial state * - RUNNING_ACTION: Executing a tool call * - QUERYING_LLM_FOR_NEXT_ACTION: AI determining next action (AUTONOMOUS mode) * - WAITING_ON_USER_FOR_NEXT_ACTION: Waiting for user input (INSTRUCT mode) * - PAUSED: Flow execution temporarily suspended * - RESUMING: Transitioning from paused to active state * - FAILED: Flow terminated unsuccessfully * - SUCCESS: Flow completed successfully */ class FlowsApi { constructor(donobuFlowsManager) { this.donobuFlowsManager = donobuFlowsManager; } /** * Generates Playwright code that can replay a completed flow. * * Creates a Node.js Microsoft Playwright script that reproduces the actions * taken during the specified flow. The generated code uses the Donobu Playwright * extension and can be executed independently. * * @throws {@link FlowNotFoundException} When the specified flow doesn't exist * * @example * ```http * GET /api/flows/abc123/code * * Response: * { * "script": "import { test } from 'donobu';\n\ntest('My Flow', async ({ page }) => {\n await page.goto('https://example.com');\n // ... more actions\n});" * } * ``` */ async getFlowAsCode(req, res) { const flowId = String(req.params.flowId); const options = CodeGenerationOptions_1.CodeGenerationOptionsSchema.optional().parse(req.query); const draftedPlaywrightScript = await this.donobuFlowsManager.getFlowAsPlaywrightScript(flowId, options ?? FlowsApi.DEFAULT_CODE_OPTIONS); res.send({ script: draftedPlaywrightScript }); } /** * Returns the log snapshot for a flow. For running flows, returns the live * in-memory buffer. For completed flows, returns the persisted snapshot. */ async getFlowLogs(req, res) { const flowId = String(req.params.flowId); const snapshot = await this.donobuFlowsManager.getFlowLogs(flowId); res.json(snapshot); } /** * Generates a complete Playwright project structure for multiple flows. * * Creates a full Playwright project with proper dependency management, configuration, * and test files. Automatically resolves and includes flow dependencies to ensure * a complete, runnable project. * * @throws {@link FlowNotFoundException} When any specified flow doesn't exist * * @example * ```http * POST /api/flows/project * Content-Type: application/json * * { * "flowIds": [ "flowId1", "flowId2", "flowId3" ], * "options": { * "areElementIdsVolatile": true, * "disableSelectorFailover": true * } * } * * Response: * { * "files": [ * { * "path": "tests/login-flow.spec.ts", * "content": "import { test } from 'donobu';\n..." * }, * { * "path": "playwright.config.ts", * "content": "import { defineConfig } from 'donobu';\n..." * } * ] * } * ``` */ async getFlowsAsProject(req, res) { const parsedBody = v4_1.z .object({ flowIds: v4_1.z.array(v4_1.z.string()), options: CodeGenerationOptions_1.CodeGenerationOptionsSchema.optional(), }) .parse(req.body); const project = await this.donobuFlowsManager.getFlowsAsPlaywrightProject(parsedBody.flowIds, parsedBody.options ?? FlowsApi.DEFAULT_CODE_OPTIONS); res.json({ files: project.files, }); } /** * Converts a flow into a rerunnable format. * * Returns a {@link CreateDonobuFlow} object that can be used to replay the specified * flow deterministically. The returned parameters include all original tool calls * configured for deterministic execution. * * @throws {@link FlowNotFoundException} When the specified flow doesn't exist * * @example * ```http * GET /api/flows/abc123/rerun * * Response: CreateDonobuFlow object ready for POST to /api/flows * ``` */ async getFlowAsRerun(req, res) { const flowId = String(req.params.flowId); const createDonobuFlow = await this.donobuFlowsManager.getFlowAsRerun(flowId, { areElementIdsVolatile: false, disableSelectorFailover: false, }); res.json(createDonobuFlow); } /** * Retrieves paginated flows with optional filtering. * * Returns a list of flow metadata objects matching the specified criteria. * Supports pagination, filtering by name/state/runMode, and time-based filtering. * Results are ordered by startedAt timestamp in descending order (newest first). * * @throws {@link InvalidParamValueException} When query parameters are invalid * * @example * ```http * GET /api/flows?limit=10&state=SUCCESS&startedAfter=1640995200000 * * Response: * { * "flows": [ * { * "id": "abc123", * "name": "Login Test", * "state": "SUCCESS", * "startedAt": 1641000000000, * // ... other metadata * } * ], * "nextPageToken": "eyJ..." * } * ``` * * Query Parameters: * - `pageToken`: Pagination token for subsequent pages * - `limit`: Maximum results per page (1-100, default varies) * - `name`: Filter by exact flow name * - `partialName`: Filter by partial flow name * - `runMode`: Filter by execution mode (AUTONOMOUS|INSTRUCT|DETERMINISTIC) * - `state`: Filter by current state * - `startedAfter`: Unix timestamp - flows started after this time * - `startedBefore`: Unix timestamp - flows started before this time * - `sortBy`: Column to sort results by (created_at, name, run_mode, state) (default: created_at) * - `sortOrder`: Sort direction (asc, desc) (default: desc) */ async getFlows(req, res) { const query = FlowMetadata_1.FlowsQuerySchema.parse(req.query); const flows = await this.donobuFlowsManager.getFlows(query); res.json({ flows: flows.items, nextPageToken: flows.nextPageToken }); } /** * Creates and starts a new Donobu flow. * * Validates the provided parameters, creates a new flow instance, and begins * execution according to the specified run mode. The flow runs asynchronously * after this method returns. * * @throws {@link InvalidParamValueException} When flow parameters are invalid * @throws {@link ToolRequiresGptException} When tools require GPT but none configured * * @example * ```http * POST /api/flows * Content-Type: application/json * * { * "targetWebsite": "https://example.com", * "overallObjective": "Sign up for an account", * "initialRunMode": "AUTONOMOUS", * "maxToolCalls": 30 * } * * Response: FlowMetadata object with state "UNSTARTED" or "INITIALIZING" * ``` */ async createFlow(req, res) { const flowParams = CreateDonobuFlow_1.CreateDonobuFlowSchema.parse(req.body); const flow = (await this.donobuFlowsManager.createFlow(flowParams)) .donobuFlow; res.json(flow.metadata); } /** * Updates the name of an existing flow. * * Changes the display name of the specified flow. The name is used for * identification and organization purposes and can be null to clear the name. * * @throws {@link FlowNotFoundException} When the specified flow doesn't exist. * @throws {@link InvalidParamValueException} When the name is invalid. * * @example * ```http * POST /api/flows/abc123/rename * Content-Type: application/json * * { * "name": "Updated Flow Name" * } * ``` */ async renameFlow(req, res) { const flowId = String(req.params.flowId); const flowParams = v4_1.z.object({ name: v4_1.z.string() }).parse(req.body); const metadata = await this.donobuFlowsManager.renameFlow(flowId, flowParams.name); res.json(metadata); } /** * Permanently deletes a flow and all associated data. * * Removes the flow from storage including metadata, tool calls, screenshots, * and video recordings. This operation cannot be undone. * * @throws {@link FlowNotFoundException} When the specified flow doesn't exist * @throws {@link CannotDeleteRunningFlowException} When attempting to delete an active flow * * @example * ```http * DELETE /api/flows/abc123 * * Response: HTTP 200 OK * ``` */ async deleteFlow(req, res) { const flowId = String(req.params.flowId); await this.donobuFlowsManager.deleteFlowById(flowId); res.sendStatus(200); } /** * Retrieves complete metadata for a specific flow. * * Returns the full {@link FlowMetadata} object containing current state, * configuration, execution history, and results. * * @throws {@link FlowNotFoundException} When the specified flow doesn't exist * * @example * ```http * GET /api/flows/abc123 * * Response: Complete FlowMetadata object * ``` */ async getFlowMetadata(req, res) { const flowId = String(req.params.flowId); const metadata = await this.donobuFlowsManager.getFlowById(flowId); res.json(metadata); } /** * Pauses execution of an active flow. * * Signals the flow to pause after completing its current action. The flow * will transition to PAUSED state and can be resumed later. Only works * for flows that are not already in a terminal state. * * @throws {@link ActiveFlowNotFoundException} When the flow is not active locally * * @example * ```http * POST /api/flows/abc123/pause * * Response: FlowMetadata with nextState set to "PAUSED" * ``` * * @remarks * This endpoint is only available in LOCAL deployment environments for security. * The flow will complete its current action before pausing. */ async pauseFlow(req, res) { const flowId = String(req.params.flowId); const flow = this.donobuFlowsManager.getActiveFlow(flowId); if (!(0, FlowMetadata_1.isComplete)(flow.metadata.state)) { flow.metadata.nextState = 'PAUSED'; } res.json(flow.metadata); } /** * Resumes execution of a paused flow. * * Signals a paused flow to continue execution. The flow will transition * from PAUSED to RESUMING state and then continue normal operation. * * @throws {@link ActiveFlowNotFoundException} When the flow is not active locally * * @example * ```http * POST /api/flows/abc123/resume * * Response: FlowMetadata with nextState set to "RESUMING" * ``` * * @remarks * This endpoint is only available in LOCAL deployment environments for security. * Only works on flows currently in PAUSED state. */ async resumeFlow(req, res) { const flowId = String(req.params.flowId); const flow = this.donobuFlowsManager.getActiveFlow(flowId); if (flow.metadata.state === 'PAUSED') { flow.metadata.nextState = 'RESUMING'; } res.json(flow.metadata); } /** * Cancels and terminates an active flow. * * Forcibly stops flow execution and sets the state to FAILED. For active * flows, this closes the browser context and terminates the execution thread. * * @throws {@link FlowNotFoundException} When the specified flow doesn't exist * * @example * ```http * POST /api/flows/abc123/cancel * * Response: FlowMetadata with state "FAILED" * ``` * * @remarks * This endpoint is only available in LOCAL deployment environments for security. * Cannot be undone - the flow will need to be recreated to retry. */ async cancelFlow(req, res) { const flowId = String(req.params.flowId); const metadata = await this.donobuFlowsManager.cancelFlow(flowId); res.json(metadata); } } exports.FlowsApi = FlowsApi; FlowsApi.DEFAULT_CODE_OPTIONS = { areElementIdsVolatile: false, disableSelectorFailover: false, playwrightScriptVariant: 'ai', }; //# sourceMappingURL=FlowsApi.js.map