HAL AI 채팅룸 도입(0.3.0): 운영자 콘솔 오른쪽 패널에 통합 방(첫 탭 고정) + 모델별 개별 방, 모델 연결(로그인·OAuth PKCE·API 키, 비밀은 Vault), 모델별 고유 정보(이름·역할·특성), HAL 제어·감시 스킬 문서·도구 29종(읽기 즉시 / 쓰기·고위험은 제안 → 운영자 승인(재인증·3단계 확인)), 통합 방 규칙(@이름 지정, 모델→모델 자동 1회 전달), MCP stdio 서버(읽기 전용), 개발 지시 v0.3.0(Oz), docs/HAL-AI.md, VERSION_HISTORY 0.3.0 — test/hal-ai.test.mjs RED 0/11 → GREEN 11/11, 전체 26파일 182건 통과
2026-10-28 17:41:50 +00:00
/**
* hal/ai/routes.mjs — AI 채팅룸 운영자 API(0.3.0). hal/app.mjs 의 운영자 미들웨어(네트워크 관문 → 운영자 세션 → CSRF) 뒤에서만 불린다.
*
* 연결 GET/POST /ops/api/ai/connections · PATCH/DELETE /ops/api/ai/connections/:id · POST …/:id/secret · POST …/:id/test · POST …/:id/oauth/start · GET /ops/ai/oauth/callback
* 방 GET /ops/api/ai/rooms · PATCH /ops/api/ai/rooms/:id · GET/POST /ops/api/ai/rooms/:id/messages · POST /ops/api/ai/rooms/:id/reset
* 제안 GET /ops/api/ai/actions · POST /ops/api/ai/actions/:id/approve · POST /ops/api/ai/actions/:id/reject
2026-10-29 01:11:32 +00:00
* 도구 GET /ops/api/ai/tools · GET /ops/api/ai/skill · GET /ops/api/ai/cli(0.3.1, 서버에 설치된 CLI 탐색)
HAL AI 채팅룸 도입(0.3.0): 운영자 콘솔 오른쪽 패널에 통합 방(첫 탭 고정) + 모델별 개별 방, 모델 연결(로그인·OAuth PKCE·API 키, 비밀은 Vault), 모델별 고유 정보(이름·역할·특성), HAL 제어·감시 스킬 문서·도구 29종(읽기 즉시 / 쓰기·고위험은 제안 → 운영자 승인(재인증·3단계 확인)), 통합 방 규칙(@이름 지정, 모델→모델 자동 1회 전달), MCP stdio 서버(읽기 전용), 개발 지시 v0.3.0(Oz), docs/HAL-AI.md, VERSION_HISTORY 0.3.0 — test/hal-ai.test.mjs RED 0/11 → GREEN 11/11, 전체 26파일 182건 통과
2026-10-28 17:41:50 +00:00
*
* 보안 경계(기존 규칙을 그대로 적용, 약화 없음)
* - 인증 키 변경(API 키 등록·교체, OAuth client secret, 연결 삭제) → requireRecent(최근 10분 재인증).
* - 제안 승인: write → requireRecent, high → requireRecent + threeStep(대상 식별정보 = 도구가 정한 identifiers). 거부는 재인증 없이 가능(실행하지 않는 쪽).
* - OAuth 콜백은 /ops/api 밖(GET, 브라우저 리다이렉트)이지만 운영자 세션이 없으면 401, state 가 시작 운영자와 다르면 거부.
*/
import { HttpError } from '../../lib/http.mjs' ;
import * as C from './connections.mjs' ;
import * as R from './rooms.mjs' ;
import { publicTools , SKILL_DOC } from './tools.mjs' ;
export function registerAiRoutes ( app , d , { requireRecent , threeStep , wrap }) {
const ai = d . ai ; const op = ( ctx ) => ({ id : ctx . operator . id , login : ctx . operator . login , displayName : ctx . operator . displayName });
// ---------------- 연결
app . get ( '/ops/api/ai/connections' , () => ({ ok : true , connections : C . list ( d ), providers : Object . entries ( C_PROVIDERS ()). map (([ k , v ]) => ({ id : k , label : v . label , authKinds : v . authKinds , tools : !! v . tools })) }));
app . post ( '/ops/api/ai/connections' , wrap ( async ( ctx ) => { const b = await ctx . body (); if ( b . authKind === 'api_key' || ( b . oauth && b . oauth . clientSecret )) requireRecent ( ctx ); return { ok : true , connection : C . create ( d , b , ctx . operator . id ) }; }));
app . patch ( '/ops/api/ai/connections/:id' , wrap ( async ( ctx ) => { const b = await ctx . body (); if ( b . oauth && b . oauth . clientSecret ) requireRecent ( ctx ); return { ok : true , connection : C . update ( d , ctx . params . id , b , ctx . operator . id ) }; }));
app . delete ( '/ops/api/ai/connections/:id' , wrap (( ctx ) => { requireRecent ( ctx ); return { ok : true , ... C . remove ( d , ctx . params . id , ctx . operator . id ) }; }));
app . post ( '/ops/api/ai/connections/:id/secret' , wrap ( async ( ctx ) => { requireRecent ( ctx ); const b = await ctx . body (); return { ok : true , connection : C . setSecret ( d , ctx . params . id , b . apiKey , ctx . operator . id ) }; }));
app . post ( '/ops/api/ai/connections/:id/test' , wrap ( async ( ctx ) => ({ ok : true , ...( await C . testConnection ( d , ctx . params . id , ctx . operator . id )) })));
2026-10-29 01:11:32 +00:00
app . get ( '/ops/api/ai/cli' , wrap ( async () => ({ ok : true , ...( await discoverClis ()), tools : Object . entries ( CLI_TOOLS ). map (([ k , v ]) => ({ tool : k , label : v . label , loginCommand : v . loginCommand })) }))); // [0.3.1] 서버에 설치된 CLI 자동 탐색(버전·로그인·기본 모델)
HAL AI 채팅룸 도입(0.3.0): 운영자 콘솔 오른쪽 패널에 통합 방(첫 탭 고정) + 모델별 개별 방, 모델 연결(로그인·OAuth PKCE·API 키, 비밀은 Vault), 모델별 고유 정보(이름·역할·특성), HAL 제어·감시 스킬 문서·도구 29종(읽기 즉시 / 쓰기·고위험은 제안 → 운영자 승인(재인증·3단계 확인)), 통합 방 규칙(@이름 지정, 모델→모델 자동 1회 전달), MCP stdio 서버(읽기 전용), 개발 지시 v0.3.0(Oz), docs/HAL-AI.md, VERSION_HISTORY 0.3.0 — test/hal-ai.test.mjs RED 0/11 → GREEN 11/11, 전체 26파일 182건 통과
2026-10-28 17:41:50 +00:00
app . post ( '/ops/api/ai/connections/:id/oauth/start' , wrap (( ctx ) => ({ ok : true , ... C . oauthStart ( d , ctx . params . id , { operatorId : ctx . operator . id }) })));
app . get ( '/ops/ai/oauth/callback' , wrap ( async ( ctx ) => { if ( ! ctx . operator ) throw new HttpError ( 401 , 'unauthenticated' , '운영자 로그인이 필요합니다' ); if ( ctx . query . error ) throw new HttpError ( 400 , 'oauth_denied' , `공급자가 로그인을 거부했습니다: ${ String ( ctx . query . error ). slice ( 0 , 100 ) } ` ); const c = await C . oauthCallback ( d , { code : ctx . query . code , state : ctx . query . state , operatorId : ctx . operator . id }); return ctx . redirect ( `/ops/ai?oauth=ok&connection= ${ encodeURIComponent ( c . id ) } ` ); }));
// ---------------- 방·메시지
app . get ( '/ops/api/ai/rooms' , () => ({ ok : true , rooms : R . listRooms ( d , C . list ( d )) }));
app . patch ( '/ops/api/ai/rooms/:id' , wrap ( async ( ctx ) => { const b = await ctx . body (); const r = R . renameRoom ( d , ctx . params . id , b . title ); return { ok : true , room : { id : r . id , kind : r . kind , title : r . title , connectionId : r . connection_id } }; }));
app . get ( '/ops/api/ai/rooms/:id/messages' , wrap (( ctx ) => { const room = R . getRoom ( d , ctx . params . id ); if ( ! room ) throw new HttpError ( 404 , 'room_not_found' , '방이 없습니다' ); return { ok : true , messages : R . listMessages ( d , room . id , { after : ctx . query . after , limit : Number ( ctx . query . limit || 500 ) }), pending : ai . pending ( room . id ), actions : ai . listActions ({ roomId : room . id , limit : 100 }), serverTime : new Date (). toISOString () }; }));
app . post ( '/ops/api/ai/rooms/:id/messages' , wrap ( async ( ctx ) => { const b = await ctx . body (); const r = ai . send ( ctx . params . id , { text : b . text , operator : op ( ctx ), context : b . context && typeof b . context === 'object' ? { page : String ( b . context . page || '' ). slice ( 0 , 60 ), id : String ( b . context . id || '' ). slice ( 0 , 120 ) } : null }); return { ok : true , ... r }; }));
app . post ( '/ops/api/ai/rooms/:id/reset' , wrap (( ctx ) => { const room = R . getRoom ( d , ctx . params . id ); if ( ! room ) throw new HttpError ( 404 , 'room_not_found' , '방이 없습니다' ); R . resetRoom ( d , room . id ); return { ok : true }; }));
// ---------------- 제안(승인·거부)
app . get ( '/ops/api/ai/actions' , ( ctx ) => ({ ok : true , actions : ai . listActions ({ status : ctx . query . status || null , roomId : ctx . query . roomId || null , limit : Number ( ctx . query . limit || 200 ) }) }));
app . post ( '/ops/api/ai/actions/:id/approve' , wrap ( async ( ctx ) => {
const a = ai . getAction ( ctx . params . id ); if ( ! a ) throw new HttpError ( 404 , 'action_not_found' , '제안이 없습니다' ); if ( a . status !== 'proposed' ) throw new HttpError ( 409 , 'action_not_proposed' , `이미 처리된 제안입니다( ${ a . status } )` );
requireRecent ( ctx ); const b = await ctx . body (); if ( a . risk === 'high' ) threeStep ( b , a . identifiers ); // 기존 고위험 규칙 그대로: 재인증 + 3단계 확인
return { ok : true , action : await ai . execute ( a . id , op ( ctx )) };
}));
app . post ( '/ops/api/ai/actions/:id/reject' , wrap ( async ( ctx ) => { const b = await ctx . body (); return { ok : true , action : ai . reject ( ctx . params . id , op ( ctx ), b . reason || '' ) }; }));
// ---------------- 도구·스킬
app . get ( '/ops/api/ai/tools' , () => ({ ok : true , tools : publicTools () }));
app . get ( '/ops/api/ai/skill' , () => ({ ok : true , skill : SKILL_DOC }));
}
// providers 모듈을 여기서 직접 import 하지 않고 connections 를 통해 노출(순환 없음)
2026-10-29 01:11:32 +00:00
import { PROVIDERS , discoverClis , CLI_TOOLS } from './providers.mjs' ;
HAL AI 채팅룸 도입(0.3.0): 운영자 콘솔 오른쪽 패널에 통합 방(첫 탭 고정) + 모델별 개별 방, 모델 연결(로그인·OAuth PKCE·API 키, 비밀은 Vault), 모델별 고유 정보(이름·역할·특성), HAL 제어·감시 스킬 문서·도구 29종(읽기 즉시 / 쓰기·고위험은 제안 → 운영자 승인(재인증·3단계 확인)), 통합 방 규칙(@이름 지정, 모델→모델 자동 1회 전달), MCP stdio 서버(읽기 전용), 개발 지시 v0.3.0(Oz), docs/HAL-AI.md, VERSION_HISTORY 0.3.0 — test/hal-ai.test.mjs RED 0/11 → GREEN 11/11, 전체 26파일 182건 통과
2026-10-28 17:41:50 +00:00
const C_PROVIDERS = () => PROVIDERS ;