docs: Add detailed backend, database, and frontend architecture analysis documents
- Created a comprehensive analysis document for the backend architecture, detailing the directory structure, API routes, authentication workflows, and more. - Added a database architecture analysis report outlining the database structure, multi-tenancy architecture, and key system tables. - Introduced a frontend architecture analysis document that covers the directory structure, component systems, and Next.js App Router structure. These documents aim to enhance the understanding of the WACE ERP system's architecture and facilitate better workflow documentation.
This commit is contained in:
parent
0ac2d78ad3
commit
08dde416b1
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -16,15 +16,12 @@ import {
|
||||||
CallToolRequestSchema,
|
CallToolRequestSchema,
|
||||||
ListToolsRequestSchema,
|
ListToolsRequestSchema,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { exec } from "child_process";
|
import { spawn } from "child_process";
|
||||||
import { promisify } from "util";
|
|
||||||
import { platform } from "os";
|
import { platform } from "os";
|
||||||
import { AGENT_CONFIGS } from "./agents/prompts.js";
|
import { AGENT_CONFIGS } from "./agents/prompts.js";
|
||||||
import { AgentType, ParallelResult } from "./agents/types.js";
|
import { AgentType, ParallelResult } from "./agents/types.js";
|
||||||
import { logger } from "./utils/logger.js";
|
import { logger } from "./utils/logger.js";
|
||||||
|
|
||||||
const execAsync = promisify(exec);
|
|
||||||
|
|
||||||
// OS 감지
|
// OS 감지
|
||||||
const isWindows = platform() === "win32";
|
const isWindows = platform() === "win32";
|
||||||
logger.info(`Platform detected: ${platform()} (isWindows: ${isWindows})`);
|
logger.info(`Platform detected: ${platform()} (isWindows: ${isWindows})`);
|
||||||
|
|
@ -46,9 +43,11 @@ const server = new Server(
|
||||||
* Cursor Agent CLI를 통해 에이전트 호출
|
* Cursor Agent CLI를 통해 에이전트 호출
|
||||||
* Cursor Team Plan 사용 - API 키 불필요!
|
* Cursor Team Plan 사용 - API 키 불필요!
|
||||||
*
|
*
|
||||||
|
* spawn + stdin 직접 전달 방식으로 쉘 이스케이프 문제 완전 해결
|
||||||
|
*
|
||||||
* 크로스 플랫폼 지원:
|
* 크로스 플랫폼 지원:
|
||||||
* - Windows: cmd /c "echo. | agent ..." (stdin 닫기 위해)
|
* - Windows: agent (PATH에서 검색)
|
||||||
* - Mac/Linux: ~/.local/bin/agent 사용
|
* - Mac/Linux: ~/.local/bin/agent
|
||||||
*/
|
*/
|
||||||
async function callAgentCLI(
|
async function callAgentCLI(
|
||||||
agentType: AgentType,
|
agentType: AgentType,
|
||||||
|
|
@ -60,56 +59,90 @@ async function callAgentCLI(
|
||||||
// 모델 선택: PM은 opus, 나머지는 sonnet
|
// 모델 선택: PM은 opus, 나머지는 sonnet
|
||||||
const model = agentType === 'pm' ? 'opus-4.5' : 'sonnet-4.5';
|
const model = agentType === 'pm' ? 'opus-4.5' : 'sonnet-4.5';
|
||||||
|
|
||||||
logger.info(`Calling ${agentType} agent via CLI`, { model, task: task.substring(0, 100) });
|
logger.info(`Calling ${agentType} agent via CLI (spawn)`, { model, task: task.substring(0, 100) });
|
||||||
|
|
||||||
try {
|
const userMessage = context
|
||||||
const userMessage = context
|
? `${task}\n\n배경 정보:\n${context}`
|
||||||
? `${task}\n\n배경 정보:\n${context}`
|
: task;
|
||||||
: task;
|
|
||||||
|
const fullPrompt = `${config.systemPrompt}\n\n---\n\n${userMessage}`;
|
||||||
// 프롬프트를 임시 파일에 저장하여 쉘 이스케이프 문제 회피
|
const agentPath = isWindows ? 'agent' : `${process.env.HOME}/.local/bin/agent`;
|
||||||
const fullPrompt = `${config.systemPrompt}\n\n---\n\n${userMessage}`;
|
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
// Base64 인코딩으로 특수문자 문제 해결
|
let stdout = '';
|
||||||
const encodedPrompt = Buffer.from(fullPrompt).toString('base64');
|
let stderr = '';
|
||||||
|
let settled = false;
|
||||||
let cmd: string;
|
|
||||||
let shell: string;
|
const child = spawn(agentPath, ['--model', model, '--print'], {
|
||||||
const agentPath = isWindows ? 'agent' : `${process.env.HOME}/.local/bin/agent`;
|
|
||||||
|
|
||||||
if (isWindows) {
|
|
||||||
// Windows: PowerShell을 통해 Base64 디코딩 후 실행
|
|
||||||
cmd = `powershell -Command "$prompt = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${encodedPrompt}')); echo $prompt | ${agentPath} --model ${model} --print"`;
|
|
||||||
shell = 'powershell.exe';
|
|
||||||
} else {
|
|
||||||
// Mac/Linux: echo로 base64 디코딩 후 파이프
|
|
||||||
cmd = `echo "${encodedPrompt}" | base64 -d | ${agentPath} --model ${model} --print`;
|
|
||||||
shell = '/bin/bash';
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`Executing: ${agentPath} --model ${model} --print`);
|
|
||||||
|
|
||||||
const { stdout, stderr } = await execAsync(cmd, {
|
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
|
|
||||||
timeout: 300000, // 5분 타임아웃
|
|
||||||
shell,
|
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
PATH: `${process.env.HOME}/.local/bin:${process.env.PATH}`,
|
PATH: `${process.env.HOME}/.local/bin:${process.env.PATH}`,
|
||||||
},
|
},
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (stderr && !stderr.includes('warning') && !stderr.includes('info')) {
|
child.stdout.on('data', (data: Buffer) => {
|
||||||
logger.warn(`${agentType} agent stderr`, { stderr: stderr.substring(0, 500) });
|
stdout += data.toString();
|
||||||
}
|
});
|
||||||
|
|
||||||
logger.info(`${agentType} agent completed via CLI`);
|
child.stderr.on('data', (data: Buffer) => {
|
||||||
return stdout.trim();
|
stderr += data.toString();
|
||||||
} catch (error) {
|
});
|
||||||
logger.error(`${agentType} agent CLI error`, error);
|
|
||||||
throw error;
|
child.on('error', (err: Error) => {
|
||||||
}
|
if (!settled) {
|
||||||
|
settled = true;
|
||||||
|
logger.error(`${agentType} agent spawn error`, err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('close', (code: number | null) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
|
||||||
|
if (stderr) {
|
||||||
|
// 경고/정보 레벨 stderr는 무시
|
||||||
|
const significantStderr = stderr
|
||||||
|
.split('\n')
|
||||||
|
.filter((line: string) => line && !line.includes('warning') && !line.includes('info') && !line.includes('debug'))
|
||||||
|
.join('\n');
|
||||||
|
if (significantStderr) {
|
||||||
|
logger.warn(`${agentType} agent stderr`, { stderr: significantStderr.substring(0, 500) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code === 0 || stdout.trim().length > 0) {
|
||||||
|
// 정상 종료이거나, 에러 코드여도 stdout에 결과가 있으면 성공 처리
|
||||||
|
logger.info(`${agentType} agent completed via CLI (exit code: ${code})`);
|
||||||
|
resolve(stdout.trim());
|
||||||
|
} else {
|
||||||
|
const errorMsg = `Agent exited with code ${code}. stderr: ${stderr.substring(0, 1000)}`;
|
||||||
|
logger.error(`${agentType} agent CLI error`, { code, stderr: stderr.substring(0, 1000) });
|
||||||
|
reject(new Error(errorMsg));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 타임아웃 (5분)
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
if (!settled) {
|
||||||
|
settled = true;
|
||||||
|
child.kill('SIGTERM');
|
||||||
|
logger.error(`${agentType} agent timed out after 5 minutes`);
|
||||||
|
reject(new Error(`${agentType} agent timed out after 5 minutes`));
|
||||||
|
}
|
||||||
|
}, 300000);
|
||||||
|
|
||||||
|
// 프로세스 종료 시 타이머 클리어
|
||||||
|
child.on('close', () => clearTimeout(timeout));
|
||||||
|
|
||||||
|
// stdin으로 프롬프트 직접 전달 (쉘 이스케이프 문제 없음!)
|
||||||
|
child.stdin.write(fullPrompt);
|
||||||
|
child.stdin.end();
|
||||||
|
|
||||||
|
logger.debug(`Prompt sent to ${agentType} agent via stdin (${fullPrompt.length} chars)`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue