@pixel-normal-edit/mcp 2.0.10 → 2.2.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.
Files changed (136) hide show
  1. package/clear-firebase.js +37 -0
  2. package/core/command-bus.js +6 -3
  3. package/core/server.js +98 -3
  4. package/domains/art-tree/README.md +134 -0
  5. package/domains/art-tree/REORGANIZATION_PLAN.md +168 -0
  6. package/domains/art-tree/REORGANIZATION_SUMMARY.md +260 -0
  7. package/domains/art-tree/index.js +58 -58
  8. package/domains/art-tree/lessons/3d/box-properties.js +38 -0
  9. package/domains/art-tree/lessons/3d/cone-properties.js +31 -0
  10. package/domains/art-tree/lessons/3d/cylinder-properties.js +29 -0
  11. package/domains/art-tree/lessons/3d/sphere-properties.js +25 -0
  12. package/domains/art-tree/lessons/advanced/angles.js +30 -0
  13. package/domains/art-tree/lessons/advanced/distances.js +26 -0
  14. package/domains/art-tree/lessons/advanced/shape-ratios.js +25 -0
  15. package/domains/art-tree/lessons/advanced/shape-relationships.js +28 -0
  16. package/domains/art-tree/lessons/advanced/symmetry-and-axis.js +26 -0
  17. package/domains/art-tree/{analysis_lessons.js → lessons/analysis/object-analysis.js} +5 -16
  18. package/domains/art-tree/lessons/basic/circles.js +26 -0
  19. package/domains/art-tree/lessons/basic/composition.js +34 -0
  20. package/domains/art-tree/lessons/basic/curves.js +31 -0
  21. package/domains/art-tree/lessons/basic/ellipses.js +26 -0
  22. package/domains/art-tree/lessons/basic/freehand.js +19 -0
  23. package/domains/art-tree/lessons/basic/index.js +36 -0
  24. package/domains/art-tree/lessons/basic/lines.js +38 -0
  25. package/domains/art-tree/lessons/basic/parallel-and-intersecting.js +22 -0
  26. package/domains/art-tree/lessons/basic/polygons.js +25 -0
  27. package/domains/art-tree/lessons/basic/proportions-and-angles.js +20 -0
  28. package/domains/art-tree/lessons/basic/spiral.js +21 -0
  29. package/domains/art-tree/lessons/basic/squares.js +28 -0
  30. package/domains/art-tree/lessons/basic/strokes.js +23 -0
  31. package/domains/art-tree/lessons/basic/triangles.js +30 -0
  32. package/domains/art-tree/lessons/cross-sections/bottle-sections.js +26 -0
  33. package/domains/art-tree/lessons/cross-sections/glass-sections.js +24 -0
  34. package/domains/art-tree/lessons/cross-sections/head-sections.js +17 -0
  35. package/domains/art-tree/lessons/curves/curve-properties.js +50 -0
  36. package/domains/art-tree/lessons/curves/curve-topology.js +45 -0
  37. package/domains/art-tree/lessons/curves/curve-types.js +44 -0
  38. package/domains/art-tree/lessons/ellipses/coaxial-ellipses.js +29 -0
  39. package/domains/art-tree/lessons/ellipses/ellipse-anatomy.js +26 -0
  40. package/domains/art-tree/lessons/ellipses/ellipse-orientations.js +28 -0
  41. package/domains/art-tree/lessons/ellipses/ellipse-proportions.js +27 -0
  42. package/domains/art-tree/lessons/hidden/hidden-box.js +20 -0
  43. package/domains/art-tree/lessons/hidden/hidden-cup.js +19 -0
  44. package/domains/art-tree/lessons/hidden/hidden-head.js +18 -0
  45. package/domains/art-tree/lessons/index.js +42 -0
  46. package/domains/art-tree/{layer_lessons.js → lessons/layers/layer-step.js} +5 -16
  47. package/domains/art-tree/lessons/lighting/contact-shadow.js +33 -0
  48. package/domains/art-tree/lessons/lighting/light-direction.js +30 -0
  49. package/domains/art-tree/lessons/lighting/light-zones.js +30 -0
  50. package/domains/art-tree/lessons/materials/material-glass.js +21 -0
  51. package/domains/art-tree/lessons/materials/material-shiny.js +26 -0
  52. package/domains/art-tree/lessons/materials/material-texture.js +26 -0
  53. package/domains/art-tree/lessons/perspective/1point-perspective.js +30 -0
  54. package/domains/art-tree/lessons/perspective/2point-perspective.js +30 -0
  55. package/domains/art-tree/lessons/perspective/3point-perspective.js +45 -0
  56. package/domains/art-tree/lessons/perspective/foreshortening.js +38 -0
  57. package/domains/art-tree/lessons/sky/sun-shapes.js +25 -0
  58. package/domains/art-tree/lessons/sky/sunset-colors.js +27 -0
  59. package/domains/art-tree/lessons/sky/sunset-sky.js +32 -0
  60. package/domains/art-tree/{structure_lessons.js → lessons/structure/axis-orientation.js} +3 -53
  61. package/domains/art-tree/lessons/structure/complex-structure.js +21 -0
  62. package/domains/art-tree/lessons/structure/x-yzaxes.js +28 -0
  63. package/domains/art-tree/lessons/surfaces/chair-analysis.js +17 -0
  64. package/domains/art-tree/lessons/surfaces/cup-analysis.js +18 -0
  65. package/domains/art-tree/lessons/surfaces/edge-types.js +40 -0
  66. package/domains/art-tree/lessons/surfaces/surface-types.js +42 -0
  67. package/domains/art-tree/lessons/transforms/bend.js +27 -0
  68. package/domains/art-tree/{transform_lessons_2.js → lessons/transforms/combine.js} +3 -54
  69. package/domains/art-tree/lessons/transforms/cut.js +25 -0
  70. package/domains/art-tree/lessons/transforms/hollow.js +24 -0
  71. package/domains/art-tree/lessons/transforms/rotate.js +16 -0
  72. package/domains/art-tree/lessons/transforms/squash.js +31 -0
  73. package/domains/art-tree/lessons/transforms/stretch.js +31 -0
  74. package/domains/art-tree/lessons/transforms/taper-swell.js +33 -0
  75. package/domains/art-tree/{vocab_lessons.js → lessons/vocab/vocab-house.js} +5 -16
  76. package/domains/art-tree/lib/tool-register.js +104 -0
  77. package/domains/art-tree/lib/validators.js +78 -0
  78. package/domains/art-tree/shapes/grid.js +37 -0
  79. package/domains/art-tree/shapes/polyline.js +26 -0
  80. package/domains/art-tree/{3d_tools.js → tools/3d.js} +2 -2
  81. package/domains/art-tree/{advanced_tools.js → tools/advanced.js} +2 -2
  82. package/domains/art-tree/{analysis_tools.js → tools/analysis.js} +2 -2
  83. package/domains/art-tree/{tools.js → tools/basic.js} +3 -3
  84. package/domains/art-tree/{cross_section_tools.js → tools/cross-sections.js} +2 -2
  85. package/domains/art-tree/{curve_tools.js → tools/curves.js} +2 -2
  86. package/domains/art-tree/{ellipse_tools.js → tools/ellipses.js} +2 -2
  87. package/domains/art-tree/{hidden_tools.js → tools/hidden.js} +2 -2
  88. package/domains/art-tree/tools/index.js +53 -0
  89. package/domains/art-tree/{layer_tools.js → tools/layers.js} +2 -2
  90. package/domains/art-tree/{light_tools.js → tools/lighting.js} +2 -2
  91. package/domains/art-tree/{material_tools.js → tools/materials.js} +2 -2
  92. package/domains/art-tree/{perspective_tools.js → tools/perspective.js} +2 -2
  93. package/domains/art-tree/{sky_tools.js → tools/sky.js} +2 -2
  94. package/domains/art-tree/{structure_tools.js → tools/structure.js} +2 -2
  95. package/domains/art-tree/{surface_tools.js → tools/surfaces.js} +2 -2
  96. package/domains/art-tree/{transform_tools.js → tools/transforms.js} +3 -3
  97. package/domains/art-tree/{vocab_tools.js → tools/vocab.js} +2 -2
  98. package/package.json +1 -1
  99. package/rules/README.md +358 -285
  100. package/rules/index.js +9 -9
  101. package/rules/prerequisites.js +121 -64
  102. package/rules/rules.js +76 -35
  103. package/rules/workflow.js +270 -56
  104. package/tools/canvas-tools.js +2 -2
  105. package/tools/drawing-tools.js +23 -12
  106. package/tools/workspace-tools.js +5 -4
  107. package/domains/art-tree/3d_lessons.js +0 -114
  108. package/domains/art-tree/advanced_lessons.js +0 -124
  109. package/domains/art-tree/cross_section_lessons.js +0 -65
  110. package/domains/art-tree/curve_lessons.js +0 -138
  111. package/domains/art-tree/ellipse_lessons.js +0 -105
  112. package/domains/art-tree/hidden_lessons.js +0 -55
  113. package/domains/art-tree/lessons.js +0 -305
  114. package/domains/art-tree/light_lessons.js +0 -92
  115. package/domains/art-tree/material_lessons.js +0 -71
  116. package/domains/art-tree/perspective_lessons.js +0 -138
  117. package/domains/art-tree/sky_lessons_1.js +0 -82
  118. package/domains/art-tree/surface_lessons.js +0 -112
  119. package/domains/art-tree/transform_lessons_1.js +0 -108
  120. /package/domains/art-tree/{3d_shapes.js → shapes/3d.js} +0 -0
  121. /package/domains/art-tree/{advanced_shapes.js → shapes/advanced.js} +0 -0
  122. /package/domains/art-tree/{analysis_shapes.js → shapes/analysis.js} +0 -0
  123. /package/domains/art-tree/{shapes.js → shapes/basic.js} +0 -0
  124. /package/domains/art-tree/{cross_section_shapes.js → shapes/cross-sections.js} +0 -0
  125. /package/domains/art-tree/{curve_shapes.js → shapes/curves.js} +0 -0
  126. /package/domains/art-tree/{ellipse_shapes.js → shapes/ellipses.js} +0 -0
  127. /package/domains/art-tree/{hidden_shapes.js → shapes/hidden.js} +0 -0
  128. /package/domains/art-tree/{layer_shapes.js → shapes/layers.js} +0 -0
  129. /package/domains/art-tree/{light_shapes.js → shapes/lighting.js} +0 -0
  130. /package/domains/art-tree/{material_shapes.js → shapes/materials.js} +0 -0
  131. /package/domains/art-tree/{perspective_shapes.js → shapes/perspective.js} +0 -0
  132. /package/domains/art-tree/{sky_shapes_1.js → shapes/sky.js} +0 -0
  133. /package/domains/art-tree/{structure_shapes.js → shapes/structure.js} +0 -0
  134. /package/domains/art-tree/{surface_shapes.js → shapes/surfaces.js} +0 -0
  135. /package/domains/art-tree/{transform_shapes.js → shapes/transforms.js} +0 -0
  136. /package/domains/art-tree/{vocab_shapes.js → shapes/vocab.js} +0 -0
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,249 @@ 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
120
202
  * @param {string} sessionId
