Add reliable execution reporting and coordinated dashboard views
This commit is contained in:
@@ -63,6 +63,100 @@ class NotifierTests(unittest.TestCase):
|
||||
self.assertTrue(state['deliveries']['note2']['acked'])
|
||||
self.assertEqual(state['deliveries']['note1']['state'],'submitting')
|
||||
|
||||
def test_decision_response_goes_only_to_control_and_retries_ack_without_queue(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,n,path=self.fixture(root)
|
||||
cfg['decision_notifications']=True
|
||||
item={'notification_id':'decision-note','target':'control','decision':{'id':'choice','title':'Choose plan','question':'Which plan?','status':'decided','requires_authorization':False,'options':[{'id':'a','label':'A','description':'Plan A'}],'response':{'id':'response','option_id':'a','comment':'Use A','authorization':False}}}
|
||||
calls=[]
|
||||
class Client:
|
||||
def call(self,p,data=None):
|
||||
calls.append(p)
|
||||
return {'notifications':[item] if p=='/api/decision-notifications' else []}
|
||||
result=SimpleNamespace(returncode=0,stdout='Queued message 22222222-2222-4222-8222-222222222222 for thread '+cfg['notification_targets']['control']+'.')
|
||||
state={}
|
||||
with patch.object(notifier.subprocess,'run',return_value=result) as run:
|
||||
notifier.deliver(Client(),cfg,state,path)
|
||||
notifier.deliver(Client(),cfg,state,path)
|
||||
self.assertEqual(run.call_count,1)
|
||||
self.assertEqual(calls.count('/api/decision-notifications/ack'),2)
|
||||
self.assertTrue(state['deliveries']['decision:decision-note']['acked'])
|
||||
|
||||
def test_eta_checkpoint_only_goes_to_control_and_ack_is_not_review_completion(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,n,path=self.fixture(root);cfg['eta_notifications']=True
|
||||
item={'notification_id':'eta-note','target':'control','task':{'id':'work','current_progress':'checking','status':'running'},'checkpoint':'half','revision':1}
|
||||
calls=[]
|
||||
class Client:
|
||||
def call(self,p,data=None):
|
||||
calls.append(p)
|
||||
return {'notifications':[item] if p=='/api/eta-notifications' else []}
|
||||
result=SimpleNamespace(returncode=0,stdout='Queued message 22222222-2222-4222-8222-222222222222 for thread '+cfg['notification_targets']['control']+'.')
|
||||
with patch.object(notifier.subprocess,'run',return_value=result):notifier.deliver(Client(),cfg,{},path)
|
||||
self.assertIn('/api/eta-notifications/ack',calls)
|
||||
self.assertFalse(any('/complete' in p for p in calls))
|
||||
|
||||
def test_internal_review_merges_control_and_human_hook_without_root_injection(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,n,path=self.fixture(root)
|
||||
cfg['notification_targets']['human']='44444444-4444-4444-8444-444444444444'
|
||||
cfg['internal_event_thread_id']='33333333-3333-4333-8333-333333333333'
|
||||
other={**n,'notification_id':'note2','target':'human'}
|
||||
calls=[]
|
||||
class Client:
|
||||
def call(self,p,data=None):
|
||||
calls.append((p,data))
|
||||
return {'notifications':[n,other]} if p.endswith('notifications') else {}
|
||||
result=SimpleNamespace(returncode=0,stdout='Queued message 22222222-2222-4222-8222-222222222222 for thread '+cfg['internal_event_thread_id']+'.')
|
||||
state={}
|
||||
with patch.object(notifier.subprocess,'run',return_value=result) as run:
|
||||
notifier.deliver(Client(),cfg,state,path)
|
||||
self.assertEqual(run.call_count,1)
|
||||
self.assertIn(cfg['internal_event_thread_id'],run.call_args.args[0])
|
||||
self.assertNotIn(cfg['notification_targets']['control'],run.call_args.args[0])
|
||||
self.assertEqual(len(state['deliveries']),1)
|
||||
self.assertEqual(len([x for x in calls if x[0]=='/api/hooks/ack']),2)
|
||||
|
||||
def test_busy_target_keeps_backlog_until_real_completed_final(self):
|
||||
import hashlib
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,n,path=self.fixture(root);cfg['enforce_backpressure']=True
|
||||
notes=[{**n,'notification_id':'note'+str(i),'hook':{**n['hook'],'event_id':'incident'+str(i)}} for i in range(20)]
|
||||
class Client:
|
||||
def call(self,p,data=None):return {'notifications':notes} if p.endswith('notifications') else {}
|
||||
class Reader:
|
||||
turns=[]
|
||||
def __init__(self,*a):pass
|
||||
def __enter__(self):return self
|
||||
def __exit__(self,*a):pass
|
||||
def call(self,method,params):
|
||||
return {'thread':{'id':cfg['notification_targets']['control']}} if method=='thread/read' else {'data':self.turns}
|
||||
result=SimpleNamespace(returncode=0,stdout='Queued message 22222222-2222-4222-8222-222222222222 for thread '+cfg['notification_targets']['control']+'.')
|
||||
state={}
|
||||
with patch.object(notifier.subprocess,'run',return_value=result) as run,patch.object(notifier,'AppServer',Reader):
|
||||
for _ in range(5):notifier.deliver(Client(),cfg,state,path)
|
||||
self.assertEqual(run.call_count,1)
|
||||
prompt=run.call_args.args[0][-1]
|
||||
self.assertEqual(hashlib.sha256(prompt.encode()).hexdigest(),next(iter(state['deliveries'].values()))['input_sha256'])
|
||||
Reader.turns=[{'id':'turn','status':'completed','completedAt':1,'items':[{'type':'userMessage','content':[{'type':'text','text':prompt}]},{'type':'agentMessage','phase':'final_answer','text':'reviewed'}]}]
|
||||
notifier.deliver(Client(),cfg,state,path)
|
||||
self.assertEqual(run.call_count,2)
|
||||
|
||||
def test_task_event_routes_to_registered_executor_before_deputy(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,n,path=self.fixture(root)
|
||||
cfg.update(internal_event_thread_id='33333333-3333-4333-8333-333333333333',execution_notification_targets={'worker':'44444444-4444-4444-8444-444444444444'})
|
||||
n['hook']['task_id']='owned-task'
|
||||
class Client:
|
||||
def call(self,p,data=None):
|
||||
if p=='/api/state':return {'tasks':[{'id':'owned-task','session_id':'worker'}]}
|
||||
return {'notifications':[n]} if p.endswith('notifications') else {}
|
||||
result=SimpleNamespace(returncode=0,stdout='Queued message 22222222-2222-4222-8222-222222222222 for thread '+cfg['execution_notification_targets']['worker']+'.')
|
||||
with patch.object(notifier.subprocess,'run',return_value=result) as run:
|
||||
notifier.deliver(Client(),cfg,{},path)
|
||||
self.assertIn(cfg['execution_notification_targets']['worker'],run.call_args.args[0])
|
||||
self.assertNotIn(cfg['internal_event_thread_id'],run.call_args.args[0])
|
||||
|
||||
def test_no_events_means_no_conversation_input(self):
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
cfg,n,path=self.fixture(root)
|
||||
|
||||
Reference in New Issue
Block a user