Files
hermes-webui/tests/test_steer_terminal_settlement.py

69 lines
3.2 KiB
Python

"""All terminal worker exits must close Steer and preserve accepted guidance."""
import pytest
from api import config
from tests import test_steer_worker_boundaries as boundaries
worker_scene = boundaries.worker_scene
@pytest.mark.parametrize('outcome', ['success', 'returned-error', 'exception'])
def test_terminal_exit_fences_before_event_and_keeps_guidance(worker_scene, monkeypatch, outcome):
scene = worker_scene
observed = []
put = scene.events.put_nowait
def observe(item):
if item[0] in ('done', 'apperror'):
observed.append(config.ACTIVE_RUNS.get('run', {}).get('phase'))
return put(item)
monkeypatch.setattr(scene.events, 'put_nowait', observe)
def running():
scene.agent.session_id = 'compressed-child'
scene.agent.steer('retained guidance')
if outcome == 'exception':
raise RuntimeError('terminal worker failure')
if outcome == 'returned-error':
scene.result = {'messages': [], 'error': 'terminal worker failure'}
else:
# The real Agent consumes this slot into its returned result.
text = scene.agent._drain_pending_steer()
scene.result = {'messages': [
{'role': 'user', 'content': 'Do the task.'},
{'role': 'assistant', 'content': 'Finished.'},
], 'pending_steer': text}
scene.on_run = running
scene.run()
assert observed and all(phase == 'finalizing' for phase in observed)
events = list(scene.events.queue)
leftovers = [data['text'] for event, data in events if event == 'pending_steer_leftover']
assert leftovers == ['retained guidance']
assert next(i for i, item in enumerate(events) if item[0] == 'pending_steer_leftover') < next(i for i, item in enumerate(events) if item[0] in ('done', 'apperror'))
assert scene.agent.pending == []
def test_finalizing_response_preserves_browser_live_stream():
import subprocess
from pathlib import Path
source = (Path(__file__).parents[1] / 'static/commands.js').read_text()
fn = source[source.index('async function _trySteer('):source.index('async function cmdTitle(')]
predicate = source[source.index('function _steerFallbackIsDeadRun('):source.index('function _steerOwnerStreamIsCurrent(')]
script = r'''
const assert=require('node:assert/strict');
const S={session:{session_id:'s',active_stream_id:'run'},activeStreamId:'run',busy:true,pendingFiles:[]};
const INFLIGHT={s:{stream_id:'run'}},inp={value:''};
const $=()=>inp,t=x=>x,showToast=()=>{};
const _steerTextWithPendingFiles=async x=>x,_steerOwnerIsCurrent=()=>true,_steerRestoreText=x=>x;
const _steerFailureMessageKey=x=>x,_showSteerRecovery=()=>{};
const _steerClearCurrentOwnerDeadRun=()=>{S.busy=false;S.activeStreamId=null;delete INFLIGHT.s};
const api=async()=>({accepted:false,fallback:'not_running',stream_id:'run'});
(async()=>{
assert.equal(await _trySteer('guidance',true),false);
assert.equal(S.busy,true);assert.equal(S.activeStreamId,'run');
assert.equal(INFLIGHT.s.stream_id,'run');assert.equal(inp.value,'guidance');
})().catch(e=>{console.error(e);process.exit(1)});
'''
subprocess.run(['node','-e',fn+predicate+script],check=True,timeout=15)