@pixel-normal-edit/mcp 2.0.10 → 2.1.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/rules/workflow.js CHANGED
@@ -1,45 +1,79 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * workflow.js — Workflow Engine (Bắt buộc tuân thủ thứ tự)
4
- *
5
- * Engine này quản lý trạng thái workflow của agent.
6
- * Mỗi lần agent gọi 1 tool, workflow engine sẽ kiểm tra:
7
- * - Agent đang ở bước nào?
8
- * - Bước tiếp theo phải là gì?
9
- * - Nếu sai thứ tự → từ chối và yêu cầu làm đúng bước
10
- *
11
- * Cách dùng:
12
- * const workflow = require('./rules/workflow');
13
- * workflow.start('my_project'); // Bắt đầu workflow mới
14
- * workflow.getCurrentStep(); // Xem đang ở bước nào
15
- * workflow.advance(); // Chuyển sang bước tiếp theo
16
- * workflow.validateStep('draw'); // Kiểm tra có được phép vẽ chưa
3
+ * workflow.js — Workflow Engine (Strict step ordering with modes, phases, layers)
17
4
  */
18
5
 
19
- const { WORKFLOW_STEPS } = require('./rules');
6
+ const { WORKFLOW_STEPS, MODES, STATUSES, DRAWING_PHASES } = require('./rules');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const STATE_FILE = path.join(__dirname, '..', 'workflow_state.json');
11
+ let sessions = new Map();
12
+
13
+ // --- PERSISTENCE ---
14
+ function saveState() {
15
+ const data = {};
16
+ for (const [key, val] of sessions.entries()) {
17
+ data[key] = val;
18
+ }
19
+ try {
20
+ fs.writeFileSync(STATE_FILE, JSON.stringify(data, null, 2), 'utf-8');
21
+ } catch (e) {
22
+ console.error('Failed to save workflow state:', e);
23
+ }
24
+ }
25
+
26
+ function loadState() {
27
+ if (fs.existsSync(STATE_FILE)) {
28
+ try {
29
+ const content = fs.readFileSync(STATE_FILE, 'utf-8');
30
+ const data = JSON.parse(content);
31
+ sessions = new Map(Object.entries(data));
32
+ } catch (e) {
33
+ console.error('Failed to load workflow state:', e);
34
+ }
35
+ }
36
+ }
37
+
38
+ // Load state on startup
39
+ loadState();
20
40
 
21
- // ── In-memory workflow state ────────────────────────────────────────────────
22
- // Key: sessionId, Value: { currentStepIndex, steps, completedSteps, startedAt }
23
- const sessions = new Map();
24
41
 
25
42
  /**
26
- * Bắt đầu một workflow mới cho session
27
- * @param {string} sessionId - session (thường MCP_SESSION)
28
- * @returns {Object} Trạng thái workflow ban đầu
43
+ * Start a new workflow for a session
44
+ * @param {string} sessionId - Session ID (usually MCP_SESSION)
45
+ * @param {string} mode - The mode to start in (CREATE_IMAGE, EDIT_IMAGE, CREATE_ANIMATION)
46
+ * @returns {Object} Initial workflow state
29
47
  */
