fix(group): preserve peer identity when reloading history

This commit is contained in:
red person
2026-10-28 14:27:17 +03:00
parent 57a1859f08
commit 4795c8d462
4 changed files with 85 additions and 5 deletions
+67
View File
@@ -0,0 +1,67 @@
"""Peer injections retain their context role but hydrate as the named agent."""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
from tests.helpers.js_modules import js_function_source
ROOT = Path(__file__).resolve().parents[1]
def _node(script, *args):
result = subprocess.run(['node', '--input-type=module', '-e', script, *map(json.dumps, args)],
cwd=ROOT, capture_output=True, text=True, timeout=15)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
@pytest.mark.skipif(not shutil.which('node'), reason='node not installed')
@pytest.mark.parametrize('mode', ['parallel', 'round-robin'])
def test_peer_sync_persists_explicit_identity(mode):
source = (ROOT / 'static/js/group.js').read_text()
functions = '\n'.join(js_function_source(name, source) for name in ['_syncAllResponses', '_sendRoundRobin'])
data = _node(r'''
const source=JSON.parse(process.argv[1]),mode=JSON.parse(process.argv[2]);
const _models=[{mid:'model-a',display:'Model A',_groupName:'Persona A'}, {mid:'model-b',display:'Model B'}];
const _participantSessions=['a','b'],API_BASE='http://app',sent=[];
let _abortControllers=[];
const uiModule={scrollHistory(){}};
function _createGroupBubble(model){return {dataset:{raw:'Reply from '+model.mid}};}
async function _streamToHolder(){} function _saveState(){}
async function fetch(url,options){sent.push({url,...JSON.parse(options.body)});return {ok:true};}
eval(source+'\nglobalThis.runSync=async()=> mode==="parallel" ? _syncAllResponses(_models.map(_createGroupBubble)) : _sendRoundRobin("Hi",null);');
await globalThis.runSync();console.log(JSON.stringify(sent));
''', functions, mode)
assert len(data) == 2
for item in data:
message = item['messages'][0]
assert message['role'] == 'user' # Model context must retain peer attribution.
peer = 'model-b' if item['url'].endswith('/a/inject_messages') else 'model-a'
name = 'Model B' if peer == 'model-b' else 'Persona A'
assert message['content'] == f'[{name}]: Reply from {peer}'
assert message['metadata'] == {'group_peer': True, 'group_model': name, 'model': peer}
@pytest.mark.skipif(not shutil.which('node'), reason='node not installed')
@pytest.mark.parametrize('role,content,meta,expected_role,expected_content', [
('user', '[Persona A]: Reply', {'group_peer': True, 'group_model': 'Persona A'}, 'assistant', 'Reply'),
('user', '[Persona A]: Human text', {}, 'user', '[Persona A]: Human text'),
('user', 'Human text', {}, 'user', 'Human text'),
('assistant', 'Parent reply', {'group_model': 'Persona A'}, 'assistant', 'Parent reply'),
('user', 'No matching prefix', {'group_peer': True, 'group_model': 'Persona A'}, 'user', 'No matching prefix'),
('user', '[3]: Human text', {'group_peer': True, 'group_model': 3}, 'user', '[3]: Human text'),
('user', '[Persona A]: Human text', {'group_peer': False, 'group_model': 'Persona A'}, 'user', '[Persona A]: Human text'),
])
def test_history_uses_explicit_peer_identity_only(role, content, meta, expected_role, expected_content):
source = js_function_source('_renderHistoryMessage', (ROOT / 'static/js/sessions.js').read_text())
result = _node(r'''
const source=JSON.parse(process.argv[1]),message=JSON.parse(process.argv[2]);
function _displayHistoryContent(text){return text;}function _stripUserVisionBlocks(text){return text;}
function _addHistoryMessageWithFullRenderer(role,content,model,metadata){return {role,content,metadata};}
const run=eval('('+source+')');console.log(JSON.stringify(run(message,'session-model')));
''', source, {'role': role, 'content': content, 'metadata': meta})
assert result['role'] == expected_role
assert result['content'] == expected_content
assert result['metadata'] == {**meta, '_fromHistory': True}