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

60 lines
8.9 KiB
Markdown
Raw Normal View 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_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 서버
```bash
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` 필요).