feat: Add close confirmation dialog to ScreenModal and enhance SelectedItemsDetailInputComponent
- Implemented a confirmation dialog in ScreenModal to prevent accidental closure, allowing users to confirm before exiting and potentially losing unsaved data. - Enhanced SelectedItemsDetailInputComponent by ensuring that base records are created even when detail data is absent, maintaining item-client mapping. - Improved logging for better traceability during the UPSERT process and refined the handling of parent data mappings for more robust data management.
This commit is contained in:
@@ -40,32 +40,21 @@ const server = new Server(
|
||||
);
|
||||
|
||||
/**
|
||||
* Cursor Agent CLI를 통해 에이전트 호출
|
||||
* Cursor Team Plan 사용 - API 키 불필요!
|
||||
*
|
||||
* spawn + stdin 직접 전달 방식으로 쉘 이스케이프 문제 완전 해결
|
||||
*
|
||||
* 크로스 플랫폼 지원:
|
||||
* - Windows: agent (PATH에서 검색)
|
||||
* - Mac/Linux: ~/.local/bin/agent
|
||||
* 유틸: ms만큼 대기
|
||||
*/
|
||||
async function callAgentCLI(
|
||||
agentType: AgentType,
|
||||
task: string,
|
||||
context?: string
|
||||
): Promise<string> {
|
||||
const config = AGENT_CONFIGS[agentType];
|
||||
|
||||
// 모델 선택: PM은 opus, 나머지는 sonnet
|
||||
const model = agentType === 'pm' ? 'opus-4.5' : 'sonnet-4.5';
|
||||
|
||||
logger.info(`Calling ${agentType} agent via CLI (spawn)`, { model, task: task.substring(0, 100) });
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const userMessage = context
|
||||
? `${task}\n\n배경 정보:\n${context}`
|
||||
: task;
|
||||
|
||||
const fullPrompt = `${config.systemPrompt}\n\n---\n\n${userMessage}`;
|
||||
/**
|
||||
* Cursor Agent CLI 단일 호출 (내부용)
|
||||
* spawn + stdin 직접 전달
|
||||
*/
|
||||
function spawnAgentOnce(
|
||||
agentType: AgentType,
|
||||
fullPrompt: string,
|
||||
model: string
|
||||
): Promise<string> {
|
||||
const agentPath = isWindows ? 'agent' : `${process.env.HOME}/.local/bin/agent`;
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
@@ -93,7 +82,6 @@ async function callAgentCLI(
|
||||
child.on('error', (err: Error) => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
logger.error(`${agentType} agent spawn error`, err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
@@ -103,7 +91,6 @@ async function callAgentCLI(
|
||||
settled = true;
|
||||
|
||||
if (stderr) {
|
||||
// 경고/정보 레벨 stderr는 무시
|
||||
const significantStderr = stderr
|
||||
.split('\n')
|
||||
.filter((line: string) => line && !line.includes('warning') && !line.includes('info') && !line.includes('debug'))
|
||||
@@ -114,13 +101,11 @@ async function callAgentCLI(
|
||||
}
|
||||
|
||||
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));
|
||||
reject(new Error(
|
||||
`Agent exited with code ${code}. stderr: ${stderr.substring(0, 1000)}`
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,22 +114,69 @@ async function callAgentCLI(
|
||||
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으로 프롬프트 직접 전달 (쉘 이스케이프 문제 없음!)
|
||||
// stdin으로 프롬프트 직접 전달
|
||||
child.stdin.write(fullPrompt);
|
||||
child.stdin.end();
|
||||
|
||||
logger.debug(`Prompt sent to ${agentType} agent via stdin (${fullPrompt.length} chars)`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cursor Agent CLI를 통해 에이전트 호출 (재시도 포함)
|
||||
*
|
||||
* - 최대 2회 재시도 (총 3회 시도)
|
||||
* - 재시도 간 2초 대기 (Cursor CLI 동시 실행 제한 대응)
|
||||
*/
|
||||
async function callAgentCLI(
|
||||
agentType: AgentType,
|
||||
task: string,
|
||||
context?: string
|
||||
): Promise<string> {
|
||||
const config = AGENT_CONFIGS[agentType];
|
||||
const model = agentType === 'pm' ? 'opus-4.5' : 'sonnet-4.5';
|
||||
const maxRetries = 2;
|
||||
|
||||
logger.info(`Calling ${agentType} agent via CLI (spawn+retry)`, {
|
||||
model,
|
||||
task: task.substring(0, 100),
|
||||
});
|
||||
|
||||
const userMessage = context
|
||||
? `${task}\n\n배경 정보:\n${context}`
|
||||
: task;
|
||||
const fullPrompt = `${config.systemPrompt}\n\n---\n\n${userMessage}`;
|
||||
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
if (attempt > 0) {
|
||||
const delay = attempt * 2000; // 2초, 4초
|
||||
logger.info(`${agentType} agent retry ${attempt}/${maxRetries} (waiting ${delay}ms)`);
|
||||
await sleep(delay);
|
||||
}
|
||||
|
||||
const result = await spawnAgentOnce(agentType, fullPrompt, model);
|
||||
logger.info(`${agentType} agent completed (attempt ${attempt + 1})`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
logger.warn(`${agentType} agent attempt ${attempt + 1} failed`, {
|
||||
error: lastError.message.substring(0, 200),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 모든 재시도 실패
|
||||
logger.error(`${agentType} agent failed after ${maxRetries + 1} attempts`);
|
||||
throw lastError!;
|
||||
}
|
||||
|
||||
/**
|
||||
* 도구 목록 핸들러
|
||||
*/
|
||||
@@ -310,12 +342,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
}>;
|
||||
};
|
||||
|
||||
logger.info(`Parallel ask to ${requests.length} agents (TRUE PARALLEL!)`);
|
||||
logger.info(`Parallel ask to ${requests.length} agents (STAGGERED PARALLEL)`);
|
||||
|
||||
// 시차 병렬 실행: 각 에이전트를 500ms 간격으로 시작
|
||||
// Cursor Agent CLI 동시 실행 제한 대응
|
||||
const STAGGER_DELAY = 500; // ms
|
||||
|
||||
// 진짜 병렬 실행! 모든 에이전트가 동시에 작업
|
||||
const results: ParallelResult[] = await Promise.all(
|
||||
requests.map(async (req) => {
|
||||
requests.map(async (req, index) => {
|
||||
try {
|
||||
// 시차 적용 (첫 번째는 즉시, 이후 500ms 간격)
|
||||
if (index > 0) {
|
||||
await sleep(index * STAGGER_DELAY);
|
||||
}
|
||||
const result = await callAgentCLI(req.agent, req.task, req.context);
|
||||
return { agent: req.agent, result };
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user