feat: handle auto-compaction side effects + /compact command

The agent's run_conversation() already triggers context compression
internally, but the WebUI was unaware of the side effects:

1. Session ID rotation: compression creates a new session_id inside
   the agent. The WebUI kept writing to the old session file, causing
   silent data loss. Fix: detect agent.session_id mismatch after
   run_conversation(), rename the session file, and update in-memory
   caches.

2. No user notification: compression was invisible. Fix: emit a
   'compressed' SSE event when compression is detected. Frontend shows
   a system message and toast.

3. No manual control: Fix: add /compact slash command that sends a
   message to the agent requesting context compression. Shows in the
   autocomplete dropdown.

Detection works two ways:
- agent.session_id != original session_id (ID rotation)
- agent.context_compressor.compression_count > 0 (compressor state)

Closes #90

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nathan Esquenazi
2026-04-04 18:46:34 -07:00
parent 2e7ce0a341
commit 429a0ea228
3 changed files with 56 additions and 0 deletions

View File

@@ -5,6 +5,7 @@
const COMMANDS=[
{name:'help', desc:'List available commands', fn:cmdHelp},
{name:'clear', desc:'Clear conversation messages', fn:cmdClear},
{name:'compact', desc:'Compress conversation context', fn:cmdCompact},
{name:'model', desc:'Switch model (e.g. /model gpt-4o)', fn:cmdModel, arg:'model_name'},
{name:'workspace', desc:'Switch workspace by name', fn:cmdWorkspace, arg:'name'},
{name:'new', desc:'Start a new chat session', fn:cmdNew},
@@ -99,6 +100,15 @@ async function cmdNew(){
showToast('New session created');
}
function cmdCompact(){
// Send as a regular message to the agent -- the agent's run_conversation
// preflight will detect the high token count and trigger _compress_context.
// We send a user message so it appears in the conversation.
$('msg').value='Please compress and summarize the conversation context to free up space.';
send();
showToast('Requesting context compression...');
}
async function cmdUsage(){
const next=!window._showTokenUsage;
window._showTokenUsage=next;

View File

@@ -177,6 +177,17 @@ async function send(){
renderSessionList();setBusy(false);setStatus('');
});
source.addEventListener('compressed',e=>{
// Context was auto-compressed during this turn -- show a system message
if(!S.session||S.session.session_id!==activeSid) return;
try{
const d=JSON.parse(e.data);
const sysMsg={role:'assistant',content:'*[Context was auto-compressed to continue the conversation]*'};
S.messages.push(sysMsg);
showToast(d.message||'Context compressed');
}catch(err){}
});
source.addEventListener('apperror',e=>{
// Application-level error sent explicitly by the server (rate limit, crash, etc.)
// This is distinct from the SSE network 'error' event below.