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/engine.mjs — AI 채팅룸 엔진(0.3.0): 운영자 지시 → 응답할 모델 결정 → 모델 호출(도구 왕복) → 통합 방 전달 규칙 → 제안·승인·거부.
*
* 한 "턴"(runTurn)은 모델 하나가 방의 대화 기록을 받아 답하는 과정이다.
* 1) system prompt = HAL 역할·규칙 + 이 모델의 고유 정보(이름·역할·특성·지시) + 방 정보(참여자) + 운영자·화면 문맥 + 스킬 문서(hal/ai/skill.md).
* 2) 대화 기록을 공급자 블록 형식으로 편다(buildMessages). 다른 모델의 발언은 "[이름] …" 인 user 메시지로 보인다. 아직 결과가 없는 도구 호출(승인 대기)은 자리표시 결과를 넣는다.
* 3) 모델이 도구를 부르면: 읽기 도구는 바로 실행해 결과를 돌려주고 다시 부른다(최대 MAX_ITER 회). 쓰기·고위험 도구는 "제안"(ai_actions, status=proposed)으로 저장하고
* 대화에 제안 카드를 붙인 뒤 턴을 끝낸다 — 운영자가 승인(라우트가 재인증·3단계 확인을 적용)하면 execute() 가 딱 한 번 실행하고 결과를 도구 결과로 넣은 뒤 이어서 부른다. 거부하면 실행하지 않는다.
* 4) 통합 방에서 모델이 `@이름` 으로 다른 모델을 부르면 그 모델의 턴을 relayDepth=1 로 1회 돌린다. depth 1 의 답이 또 누군가를 불러도 자동으로 돌리지 않고 안내만 남긴다.
* 공급자 오류(인증·429·timeout·형식)는 모델 답으로 꾸미지 않고 status=failed 메시지로 남긴다.
* 비밀값은 자격(secretFor)으로만 흐르고 메시지·감사·오류에는 들어가지 않는다(redact·safeError).
* 턴은 프로세스 안에서 비동기로 돈다(HAL 재시작 뒤 진행 중 턴은 사라지며 "진행 중" 표시도 사라진다 — 거짓 진행 표시를 하지 않는다). 테스트는 drain() 으로 모든 턴을 기다린다.
*/
import { HttpError } from '../../lib/http.mjs' ;
import { audit } from '../../lib/audit.mjs' ;
import { nowIso , toJson , parseJson } from '../../lib/db.mjs' ;
import { newId } from '../../lib/crypto.mjs' ;
import { redact , safeError } from '../../lib/redact.mjs' ;
import * as C from './connections.mjs' ;
import * as R from './rooms.mjs' ;
import { toolByName , toolDefs , validateArgs , runTool , toolOutputText , SKILL_DOC , RISK_LABEL } from './tools.mjs' ;
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건 통과
2026-10-29 01:43:02 +00:00
import { toolFormat } from './providers.mjs' ;
HAL 0.3.9: 로그인 터미널·텍스트 셸·모델 선택·슬래시 명령·점검 수정 — claude 실제 로그인 명령 auth login(대화형 세션·신뢰 설정도 허용, 작업 폴더 HOME), 텍스트 셸 범용화(순수 단말 에뮬레이터 term.mjs 로 TUI 렌더, 원시 키 전달·키 단추·붙여넣기·단말 크기), 유령 세션 한도 오류 수정(닫기=종료, 살아 있는 프로세스만 계산, 한도 3, 모두 종료·세션 목록), 모델 선택(확인된 목록·PATCH·/model), 슬래시 명령 20개(/help /status /order /deploy …, 원문 기록·확장 지시), 패널 로그인·재인증 단추, 개발 지시 v0.3.9(Oz)·docs/HAL-AI.md §8·VERSION_HISTORY 0.3.9 — test/hal-ai-039.test.mjs RED 0/6 → GREEN 6/6, 전체 28파일 195건 통과
2026-10-29 02:22:21 +00:00
import { parseCommand , helpText } from './commands.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
import * as P from './providers.mjs' ;
const MAX_ITER = 8 ; // 한 턴에서 도구 왕복 최대 횟수(무한 반복 방지)
const MAX_TEXT = 20000 ; // 운영자 지시 최대 글자 수
const textOf = ( blocks ) => ( blocks || []). filter (( b ) => b . type === 'text' ). map (( b ) => b . text ). join ( '\n' ). trim ();
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건 통과
2026-10-29 01:43:02 +00:00
export function publicAction ( r ) { if ( ! r ) return null ; const tool = toolByName ( r . tool ); const args = parseJson ( r . args_json , {}); return { id : r . id , roomId : r . room_id , messageId : r . message_id , connectionId : r . connection_id , source : r . source || 'chat' , tool : r . tool , args , risk : r . risk , riskLabel : RISK_LABEL [ r . risk ] || r . risk , description : tool ? tool . description : '(알 수 없는 도구)' , identifiers : tool && tool . identifiers ? tool . identifiers ( args ) : [], status : r . status , proposedAt : r . proposed_at , decidedBy : r . decided_by , decidedAt : r . decided_at , result : parseJson ( r . result_json , null ), error : r . error , toolUseId : r . tool_use_id , relayDepth : r . relay_depth }; }
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
export function createEngine ( d ) {
const inflight = new Set (); const pendingByRoom = new Map ();
const bump = ( roomId , n ) => pendingByRoom . set ( roomId , Math . max ( 0 , ( pendingByRoom . get ( roomId ) || 0 ) + n ));
const track = ( p ) => { inflight . add ( p ); p . finally (() => inflight . delete ( p )); return p ; };
R . ensureGroupRoom ( d );
/** system prompt — 모델별 고유 정보와 방 규칙. 비밀 없음. */
function systemPrompt ( room , conn , participants , { operator , context } = {}) {
const idn = conn . identity || {}; const others = participants . filter (( p ) => p . id !== conn . id );
const lines = [
`너는 Ariweb Suite 제어 평면 HAL 의 운영자 콘솔 AI 채팅룸에 연결된 모델 참여자다. 네 이름은 " ${ conn . name } " 이다(공급자 ${ conn . providerLabel } , 모델 ${ conn . model } ).` ,
`역할: ${ idn . role || '(지정 없음)' } ` , `특성: ${ idn . traits || '(지정 없음)' } ` , idn . instructions ? `추가 지시: ${ idn . instructions } ` : '' ,
room . kind === 'group' ? `방: 통합 방 — 연결된 모든 활성 모델이 함께 있다. 참여자: ${ participants . map (( p ) => `" ${ p . name } " ${ p . id === conn . id ? '(나)' : '' }${ p . identity && p . identity . role ? ` — ${ p . identity . role } ` : '' } ` ). join ( ', ' ) } . 운영자의 지시는 모두에게 전달되며, 운영자가 "@이름" 으로 지정한 모델만 답해야 할 때는 그 모델만 답한다(지금 네가 부름을 받았다). 다른 참여자에게 말을 넘기려면 "@이름" 을 쓴다(그 참여자는 자동으로 1회만 답한다). 자기 자신(@ ${ conn . name } )을 부르지 마라. ${ others . length ? '' : ' 지금은 다른 활성 참여자가 없다.' } ` : `방: 개별 방 — 운영자와 1:1 대화. 다른 모델은 이 대화를 보지 않는다.` ,
operator ? `운영자: ${ operator . displayName || operator . login } ( ${ operator . login } ).` : '' , context && ( context . page || context . id ) ? `운영자가 보고 있는 HAL 화면: ${ context . page || '' }${ context . id ? ` ( ${ context . id } )` : '' } .` : '' ,
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건 통과
2026-10-29 01:43:02 +00:00
conn . toolsSupported && ( conn . tools ? . enabled || []). length ? ( conn . toolsVia === 'mcp' ? `도구: HAL MCP 서버(hal)가 붙어 있다 — mcp__hal 도구로 HAL 을 조회할 수 있고, 쓰기·고위험 도구는 승인 요청(제안)으로 올라가며 hal_action_status 로 결과를 본다.` : `도구: 아래 스킬 문서의 규칙대로 HAL 도구를 쓸 수 있다(이 연결에 켜진 도구 ${ conn . tools . enabled . length } 개). 읽기 도구는 즉시 실행되고, 쓰기·고위험 도구는 운영자 승인 전에는 실행되지 않는다(제안으로 멈춤).` ) : `도구: 이 연결에는 HAL 도구가 없다(대화·분석 전용). 운영자가 준 정보로만 답하고 상태를 추측하지 마라.` ,
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
'한국어로 답하라. 사실·근거·확인하지 못한 것을 구분하라.' , '' , SKILL_DOC ,
];
return lines . filter (( l ) => l !== '' ). join ( '\n' );
}
/** 방의 기록 → 이 모델이 받을 공급자 블록 메시지. */
function buildMessages ( room , conn ) {
const rows = R . listMessages ( d , room . id , { limit : 2000 }); const resolved = new Set ();
for ( const m of rows ) if ( m . role === 'tool' && m . connectionId === conn . id ) for ( const b of m . content ) if ( b . type === 'tool_result' ) resolved . add ( b . toolUseId );
const out = [];
for ( const m of rows ) {
if ( m . status === 'failed' ) continue ;
HAL 0.3.9: 로그인 터미널·텍스트 셸·모델 선택·슬래시 명령·점검 수정 — claude 실제 로그인 명령 auth login(대화형 세션·신뢰 설정도 허용, 작업 폴더 HOME), 텍스트 셸 범용화(순수 단말 에뮬레이터 term.mjs 로 TUI 렌더, 원시 키 전달·키 단추·붙여넣기·단말 크기), 유령 세션 한도 오류 수정(닫기=종료, 살아 있는 프로세스만 계산, 한도 3, 모두 종료·세션 목록), 모델 선택(확인된 목록·PATCH·/model), 슬래시 명령 20개(/help /status /order /deploy …, 원문 기록·확장 지시), 패널 로그인·재인증 단추, 개발 지시 v0.3.9(Oz)·docs/HAL-AI.md §8·VERSION_HISTORY 0.3.9 — test/hal-ai-039.test.mjs RED 0/6 → GREEN 6/6, 전체 28파일 195건 통과
2026-10-29 02:22:21 +00:00
if ( m . role === 'user' ) { const cmd = m . content . find (( b ) => b . type === 'command' ); const t = cmd && cmd . expanded ? cmd . expanded : textOf ( m . content ); if ( t ) out . push ({ role : 'user' , content : [{ type : 'text' , text : room . kind === 'group' ? `[ ${ m . author || '운영자' } ] ${ t } ` : t }] }); continue ; } // [0.3.9] 슬래시 명령은 확장된 지시를 모델에게
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
if ( m . role === 'system' ) { const t = textOf ( m . content ); if ( t ) out . push ({ role : 'user' , content : [{ type : 'text' , text : `[HAL] ${ t } ` }] }); continue ; }
if ( m . role === 'tool' ) { if ( m . connectionId === conn . id ) out . push ({ role : 'user' , content : m . content . filter (( b ) => b . type === 'tool_result' ) }); continue ; }
if ( m . role === 'assistant' ) {
if ( m . connectionId === conn . id ) {
const blocks = m . content . filter (( b ) => b . type === 'text' || b . type === 'tool_use' ); if ( blocks . length ) out . push ({ role : 'assistant' , content : blocks });
const open = blocks . filter (( b ) => b . type === 'tool_use' && ! resolved . has ( b . id )); if ( open . length ) out . push ({ role : 'user' , content : open . map (( b ) => ({ type : 'tool_result' , toolUseId : b . id , name : b . name , ok : false , output : '(결과 없음 — 운영자 승인 대기 중이거나 실행되지 않았습니다)' })) });
} else { const t = textOf ( m . content ); if ( t ) out . push ({ role : 'user' , content : [{ type : 'text' , text : `[ ${ m . author || '다른 모델' } ] ${ t } ` }] }); }
}
}
return out ;
}
const actorFor = ( operator , conn ) => ({ operatorId : operator ? operator . id : null , label : `ai: ${ conn . id } ` });
/** 모델 턴 하나. 끝나면 (통합 방) @전달 규칙을 적용한다. */
async function runTurn ( room , conn , { relayDepth = 0 , operator = null , context = null } = {}) {
bump ( room . id , 1 );
try {
const participants = C . active ( d ); const system = systemPrompt ( room , conn , participants , { operator , context });
let secret ; try { secret = await C . secretFor ( d , conn ); } catch ( e ) { fail ( room , conn , relayDepth , e ); return ; }
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건 통과
2026-10-29 01:43:02 +00:00
const enabled = conn . toolsSupported ? ( conn . tools ? . enabled || []) : []; const tools = enabled . length && conn . toolsVia !== 'mcp' ? toolDefs ( toolFormat ( conn . provider ), enabled ) : []; // [0.3.2] 연결별 도구 정책
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
let finalText = '' ; let iter = 0 ;
while ( iter ++ < MAX_ITER ) {
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건 통과
2026-10-29 01:43:02 +00:00
let resp ; try { resp = await P . chat ( d , conn , secret , { system , messages : buildMessages ( room , conn ), tools , mcp : conn . toolsVia === 'mcp' && enabled . length > 0 }); } catch ( e ) { fail ( room , conn , relayDepth , e ); return ; }
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 asst = R . appendMessage ( d , { roomId : room . id , role : 'assistant' , connectionId : conn . id , author : conn . name , content : resp . blocks , relayDepth , usage : { ... resp . usage , model : resp . model || conn . model }, status : 'done' });
const uses = resp . blocks . filter (( b ) => b . type === 'tool_use' );
if ( ! uses . length ) { finalText = textOf ( resp . blocks ); break ; }
const results = []; const proposals = [];
for ( const u of uses ) {
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건 통과
2026-10-29 01:43:02 +00:00
const tool = toolByName ( u . name ); if ( tool && ! enabled . includes ( tool . name )) { results . push ({ type : 'tool_result' , toolUseId : u . id , name : u . name , ok : false , output : `이 연결에서 꺼진 도구입니다: ${ u . name } (도구 정책 ${ conn . tools ? . preset || 'all' } )` }); continue ; }
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
if ( ! tool ) { results . push ({ type : 'tool_result' , toolUseId : u . id , name : u . name , ok : false , output : `알 수 없는 도구: ${ u . name } . HAL 이 제공하는 도구만 부를 수 있다(임의 명령·셸 실행 도구는 없다).` }); continue ; }
const why = validateArgs ( tool , u . input ); if ( why ) { results . push ({ type : 'tool_result' , toolUseId : u . id , name : u . name , ok : false , output : `인자 오류: ${ why } ` }); continue ; }
if ( tool . risk === 'read' ) { try { const out = await runTool ( d , tool , u . input , actorFor ( operator , conn )); results . push ({ type : 'tool_result' , toolUseId : u . id , name : u . name , ok : true , output : toolOutputText ( out ) }); } catch ( e ) { results . push ({ type : 'tool_result' , toolUseId : u . id , name : u . name , ok : false , output : `도구 실행 실패( ${ e . code || 'error' } ): ${ safeError ( e ) } ` }); } continue ; }
const a = propose ( room , conn , asst , u , tool , relayDepth , operator ); proposals . push ( a );
}
if ( proposals . length ) R . updateMessage ( d , asst . id , { content : [... resp . blocks , ... proposals . map (( a ) => ({ type : 'proposal' , actionId : a . id , tool : a . tool , args : a . args , risk : a . risk , riskLabel : a . riskLabel , identifiers : a . identifiers , description : a . description }))] });
if ( results . length ) R . appendMessage ( d , { roomId : room . id , role : 'tool' , connectionId : conn . id , author : conn . name , content : results , relayDepth });
if ( proposals . length ) return ; // 승인 대기 — 승인·거부 전에는 모델을 다시 부르지 않는다
}
if ( iter > MAX_ITER ) R . appendMessage ( d , { roomId : room . id , role : 'system' , author : 'HAL' , content : [{ type : 'text' , text : ` ${ conn . name } 의 도구 왕복이 ${ MAX_ITER } 회를 넘어 이 턴을 멈췄습니다 — 필요하면 다시 지시하세요.` }], relayDepth });
if ( room . kind === 'group' && finalText ) {
const targets = R . mentionedParticipants ( finalText , participants , conn . id );
if ( targets . length ) {
if ( relayDepth === 0 ) { for ( const t of targets ) await runTurn ( room , t , { relayDepth : 1 , operator , context }); }
else R . appendMessage ( d , { roomId : room . id , role : 'system' , author : 'HAL' , content : [{ type : 'text' , text : ` ${ conn . name } 이(가) ${ targets . map (( t ) => '@' + t . name ). join ( ' ' ) } 을(를) 불렀지만 모델→모델 전달은 자동으로 1회만 이어집니다 — 추가 반응은 사용자 지시로 진행합니다.` }], relayDepth });
}
}
} finally { bump ( room . id , - 1 ); }
}
function fail ( room , conn , relayDepth , e ) {
const msg = safeError ( e ); const code = e && e . code ? e . code : 'provider_error' ;
R . appendMessage ( d , { roomId : room . id , role : 'assistant' , connectionId : conn . id , author : conn . name , content : [{ type : 'text' , text : '' }], relayDepth , status : 'failed' , error : ` ${ code } : ${ msg } ` });
2026-10-29 01:11:32 +00:00
if ([ 'oauth_required' , 'oauth_expired' , 'provider_401' , 'cli_not_logged_in' ]. includes ( code )) d . store . run ( 'UPDATE ai_connections SET status=CASE WHEN status=\'disabled\' THEN status ELSE \'reauth_required\' END, last_error=?, updated_at=? WHERE id=?' , msg , nowIso (), conn . id );
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
else d . store . run ( 'UPDATE ai_connections SET last_error=?, updated_at=? WHERE id=?' , msg , nowIso (), conn . id );
audit ( d . store , { actorType : 'system' , actorId : `ai: ${ conn . id } ` , action : 'ai.turn.failed' , subjectType : 'ai_room' , subjectId : room . id , outcome : 'failed' , automatic : true , detail : { connection : conn . id , code , message : msg } });
}
function propose ( room , conn , asst , use , tool , relayDepth , operator ) {
const id = newId ( 'aia_' ); const args = use . input || {};
d . store . run ( 'INSERT INTO ai_actions(id, room_id, message_id, connection_id, tool, args_json, risk, status, proposed_at, tool_use_id, relay_depth) VALUES(?,?,?,?,?,?,?,?,?,?,?)' , id , room . id , asst . id , conn . id , tool . name , toJson ( redact ( args )), tool . risk , 'proposed' , nowIso (), use . id , relayDepth );
audit ( d . store , { actorType : 'system' , actorId : `ai: ${ conn . id } ` , action : 'ai.action.propose' , subjectType : 'ai_action' , subjectId : id , automatic : true , detail : { room : room . id , tool : tool . name , risk : tool . risk , args , operator : operator ? operator . id : null } });
return publicAction ( d . store . get ( 'SELECT * FROM ai_actions WHERE id=?' , id ));
}
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건 통과
2026-10-29 01:43:02 +00:00
/** [0.3.2] 채팅 밖 출처(MCP 클라이언트 등)의 승인 요청: 통합 방에 HAL 안내 + 제안 카드로 올린다. 인자 검증은 여기서(권한 아님). */
function proposeExternal ({ source = 'mcp' , tool : name , args = {}, label = null }) {
const tool = toolByName ( name ); if ( ! tool ) throw new HttpError ( 404 , 'unknown_tool' , `알 수 없는 도구: ${ name } ` ); if ( tool . risk === 'read' ) throw new HttpError ( 400 , 'not_approvable' , '읽기 도구는 승인 대상이 아닙니다' );
const why = validateArgs ( tool , args ); if ( why ) throw new HttpError ( 400 , 'bad_tool_args' , why );
R . ensureGroupRoom ( d ); const id = newId ( 'aia_' ); const src = String ( source ). slice ( 0 , 40 );
const msg = R . appendMessage ( d , { roomId : R . GROUP_ROOM , role : 'system' , author : 'HAL' , content : [{ type : 'text' , text : ` ${ label || `외부 클라이언트( ${ src } )` } 가 ${ RISK_LABEL [ tool . risk ] } 도구 ${ tool . name } 실행 승인을 요청했습니다.` }], relayDepth : 0 });
d . store . run ( 'INSERT INTO ai_actions(id, room_id, message_id, connection_id, tool, args_json, risk, status, proposed_at, tool_use_id, relay_depth, source) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)' , id , R . GROUP_ROOM , msg . id , `ext: ${ src } ` , tool . name , toJson ( redact ( args )), tool . risk , 'proposed' , nowIso (), null , 0 , src );
const a = publicAction ( d . store . get ( 'SELECT * FROM ai_actions WHERE id=?' , id ));
R . updateMessage ( d , msg . id , { content : [... msg . content , { type : 'proposal' , actionId : a . id , tool : a . tool , args : a . args , risk : a . risk , riskLabel : a . riskLabel , identifiers : a . identifiers , description : a . description , source : src }] });
audit ( d . store , { actorType : 'system' , actorId : `ext: ${ src } ` , action : 'ai.action.propose' , subjectType : 'ai_action' , subjectId : id , automatic : true , detail : { source : src , tool : tool . name , risk : tool . risk , args } });
return a ;
}
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 startTurn = ( room , conn , opts ) => { const id = newId ( 'turn_' ); track ( runTurn ( room , conn , opts ). catch (( e ) => d . log ( '[ai] turn error' , safeError ( e )))); return id ; };
const engine = {
/** 모든 진행 중 턴이 끝날 때까지(테스트·종료용) */
async drain () { while ( inflight . size ) await Promise . allSettled ([... inflight ]); },
pending : ( roomId ) => pendingByRoom . get ( roomId ) || 0 ,
/** 운영자 지시 보내기 → 응답할 모델들의 턴을 시작한다. */
send ( roomId , { text , operator , context = null }) {
const t = String ( text || '' ). trim (); if ( ! t ) throw new HttpError ( 400 , 'empty_message' , '지시 내용이 비어 있습니다' ); if ( t . length > MAX_TEXT ) throw new HttpError ( 400 , 'message_too_long' , `지시는 ${ MAX_TEXT } 자까지입니다` );
const room = R . getRoom ( d , roomId ); if ( ! room ) throw new HttpError ( 404 , 'room_not_found' , '방이 없습니다' );
HAL 0.3.9: 로그인 터미널·텍스트 셸·모델 선택·슬래시 명령·점검 수정 — claude 실제 로그인 명령 auth login(대화형 세션·신뢰 설정도 허용, 작업 폴더 HOME), 텍스트 셸 범용화(순수 단말 에뮬레이터 term.mjs 로 TUI 렌더, 원시 키 전달·키 단추·붙여넣기·단말 크기), 유령 세션 한도 오류 수정(닫기=종료, 살아 있는 프로세스만 계산, 한도 3, 모두 종료·세션 목록), 모델 선택(확인된 목록·PATCH·/model), 슬래시 명령 20개(/help /status /order /deploy …, 원문 기록·확장 지시), 패널 로그인·재인증 단추, 개발 지시 v0.3.9(Oz)·docs/HAL-AI.md §8·VERSION_HISTORY 0.3.9 — test/hal-ai-039.test.mjs RED 0/6 → GREEN 6/6, 전체 28파일 195건 통과
2026-10-29 02:22:21 +00:00
// [0.3.9] 슬래시 명령: local 은 바로 처리(모델 호출 없음), prompt 는 확장 지시로 바꿔 보낸다
let command = null ; if ( t . startsWith ( '/' )) {
const pc = parseCommand ( t ); if ( ! pc || ! pc . command ) throw new HttpError ( 400 , 'unknown_command' , `모르는 명령입니다: ${ t . split ( /\s+/ )[ 0 ]. slice ( 0 , 30 ) } — /help 로 명령 목록을 보세요` );
if ( pc . command . requiresArgs && ! pc . args ) throw new HttpError ( 400 , 'command_args_required' , `인자가 필요합니다: ${ pc . command . usage } ` );
if ( pc . command . kind === 'local' ) return engine . localCommand ( room , pc , operator );
command = { type : 'command' , name : pc . name , args : pc . args , expanded : pc . command . expand ( pc . args ) };
}
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
let responders ;
if ( room . kind === 'direct' ) { const conn = C . get ( d , room . connection_id ); if ( ! conn ) throw new HttpError ( 404 , 'connection_not_found' , '연결이 없습니다' ); if ( conn . status !== 'active' ) throw new HttpError ( 409 , 'connection_inactive' , `연결 " ${ conn . name } " 이(가) ${ conn . status } 상태라 보낼 수 없습니다` , { status : conn . status , lastError : conn . lastError }); responders = [ conn ]; }
else { const participants = C . active ( d ); if ( ! participants . length ) throw new HttpError ( 409 , 'no_active_connections' , '활성 AI 연결이 없습니다 — AI 연동 화면에서 연결을 추가하세요' ); const { addressed , unknown } = R . parseAddressing ( t , participants ); if ( unknown . length ) throw new HttpError ( 400 , 'unknown_addressee' , `모르는 이름입니다: ${ unknown . map (( n ) => '@' + n ). join ( ', ' ) } ` , { unknown , participants : participants . map (( p ) => p . name ) }); responders = addressed . length ? participants . filter (( p ) => addressed . includes ( p . id )) : participants ; }
HAL 0.3.9: 로그인 터미널·텍스트 셸·모델 선택·슬래시 명령·점검 수정 — claude 실제 로그인 명령 auth login(대화형 세션·신뢰 설정도 허용, 작업 폴더 HOME), 텍스트 셸 범용화(순수 단말 에뮬레이터 term.mjs 로 TUI 렌더, 원시 키 전달·키 단추·붙여넣기·단말 크기), 유령 세션 한도 오류 수정(닫기=종료, 살아 있는 프로세스만 계산, 한도 3, 모두 종료·세션 목록), 모델 선택(확인된 목록·PATCH·/model), 슬래시 명령 20개(/help /status /order /deploy …, 원문 기록·확장 지시), 패널 로그인·재인증 단추, 개발 지시 v0.3.9(Oz)·docs/HAL-AI.md §8·VERSION_HISTORY 0.3.9 — test/hal-ai-039.test.mjs RED 0/6 → GREEN 6/6, 전체 28파일 195건 통과
2026-10-29 02:22:21 +00:00
const message = R . appendMessage ( d , { roomId : room . id , role : 'user' , author : operator ? operator . login : null , content : command ? [{ type : 'text' , text : t }, command ] : [{ type : 'text' , text : t }], addressed : responders . map (( c ) => c . id ), relayDepth : 0 });
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 turns = responders . map (( conn ) => startTurn ( room , conn , { relayDepth : 0 , operator , context }));
return { message , responders : responders . map (( c ) => c . id ), turns };
},
HAL 0.3.9: 로그인 터미널·텍스트 셸·모델 선택·슬래시 명령·점검 수정 — claude 실제 로그인 명령 auth login(대화형 세션·신뢰 설정도 허용, 작업 폴더 HOME), 텍스트 셸 범용화(순수 단말 에뮬레이터 term.mjs 로 TUI 렌더, 원시 키 전달·키 단추·붙여넣기·단말 크기), 유령 세션 한도 오류 수정(닫기=종료, 살아 있는 프로세스만 계산, 한도 3, 모두 종료·세션 목록), 모델 선택(확인된 목록·PATCH·/model), 슬래시 명령 20개(/help /status /order /deploy …, 원문 기록·확장 지시), 패널 로그인·재인증 단추, 개발 지시 v0.3.9(Oz)·docs/HAL-AI.md §8·VERSION_HISTORY 0.3.9 — test/hal-ai-039.test.mjs RED 0/6 → GREEN 6/6, 전체 28파일 195건 통과
2026-10-29 02:22:21 +00:00
/** [0.3.9] HAL 이 바로 처리하는 명령(/help·/tools·/model·/reset). 모델을 부르지 않는다. */
localCommand ( room , pc , operator ) {
const note = ( text ) => ({ message : R . appendMessage ( d , { roomId : room . id , role : 'system' , author : 'HAL' , content : [{ type : 'text' , text }], relayDepth : 0 }), responders : [], turns : [], command : pc . name });
if ( pc . name === 'help' ) return note ( helpText ());
if ( pc . name === 'reset' ) { R . resetRoom ( d , room . id ); return { message : null , responders : [], turns : [], command : 'reset' }; }
const conn = room . kind === 'direct' ? C . get ( d , room . connection_id ) : null ;
if ( pc . name === 'tools' ) { const list = conn ? [ conn ] : C . active ( d ); return note ( list . length ? list . map (( c ) => ` ${ c . name } : 도구 정책 ${ c . tools . preset } ( ${ c . tools . enabled . length } 개) ${ c . toolsVia === 'mcp' ? ' · MCP' : '' } \n ${ c . tools . enabled . join ( ', ' ) || '(없음)' } ` ). join ( '\n' ) : '활성 연결이 없습니다' ); }
if ( pc . name === 'model' ) { if ( ! conn ) throw new HttpError ( 400 , 'command_needs_direct_room' , '/model 은 개별 방(모델 탭)에서만 쓸 수 있습니다' ); const before = conn . model ; const c2 = C . update ( d , conn . id , { model : pc . args }, operator ? operator . id : null ); return note ( `모델을 ${ before } → ${ c2 . model } 로 바꿨습니다(연결 ${ c2 . name } ).` ); }
throw new HttpError ( 400 , 'unknown_command' , '모르는 명령입니다 — /help' );
},
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
listActions ({ roomId = null , status = null , limit = 200 } = {}) { const w = []; const p = []; if ( roomId ) { w . push ( 'room_id=?' ); p . push ( roomId ); } if ( status ) { w . push ( 'status=?' ); p . push ( status ); } return d . store . all ( `SELECT * FROM ai_actions ${ w . length ? 'WHERE ' + w . join ( ' AND ' ) : '' } ORDER BY proposed_at DESC LIMIT ?` , ... p , Math . min ( 1000 , limit )). map ( publicAction ); },
getAction ( id ) { return publicAction ( d . store . get ( 'SELECT * FROM ai_actions WHERE id=?' , id )); },
/** 승인 뒤 실행(라우트가 재인증·3단계 확인을 먼저 적용한다). 딱 한 번만 실행되며 결과를 도구 결과로 넣고 모델을 이어서 부른다. */
async execute ( actionId , operator ) {
const a = engine . getAction ( actionId ); if ( ! a ) throw new HttpError ( 404 , 'action_not_found' , '제안이 없습니다' ); if ( a . status !== 'proposed' ) throw new HttpError ( 409 , 'action_not_proposed' , `이미 처리된 제안입니다( ${ a . status } )` );
const tool = toolByName ( a . tool ); const conn = C . get ( d , a . connectionId ); const room = R . getRoom ( d , a . roomId );
// 상태를 먼저 바꿔 두 번 실행되지 않게 한다(동시 승인 클릭)
const claimed = d . store . run ( 'UPDATE ai_actions SET status=\'executing\', decided_by=?, decided_at=? WHERE id=? AND status=\'proposed\'' , operator . id , nowIso (), actionId ). changes ; if ( ! claimed ) throw new HttpError ( 409 , 'action_not_proposed' , '이미 처리된 제안입니다' );
let result = null ; let error = null ;
try { if ( ! tool ) throw new HttpError ( 400 , 'unknown_tool' , '알 수 없는 도구' ); result = await runTool ( d , tool , a . args , { operatorId : operator . id , label : `ai: ${ a . connectionId } ` }); } catch ( e ) { error = ` ${ e . code || 'error' } : ${ safeError ( e ) } ` ; }
d . store . run ( 'UPDATE ai_actions SET status=?, result_json=?, error=? WHERE id=?' , error ? 'failed' : 'executed' , result ? toJson ( result ) : null , error , actionId );
audit ( d . store , { actorType : 'operator' , actorId : operator . id , action : 'ai.action.execute' , subjectType : 'ai_action' , subjectId : actionId , outcome : error ? 'failed' : 'ok' , detail : { tool : a . tool , risk : a . risk , args : a . args , room : a . roomId , connection : a . connectionId , error } });
if ( room && conn ) { R . appendMessage ( d , { roomId : room . id , role : 'tool' , connectionId : conn . id , author : conn . name , content : [{ type : 'tool_result' , toolUseId : a . toolUseId , name : a . tool , ok : ! error , output : error ? `운영자가 승인했지만 실행에 실패했습니다 — ${ error } ` : toolOutputText ( result ), actionId }], relayDepth : a . relayDepth || 0 }); if ( conn . status === 'active' ) startTurn ( room , conn , { relayDepth : a . relayDepth || 0 , operator }); }
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건 통과
2026-10-29 01:43:02 +00:00
else if ( room ) R . appendMessage ( d , { roomId : room . id , role : 'system' , author : 'HAL' , content : [{ type : 'text' , text : ` ${ a . source } 의 승인 요청 ${ a . tool } 을(를) ${ operator . login } 이(가) 승인해 실행했습니다 ${ error ? ' — 실패' : '' } .` }, { type : 'tool_result' , toolUseId : null , name : a . tool , ok : ! error , output : error ? `실행 실패 — ${ error } ` : toolOutputText ( result ), actionId }], relayDepth : 0 }); // [0.3.2] 외부 출처: 통합 방에 결과 안내(이어 부를 모델 없음)
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
return engine . getAction ( actionId );
},
reject ( actionId , operator , reason = '' ) {
const a = engine . getAction ( actionId ); if ( ! a ) throw new HttpError ( 404 , 'action_not_found' , '제안이 없습니다' ); if ( a . status !== 'proposed' ) throw new HttpError ( 409 , 'action_not_proposed' , `이미 처리된 제안입니다( ${ a . status } )` );
d . store . run ( 'UPDATE ai_actions SET status=\'rejected\', decided_by=?, decided_at=?, error=? WHERE id=?' , operator . id , nowIso (), reason ? String ( reason ). slice ( 0 , 500 ) : null , actionId );
audit ( d . store , { actorType : 'operator' , actorId : operator . id , action : 'ai.action.reject' , subjectType : 'ai_action' , subjectId : actionId , outcome : 'denied' , detail : { tool : a . tool , risk : a . risk , reason } });
const conn = C . get ( d , a . connectionId ); if ( conn ) R . appendMessage ( d , { roomId : a . roomId , role : 'tool' , connectionId : conn . id , author : conn . name , content : [{ type : 'tool_result' , toolUseId : a . toolUseId , name : a . tool , ok : false , output : `운영자가 이 제안을 거부했습니다 ${ reason ? ` — ${ String ( reason ). slice ( 0 , 500 ) } ` : '' } . 실행되지 않았습니다.` , actionId }], relayDepth : a . relayDepth || 0 });
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건 통과
2026-10-29 01:43:02 +00:00
else R . appendMessage ( d , { roomId : a . roomId , role : 'system' , author : 'HAL' , content : [{ type : 'text' , text : ` ${ a . source } 의 승인 요청 ${ a . tool } 을(를) ${ operator . login } 이(가) 거부했습니다 ${ reason ? ` — ${ String ( reason ). slice ( 0 , 300 ) } ` : '' } . 실행되지 않았습니다.` }], relayDepth : 0 });
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
return engine . getAction ( actionId ); // 거부는 모델을 자동으로 다시 부르지 않는다(운영자가 다음 지시를 준다)
},
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건 통과
2026-10-29 01:43:02 +00:00
systemPrompt , proposeExternal ,
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
};
return engine ;
}