Files
AriWeb-Suite/docs/HAL-AI.md
T

16 KiB
Raw Blame History

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 /connections with 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 가 아니면 409 connection_inactive).
    • 통합 방 → 활성 모델 모두. @이름(어디든) 또는 글머리 이름:/이름, → 그 모델만. 모르는 @이름 → 400 unknown_addressee(참여자 목록 포함).
    • 응답은 비동기 턴. GET /rooms/:id/messages?after=<seq> 가 새 메시지·pending(응답 중 턴 수)·actions(제안)를 준다.
  • 모델→모델 전달: 모델 답에 @다른이름 이 있으면 그 모델이 자동 1회 답한다(relayDepth 1, 화면에 "전달 응답"). 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 제외, 0.3.17) 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-linux script PTY 로 실행. 시작은 재인증 필요, 운영자당 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단계)이며 결과는 통합 방 안내로 남는다.

8. 0.3.9 — 로그인 터미널·텍스트 셸·모델 선택·슬래시 명령

  • claude 로그인 명령 수정: 실제 명령은 claude auth login(claude login 은 대화형 세션에 "login" 을 넣는 것 — 세션이 만료되면 인증 오류만 낸다). 셸 명령표(SHELL_COMMANDS): claude login→auth login · login_console · logout→auth logout · status→auth status · version · interactive(bare claude, 신뢰(trust)·첫 설정 TUI 용), codex login · logout · status→login status · version · interactive. 작업 폴더는 HAL 실행 계정 HOME(이미 신뢰된 폴더). 로그인 안내 문구도 claude auth login 으로.
  • 텍스트 셸 범용화: 브라우저는 순수 단말 에뮬레이터 hal/ui/public/term.mjs(VT100/xterm 부분집합 — 커서 이동·지우기·대체 화면·스크롤백, 색 무시)로 TUI 출력을 그리고, 단말 영역에 포커스하면 키를 바이트 그대로 보낸다(keyToBytes: Enter·Tab·Esc·화살표·Home/End·Delete·Ctrl+문자). 키 단추(↑↓←→ Enter Esc y n Ctrl+C Ctrl+D)·줄 입력란(한글·붙여넣기)·붙여넣기도 지원. 단말 크기(cols·rows)는 세션 시작 때 넘겨 COLUMNS/LINES 로 전달(기본 100×30).
  • 세션 한도 오류 수정: 0.3.2 는 모달 "닫기"가 세션을 끝내지 않아 유령 세션이 한도(2)를 채웠다. 0.3.9: 닫기(모달 밖 클릭 포함)가 세션을 종료하고, 한도는 살아 있는 프로세스만 세며(끝났는데 running 으로 남은 것은 정리) 3개, 409 응답에 열린 세션 목록을 담고, "열린 셸 모두 종료"(POST /ops/api/ai/shell/kill-all)와 세션 목록(열기·종료)을 화면에 둔다.
  • 모델 선택: 연결 표와 패널 상태 줄의 모델 선택 상자(확인된 목록) → PATCH /connections/:id {model}(목록 밖 거부). /model <이름> 명령(개별 방). GET /rooms 의 connection.models.
  • 슬래시 명령(hal/ai/commands.mjs, GET /ops/api/ai/commands): local — /help /tools /model /reset(모델 호출 없음) / prompt — /status /health /servers [역할] /server <id> /jobs [상태] /job <id> /releases /incidents /audit [접두어] /dr /emergency /order <지시> /deploy <모듈> <버전> [서버] /rollback … /isolate <서버> <사유> /incident <key> <제목>. prompt 명령은 운영자 원문을 기록에 남기고 모델에게는 확장 지시(content[].type=command.expanded)를 보낸다. 확장 지시는 승인 규칙을 바꾸지 않는다(쓰기·고위험은 제안). 모르는 명령 400 unknown_command, 인자 누락 400 command_args_required, 통합 방 /model 400 command_needs_direct_room.
  • 점검 수정: 패널 상태 줄에 reauth_required 연결용 "로그인·재인증" 단추(OAuth → 로그인 창, CLI → 로그인 셸), window.open 기능 문자열 정리.

9. 0.3.10 — 텍스트 셸 복사·주소 추출

  • 원인: 단말 영역이 모든 키를 셸로 보내 Ctrl+C 가 복사 대신 ^C 가 되었고, 긴 인증 주소가 줄 끝(열 수)에서 끊겨 두 줄로 보여 통째로 고를 수 없었다.
  • term.mjs: joinedLines(꽉 찬 줄은 다음 줄과 이어 붙임) · plainText · extractUrls(스크롤백·대체 화면 포함, 상자 문자 제외, 꼬리 구두점 제거, 중복 제거) · copyDecision(선택이 있으면 Ctrl+C=복사, Ctrl+Shift+C·Ctrl+Insert=항상 복사, Ctrl+V/Shift+Insert=붙여넣기, 나머지=전송).
  • 화면(ai-render.shellModalHtml·shellUrlsHtml, ai.js): "감지된 주소" 목록(복사·열기 — 새 탭, noopener), "전체 출력 복사", "선택 복사", "텍스트로 보기"(선택 가능한 textarea), 단말 드래그 선택 가능(user-select:text). 복사는 Clipboard API → 실패 시 숨은 textarea+execCommand → 안내.

10. 0.4.0 — 통합 설정 관제 도구

  • hal_settings_get(읽기: 저장 값·비밀값 설정 여부 confidential[]·서버 범위 유효값·적용 상태) · hal_settings_set(고위험 — 비밀값은 받지 않음 secret_not_allowed) · hal_settings_apply(고위험 — 작업 settings.apply). 스킬 문서 §4.1. 자세한 계약은 docs/HAL-SETTINGS.md.