makecc 0.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/bin/cli.js +111 -0
- package/dist/assets/index-BcaHeFM8.css +1 -0
- package/dist/assets/index-CryYaA6W.js +57 -0
- package/dist/client/assets/index-BkAJX_uj.js +133 -0
- package/dist/client/assets/index-CRHGYTA2.css +1 -0
- package/dist/client/index.html +14 -0
- package/dist/client/vite.svg +1 -0
- package/dist/index.html +14 -0
- package/dist/server/index.js +207 -0
- package/dist/server/services/claudeService.js +153 -0
- package/dist/server/services/fileService.js +193 -0
- package/dist/server/services/skillExecutionService.js +622 -0
- package/dist/server/services/terminalService.js +216 -0
- package/dist/server/services/workflowAIService.js +191 -0
- package/dist/server/services/workflowExecutionService.js +342 -0
- package/dist/server/types.js +1 -0
- package/dist/vite.svg +1 -0
- package/package.json +84 -0
- package/server/index.ts +253 -0
- package/server/services/claudeService.ts +222 -0
- package/server/services/fileService.ts +277 -0
- package/server/services/skillExecutionService.ts +732 -0
- package/server/services/terminalService.ts +266 -0
- package/server/services/workflowAIService.ts +240 -0
- package/server/services/workflowExecutionService.ts +471 -0
- package/server/types.ts +110 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { writeFile, mkdir } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { skillExecutionService } from './skillExecutionService';
|
|
6
|
+
import type {
|
|
7
|
+
ExecutionNode,
|
|
8
|
+
SubagentNodeData,
|
|
9
|
+
SkillNodeData,
|
|
10
|
+
InputNodeData,
|
|
11
|
+
OutputNodeData,
|
|
12
|
+
McpNodeData,
|
|
13
|
+
NodeExecutionUpdate,
|
|
14
|
+
} from '../types';
|
|
15
|
+
|
|
16
|
+
export interface WorkflowEdge {
|
|
17
|
+
id: string;
|
|
18
|
+
source: string;
|
|
19
|
+
target: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ExecutionContext {
|
|
23
|
+
workflowId: string;
|
|
24
|
+
workflowName: string;
|
|
25
|
+
nodes: ExecutionNode[];
|
|
26
|
+
edges: WorkflowEdge[];
|
|
27
|
+
inputs?: Record<string, string>;
|
|
28
|
+
outputDir: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ExecutionResult {
|
|
32
|
+
nodeId: string;
|
|
33
|
+
success: boolean;
|
|
34
|
+
result?: string;
|
|
35
|
+
files?: Array<{ path: string; type: string; name: string }>;
|
|
36
|
+
error?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type ProgressCallback = (update: NodeExecutionUpdate) => void;
|
|
40
|
+
type LogCallback = (type: 'info' | 'warn' | 'error' | 'debug', message: string) => void;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Anthropic API를 사용한 워크플로우 실행 서비스
|
|
44
|
+
*/
|
|
45
|
+
export class WorkflowExecutionService {
|
|
46
|
+
private client: Anthropic;
|
|
47
|
+
private results: Map<string, ExecutionResult> = new Map();
|
|
48
|
+
private outputDir: string = '';
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
this.client = new Anthropic({
|
|
52
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 워크플로우 전체 실행
|
|
58
|
+
*/
|
|
59
|
+
async execute(
|
|
60
|
+
context: ExecutionContext,
|
|
61
|
+
onProgress?: ProgressCallback,
|
|
62
|
+
onLog?: LogCallback
|
|
63
|
+
): Promise<Map<string, ExecutionResult>> {
|
|
64
|
+
this.results.clear();
|
|
65
|
+
this.outputDir = context.outputDir;
|
|
66
|
+
|
|
67
|
+
// 출력 디렉토리 생성
|
|
68
|
+
if (!existsSync(this.outputDir)) {
|
|
69
|
+
await mkdir(this.outputDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const executionOrder = this.topologicalSort(context.nodes, context.edges);
|
|
73
|
+
|
|
74
|
+
onLog?.('info', `워크플로우 "${context.workflowName}" 실행 시작 (${executionOrder.length}개 노드)`);
|
|
75
|
+
|
|
76
|
+
for (const node of executionOrder) {
|
|
77
|
+
try {
|
|
78
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 0 });
|
|
79
|
+
onLog?.('info', `노드 "${node.data.label}" 실행 중...`);
|
|
80
|
+
|
|
81
|
+
const result = await this.executeNode(node, context, onProgress, onLog);
|
|
82
|
+
this.results.set(node.id, result);
|
|
83
|
+
|
|
84
|
+
if (result.success) {
|
|
85
|
+
onProgress?.({ nodeId: node.id, status: 'completed', progress: 100, result: result.result });
|
|
86
|
+
onLog?.('info', `노드 "${node.data.label}" 완료`);
|
|
87
|
+
} else {
|
|
88
|
+
onProgress?.({ nodeId: node.id, status: 'error', error: result.error });
|
|
89
|
+
onLog?.('error', `노드 "${node.data.label}" 실패: ${result.error}`);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
93
|
+
this.results.set(node.id, { nodeId: node.id, success: false, error: errorMessage });
|
|
94
|
+
onProgress?.({ nodeId: node.id, status: 'error', error: errorMessage });
|
|
95
|
+
onLog?.('error', `노드 "${node.data.label}" 실행 오류: ${errorMessage}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return this.results;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 개별 노드 실행
|
|
104
|
+
*/
|
|
105
|
+
private async executeNode(
|
|
106
|
+
node: ExecutionNode,
|
|
107
|
+
context: ExecutionContext,
|
|
108
|
+
onProgress?: ProgressCallback,
|
|
109
|
+
onLog?: LogCallback
|
|
110
|
+
): Promise<ExecutionResult> {
|
|
111
|
+
// 이전 노드 결과 수집
|
|
112
|
+
const previousResults = this.collectPreviousResults(node, context.edges);
|
|
113
|
+
|
|
114
|
+
switch (node.type) {
|
|
115
|
+
case 'input':
|
|
116
|
+
return this.executeInputNode(node, context.inputs);
|
|
117
|
+
|
|
118
|
+
case 'subagent':
|
|
119
|
+
return this.executeSubagentNode(node, previousResults, onProgress, onLog);
|
|
120
|
+
|
|
121
|
+
case 'skill':
|
|
122
|
+
return this.executeSkillNode(node, previousResults, onProgress, onLog);
|
|
123
|
+
|
|
124
|
+
case 'mcp':
|
|
125
|
+
return this.executeMcpNode(node, previousResults, onProgress, onLog);
|
|
126
|
+
|
|
127
|
+
case 'output':
|
|
128
|
+
return this.executeOutputNode(node, previousResults, onLog);
|
|
129
|
+
|
|
130
|
+
default:
|
|
131
|
+
return { nodeId: node.id, success: false, error: `Unknown node type: ${node.type}` };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Input 노드 실행 - 사용자 입력값 반환
|
|
137
|
+
*/
|
|
138
|
+
private async executeInputNode(
|
|
139
|
+
node: ExecutionNode,
|
|
140
|
+
inputs?: Record<string, string>
|
|
141
|
+
): Promise<ExecutionResult> {
|
|
142
|
+
const data = node.data as InputNodeData;
|
|
143
|
+
const value = inputs?.[node.id] || data.value || '';
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
nodeId: node.id,
|
|
147
|
+
success: true,
|
|
148
|
+
result: value,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Subagent 노드 실행 - Claude API로 작업 수행
|
|
154
|
+
*/
|
|
155
|
+
private async executeSubagentNode(
|
|
156
|
+
node: ExecutionNode,
|
|
157
|
+
previousResults: string,
|
|
158
|
+
onProgress?: ProgressCallback,
|
|
159
|
+
onLog?: LogCallback
|
|
160
|
+
): Promise<ExecutionResult> {
|
|
161
|
+
const data = node.data as SubagentNodeData;
|
|
162
|
+
|
|
163
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 20 });
|
|
164
|
+
|
|
165
|
+
// 역할별 시스템 프롬프트
|
|
166
|
+
const rolePrompts: Record<string, string> = {
|
|
167
|
+
researcher: `당신은 전문 리서처입니다. 주어진 주제에 대해 깊이 있는 조사를 수행하고, 핵심 정보를 정리하여 제공합니다.`,
|
|
168
|
+
writer: `당신은 전문 작가입니다. 명확하고 매력적인 콘텐츠를 작성합니다. 사용자의 요구에 맞는 톤과 스타일로 글을 작성합니다.`,
|
|
169
|
+
analyst: `당신은 데이터 분석가입니다. 정보를 분석하고 패턴을 파악하여 인사이트를 도출합니다.`,
|
|
170
|
+
coder: `당신은 전문 개발자입니다. 깔끔하고 효율적인 코드를 작성하며, 모범 사례를 따릅니다.`,
|
|
171
|
+
designer: `당신은 디자인 전문가입니다. 상세페이지, 배너, UI 등을 위한 디자인 가이드와 컨셉을 제안합니다.`,
|
|
172
|
+
custom: `당신은 AI 어시스턴트입니다. 주어진 작업을 최선을 다해 수행합니다.`,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const systemPrompt = data.systemPrompt || rolePrompts[data.role] || rolePrompts.custom;
|
|
176
|
+
|
|
177
|
+
const userMessage = `## 작업 설명
|
|
178
|
+
${data.description || '주어진 작업을 수행해주세요.'}
|
|
179
|
+
|
|
180
|
+
## 이전 단계 결과
|
|
181
|
+
${previousResults || '(없음)'}
|
|
182
|
+
|
|
183
|
+
위 내용을 바탕으로 작업을 수행하고 결과를 제공해주세요.`;
|
|
184
|
+
|
|
185
|
+
onLog?.('debug', `Subagent "${data.label}" (${data.role}) 호출 중...`);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 40 });
|
|
189
|
+
|
|
190
|
+
const modelId = this.getModelId(data.model);
|
|
191
|
+
|
|
192
|
+
const response = await this.client.messages.create({
|
|
193
|
+
model: modelId,
|
|
194
|
+
max_tokens: 4096,
|
|
195
|
+
system: systemPrompt,
|
|
196
|
+
messages: [
|
|
197
|
+
{ role: 'user', content: userMessage }
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 80 });
|
|
202
|
+
|
|
203
|
+
// 응답 텍스트 추출
|
|
204
|
+
const resultText = response.content
|
|
205
|
+
.filter((block): block is Anthropic.TextBlock => block.type === 'text')
|
|
206
|
+
.map((block) => block.text)
|
|
207
|
+
.join('\n');
|
|
208
|
+
|
|
209
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 100 });
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
nodeId: node.id,
|
|
213
|
+
success: true,
|
|
214
|
+
result: resultText,
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const errorMsg = error instanceof Error ? error.message : 'Claude API 호출 실패';
|
|
218
|
+
onLog?.('error', `Subagent 오류: ${errorMsg}`);
|
|
219
|
+
return {
|
|
220
|
+
nodeId: node.id,
|
|
221
|
+
success: false,
|
|
222
|
+
error: errorMsg,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Skill 노드 실행 - skillExecutionService 사용
|
|
229
|
+
*/
|
|
230
|
+
private async executeSkillNode(
|
|
231
|
+
node: ExecutionNode,
|
|
232
|
+
previousResults: string,
|
|
233
|
+
onProgress?: ProgressCallback,
|
|
234
|
+
onLog?: LogCallback
|
|
235
|
+
): Promise<ExecutionResult> {
|
|
236
|
+
const data = node.data as SkillNodeData;
|
|
237
|
+
const skillId = data.skillId || 'generic';
|
|
238
|
+
|
|
239
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 10 });
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
// skillExecutionService를 사용하여 실제 파일 생성
|
|
243
|
+
const result = await skillExecutionService.execute(
|
|
244
|
+
skillId,
|
|
245
|
+
previousResults,
|
|
246
|
+
this.outputDir,
|
|
247
|
+
onLog
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 100 });
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
nodeId: node.id,
|
|
254
|
+
success: result.success,
|
|
255
|
+
result: result.result,
|
|
256
|
+
files: result.files,
|
|
257
|
+
error: result.error,
|
|
258
|
+
};
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const errorMsg = error instanceof Error ? error.message : '스킬 실행 실패';
|
|
261
|
+
onLog?.('error', errorMsg);
|
|
262
|
+
return {
|
|
263
|
+
nodeId: node.id,
|
|
264
|
+
success: false,
|
|
265
|
+
error: errorMsg,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* MCP 노드 실행 - 외부 도구/서비스 연결
|
|
272
|
+
*/
|
|
273
|
+
private async executeMcpNode(
|
|
274
|
+
node: ExecutionNode,
|
|
275
|
+
previousResults: string,
|
|
276
|
+
onProgress?: ProgressCallback,
|
|
277
|
+
onLog?: LogCallback
|
|
278
|
+
): Promise<ExecutionResult> {
|
|
279
|
+
const data = node.data as McpNodeData;
|
|
280
|
+
|
|
281
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 10 });
|
|
282
|
+
onLog?.('info', `MCP 서버 "${data.serverName}" 연결 중...`);
|
|
283
|
+
|
|
284
|
+
// MCP 서버 타입별 처리
|
|
285
|
+
const mcpPrompt = `당신은 MCP (Model Context Protocol) 서버와 상호작용하는 전문가입니다.
|
|
286
|
+
|
|
287
|
+
## MCP 서버 정보
|
|
288
|
+
- 서버 이름: ${data.serverName}
|
|
289
|
+
- 서버 타입: ${data.serverType}
|
|
290
|
+
- 설정: ${JSON.stringify(data.serverConfig, null, 2)}
|
|
291
|
+
|
|
292
|
+
## 이전 단계 결과
|
|
293
|
+
${previousResults}
|
|
294
|
+
|
|
295
|
+
## 작업
|
|
296
|
+
위 MCP 서버를 사용하여 이전 단계의 결과를 처리하세요.
|
|
297
|
+
|
|
298
|
+
다음 MCP 서버 유형에 따라 적절한 작업을 수행하세요:
|
|
299
|
+
- PostgreSQL/데이터베이스: 데이터 조회 또는 저장
|
|
300
|
+
- Notion/Google Drive: 문서 생성 또는 업데이트
|
|
301
|
+
- Slack/Discord: 메시지 전송 시뮬레이션
|
|
302
|
+
- GitHub/Jira: 이슈 또는 PR 관련 작업 시뮬레이션
|
|
303
|
+
|
|
304
|
+
작업 결과를 상세히 설명해주세요.`;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 50 });
|
|
308
|
+
|
|
309
|
+
const response = await this.client.messages.create({
|
|
310
|
+
model: 'claude-sonnet-4-20250514',
|
|
311
|
+
max_tokens: 4096,
|
|
312
|
+
messages: [{ role: 'user', content: mcpPrompt }],
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const result = response.content
|
|
316
|
+
.filter((block): block is Anthropic.TextBlock => block.type === 'text')
|
|
317
|
+
.map((block) => block.text)
|
|
318
|
+
.join('\n');
|
|
319
|
+
|
|
320
|
+
onProgress?.({ nodeId: node.id, status: 'running', progress: 100 });
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
nodeId: node.id,
|
|
324
|
+
success: true,
|
|
325
|
+
result,
|
|
326
|
+
};
|
|
327
|
+
} catch (error) {
|
|
328
|
+
return {
|
|
329
|
+
nodeId: node.id,
|
|
330
|
+
success: false,
|
|
331
|
+
error: error instanceof Error ? error.message : 'MCP 노드 실행 실패',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Output 노드 실행 - 결과 수집 및 파일 저장
|
|
338
|
+
*/
|
|
339
|
+
private async executeOutputNode(
|
|
340
|
+
node: ExecutionNode,
|
|
341
|
+
previousResults: string,
|
|
342
|
+
onLog?: LogCallback
|
|
343
|
+
): Promise<ExecutionResult> {
|
|
344
|
+
const data = node.data as OutputNodeData;
|
|
345
|
+
|
|
346
|
+
onLog?.('info', '최종 결과 수집 및 저장 중...');
|
|
347
|
+
|
|
348
|
+
// 모든 이전 결과와 파일 수집
|
|
349
|
+
const allFiles: Array<{ path: string; type: string; name: string }> = [];
|
|
350
|
+
|
|
351
|
+
for (const [, result] of this.results) {
|
|
352
|
+
if (result.files) {
|
|
353
|
+
allFiles.push(...result.files);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 결과 요약 파일 생성
|
|
358
|
+
const summaryPath = join(this.outputDir, 'result-summary.md');
|
|
359
|
+
const summaryContent = `# 워크플로우 실행 결과
|
|
360
|
+
|
|
361
|
+
## 생성된 컨텐츠
|
|
362
|
+
|
|
363
|
+
${previousResults}
|
|
364
|
+
|
|
365
|
+
## 생성된 파일 목록
|
|
366
|
+
${allFiles.map((f) => `- **${f.name}**: \`${f.path}\``).join('\n') || '없음'}
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
생성 시간: ${new Date().toLocaleString('ko-KR')}
|
|
370
|
+
`;
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
await writeFile(summaryPath, summaryContent, 'utf-8');
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
nodeId: node.id,
|
|
377
|
+
success: true,
|
|
378
|
+
result: summaryContent,
|
|
379
|
+
files: [
|
|
380
|
+
{ path: summaryPath, type: 'markdown', name: '결과 요약' },
|
|
381
|
+
...allFiles,
|
|
382
|
+
],
|
|
383
|
+
};
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return {
|
|
386
|
+
nodeId: node.id,
|
|
387
|
+
success: true,
|
|
388
|
+
result: previousResults,
|
|
389
|
+
files: allFiles,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 모델 ID 변환
|
|
396
|
+
*/
|
|
397
|
+
private getModelId(model?: 'sonnet' | 'opus' | 'haiku'): string {
|
|
398
|
+
switch (model) {
|
|
399
|
+
case 'opus':
|
|
400
|
+
return 'claude-opus-4-20250514';
|
|
401
|
+
case 'haiku':
|
|
402
|
+
return 'claude-3-5-haiku-20241022';
|
|
403
|
+
default:
|
|
404
|
+
return 'claude-sonnet-4-20250514';
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 이전 노드 결과 수집
|
|
410
|
+
*/
|
|
411
|
+
private collectPreviousResults(node: ExecutionNode, edges: WorkflowEdge[]): string {
|
|
412
|
+
const incomingEdges = edges.filter((e) => e.target === node.id);
|
|
413
|
+
const previousResults: string[] = [];
|
|
414
|
+
|
|
415
|
+
for (const edge of incomingEdges) {
|
|
416
|
+
const result = this.results.get(edge.source);
|
|
417
|
+
if (result?.result) {
|
|
418
|
+
previousResults.push(result.result);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return previousResults.join('\n\n---\n\n');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 위상 정렬 (실행 순서 결정)
|
|
427
|
+
*/
|
|
428
|
+
private topologicalSort(nodes: ExecutionNode[], edges: WorkflowEdge[]): ExecutionNode[] {
|
|
429
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
430
|
+
const inDegree = new Map<string, number>();
|
|
431
|
+
const adjList = new Map<string, string[]>();
|
|
432
|
+
|
|
433
|
+
nodes.forEach((node) => {
|
|
434
|
+
inDegree.set(node.id, 0);
|
|
435
|
+
adjList.set(node.id, []);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
edges.forEach((edge) => {
|
|
439
|
+
adjList.get(edge.source)?.push(edge.target);
|
|
440
|
+
inDegree.set(edge.target, (inDegree.get(edge.target) || 0) + 1);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const queue: string[] = [];
|
|
444
|
+
inDegree.forEach((degree, nodeId) => {
|
|
445
|
+
if (degree === 0) {
|
|
446
|
+
queue.push(nodeId);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const result: ExecutionNode[] = [];
|
|
451
|
+
while (queue.length > 0) {
|
|
452
|
+
const nodeId = queue.shift()!;
|
|
453
|
+
const node = nodeMap.get(nodeId);
|
|
454
|
+
if (node) {
|
|
455
|
+
result.push(node);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
adjList.get(nodeId)?.forEach((neighborId) => {
|
|
459
|
+
const newDegree = (inDegree.get(neighborId) || 0) - 1;
|
|
460
|
+
inDegree.set(neighborId, newDegree);
|
|
461
|
+
if (newDegree === 0) {
|
|
462
|
+
queue.push(neighborId);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return result;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export const workflowExecutionService = new WorkflowExecutionService();
|
package/server/types.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Node status type
|
|
2
|
+
export type NodeStatus = 'idle' | 'pending' | 'running' | 'completed' | 'error';
|
|
3
|
+
|
|
4
|
+
// Base node data
|
|
5
|
+
export interface BaseNodeData {
|
|
6
|
+
label: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
status: NodeStatus;
|
|
9
|
+
progress?: number;
|
|
10
|
+
error?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Node types
|
|
15
|
+
export type InputType = 'text' | 'file' | 'select' | 'multi';
|
|
16
|
+
export type AgentRole = 'researcher' | 'writer' | 'analyst' | 'coder' | 'custom';
|
|
17
|
+
export type SkillType = 'official' | 'custom';
|
|
18
|
+
export type McpServerType = 'stdio' | 'sse' | 'http';
|
|
19
|
+
export type OutputType = 'markdown' | 'document' | 'image' | 'webpage' | 'link' | 'auto';
|
|
20
|
+
|
|
21
|
+
export interface InputNodeData extends BaseNodeData {
|
|
22
|
+
inputType: InputType;
|
|
23
|
+
value?: string;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
options?: string[];
|
|
26
|
+
fileTypes?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SubagentNodeData extends BaseNodeData {
|
|
30
|
+
role: AgentRole;
|
|
31
|
+
tools: string[];
|
|
32
|
+
mdContent?: string;
|
|
33
|
+
systemPrompt?: string;
|
|
34
|
+
model?: 'sonnet' | 'opus' | 'haiku';
|
|
35
|
+
usedInputs?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SkillNodeData extends BaseNodeData {
|
|
39
|
+
skillType: SkillType;
|
|
40
|
+
skillId?: string;
|
|
41
|
+
skillCategory?: string;
|
|
42
|
+
mdContent?: string;
|
|
43
|
+
skillContent?: string; // AI가 생성한 커스텀 스킬 SKILL.md 내용
|
|
44
|
+
usedInputs?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface McpNodeData extends BaseNodeData {
|
|
48
|
+
serverType: McpServerType;
|
|
49
|
+
serverName: string;
|
|
50
|
+
serverConfig: {
|
|
51
|
+
command?: string;
|
|
52
|
+
args?: string[];
|
|
53
|
+
url?: string;
|
|
54
|
+
env?: Record<string, string>;
|
|
55
|
+
};
|
|
56
|
+
usedInputs?: string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface OutputNodeData extends BaseNodeData {
|
|
60
|
+
outputType: OutputType;
|
|
61
|
+
layoutType?: 'manual' | 'auto' | 'google-docs' | 'google-slides' | 'google-sheets';
|
|
62
|
+
result?: string;
|
|
63
|
+
filePath?: string;
|
|
64
|
+
usedInputs?: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type WorkflowNodeData =
|
|
68
|
+
| InputNodeData
|
|
69
|
+
| SubagentNodeData
|
|
70
|
+
| SkillNodeData
|
|
71
|
+
| McpNodeData
|
|
72
|
+
| OutputNodeData;
|
|
73
|
+
|
|
74
|
+
// Node structure for execution
|
|
75
|
+
export interface ExecutionNode {
|
|
76
|
+
id: string;
|
|
77
|
+
type: 'input' | 'subagent' | 'skill' | 'mcp' | 'output';
|
|
78
|
+
data: WorkflowNodeData;
|
|
79
|
+
position: { x: number; y: number };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Workflow execution request
|
|
83
|
+
export interface WorkflowExecutionRequest {
|
|
84
|
+
workflowId: string;
|
|
85
|
+
workflowName: string;
|
|
86
|
+
nodes: ExecutionNode[];
|
|
87
|
+
edges: Array<{
|
|
88
|
+
id: string;
|
|
89
|
+
source: string;
|
|
90
|
+
target: string;
|
|
91
|
+
}>;
|
|
92
|
+
inputs?: Record<string, string>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Node execution update
|
|
96
|
+
export interface NodeExecutionUpdate {
|
|
97
|
+
nodeId: string;
|
|
98
|
+
status: NodeStatus;
|
|
99
|
+
progress?: number;
|
|
100
|
+
result?: string;
|
|
101
|
+
error?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Console log entry
|
|
105
|
+
export interface ConsoleLogEntry {
|
|
106
|
+
type: 'info' | 'warn' | 'error' | 'debug';
|
|
107
|
+
message: string;
|
|
108
|
+
timestamp: string;
|
|
109
|
+
nodeId?: string;
|
|
110
|
+
}
|