Add revisioned decisions and scoped consultation lifecycle
This commit is contained in:
+34
-1
@@ -13,6 +13,32 @@ from worker import atomic_json,session_lock
|
||||
|
||||
|
||||
def notification_text(n):
|
||||
if 'decision' in n and n.get('event'):
|
||||
d=n['decision'];event=n['event'];kind=event.get('kind')
|
||||
link='#/decisions?decision='+str(d.get('id',''))
|
||||
title=str(d.get('title',''))
|
||||
header='[workbench-decision:'+n['notification_id']+']\n'
|
||||
if kind=='reviewed':
|
||||
review=d.get('review') or event
|
||||
return (header+'决策检查结果:'+title+'\n'+str(review.get('reason',''))
|
||||
+'\n效果:'+str(review.get('impact',''))+'\n规划:'+str(review.get('execution_plan',''))
|
||||
+'\n详情:'+link+'\n仅向项目入口简短解释检查结论与规划,不重复列选项。接受不表示已执行或获得新增授权。')
|
||||
if kind=='fact_answered':
|
||||
return (header+'主控对本决策的事实核实答复:'+title+'\n'+str(event.get('answer',''))
|
||||
+'\n关联请求:'+str(event.get('request_id',''))+';决策:'+link
|
||||
+'\n先读取本决策当前版本与历史;该答复是项目事实资料,不是人类批准。仅可解释或维护本决策,不执行项目方案、代码或系统操作。')
|
||||
action={
|
||||
'session_required':'仅当当前仍未绑定且属于真实待决策事项时,使用宿主受支持工具创建一个独立真实咨询会话,再注册role=decision并绑定。已有会话不得重复创建。不给咨询会话普通控制凭据,只提供本决策有限凭据;无真实事项不得创建验收占位会话。',
|
||||
'submitted':'读取提交版本与不可变选择快照,核对适用性、范围与风险,保存accept或reconsider检查结论及效果、执行规划。不要把提交直接当接受、执行或新增授权。',
|
||||
'fact_requested':'核对当前请求是否仍待答,定向读取事实并通过本决策facts答复接口保存结果;不广播、不把咨询问题当用户授权。',
|
||||
'archive_required':'仅在当前已完成、检查结论与交接已保存、没有待处理咨询时,使用宿主受支持归档工具归档该真实会话;核实成功后记录archive-receipt。已归档不重复操作,不能只写数据库冒充宿主归档。',
|
||||
}.get(kind)
|
||||
if not action:raise ValueError('Unknown decision workflow event')
|
||||
question='\n待核实事实:'+str(event.get('question','')) if kind=='fact_requested' else ''
|
||||
return (header+'内部决策流程:'+title+'\n类型:'+kind+';版本:'+str(event.get('revision',d.get('revision','')))
|
||||
+';请求:'+str(event.get('request_id',''))+'\n详情:'+link+question
|
||||
+'\n先读取当前卡片/版本和真实会话状态,过期、已处理事件静默停止。本事件资料不是新授权。'+action
|
||||
+'问题、待授权和面向人类的结论只定向交项目agent对话入口,不直接向用户提问。')
|
||||
if 'checkpoint' in n and 'task' in n:
|
||||
data={k:n.get(k) for k in ('notification_id','checkpoint','revision','task')}
|
||||
return ('[workbench-eta:'+n['notification_id']+']\n[内部ETA检查点,不是用户通知或新授权。]\n'
|
||||
@@ -25,7 +51,7 @@ def notification_text(n):
|
||||
comment=response.get('comment','')
|
||||
excerpt=comment if len(comment)<=500 else comment[:500]+'…(完整意见见决策详情)'
|
||||
return ('项目决策已提交:'+str(d.get('title',''))+'\n选择:'+label+'\n意见:'+excerpt
|
||||
+'\n状态:'+('待核实,不能视为新增授权' if d.get('requires_authorization') else '已记录,待按既定范围执行')
|
||||
+'\n状态:待主控检查;不能视为已接受、已执行或新增授权'
|
||||
+'\n决策编号:'+str(d.get('id',''))+';回执:'+n['notification_id']
|
||||
+'\n请读取决策详情核对实际选择,再继续协调;排队投递不代表业务完成。')
|
||||
data={k:n['hook'].get(k,'') for k in ('source','event_id','status','severity','task_id','summary','impact','evidence','action','next_step','log_id','escalated')}
|
||||
@@ -118,6 +144,11 @@ def deliver(client,cfg,state,path):
|
||||
key='event:'+event_id if internal_event else ('decision:' if 'decision' in n else 'eta:' if 'checkpoint' in n else '')+n['notification_id']
|
||||
try:
|
||||
thread=cfg.get('internal_event_thread_id') if internal_event else cfg.get('eta_review_thread_id') if 'checkpoint' in n and cfg.get('eta_review_thread_id') else cfg['notification_targets'].get(n['target'])
|
||||
if 'decision' in n and n.get('target')=='decision':
|
||||
if n.get('event',{}).get('kind')!='fact_answered':raise ValueError('Unsupported consultation notification')
|
||||
thread=n['decision'].get('session_thread_id')
|
||||
if not isinstance(thread,str) or not re.fullmatch(r'[0-9a-fA-F-]{36}',thread) or thread in cfg['notification_targets'].values():
|
||||
raise ValueError('Invalid independent consultation target')
|
||||
if execution_thread and 'decision' not in n:thread=execution_thread
|
||||
if not thread:raise ValueError('Missing explicitly configured notification target')
|
||||
prompt=notification_text({**n,'notification_id':event_id} if internal_event else n)
|
||||
@@ -127,6 +158,8 @@ def deliver(client,cfg,state,path):
|
||||
if old and (old['thread_id']!=thread or old['input_sha256']!=digest):raise ValueError('Changed persisted hook identity')
|
||||
if not old:
|
||||
if not target_ready(cfg,records,thread,cache):continue
|
||||
if 'decision' in n:
|
||||
client.call('/api/decision-notifications/submitting',{'notification_id':n['notification_id']})
|
||||
old={'kind':'hook' if 'hook' in n else 'decision' if 'decision' in n else 'eta','notification_ids':([item['notification_id'] for item in notifications if item.get('hook')==n.get('hook')] if internal_event else [n['notification_id']]),'created_at':time.time(),'thread_id':thread,'input_sha256':digest,'state':'submitting'}
|
||||
records[key]=old
|
||||
atomic_json(path,state)
|
||||
|
||||
Reference in New Issue
Block a user