HAL 0.3.2: AI 연동 편의성 + 도구 기본 설정·승인 요청 — 공급자 프리셋(Claude·ChatGPT·Gemini·OpenRouter + 기타 OpenAI 호환, Gemini 자체 API 어댑터), 사전 준비 안내, OAuth 저장 직후 로그인 창 자동(완료 페이지 postMessage·닫기, Gemini Google 프리셋, OpenRouter PKCE 키 발급), CLI 로그인용 텍스트 셸(허용 명령만 PTY, 재인증, 입력 미저장, 미로그인 시 자동 열기), 연결별 도구 정책(기본 전부)·로컬 CLI 에 HAL MCP 설정 자동 주입·MCP 설정 스니펫, MCP 쓰기·고위험 호출은 승인 요청(제안)으로 + hal_action_status/hal_actions_pending, 개발 지시 v0.3.2(Oz)·docs/HAL-AI.md §7·VERSION_HISTORY 0.3.2 — test/hal-ai-032.test.mjs RED 0/6 → GREEN 6/6, 전체 27파일 189건 통과

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Claude Fable 5.1
2026-10-29 01:43:02 +00:00
parent ad96f0b986
commit 8a111bcb8b
21 changed files with 609 additions and 78 deletions
+6 -3
View File
@@ -3,8 +3,8 @@
*
* 용도: 서버에 로그인된 Claude Code 같은 MCP 클라이언트(로컬 CLI 연결의 `--mcp-config`)나 운영자의 개발 도구가 HAL 상태를 같은 도구 정의로 읽게 한다.
* node hal/index.mjs mcp (ARIWEB_HAL_DATA 가 가리키는 HAL 데이터로 연다. HAL 서비스와 같은 SQLite 를 WAL 로 함께 읽는다)
* 경계: 읽기 도구만 실행한다. 쓰기·고위험 도구는 "운영자 콘솔 AI 채팅룸에서 제안으로 올려 승인받으라"는 isError 결과를 돌려주고 실행하지 않는다
* (MCP 경로에는 운영자 세션·재인증·3단계 확인이 없기 때문). 임의 명령·셸 도구는 없다. stdout 에는 JSON-RPC 한 줄씩만 쓴다(로그는 stderr).
* 경계: 읽기 도구만 실행한다. 쓰기·고위험 도구는 [0.3.2] 승인 요청(ai_actions, 통합 방 카드)으로 올리고 actionId 를 돌려준다 — 운영자가 콘솔에서 승인(재인증·고위험 3단계)하면 실행되며
* 클라이언트는 hal_action_status 로 결과를 본다(MCP 경로에는 운영자 세션이 없기 때문). 임의 명령·셸 도구는 없다. stdout 에는 JSON-RPC 한 줄씩만 쓴다(로그는 stderr).
* 지원 메서드: initialize · notifications/initialized · ping · tools/list · tools/call. 그 밖은 -32601.
*/
import { createInterface } from 'node:readline';
@@ -26,7 +26,10 @@ export function runMcpServer(d, { input = process.stdin, output = process.stdout
if (method === 'tools/list') return reply(id, { tools: toolDefs('mcp') });
if (method === 'tools/call') {
const tool = toolByName(params.name); if (!tool) return reply(id, { content: [{ type: 'text', text: `알 수 없는 도구: ${params.name}` }], isError: true });
if (tool.risk !== 'read') return reply(id, { content: [{ type: 'text', text: `${tool.name} 은(는) ${RISK_LABEL[tool.risk]} 도구입니다 — MCP 경로로는 실행하지 않습니다. HAL 운영자 콘솔 AI 채팅룸에서 제안으로 올려 운영자 승인(재인증${tool.risk === 'high' ? '·3단계 확인' : ''})을 받으세요.` }], isError: true });
if (tool.risk !== 'read') { // [0.3.2] 실행하지 않고 승인 요청(제안)을 올린다 — 운영자가 콘솔에서 승인(재인증, 고위험은 3단계 확인)하면 실행되고 hal_action_status 로 결과를 본다
try { const a = d.ai.proposeExternal({ source: 'mcp', tool: tool.name, args: params.arguments || {} }); return reply(id, { content: [{ type: 'text', text: JSON.stringify({ approvalRequested: true, actionId: a.id, status: a.status, risk: a.risk, riskLabel: a.riskLabel, message: `${tool.name} 은(는) ${RISK_LABEL[tool.risk]} 도구라 바로 실행하지 않았습니다. HAL 운영자 콘솔(통합 방)에 승인 요청을 올렸습니다 — 운영자가 승인(재인증${tool.risk === 'high' ? '·3단계 확인' : ''})하면 실행됩니다. hal_action_status(actionId) 로 결과를 확인하세요.` }) }] }); }
catch (e) { return reply(id, { content: [{ type: 'text', text: `승인 요청 실패(${e.code || 'error'}): ${safeError(e)}` }], isError: true }); }
}
try { const out = await runTool(d, tool, params.arguments || {}, { operatorId: null, label: 'mcp' }); return reply(id, { content: [{ type: 'text', text: toolOutputText(out) }] }); }
catch (e) { return reply(id, { content: [{ type: 'text', text: `도구 실행 실패(${e.code || 'error'}): ${safeError(e)}` }], isError: true }); }
}