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
28 changes: 16 additions & 12 deletions __tests__/integration/actions/createWorkflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result?.success).toBe(true);
expect(result?.data).toEqual({ awaitingUserInput: true });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good test practice: Using optional chaining (`result?.data`) prevents tests from failing with null reference errors if the action handler has issues. The assertion verifies both the structure and the specific flag value. ✅

Consider: You might also want to add a test that explicitly verifies the flag is NOT present (or is false) in terminal states like deployment success or cancellation, to ensure the behavior is complete.

expect(mockService.generateWorkflowDraft).toHaveBeenCalledTimes(1);
expect(mockService.deployWorkflow).not.toHaveBeenCalled();

Expand Down Expand Up @@ -108,7 +109,8 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result?.success).toBe(true);
expect(result?.data).toEqual({ awaitingUserInput: true });
const calls = (callback as any).mock.calls;
const lastText = calls[calls.length - 1][0].text;
// Clarification questions should be in the data passed to the LLM
Expand All @@ -131,7 +133,7 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(false);
expect(result?.success).toBe(false);
// Callback should be called (LLM formats EMPTY_PROMPT response)
expect((callback as any).mock.calls.length).toBeGreaterThan(0);
});
Expand All @@ -151,7 +153,7 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(false);
expect(result?.success).toBe(false);
});

test('handles service error gracefully', async () => {
Expand All @@ -174,7 +176,7 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(false);
expect(result?.success).toBe(false);
const calls = (callback as any).mock.calls;
const errorText = calls[calls.length - 1][0].text;
expect(errorText).toContain('LLM generation failed');
Expand Down Expand Up @@ -240,8 +242,8 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result.data).toBeDefined();
expect(result?.success).toBe(true);
expect(result?.data).toBeDefined();
expect(mockService.deployWorkflow).toHaveBeenCalledTimes(1);
expect(mockService.generateWorkflowDraft).not.toHaveBeenCalled();

Expand Down Expand Up @@ -277,7 +279,7 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result?.success).toBe(true);
expect(runtime.deleteCache).toHaveBeenCalled();

// Callback called with cancelled response containing workflow name
Expand Down Expand Up @@ -313,7 +315,8 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result?.success).toBe(true);
expect(result?.data).toEqual({ awaitingUserInput: true });
// Should call modifyWorkflowDraft (hybrid), NOT generateWorkflowDraft (from scratch)
expect(mockService.modifyWorkflowDraft).toHaveBeenCalledTimes(1);
expect(mockService.generateWorkflowDraft).not.toHaveBeenCalled();
Expand Down Expand Up @@ -379,7 +382,8 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result?.success).toBe(true);
expect(result?.data).toEqual({ awaitingUserInput: true });

// Draft should be restored in cache
expect(runtime.setCache).toHaveBeenCalled();
Expand Down Expand Up @@ -419,7 +423,7 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result?.success).toBe(true);
// Should call modifyWorkflowDraft (override confirm → modify), NOT deployWorkflow
expect(mockService.modifyWorkflowDraft).toHaveBeenCalledTimes(1);
expect(mockService.deployWorkflow).not.toHaveBeenCalled();
Expand Down Expand Up @@ -494,7 +498,7 @@ describe('CREATE_N8N_WORKFLOW action', () => {
callback
);

expect(result.success).toBe(true);
expect(result?.success).toBe(true);

// Should NOT clear cache — draft stays for retry after auth
expect(runtime.deleteCache).not.toHaveBeenCalled();
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@elizaos/plugin-n8n-workflow",
"version": "1.0.11",
"version": "1.0.12",
"description": "ElizaOS plugin for generating and managing n8n workflows from natural language",
"type": "module",
"main": "dist/index.js",
Expand Down
14 changes: 7 additions & 7 deletions src/actions/createWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export const createWorkflowAction: Action = {
if (callback) {
await callback({ text, success: true });
}
return { success: true };
return { success: true, data: { awaitingUserInput: true } };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good addition: The awaitingUserInput: true flag correctly signals that the workflow is waiting for the user to handle authentication before proceeding. This prevents the multi-step loop from auto-confirming while credentials are pending. ✅

}

await runtime.deleteCache(cacheKey);
Expand Down Expand Up @@ -372,7 +372,7 @@ export const createWorkflowAction: Action = {
if (callback) {
await callback({ text, success: true });
}
return { success: true };
return { success: true, data: { awaitingUserInput: true } };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct placement: Adding the flag here is right - when clarification questions are returned after a modification, the loop should pause and wait for the user's answers. ✅

}

const text = await formatActionResponse(
Expand All @@ -383,7 +383,7 @@ export const createWorkflowAction: Action = {
if (callback) {
await callback({ text, success: true });
}
return { success: true };
return { success: true, data: { awaitingUserInput: true } };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistent pattern: The modified workflow preview correctly includes the flag. This ensures the user sees the updated draft and must confirm before deployment. ✅

}

case 'new': {
Expand Down Expand Up @@ -419,7 +419,7 @@ export const createWorkflowAction: Action = {
if (callback) {
await callback({ text, success: true });
}
return { success: true };
return { success: true, data: { awaitingUserInput: true } };
}
}

Expand All @@ -436,7 +436,7 @@ export const createWorkflowAction: Action = {
if (callback) {
await callback({ text, success: true });
}
return { success: true };
return { success: true, data: { awaitingUserInput: true } };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edge case handling: When a "new" workflow generation fails and the previous draft is restored, the awaitingUserInput flag is correctly set. This keeps the user in the loop with the restored draft rather than failing silently. Good defensive programming! ✅

}
}
}
Expand Down Expand Up @@ -516,12 +516,12 @@ async function generateAndPreview(
if (callback) {
await callback({ text, success: true });
}
return { success: true };
return { success: true, data: { awaitingUserInput: true } };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clarification flow: Correctly returns awaitingUserInput: true when the LLM identifies that clarification questions need to be answered. The multi-step loop will pause here until the user provides answers. ✅

}

const text = await formatActionResponse(runtime, 'PREVIEW', buildPreviewData(workflow));
if (callback) {
await callback({ text, success: true });
}
return { success: true };
return { success: true, data: { awaitingUserInput: true } };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preview generation: The initial workflow preview correctly includes the flag. This is the core use case - after generating a draft, the system waits for user confirmation before deploying. ✅

}