121
- * @returns {Object} Kết quả chuyển bước
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
+ // Auto-set flags based on completed request type
215
+ if (state.user_input_request.type === 'CANVAS_SIZE') {
216
+ state.step_completion_flags.canvas_initialized = true;
217
+ }
218
+ if (state.user_input_request.type === 'APPROVE_PLAN') {
219
+ state.step_completion_flags.plan_created = true;
220
+ }
221
+
222
+ state.history.push({ event: 'input_request_completed', reqId, time: Date.now() });
223
+ saveState();
224
+ return true;
225
+ }
226
+
227
+ /**
228
+ * Update layer/frame
229
+ */
230
+ function setLayerAndFrame(sessionId, layer, frame = null) {
231
+ const state = sessions.get(sessionId);
232
+ if (!state) return false;
233
+
234
+ if (layer !== undefined && layer !== null) {
235
+ state.current_layer = layer;
236
+ }
237
+ if (frame !== undefined && frame !== null) {
238
+ state.current_frame = frame;
239
+ }
240
+ state.history.push({
241
+ event: 'layer_frame_change',
242
+ layer: state.current_layer,
243
+ frame: state.current_frame,
244
+ time: Date.now()
245
+ });
246
+ saveState();
247
+ return true;
248
+ }
249
+
250
+ /**
251
+ * Update phase
252
+ */
253
+ function setPhase(sessionId, phase) {
254
+ const state = sessions.get(sessionId);
255
+ if (!state) return false;
256
+
257
+ state.current_phase = phase;
258
+ state.history.push({
259
+ event: 'phase_change',
260
+ phase: phase,
261
+ time: Date.now()
262
+ });
263
+ saveState();
264
+ return true;
265
+ }
266
+
267
+ /**
268
+ * Set animation meta
269
+ */
270
+ function setAnimationMeta(sessionId, fps, total_frames, loop) {
271
+ const state = sessions.get(sessionId);
272
+ if (!state) return false;
273
+
274
+ if (fps !== undefined) state.animation_meta.fps = fps;
275
+ if (total_frames !== undefined) state.animation_meta.total_frames = total_frames;
276
+ if (loop !== undefined) state.animation_meta.loop = loop;
277
+
278
+ saveState();
279
+ return true;
280
+ }
281
+
282
+ /**
283
+ * Set step completion flag (internal validation)
284
+ */
285
+ function setStepFlag(sessionId, flagName, value) {
286
+ const state = sessions.get(sessionId);
287
+ if (!state) return false;
288
+ state.step_completion_flags[flagName] = value;
289
+ saveState();
290
+ return true;
291
+ }
292
+
293
+ /**
294
+ * Mark current step as completed and advance to the next step
295
+ * @param {string} sessionId
296
+ * @returns {Object} Step transition result
122
297
  */