30
- function start(sessionId) {
48
+ function start(sessionId, mode = MODES.CREATE_IMAGE) {
49
+ if (!WORKFLOW_STEPS[mode]) {
50
+ throw new Error(`Invalid mode: ${mode}`);
51
+ }
52
+
31
53
  const state = {
54
+ current_mode: mode,
32
55
  currentStepIndex: 0,
33
- steps: WORKFLOW_STEPS.map(s => ({ ...s, status: 'pending' })),
56
+ steps: WORKFLOW_STEPS[mode].map(s => ({ ...s, status: STATUSES.PENDING })),
57
+ current_layer: 0,
58
+ current_frame: mode === MODES.CREATE_ANIMATION ? 0 : null,
59
+ current_phase: null,
60
+ animation_meta: { fps: null, total_frames: null, loop: true },
61
+ step_status: STATUSES.IN_PROGRESS,
62
+ user_approval_required: false,
63
+ user_input_request: null, // { id, type, fields, status, created_at, response }
64
+ step_completion_flags: {}, // track internal flags for steps
34
65
  completedSteps: [],
66
+ history: [],
35
67
  startedAt: Date.now(),
36
68
  };
69
+
37
70
  sessions.set(sessionId, state);
71
+ saveState();
38
72
  return getState(sessionId);
39
73
  }
40
74
 
41
75
  /**
42
- * Lấy trạng thái workflow hiện tại
76
+ * Get current workflow state
43
77
  * @param {string} sessionId
44
78
  * @returns {Object|null}
45
79
  */
@@ -48,9 +82,9 @@ function getState(sessionId) {
48
82
  }
49
83
 
50
84
  /**
51
- * Lấy bước hiện tại
85
+ * Get current step
52
86
  * @param {string} sessionId
53
- * @returns {{ id: string, label: string, description: string, status: string }|null}
87
+ * @returns {Object|null}
54
88
  */
55
89
  function getCurrentStep(sessionId) {
56
90
  const state = sessions.get(sessionId);
@@ -59,9 +93,9 @@ function getCurrentStep(sessionId) {
59
93
  }
60
94
 
61
95
  /**
62
- * Kiểm tra xem agent được phép thực hiện hành động này không
96
+ * Check if the agent is allowed to perform this action
63
97
  * @param {string} sessionId
64
- * @param {string} stepId - ID của bước muốn thực hiện
98
+ * @param {string} stepId - ID of the step to perform
65
99
  * @returns {{ allowed: boolean, message?: string, currentStep?: Object }}
66
100
  */
67
101
  function validateStep(sessionId, stepId) {
@@ -69,7 +103,15 @@ function validateStep(sessionId, stepId) {
69
103
  if (!state) {
70
104
  return {
71
105
  allowed: false,
72
- message: '⚠️ Chưa bắt đầu workflow. Hãy gọi workflow_start trước!',
106
+ message: '⚠️ Workflow has not started. Call workflow_start first!',
107
+ };
108
+ }
109
+
110
+ if (state.user_approval_required) {
111
+ return {
112
+ allowed: false,
113
+ message: '⛔ Workflow is waiting for user approval. Ask the user for confirmation.',
114
+ currentStep: state.steps[state.currentStepIndex]
73
115
  };
74
116
  }
75
117
 
@@ -77,29 +119,26 @@ function validateStep(sessionId, stepId) {
77
119
  if (!current) {
78
120
  return {
79
121
  allowed: false,
80
- message: '✅ Workflow đã hoàn tất! Không còn bước nào cần làm.',
122
+ message: '✅ Workflow is complete! No remaining steps.',
81
123
  };
82
124
  }
83
125
 
84
- // Tìm index của stepId trong workflow
85
126
  const targetIndex = state.steps.findIndex(s => s.id === stepId);
86
127
  if (targetIndex === -1) {
87
128
  return {
88
129
  allowed: false,
89
- message: `❌ Không tìm thấy bước "${stepId}" trong workflow.`,
130
+ message: `❌ Step "${stepId}" not found in current mode workflow.`,
90
131
  };
91
132
  }
92
133
 
93
- // Nếu stepId nằm trước bước hiện tại (đã hoàn thành) → cho phép (có thể cần quay lại)
94
134
  if (targetIndex < state.currentStepIndex) {
95
135
  return {
96
136
  allowed: true,
97
- message: `⚠️ Bước "${stepId}" đã hoàn thành trước đó. Bạn chắc muốn làm lại?`,
137
+ message: `⚠️ Step "${stepId}" was already completed. Are you sure you want to redo it?`,
98
138
  currentStep: current,
99
139
  };
100
140
  }
101
141
 
102
- // Nếu stepId đúng là bước hiện tại → cho phép
103
142
  if (targetIndex === state.currentStepIndex) {
104
143
  return {
105
144
  allowed: true,
@@ -107,81 +146,241 @@ function validateStep(sessionId, stepId) {
107
146
  };
108
147
  }
109
148
 
110
- // Nếu stepId nằm ở tương lai → CHẶN
111
149
  return {
112
150
  allowed: false,
113
- message: `⛔ Bạn chưa thể thực hiện "${stepId}". Trước hết hãy hoàn thành: "${current.label}" (${current.description})`,
151
+ message: `⛔ You cannot perform "${stepId}" yet. First complete: "${current.label}" (${current.description})`,
114
152
  currentStep: current,
115
153
  };
116
154
  }
117
155
 
118
156
  /**
119
- * Đánh dấu bước hiện tại là hoàn thành và chuyển sang bước tiếp theo
157
+ * Set user approval requirement
158
+ * @param {string} sessionId
159
+ * @param {boolean} required
160
+ */
161
+ function setApprovalRequired(sessionId, required) {
162
+ const state = sessions.get(sessionId);
163
+ if (!state) return false;
164
+ state.user_approval_required = required;
165
+ if (required) {
166
+ state.step_status = STATUSES.WAITING_FOR_USER;
167
+ } else {
168
+ if (state.user_input_request && state.user_input_request.status === 'WAITING_FOR_USER') {
169
+ return false; // Cannot unpause if waiting for explicit input request
170
+ }
171
+ state.step_status = STATUSES.IN_PROGRESS;
172
+ }
173
+ saveState();
174
+ return true;
175
+ }
176
+
177
+ /**
178
+ * Create a user input request
179
+ * @param {string} sessionId
180
+ * @param {string} type - e.g. CANVAS_SIZE, APPROVE_PLAN, SELECT_EDIT_REGION
181
+ * @param {Object} fields - fields required
182
+ */
183
+ function createInputRequest(sessionId, type, fields = {}) {
184
+ const state = sessions.get(sessionId);
185
+ if (!state) return false;
186
+
187
+ state.user_input_request = {
188
+ id: `req_${Date.now()}_${Math.floor(Math.random()*1000)}`,
189
+ type,
190
+ fields,
191
+ status: 'WAITING_FOR_USER',
192
+ created_at: Date.now()
193
+ };
194
+ state.step_status = STATUSES.WAITING_FOR_USER;
195
+ state.history.push({ event: 'input_request_created', type, time: Date.now() });
196
+ saveState();
197
+ return state.user_input_request.id;
198
+ }
199
+
200
+ /**
201
+ * Complete a user input request
202
+ * @param {string} sessionId
203
+ * @param {string} reqId
204
+ * @param {string} response
205
+ */
206
+ function completeInputRequest(sessionId, reqId, response) {
207
+ const state = sessions.get(sessionId);
208
+ if (!state || !state.user_input_request || state.user_input_request.id !== reqId) return false;
209
+
210
+ state.user_input_request.status = 'COMPLETED';
211
+ state.user_input_request.response = response;
212
+ state.step_status = STATUSES.IN_PROGRESS; // Unpause workflow
213
+
214
+ state.history.push({ event: 'input_request_completed', reqId, time: Date.now() });
215
+ saveState();
216
+ return true;
217
+ }
218
+
219
+ /**
220
+ * Update layer/frame
221
+ */
222
+ function setLayerAndFrame(sessionId, layer, frame = null) {
223
+ const state = sessions.get(sessionId);
224
+ if (!state) return false;
225
+
226
+ if (layer !== undefined && layer !== null) {
227
+ state.current_layer = layer;
228
+ }
229
+ if (frame !== undefined && frame !== null) {
230
+ state.current_frame = frame;
231
+ }
232
+ state.history.push({
233
+ event: 'layer_frame_change',
234
+ layer: state.current_layer,
235
+ frame: state.current_frame,
236
+ time: Date.now()
237
+ });
238
+ saveState();
239
+ return true;
240
+ }
241
+
242
+ /**
243
+ * Update phase
244
+ */
245
+ function setPhase(sessionId, phase) {
246
+ const state = sessions.get(sessionId);
247
+ if (!state) return false;
248
+
249
+ state.current_phase = phase;
250
+ state.history.push({
251
+ event: 'phase_change',
252
+ phase: phase,
253
+ time: Date.now()
254
+ });
255
+ saveState();
256
+ return true;
257
+ }
258
+
259
+ /**
260
+ * Set animation meta
261
+ */
262
+ function setAnimationMeta(sessionId, fps, total_frames, loop) {
263
+ const state = sessions.get(sessionId);
264
+ if (!state) return false;
265
+
266
+ if (fps !== undefined) state.animation_meta.fps = fps;
267
+ if (total_frames !== undefined) state.animation_meta.total_frames = total_frames;
268
+ if (loop !== undefined) state.animation_meta.loop = loop;
269
+
270
+ saveState();
271
+ return true;
272
+ }
273
+
274
+ /**
275
+ * Set step completion flag (internal validation)
276
+ */
277
+ function setStepFlag(sessionId, flagName, value) {
278
+ const state = sessions.get(sessionId);
279
+ if (!state) return false;
280
+ state.step_completion_flags[flagName] = value;
281
+ saveState();
282
+ return true;
283
+ }
284
+
285
+ /**
286
+ * Mark current step as completed and advance to the next step
120
287
  * @param {string} sessionId
121
- * @returns {Object} Kết quả chuyển bước
288
+ * @returns {Object} Step transition result
122
289
  */
123
290
  function advance(sessionId) {
124
291
  const state = sessions.get(sessionId);
125
292
  if (!state) {
126
- return { success: false, message: 'Chưa bắt đầu workflow' };
293
+ return { success: false, message: 'Workflow has not started' };
294
+ }
295
+
296
+ if (state.user_approval_required) {
297
+ return { success: false, message: 'Cannot advance: Waiting for user approval.' };
298
+ }
299
+
300
+ if (state.step_status === STATUSES.WAITING_FOR_USER) {
301
+ return { success: false, message: 'Cannot advance: Waiting for user approval or input.' };
127
302
  }
128
303
 
129
304
  const current = state.steps[state.currentStepIndex];
130
305
  if (!current) {
131
- return { success: false, message: 'Workflow đã hoàn tất' };
306
+ return { success: false, message: 'Workflow is complete' };
132
307
  }
133
308
 
134
- // Đánh dấu bước hiện tại là completed
135
- current.status = 'completed';
309
+ // Pre-condition validation block
310
+ if (current.id === 'INITIALIZE_CANVAS' && !state.step_completion_flags.canvas_initialized) {
311
+ return { success: false, message: 'Cannot advance: Canvas has not been initialized. Create a canvas first.' };
312
+ }
313
+ if (current.id === 'PLAN_DRAWING' && !state.step_completion_flags.plan_created) {
314
+ return { success: false, message: 'Cannot advance: Drawing plan has not been finalized.' };
315
+ }
316
+
317
+ current.status = STATUSES.COMPLETED;
136
318
  state.completedSteps.push(current.id);
319
+ state.history.push({ event: 'step_completed', step: current.id, layer: state.current_layer, frame: state.current_frame, time: Date.now() });
137
320
 
138
- // Chuyển sang bước tiếp theo
139
321
  state.currentStepIndex++;
140
322
 
141
323
  const next = state.steps[state.currentStepIndex] || null;
142
324
  if (next) {
143
- next.status = 'in_progress';
325
+ next.status = STATUSES.IN_PROGRESS;
326
+ state.step_status = STATUSES.IN_PROGRESS;
327
+ saveState();
144
328
  return {
145
329
  success: true,
146
330
  completedStep: current,
147
331
  nextStep: next,
148
- message: `✅ Hoàn thành: "${current.label}". Bước tiếp theo: "${next.label}" — ${next.description}`,
332
+ message: `✅ Completed: "${current.label}". Next step: "${next.label}" — ${next.description}`,
149
333
  };
150
334
  }
151
335
 
336
+ state.step_status = STATUSES.COMPLETED;
337
+ saveState();
152
338
  return {
153
339
  success: true,
154
340
  completedStep: current,
155
341
  nextStep: null,
156
- message: `🎉 Hoàn tất tất cả các bước! Tác phẩm đã sẵn sàng.`,
342
+ message: `🎉 All steps completed! The task is ready.`,
157
343
  };
158
344
  }
159
345
 
160
346
  /**
161
- * Reset workflow về đầu
347
+ * Reset workflow to the beginning
162
348
  * @param {string} sessionId
163
349
  */
164
350
  function reset(sessionId) {
351
+ const state = sessions.get(sessionId);
352
+ const mode = state ? state.current_mode : MODES.CREATE_IMAGE;
165
353
  sessions.delete(sessionId);
166
- return start(sessionId);
354
+ saveState();
355
+ return start(sessionId, mode);
167
356
  }
168
357
 
169
358
  /**
170
- * Lấy toàn bộ tiến trình dạng text để trả về cho agent
359
+ * Get text-based progress report for the agent
171
360
  * @param {string} sessionId
172
361
  * @returns {string}
173
362
  */
174
363
  function getProgressReport(sessionId) {
175
364
  const state = sessions.get(sessionId);
176
- if (!state) return 'Chưa bắt đầu workflow.';
365
+ if (!state) return 'Workflow has not started.';
177
366
 
178
- const lines = ['📋 **Tiến trình vẽ:**\n'];
367
+ const lines = [
368
+ `📋 **Workflow Progress: [Mode: ${state.current_mode}]**`,
369
+ `Layer: ${state.current_layer} | Frame: ${state.current_frame !== null ? state.current_frame : 'N/A'}`,
370
+ `Phase: ${state.current_phase !== null ? state.current_phase : 'N/A'}`,
371
+ `Animation Meta: FPS=${state.animation_meta.fps || 'N/A'}, Frames=${state.animation_meta.total_frames || 'N/A'}, Loop=${state.animation_meta.loop}`,
372
+ `Status: ${state.step_status}`,
373
+ `User Approval Required: ${state.user_approval_required ? 'YES' : 'NO'}`,
374
+ state.user_input_request && state.user_input_request.status === 'WAITING_FOR_USER' ? `⚠️ PENDING REQUEST: ${state.user_input_request.type}` : '',
375
+ `\n**Steps:**`
376
+ ];
377
+
179
378
  state.steps.forEach((step, i) => {
180
- const icon = step.status === 'completed' ? '✅' :
181
- step.status === 'in_progress' ? '🔄' : '⬜';
182
- const marker = i === state.currentStepIndex ? ' ← **ĐANG LÀM**' : '';
379
+ const icon = step.status === STATUSES.COMPLETED ? '✅' :
380
+ step.status === STATUSES.IN_PROGRESS ? '🔄' : '⬜';
381
+ const marker = i === state.currentStepIndex ? ' ← **IN PROGRESS**' : '';
183
382
  lines.push(`${icon} **${step.label}**${marker}`);
184
- if (step.status === 'in_progress' || i === state.currentStepIndex) {
383
+ if (step.status === STATUSES.IN_PROGRESS || i === state.currentStepIndex) {
185
384
  lines.push(` → ${step.description}`);
186
385
  }
187
386
  });
@@ -197,4 +396,11 @@ module.exports = {
197
396
  advance,
198
397
  reset,
199
398
  getProgressReport,
399
+ setApprovalRequired,
400
+ createInputRequest,
401
+ completeInputRequest,
402
+ setLayerAndFrame,
403
+ setPhase,
404
+ setAnimationMeta,
405
+ setStepFlag
200
406
  };