diff --git a/static/ui.js b/static/ui.js index d7b6b836..fe13e0c1 100644 --- a/static/ui.js +++ b/static/ui.js @@ -12084,6 +12084,7 @@ function _anchorSceneNodeForRow(row, opts){ if(!node) return null; node.setAttribute('data-anchor-scene-row','1'); node.setAttribute('data-anchor-row-id',String(row.row_id||row.local_id||'')); + if(row.local_id) node.setAttribute('data-anchor-local-id',String(row.local_id)); node.setAttribute('data-anchor-row-role',String(row.role||'activity')); node.setAttribute('data-anchor-source-event-type',String(row.source_event_type||'')); return node; @@ -12153,6 +12154,7 @@ function _anchorSceneTransparentNodeForRow(row, opts){ if(settled) node.setAttribute('data-anchor-settled-scene-row','1'); if(live) node.setAttribute('data-anchor-live-scene-row','1'); node.setAttribute('data-anchor-row-id',String(row.row_id||row.local_id||'')); + if(row.local_id) node.setAttribute('data-anchor-local-id',String(row.local_id)); node.setAttribute('data-anchor-row-role',String(row.role||'activity')); node.setAttribute('data-anchor-source-event-type',String(row.source_event_type||'')); if(opts&&opts.streamId) node.setAttribute('data-anchor-stream-id',String(opts.streamId)); @@ -12366,18 +12368,14 @@ function _liveAnchorReasoningRowForFallback(turn, opts){ if(!turn||!blocks||!blocks.querySelectorAll) return null; const streamId=String(opts.streamId||S.activeStreamId||''); const sessionId=String(opts.sessionId||(S.session&&S.session.session_id)||''); - const segmentSeq=opts.segmentSeq!==undefined&&opts.segmentSeq!==null - ? String(opts.segmentSeq).trim() - : ''; const localId=String(opts.anchorReasoningLocalId||opts.localId||'').trim(); - const rowKey=localId||(streamId&&segmentSeq?`live-reasoning:${streamId}:${segmentSeq}`:''); - if(!rowKey) return null; + if(!localId) return null; const rows=blocks.querySelectorAll( - '[data-anchor-scene-row="1"][data-anchor-row-id]' + '[data-anchor-scene-row="1"][data-anchor-local-id]' ); for(const row of Array.from(rows)){ - const anchorRowId=String(row.getAttribute&&row.getAttribute('data-anchor-row-id')||''); - if(anchorRowId!==rowKey&&!anchorRowId.startsWith(`${rowKey}:`)) continue; + const anchorLocalId=String(row.getAttribute&&row.getAttribute('data-anchor-local-id')||''); + if(anchorLocalId!==localId) continue; const rowRole=String(row.getAttribute&&row.getAttribute('data-anchor-row-role')||''); const rowSource=String(row.getAttribute&&row.getAttribute('data-anchor-source-event-type')||''); if(rowRole!=='thinking'&&rowSource!=='reasoning') continue; diff --git a/tests/test_issue5720_reasoning_owner.py b/tests/test_issue5720_reasoning_owner.py index 4bd0b31c..8a0223e5 100644 --- a/tests/test_issue5720_reasoning_owner.py +++ b/tests/test_issue5720_reasoning_owner.py @@ -175,6 +175,9 @@ def test_later_deferred_anchor_paint_updates_exact_reasoning_owner(activity_mode assert exact["second_before"] == "Second draft" assert exact["second_after"] == "Second updated" assert exact["legacy_after"] == "Legacy unkeyed" + assert exact["anchor_run_id"] == "run-5720" + assert exact["second_row_id"] == "run-5720:2" + assert exact["second_local_id"] == "live-reasoning:stream-1:2" assert exact["live_reasoning_rows"] == 2 assert exact["fallback_rows"] == 0 @@ -548,31 +551,40 @@ function reasoningText(row){ const pre=row.querySelector&&row.querySelector('pre'); return pre?pre.textContent:row.textContent; } -function appendSyntheticAnchorReasoningRow(rowId, text){ - const row=_thinkingActivityNode(text, false, rowId); - row.setAttribute('data-anchor-scene-row','1'); - row.setAttribute('data-anchor-row-id',rowId); - row.setAttribute('data-anchor-row-role','thinking'); - row.setAttribute('data-anchor-source-event-type','reasoning'); - row.setAttribute('data-anchor-stream-id','stream-1'); - row.setAttribute('data-session-id','sid-1'); - if(isTransparentStream()){ - _decorateTransparentEventRow(row,{ - type:'thinking', - text, - preview:text, - live:true, - segmentSeq:'2', - burstId:'0', - }); - row.setAttribute('data-anchor-live-scene-row','1'); - row.setAttribute('data-live-stream-owned','1'); - turn.appendChild(row); - }else{ - const group=ensureActivityGroup(turn,{activityKey:'live:stream-1'}); - const list=_toolWorklogListEl(group); - (list||group||turn).appendChild(row); - } +function applyProductionAnchorEvent(sourceEventType, data){ + const registry=window._liveAnchorRegistries&&window._liveAnchorRegistries.get('stream-1'); + if(!registry) throw new Error('missing live anchor registry'); + window.HermesAssistantTurnAnchors.applyAssistantTurnAnchorSourceEvent( + registry, + { + source_event_type:sourceEventType, + run_id:'run-5720', + stream_id:'stream-1', + ...data, + }, + { + session_id:'sid-1', + stream_id:'stream-1', + run_id:'run-5720', + } + ); + return registry; +} +function appendProductionAnchorReasoningRow(localId, text, seq){ + applyProductionAnchorEvent('reasoning',{ + local_id:localId, + seq, + text, + activitySegmentSeq:seq, + activityBurstId:0, + }); + window._renderLiveAnchorActivitySceneForStream('stream-1','sid-1',{ + mode:process.env.ISSUE5720_ACTIVITY_MODE||'transparent_stream', + }); + const row=anchorReasoningRows().find(candidate=> + candidate.getAttribute('data-anchor-row-id')===`run-5720:${seq}` + ); + if(!row) throw new Error('production anchor reasoning row not rendered'); return row; } function appendUnkeyedLegacyReasonRow(text){ @@ -610,7 +622,11 @@ if(process.env.ISSUE5720_MULTI_SEGMENT_FALLBACK==='1'){ const firstReasoningRow=anchorReasoningRows()[0]||null; const firstBefore=reasoningText(firstReasoningRow); const secondId='live-reasoning:stream-1:2'; - const secondReasoningRow=appendSyntheticAnchorReasoningRow(`${secondId}:reasoning:1`,'Second draft'); + const registry=applyProductionAnchorEvent('interim_assistant',{ + seq:1, + text:'', + }); + const secondReasoningRow=appendProductionAnchorReasoningRow(secondId,'Second draft',2); const secondBefore=reasoningText(secondReasoningRow); const legacyRow=appendUnkeyedLegacyReasonRow('Legacy unkeyed'); appendThinking('Second updated',{ @@ -622,10 +638,12 @@ if(process.env.ISSUE5720_MULTI_SEGMENT_FALLBACK==='1'){ burstId:0, }); multiSegmentExactFallback={ + anchor_run_id:registry&®istry.anchor&®istry.anchor.identity&®istry.anchor.identity.run_id, first_row_id:firstReasoningRow&&firstReasoningRow.getAttribute('data-anchor-row-id'), first_before:firstBefore, first_after:reasoningText(firstReasoningRow), second_row_id:secondReasoningRow.getAttribute('data-anchor-row-id'), + second_local_id:secondReasoningRow.getAttribute('data-anchor-local-id'), second_before:secondBefore, second_after:reasoningText(secondReasoningRow), legacy_after:reasoningText(legacyRow),