123
298
  function advance(sessionId) {
124
299
  const state = sessions.get(sessionId);
125
300
  if (!state) {
126
- return { success: false, message: 'Chưa bắt đầu workflow' };
301
+ return { success: false, message: 'Workflow has not started' };
302
+ }
303
+
304
+ if (state.user_approval_required) {
305
+ return { success: false, message: 'Cannot advance: Waiting for user approval.' };
306
+ }
307
+
308
+ if (state.step_status === STATUSES.WAITING_FOR_USER) {
309
+ return { success: false, message: 'Cannot advance: Waiting for user approval or input.' };
127
310
  }
128
311
 
129
312
  const current = state.steps[state.currentStepIndex];
130
313
  if (!current) {
131
- return { success: false, message: 'Workflow đã hoàn tất' };
314
+ return { success: false, message: 'Workflow is complete' };
132
315
  }
133
316
 
134
- // Đánh dấu bước hiện tại là completed
135
- current.status = 'completed';
317
+ // Pre-condition validation block
318
+ if (current.id === 'INITIALIZE_CANVAS' && !state.step_completion_flags.canvas_initialized) {
319
+ return { success: false, message: 'Cannot advance: Canvas has not been initialized. You MUST use workflow_create_input_request with type CANVAS_SIZE first.' };
320
+ }
321
+ if (current.id === 'PLAN_DRAWING' && !state.step_completion_flags.plan_created) {
322
+ return { success: false, message: 'Cannot advance: Drawing plan has not been finalized.' };
323
+ }
324
+
325
+ current.status = STATUSES.COMPLETED;
136
326
  state.completedSteps.push(current.id);
327
+ state.history.push({ event: 'step_completed', step: current.id, layer: state.current_layer, frame: state.current_frame, time: Date.now() });
137
328
 
138
- // Chuyển sang bước tiếp theo
139
329
  state.currentStepIndex++;
140
330
 
141
331
  const next = state.steps[state.currentStepIndex] || null;
142
332
  if (next) {
143
- next.status = 'in_progress';
333
+ next.status = STATUSES.IN_PROGRESS;
334
+ state.step_status = STATUSES.IN_PROGRESS;
335
+ saveState();
144
336
  return {
145
337
  success: true,
146
338
  completedStep: current,
147
339
  nextStep: next,
148
- message: `✅ Hoàn thành: "${current.label}". Bước tiếp theo: "${next.label}" — ${next.description}`,
340
+ message: `✅ Completed: "${current.label}". Next step: "${next.label}" — ${next.description}`,
149
341
  };
150
342
  }
151
343
 
344
+ state.step_status = STATUSES.COMPLETED;
345
+ saveState();
152
346
  return {
153
347
  success: true,
154
348
  completedStep: current,
155
349
  nextStep: null,
156
- message: `🎉 Hoàn tất tất cả các bước! Tác phẩm đã sẵn sàng.`,
350
+ message: `🎉 All steps completed! The task is ready.`,
157
351
  };
158
352
  }
