node-red-contrib-ai-agent 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,9 +93,15 @@ Configures the AI model and API settings.
93
93
  ### AI Orchestrator
94
94
  Coordinates multiple AI agents by creating and executing plans. It uses an autonomy loop (observe-think-act-reflect) to achieve complex goals.
95
95
 
96
+ **Key Features:**
97
+ - **Non-linear Planning**: Supports task dependencies (tasks wait for their predecessors).
98
+ - **Task Prioritization**: Executes higher priority tasks first within dependency constraints.
99
+ - **Dynamic Plan Revision**: Refines the plan based on task outcomes and agent feedback.
100
+ - **Error Recovery**: Automatically handles task failures with recovery strategies (retry, pivot, or fail).
101
+
96
102
  **Properties:**
97
103
  - **Max Iterations**: Maximum cycles for the autonomy loop
98
- - **Planning Strategy**: Simple (linear) or Advanced
104
+ - **Planning Strategy**: Simple (linear) or Advanced (dependency-aware)
99
105
  - **Default Goal**: Optional fallback goal
100
106
  - **Name**: Display name for the node
101
107
 
@@ -179,7 +185,7 @@ The AI Orchestrator can manage complex, multi-step tasks:
179
185
  4. Connect the orchestrator's second output to a **Debug** node
180
186
  5. Configure the orchestrator with a goal (e.g., "Write a blog post and then translate it to Spanish")
181
187
 
182
- The orchestrator will create a plan, dispatch the first task to the agent, reflect on the result, and then dispatch the next task until completion.
188
+ The orchestrator will create a plan (optionally with dependencies and priorities), dispatch the first available task to the agent, reflect on the result, and then dispatch the next task until completion. If a task fails, it can revise the plan to recover.
183
189
 
184
190
  ## Best Practices
185
191
 
@@ -72,5 +72,11 @@
72
72
 
73
73
  <h3>Details</h3>
74
74
  <p>Connect this node to one or more AI agents. The first output should flow into an agent, and the agent's output should loop back to this node's input.</p>
75
+ <p>The orchestrator supports advanced planning features in "Advanced" mode:</p>
76
+ <ul>
77
+ <li><strong>Dependencies:</strong> Tasks can wait for other tasks to complete (<code>dependsOn</code> array).</li>
78
+ <li><strong>Priorities:</strong> Tasks with higher <code>priority</code> numbers are executed first.</li>
79
+ <li><strong>Error Recovery:</strong> If a task fails, the orchestrator reflects on the error and can revise the plan to retry or try an alternative approach.</li>
80
+ </ul>
75
81
  <p>When the goal is achieved or max iterations are reached, the final message is sent to the second output.</p>
76
82
  </script>
