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
8 changes: 4 additions & 4 deletions apps/docs/openapi-v2-workflows.json
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@
"notes": []
},
"warnings": [
"Workflow lint found issues. Fix these before continuing: Blocks missing required fields: \"Triage\" (systemPrompt)"
"Workflow lint found issues (Blocks missing required fields: 1). The findings are in `lint`."
],
"needsRedeployment": true,
"dryRun": false
Expand Down Expand Up @@ -8885,7 +8885,7 @@
"type": "null"
}
],
"description": "Why a manual run from the editor's Run button would be refused before it starts: no enabled trigger it can start from, more than one API trigger, or a selected trigger with no outgoing connection. `null` when no manual-run issue is reported: the run can begin, only a chat trigger can start the workflow, or the graph is empty (reported in `notes` instead)."
"description": "Why a manual run from the editor's Run button would be refused before it starts: no enabled trigger it can start from, more than one API trigger, or a selected trigger with no outgoing connection. The API run endpoints do not apply these checks. `null` when no manual-run issue is reported: the run can begin, only a chat trigger can start the workflow, or the graph is empty (reported in `notes` instead)."
},
"notes": {
"type": "array",
Expand Down Expand Up @@ -9537,7 +9537,7 @@
"items": {
"type": "string"
},
"description": "Non-fatal notes about blocks and edges that were normalized or dropped before persistence and, when `lint` has findings, one summary of them, so the findings are not lost to a caller that reads only `warnings`. Empty when there is nothing to report."
"description": "Non-fatal notes about blocks and edges that were normalized or dropped before persistence and, when `lint` has findings, one line counting them by category, so a caller that reads only `warnings` still learns they exist. The findings themselves are in `lint`. Empty when there is nothing to report."
},
"needsRedeployment": {
"type": "boolean",
Expand Down Expand Up @@ -9673,7 +9673,7 @@
"notes": []
},
"warnings": [
"Workflow lint found issues. Fix these before continuing: Blocks missing required fields: \"Triage\" (systemPrompt)"
"Workflow lint found issues (Blocks missing required fields: 1). The findings are in `lint`."
],
"needsRedeployment": true,
"dryRun": false
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/api/contracts/v2/openapi/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ const declaredRoutes = [
notes: [],
},
warnings: [
'Workflow lint found issues. Fix these before continuing: Blocks missing required fields: "Triage" (systemPrompt)',
'Workflow lint found issues (Blocks missing required fields: 1). The findings are in `lint`.',
],
needsRedeployment: true,
dryRun: false,
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/api/contracts/v2/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ const v2WorkflowLintSchema = z
})
.nullable()
.describe(
"Why a manual run from the editor's Run button would be refused before it starts: no enabled trigger it can start from, more than one API trigger, or a selected trigger with no outgoing connection. `null` when no manual-run issue is reported: the run can begin, only a chat trigger can start the workflow, or the graph is empty (reported in `notes` instead)."
"Why a manual run from the editor's Run button would be refused before it starts: no enabled trigger it can start from, more than one API trigger, or a selected trigger with no outgoing connection. The API run endpoints do not apply these checks. `null` when no manual-run issue is reported: the run can begin, only a chat trigger can start the workflow, or the graph is empty (reported in `notes` instead)."
),
notes: z.array(z.string()).describe('Advisory notes about the report itself.'),
})
Expand Down Expand Up @@ -3646,7 +3646,7 @@ export const v2ApplyWorkflowOperationsDataSchema = v2WorkflowGraphWriteResultSch
warnings: z
.array(z.string())
.describe(
'Non-fatal notes about blocks and edges that were normalized or dropped before persistence and, when `lint` has findings, one summary of them, so the findings are not lost to a caller that reads only `warnings`. Empty when there is nothing to report.'
'Non-fatal notes about blocks and edges that were normalized or dropped before persistence and, when `lint` has findings, one line counting them by category, so a caller that reads only `warnings` still learns they exist. The findings themselves are in `lint`. Empty when there is nothing to report.'
),
dryRun: z
.boolean()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,17 +269,20 @@ describe('applyWorkflowOperations', () => {
* An agent piping the apply response through a projection of `warnings`
* never saw lint findings, which lived only under `lint`.
*/
it('summarizes lint findings in warnings, on commit and on dry run', async () => {
it('points at lint findings from warnings, on commit and on dry run', async () => {
const orphan = { blockId: 'block-1', blockName: 'Start', blockType: 'starter' }
mocks.lintGraph.mockReturnValue({ ...EMPTY_GRAPH_LINT, orphanBlocks: [orphan] })
mocks.lintGraph.mockReturnValue({
...EMPTY_GRAPH_LINT,
orphanBlocks: [orphan, { ...orphan, blockId: 'block-2', blockName: 'Fn' }],
})

for (const dryRun of [false, true]) {
const result = await applyWorkflowOperations.execute({
principal: sessionPrincipal,
input: { workflowId: 'workflow-1', operations, dryRun },
})
expect(result.warnings).toEqual([
'Workflow lint found issues. Fix these before continuing: Blocks with no incoming edge: "Start" (starter)',
'Workflow lint found issues (Blocks with no incoming edge: 2). The findings are in `lint`.',
])
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
} from '@/lib/workflows/editing/block-enablement'
import { applyOperationsToWorkflowState } from '@/lib/workflows/editing/engine'
import type { WorkflowLintReport } from '@/lib/workflows/editing/lint'
import { formatWorkflowLintMessage, hasWorkflowLintIssues } from '@/lib/workflows/editing/lint'
import { hasWorkflowLintIssues, summarizeWorkflowLintIssues } from '@/lib/workflows/editing/lint'
import { buildWorkflowLintReport } from '@/lib/workflows/editing/lint-report'
import { operationsReferenceSimSandbox } from '@/lib/workflows/editing/sandbox-projection'
import {
Expand Down Expand Up @@ -365,10 +365,10 @@ export const applyWorkflowOperations = defineAuthorizedWorkflowUseCase({
input.checkAborted?.()

/**
* The findings also ride in `warnings`, where a caller that projects the
* response down to its warnings still sees them.
* A caller that projects the response down to its warnings still learns
* the findings exist; the findings themselves stay only in `lint`.
*/
const lintWarnings = hasWorkflowLintIssues(lint) ? [formatWorkflowLintMessage(lint)] : []
const lintWarnings = hasWorkflowLintIssues(lint) ? [summarizeWorkflowLintIssues(lint)] : []

/**
* A dry run still runs the whole engine — operations are applied, refusals
Expand Down
53 changes: 53 additions & 0 deletions apps/sim/lib/workflows/editing/lint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
collectTableBlockFieldIssues,
collectWorkflowFieldIssues,
collectWorkflowTableIds,
formatWorkflowLintMessage,
hasWorkflowLintIssues,
lintEditedWorkflowState,
summarizeWorkflowLintIssues,
} from './lint'

const mockGetToolParams = getToolParams as Mock
Expand Down Expand Up @@ -535,3 +537,54 @@ describe('collectWorkflowTableIds', () => {
expect(ids).toEqual(['tbl_1'])
})
})

/**
* The agent's own runs name their trigger and skip the Run button's checks, so
* a Run-button refusal told under "Fix these before continuing" pushed agents
* to wire a stray Start into the graph instead of removing it.
*/
describe('formatWorkflowLintMessage', () => {
const clean = {
sources: [],
sinks: [],
orphanBlocks: [],
emptyOutgoingPorts: [],
invalidBranchPorts: [],
invalidConnectionTargets: [],
}
const strayStart = {
reason: 'trigger-not-connected' as const,
message: 'Start must be connected to other blocks to execute',
blockId: 'start-1',
blockName: 'Start',
blockType: 'start_trigger',
}

it('reports a Run-button refusal as advice outside the blocking findings, naming both remedies', () => {
const message = formatWorkflowLintMessage({ ...clean, manualRunIssue: strayStart })
expect(message).not.toContain('Fix these before continuing')
expect(message).toBe(
'The editor\'s Run button would fail: Start must be connected to other blocks to execute. Run starts from "Start" (start_trigger) ahead of any other trigger: connect it, or delete it if the workflow starts from another trigger. Runs that name their trigger are not affected.'
)
})

it('points at a Run-button refusal alone without calling it a lint issue', () => {
expect(summarizeWorkflowLintIssues({ ...clean, manualRunIssue: strayStart })).toBe(
"The editor's Run button would fail. The findings are in `lint`."
)
})

it('keeps blocking findings first and the Run-button advice after them', () => {
const message = formatWorkflowLintMessage({
...clean,
orphanBlocks: [{ blockId: 'fn', blockName: 'Fn', blockType: 'function' }],
manualRunIssue: {
reason: 'multiple-api-triggers',
message: 'Multiple API Trigger blocks found. Keep only one.',
},
})
expect(message).toBe(
'Workflow lint found issues. Fix these before continuing: Blocks with no incoming edge: "Fn" (function). The editor\'s Run button would fail: Multiple API Trigger blocks found. Keep only one.'
)
})
})
Loading
Loading