159
353
 
160
354
  /**
161
- * Reset workflow về đầu
355
+ * Reset workflow to the beginning
162
356
  * @param {string} sessionId
163
357
  */
164
358
  function reset(sessionId) {
359
+ const state = sessions.get(sessionId);
360
+ const mode = state ? state.current_mode : MODES.CREATE_IMAGE;
165
361
  sessions.delete(sessionId);
166
- return start(sessionId);
362
+ saveState();
363
+ return start(sessionId, mode);
167
364
  }
168
365
 
169
366
  /**
170
- * Lấy toàn bộ tiến trình dạng text để trả về cho agent
367
+ * Get text-based progress report for the agent
171
368
  * @param {string} sessionId
172
369
  * @returns {string}
173
370
  */
174
371
  function getProgressReport(sessionId) {
175
372
  const state = sessions.get(sessionId);
176
- if (!state) return 'Chưa bắt đầu workflow.';
373
+ if (!state) return 'Workflow has not started.';
374
+
375
+ const lines = [
376
+ `📋 **Workflow Progress: [Mode: ${state.current_mode}]**`,
377
+ `Layer: ${state.current_layer} | Frame: ${state.current_frame !== null ? state.current_frame : 'N/A'}`,
378
+ `Phase: ${state.current_phase !== null ? state.current_phase : 'N/A'}`,
379
+ `Animation Meta: FPS=${state.animation_meta.fps || 'N/A'}, Frames=${state.animation_meta.total_frames || 'N/A'}, Loop=${state.animation_meta.loop}`,
380
+ `Status: ${state.step_status}`,
381
+ `User Approval Required: ${state.user_approval_required ? 'YES' : 'NO'}`,
382
+ state.user_input_request && state.user_input_request.status === 'WAITING_FOR_USER' ? `⚠️ PENDING REQUEST: ${state.user_input_request.type}` : '',
383
+ `\n**Steps:**`
384
+ ];
177
385
 
178
- const lines = ['📋 **Tiến trình vẽ:**\n'];
179
386
  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**' : '';
387
+ const icon = step.status === STATUSES.COMPLETED ? '✅' :
388
+ step.status === STATUSES.IN_PROGRESS ? '🔄' : '⬜';
389
+ const marker = i === state.currentStepIndex ? ' ← **IN PROGRESS**' : '';
183
390
  lines.push(`${icon} **${step.label}**${marker}`);
184
- if (step.status === 'in_progress' || i === state.currentStepIndex) {
391
+ if (step.status === STATUSES.IN_PROGRESS || i === state.currentStepIndex) {
185
392
  lines.push(` → ${step.description}`);
186
393
  }
187
394
  });
