chore: v2.2.73 — ASTRA-DEBUG 로그 레벨 + webview CSP font-src 보강
- ASTRA-DEBUG 정상 흐름 로그를 console.error → logInfo/console.log 로 강등 (chatHandlers, extension, slashRouter): DevTools에 ERR로 찍히던 오탐 제거 - sidebar webview에 명시적 CSP meta 추가 + font-src에 data: 허용 (sidebar.html, sidebarProvider._getHtml): VS Code outer iframe이 codicon.ttf를 data:font/ttf 로 inject하면서 기본 CSP에 막혀 매 prompt 마다 violation 경고가 찍히던 문제 해소 - 누적된 LM Studio / agent / 컨텍스트 매니저 / 테스트 갱신 동반 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+13
-20
@@ -2,6 +2,7 @@
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="__CSP__">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Astra</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
@@ -40,7 +41,15 @@
|
||||
<button class="icon-btn" id="companyManageBtn" data-tooltip="기업 모드 관리 (에이전트 · 모델 · 프롬프트 · 지식 비중)">▾</button>
|
||||
<div class="hdr-dropdown" data-dd>
|
||||
<button class="icon-btn" id="toolsMenuBtn" data-dd-trigger data-tooltip="개발자 도구 모음">도구 ▾</button>
|
||||
<div class="hdr-menu" id="toolsMenu" data-dd-menu>
|
||||
<div class="hdr-menu hdr-menu-wide" id="toolsMenu" data-dd-menu>
|
||||
<div class="hdr-menu-label">자동 기록</div>
|
||||
<button class="hdr-menu-item toggle-item" id="chronicleAutoRecordBtn" data-tooltip="의미 있는 대화 turn 을 활성 프로젝트의 Chronicle 폴더에 자동 저장">자동 기록: 켜짐</button>
|
||||
<div class="hdr-menu-hint" id="chronicleAutoStatus" title="가장 최근에 자동 저장된 기록"><span class="status-dot ready"></span> <span id="recordsLatest"></span></div>
|
||||
<div class="select-wrap"><select id="chronicleRecordSel" title="열어볼 작업 기록 선택"></select></div>
|
||||
<button class="hdr-menu-item" id="openChronicleRecordBtn" data-tooltip="선택한 기록 열기">선택한 기록 열기</button>
|
||||
<button class="hdr-menu-item" id="refreshChronicleRecordsBtn" data-tooltip="기록 목록 다시 불러오기">기록 새로고침</button>
|
||||
<button class="hdr-menu-item" id="openDesignerBtn" data-tooltip="기록이 저장된 폴더 열기">기록 폴더 열기</button>
|
||||
|
||||
<div class="hdr-menu-label">도구</div>
|
||||
<button class="hdr-menu-item toggle-item" id="brainTraceDebugBtn" data-tooltip="근거 추적의 원본 JSON 표시 (개발자용)">근거 추적 JSON 보기</button>
|
||||
<button class="hdr-menu-item" id="saveWikiRawBtn" data-tooltip="현재 답변의 원본 마크다운을 두뇌(지식)에 저장">원본 답변을 두뇌에 저장</button>
|
||||
@@ -123,25 +132,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="records-line">
|
||||
<div class="rl-summary">
|
||||
<span class="status-dot ready"></span>
|
||||
<span id="chronicleAutoStatus" title="의미 있는 대화 후 프로젝트 기록이 자동으로 저장됩니다.">자동 기록</span>
|
||||
<span class="rl-latest" id="recordsLatest"></span>
|
||||
</div>
|
||||
<!-- (Removed) Corp chip moved to the header toolbar above —
|
||||
see #companyChip / #companyManageBtn alongside New/Trace/Web. -->
|
||||
<div class="hdr-dropdown" data-dd>
|
||||
<button class="icon-btn" id="recordsMenuBtn" data-dd-trigger data-tooltip="저장된 작업 기록 열기">기록 ▾</button>
|
||||
<div class="hdr-menu hdr-menu-wide" id="recordsMenu" data-dd-menu>
|
||||
<div class="hdr-menu-label">작업 기록</div>
|
||||
<div class="select-wrap"><select id="chronicleRecordSel" title="열어볼 작업 기록 선택"></select></div>
|
||||
<button class="hdr-menu-item" id="openChronicleRecordBtn" data-tooltip="선택한 기록 열기">선택한 기록 열기</button>
|
||||
<button class="hdr-menu-item" id="refreshChronicleRecordsBtn" data-tooltip="기록 목록 다시 불러오기">기록 새로고침</button>
|
||||
<button class="hdr-menu-item" id="openDesignerBtn" data-tooltip="기록이 저장된 폴더 열기">기록 폴더 열기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- v2.2.71 — records-line 전체를 도구 ▾ 드롭다운 안으로 이동. 사이드바 본체엔 더 이상 자동 기록
|
||||
라벨/selector/기록 ▾ 가 노출되지 않는다. 모든 자동 기록 UI 는 도구 ▾ 메뉴 첫 섹션 (자동 기록) 에서 접근. -->
|
||||
|
||||
|
||||
<!--
|
||||
Company manage overlay. Uses the same overlay framework as the agent
|
||||
|
||||
+144
-9
@@ -359,10 +359,31 @@
|
||||
try { _renderWelcome(); } catch {}
|
||||
}
|
||||
});
|
||||
// v2.2.70 — 자동 기록 on/off 토글 상태. 도구 ▾ 메뉴의 토글 항목과 records-line 라벨
|
||||
// (자동 기록 / 자동 기록 (꺼짐)) 모두 이 변수에서 동기화.
|
||||
let chronicleAutoEnabled = true;
|
||||
function renderChronicleAutoToggle() {
|
||||
const btn = document.getElementById('chronicleAutoRecordBtn');
|
||||
if (btn) {
|
||||
btn.textContent = '자동 기록: ' + (chronicleAutoEnabled ? '켜짐' : '꺼짐');
|
||||
btn.classList.toggle('active', chronicleAutoEnabled);
|
||||
}
|
||||
// v2.2.71 — chronicleAutoStatus 는 이제 도구 메뉴 안에서 "최근 저장 기록" 표시 컨테이너.
|
||||
// textContent 를 직접 쓰면 자식(status-dot · recordsLatest)이 지워지므로 opacity / title 만 갱신.
|
||||
const statusEl = document.getElementById('chronicleAutoStatus');
|
||||
if (statusEl) {
|
||||
statusEl.style.opacity = chronicleAutoEnabled ? '' : '0.55';
|
||||
statusEl.title = chronicleAutoEnabled
|
||||
? '자동 기록 켜짐 — 의미 있는 대화가 자동 저장됨'
|
||||
: '자동 기록 꺼짐 — 자동 저장 안 됨 (수동 기록은 가능)';
|
||||
}
|
||||
}
|
||||
function syncRecordsLine() {
|
||||
if (!recordsLatest) return;
|
||||
const opt = chronicleRecordSel && chronicleRecordSel.value ? selText(chronicleRecordSel) : '';
|
||||
recordsLatest.textContent = opt ? '· ' + truncMid(opt, 38) : '';
|
||||
// OFF 면 최근 기록 라벨도 dim 처리해서 "지금은 저장 안 됨" 이 한눈에 보이게.
|
||||
recordsLatest.style.opacity = chronicleAutoEnabled ? '' : '0.55';
|
||||
}
|
||||
|
||||
// ── Ready-status bar (Engine / Model / Brain count / Context / Memory) ──
|
||||
@@ -403,6 +424,8 @@
|
||||
}
|
||||
|
||||
// ── Context-budget badge (직전 요청 기준) ────────────────────────────
|
||||
// Last LM Studio prediction stats — merged into the badge after the turn finishes.
|
||||
let lastLmStats = null;
|
||||
function renderCtxBadge(b) {
|
||||
if (!ctxBadge) return;
|
||||
if (!b || typeof b.inputTokens !== 'number') { ctxBadge.textContent = ''; ctxBadge.className = 'ctx-badge'; ctxBadge.title = ''; return; }
|
||||
@@ -420,8 +443,35 @@
|
||||
const warn = b.tight || b.systemTruncated;
|
||||
ctxBadge.textContent = parts.join(' · ');
|
||||
ctxBadge.className = 'ctx-badge' + (warn ? ' warn' : ' ok');
|
||||
// New turn starts → drop stale stats from the previous answer.
|
||||
lastLmStats = null;
|
||||
ctxBadge.title = `model: ${b.model || ''}${b.paramB != null ? ' (~' + b.paramB + 'B)' : ''}\n입력 ≈ ${b.inputTokens} tokens (시스템 ${b.systemTokens}, 기록 ${b.historyKept}개)\n출력 상한 ${b.maxOutputTokens} tokens / 유효 context window ${b.contextLength} tokens${b.cappedForSmallModel ? ' (작은 모델용 축소; 설정값 ' + b.nominalContextLength + ')' : ''}`;
|
||||
}
|
||||
function renderLmStudioStats(s) {
|
||||
if (!ctxBadge || !s) return;
|
||||
lastLmStats = s;
|
||||
const extra = [];
|
||||
if (typeof s.tokensPerSecond === 'number') extra.push(`${s.tokensPerSecond.toFixed(1)} tok/s`);
|
||||
if (typeof s.timeToFirstTokenSec === 'number') extra.push(`TTFT ${s.timeToFirstTokenSec.toFixed(2)}s`);
|
||||
// Speculative-decoding hit rate — shows whether the draft model is paying for itself.
|
||||
// A healthy ratio is ~60%+; below ~30% means the draft is mis-predicting and slowing things down.
|
||||
if (typeof s.draftTokensCount === 'number' && s.draftTokensCount > 0 && typeof s.acceptedDraftTokensCount === 'number') {
|
||||
const pct = Math.round((s.acceptedDraftTokensCount / s.draftTokensCount) * 100);
|
||||
extra.push(`spec ${pct}%`);
|
||||
}
|
||||
if (extra.length === 0) return;
|
||||
// Append to badge without clobbering — only if current badge text doesn't already include it.
|
||||
const current = ctxBadge.textContent || '';
|
||||
const tail = ' · ' + extra.join(' · ');
|
||||
if (!current.endsWith(tail)) ctxBadge.textContent = current + tail;
|
||||
if (ctxBadge.title) {
|
||||
ctxBadge.title += `\n\n[LM Studio]\n${extra.join(' · ')}`;
|
||||
if (typeof s.predictedTokensCount === 'number') ctxBadge.title += `\n출력 ${s.predictedTokensCount} tokens`;
|
||||
if (typeof s.totalTimeSec === 'number') ctxBadge.title += ` / 총 ${s.totalTimeSec.toFixed(2)}s`;
|
||||
if (s.draftModelKey) ctxBadge.title += `\ndraft: ${s.draftModelKey} (${s.acceptedDraftTokensCount || 0}/${s.draftTokensCount || 0} accepted)`;
|
||||
if (s.stopReason) ctxBadge.title += `\nstop: ${s.stopReason}`;
|
||||
}
|
||||
}
|
||||
if (readyBar) {
|
||||
readyBar.addEventListener('click', e => {
|
||||
const t = e.target;
|
||||
@@ -865,18 +915,39 @@
|
||||
try { _renderWelcome(); } catch {}
|
||||
break;
|
||||
}
|
||||
case 'brainProfiles':
|
||||
case 'brainProfiles': {
|
||||
// 방어: profiles 가 비어/null 로 오면 기존 옵션 보존. 잘못된 상태로 dropdown 을 비워
|
||||
// "+ Add New Brain..." 만 남기는 회귀를 막는다 (v2.2.66 fix).
|
||||
const profilesArr = (msg.value && Array.isArray(msg.value.profiles)) ? msg.value.profiles : [];
|
||||
const activeId = msg.value && msg.value.activeBrainId;
|
||||
if (profilesArr.length === 0) {
|
||||
console.warn('[Astra] brainProfiles message had empty profiles list — preserving existing dropdown.');
|
||||
break;
|
||||
}
|
||||
brainSel.innerHTML = '';
|
||||
msg.value.profiles.forEach(p => {
|
||||
const o = document.createElement('option'); o.value = p.id; o.innerText = p.name;
|
||||
if (p.id === msg.value.activeBrainId) o.selected = true;
|
||||
profilesArr.forEach(p => {
|
||||
const o = document.createElement('option');
|
||||
o.value = p.id;
|
||||
o.innerText = p.name;
|
||||
brainSel.appendChild(o);
|
||||
});
|
||||
const addOpt = document.createElement('option');
|
||||
addOpt.value = 'new'; addOpt.innerText = '+ Add New Brain...';
|
||||
addOpt.value = 'new';
|
||||
addOpt.innerText = '+ Add New Brain...';
|
||||
brainSel.appendChild(addOpt);
|
||||
// v2.2.67 — 옵션을 모두 넣은 *후* selection 을 적용한다. appendChild 이전에 `o.selected = true`
|
||||
// 를 거는 방식은 일부 Chromium webview 에서 무시되어 dropdown 이 마지막 옵션('+ Add New Brain...')
|
||||
// 에 머무는 회귀가 발생했다. brainSel.value 로 한 번에 잡으면 이전에 'new' 로 굳어있던 값도 확실히 덮어쓴다.
|
||||
if (activeId && profilesArr.some(p => p.id === activeId)) {
|
||||
brainSel.value = activeId;
|
||||
} else {
|
||||
brainSel.value = profilesArr[0].id;
|
||||
}
|
||||
// 다음에 사용자가 '+ Add New Brain...' 을 클릭하고 취소했을 때 복원할 "이전 유효 선택" 기억.
|
||||
brainSel.dataset.lastSelected = brainSel.value;
|
||||
syncContextBar();
|
||||
break;
|
||||
}
|
||||
case 'sessionList':
|
||||
historyList.innerHTML = '';
|
||||
msg.value.forEach(s => {
|
||||
@@ -912,6 +983,9 @@
|
||||
case 'contextBudget':
|
||||
renderCtxBadge(msg.value);
|
||||
break;
|
||||
case 'lmStudioStats':
|
||||
renderLmStudioStats(msg.value);
|
||||
break;
|
||||
case 'usedScope': {
|
||||
let target = streamBody && streamBody._parent;
|
||||
if (!target) {
|
||||
@@ -924,6 +998,13 @@
|
||||
case 'lessonCandidate':
|
||||
renderLessonCandidate(msg.value || {});
|
||||
break;
|
||||
case 'chronicleAutoRecordStatus': {
|
||||
// v2.2.70 — 자동 기록 on/off 상태 푸시. 도구 메뉴 토글과 records-line 라벨 갱신.
|
||||
chronicleAutoEnabled = !!(msg.value && msg.value.enabled);
|
||||
renderChronicleAutoToggle();
|
||||
syncRecordsLine();
|
||||
break;
|
||||
}
|
||||
case 'autoContinue':
|
||||
statusLabel.innerText = msg.value; thinkingBar.classList.add('active');
|
||||
if (msg.value.includes('Analyzing')) setStep('analyze');
|
||||
@@ -931,6 +1012,23 @@
|
||||
if (msg.value.includes('Executing')) setStep('execute');
|
||||
setTimeout(() => { thinkingBar.classList.remove('active'); }, 3000);
|
||||
break;
|
||||
case 'workflowStage': {
|
||||
// [5-stage pipeline] 채팅 본문에 단계 메시지를 흘리는 대신, 사이드바 상단의
|
||||
// 얇은 status strip 에만 한 줄로 표시한다. done=true 면 strip 을 닫는다.
|
||||
const v = msg.value || {};
|
||||
const step = String(v.step || '');
|
||||
const text = String(v.message || '');
|
||||
const done = !!v.done;
|
||||
if (done || (!step && !text)) {
|
||||
statusLabel.innerText = '';
|
||||
thinkingBar.classList.remove('active');
|
||||
} else {
|
||||
const compact = step && text ? step + ' · ' + text : (step || text);
|
||||
statusLabel.innerText = compact;
|
||||
thinkingBar.classList.add('active');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'agentsList':
|
||||
agentSel.innerHTML = '<option value="none">No Agent</option>';
|
||||
msg.value.forEach(a => {
|
||||
@@ -1655,18 +1753,44 @@
|
||||
btn.setAttribute('data-tooltip', secondBrainTraceDebug ? 'Second Brain Debug JSON: On' : 'Second Brain Debug JSON: Off');
|
||||
saveUiState();
|
||||
};
|
||||
// v2.2.70 — 자동 기록 토글. 클릭 시 즉시 서버에 새 상태 전송. 서버가 config 저장 후
|
||||
// chronicleAutoRecordStatus 메시지로 다시 푸시 → renderChronicleAutoToggle 가 UI 동기화.
|
||||
const _chronicleAutoBtn = document.getElementById('chronicleAutoRecordBtn');
|
||||
if (_chronicleAutoBtn) {
|
||||
_chronicleAutoBtn.onclick = () => {
|
||||
const next = !chronicleAutoEnabled;
|
||||
// 낙관적 갱신 — 서버 응답 전에도 즉시 라벨이 바뀌어 클릭 반응성이 좋다. 응답이 오면 다시 정정.
|
||||
chronicleAutoEnabled = next;
|
||||
renderChronicleAutoToggle();
|
||||
syncRecordsLine();
|
||||
vscode.postMessage({ type: 'setChronicleAutoRecord', enabled: next });
|
||||
};
|
||||
}
|
||||
|
||||
const syncBrain = () => { Sound.play(550, 'sine', 0.1); vscode.postMessage({ type: 'syncBrain' }); };
|
||||
document.getElementById('brainBtn').onclick = syncBrain;
|
||||
saveWikiRawBtn.onclick = () => vscode.postMessage({ type: 'saveWikiRaw' });
|
||||
addBrainBtn.onclick = () => vscode.postMessage({ type: 'addBrain' });
|
||||
// v2.2.67 — 만약 dropdown 이 'new' 상태로 굳어있어도 수정/삭제는 직전 유효 선택(또는 첫 실제 옵션)
|
||||
// 을 기준으로 동작하도록 폴백. 이전엔 'new' 인 순간 그냥 early-return 해서 "수정 안 됨" 버그 발생.
|
||||
function _resolveActiveBrainId() {
|
||||
if (brainSel.value && brainSel.value !== 'new') return brainSel.value;
|
||||
const last = brainSel.dataset.lastSelected;
|
||||
if (last && last !== 'new') return last;
|
||||
for (const opt of brainSel.options) {
|
||||
if (opt.value && opt.value !== 'new') return opt.value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
editBrainBtn.onclick = () => {
|
||||
if (!brainSel.value || brainSel.value === 'new') return;
|
||||
vscode.postMessage({ type: 'editBrain', id: brainSel.value });
|
||||
const id = _resolveActiveBrainId();
|
||||
if (!id) return;
|
||||
vscode.postMessage({ type: 'editBrain', id });
|
||||
};
|
||||
deleteBrainBtn.onclick = () => {
|
||||
if (!brainSel.value || brainSel.value === 'new') return;
|
||||
vscode.postMessage({ type: 'deleteBrain', id: brainSel.value });
|
||||
const id = _resolveActiveBrainId();
|
||||
if (!id) return;
|
||||
vscode.postMessage({ type: 'deleteBrain', id });
|
||||
};
|
||||
// (inputSyncBtn removed — Sync Knowledge is reachable via the top brainBtn / Tools menu.)
|
||||
document.getElementById('historyBtn').onclick = () => vscode.postMessage({ type: 'getSessions' });
|
||||
@@ -1702,8 +1826,18 @@
|
||||
}
|
||||
brainSel.onchange = () => {
|
||||
if (brainSel.value === 'new') {
|
||||
// v2.2.67 — '+ Add New Brain...' 클릭 시 addBrain 메시지를 보내고, 사용자가 폴더 선택을
|
||||
// 취소해도 dropdown 이 'new' 로 굳지 않도록 즉시 직전 유효 선택으로 되돌린다. 추가가 실제로
|
||||
// 성공하면 _postBrainProfiles → brainProfiles 메시지가 새 brain 으로 다시 옮긴다.
|
||||
// 이 복원이 없으면 brainSel.value === 'new' 상태가 유지되어 수정/삭제 버튼이 early-return 으로 죽는다.
|
||||
const prev = brainSel.dataset.lastSelected;
|
||||
vscode.postMessage({ type: 'addBrain' });
|
||||
if (prev && prev !== 'new') {
|
||||
brainSel.value = prev;
|
||||
syncContextBar();
|
||||
}
|
||||
} else {
|
||||
brainSel.dataset.lastSelected = brainSel.value;
|
||||
vscode.postMessage({ type: 'setBrainProfile', id: brainSel.value });
|
||||
}
|
||||
};
|
||||
@@ -1868,6 +2002,7 @@
|
||||
vscode.postMessage({ type: 'getAgents' });
|
||||
vscode.postMessage({ type: 'getChronicleProjects' });
|
||||
vscode.postMessage({ type: 'getChronicleRecords' });
|
||||
vscode.postMessage({ type: 'getChronicleAutoRecord' });
|
||||
vscode.postMessage({ type: 'getKnowledgeMix' });
|
||||
vscode.postMessage({ type: 'getArchitectureStatus' });
|
||||
vscode.postMessage({ type: 'getCompanyStatus' });
|
||||
|
||||
Reference in New Issue
Block a user