v4.1.0: 메인챗 툴/시스템프롬프트 오버헤드 66% 감소 + 실시간 컨텍스트 게이지

- 2개월 tool_audit.log 실사용 확인 후 미사용 에이전트 툴(start_task 등)과
  포트 9222 연결실패로 고장난 브라우저 툴을 메인챗에서 제외
- 날씨/법률/논문 툴을 스킬+키워드 조건부로 전환, dental/mind 전용 MCP DB
  24개를 해당 앱 세션(dental_/mn_)에만 스코핑, weather/lawyer 앱 세션은
  스킬·키워드와 무관하게 항상 포함되도록 별도 처리
- 위 변경으로 메인챗 고정 오버헤드 24,439 → 8,169 토큰(-66%) 실측 확인
- tool_overhead SSE 이벤트를 모델 호출 전에 전송해 세션 게이지가 중단된
  턴에서도 툴 스키마+시스템프롬프트 크기를 반영하도록 수정 (app.js)
- 부수 수정: heartbeat 세션을 lastMainSessionId 폴백에서 완전 격리(다른
  세션 기록 오염 방지), webSearch 빈 결과를 실패로 오판하던 버그 수정

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
kim
2026-07-13 01:09:27 +09:00
co-authored by Claude Sonnet 5
parent a11e554e88
commit b969c672b0
3 changed files with 100 additions and 18 deletions
+14 -1
View File
@@ -1893,6 +1893,19 @@ async function sendChat(queuedMessage = null) {
break;
}
// Sent before the model call, so it survives a turn that gets aborted before
// a real 'usage' event (with prompt_eval_count) arrives — lets the fallback
// estimate in renderSessionsList include tool-schema overhead instead of
// silently ignoring it.
case 'tool_overhead': {
const overhead = Number(event.tokens || 0);
if (overhead > 0) {
const idx = chatSessions.findIndex(s => s.id === activeChatSessionId);
if (idx !== -1) { chatSessions[idx].toolOverheadTokens = overhead; saveChatSessions(); }
}
break;
}
case 'done':
finalReply = event.reply || '';
if (finalReply) partialContent = finalReply;
@@ -4452,7 +4465,7 @@ function renderSessionsList() {
<div style="display:flex;align-items:center;gap:6px;margin-top:4px">
${s.automated ? '<span class="session-auto-badge">Auto</span>' : ''}
<span class="badge badge-queued">${(s.history || []).length} msgs</span>
${(() => { const fmtTok = n => n>=1e6?`${(n/1e6).toFixed(1)}M`:n>=1000?`${Math.round(n/1000)}k`:`${n}`; const ct = Number(s.contextTokens||0) || Math.round((s.history||[]).reduce((a,m)=>a+(m.content||'').length,0)/4); const mx = _appCtxMax; if (!ct) return ''; if (mx > 0) { const pct = Math.min(100, Math.round(ct/mx*100)); const col = pct>=80?'var(--err)':pct>=50?'var(--warn)':'var(--muted)'; return `<span style="font-size:10px;color:${col}">${fmtTok(ct)}/${fmtTok(mx)}</span>`; } return `<span style="font-size:10px;color:var(--muted)">${fmtTok(ct)} tok</span>`; })()}
${(() => { const fmtTok = n => n>=1e6?`${(n/1e6).toFixed(1)}M`:n>=1000?`${Math.round(n/1000)}k`:`${n}`; const real = Number(s.contextTokens||0); const ct = real || (Number(s.toolOverheadTokens||0) + Math.round((s.history||[]).reduce((a,m)=>a+(m.content||'').length,0)/4)); const mx = _appCtxMax; if (!ct) return ''; if (mx > 0) { const pct = Math.min(100, Math.round(ct/mx*100)); const col = pct>=80?'var(--err)':pct>=50?'var(--warn)':'var(--muted)'; return `<span style="font-size:10px;color:${col}">${real?'':'~'}${fmtTok(ct)}/${fmtTok(mx)}</span>`; } return `<span style="font-size:10px;color:var(--muted)">${real?'':'~'}${fmtTok(ct)} tok</span>`; })()}
<span style="color:var(--muted);font-size:10px">${timeAgo(s.updatedAt || s.createdAt)}</span>
</div>
</div>