@@ -48,7 +48,7 @@ module.exports = function (RED) {
48
48
  // Logic based on current status
49
49
  if (msg.orchestration.status === 'planning' || !msg.orchestration.plan) {
50
50
  await createInitialPlan(node, msg);
51
- } else {
51
+ } else if (msg.orchestration.currentTaskId) {
52
52
  await reflectAndRefine(node, msg);
53
53
  }
54
54
 
@@ -82,23 +82,31 @@ module.exports = function (RED) {
82
82
 
83
83
  async function createInitialPlan(node, msg) {
84
84
  const goal = msg.orchestration.goal;
85
- const prompt = `Goal: ${goal}\n\nDecompose this goal into a series of tasks for AI agents.
85
+ const strategy = node.planningStrategy;
86
+
87
+ let prompt = `Goal: ${goal}\n\nDecompose this goal into a series of tasks for AI agents.
86
88
  Return a JSON object with a "tasks" array. Each task should have:
87
- - "id": a short string id
88
- - "type": the type of task
89
- - "input": what the agent should do
89
+ - "id": a short string id (e.g., "t1", "t2")
90
+ - "type": the type of task (e.g., "research", "code", "review")
91
+ - "input": detailed instruction for the agent
90
92
  - "status": "pending"
93
+ - "priority": a number (1-10, default 5)
94
+ - "dependsOn": an array of IDs of tasks that must be completed BEFORE this task can start (empty array if none)`;
95
+
96
+ if (strategy === 'advanced') {
97
+ prompt += `\n\nThink about parallel execution. Group related tasks and identify bottlenecks. Ensure dependencies are logical.`;
98
+ }
91
99
 
92
- Example:
100
+ prompt += `\n\nExample:
93
101
  {
94
102
  "tasks": [
95
- {"id": "t1", "type": "research", "input": "Find information about X", "status": "pending"},
96
- {"id": "t2", "type": "summary", "input": "Summarize the findings", "status": "pending"}
103
+ {"id": "t1", "type": "research", "input": "...", "status": "pending", "priority": 10, "dependsOn": []},
104
+ {"id": "t2", "type": "implementation", "input": "...", "status": "pending", "priority": 5, "dependsOn": ["t1"]}
97
105
  ]
98
106
  }`;
99
107
 
100
108
  try {
101
- const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that creates plans.");
109
+ const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that creates non-linear plans with dependencies.");
102
110
  const planData = JSON.parse(extractJson(response));
103
111
  msg.orchestration.plan = planData;
104
112
  msg.orchestration.status = 'executing';
@@ -110,35 +118,47 @@ Example:
110
118
  async function reflectAndRefine(node, msg) {
111
119
  const currentTaskId = msg.orchestration.currentTaskId;
112
120
  const taskResult = msg.payload;
121
+ const isError = msg.error ? true : false;
113
122
 
114
123
  // Update history
115
124
  msg.orchestration.history.push({
116
125
  taskId: currentTaskId,
117
126
  result: taskResult,
127
+ error: msg.error,
118
128
  timestamp: new Date().toISOString()
119
129
  });
120
130
 
121
131
  // Update task status in plan
122
132
  const task = msg.orchestration.plan.tasks.find(t => t.id === currentTaskId);
123
133
  if (task) {
124
- task.status = 'completed';
125
- task.output = taskResult;
134
+ if (isError) {
135
+ task.status = 'failed';
136
+ task.error = msg.error;
137
+ } else {
138
+ task.status = 'completed';
139
+ task.output = taskResult;
140
+ }
126
141
  }
127
142
 
128
143
  const prompt = `Current Goal: ${msg.orchestration.goal}
129
144
  Current Plan: ${JSON.stringify(msg.orchestration.plan)}
130
- Last Task Result: ${JSON.stringify(taskResult)}
145
+ Last Task ID: ${currentTaskId}
146
+ Last Task ${isError ? 'Error' : 'Result'}: ${JSON.stringify(isError ? msg.error : taskResult)}
147
+
148
+ Evaluate the progress.
149
+ 1. If the last task failed, propose a recovery strategy (retry, alternative task, or fail the goal).
150
+ 2. If the goal is achieved, set status to "completed".
151
+ 3. Otherwise, continue execution. You may refine the plan by adding, removing, or modifying tasks.
131
152
 
132
- Evaluate the progress. Should we continue with the current plan, refine it, or is the goal achieved?
133
153
  Return a JSON object:
134
154
  {
135
- "analysis": "string evaluation",
155
+ "analysis": "detailed evaluation of progress and next steps",
136
156
  "status": "executing" | "completed" | "failed",
137
- "updatedPlan": { ... same structure as plan ... }
157
+ "updatedPlan": { "tasks": [...] }
138
158
  }`;
139
159
 
140
160
  try {
141
- const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that reflects on progress.");
161
+ const response = await callAI(msg.aiagent, prompt, "You are an AI Orchestrator that reflects on progress and manages plan revisions.");
142
162
  const reflection = JSON.parse(extractJson(response));
143
163
 
144
164
  msg.orchestration.status = reflection.status;
@@ -146,14 +166,37 @@ Return a JSON object:
146
166
  msg.orchestration.plan = reflection.updatedPlan;
147
167
  }
148
168
  } catch (error) {
149
- node.warn(`Reflection failed, continuing with current plan: ${error.message}`);
150
- // Fallback: just move to next task if possible
169
+ node.warn(`Reflection failed, continuing with current plan state: ${error.message}`);
170
+ // Fallback: stay in executing status if it was executing, let getNextTask decide
151
171
  }
152
172
  }
153
173
 
154
174
  function getNextTask(plan) {
155
175
  if (!plan || !plan.tasks) return null;
156
- return plan.tasks.find(t => t.status === 'pending');
176
+
177
+ // Find tasks that are pending AND all their dependencies are completed
178
+ const eligibleTasks = plan.tasks.filter(t => {
179
+ if (t.status !== 'pending') return false;
180
+
181
+ if (!t.dependsOn || t.dependsOn.length === 0) return true;
182
+
183
+ return t.dependsOn.every(depId => {
184
+ const depTask = plan.tasks.find(pt => pt.id === depId);
185
+ return depTask && depTask.status === 'completed';
186
+ });
187
+ });
188
+
189
+ if (eligibleTasks.length === 0) return null;
190
+
191
+ // Sort by priority (descending) then by ID
192
+ eligibleTasks.sort((a, b) => {
193
+ const priorityA = a.priority || 5;
194
+ const priorityB = b.priority || 5;
195
+ if (priorityB !== priorityA) return priorityB - priorityA;
196
+ return a.id.localeCompare(b.id);
197
+ });
198
+
199
+ return eligibleTasks[0];
157
200
  }
158
201
 
159
202
  async function callAI(aiConfig, prompt, systemPrompt) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ai-agent",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "AI Agent for Node-RED",
5
5
  "repository": {
6
6
  "type": "git",