refactor(core): MCP 역할별 접근 제어 + asyncio.Lock 추가
- ROLE_MCP_ACCESS: agent만 MCP 도구 접근, 나머지 역할은 제거 - _settings_lock: settings.json 쓰기~프로세스 시작 직렬화 - agent.md: 쉘 명령 금지, MCP 도구 강제 사용 지시 추가 - config.py: WORKSPACE_BASE_DIR 경로 수정 (Variet-Worker) - run_bot.bat: conda 환경 variet-agent로 변경 - workspaces.json: orphan 정리 + 경로 수정 - known-issues: MCP 접근제어, yolo 자율성, Nyaa 카테고리 이슈 추가 - devlog: 002 entry 및 index 업데이트
This commit is contained in:
@@ -29,9 +29,23 @@ ROLE_THINKING: dict[str, int] = {
|
||||
}
|
||||
DEFAULT_THINKING = 4096
|
||||
|
||||
# 역할별 MCP 서버 접근 허용 목록
|
||||
# agent만 외부 도구 사용 가능, 나머지 역할은 텍스트 전용
|
||||
ROLE_MCP_ACCESS: dict[str, list[str]] = {
|
||||
"agent": ["anime", "infra"],
|
||||
"coder": [],
|
||||
"planner": [],
|
||||
"reviewer": [],
|
||||
"summarizer": [],
|
||||
"unified": [],
|
||||
}
|
||||
|
||||
# 동시 호출 제한 (Gemini AI Ultra 120RPM 고려)
|
||||
_semaphore = asyncio.Semaphore(4)
|
||||
|
||||
# settings.json 쓰기 경합 방지 (settings 쓰기 ~ 프로세스 시작 구간 직렬화)
|
||||
_settings_lock = asyncio.Lock()
|
||||
|
||||
# Windows에서 PS ExecutionPolicy 우회
|
||||
_IS_WIN = sys.platform == "win32"
|
||||
|
||||
@@ -76,8 +90,13 @@ class GeminiCaller:
|
||||
}
|
||||
|
||||
def _set_thinking_budget(self, role: str):
|
||||
"""역할별 thinkingBudget + MCP 서버 설정을 settings.json에 반영."""
|
||||
"""역할별 thinkingBudget + MCP 서버 설정을 settings.json에 반영.
|
||||
|
||||
MCP 서버는 ROLE_MCP_ACCESS에 따라 역할별로 필터링됩니다.
|
||||
agent 역할만 MCP 도구에 접근 가능하고, 나머지 역할은 제거됩니다.
|
||||
"""
|
||||
budget = ROLE_THINKING.get(role, DEFAULT_THINKING)
|
||||
allowed_mcp = ROLE_MCP_ACCESS.get(role, [])
|
||||
try:
|
||||
if _SETTINGS_PATH.exists():
|
||||
settings = json.loads(_SETTINGS_PATH.read_text(encoding="utf-8"))
|
||||
@@ -91,16 +110,22 @@ class GeminiCaller:
|
||||
thinking = default.setdefault("thinkingConfig", {})
|
||||
thinking["thinkingBudget"] = budget
|
||||
|
||||
# MCP 서버 (홈 레벨에 등록 — cwd와 무관하게 항상 사용 가능)
|
||||
# MCP 서버 — 역할별 접근 제어
|
||||
mcp_servers = settings.setdefault("mcpServers", {})
|
||||
for name, config in self._MCP_SERVERS.items():
|
||||
mcp_servers[name] = config
|
||||
for name, cfg in self._MCP_SERVERS.items():
|
||||
if name in allowed_mcp:
|
||||
mcp_servers[name] = cfg
|
||||
else:
|
||||
mcp_servers.pop(name, None)
|
||||
|
||||
_SETTINGS_PATH.write_text(
|
||||
json.dumps(settings, indent=2, ensure_ascii=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
logger.debug(f"thinkingBudget={budget} for role={role}")
|
||||
logger.debug(
|
||||
f"settings.json 업데이트: role={role}, budget={budget}, "
|
||||
f"mcp={allowed_mcp or 'none'}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"settings.json 업데이트 실패 (role={role}): {e}")
|
||||
|
||||
@@ -132,7 +157,6 @@ class GeminiCaller:
|
||||
|
||||
async def _call_text(self, role: str, context: str, timeout: int) -> str:
|
||||
"""텍스트 전용 호출."""
|
||||
self._set_thinking_budget(role)
|
||||
prompt_file = ROLE_PROMPTS_DIR / f"{role}.md"
|
||||
if prompt_file.exists():
|
||||
system_prompt = prompt_file.read_text(encoding="utf-8")
|
||||
@@ -147,12 +171,15 @@ class GeminiCaller:
|
||||
)
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*self._build_cmd(),
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
# Lock: settings.json 쓰기 ~ 프로세스 시작 직렬화
|
||||
async with _settings_lock:
|
||||
self._set_thinking_budget(role)
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*self._build_cmd(),
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
proc.communicate(input=full_input.encode("utf-8")),
|
||||
timeout=timeout,
|
||||
@@ -207,7 +234,6 @@ class GeminiCaller:
|
||||
self, role: str, context: str, cwd: str, timeout: int,
|
||||
) -> str:
|
||||
"""에이전트 모드 구현."""
|
||||
self._set_thinking_budget(role)
|
||||
prompt_file = ROLE_PROMPTS_DIR / f"{role}.md"
|
||||
if prompt_file.exists():
|
||||
system_prompt = prompt_file.read_text(encoding="utf-8")
|
||||
@@ -241,13 +267,16 @@ class GeminiCaller:
|
||||
)
|
||||
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*self._build_cmd(),
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=cwd, # ★ 핵심: 프로젝트 디렉토리에서 실행
|
||||
)
|
||||
# Lock: settings.json 쓰기 ~ 프로세스 시작 직렬화
|
||||
async with _settings_lock:
|
||||
self._set_thinking_budget(role)
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*self._build_cmd(),
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
cwd=cwd, # ★ 핵심: 프로젝트 디렉토리에서 실행
|
||||
)
|
||||
stdout, stderr = await asyncio.wait_for(
|
||||
proc.communicate(input=full_input.encode("utf-8")),
|
||||
timeout=timeout,
|
||||
|
||||
Reference in New Issue
Block a user