@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.
- package/clear-firebase.js +37 -0
- package/core/command-bus.js +6 -3
- package/core/server.js +98 -3
- package/domains/art-tree/README.md +134 -0
- package/domains/art-tree/REORGANIZATION_PLAN.md +168 -0
- package/domains/art-tree/REORGANIZATION_SUMMARY.md +260 -0
- package/domains/art-tree/index.js +58 -58
- package/domains/art-tree/lessons/3d/box-properties.js +38 -0
- package/domains/art-tree/lessons/3d/cone-properties.js +31 -0
- package/domains/art-tree/lessons/3d/cylinder-properties.js +29 -0
- package/domains/art-tree/lessons/3d/sphere-properties.js +25 -0
- package/domains/art-tree/lessons/advanced/angles.js +30 -0
- package/domains/art-tree/lessons/advanced/distances.js +26 -0
- package/domains/art-tree/lessons/advanced/shape-ratios.js +25 -0
- package/domains/art-tree/lessons/advanced/shape-relationships.js +28 -0
- package/domains/art-tree/lessons/advanced/symmetry-and-axis.js +26 -0
- package/domains/art-tree/{analysis_lessons.js → lessons/analysis/object-analysis.js} +5 -16
- package/domains/art-tree/lessons/basic/circles.js +26 -0
- package/domains/art-tree/lessons/basic/composition.js +34 -0
- package/domains/art-tree/lessons/basic/curves.js +31 -0
- package/domains/art-tree/lessons/basic/ellipses.js +26 -0
- package/domains/art-tree/lessons/basic/freehand.js +19 -0
- package/domains/art-tree/lessons/basic/index.js +36 -0
- package/domains/art-tree/lessons/basic/lines.js +38 -0
- package/domains/art-tree/lessons/basic/parallel-and-intersecting.js +22 -0
- package/domains/art-tree/lessons/basic/polygons.js +25 -0
- package/domains/art-tree/lessons/basic/proportions-and-angles.js +20 -0
- package/domains/art-tree/lessons/basic/spiral.js +21 -0
- package/domains/art-tree/lessons/basic/squares.js +28 -0
- package/domains/art-tree/lessons/basic/strokes.js +23 -0
- package/domains/art-tree/lessons/basic/triangles.js +30 -0
- package/domains/art-tree/lessons/cross-sections/bottle-sections.js +26 -0
- package/domains/art-tree/lessons/cross-sections/glass-sections.js +24 -0
- package/domains/art-tree/lessons/cross-sections/head-sections.js +17 -0
- package/domains/art-tree/lessons/curves/curve-properties.js +50 -0
- package/domains/art-tree/lessons/curves/curve-topology.js +45 -0
- package/domains/art-tree/lessons/curves/curve-types.js +44 -0
- package/domains/art-tree/lessons/ellipses/coaxial-ellipses.js +29 -0
- package/domains/art-tree/lessons/ellipses/ellipse-anatomy.js +26 -0
- package/domains/art-tree/lessons/ellipses/ellipse-orientations.js +28 -0
- package/domains/art-tree/lessons/ellipses/ellipse-proportions.js +27 -0
- package/domains/art-tree/lessons/hidden/hidden-box.js +20 -0
- package/domains/art-tree/lessons/hidden/hidden-cup.js +19 -0
- package/domains/art-tree/lessons/hidden/hidden-head.js +18 -0
- package/domains/art-tree/lessons/index.js +42 -0
- package/domains/art-tree/{layer_lessons.js → lessons/layers/layer-step.js} +5 -16
- package/domains/art-tree/lessons/lighting/contact-shadow.js +33 -0
- package/domains/art-tree/lessons/lighting/light-direction.js +30 -0
- package/domains/art-tree/lessons/lighting/light-zones.js +30 -0
- package/domains/art-tree/lessons/materials/material-glass.js +21 -0
- package/domains/art-tree/lessons/materials/material-shiny.js +26 -0
- package/domains/art-tree/lessons/materials/material-texture.js +26 -0
- package/domains/art-tree/lessons/perspective/1point-perspective.js +30 -0
- package/domains/art-tree/lessons/perspective/2point-perspective.js +30 -0
- package/domains/art-tree/lessons/perspective/3point-perspective.js +45 -0
- package/domains/art-tree/lessons/perspective/foreshortening.js +38 -0
- package/domains/art-tree/lessons/sky/sun-shapes.js +25 -0
- package/domains/art-tree/lessons/sky/sunset-colors.js +27 -0
- package/domains/art-tree/lessons/sky/sunset-sky.js +32 -0
- package/domains/art-tree/{structure_lessons.js → lessons/structure/axis-orientation.js} +3 -53
- package/domains/art-tree/lessons/structure/complex-structure.js +21 -0
- package/domains/art-tree/lessons/structure/x-yzaxes.js +28 -0
- package/domains/art-tree/lessons/surfaces/chair-analysis.js +17 -0
- package/domains/art-tree/lessons/surfaces/cup-analysis.js +18 -0
- package/domains/art-tree/lessons/surfaces/edge-types.js +40 -0
- package/domains/art-tree/lessons/surfaces/surface-types.js +42 -0
- package/domains/art-tree/lessons/transforms/bend.js +27 -0
- package/domains/art-tree/{transform_lessons_2.js → lessons/transforms/combine.js} +3 -54
- package/domains/art-tree/lessons/transforms/cut.js +25 -0
- package/domains/art-tree/lessons/transforms/hollow.js +24 -0
- package/domains/art-tree/lessons/transforms/rotate.js +16 -0
- package/domains/art-tree/lessons/transforms/squash.js +31 -0
- package/domains/art-tree/lessons/transforms/stretch.js +31 -0
- package/domains/art-tree/lessons/transforms/taper-swell.js +33 -0
- package/domains/art-tree/{vocab_lessons.js → lessons/vocab/vocab-house.js} +5 -16
- package/domains/art-tree/lib/tool-register.js +104 -0
- package/domains/art-tree/lib/validators.js +78 -0
- package/domains/art-tree/shapes/grid.js +37 -0
- package/domains/art-tree/shapes/polyline.js +26 -0
- package/domains/art-tree/{3d_tools.js → tools/3d.js} +2 -2
- package/domains/art-tree/{advanced_tools.js → tools/advanced.js} +2 -2
- package/domains/art-tree/{analysis_tools.js → tools/analysis.js} +2 -2
- package/domains/art-tree/{tools.js → tools/basic.js} +3 -3
- package/domains/art-tree/{cross_section_tools.js → tools/cross-sections.js} +2 -2
- package/domains/art-tree/{curve_tools.js → tools/curves.js} +2 -2
- package/domains/art-tree/{ellipse_tools.js → tools/ellipses.js} +2 -2
- package/domains/art-tree/{hidden_tools.js → tools/hidden.js} +2 -2
- package/domains/art-tree/tools/index.js +53 -0
- package/domains/art-tree/{layer_tools.js → tools/layers.js} +2 -2
- package/domains/art-tree/{light_tools.js → tools/lighting.js} +2 -2
- package/domains/art-tree/{material_tools.js → tools/materials.js} +2 -2
- package/domains/art-tree/{perspective_tools.js → tools/perspective.js} +2 -2
- package/domains/art-tree/{sky_tools.js → tools/sky.js} +2 -2
- package/domains/art-tree/{structure_tools.js → tools/structure.js} +2 -2
- package/domains/art-tree/{surface_tools.js → tools/surfaces.js} +2 -2
- package/domains/art-tree/{transform_tools.js → tools/transforms.js} +3 -3
- package/domains/art-tree/{vocab_tools.js → tools/vocab.js} +2 -2
- package/package.json +1 -1
- package/rules/README.md +358 -285
- package/rules/index.js +9 -9
- package/rules/prerequisites.js +121 -64
- package/rules/rules.js +76 -35
- package/rules/workflow.js +270 -56
- package/tools/canvas-tools.js +2 -2
- package/tools/drawing-tools.js +23 -12
- package/tools/workspace-tools.js +5 -4
- package/domains/art-tree/3d_lessons.js +0 -114
- package/domains/art-tree/advanced_lessons.js +0 -124
- package/domains/art-tree/cross_section_lessons.js +0 -65
- package/domains/art-tree/curve_lessons.js +0 -138
- package/domains/art-tree/ellipse_lessons.js +0 -105
- package/domains/art-tree/hidden_lessons.js +0 -55
- package/domains/art-tree/lessons.js +0 -305
- package/domains/art-tree/light_lessons.js +0 -92
- package/domains/art-tree/material_lessons.js +0 -71
- package/domains/art-tree/perspective_lessons.js +0 -138
- package/domains/art-tree/sky_lessons_1.js +0 -82
- package/domains/art-tree/surface_lessons.js +0 -112
- package/domains/art-tree/transform_lessons_1.js +0 -108
- /package/domains/art-tree/{3d_shapes.js → shapes/3d.js} +0 -0
- /package/domains/art-tree/{advanced_shapes.js → shapes/advanced.js} +0 -0
- /package/domains/art-tree/{analysis_shapes.js → shapes/analysis.js} +0 -0
- /package/domains/art-tree/{shapes.js → shapes/basic.js} +0 -0
- /package/domains/art-tree/{cross_section_shapes.js → shapes/cross-sections.js} +0 -0
- /package/domains/art-tree/{curve_shapes.js → shapes/curves.js} +0 -0
- /package/domains/art-tree/{ellipse_shapes.js → shapes/ellipses.js} +0 -0
- /package/domains/art-tree/{hidden_shapes.js → shapes/hidden.js} +0 -0
- /package/domains/art-tree/{layer_shapes.js → shapes/layers.js} +0 -0
- /package/domains/art-tree/{light_shapes.js → shapes/lighting.js} +0 -0
- /package/domains/art-tree/{material_shapes.js → shapes/materials.js} +0 -0
- /package/domains/art-tree/{perspective_shapes.js → shapes/perspective.js} +0 -0
- /package/domains/art-tree/{sky_shapes_1.js → shapes/sky.js} +0 -0
- /package/domains/art-tree/{structure_shapes.js → shapes/structure.js} +0 -0
- /package/domains/art-tree/{surface_shapes.js → shapes/surfaces.js} +0 -0
- /package/domains/art-tree/{transform_shapes.js → shapes/transforms.js} +0 -0
- /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 (
|
|
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
|
-
*
|
|
27
|
-
* @param {string} sessionId -
|
|
28
|
-
* @
|
|
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:
|
|
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
|
-
*
|
|
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
|
-
*
|
|
85
|
+
* Get current step
|
|
52
86
|
* @param {string} sessionId
|
|
53
|
-
* @returns {
|
|
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
|
-
*
|
|
96
|
+
* Check if the agent is allowed to perform this action
|
|
63
97
|
* @param {string} sessionId
|
|
64
|
-
* @param {string} stepId - ID
|
|
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: '⚠️
|
|
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
|
|
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: `❌
|
|
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: `⚠️
|
|
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: `⛔
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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: '
|
|
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
|
|
314
|
+
return { success: false, message: 'Workflow is complete' };
|
|
132
315
|
}
|
|
133
316
|
|
|
134
|
-
//
|
|
135
|
-
current.
|
|
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 =
|
|
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: `✅
|
|
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: `🎉
|
|
350
|
+
message: `🎉 All steps completed! The task is ready.`,
|
|
157
351
|
};
|
|
158
352
|
}
|
|
159
353
|
|
|
160
354
|
/**
|
|
161
|
-
* Reset workflow
|
|
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
|
-
|
|
362
|
+
saveState();
|
|
363
|
+
return start(sessionId, mode);
|
|
167
364
|
}
|
|
168
365
|
|
|
169
366
|
/**
|
|
170
|
-
*
|
|
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 '
|
|
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 ===
|
|
181
|
-
|
|
182
|
-
const marker = i === state.currentStepIndex ? ' ←
|
|
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 ===
|
|
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
|
};
|
package/tools/canvas-tools.js
CHANGED
|
@@ -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(
|
|
20
|
-
height: z.number().int().min(1).max(
|
|
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')
|
package/tools/drawing-tools.js
CHANGED
|
@@ -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 }));
|
package/tools/workspace-tools.js
CHANGED
|
@@ -19,21 +19,22 @@ function register(server) {
|
|
|
19
19
|
{},
|
|
20
20
|
() => ({ action: 'getActiveTabId' }));
|
|
21
21
|
|
|
22
|
-
server
|
|
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
|
|
37
|
+
return { action: 'createTab', ...p };
|
|
37
38
|
}
|
|
38
39
|
);
|
|
39
40
|
|