HAL 0.3.10: 텍스트 셸 복사 대응 — 출력에서 인증 주소 자동 추출(줄 끝에서 끊긴 긴 주소 이어 붙임, 스크롤백·대체 화면 포함)과 "감지된 주소" 복사·열기 단추, 단말 드래그 선택 시 Ctrl+C 는 복사(Ctrl+Shift+C 항상 복사, 선택 없을 때만 셸로 전송), 전체 출력 복사·선택 복사·텍스트로 보기, 클립보드 폴백, 셸 모달 순수 템플릿화, 개발 지시 v0.3.10(Oz)·docs/HAL-AI.md §9·VERSION_HISTORY 0.3.10 — test/hal-ai-0310.test.mjs RED 0/4 → GREEN 4/4, 전체 29파일 199건 통과

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Claude Fable 5.1
2026-10-29 02:49:46 +00:00
parent bed038fb34
commit 72b5e9c7dc
15 changed files with 207 additions and 14 deletions
+26
View File
@@ -76,3 +76,29 @@ export function keyToBytes(e) {
const map = { Enter: '\r', Backspace: '\x7f', Tab: '\t', Escape: '\x1b', ArrowUp: '\x1b[A', ArrowDown: '\x1b[B', ArrowRight: '\x1b[C', ArrowLeft: '\x1b[D', Home: '\x1b[H', End: '\x1b[F', Delete: '\x1b[3~', PageUp: '\x1b[5~', PageDown: '\x1b[6~', Insert: '\x1b[2~' };
if (map[k]) return map[k]; if (k.length === 1 && !ctrl && !e.altKey) return k; return null;
}
/** [0.3.10] 스크롤백 + 화면의 모든 줄. 줄이 꽉 찬 경우(열 수만큼) 다음 줄과 이어진 것으로 보고 붙인다(긴 주소가 줄 끝에서 끊긴 경우). */
export function joinedLines(term) {
const raw = [...term.scrollback(), ...term.grid.map((r) => r.join(''))].map((l) => l.replace(/\s+$/, ''));
const out = []; let cur = null;
for (const l of raw) { if (cur === null) cur = l; else cur += l; if (cur.length > 0 && l.length === term.cols) continue; out.push(cur); cur = null; }
if (cur !== null) out.push(cur); return out;
}
/** [0.3.10] 복사·붙여넣기용 전체 글(이어 붙인 줄 기준) */
export const plainText = (term) => joinedLines(term).join('\n');
const URL_RE = /https?:\/\/[^\s<>"'`│┃║|]+/g;
/** [0.3.10] 출력에 나온 주소(중복 제거, 순서 유지). 꼬리 구두점은 뗀다. */
export function extractUrls(term) {
const seen = new Set(); const out = [];
for (const line of joinedLines(term)) for (const m of line.matchAll(URL_RE)) { const u = m[0].replace(/[.,;:!?)\]}'"]+$/, ''); if (u.length > 10 && !seen.has(u)) { seen.add(u); out.push(u); } }
return out;
}
/** [0.3.10] 단말 영역의 키 처리: 'copy'(브라우저 복사에 맡김) · 'paste'(클립보드 붙여넣기) · 'send'(셸로 전송). 선택된 글이 있으면 Ctrl+C 는 복사. */
export function copyDecision(e, hasSelection) {
const k = String(e.key || '').toLowerCase(); const ctrl = !!(e.ctrlKey || e.metaKey);
if (ctrl && k === 'insert') return 'copy';
if (ctrl && k === 'c') return e.shiftKey || hasSelection ? 'copy' : 'send';
if (ctrl && k === 'v') return 'paste';
if (e.shiftKey && k === 'insert') return 'paste';
return 'send';
}