From cbe708fd95c5ed8912e768939b9e3289ba318b3a Mon Sep 17 00:00:00 2001 From: tomi <10324676+tomi@users.noreply.github.com> Date: Wed, 11 Feb 2026 12:45:08 +0000 Subject: [PATCH 1/2] :rocket: Release 2.8.1 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- packages/@n8n/backend-test-utils/package.json | 2 +- packages/@n8n/db/package.json | 2 +- packages/cli/package.json | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f33c17daf611..cf814b19e75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [2.8.1](https://github.com/n8n-io/n8n/compare/n8n@2.8.0...n8n@2.8.1) (2026-02-11) + + +### Bug Fixes + +* **core:** Revert the fix for execution history when flow includes wait node ([#25610](https://github.com/n8n-io/n8n/issues/25610)) ([61394f7](https://github.com/n8n-io/n8n/commit/61394f7ad15849a111ed2f0227a2ab2c2a470e3c)) + + + # [2.8.0](https://github.com/n8n-io/n8n/compare/n8n@2.7.0...n8n@2.8.0) (2026-02-10) diff --git a/package.json b/package.json index 67a197e547d9..c061b935967f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-monorepo", - "version": "2.8.0", + "version": "2.8.1", "private": true, "engines": { "node": ">=22.16", diff --git a/packages/@n8n/backend-test-utils/package.json b/packages/@n8n/backend-test-utils/package.json index 0b7aed625f7b..dff674853d92 100644 --- a/packages/@n8n/backend-test-utils/package.json +++ b/packages/@n8n/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/backend-test-utils", - "version": "1.8.0", + "version": "1.8.1", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/@n8n/db/package.json b/packages/@n8n/db/package.json index 96362c18689e..cd8fc62319a3 100644 --- a/packages/@n8n/db/package.json +++ b/packages/@n8n/db/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/db", - "version": "1.8.0", + "version": "1.8.1", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", diff --git a/packages/cli/package.json b/packages/cli/package.json index 67642fa8ddd5..e4af20a7e20e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "2.8.0", + "version": "2.8.1", "description": "n8n Workflow Automation Tool", "main": "dist/index", "types": "dist/index.d.ts", From c61466372b1b3fcd95efc7eaef6911b86b3589a9 Mon Sep 17 00:00:00 2001 From: oleg Date: Wed, 11 Feb 2026 11:59:30 +0100 Subject: [PATCH 2/2] fix(ai-builder): Improve plan mode UX: naming, question skipping, thinking states (no-changelog) (#25609) Signed-off-by: Oleg Ivaniv --- .../src/agents/planner.agent.ts | 4 +- .../src/prompts/agents/discovery.prompt.ts | 21 +- .../src/prompts/agents/planner.prompt.ts | 2 +- .../src/tools/submit-questions.tool.ts | 4 +- .../src/utils/stream-processor.ts | 19 ++ .../src/utils/test/stream-processor.test.ts | 34 +++ .../AskAssistantChat/AskAssistantChat.test.ts | 63 ++++- .../AskAssistantChat/AskAssistantChat.vue | 27 ++- .../frontend/@n8n/i18n/src/locales/en.json | 5 +- .../ai/assistant/builder.store.test.ts | 32 +++ .../features/ai/assistant/builder.store.ts | 6 +- .../components/Agent/AskAssistantBuild.vue | 22 +- .../components/Agent/PlanDisplayMessage.vue | 1 + .../components/Agent/PlanModeSelector.test.ts | 14 +- .../components/Agent/PlanModeSelector.vue | 11 +- .../Agent/PlanQuestionsMessage.test.ts | 52 ++++ .../components/Agent/PlanQuestionsMessage.vue | 225 ++++++++---------- .../Agent/UserAnswersMessage.test.ts | 15 +- .../components/Agent/UserAnswersMessage.vue | 21 +- 19 files changed, 369 insertions(+), 209 deletions(-) diff --git a/packages/@n8n/ai-workflow-builder.ee/src/agents/planner.agent.ts b/packages/@n8n/ai-workflow-builder.ee/src/agents/planner.agent.ts index a56bebf6b8aa..a3d0f948ea2c 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/agents/planner.agent.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/agents/planner.agent.ts @@ -35,7 +35,9 @@ export const plannerOutputSchema = z.object({ suggestedNodes: z .array(z.string()) .optional() - .describe('Suggested internal n8n node type names (when known)'), + .describe( + 'Copy exact node type names from discovery_context_suggested_nodes. Do not modify, prefix, or invent names.', + ), }), ) .min(1) diff --git a/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/discovery.prompt.ts b/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/discovery.prompt.ts index d3029c031a7c..ddc7cf9d488b 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/discovery.prompt.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/discovery.prompt.ts @@ -45,7 +45,8 @@ export interface DiscoveryPromptOptions { } const ROLE = `You are a Discovery Agent for n8n AI Workflow Builder. -Identify relevant n8n nodes and their connection-changing parameters for the user's request.`; +Identify relevant n8n nodes and their connection-changing parameters for the user's request. +When the request is underspecified, ask clarifying questions to ensure the right workflow gets built.`; const N8N_EXECUTION_MODEL = `n8n executes each node once per input item. @@ -57,7 +58,7 @@ const PROCESS = `1. Search for nodes matching the user's request using search_no const PROCESS_WITH_QUESTIONS = `1. Search for nodes matching the user's request using search_nodes tool 2. Identify connection-changing parameters from input/output expressions (look for $parameter.X) -3. If the search results reveal a genuine ambiguity that would lead to very different workflows, ask clarifying questions using submit_questions (see clarifying_questions section) +3. Assess: do you have enough information to build exactly what the user wants, or would you need to make assumptions about their intent? If assumptions are needed, ask clarifying questions using submit_questions (see clarifying_questions section) 4. Call submit_discovery_results with your nodesFound array`; const PROCESS_WITH_EXAMPLES = `1. Search for nodes matching the user's request using search_nodes tool @@ -70,7 +71,7 @@ const PROCESS_WITH_EXAMPLES_AND_QUESTIONS = `1. Search for nodes matching the us 2. Identify connection-changing parameters from input/output expressions (look for $parameter.X) 3. Use get_documentation to retrieve best practices for relevant workflow techniques—this provides proven patterns that improve workflow quality 4. Use get_workflow_examples to find real community workflows using mentioned services—these examples show how experienced users structure similar integrations -5. If the search results reveal a genuine ambiguity that would lead to very different workflows, ask clarifying questions using submit_questions (see clarifying_questions section) +5. Assess: do you have enough information to build exactly what the user wants, or would you need to make assumptions about their intent? If assumptions are needed, ask clarifying questions using submit_questions (see clarifying_questions section) 6. Call submit_discovery_results with your nodesFound array`; const AI_NODE_SELECTION = `AI node selection guidance: @@ -215,22 +216,26 @@ Chat Trigger: n8n-hosted chat interface for conversational AI. Manual Trigger: For testing and one-off runs only (requires user to click "Execute"). Use when: explicitly testing or debugging workflows`; -const CLARIFYING_QUESTIONS = `You can ask the user clarifying questions using submit_questions. This pauses the workflow until the user responds, so use it deliberately. +const CLARIFYING_QUESTIONS = `You can ask the user clarifying questions using submit_questions. Asking the right questions produces much better workflows — a quick clarification now prevents building the wrong thing. -Always search for nodes FIRST. Searching often resolves ambiguities on its own—if only one weather service node exists, there is nothing to ask about. Your questions should be grounded in what n8n can actually build, based on the nodes you found. +Always search for nodes FIRST. Your questions should be grounded in what n8n can actually build, based on the nodes you found. But finding relevant nodes doesn't mean you know which ones the user actually wants — assess whether the user's intent is clear enough to pick the right ones. -Ask when the request is vague enough that it could mean multiple fundamentally different workflows. Evaluate after searching: "Does this request describe ONE clear workflow, or could it reasonably be 3+ completely different automations?" +Ask when the request has meaningful gaps — missing services, unclear goals, or unspecified triggers — that would force you to guess in ways the user might disagree with. After searching, decide: do you have enough information to build exactly what the user wants, or are you making assumptions they might not agree with? If you'd need to make more than one significant assumption, ask. Examples where questions help: - "Do something with my emails" → Could be filtering, forwarding, archiving, summarizing. Ask about the goal. - "Set up notifications" → Found Email, Slack, Telegram, SMS nodes. Ask which channel. - "Another automation for weather" → No specific action stated. Ask what should happen. +- "Automate new employee onboarding" → Which systems? What steps? Ask about the scope. +- "Automatically process invoices and update accounting" → Invoices from where? Which accounting tool? Ask about the services. +- "Use AI to help with my content creation" → What kind of content? Blog, social, email? Ask about the use case. Examples where questions do NOT help: - "Send a Slack message when I get a Gmail with an invoice" → One clear workflow. Build it. -- "Check weather every hour and store it" → Specific enough. Build it. -- "Monitor my website for downtime" → Reasonable defaults exist. Build it. +- "Check weather every hour and store it" → Specific enough, reasonable defaults exist. Build it. +- "Monitor my website for downtime" → Clear intent, reasonable defaults exist. Build it. +- "Receive webhook POST data and insert it into PostgreSQL" → All details specified. Build it. diff --git a/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/planner.prompt.ts b/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/planner.prompt.ts index ec758f306642..d78bc3c3a40e 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/planner.prompt.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/prompts/agents/planner.prompt.ts @@ -44,7 +44,7 @@ For additionalSpecs: NEVER mention API keys, credentials, authentication, or acc Rules: - Do not generate workflow JSON. - Do not mention internal n8n node type names in steps — describe what happens in plain language. -- You may include suggestedNodes in the structured output for the builder, but the step description should be human-readable. +- You may include suggestedNodes in the structured output for the builder, but the step description should be human-readable. Copy node names exactly from the discovery_context_suggested_nodes section — do not add prefixes, rename, or invent node names. - If key information is missing, make reasonable assumptions. Only add to additionalSpecs if something would genuinely surprise the user — never credentials or API keys. diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/submit-questions.tool.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/submit-questions.tool.ts index c107a4494bd1..3202be3358ad 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/tools/submit-questions.tool.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/submit-questions.tool.ts @@ -81,8 +81,8 @@ export const submitQuestionsTool = tool( name: 'submit_questions', description: `Ask clarifying questions before proceeding with node discovery. Use when: - The request is ambiguous or has multiple interpretations -- Critical information is missing (which service, what trigger, etc.) -- User preferences would significantly affect which nodes to find +- Key information is missing (which service, what goal, what trigger, etc.) +- You would need to make assumptions the user might disagree with Maximum 5 questions. Provide options where possible. IMPORTANT: Never include "Other" as an option — the UI automatically adds an "Other" free-text input to every question. Only include specific, meaningful options.`, diff --git a/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts b/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts index 50a15ccae1a0..76765037abef 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts @@ -233,6 +233,19 @@ function processOperationsUpdate(update: unknown): StreamOutput | null { return { messages: [workflowUpdateChunk] }; } +/** Handle create_workflow_name node update - emits name as workflow update */ +function processWorkflowNameUpdate(update: unknown): StreamOutput | null { + const typed = update as { workflowJSON?: { name?: string } } | undefined; + if (!typed?.workflowJSON?.name) return null; + + const workflowUpdateChunk: WorkflowUpdateChunk = { + role: 'assistant', + type: 'workflow-updated', + codeSnippet: JSON.stringify(typed.workflowJSON, null, 2), + }; + return { messages: [workflowUpdateChunk] }; +} + /** Handle agent node message update */ function processAgentNodeUpdate(nodeName: string, update: unknown): StreamOutput | null { if (!shouldEmitFromNode(nodeName)) return null; @@ -283,6 +296,12 @@ function processUpdatesChunk(nodeUpdate: Record): StreamOutput return processOperationsUpdate(nodeUpdate.process_operations); } + // Workflow name update - emit so frontend receives the generated name + // before any potential interrupt (e.g., plan mode approval) + if (nodeUpdate.create_workflow_name) { + return processWorkflowNameUpdate(nodeUpdate.create_workflow_name); + } + // Generic agent node handling for (const [nodeName, update] of Object.entries(nodeUpdate)) { if (shouldSkipNode(nodeName)) continue; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts index 0c536da61f79..1373b19d0263 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts @@ -119,6 +119,40 @@ describe('stream-processor', () => { expect(message.codeSnippet).toBe(JSON.stringify(workflowData, null, 2)); }); + it('should handle create_workflow_name with generated name', () => { + const workflowData = { + nodes: [] as unknown[], + connections: {}, + name: 'Email Automation Workflow', + }; + const chunk = { + create_workflow_name: { + workflowJSON: workflowData, + }, + }; + + const result = processStreamChunk('updates', chunk); + + expect(result).toBeDefined(); + expect(result?.messages).toHaveLength(1); + const message = result?.messages[0] as WorkflowUpdateChunk; + expect(message.role).toBe('assistant'); + expect(message.type).toBe('workflow-updated'); + expect(message.codeSnippet).toBe(JSON.stringify(workflowData, null, 2)); + }); + + it('should ignore create_workflow_name without a name', () => { + const chunk = { + create_workflow_name: { + workflowJSON: { nodes: [], connections: {} }, + }, + }; + + const result = processStreamChunk('updates', chunk); + + expect(result).toBeNull(); + }); + it('should ignore chunks without relevant content', () => { const chunk = { responder: { diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts index dc29d4919229..dfeec00f9efb 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts @@ -741,6 +741,45 @@ describe('AskAssistantChat', () => { expect(secondProps.latestStatusText).toBe('assistantChat.thinking.workflowGenerated'); }); + it('should not show "Workflow generated" when workflow-updated precedes tools (naming-only)', () => { + // workflow-updated (naming) arrives before discovery tools — should NOT trigger "Workflow generated" + const messages: ChatUI.AssistantMessage[] = [ + { + id: 'user-1', + role: 'user' as const, + type: 'text' as const, + content: 'Build a workflow', + }, + { + id: 'wu-name', + role: 'assistant' as const, + type: 'workflow-updated' as const, + codeSnippet: '{"nodes": [], "connections": {}, "name": "My Generated Name"}', + }, + createToolMessage({ + id: 't1', + status: 'completed', + toolName: 'search_nodes', + displayTitle: 'Searching nodes', + }), + { + id: 'resp-1', + role: 'assistant' as const, + type: 'text' as const, + content: 'Here is the plan', + }, + ]; + + renderWithDirectives(messages); + + expect(thinkingMessageCallCount).toBe(1); + + // Tool group should show "Thinking" (not "Workflow generated") since + // the workflow-updated was a naming-only update before any tools + const props = getThinkingMessageProps(0); + expect(props.latestStatusText).toBe('assistantChat.thinking.thinking'); + }); + it('should preserve "Workflow generated" on first build group when second build group appears', () => { // Two sequential builds — both should show "Workflow generated" const messages: ChatUI.AssistantMessage[] = [ @@ -966,7 +1005,8 @@ describe('AskAssistantChat', () => { it('should not add thinking spinner to old completed group when new turn is streaming', () => { // Old completed tool group + response + user's new message → streaming starts - // The old group should stay frozen, not get a "Thinking" spinner + // The old group should stay frozen, not get a "Thinking" spinner. + // A new placeholder thinking message appears for the current turn. const messages: ChatUI.AssistantMessage[] = [ createToolMessage({ id: '1', @@ -989,15 +1029,20 @@ describe('AskAssistantChat', () => { renderWithDirectives(messages, { streaming: true, loadingMessage: 'Thinking' }); - expect(thinkingMessageCallCount).toBe(1); + // Old tool group + new turn's thinking placeholder + expect(thinkingMessageCallCount).toBe(2); - const props = getThinkingMessageProps(0); - // Should NOT have a "thinking-item" spinner appended - expect(props.items).toHaveLength(1); - expect(props.items[0].displayTitle).toBe('Search Results'); - expect(props.items[0].status).toBe('completed'); - // Title should be frozen as "Thinking", not the dynamic loading message - expect(props.latestStatusText).toBe('assistantChat.thinking.thinking'); + // Old group should NOT have a spinner appended + const oldGroupProps = getThinkingMessageProps(0); + expect(oldGroupProps.items).toHaveLength(1); + expect(oldGroupProps.items[0].displayTitle).toBe('Search Results'); + expect(oldGroupProps.items[0].status).toBe('completed'); + expect(oldGroupProps.latestStatusText).toBe('assistantChat.thinking.thinking'); + + // New turn's placeholder should show the loading message + const placeholderProps = getThinkingMessageProps(1); + expect(placeholderProps.latestStatusText).toBe('Thinking'); + expect(placeholderProps.isStreaming).toBe(true); }); }); diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue index 517d639fa0bf..7096419790b8 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue @@ -105,7 +105,12 @@ function getToolIdsWithWorkflowUpdate( if (msg.type === 'tool') { currentGroupToolIds.push(msg.id); } else if (msg.type === 'workflow-updated') { - hasWorkflowUpdate = true; + // Only count as a workflow update if tools already exist in this region. + // This prevents naming-only updates (which arrive before tools) from + // causing discovery tool groups to show "Workflow generated". + if (currentGroupToolIds.length > 0) { + hasWorkflowUpdate = true; + } } else { // Group boundary — flush if (hasWorkflowUpdate) { @@ -305,14 +310,12 @@ const showSuggestions = computed(() => { return showPlaceholder.value && props.suggestions && props.suggestions.length > 0; }); -// Check if we have any thinking group (tool messages grouped into thinking blocks) -const hasAnyThinkingGroup = computed(() => { - return normalizedMessages.value.some((msg) => msg.type === 'thinking-group'); -}); - -// Show placeholder when streaming with loading message but no tool messages have arrived yet +// Show placeholder when streaming with loading message but no active tool group for the current turn. +// Check the last message — if it's not a thinking-group, the current turn has no tools yet. const showThinkingPlaceholder = computed(() => { - return props.streaming && props.loadingMessage && !hasAnyThinkingGroup.value; + if (!props.streaming || !props.loadingMessage) return false; + const lastMsg = normalizedMessages.value[normalizedMessages.value.length - 1]; + return !lastMsg || lastMsg.type !== 'thinking-group'; }); const showBottomInput = computed(() => { @@ -586,8 +589,8 @@ defineExpose({ @submit="onSendMessage" @stop="emit('stop')" > - @@ -652,8 +655,8 @@ defineExpose({ @submit="onSendMessage" @stop="emit('stop')" > - - + diff --git a/packages/frontend/editor-ui/src/features/ai/assistant/components/Agent/PlanDisplayMessage.vue b/packages/frontend/editor-ui/src/features/ai/assistant/components/Agent/PlanDisplayMessage.vue index a3038cdcd013..ab1f66fc210d 100644 --- a/packages/frontend/editor-ui/src/features/ai/assistant/components/Agent/PlanDisplayMessage.vue +++ b/packages/frontend/editor-ui/src/features/ai/assistant/components/Agent/PlanDisplayMessage.vue @@ -86,6 +86,7 @@ function approve() {