11 KiB
11 KiB
HAL AI 채팅룸 — 연결·방·도구·MCP (Ariweb Suite 0.3.0)
운영자 콘솔(/ops/*) 오른쪽의 AI 채팅룸과 그 백엔드(hal/ai/*) 설명. 개발 지시 Ariweb-Suitle_Dev-Order_v0.3.0.md(Oz) · v0.1.4 §41·§42. 비밀값은 이 문서에 없다.
1. 구성
| 파일 | 역할 |
|---|---|
hal/ai/connections.mjs |
모델 연결(API 키·OAuth PKCE·로컬 CLI 로그인 세션), 비밀값은 Vault(ai:conn:<id>:api_key / :oauth / :client_secret), 모델 목록 확인, 상태 |
hal/ai/providers.mjs |
공급자 어댑터: Anthropic Messages, OpenAI 호환 Chat Completions(도구 호출 포함), 로컬 CLI(claude -p --output-format json, 도구 없음) |
hal/ai/rooms.mjs |
방(통합 room_group 고정 + 연결별 개별)·메시지(블록 JSON, seq)·통합 방 호출 규칙(parseAddressing, mentionedParticipants) |
hal/ai/engine.mjs |
턴 실행: system prompt(고유 정보·참여자·스킬 문서) → 모델 호출 → 읽기 도구 즉시 실행 / 쓰기·고위험은 제안 → 통합 방 @전달 1회 → 승인 실행·거부 |
hal/ai/tools.mjs |
도구 레지스트리(이름·설명·위험 등급·인자 선언·실행), 공급자별 선언 변환(toolDefs), 인자 검증 |
hal/ai/skill.md |
스킬 문서: HAL 상태·작업·릴리스·사건·감사·긴급 복구·DR·오류 코드의 뜻과 AI 규칙(system prompt 와 hal_skill·GET /ops/api/ai/skill) |
hal/ai/mcp.mjs |
MCP(stdio JSON-RPC) 서버 — node hal/index.mjs mcp, 같은 도구 정의, 읽기 도구만 실행 |
hal/ai/routes.mjs |
운영자 API /ops/api/ai/*, OAuth 콜백 /ops/ai/oauth/callback |
hal/ui.mjs · hal/ui/public/ai.js · ai-render.mjs · ops.css |
패널(#ai-panel)·AI 연동 화면(/ops/ai)·순수 렌더러·스타일 |
| 표 | ai_connections · ai_rooms · ai_messages · ai_actions · ai_oauth_states(마이그레이션 hal_0006_ai_chat) |
2. 연결(AI 연동 화면 /ops/ai)
- 공급자:
anthropic(API 키 또는 OAuth Bearer) ·openai(OpenAI 호환,baseUrl로 다른 호환 공급자) ·local-cli(서버에 설치·로그인된 Claude Code 또는 Codex CLI 세션, 대화·분석 전용 — 0.3.1 자동 탐색, §6). - 고유 정보
identity:role(역할·업무) ·traits(특성) ·instructions(추가 지시). 통합 방에서 다른 참여자에게도 이름·역할이 안내된다. - 상태:
active·disabled(탭은 보이되 응답하지 않음) ·reauth_required(OAuth 토큰 없음·만료·401) ·error(마지막 확인 실패 —lastError). - "확인"(
POST /ops/api/ai/connections/:id/test): 실제 공급자GET /models(CLI 는--version)로 사용 가능 모델을 받아 둔다. 목록에 없는 모델로 바꾸면model_unavailable. - 인증 키 변경은 고위험: 키 등록(
POST /connectionswith apiKey)·교체(POST /:id/secret)·OAuth client secret·연결 삭제는 최근 10분 재인증(reauth_required)이 필요하다. - OAuth:
POST /:id/oauth/start→authorizeUrl(state·PKCE S256) → 공급자 로그인 →GET /ops/ai/oauth/callback?code&state→ 토큰 교환(code_verifier, client secret 은 서버→토큰 끝점에만) → Vault 저장 →/ops/ai?oauth=ok. state 는 해시 저장·10분·1회용, 시작한 운영자와 같은 세션만 허용. 만료 토큰은refresh_token으로 서버가 갱신하고 실패하면reauth_required. - 공개 객체의
secret은{ hasKey, hint }/{ hasToken, expiresAt, hasRefresh }/{ cli: true }뿐이다.
3. 방과 규칙
GET /ops/api/ai/rooms:[통합(fixed), 개별…]— 통합이 항상 먼저. 개별 방title은 비우면 연결 이름(PATCH /rooms/:id {title}로 수정, 통합은room_fixed).POST /rooms/:id/messages {text, context?}:- 개별 방 → 그 모델만(연결이
active가 아니면 409connection_inactive). - 통합 방 → 활성 모델 모두.
@이름(어디든) 또는 글머리이름:/이름,→ 그 모델만. 모르는@이름→ 400unknown_addressee(참여자 목록 포함). - 응답은 비동기 턴.
GET /rooms/:id/messages?after=<seq>가 새 메시지·pending(응답 중 턴 수)·actions(제안)를 준다.
- 개별 방 → 그 모델만(연결이
- 모델→모델 전달: 모델 답에
@다른이름이 있으면 그 모델이 자동 1회 답한다(relayDepth1, 화면에 "전달 응답"). depth 1 의 답이 또 부르면 HAL 안내만 남기고 멈춘다 — 다음 반응은 운영자 지시로. 자기 자신·비활성 모델은 전달 대상이 아니다. - 메시지 역할:
user(운영자) ·assistant(모델,connectionId·usage) ·tool(도구 결과 블록) ·system(HAL 안내). 공급자 오류는status: failed+error(코드·메시지) 로 남는다.
4. 도구와 위험 등급
| 등급 | 실행 | 도구 |
|---|---|---|
| 읽기(read) | 모델이 부르면 즉시 | hal_skill hal_health hal_overview hal_servers_list hal_server_get hal_server_metrics hal_releases_list hal_jobs_list hal_job_get hal_incidents_list hal_audit_list hal_mail_services_list hal_emergency_list hal_dr_status hal_changelog |
| 쓰기(write) | 제안 → 운영자 승인(재인증) | hal_server_create hal_server_approve hal_server_maintenance hal_incident_open hal_incident_resolve hal_job_control hal_module_health hal_dr_snapshot |
| 고위험(high) | 제안 → 승인(재인증 + 3단계 확인, 식별정보 = 도구가 정한 identifiers) | hal_server_isolate hal_server_delete hal_module_deploy hal_module_rollback hal_module_reconcile hal_release_approve hal_job_revert hal_config_set |
- 인자는
validateArgs(모르는 키·필수·형식·enum·길이)로 다시 검사한다. 모르는 도구·인자 오류는 실행하지 않고 모델에게 오류 결과로 돌려준다. 임의 셸 명령·경로·네트워크 도구는 없다. - 제안(
ai_actions):proposed→executing→executed|failed, 또는rejected/cancelled.POST /ops/api/ai/actions/:id/approve(write:requireRecent, high:requireRecent+threeStep(confirmImpact, confirmIdentifiers, confirmExecute)),POST …/reject {reason}. 승인은 한 번만 실행된다(상태를 먼저executing으로 바꿔 동시 클릭을 막음). 실행 결과는 도구 결과로 대화에 들어가고 모델이 이어서 답한다. 거부는 모델을 자동으로 부르지 않는다. - 감사:
ai.connection.*,ai.action.propose/execute/reject,ai.turn.failed. detail 은 redact 를 거친다(비밀값 없음).
5. MCP 서버
ARIWEB_HAL_DATA=/srv/ariweb/hal/data node hal/index.mjs mcp # stdio JSON-RPC: initialize · tools/list · tools/call · ping
HAL 서비스와 같은 SQLite 를 WAL 로 함께 읽는다. 읽기 도구만 실행하고, 쓰기·고위험 도구는 isError 결과로 "운영자 콘솔 AI 채팅룸에서 승인을 받으라"고 안내한다(MCP 경로에는 운영자 세션·재인증·3단계 확인이 없기 때문). 로그는 stderr.
6. 운영 메모
- 턴은 HAL 프로세스 안에서 돈다. HAL 재시작 뒤 진행 중이던 턴은 사라지며 "응답 중" 표시도 사라진다(거짓 진행 표시 없음). 운영자가 다시 지시하면 된다.
- 비용·토큰: 공급자가 준 입력·출력 토큰만 메시지
usage에 기록한다. 주지 않으면 "제공되지 않음". - [0.3.1] 로컬 CLI 자동 탐색: HAL 서비스(systemd)의 PATH 에는 사용자 설치 위치가 없어 0.3.0 은 bear 의
~/.local/bin/claude·codex를 찾지 못했다. 0.3.1 은GET /ops/api/ai/cli가 CLAUDE_BIN/CODEX_BIN → HAL_CLI_DIRS →~/.local/bin·~/.npm-global/bin·~/.claude/local·~/.codex/packages/standalone/current/bin·~/node_modules/.bin→/usr/local/bin·/usr/bin→ PATH 순으로 탐색해 버전·로그인 상태(claude auth status/codex login status)·기본 모델(codex:~/.codex/config.toml, 모델 목록~/.codex/models_cache.json)을 AI 연동 화면 "서버에서 찾은 CLI" 카드에 보인다. 연결은cli.tool(claude|codex)만 주면 탐색된 실행 파일을 쓰고, "확인" 때 그 경로·버전·로그인 상태를 연결에 남긴다. - Codex 는
codex exec --json -s read-only --skip-git-repo-check --ephemeral -C <임시 폴더> -m <모델> <프롬프트>로 실행하고 JSONL 의item.completed(agent_message)를 답,turn.completed.usage를 사용량으로 쓴다. Claude 는claude -p … --output-format json. 둘 다 HAL 도구는 없다(대화·분석 전용). bear 의ProtectSystem=strict유닛 안에서도 두 CLI 모두 실행된다(systemd-run 으로 확인). - 로그인은 HAL 이 대신할 수 없다: 확인 결과
cli_not_logged_in이면 연결이reauth_required가 되고 "서버(HAL 실행 계정)에서claude login/codex login" 안내가 붙는다. 2026-10-29 bear 기준 codex 는 ChatGPT 로그인됨, claude 는 OAuth 세션 만료(claude login필요).
7. 0.3.2 — 연동 편의성·도구 기본 설정·승인 요청
- 공급자 프리셋:
anthropic(Claude) ·openai(ChatGPT) ·gemini(Google, 자체 generateContent API, functionDeclarations) ·openrouter(OpenAI 호환, 기본 주소https://openrouter.ai/api/v1) ·other(기타 OpenAI 호환 서비스 — 기본 주소 필수) ·local-cli.GET /ops/api/ai/connections의providers[].guide(키 발급 주소·절차·OAuth 안내)와callbackUrl로 화면이 사전 준비 안내를 그린다. - OAuth 자동 진행:
POST /connections응답의next.action === 'oauth'이면 화면이 바로oauth/start를 불러 로그인 팝업을 연다. 콜백은/ops/ai/oauth/done?ok=1|0완료 페이지로 가고, 팝업이면postMessage({type:'hal-oauth'})로 부모 창에 알린 뒤 닫힌다(부모는 자동으로 "확인"까지). Gemini 는 Google 끝점·scope 프리셋(client id/secret 만), OpenRouter 는 PKCE 프리셋(설정값 없음 →openrouter.ai/auth→/api/v1/auth/keys로 키 발급 → Vault). - 텍스트 셸(
hal/ai/shell.mjs,/ops/api/ai/shell): 허용 명령(claude: login·logout·auth status·--version / codex: login·logout·login status·--version)만 util-linuxscriptPTY 로 실행. 시작은 재인증 필요, 운영자당 2개, 유휴 10분·최대 30분. 입력은 저장·감사하지 않는다. 로컬 CLI 연결 저장·확인에서cli_not_logged_in이면 화면이 자동으로 login 셸을 연다. - 도구 기본 설정: 연결마다
tools: {preset, enabled}(기본all— 읽기 즉시, 쓰기·고위험은 승인). 프리셋all·read_write·read_only·none·custom. 엔진은 켜진 도구만 공급자에 선언하고, 꺼진 도구 호출은 실행하지 않는다. 로컬 CLI 연결에는 HAL MCP 설정이 자동으로 붙는다(claude--mcp-config … --strict-mcp-config --allowedTools mcp__hal, codex-c mcp_servers.hal.*).GET /ops/api/ai/mcp-config가 외부 클라이언트용 스니펫을 준다. - 승인 요청(MCP 경로): 쓰기·고위험
tools/call은 실행하지 않고d.ai.proposeExternal로 통합 방에 HAL 안내 + 제안 카드를 올리며{approvalRequested, actionId}를 돌려준다. 클라이언트는hal_action_status {actionId}/hal_actions_pending으로 상태를 본다. 승인은 같은 라우트(/ops/api/ai/actions/:id/approve, 재인증·고위험 3단계)이며 결과는 통합 방 안내로 남는다.