diff --git a/agent/protocol/agent_stream.py b/agent/protocol/agent_stream.py
index 19ca33b..d1b5283 100644
--- a/agent/protocol/agent_stream.py
+++ b/agent/protocol/agent_stream.py
@@ -574,7 +574,7 @@ class AgentStreamExecutor:
raise Exception(f"{error_msg} (Status: {status_code}, Code: {error_code}, Type: {error_type})")
# Parse chunk
- if isinstance(chunk, dict) and "choices" in chunk:
+ if isinstance(chunk, dict) and chunk.get("choices"):
choice = chunk["choices"][0]
delta = choice.get("delta", {})
diff --git a/bridge/agent_initializer.py b/bridge/agent_initializer.py
index b481d83..5154eac 100644
--- a/bridge/agent_initializer.py
+++ b/bridge/agent_initializer.py
@@ -140,13 +140,19 @@ class AgentInitializer:
try:
from agent.memory import get_conversation_store
store = get_conversation_store()
+ # On restore, load at most min(10, max_turns // 2) turns so that
+ # a long-running session does not immediately fill the context window
+ # after a restart. The full max_turns budget is reserved for the
+ # live conversation that follows.
max_turns = conf().get("agent_max_context_turns", 30)
- saved = store.load_messages(session_id, max_turns=max_turns)
+ restore_turns = min(6, max(1, max_turns // 3))
+ saved = store.load_messages(session_id, max_turns=restore_turns)
if saved:
with agent.messages_lock:
agent.messages = saved
logger.info(
- f"[AgentInitializer] Restored {len(saved)} messages for session={session_id}"
+ f"[AgentInitializer] Restored {len(saved)} messages "
+ f"({restore_turns} turns cap) for session={session_id}"
)
except Exception as e:
logger.warning(
diff --git a/channel/web/chat.html b/channel/web/chat.html
index 6f28ebc..904eb45 100644
--- a/channel/web/chat.html
+++ b/channel/web/chat.html
@@ -43,8 +43,17 @@
}
+
+
-
+
diff --git a/channel/web/static/js/console.js b/channel/web/static/js/console.js
index d4a0f35..7320d7e 100644
--- a/channel/web/static/js/console.js
+++ b/channel/web/static/js/console.js
@@ -284,7 +284,11 @@ const sendBtn = document.getElementById('send-btn');
const messagesDiv = document.getElementById('chat-messages');
chatInput.addEventListener('compositionstart', () => { isComposing = true; });
-chatInput.addEventListener('compositionend', () => { isComposing = false; });
+// Safari fires compositionend *before* the confirming keydown event, so if we
+// reset isComposing synchronously the keydown handler sees !isComposing and
+// sends the message prematurely. A setTimeout(0) defers the reset until after
+// keydown has been processed, fixing the Safari IME Enter-to-confirm bug.
+chatInput.addEventListener('compositionend', () => { setTimeout(() => { isComposing = false; }, 0); });
chatInput.addEventListener('input', function() {
this.style.height = '42px';
@@ -684,7 +688,9 @@ function loadHistory(page) {
historyPage = page;
if (isFirstLoad) {
- scrollChatToBottom();
+ // Use requestAnimationFrame to ensure the DOM has fully rendered
+ // before scrolling, otherwise scrollHeight may not reflect new content.
+ requestAnimationFrame(() => scrollChatToBottom());
} else {
// Restore scroll position so loading older messages doesn't jump the view
messagesDiv.scrollTop = messagesDiv.scrollHeight - prevScrollHeight;
@@ -1102,3 +1108,11 @@ applyTheme();
applyI18n();
document.getElementById('sidebar-version').textContent = `CowAgent ${APP_VERSION}`;
chatInput.focus();
+
+// Re-enable color transition AFTER first paint so the theme applied in
+// doesn't produce an animated flash on load. The class is missing from the
+// body initially; adding it here means transitions only fire on user-triggered
+// theme toggles, not on page load.
+requestAnimationFrame(() => {
+ document.body.classList.add('transition-colors', 'duration-200');
+});