@@ -197,4 +404,11 @@ module.exports = {
197
404
  advance,
198
405
  reset,
199
406
  getProgressReport,
407
+ setApprovalRequired,
408
+ createInputRequest,
409
+ completeInputRequest,
410
+ setLayerAndFrame,
411
+ setPhase,
412
+ setAnimationMeta,
413
+ setStepFlag
200
414
  };
@@ -16,8 +16,8 @@ function register(server) {
16
16
  registerTool(server, 'canvas_resize',
17
17
  'Resize canvas to new dimensions. mode=clear resets pixels, extend keeps them.',
18
18
  {
19
- width: z.number().int().min(1).max(1024).describe('New width in pixels'),
20
- height: z.number().int().min(1).max(1024).describe('New height in pixels'),
19
+ width: z.number().int().min(1).max(256).describe('New width in pixels (max 256)'),
20
+ height: z.number().int().min(1).max(256).describe('New height in pixels (max 256)'),
21
21
  mode: z.enum(['clear', 'extend', 'fit']).default('clear').describe('How to handle existing content'),
22
22
  dx: z.number().int().default(0).describe('X offset for content when extending'),
23
23
  dy: z.number().int().default(0).describe('Y offset for content when extending')
@@ -14,54 +14,65 @@ const hexColor = z.string().regex(/^#[0-9a-fA-F]{6}$/).describe('Hex color e.g.
14
14
  function register(server) {
15
15
  registerTool(server, 'draw_pixel',
16
16
  'Set a single pixel to a color',
17
- { x: z.number().int().describe('X coordinate'), y: z.number().int().describe('Y coordinate'), color: hexColor },
17
+ { x: z.number().int().max(1024).describe('X coordinate'), y: z.number().int().max(1024).describe('Y coordinate'), color: hexColor },
18
18
  (p) => ({ action: 'drawPixel', ...p }));
19
19
 
20
20
  registerTool(server, 'draw_erase',
21
21
  'Erase a single pixel (make transparent)',
22
- { x: z.number().int(), y: z.number().int() },
22
+ { x: z.number().int().max(1024), y: z.number().int().max(1024) },
23
23
  (p) => ({ action: 'erasePixel', ...p }));
24
24
 
25
+ registerTool(server, 'draw_pixels_bulk',
26
+ 'Set multiple pixels at once (much faster than calling draw_pixel repeatedly)',
27
+ {
28
+ pixels: z.array(z.object({
29
+ x: z.number().int().max(1024),
30
+ y: z.number().int().max(1024),
31
+ color: hexColor.optional()
32
+ })).max(5000).describe('Array of pixels {x, y, color}. Omit color to erase.')
33
+ },
34
+ (p) => ({ action: 'drawPixelsBulk', pixels: p.pixels }));
35
+
25
36
  registerTool(server, 'draw_line',
26
37
  'Draw a straight line using Bresenham algorithm',
27
- { x0: z.number().int(), y0: z.number().int(), x1: z.number().int(), y1: z.number().int(), color: hexColor },
38
+ { x0: z.number().int().max(1024), y0: z.number().int().max(1024), x1: z.number().int().max(1024), y1: z.number().int().max(1024), color: hexColor },
28
39
  (p) => ({ action: 'drawLine', ...p }));
29
40
 
30
41
  registerTool(server, 'draw_rect',
31
42
  'Draw a rectangle (outline or filled)',
32
43
  {
33
- x: z.number().int().describe('Top-left X'), y: z.number().int().describe('Top-left Y'),
34
- w: z.number().int().min(1).describe('Width'), h: z.number().int().min(1).describe('Height'),
44
+ x: z.number().int().max(1024).describe('Top-left X'), y: z.number().int().max(1024).describe('Top-left Y'),
45
+ w: z.number().int().min(1).max(256).describe('Width (max 256)'), h: z.number().int().min(1).max(256).describe('Height (max 256)'),
35
46
  color: hexColor, filled: z.boolean().default(false).describe('Fill interior?')
36
47
  },
37
48
  (p) => ({ action: 'drawRect', ...p }));
38
49
 
39
50
  registerTool(server, 'draw_circle',
40
51
  'Draw a circle (outline or filled)',
41
- { cx: z.number().int().describe('Center X'), cy: z.number().int().describe('Center Y'),
42
- r: z.number().int().min(1).describe('Radius'), color: hexColor, filled: z.boolean().default(false) },
52
+ { cx: z.number().int().max(1024).describe('Center X'), cy: z.number().int().max(1024).describe('Center Y'),
53
+ r: z.number().int().min(1).max(128).describe('Radius (max 128)'), color: hexColor, filled: z.boolean().default(false) },
43
54
  (p) => ({ action: 'drawCircle', ...p }));
44
55
 
45
56
  registerTool(server, 'draw_ellipse',
46
57
  'Draw an ellipse (outline or filled)',
47
- { cx: z.number().int(), cy: z.number().int(), rx: z.number().int().min(1).describe('Horizontal radius'),
48
- ry: z.number().int().min(1).describe('Vertical radius'), color: hexColor, filled: z.boolean().default(false) },
58
+ { cx: z.number().int().max(1024), cy: z.number().int().max(1024), rx: z.number().int().min(1).max(128).describe('Horizontal radius'),
59
+ ry: z.number().int().min(1).max(128).describe('Vertical radius'), color: hexColor, filled: z.boolean().default(false) },
49
60
  (p) => ({ action: 'drawEllipse', ...p }));
50
61
 
51
62
  registerTool(server, 'draw_polygon',
52
63
  'Draw a polygon from a list of points (outline or filled)',
53
- { points: z.array(z.object({ x: z.number().int(), y: z.number().int() })).min(3).describe('Array of {x,y} vertices'),
64
+ { points: z.array(z.object({ x: z.number().int().max(1024), y: z.number().int().max(1024) })).min(3).describe('Array of {x,y} vertices'),
54
65
  color: hexColor, filled: z.boolean().default(false) },
55
66
  (p) => ({ action: 'drawPolygon', ...p }));
56
67
 
57
68
  registerTool(server, 'draw_fill',
58
69
  'Flood-fill starting from (x,y) with a color',
59
- { x: z.number().int(), y: z.number().int(), color: hexColor },
70
+ { x: z.number().int().max(1024), y: z.number().int().max(1024), color: hexColor },
60
71
  (p) => ({ action: 'fill', ...p }));
61
72
 
62
73
  registerTool(server, 'draw_gradient_rect',
63
74
  'Draw a rectangle filled with a smooth gradient',
64
- { x: z.number().int(), y: z.number().int(), w: z.number().int().min(1), h: z.number().int().min(1),
75
+ { x: z.number().int().max(1024), y: z.number().int().max(1024), w: z.number().int().min(1).max(256).describe('Width (max 256)'), h: z.number().int().min(1).max(256).describe('Height (max 256)'),
65
76
  colorFrom: hexColor.describe('Start color'), colorTo: hexColor.describe('End color'),
66
77
  direction: z.enum(['h', 'v']).default('h').describe('h=horizontal, v=vertical') },
67
78
  (p) => ({ action: 'drawGradientRect', ...p }));
@@ -19,21 +19,22 @@ function register(server) {
19
19
  {},
20
20
  () => ({ action: 'getActiveTabId' }));
21
21
 
22
- server.tool('workspace_create_tab',
22
+ registerTool(server, 'workspace_create_tab',
23
23
  'Create a new blank canvas tab',
24
24
  {
25
25
  name: z.string().optional().describe('Tab name'),
26
- width: z.number().int().optional().describe('Canvas width'),
27
- height: z.number().int().optional().describe('Canvas height')
26
+ width: z.number().int().min(1).max(256).optional().describe('Canvas width (max 256)'),
27
+ height: z.number().int().min(1).max(256).optional().describe('Canvas height (max 256)')
28
28
  },
29
29
  async (p) => {
30
30
  if (p.width === undefined || p.height === undefined) {
31
31
  return {
32
+ action: 'noop',
32
33
  isError: true,
33
34
  content: [{ type: 'text', text: 'Please provide the width and height dimensions (resize) when creating a new canvas.' }]
34
35
  };
35
36
  }
36
- return sendCommand({ action: 'createTab', ...p });
37
+ return { action: 'createTab', ...p };
37
38
  }
38
39
  );
39
40