From 346fa3c8d2103b6f6e4aaa0b17ac81aeea2a4e65 Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:49:46 +0100 Subject: [PATCH] feat: Add workflow activation/deactivation via diff operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements workflow activation and deactivation as diff operations in n8n_update_partial_workflow tool, following the pattern of other configuration operations. Changes: - Add activateWorkflow/deactivateWorkflow API methods - Add operation types to diff engine - Update tool documentation - Remove activation limitation Resolves #399 Credits: ArtemisAI, cmj-hub for investigation and initial implementation Conceived by Romuald Członkowski - www.aiadvisors.pl/en --- CHANGELOG.md | 105 ++++++++++++++++++ data/nodes.db | Bin 70729728 -> 70729728 bytes package.json | 2 +- package.runtime.json | 2 +- src/mcp/handlers-n8n-manager.ts | 1 - src/mcp/handlers-workflow-diff.ts | 47 +++++++- .../n8n-update-partial-workflow.ts | 11 +- src/services/n8n-api-client.ts | 18 +++ src/services/workflow-diff-engine.ts | 61 +++++++++- src/types/workflow-diff.ts | 14 +++ 10 files changed, 248 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7836f46..bb3527a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,111 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.22.11] - 2025-01-06 + +### ✨ New Features + +**Issue #399: Workflow Activation via Diff Operations** + +Added workflow activation and deactivation as diff operations in `n8n_update_partial_workflow`, using n8n's dedicated API endpoints. + +#### Problem + +The n8n API provides dedicated `POST /workflows/{id}/activate` and `POST /workflows/{id}/deactivate` endpoints, but these were not accessible through n8n-mcp. Users could not programmatically control workflow activation status, forcing manual activation through the n8n UI. + +#### Solution + +Implemented activation/deactivation as diff operations, following the established pattern of metadata operations like `updateSettings` and `updateName`. This keeps the tool count manageable (40 tools, not 42) and provides a consistent interface. + +#### Changes + +**API Client** (`src/services/n8n-api-client.ts`): +- Added `activateWorkflow(id: string): Promise` method +- Added `deactivateWorkflow(id: string): Promise` method +- Both use POST requests to dedicated n8n API endpoints + +**Diff Engine Types** (`src/types/workflow-diff.ts`): +- Added `ActivateWorkflowOperation` interface +- Added `DeactivateWorkflowOperation` interface +- Added `shouldActivate` and `shouldDeactivate` flags to `WorkflowDiffResult` +- Increased supported operations from 15 to 17 + +**Diff Engine** (`src/services/workflow-diff-engine.ts`): +- Added validation for activation (requires activatable triggers) +- Added operation application logic +- Transfers activation intent from workflow object to result +- Validates workflow has activatable triggers (webhook, schedule, etc.) +- Rejects workflows with only `executeWorkflowTrigger` (cannot activate) + +**Handler** (`src/mcp/handlers-workflow-diff.ts`): +- Checks `shouldActivate` and `shouldDeactivate` flags after workflow update +- Calls appropriate API methods +- Includes activation status in response message and details +- Handles activation/deactivation errors gracefully + +**Documentation** (`src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts`): +- Updated operation count from 15 to 17 +- Added "Workflow Activation Operations" section +- Added activation tip to essentials + +**Tool Registration** (`src/mcp/handlers-n8n-manager.ts`): +- Removed "Cannot activate/deactivate workflows via API" from limitations + +#### Usage + +```javascript +// Activate workflow +n8n_update_partial_workflow({ + id: "workflow_id", + operations: [{ + type: "activateWorkflow" + }] +}) + +// Deactivate workflow +n8n_update_partial_workflow({ + id: "workflow_id", + operations: [{ + type: "deactivateWorkflow" + }] +}) + +// Combine with other operations +n8n_update_partial_workflow({ + id: "workflow_id", + operations: [ + {type: "updateNode", nodeId: "abc", updates: {name: "Updated"}}, + {type: "activateWorkflow"} + ] +}) +``` + +#### Validation + +- **Activation**: Requires at least one enabled activatable trigger node +- **Deactivation**: Always valid +- **Error Handling**: Clear messages when activation fails due to missing triggers +- **Trigger Detection**: Uses `isActivatableTrigger()` utility (Issue #351 compliance) + +#### Benefits + +- ✅ Consistent with existing architecture (metadata operations pattern) +- ✅ Keeps tool count at 40 (not 42) +- ✅ Atomic operations - activation happens after workflow update +- ✅ Proper validation - prevents activation without triggers +- ✅ Clear error messages - guides users on trigger requirements +- ✅ Works with other operations - can update and activate in one call + +#### Credits + +- **@ArtemisAI** - Original investigation and API endpoint discovery +- **@cmj-hub** - Implementation attempt and PR contribution +- Architectural guidance from project maintainer + +Resolves #399 + +Conceived by Romuald Członkowski - [www.aiadvisors.pl/en](https://www.aiadvisors.pl/en) + ## [2.22.10] - 2025-11-04 ### 🐛 Bug Fixes diff --git a/data/nodes.db b/data/nodes.db index 715851255604e56fbd2e9163a643e75c711f3f4b..1ccd821aa0782eef7d687a931a639182888d9158 100644 GIT binary patch delta 4096 zcmWmD)3O){00hw_H@0otwrx8T+cqb*ZQHhO+qRwUeK~tz*Z0|zzS*wvw~Y8tdLeHE3_5H3TuV4!dnrn zh*l&kvK7UOYDKf6TQRJdRxB&F6~~He#k1mD39N)xA}g_##7b%ZYE`qUTQ#hjRxPWxRmZAp)wAkb4J`UJv>I8B zttM7etC`i@YGJjsT3M~FHdb4!oz>pzV0E-QS)HveR#&T=)!pi0^|X3fy{$f0U#p+h z-x^>Iv<6v&ts$1Jq1G^KxHZBWX^pZ*TVt%T);MdtHNl!_O|m9iQ>>}hG;6vw!T4*h@7F$cKrPeZQxwXPtX|1wWTWhSf);epwwZYnGZL&67Tdb|t zHfy`J!`f->vUXd0ti9GgYrl2CI%plT4qHd8qt-F&xOKuhX`Ql8TW74Z);a6Eb-}u5 zU9v7)SFEenHS4-{!@6nRvTj>%R5CdT2ee9$QbWr`9v;x%I+&X}z*uTW_ql z);sIH^}+gReX>4VU#zdzH|x9g!}@9cvVL2CtiRU35ehs;fe{1&2#R0`jt~fmPza4M z2#atCj|hl}NQjImh>B>4ju?oEScr`{h>LiLj|51FL`aMzNQz`gjuc3VR7j09NQ-nx zj||9&OvsEZ$ck*pjvUB|T*!?)$cuc)j{+!&LMV(PD2iezjuI$|QYeiwD2s9^j|!-W zN~nw~sETT+jvACfiG(&T=KufejYqUXIv_pGzKu2^! zXLLbVbVGOaKu`2SZ}dT5^h19Pz(5SbU<`pV6vHqaBQO%9FdAbp7UM7;6EG2zFd0)Y z71J;sGcXggFdK6)7xOS53$PH2uoz3Q6w9z2E3gu)uo`Qy7VEGc8?X_Zuo+vh72B{K zJFpYGup4`@7yGau2XGLFa2Q8$6vuEJCvXy{a2jWD7Uyst7jO}ma2Z!{71wYbH*gcT za2t1U7x!=<5AYC=@EA|<6wmM+FYpqt@EULM7Vq#LAMg>M@EKq572oh3KkyU3@Ed>d z7ykl}P!RS<5CkA7f+09UAS6N|G{PV(!XZ2&AR;0mGNK?Vq9HnBASPlVHsT;I;vqf~ zAR!VVF_IuDk|8-#ASF^EHPRq0(jh%EAR{s%GqNBnvLQQiASZGmH}W7a@*zJ8pdbpN zFp8ikilI14pd?D6G|HeX%Aq_epdu=vGOC~|s-Ze+peAaeHtL`*>Y+Xw;Q#&&(Fl#v z1WnNl&Cvoa(F(2625r#}?a=`p(FvW=1zph%-O&R*(F?uN2Yt~G{V@OoF$jY(1jbMd z!*GniNQ}a0jKNrp!+1=!*QIzNu0uI zoWWU~!+Bi5MO?yVT)|ab!*$%iP29q5+`(Pk!+ku!Lp;J`Ji${u!*jgAOT5Bsyun+% z!+U(dM|{F(e8E?I!*~3^PyE7f{J~%R3o=3h?2jM_Ku`ojaD+feghFV9L0E)Cctk)% zL_%alK~zLTbi_bR#6oPuL0rT`d?Y|ZBtl{&K~f|`a-={?q(W+>L0Y6kdSpOGWI|?S zK~`i#cH}@#k zb<{vj)Ix34L0!~CeKf%T{TreY8lwrCq8XZ_1zMsNTB8lxq8-|!13ID;I-?7^q8qxS z2YR9xdZQ2eq96KW00v?Z24e_}p%{kY7=e)(h0z#;u^5N(n1G3xgvpqKshEc8n1Pv? zh1r;cxtNFfSb&9CgvD5brC5gLSb>#Th1FPtwOEJs*no}Lgw5E3t=NX`*nyqch27YL zz1WBSIDmsVgu^(3qd11+IDwNmh0{2Lvp9$IxPXhegv+>stGI^ixPhCvh1SUIg+R&FbgmDkE=<+lo01+79>VXKH$)GB5bw@O$g ztx{HLtBh6FDrc3qDp(b*N>*j7idEIBW>vRpST(I$R&A?}RoAL#)wdd04K4aKvKm`W ztfp2otGU&}YH78yT3cE= zS+lJ<)?90zHQ!obEwmO{i>)QrQfryD+*)C+v{qTGtu@wKYn`>;+F)(8Hd&jkE!I|R zo3-8AVePbbS-Y)0)?RC$wck2m9kdQvhpi*lQR|p>+&W>Mv`$&4tuxkH>zsAox?o+j zE?JkYE7n!(nswc}VcoQDS+}h_)?MqKb>DhmJ+vNKkF6)xQ|p=a+z(!9`e1#uK3SiwFV1VeCyKuCl_XoNvn zghO~lKtx1BWJEz!L_>7MKup9!Y{Wra#6x@}Ktd!!VkALQBtvqfKuV-SYNSD0q(gdS zKt^OjW@JHDWJ7l3Ku+XBZsb8;8KuMHBX_P@(ltXz`Kt)tS zWmG{`R6}*tKuy#_ZPYcP#W|eE1zf}>T*eh##Wh^V4cx>n z+{PW;#Xa1|13bhdJjN3|#WOs|3%tZDyv7^6#XG#m2YkdQe8v}i#W#G%5B$V0{Kg;r z#lOG-3c~&ff}jY7;0S?`2!+rHgRlsP@Q8qjh=j<9f~bgw=!k)sh=tgQgSd!?_(*_+ zNQA^lf}}`>f~u&7>ZpO5sD;|7gSx1P`e=ZL_`iQ6G)5CN zMKd%<3$#Qlv_>1WMLV=d2XsUybVe6+MK^Ru5A;MY^hO`_ML+b%01U(+48{-)g)t1n zF#-V?iBTAhF&K++7>@~Th(~ygCwPiyc#ao%iC1`yH+YM8 zc#jYGh)?*8FZhaY_>Ld=iC_4QKlqD(K>`$%{SgE~5e&f*0wEC!p%DgQ5f0%I0TB@i zkr4$^5e?B112GW`u@MJx5fAZ^011%@iID_JkqpU^0x6LSsgVY0kq+sR0U41AnUMuq zkqz0A138fkxseBXkq`M%00mJ9g;4}WQ4GaV0wqxjrBMcDQ4Zx%0TodRl~Dy%Q4Q5m z12s_#wNVFkQ4jUe01fef|3+wxCTNOgXpRXpau)h)(E?F6fGG=#C!f ziC*Z9KIn^n=#K#yh(Q>PAs7l{7=~j60x%MzFdAbp7UM7;6EG2zFd0)Y71J;sGcXgg zFdK6)7xOS53$PH2uoz3Q6w9z2E3gu)uo`Qy7VEGc8?X_Zuo+vh72B{KJFpYGup4`@ z7yGau2XGLFa2Q8$6vuEJCvXy{a2jWD7Uyst7jO}ma2Z!{71wYbH*gcTa2t1U7x!=< z5AYC=@EA|<6wmM+FYpqt@EULM7Vq#LAMg>M@EKq572oh3KkyU3@Ed>d7yp6=C>Z-A z2!bLQf+GY%A{0U+48kHD!XpAAA`&7a3Zfz!q9X=kA{JsJ4&ovn;v)ePA`ucJ36dfi nk|PCDA{A024bmbV(jx;hA`>zr3$h{`vLi>ZJ<1tue!>3$rIYvS diff --git a/package.json b/package.json index 2dd31cb..5cc73ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp", - "version": "2.22.10", + "version": "2.22.11", "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/package.runtime.json b/package.runtime.json index 0828b94..50bdc40 100644 --- a/package.runtime.json +++ b/package.runtime.json @@ -1,6 +1,6 @@ { "name": "n8n-mcp-runtime", - "version": "2.22.10", + "version": "2.22.11", "description": "n8n MCP Server Runtime Dependencies Only", "private": true, "dependencies": { diff --git a/src/mcp/handlers-n8n-manager.ts b/src/mcp/handlers-n8n-manager.ts index b8b50ab..4dd229e 100644 --- a/src/mcp/handlers-n8n-manager.ts +++ b/src/mcp/handlers-n8n-manager.ts @@ -1561,7 +1561,6 @@ export async function handleListAvailableTools(context?: InstanceContext): Promi maxRetries: config.maxRetries } : null, limitations: [ - 'Cannot activate/deactivate workflows via API', 'Cannot execute workflows directly (must use webhooks)', 'Cannot stop running executions', 'Tags and credentials have limited API support' diff --git a/src/mcp/handlers-workflow-diff.ts b/src/mcp/handlers-workflow-diff.ts index 59c40c9..5498ed1 100644 --- a/src/mcp/handlers-workflow-diff.ts +++ b/src/mcp/handlers-workflow-diff.ts @@ -245,15 +245,52 @@ export async function handleUpdatePartialWorkflow( // Update workflow via API try { const updatedWorkflow = await client.updateWorkflow(input.id, diffResult.workflow!); - + + // Handle activation/deactivation if requested + let finalWorkflow = updatedWorkflow; + let activationMessage = ''; + + if (diffResult.shouldActivate) { + try { + finalWorkflow = await client.activateWorkflow(input.id); + activationMessage = ' Workflow activated.'; + } catch (activationError) { + logger.error('Failed to activate workflow after update', activationError); + return { + success: false, + error: 'Workflow updated successfully but activation failed', + details: { + workflowUpdated: true, + activationError: activationError instanceof Error ? activationError.message : 'Unknown error' + } + }; + } + } else if (diffResult.shouldDeactivate) { + try { + finalWorkflow = await client.deactivateWorkflow(input.id); + activationMessage = ' Workflow deactivated.'; + } catch (deactivationError) { + logger.error('Failed to deactivate workflow after update', deactivationError); + return { + success: false, + error: 'Workflow updated successfully but deactivation failed', + details: { + workflowUpdated: true, + deactivationError: deactivationError instanceof Error ? deactivationError.message : 'Unknown error' + } + }; + } + } + return { success: true, - data: updatedWorkflow, - message: `Workflow "${updatedWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.`, + data: finalWorkflow, + message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage}`, details: { operationsApplied: diffResult.operationsApplied, - workflowId: updatedWorkflow.id, - workflowName: updatedWorkflow.name, + workflowId: finalWorkflow.id, + workflowName: finalWorkflow.name, + active: finalWorkflow.active, applied: diffResult.applied, failed: diffResult.failed, errors: diffResult.errors, diff --git a/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts b/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts index 0dc9835..6c0a485 100644 --- a/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts +++ b/src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts @@ -4,7 +4,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { name: 'n8n_update_partial_workflow', category: 'workflow_management', essentials: { - description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).', + description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).', keyParameters: ['id', 'operations', 'continueOnError'], example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "rewireConnection", source: "IF", from: "Old", to: "New", branch: "true"}]})', performance: 'Fast (50-200ms)', @@ -19,11 +19,12 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { 'For AI connections, specify sourceOutput type (ai_languageModel, ai_tool, etc.)', 'Batch AI component connections for atomic updates', 'Auto-sanitization: ALL nodes auto-fixed during updates (operator structures, missing metadata)', - 'Node renames automatically update all connection references - no manual connection operations needed' + 'Node renames automatically update all connection references - no manual connection operations needed', + 'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)' ] }, full: { - description: `Updates workflows using surgical diff operations instead of full replacement. Supports 15 operation types for precise modifications. Operations are validated and applied atomically by default - all succeed or none are applied. + description: `Updates workflows using surgical diff operations instead of full replacement. Supports 17 operation types for precise modifications. Operations are validated and applied atomically by default - all succeed or none are applied. ## Available Operations: @@ -48,6 +49,10 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = { - **addTag**: Add a workflow tag - **removeTag**: Remove a workflow tag +### Workflow Activation Operations (2 types): +- **activateWorkflow**: Activate the workflow to enable automatic execution via triggers +- **deactivateWorkflow**: Deactivate the workflow to prevent automatic execution + ## Smart Parameters for Multi-Output Nodes For **IF nodes**, use semantic 'branch' parameter instead of technical sourceIndex: diff --git a/src/services/n8n-api-client.ts b/src/services/n8n-api-client.ts index 3f4c8d0..a369d35 100644 --- a/src/services/n8n-api-client.ts +++ b/src/services/n8n-api-client.ts @@ -170,6 +170,24 @@ export class N8nApiClient { } } + async activateWorkflow(id: string): Promise { + try { + const response = await this.client.post(`/workflows/${id}/activate`); + return response.data; + } catch (error) { + throw handleN8nApiError(error); + } + } + + async deactivateWorkflow(id: string): Promise { + try { + const response = await this.client.post(`/workflows/${id}/deactivate`); + return response.data; + } catch (error) { + throw handleN8nApiError(error); + } + } + /** * Lists workflows from n8n instance. * diff --git a/src/services/workflow-diff-engine.ts b/src/services/workflow-diff-engine.ts index ab5baa9..0fa987b 100644 --- a/src/services/workflow-diff-engine.ts +++ b/src/services/workflow-diff-engine.ts @@ -25,6 +25,8 @@ import { UpdateNameOperation, AddTagOperation, RemoveTagOperation, + ActivateWorkflowOperation, + DeactivateWorkflowOperation, CleanStaleConnectionsOperation, ReplaceConnectionsOperation } from '../types/workflow-diff'; @@ -32,6 +34,7 @@ import { Workflow, WorkflowNode, WorkflowConnection } from '../types/n8n-api'; import { Logger } from '../utils/logger'; import { validateWorkflowNode, validateWorkflowConnections } from './n8n-validation'; import { sanitizeNode, sanitizeWorkflowNodes } from './node-sanitizer'; +import { isActivatableTrigger } from '../utils/node-type-utils'; const logger = new Logger({ prefix: '[WorkflowDiffEngine]' }); @@ -214,12 +217,23 @@ export class WorkflowDiffEngine { } const operationsApplied = request.operations.length; + + // Extract activation flags from workflow object + const shouldActivate = (workflowCopy as any)._shouldActivate === true; + const shouldDeactivate = (workflowCopy as any)._shouldDeactivate === true; + + // Clean up temporary flags + delete (workflowCopy as any)._shouldActivate; + delete (workflowCopy as any)._shouldDeactivate; + return { success: true, workflow: workflowCopy, operationsApplied, message: `Successfully applied ${operationsApplied} operations (${nodeOperations.length} node ops, ${otherOperations.length} other ops)`, - warnings: this.warnings.length > 0 ? this.warnings : undefined + warnings: this.warnings.length > 0 ? this.warnings : undefined, + shouldActivate: shouldActivate || undefined, + shouldDeactivate: shouldDeactivate || undefined }; } } catch (error) { @@ -262,6 +276,10 @@ export class WorkflowDiffEngine { case 'addTag': case 'removeTag': return null; // These are always valid + case 'activateWorkflow': + return this.validateActivateWorkflow(workflow, operation); + case 'deactivateWorkflow': + return this.validateDeactivateWorkflow(workflow, operation); case 'cleanStaleConnections': return this.validateCleanStaleConnections(workflow, operation); case 'replaceConnections': @@ -315,6 +333,12 @@ export class WorkflowDiffEngine { case 'removeTag': this.applyRemoveTag(workflow, operation); break; + case 'activateWorkflow': + this.applyActivateWorkflow(workflow, operation); + break; + case 'deactivateWorkflow': + this.applyDeactivateWorkflow(workflow, operation); + break; case 'cleanStaleConnections': this.applyCleanStaleConnections(workflow, operation); break; @@ -847,13 +871,46 @@ export class WorkflowDiffEngine { private applyRemoveTag(workflow: Workflow, operation: RemoveTagOperation): void { if (!workflow.tags) return; - + const index = workflow.tags.indexOf(operation.tag); if (index !== -1) { workflow.tags.splice(index, 1); } } + // Workflow activation operation validators + private validateActivateWorkflow(workflow: Workflow, operation: ActivateWorkflowOperation): string | null { + // Check if workflow has at least one activatable trigger + // Issue #351: executeWorkflowTrigger cannot activate workflows + const activatableTriggers = workflow.nodes.filter( + node => !node.disabled && isActivatableTrigger(node.type) + ); + + if (activatableTriggers.length === 0) { + return 'Cannot activate workflow: No activatable trigger nodes found. Workflows must have at least one enabled trigger node (webhook, schedule, email, etc.). Note: executeWorkflowTrigger cannot activate workflows as they can only be invoked by other workflows.'; + } + + return null; + } + + private validateDeactivateWorkflow(workflow: Workflow, operation: DeactivateWorkflowOperation): string | null { + // Deactivation is always valid - any workflow can be deactivated + return null; + } + + // Workflow activation operation appliers + private applyActivateWorkflow(workflow: Workflow, operation: ActivateWorkflowOperation): void { + // Set flag in workflow object to indicate activation intent + // The handler will call the API method after workflow update + (workflow as any)._shouldActivate = true; + } + + private applyDeactivateWorkflow(workflow: Workflow, operation: DeactivateWorkflowOperation): void { + // Set flag in workflow object to indicate deactivation intent + // The handler will call the API method after workflow update + (workflow as any)._shouldDeactivate = true; + } + // Connection cleanup operation validators private validateCleanStaleConnections(workflow: Workflow, operation: CleanStaleConnectionsOperation): string | null { // This operation is always valid - it just cleans up what it finds diff --git a/src/types/workflow-diff.ts b/src/types/workflow-diff.ts index e35169d..06173e6 100644 --- a/src/types/workflow-diff.ts +++ b/src/types/workflow-diff.ts @@ -114,6 +114,16 @@ export interface RemoveTagOperation extends DiffOperation { tag: string; } +export interface ActivateWorkflowOperation extends DiffOperation { + type: 'activateWorkflow'; + // No additional properties needed - just activates the workflow +} + +export interface DeactivateWorkflowOperation extends DiffOperation { + type: 'deactivateWorkflow'; + // No additional properties needed - just deactivates the workflow +} + // Connection Cleanup Operations export interface CleanStaleConnectionsOperation extends DiffOperation { type: 'cleanStaleConnections'; @@ -148,6 +158,8 @@ export type WorkflowDiffOperation = | UpdateNameOperation | AddTagOperation | RemoveTagOperation + | ActivateWorkflowOperation + | DeactivateWorkflowOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation; @@ -176,6 +188,8 @@ export interface WorkflowDiffResult { applied?: number[]; // Indices of successfully applied operations (when continueOnError is true) failed?: number[]; // Indices of failed operations (when continueOnError is true) staleConnectionsRemoved?: Array<{ from: string; to: string }>; // For cleanStaleConnections operation + shouldActivate?: boolean; // Flag to activate workflow after update (for activateWorkflow operation) + shouldDeactivate?: boolean; // Flag to deactivate workflow after update (for deactivateWorkflow operation) } // Helper type for node reference (supports both ID and name)