fix: correct tool call card rendering on session load after context compaction (#408)
* fix: correct tool call card rendering on session load Two bugs caused duplicate/incorrect tool call cards when loading sessions (especially after context compaction): 1. loadSession() sanitized messages (B9 filter) but did NOT update the session-level tool_calls array's assistant_msg_idx references. Since compact() returns only sanitized messages and recomputes tool_calls with indices into the compacted array, the original assistant_msg_idx values became stale/misaligned. 2. loadSession() then assigned the broken session-level tool_calls directly to S.toolCalls. This prevented renderMessages()'s fallback path (which derives tool_calls from per-message tool_calls using correct sanitized-array indices) from ever running. Fix: - Keep full sanitization loop with index remapping for session-level tool_calls (in case they're needed by other code paths). - Instead of assigning broken session-level tool_calls to S.toolCalls, set S.toolCalls=[] so renderMessages() uses the fallback derivation from per-message tool_calls, which already have correct indices. * test: add 8 regression tests for issue #401 tool call index remapping * docs: v0.50.29 release — version badge and CHANGELOG --------- Co-authored-by: Frank Song <franksong2702@gmail.com> Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
@@ -535,7 +535,7 @@
|
||||
<div class="settings-section-title">System</div>
|
||||
<div class="settings-section-meta">Instance version and access controls.</div>
|
||||
</div>
|
||||
<span class="settings-version-badge">v0.50.28</span>
|
||||
<span class="settings-version-badge">v0.50.29</span>
|
||||
</div>
|
||||
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
||||
<label for="settingsPassword" data-i18n="settings_label_password">Access Password</label>
|
||||
|
||||
@@ -42,6 +42,35 @@ async function loadSession(sid){
|
||||
S.session=data.session;
|
||||
S.lastUsage={...(data.session.last_usage||{})};
|
||||
localStorage.setItem('hermes-webui-session',S.session.session_id);
|
||||
// B9: sanitize empty assistant messages (PR #402) — build index map to remap
|
||||
// session-level tool_calls.assistant_msg_idx to the new sanitized positions.
|
||||
const allMsgs = data.session.messages || [];
|
||||
const sanitized = [];
|
||||
const origIdxToSanitizedIdx = {};
|
||||
let lastKeptAsstIdx = -1;
|
||||
for (let i = 0; i < allMsgs.length; i++) {
|
||||
const m = allMsgs[i];
|
||||
if (!m || !m.role) continue;
|
||||
if (m.role === 'tool') continue;
|
||||
if (m.role === 'assistant') {
|
||||
let c = m.content || '';
|
||||
if (Array.isArray(c)) c = c.filter(p => p && p.type === 'text').map(p => p.text || '').join('');
|
||||
if (!String(c).trim().length) { continue; } // empty assistant — skip
|
||||
lastKeptAsstIdx = sanitized.length;
|
||||
}
|
||||
origIdxToSanitizedIdx[i] = sanitized.length;
|
||||
sanitized.push(m);
|
||||
}
|
||||
if (data.session.tool_calls && data.session.tool_calls.length) {
|
||||
for (const tc of data.session.tool_calls) {
|
||||
if (!tc || tc.assistant_msg_idx === undefined) continue;
|
||||
const origIdx = tc.assistant_msg_idx;
|
||||
tc.assistant_msg_idx = (origIdx in origIdxToSanitizedIdx)
|
||||
? origIdxToSanitizedIdx[origIdx]
|
||||
: (lastKeptAsstIdx >= 0 ? lastKeptAsstIdx : -1);
|
||||
}
|
||||
}
|
||||
data.session.messages = sanitized;
|
||||
const activeStreamId=data.session.active_stream_id||null;
|
||||
if(!INFLIGHT[sid]&&activeStreamId&&typeof loadInflightState==='function'){
|
||||
const stored=loadInflightState(sid, activeStreamId);
|
||||
@@ -54,9 +83,6 @@ async function loadSession(sid){
|
||||
};
|
||||
}
|
||||
}
|
||||
// Keep raw session.messages intact so side panels (e.g. Todos) can still
|
||||
// reconstruct state from tool outputs after reload. Visible transcript rows
|
||||
// are filtered later by renderMessages().
|
||||
if(INFLIGHT[sid]){
|
||||
S.messages=INFLIGHT[sid].messages;
|
||||
S.toolCalls=(INFLIGHT[sid].toolCalls||[]);
|
||||
@@ -80,7 +106,11 @@ async function loadSession(sid){
|
||||
S.messages=data.session.messages||[];
|
||||
const pendingMsg=typeof getPendingSessionMessage==='function'?getPendingSessionMessage(data.session):null;
|
||||
if(pendingMsg) S.messages.push(pendingMsg);
|
||||
S.toolCalls=(data.session.tool_calls||[]).map(tc=>({...tc,done:true}));
|
||||
// Fix (PR #402): do NOT pre-fill S.toolCalls from session-level tool_calls —
|
||||
// those have stale assistant_msg_idx values after B9 sanitization. Instead,
|
||||
// set S.toolCalls=[] and let renderMessages() derive them from per-message
|
||||
// tool_calls (which already have correct sanitized-array indices).
|
||||
S.toolCalls=[];
|
||||
clearLiveToolCards();
|
||||
if(activeStreamId){
|
||||
S.busy=true;
|
||||
|
||||
Reference in New Issue
Block a user