@lhi/n8m 0.2.2 → 0.2.3
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/dist/agentic/graph.js +3 -9
- package/dist/commands/create.js +125 -92
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/agentic/graph.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StateGraph, START, END
|
|
1
|
+
import { StateGraph, START, END } from "@langchain/langgraph";
|
|
2
2
|
import { checkpointer } from "./checkpointer.js";
|
|
3
3
|
import { architectNode } from "./nodes/architect.js";
|
|
4
4
|
import { engineerNode } from "./nodes/engineer.js";
|
|
@@ -15,14 +15,8 @@ const workflow = new StateGraph(TeamState)
|
|
|
15
15
|
.addNode("qa", qaNode)
|
|
16
16
|
// Edges
|
|
17
17
|
.addEdge(START, "architect")
|
|
18
|
-
//
|
|
19
|
-
.
|
|
20
|
-
if (state.strategies && state.strategies.length > 0) {
|
|
21
|
-
return state.strategies.map(s => new Send("engineer", { spec: s }));
|
|
22
|
-
}
|
|
23
|
-
// Fallback for linear path
|
|
24
|
-
return "engineer";
|
|
25
|
-
}, ["engineer"]) // We must declare valid destination nodes for visualization/compilation
|
|
18
|
+
// Architect -> Engineer (spec is chosen interactively in create.ts before resuming)
|
|
19
|
+
.addEdge("architect", "engineer")
|
|
26
20
|
// Fan-In: Engineer -> Supervisor (Wait for all to finish) or route to Reviewer (if fixing)
|
|
27
21
|
.addConditionalEdges("engineer", (state) => {
|
|
28
22
|
// If we have errors, we are in "Repair Mode" -> Skip Supervisor (which only handles fresh candidates)
|
package/dist/commands/create.js
CHANGED
|
@@ -98,117 +98,150 @@ export default class Create extends Command {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
-
else if (nodeName === 'engineer') {
|
|
102
|
-
this.log(theme.agent(`⚙️ Engineer: Workflow code generated/updated.`));
|
|
103
|
-
if (stateUpdate.workflowJson) {
|
|
104
|
-
lastWorkflowJson = stateUpdate.workflowJson;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else if (nodeName === 'qa') {
|
|
108
|
-
const status = stateUpdate.validationStatus;
|
|
109
|
-
if (status === 'passed') {
|
|
110
|
-
this.log(theme.success(`🧪 QA: Validation Passed!`));
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
this.log(theme.fail(`🧪 QA: Validation Failed.`));
|
|
114
|
-
if (stateUpdate.validationErrors && stateUpdate.validationErrors.length > 0) {
|
|
115
|
-
stateUpdate.validationErrors.forEach((e) => this.log(theme.error(` - ${e}`)));
|
|
116
|
-
}
|
|
117
|
-
this.log(theme.warn(` Looping back to Engineer for repairs...`));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
101
|
}
|
|
121
|
-
//
|
|
102
|
+
// Handle interrupt/pause loop
|
|
122
103
|
let snapshot = await graph.getState({ configurable: { thread_id: threadId } });
|
|
123
104
|
while (snapshot.next.length > 0) {
|
|
124
105
|
const nextNode = snapshot.next[0];
|
|
125
|
-
this.log(theme.warn(`\n⏸️ Workflow Paused at step: ${nextNode}`));
|
|
126
106
|
if (nextNode === 'engineer') {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.log(theme.agent("Approve! Proceeding to engineering..."));
|
|
139
|
-
await graph.updateState({ configurable: { thread_id: threadId } }, { userFeedback: undefined }, nextNode);
|
|
140
|
-
const stream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
141
|
-
for await (const event of stream) {
|
|
142
|
-
const nodeName = Object.keys(event)[0];
|
|
143
|
-
const stateUpdate = event[nodeName];
|
|
144
|
-
if (nodeName === 'engineer') {
|
|
145
|
-
this.log(theme.agent(`⚙️ Engineer: Workflow code generated/updated.`));
|
|
146
|
-
if (stateUpdate.workflowJson)
|
|
147
|
-
lastWorkflowJson = stateUpdate.workflowJson;
|
|
107
|
+
const isRepair = (snapshot.values.validationErrors || []).length > 0;
|
|
108
|
+
if (isRepair) {
|
|
109
|
+
// Repair iteration — auto-continue without asking the user
|
|
110
|
+
const repairStream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
111
|
+
for await (const event of repairStream) {
|
|
112
|
+
const n = Object.keys(event)[0];
|
|
113
|
+
const u = event[n];
|
|
114
|
+
if (n === 'engineer') {
|
|
115
|
+
this.log(theme.agent(`⚙️ Engineer: Applying fixes...`));
|
|
116
|
+
if (u.workflowJson)
|
|
117
|
+
lastWorkflowJson = u.workflowJson;
|
|
148
118
|
}
|
|
149
|
-
else if (
|
|
150
|
-
|
|
151
|
-
if (status === 'passed')
|
|
152
|
-
this.log(theme.success(`🧪 QA: Validation Passed!`));
|
|
153
|
-
else
|
|
154
|
-
this.log(theme.fail(`🧪 QA: Validation Failed.`));
|
|
119
|
+
else if (n === 'supervisor' && u.workflowJson) {
|
|
120
|
+
lastWorkflowJson = u.workflowJson;
|
|
155
121
|
}
|
|
156
122
|
}
|
|
157
123
|
}
|
|
158
|
-
else
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
124
|
+
else {
|
|
125
|
+
// Initial build — let user choose which strategy to use
|
|
126
|
+
const strategies = snapshot.values.strategies || [];
|
|
127
|
+
const spec = snapshot.values.spec;
|
|
128
|
+
const choices = [];
|
|
129
|
+
if (strategies.length > 0) {
|
|
130
|
+
strategies.forEach((s, i) => {
|
|
131
|
+
const tag = i === 0 ? 'Primary' : 'Alternative';
|
|
132
|
+
const nodes = s.nodes
|
|
133
|
+
?.map((n) => n.type?.split('.').pop())
|
|
134
|
+
.join(', ');
|
|
135
|
+
choices.push({
|
|
136
|
+
name: `[${tag}] ${s.suggestedName}${nodes ? ` · ${nodes}` : ''}`,
|
|
137
|
+
value: { type: 'build', strategy: s },
|
|
138
|
+
short: s.suggestedName,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else if (spec) {
|
|
143
|
+
choices.push({
|
|
144
|
+
name: spec.suggestedName,
|
|
145
|
+
value: { type: 'build', strategy: spec },
|
|
146
|
+
short: spec.suggestedName,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
choices.push(new inquirer.Separator());
|
|
150
|
+
choices.push({
|
|
151
|
+
name: 'Add feedback before building',
|
|
152
|
+
value: { type: 'feedback' },
|
|
153
|
+
short: 'Add feedback',
|
|
154
|
+
});
|
|
155
|
+
choices.push({
|
|
156
|
+
name: 'Exit (save session to resume later)',
|
|
157
|
+
value: { type: 'exit' },
|
|
158
|
+
short: 'Exit',
|
|
159
|
+
});
|
|
160
|
+
const { choice } = await inquirer.prompt([{
|
|
161
|
+
type: 'list',
|
|
162
|
+
name: 'choice',
|
|
163
|
+
message: strategies.length > 1
|
|
164
|
+
? 'The Architect designed two approaches — which should the Engineer build?'
|
|
165
|
+
: 'Blueprint ready. How would you like to proceed?',
|
|
166
|
+
choices,
|
|
163
167
|
}]);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
168
|
+
if (choice.type === 'exit') {
|
|
169
|
+
this.log(theme.info(`\nSession saved. Resume later with: n8m resume ${threadId}`));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
let chosenSpec = choice.strategy ?? spec;
|
|
173
|
+
let stateUpdate = { spec: chosenSpec, userFeedback: undefined };
|
|
174
|
+
if (choice.type === 'feedback') {
|
|
175
|
+
const { feedback } = await inquirer.prompt([{
|
|
176
|
+
type: 'input',
|
|
177
|
+
name: 'feedback',
|
|
178
|
+
message: 'Describe your refinements (the Engineer will incorporate them):',
|
|
179
|
+
}]);
|
|
180
|
+
chosenSpec = strategies[0] ?? spec;
|
|
181
|
+
stateUpdate = { spec: chosenSpec, userFeedback: feedback };
|
|
182
|
+
this.log(theme.agent(`Feedback noted. Building "${chosenSpec?.suggestedName}" with your refinements...`));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
this.log(theme.agent(`Building "${chosenSpec?.suggestedName}"...`));
|
|
186
|
+
}
|
|
187
|
+
await graph.updateState({ configurable: { thread_id: threadId } }, stateUpdate, nextNode);
|
|
188
|
+
const buildStream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
189
|
+
for await (const event of buildStream) {
|
|
190
|
+
const n = Object.keys(event)[0];
|
|
191
|
+
const u = event[n];
|
|
192
|
+
if (n === 'engineer') {
|
|
193
|
+
this.log(theme.agent(`⚙️ Engineer: Building workflow...`));
|
|
194
|
+
if (u.workflowJson)
|
|
195
|
+
lastWorkflowJson = u.workflowJson;
|
|
179
196
|
}
|
|
180
|
-
else if (
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
else if (n === 'supervisor' && u.workflowJson) {
|
|
198
|
+
lastWorkflowJson = u.workflowJson;
|
|
199
|
+
}
|
|
200
|
+
else if (n === 'reviewer' && u.validationStatus === 'failed') {
|
|
201
|
+
this.log(theme.warn(` Reviewer flagged issues — Engineer will revise...`));
|
|
183
202
|
}
|
|
184
203
|
}
|
|
185
204
|
}
|
|
186
|
-
else {
|
|
187
|
-
this.log(theme.info(`Session persisted. Resume later with: n8m resume ${threadId}`));
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
205
|
}
|
|
191
|
-
else {
|
|
192
|
-
|
|
193
|
-
const { resume } = await inquirer.prompt([{
|
|
206
|
+
else if (nextNode === 'qa') {
|
|
207
|
+
const { proceed } = await inquirer.prompt([{
|
|
194
208
|
type: 'confirm',
|
|
195
|
-
name: '
|
|
196
|
-
message:
|
|
197
|
-
default: true
|
|
209
|
+
name: 'proceed',
|
|
210
|
+
message: 'Workflow generated! Ready to run QA tests?',
|
|
211
|
+
default: true,
|
|
198
212
|
}]);
|
|
199
|
-
if (
|
|
200
|
-
this.log(theme.
|
|
201
|
-
const result = await resumeAgenticWorkflow(threadId);
|
|
202
|
-
if (result.validationStatus === 'passed') {
|
|
203
|
-
this.log(theme.success(`🧪 QA (Resumed): Validation Passed!`));
|
|
204
|
-
if (result.workflowJson)
|
|
205
|
-
lastWorkflowJson = result.workflowJson;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
this.log(theme.info(`Session persisted. Resume later with: n8m resume ${threadId}`));
|
|
213
|
+
if (!proceed) {
|
|
214
|
+
this.log(theme.info(`\nSession saved. Resume later with: n8m resume ${threadId}`));
|
|
210
215
|
return;
|
|
211
216
|
}
|
|
217
|
+
const qaStream = await graph.stream(null, { configurable: { thread_id: threadId } });
|
|
218
|
+
for await (const event of qaStream) {
|
|
219
|
+
const n = Object.keys(event)[0];
|
|
220
|
+
const u = event[n];
|
|
221
|
+
if (n === 'qa') {
|
|
222
|
+
if (u.validationStatus === 'passed') {
|
|
223
|
+
this.log(theme.success(`🧪 QA: Validation Passed!`));
|
|
224
|
+
if (u.workflowJson)
|
|
225
|
+
lastWorkflowJson = u.workflowJson;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.log(theme.fail(`🧪 QA: Validation Failed.`));
|
|
229
|
+
if (u.validationErrors?.length) {
|
|
230
|
+
u.validationErrors.forEach(e => this.log(theme.error(` - ${e}`)));
|
|
231
|
+
}
|
|
232
|
+
this.log(theme.warn(` Looping back to Engineer for repairs...`));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (n === 'supervisor' && u.workflowJson) {
|
|
236
|
+
lastWorkflowJson = u.workflowJson;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Unknown interrupt — auto-resume
|
|
242
|
+
const result = await resumeAgenticWorkflow(threadId);
|
|
243
|
+
if (result.workflowJson)
|
|
244
|
+
lastWorkflowJson = result.workflowJson;
|
|
212
245
|
}
|
|
213
246
|
snapshot = await graph.getState({ configurable: { thread_id: threadId } });
|
|
214
247
|
}
|
package/oclif.manifest.json
CHANGED