Files
hermes-webui/static/assistant_turn_anchors.js
2026-06-13 00:44:26 +08:00

778 lines
29 KiB
JavaScript

// Stable Assistant Turn Anchors scaffold (#3926).
//
// This file is intentionally inert: it defines the current ownership inventory,
// event classifications, and small owner helpers, but it does not register
// anchors globally or change any renderer. Later phases can wire these helpers into
// send(), attachLiveStream(), replay hydration, and renderMessages().
(function(){
const ROOT=(typeof window!=='undefined')?window:globalThis;
const ACTIVITY_EVENT_KINDS=Object.freeze([
'process_prose',
'reasoning',
'tool_started',
'tool_updated',
'tool_completed',
'lifecycle_status',
'control_boundary',
'terminal_status',
]);
const STATE_LAYERS=Object.freeze([
Object.freeze({
id:'event_envelope',
label:'RuntimeAdapter / run-journal Event Envelope',
currentSurface:'event_id, run_id, seq, Last-Event-ID / after_seq',
role:'durable_identity',
authorityRank:1,
anchorPolicy:'Anchor identity and replay dedupe must consume this first.',
}),
Object.freeze({
id:'run_journal',
label:'Run journal replay events',
currentSurface:'read_run_events(), _replay_run_journal, runtime_journal_snapshot',
role:'durable_replay',
authorityRank:2,
anchorPolicy:'Replay hydration should rebuild activity events from this before caches.',
}),
Object.freeze({
id:'settled_transcript',
label:'Server settled transcript messages',
currentSurface:'/api/session messages and message metadata',
role:'durable_settlement',
authorityRank:3,
anchorPolicy:'Settlement updates the existing anchor final answer and terminal state.',
}),
Object.freeze({
id:'S.messages',
label:'Browser transcript projection',
currentSurface:'S.messages consumed by renderMessages()',
role:'projection_cache',
authorityRank:4,
anchorPolicy:'Projection input/output, not a second owner for one assistant turn.',
}),
Object.freeze({
id:'INFLIGHT',
label:'Browser in-flight recovery cache',
currentSurface:'INFLIGHT[session_id], localStorage persisted in-flight state',
role:'recovery_cache',
authorityRank:5,
anchorPolicy:'Recovery fallback only; must not outrank journal or settled transcript.',
}),
Object.freeze({
id:'stream_closure',
label:'attachLiveStream closure-local state',
currentSurface:'assistantText, reasoningText, parser targets, live tool state',
role:'hot_path_cache',
authorityRank:6,
anchorPolicy:'Hot-path write buffer; normalize into anchor events as the stream advances.',
}),
Object.freeze({
id:'live_dom',
label:'Live DOM / Worklog nodes',
currentSurface:'#liveAssistantTurn, tool-card rows, Thinking cards',
role:'renderer_output',
authorityRank:7,
anchorPolicy:'DOM continuity is useful, but DOM is never semantic truth.',
}),
]);
const SOURCE_EVENT_CLASSIFICATION=Object.freeze({
token:Object.freeze({classification:'activity',kind:'process_prose',source:'sse'}),
interim_assistant:Object.freeze({classification:'activity',kind:'process_prose',source:'sse'}),
reasoning:Object.freeze({classification:'activity',kind:'reasoning',source:'sse'}),
tool:Object.freeze({classification:'activity',kind:'tool_started',source:'sse'}),
tool_complete:Object.freeze({classification:'activity',kind:'tool_completed',source:'sse'}),
tool_update:Object.freeze({classification:'activity',kind:'tool_updated',source:'future_sse'}),
compressing:Object.freeze({classification:'activity',kind:'lifecycle_status',source:'sse'}),
compressed:Object.freeze({classification:'activity',kind:'lifecycle_status',source:'sse'}),
approval:Object.freeze({classification:'activity',kind:'control_boundary',source:'sse'}),
clarify:Object.freeze({classification:'activity',kind:'control_boundary',source:'sse'}),
pending_steer_leftover:Object.freeze({classification:'activity',kind:'control_boundary',source:'sse'}),
goal_continue:Object.freeze({classification:'activity',kind:'control_boundary',source:'sse'}),
artifact_reference:Object.freeze({classification:'artifact',kind:'artifact_reference',source:'derived'}),
state_saved:Object.freeze({classification:'side_effect',kind:null,source:'sse'}),
usage:Object.freeze({classification:'metadata',kind:null,source:'settlement'}),
title:Object.freeze({classification:'metadata',kind:null,source:'settlement'}),
done:Object.freeze({classification:'activity',kind:'terminal_status',source:'sse'}),
cancel:Object.freeze({classification:'activity',kind:'terminal_status',source:'sse'}),
error:Object.freeze({classification:'activity',kind:'terminal_status',source:'sse'}),
apperror:Object.freeze({classification:'activity',kind:'terminal_status',source:'sse'}),
stream_end:Object.freeze({classification:'transport',kind:null,source:'sse'}),
runtime_journal_snapshot:Object.freeze({classification:'metadata',kind:null,source:'session_payload'}),
inflight_snapshot:Object.freeze({classification:'metadata',kind:null,source:'browser_storage'}),
settled_message:Object.freeze({classification:'metadata',kind:null,source:'session_payload'}),
});
const CLASSIFICATION_ORDER=Object.freeze([
'activity',
'artifact',
'side_effect',
'metadata',
'transport',
'excluded',
]);
const TERMINAL_STATES=Object.freeze({
completed:'completed',
cancelled:'cancelled',
interrupted:'interrupted',
no_response:'no_response',
tool_limit_reached:'tool_limit_reached',
compression_exhausted:'compression_exhausted',
connection_lost:'connection_lost',
degraded:'degraded',
error:'error',
});
const TERMINAL_STATE_ALIASES=Object.freeze({
completed:TERMINAL_STATES.completed,
complete:TERMINAL_STATES.completed,
done:TERMINAL_STATES.completed,
cancelled:TERMINAL_STATES.cancelled,
canceled:TERMINAL_STATES.cancelled,
cancel:TERMINAL_STATES.cancelled,
interrupted:TERMINAL_STATES.interrupted,
interrupted_by_user:TERMINAL_STATES.interrupted,
no_response:TERMINAL_STATES.no_response,
no_response_generated:TERMINAL_STATES.no_response,
tool_limit_reached:TERMINAL_STATES.tool_limit_reached,
max_iterations:TERMINAL_STATES.tool_limit_reached,
compression_exhausted:TERMINAL_STATES.compression_exhausted,
connection_lost:TERMINAL_STATES.connection_lost,
lost_worker_bookkeeping:TERMINAL_STATES.connection_lost,
degraded:TERMINAL_STATES.degraded,
error:TERMINAL_STATES.error,
failed:TERMINAL_STATES.error,
apperror:TERMINAL_STATES.error,
});
const UNSAFE_OBJECT_KEYS=Object.freeze([
'__proto__',
'constructor',
'prototype',
]);
function _isUnsafeObjectKey(key){
return UNSAFE_OBJECT_KEYS.indexOf(key)!==-1;
}
function _hasOwn(value, key){
return !!value&&typeof value==='object'&&Object.prototype.hasOwnProperty.call(value,key);
}
function _own(value, key){
return _hasOwn(value,key)?value[key]:undefined;
}
function _firstOwn(value, keys){
if(!value||typeof value!=='object') return undefined;
for(let i=0;i<keys.length;i+=1){
const item=_own(value,keys[i]);
if(item!==undefined&&item!==null&&item!=='') return item;
}
return undefined;
}
function _cleanString(value){
return typeof value==='string'?value.trim():'';
}
function _terminalStateKey(value){
return _cleanString(value).toLowerCase().replace(/[\s-]+/g,'_');
}
function normalizeAssistantTurnAnchorTerminalState(value, fallback){
const key=_terminalStateKey(value);
if(key&&_hasOwn(TERMINAL_STATE_ALIASES,key)) return TERMINAL_STATE_ALIASES[key];
const fallbackKey=_terminalStateKey(fallback);
if(fallbackKey&&_hasOwn(TERMINAL_STATE_ALIASES,fallbackKey)){
return TERMINAL_STATE_ALIASES[fallbackKey];
}
return null;
}
function _coercePayload(value){
if(value==null) return {};
if(typeof value==='string'){
const raw=value.trim();
if(!raw) return {};
try{
const parsed=JSON.parse(raw);
return parsed&&typeof parsed==='object'?parsed:{value:parsed};
}catch(_){
return {text:value};
}
}
if(typeof value==='object') return value;
return {value};
}
function _sanitizePayload(value, depth=0){
if(value==null) return value;
const type=typeof value;
if(type==='string'||type==='number'||type==='boolean') return value;
if(type==='bigint') return String(value);
if(type!=='object') return undefined;
if(depth>=6) return '[MaxDepth]';
if(Array.isArray(value)){
return value.map((item)=>_sanitizePayload(item,depth+1)).filter((item)=>item!==undefined);
}
const proto=Object.getPrototypeOf(value);
if(proto!==null&&Object.prototype.toString.call(value)!=='[object Object]') return '[Object]';
const out=Object.create(null);
Object.keys(value).sort().forEach((key)=>{
if(_isUnsafeObjectKey(key)) return;
const safe=_sanitizePayload(value[key],depth+1);
if(safe!==undefined) out[key]=safe;
});
return out;
}
function _coerceSeq(value){
if(value==null||value==='') return null;
const str=String(value);
const numeric=Number(str);
return Number.isFinite(numeric)?numeric:str;
}
function _eventIdSeq(eventId){
const raw=_cleanString(eventId);
if(!raw||!raw.includes(':')) return null;
return _coerceSeq(raw.slice(raw.lastIndexOf(':')+1));
}
function _eventIdRunId(eventId){
const raw=_cleanString(eventId);
if(!raw||!raw.includes(':')) return '';
return raw.slice(0,raw.lastIndexOf(':'));
}
function _sourceEventType(input, payload){
return _cleanString(_firstOwn(input,[
'source_event_type',
'sourceType',
'source_type',
'event_type',
'type',
'event',
])) || _cleanString(_firstOwn(payload,['source_event_type','type','event']));
}
function _sourceEventPayload(input){
if(!input||typeof input!=='object') return {};
if(_hasOwn(input,'payload')) return _coercePayload(_own(input,'payload'));
if(_hasOwn(input,'data')) return _coercePayload(_own(input,'data'));
const payload=Object.create(null);
const reserved=new Set([
'source_event_type',
'sourceType',
'source_type',
'event_type',
'type',
'event',
'event_id',
'lastEventId',
'last_event_id',
'seq',
'session_id',
'turn_id',
'run_id',
'stream_id',
'created_at',
'timestamp',
]);
Object.keys(input).forEach((key)=>{
if(_isUnsafeObjectKey(key)) return;
if(!reserved.has(key)) payload[key]=input[key];
});
return payload;
}
function _statusForSourceEvent(sourceType, kind, payload){
const explicit=_cleanString(_firstOwn(payload,['status','state','phase']));
if(explicit){
return kind==='terminal_status'
?normalizeAssistantTurnAnchorTerminalState(explicit,sourceType)||explicit
:explicit;
}
if(kind==='tool_started') return 'running';
if(kind==='tool_completed') return _own(payload,'is_error')?'error':'completed';
if(kind==='terminal_status'){
return normalizeAssistantTurnAnchorTerminalState(sourceType)||TERMINAL_STATES.error;
}
if(kind==='lifecycle_status') return 'running';
if(kind==='control_boundary') return 'pending';
if(sourceType==='stream_end') return 'transport_closed';
return null;
}
function _localIdForSourceEvent(sourceType, context, payload){
const explicit=_cleanString(
_own(context,'local_id')||
_firstOwn(payload,['local_id','id','tid','tool_call_id','tool_use_id','call_id'])
);
if(explicit) return explicit;
const sessionId=_cleanString(_own(context,'session_id'))||'session';
const turnId=_cleanString(_own(context,'turn_id'))||'turn';
const ctxSeq=_own(context,'seq');
const seq=(ctxSeq!=null&&ctxSeq!=='')?String(ctxSeq):'pending';
return [sessionId,turnId,sourceType||'event',seq].join(':');
}
function assistantTurnAnchorEventDedupeKey(event){
if(!event||typeof event!=='object') return '';
const eventId=_cleanString(_own(event,'event_id'));
if(eventId) return 'event_id:'+JSON.stringify(eventId);
const runId=_cleanString(_own(event,'run_id'));
const eventSeq=_own(event,'seq');
const seq=(eventSeq!=null&&eventSeq!=='')?String(eventSeq):'';
if(runId&&seq) return 'run_seq:'+JSON.stringify([runId,seq]);
const sid=_cleanString(_own(event,'session_id'));
const localId=_cleanString(_own(event,'local_id'));
const sourceType=_cleanString(_own(event,'source_event_type'))||'event';
if(sid&&localId&&seq&&seq!=='pending') return 'local:'+JSON.stringify([sid,sourceType,localId,seq]);
return '';
}
function classifyAssistantTurnAnchorSourceEvent(sourceType){
const key=_cleanString(sourceType);
return SOURCE_EVENT_CLASSIFICATION[key]||Object.freeze({
classification:'excluded',
kind:null,
source:key||'unknown',
});
}
function isAssistantTurnAnchorActivityKind(kind){
return ACTIVITY_EVENT_KINDS.indexOf(kind)!==-1;
}
function normalizeAssistantTurnAnchorSourceEvent(input, context){
const event=(input&&typeof input==='object')?input:{};
const ctx=(context&&typeof context==='object')?context:{};
const sanitizedPayload=_sanitizePayload(_sourceEventPayload(event));
const rawPayload=(sanitizedPayload&&typeof sanitizedPayload==='object'&&!Array.isArray(sanitizedPayload))?sanitizedPayload:{};
const {
session_id:_payloadSessionId,
turn_id:_payloadTurnId,
run_id:_payloadRunId,
stream_id:_payloadStreamId,
event_id:_payloadEventId,
seq:_payloadSeq,
...payload
}=rawPayload;
const sourceType=_sourceEventType(event,payload);
const meta=classifyAssistantTurnAnchorSourceEvent(sourceType);
const classification=meta.classification;
if(classification==='excluded'){
return Object.freeze({
classification,
source_event_type:sourceType||'unknown',
anchor_event:null,
dedupe_key:'',
});
}
const eventId=_cleanString(_firstOwn(event,['event_id','lastEventId','last_event_id'])||_payloadEventId);
const eventSeq=_own(event,'seq');
const ctxSeq=_own(ctx,'seq');
const seq=_coerceSeq(
eventSeq!==undefined?eventSeq:
_payloadSeq!==undefined?_payloadSeq:
ctxSeq!==undefined?ctxSeq:
_eventIdSeq(eventId)
);
const runId=_cleanString(_own(event,'run_id')||_payloadRunId||_own(ctx,'run_id'))||_eventIdRunId(eventId)||null;
const sessionId=_cleanString(_own(event,'session_id')||_payloadSessionId||_own(ctx,'session_id'));
const turnId=_cleanString(_own(event,'turn_id')||_payloadTurnId||_own(ctx,'turn_id'));
const streamId=_cleanString(_own(event,'stream_id')||_payloadStreamId||_own(ctx,'stream_id'))||null;
const localId=_localIdForSourceEvent(sourceType, {...ctx,seq}, payload);
const anchorEvent={
event_id:eventId||null,
local_id:localId,
session_id:sessionId||null,
turn_id:turnId||null,
run_id:runId,
stream_id:streamId,
seq,
kind:meta.kind,
source_event_type:sourceType,
created_at:_own(event,'created_at')||_own(event,'timestamp')||_own(payload,'created_at')||_own(payload,'ts')||_own(ctx,'created_at')||null,
status:_statusForSourceEvent(sourceType,meta.kind,payload),
payload,
};
const dedupeKey=assistantTurnAnchorEventDedupeKey(anchorEvent);
return Object.freeze({
classification,
source_event_type:sourceType,
anchor_event:Object.freeze(anchorEvent),
dedupe_key:dedupeKey,
});
}
function normalizeAssistantTurnAnchorSourceEvents(events, context){
const list=Array.isArray(events)?events:[];
const out=[];
const seen=new Set();
list.forEach((event)=>{
const normalized=normalizeAssistantTurnAnchorSourceEvent(event,context);
if(!normalized.anchor_event) return;
const key=normalized.dedupe_key;
if(key&&seen.has(key)) return;
if(key) seen.add(key);
out.push(normalized);
});
return out;
}
function _copyObject(value){
if(!value||typeof value!=='object'||Array.isArray(value)) return {};
return {...value};
}
function _frozenIdentityCopy(identity){
const refs=Array.isArray(identity&&identity.source_message_refs)
?identity.source_message_refs.slice()
:[];
return Object.freeze({
..._copyObject(identity),
source_message_refs:Object.freeze(refs),
});
}
function _registryAnchor(registry){
return registry&&typeof registry==='object'&&registry.anchor&&typeof registry.anchor==='object'
?registry.anchor
:null;
}
function _registryContext(registry, context){
const anchor=_registryAnchor(registry);
const identity=anchor&&anchor.identity?anchor.identity:{};
return {
..._copyObject(context),
session_id:_cleanString(_own(identity,'session_id'))||_cleanString(_own(context,'session_id')),
turn_id:_cleanString(_own(identity,'turn_id'))||_cleanString(_own(context,'turn_id')),
run_id:_cleanString(_own(identity,'run_id'))||_cleanString(_own(context,'run_id')),
stream_id:_cleanString(_own(identity,'stream_id'))||_cleanString(_own(context,'stream_id')),
};
}
function _eventBelongsToAnchor(anchor, event){
const identity=anchor.identity||{};
const sessionId=_cleanString(_own(event,'session_id'));
const identitySessionId=_cleanString(_own(identity,'session_id'));
if(sessionId&&identitySessionId&&sessionId!==identitySessionId) return false;
const turnId=_cleanString(_own(event,'turn_id'));
const identityTurnId=_cleanString(_own(identity,'turn_id'));
if(turnId&&identityTurnId&&turnId!==identityTurnId) return false;
const runId=_cleanString(_own(event,'run_id'));
const identityRunId=_cleanString(_own(identity,'run_id'));
if(runId&&identityRunId&&runId!==identityRunId) return false;
return true;
}
function _ensureDedupeKeySet(eventIndex){
const existing=eventIndex.dedupe_key_set;
if(existing instanceof Set) return existing;
const set=new Set(Array.isArray(eventIndex.dedupe_keys)?eventIndex.dedupe_keys:[]);
Object.defineProperty(eventIndex,'dedupe_key_set',{
value:set,
enumerable:false,
configurable:true,
writable:true,
});
return set;
}
function _ensureRegistryShape(registry){
const anchor=_registryAnchor(registry);
if(!anchor) throw new Error('assistant turn anchor registry requires anchor');
registry.event_index=registry.event_index&&typeof registry.event_index==='object'
?registry.event_index
:{};
if(!Array.isArray(registry.event_index.dedupe_keys)) registry.event_index.dedupe_keys=[];
registry.stats=registry.stats&&typeof registry.stats==='object'?registry.stats:{};
registry.stats.applied=Number(registry.stats.applied)||0;
registry.stats.skipped_duplicate=Number(registry.stats.skipped_duplicate)||0;
registry.stats.skipped_excluded=Number(registry.stats.skipped_excluded)||0;
registry.stats.skipped_mismatched=Number(registry.stats.skipped_mismatched)||0;
if(!Array.isArray(anchor.metadata_events)) anchor.metadata_events=[];
if(!Array.isArray(anchor.transport_events)) anchor.transport_events=[];
_ensureDedupeKeySet(registry.event_index);
return anchor;
}
function _syncAnchorIdentity(anchor, event){
const identity=anchor.identity||{};
const runId=_cleanString(_own(event,'run_id'));
const streamId=_cleanString(_own(event,'stream_id'));
if(!_cleanString(_own(identity,'run_id'))&&runId) identity.run_id=runId;
if(!_cleanString(_own(identity,'stream_id'))&&streamId) identity.stream_id=streamId;
}
function _textFromContentValue(value){
if(typeof value==='string') return value;
if(Array.isArray(value)){
return value.map((item)=>{
if(typeof item==='string') return item;
if(!item||typeof item!=='object') return '';
const text=_firstOwn(item,['text','content']);
return typeof text==='string'?text:'';
}).join('');
}
if(value&&typeof value==='object'){
const text=_firstOwn(value,['text','content']);
return typeof text==='string'?text:'';
}
return '';
}
function _firstTextValue(...values){
for(let i=0;i<values.length;i+=1){
const value=_textFromContentValue(values[i]);
if(value.length>0) return value;
}
return '';
}
function _messageRefFromPayload(payload, event){
return _firstTextValue(
_firstOwn(payload,['message_id','id','local_id']),
_firstOwn(event,['local_id','event_id'])
)||null;
}
function _updateLifecycleFromEvent(anchor, event){
const lifecycle=anchor.lifecycle||{};
const createdAt=_own(event,'created_at');
const status=_cleanString(_own(event,'status'));
const kind=_cleanString(_own(event,'kind'));
if(!lifecycle.started_at&&createdAt) lifecycle.started_at=createdAt;
if((!lifecycle.status||lifecycle.status==='created')&&status==='running'){
lifecycle.status='running';
}
if(kind==='terminal_status'){
const terminal=normalizeAssistantTurnAnchorTerminalState(
status,
_own(event,'source_event_type')
)||TERMINAL_STATES.completed;
lifecycle.status=terminal;
lifecycle.terminal_state=terminal;
lifecycle.completed_at=createdAt||lifecycle.completed_at||null;
}
anchor.lifecycle=lifecycle;
}
function _updateContentFromMetadata(anchor, event){
const payload=_own(event,'payload')||{};
const sourceType=_cleanString(_own(event,'source_event_type'));
if(sourceType==='usage'){
anchor.usage=_copyObject(payload);
return;
}
if(sourceType!=='settled_message') return;
const role=_cleanString(_own(payload,'role'));
if(role&&role!=='assistant') return;
const finalAnswer=_firstTextValue(
_own(payload,'content'),
_own(payload,'text'),
_own(payload,'final_answer'),
_own(payload,'answer')
);
if(finalAnswer){
anchor.content=anchor.content||{};
anchor.content.final_answer=finalAnswer;
anchor.content.final_message_ref=_messageRefFromPayload(payload,event);
}
const usage=_own(payload,'usage');
const turnUsage=_own(payload,'_turnUsage');
if(usage&&typeof usage==='object') anchor.usage=_copyObject(usage);
if(turnUsage&&typeof turnUsage==='object') anchor.usage=_copyObject(turnUsage);
}
function _routeAnchorEvent(anchor, normalized){
const event=_own(normalized,'anchor_event');
const classification=_cleanString(_own(normalized,'classification'));
if(classification==='activity'){
anchor.activity_events.push(event);
_updateLifecycleFromEvent(anchor,event);
}else if(classification==='artifact'){
anchor.artifacts.push(event);
}else if(classification==='side_effect'){
anchor.side_effects.push(event);
}else if(classification==='metadata'){
anchor.metadata_events.push(event);
_updateContentFromMetadata(anchor,event);
}else if(classification==='transport'){
anchor.transport_events.push(event);
}
}
function applyAssistantTurnAnchorNormalizedEvent(registry, normalized){
const anchor=_ensureRegistryShape(registry);
const item=(normalized&&typeof normalized==='object')?normalized:{};
const event=_own(item,'anchor_event');
if(!event){
registry.stats.skipped_excluded+=1;
return Object.freeze({applied:false,reason:'excluded',normalized:item});
}
if(!_eventBelongsToAnchor(anchor,event)){
registry.stats.skipped_mismatched+=1;
return Object.freeze({applied:false,reason:'mismatched_anchor',normalized:item});
}
const dedupeKey=_cleanString(_own(item,'dedupe_key'))||assistantTurnAnchorEventDedupeKey(event);
const dedupeKeySet=_ensureDedupeKeySet(registry.event_index);
if(dedupeKey&&dedupeKeySet.has(dedupeKey)){
registry.stats.skipped_duplicate+=1;
return Object.freeze({applied:false,reason:'duplicate',normalized:item});
}
if(dedupeKey){
dedupeKeySet.add(dedupeKey);
registry.event_index.dedupe_keys.push(dedupeKey);
}
_syncAnchorIdentity(anchor,event);
_routeAnchorEvent(anchor,item);
registry.stats.applied+=1;
return Object.freeze({applied:true,reason:null,normalized:item});
}
function applyAssistantTurnAnchorSourceEvent(registry, input, context){
const normalized=normalizeAssistantTurnAnchorSourceEvent(input,_registryContext(registry,context));
return applyAssistantTurnAnchorNormalizedEvent(registry,normalized);
}
function applyAssistantTurnAnchorSourceEvents(registry, events, context){
const list=Array.isArray(events)?events:[];
return list.map((event)=>applyAssistantTurnAnchorSourceEvent(registry,event,context));
}
function _eventsForShadowSource(sources, primaryKey, fallbackKey){
if(!sources||typeof sources!=='object') return [];
const primary=sources[primaryKey];
if(Array.isArray(primary)) return primary;
const fallback=fallbackKey?sources[fallbackKey]:null;
return Array.isArray(fallback)?fallback:[];
}
function _shadowSourceContext(context, sourceLayer){
return {
..._copyObject(context),
source_layer:sourceLayer,
};
}
function createAssistantTurnAnchorShadowSnapshot(input){
const opts=(input&&typeof input==='object')?input:{};
const anchorInput=(opts.anchor&&typeof opts.anchor==='object')?opts.anchor:opts;
const sources=(opts.sources&&typeof opts.sources==='object')?opts.sources:opts;
const context=(opts.context&&typeof opts.context==='object')?opts.context:{};
const registry=createAssistantTurnAnchorRegistry(anchorInput);
const results={
live:applyAssistantTurnAnchorSourceEvents(
registry,
_eventsForShadowSource(sources,'live_events'),
_shadowSourceContext(context,'live')
),
replay:applyAssistantTurnAnchorSourceEvents(
registry,
_eventsForShadowSource(sources,'replay_events','run_journal_events'),
_shadowSourceContext(context,'replay')
),
settled:applyAssistantTurnAnchorSourceEvents(
registry,
_eventsForShadowSource(sources,'settled_events'),
_shadowSourceContext(context,'settled')
),
inflight:applyAssistantTurnAnchorSourceEvents(
registry,
_eventsForShadowSource(sources,'inflight_events'),
_shadowSourceContext(context,'inflight')
),
};
return Object.freeze({
registry,
results:Object.freeze(results),
});
}
function createAssistantTurnAnchorSeed(input){
const opts=(input&&typeof input==='object')?input:{};
const sessionId=_cleanString(opts.session_id);
if(!sessionId) throw new Error('assistant turn anchor requires session_id');
const streamId=_cleanString(opts.stream_id);
const runId=_cleanString(opts.run_id);
const turnId=_cleanString(opts.turn_id)||[
'local',
sessionId,
runId||streamId||'pending',
_cleanString(opts.local_id)||'assistant',
].join(':');
return {
identity:{
session_id:sessionId,
turn_id:turnId,
run_id:runId||null,
stream_id:streamId||null,
source_message_refs:Array.isArray(opts.source_message_refs)?opts.source_message_refs.slice():[],
},
lifecycle:{
status:_cleanString(opts.status)||'created',
terminal_state:null,
started_at:opts.started_at||null,
completed_at:null,
},
content:{
final_answer:'',
final_message_ref:null,
},
activity_events:[],
artifacts:[],
side_effects:[],
metadata_events:[],
transport_events:[],
usage:null,
};
}
function createAssistantTurnAnchorRegistry(input){
const anchor=createAssistantTurnAnchorSeed(input);
const registry={
identity:_frozenIdentityCopy(anchor.identity),
anchor,
event_index:{
dedupe_keys:[],
},
stats:{
applied:0,
skipped_duplicate:0,
skipped_excluded:0,
skipped_mismatched:0,
},
};
_ensureDedupeKeySet(registry.event_index);
return registry;
}
ROOT.HermesAssistantTurnAnchors=Object.freeze({
version:'slice3-registry-shadow',
activityEventKinds:ACTIVITY_EVENT_KINDS,
stateLayers:STATE_LAYERS,
sourceEventClassification:SOURCE_EVENT_CLASSIFICATION,
classificationOrder:CLASSIFICATION_ORDER,
terminalStates:TERMINAL_STATES,
createAssistantTurnAnchorSeed,
normalizeAssistantTurnAnchorTerminalState,
assistantTurnAnchorEventDedupeKey,
classifyAssistantTurnAnchorSourceEvent,
normalizeAssistantTurnAnchorSourceEvent,
normalizeAssistantTurnAnchorSourceEvents,
createAssistantTurnAnchorRegistry,
applyAssistantTurnAnchorNormalizedEvent,
applyAssistantTurnAnchorSourceEvent,
applyAssistantTurnAnchorSourceEvents,
createAssistantTurnAnchorShadowSnapshot,
isAssistantTurnAnchorActivityKind,
});
})();