Add assistant turn anchor renderer snapshot adapter

This commit is contained in:
Frank Song committed 2026-06-14 02:03:45 +08:00
1 parent bba5631a69
commit 409fb59760
5 files changed
+537 -13

No files matched your search

+4
View File
@@ -3,6 +3,10 @@
## [Unreleased]
### Added
- **Stable Assistant Turn Anchors renderer snapshot adapter (#3926).** Adds `createAssistantTurnAnchorRendererSnapshot()` and `reconcileAssistantTurnAnchorRendererSnapshot()` so current Compact Worklog / Transparent Stream row hooks can be summarized as `renderer_snapshot_v1` and compared with the anchor-owned `activity_scene_v1` through the dual-run reconciler. This is still an inert migration harness: no current `renderMessages()`, live SSE, `S.messages`, `INFLIGHT`, Compact Worklog, Transparent Stream, or DOM path invokes it automatically.
## [v0.51.389] — 2026-06-13 — Release NB (surface dirty-install state in update check, #4085)
### Fixed
@@ -36,6 +36,11 @@ streaming or rendering yet.
`S.messages`, `INFLIGHT`, stream-local state, and DOM nodes remain
projection/cache layers outside the settled final-prose path and the live
shadow registry.
- Slice 8 adds the renderer snapshot adapter that can extract
`renderer_snapshot_v1` summaries from current Compact Worklog /
Transparent Stream row hooks and feed them through the reconciler to produce a
concrete matched / mismatched answer. The adapter remains opt-in and is not
invoked by `renderMessages()` or the live SSE hot path.
## State Layers
@@ -223,6 +228,28 @@ callbacks, `S.messages`, `INFLIGHT`, Compact Worklog, Transparent Stream, and
DOM continuity continue to render exactly as before until a later replacement
slice deliberately switches a renderer to anchor-owned rows.
## Slice 8 Renderer Snapshot Adapter
`HermesAssistantTurnAnchors.createAssistantTurnAnchorRendererSnapshot()` turns
current renderer rows into `renderer_snapshot_v1` summaries. The helper accepts
plain row-like objects or a DOM/root object with the existing renderer hooks:
Transparent Stream rows (`.transparent-event-row` /
`data-transparent-event-row`), Compact Worklog reasoning rows (`.wl-reason`,
`.agent-activity-thinking`, `.thinking-card-row`), and Compact Worklog tool rows
(`.tool-card-row`).
`HermesAssistantTurnAnchors.reconcileAssistantTurnAnchorRendererSnapshot()` is
the first one-call yes/no harness: it builds or accepts a renderer snapshot,
passes its rows into `reconcileAssistantTurnAnchorActivityScene()`, and returns
`renderer_snapshot_reconciliation_v1` with `matched: true` or `matched: false`
plus the underlying mismatch diagnostics.
This slice still does not change visible rendering. It gives later work a
bounded way to ask whether the current renderer output is equivalent to the
anchor-owned activity scene. A `matched: false` result is expected while current
renderers intentionally collapse or omit events, such as representing a tool
start + tool completion as one visible row or omitting terminal status rows.
## Source Event Classification
Phase 0 classifies current sources before changing render behavior:
+254 -3
View File
@@ -1217,8 +1217,8 @@
function _activityReconciliationMismatch(kind, detail){
return Object.freeze({
kind,
..._copyObject(detail),
kind,
});
}
@@ -1240,7 +1240,7 @@
}
const expectedById=_activityReconciliationRowsById(expectedRows);
const actualById=_activityReconciliationRowsById(actualRows);
const useRowIds=expectedById.size===expectedRows.length&&actualById.size>0;
const useRowIds=expectedById.size===expectedRows.length&&actualById.size===actualRows.length;
const matchedActualIds=new Set();
_activityReconciliationDuplicateRowIds(actualRows).forEach((duplicate)=>{
mismatches.push(_activityReconciliationMismatch('duplicate_actual_row',duplicate));
@@ -1315,6 +1315,255 @@
});
}
function _rendererSnapshotMode(input, options){
const item=(input&&typeof input==='object')?input:{};
const opts=(options&&typeof options==='object')?options:{};
const requested=_cleanString(_own(item,'mode'))||_cleanString(_own(opts,'mode'));
return requested==='transparent_stream'?'transparent_stream':'compact_worklog';
}
function _rendererSnapshotRowsInput(input, options){
const item=(input&&typeof input==='object')?input:{};
const opts=(options&&typeof options==='object')?options:{};
for(const value of [
_own(item,'renderer_rows'),
_own(item,'actual_rows'),
_own(item,'rows'),
_own(opts,'renderer_rows'),
_own(opts,'actual_rows'),
_own(opts,'rows'),
]){
if(Array.isArray(value)) return value;
}
return null;
}
function _rendererSnapshotRoot(input, options){
const item=(input&&typeof input==='object')?input:{};
const opts=(options&&typeof options==='object')?options:{};
return _own(item,'root')||_own(item,'turn')||_own(item,'element')||
_own(opts,'root')||_own(opts,'turn')||_own(opts,'element')||null;
}
function _rendererSnapshotDomRows(root, mode){
if(!root||typeof root.querySelectorAll!=='function') return [];
const selector=mode==='transparent_stream'
? '.transparent-event-row,[data-transparent-event-row="1"]'
: [
'.wl-reason[data-worklog-reason-source="reasoning"]',
'.wl-reason[data-worklog-anchor-reason="1"]',
'.agent-activity-thinking',
'.thinking-card-row',
'.tool-card-row',
].join(',');
return Array.from(root.querySelectorAll(selector)||[]);
}
function _rendererSnapshotAttr(row, keys){
return _activityReconciliationDataValue(row,keys);
}
function _rendererSnapshotQueryText(row, selector){
if(!row||typeof row.querySelector!=='function') return '';
const el=row.querySelector(selector);
return el&&typeof el.textContent==='string'?el.textContent:'';
}
function _rendererSnapshotText(row){
const explicit=_firstTextValue(
_rendererSnapshotAttr(row,['text']),
_rendererSnapshotAttr(row,['preview']),
_rendererSnapshotAttr(row,['summary']),
_rendererSnapshotAttr(row,['content'])
);
if(explicit) return explicit;
return _firstTextValue(
_rendererSnapshotQueryText(row,'.transparent-event-preview'),
_rendererSnapshotQueryText(row,'.transparent-event-thinking-preview'),
_rendererSnapshotQueryText(row,'.thinking-card-body pre'),
_rendererSnapshotQueryText(row,'.tool-card-preview'),
_rendererSnapshotQueryText(row,'.tool-card-result pre'),
typeof row.textContent==='string'?row.textContent:''
);
}
function _rendererSnapshotHasClass(row, className){
return !!(row&&row.classList&&typeof row.classList.contains==='function'&&row.classList.contains(className));
}
function _rendererSnapshotHasAttribute(row, attrName){
return !!(row&&typeof row.hasAttribute==='function'&&row.hasAttribute(attrName));
}
function _rendererSnapshotStatus(row){
const raw=_rendererSnapshotAttr(row,['status','state','event_status','eventStatus']);
if(raw!==undefined&&raw!==null&&raw!=='') return _activityReconciliationStatus(raw);
if(_rendererSnapshotHasClass(row,'tool-card-running')) return 'running';
return null;
}
function _rendererSnapshotToolDone(row, status){
const explicit=_rendererSnapshotAttr(row,['tool_done','toolDone','done']);
const parsed=_activityReconciliationBool(explicit);
if(parsed!==null) return parsed;
if(status==='completed'||status==='error'||status==='failed') return true;
if(status==='running'||status==='pending'||status==='interrupted') return false;
return null;
}
function _rendererSnapshotToolError(row, status){
const explicit=_rendererSnapshotAttr(row,['tool_is_error','toolError','is_error','isError']);
const parsed=_activityReconciliationBool(explicit);
if(parsed!==null) return parsed;
if(status==='error'||status==='failed') return true;
return false;
}
function _rendererSnapshotToolName(row){
return _firstTextValue(
_rendererSnapshotAttr(row,['tool_name','toolName','name']),
_rendererSnapshotQueryText(row,'.tool-card-name')
)||null;
}
function _rendererSnapshotToolCallId(row){
return _firstTextValue(
_rendererSnapshotAttr(row,['tool_call_id','toolCallId','tool_use_id','toolUseId','call_id','callId','tid','live_tid','liveTid'])
)||null;
}
function _rendererSnapshotRowId(row){
return _firstTextValue(
_rendererSnapshotAttr(row,[
'row_id',
'rowId',
'activity_row_id',
'activityRowId',
'event_id',
'eventId',
'anchor_event_id',
'anchorEventId',
'id',
])
)||null;
}
function _rendererSnapshotKindFromType(type, status, toolDone){
if(type==='thinking'||type==='reasoning') return 'reasoning';
if(type==='tool'||type==='tool_call'||type==='tool-card'){
if(toolDone===true||status==='completed'||status==='error'||status==='failed') return 'tool_completed';
return 'tool_started';
}
if(type==='compressing'||type==='compressed') return 'lifecycle_status';
if(type==='done'||type==='cancel'||type==='error'||type==='apperror') return 'terminal_status';
return type||null;
}
function _rendererSnapshotKind(row, mode, status, toolDone){
const explicit=_cleanString(_rendererSnapshotAttr(row,['kind']));
if(explicit) return explicit;
const type=_cleanString(_rendererSnapshotAttr(row,['event_type','eventType','type']));
if(type) return _rendererSnapshotKindFromType(type,status,toolDone);
if(mode==='transparent_stream'){
if(_rendererSnapshotAttr(row,['tool_name','toolName','name'])||_rendererSnapshotQueryText(row,'.tool-card-name')){
return _rendererSnapshotKindFromType('tool',status,toolDone);
}
return 'reasoning';
}
if(_rendererSnapshotHasClass(row,'wl-reason')||_rendererSnapshotHasClass(row,'agent-activity-thinking')||_rendererSnapshotHasClass(row,'thinking-card-row')){
return 'reasoning';
}
if(_rendererSnapshotHasClass(row,'tool-card-row')){
const compressionCardValue=_rendererSnapshotAttr(row,['compression_card','compressionCard']);
const hasCompressionCard=compressionCardValue!==undefined||
_rendererSnapshotHasAttribute(row,'data-compression-card')||
_rendererSnapshotHasAttribute(row,'compression-card')||
_rendererSnapshotHasAttribute(row,'compression_card');
if(hasCompressionCard&&_activityReconciliationBool(compressionCardValue)!==false) return 'lifecycle_status';
return _rendererSnapshotKindFromType('tool',status,toolDone);
}
return null;
}
function _rendererSnapshotRole(kind){
return _activityRowRole(kind||'activity');
}
function _rendererSnapshotSourceEventType(row, kind){
const explicit=_cleanString(_rendererSnapshotAttr(row,['source_event_type','sourceEventType']));
if(explicit) return explicit;
const type=_cleanString(_rendererSnapshotAttr(row,['event_type','eventType','type']));
if(type==='thinking') return 'reasoning';
if(type==='tool') return kind==='tool_completed'?'tool_complete':'tool';
if(kind==='reasoning') return 'reasoning';
if(kind==='tool_started') return 'tool';
if(kind==='tool_completed') return 'tool_complete';
if(kind==='lifecycle_status') return type||'compressed';
if(kind==='terminal_status') return type||'done';
return type||null;
}
function _rendererSnapshotRow(row, index, mode){
const status=_rendererSnapshotStatus(row);
const provisionalDone=_rendererSnapshotToolDone(row,status);
const kind=_rendererSnapshotKind(row,mode,status,provisionalDone);
const toolDone=_isToolActivityKind(kind)?_rendererSnapshotToolDone(row,status):null;
const toolError=_isToolActivityKind(kind)?_rendererSnapshotToolError(row,status):null;
const sourceEventType=_rendererSnapshotSourceEventType(row,kind);
return Object.freeze({
row_id:_rendererSnapshotRowId(row),
order_index:index,
kind,
role:_rendererSnapshotRole(kind),
source_event_type:sourceEventType,
status,
text:_rendererSnapshotText(row),
tool_call_id:_isToolActivityKind(kind)?_rendererSnapshotToolCallId(row):null,
tool_name:_isToolActivityKind(kind)?_rendererSnapshotToolName(row):null,
tool_done:toolDone,
tool_is_error:toolError,
});
}
function createAssistantTurnAnchorRendererSnapshot(input, options){
const item=(input&&typeof input==='object')?input:{};
const opts=(options&&typeof options==='object')?options:{};
const mode=_rendererSnapshotMode(item,opts);
const renderer=_cleanString(_own(item,'renderer'))||_cleanString(_own(opts,'renderer'))||mode;
const explicitRows=_rendererSnapshotRowsInput(item,opts);
const rows=(explicitRows||_rendererSnapshotDomRows(_rendererSnapshotRoot(item,opts),mode))
.map((row,index)=>_rendererSnapshotRow(row,index,mode));
return Object.freeze({
version:'renderer_snapshot_v1',
mode,
renderer,
row_count:rows.length,
rows:Object.freeze(rows),
});
}
function reconcileAssistantTurnAnchorRendererSnapshot(input, options){
const item=(input&&typeof input==='object')?input:{};
const opts=(options&&typeof options==='object')?options:{};
const snapshotInput=_own(item,'renderer_snapshot')||_own(item,'snapshot')||_own(opts,'renderer_snapshot')||_own(opts,'snapshot');
const snapshot=(snapshotInput&&typeof snapshotInput==='object'&&_own(snapshotInput,'version')==='renderer_snapshot_v1')
? snapshotInput
: createAssistantTurnAnchorRendererSnapshot(item,opts);
const reconciliation=reconcileAssistantTurnAnchorActivityScene({
...item,
mode:snapshot.mode,
renderer_rows:snapshot.rows,
},opts);
return Object.freeze({
version:'renderer_snapshot_reconciliation_v1',
mode:snapshot.mode,
renderer:snapshot.renderer,
matched:reconciliation.matched,
snapshot,
reconciliation,
});
}
function createAssistantTurnAnchorSeed(input){
const opts=(input&&typeof input==='object')?input:{};
const sessionId=_cleanString(opts.session_id);
@@ -1374,7 +1623,7 @@
}
ROOT.HermesAssistantTurnAnchors=Object.freeze({
version:'slice7-dual-run-reconciler',
version:'slice8-renderer-snapshot-adapter',
activityEventKinds:ACTIVITY_EVENT_KINDS,
stateLayers:STATE_LAYERS,
sourceEventClassification:SOURCE_EVENT_CLASSIFICATION,
@@ -1394,6 +1643,8 @@
projectAssistantTurnAnchorSettledMessageFinalAnswer,
projectAssistantTurnAnchorActivityScene,
reconcileAssistantTurnAnchorActivityScene,
createAssistantTurnAnchorRendererSnapshot,
reconcileAssistantTurnAnchorRendererSnapshot,
isAssistantTurnAnchorActivityKind,
});
})();
@@ -182,7 +182,7 @@ def test_normalizer_maps_live_and_replay_to_same_anchor_event_identity():
live = data["liveToken"]
replay = data["replayToken"]
assert data["version"] == "slice7-dual-run-reconciler"
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert live["classification"] == "activity"
assert live["dedupe_key"] == 'event_id:"run-1:7"'
assert replay["dedupe_key"] == live["dedupe_key"]
@@ -280,12 +280,156 @@ const duplicated = api.reconcileAssistantTurnAnchorActivityScene({{
scene,
renderer_rows:duplicatedRows,
}});
const mixedIdRows = rendererRows.map((row) => ({{...row}}));
delete mixedIdRows[1].row_id;
const mixedIds = api.reconcileAssistantTurnAnchorActivityScene({{
scene,
renderer_rows:mixedIdRows,
}});
console.log(JSON.stringify({{
version:api.version,
scene,
matched,
mismatched,
duplicated,
mixedIds,
}}));
"""
result = subprocess.run([NODE, "-e", script], text=True, capture_output=True, check=False)
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def _renderer_snapshot_adapter_snapshot() -> dict:
assert NODE, "node is required for assistant_turn_anchors.js registry tests"
script = f"""
const fs = require('fs');
const vm = require('vm');
const src = fs.readFileSync({json.dumps(str(ANCHORS_JS))}, 'utf8');
const sandbox = {{window:{{}}}};
vm.createContext(sandbox);
vm.runInContext(src, sandbox, {{filename:'assistant_turn_anchors.js'}});
const api = sandbox.window.HermesAssistantTurnAnchors;
function node(attrs, text, children, classes) {{
const attrMap = attrs || {{}};
const classSet = new Set(classes || []);
return {{
textContent: text || '',
dataset: {{}},
classList: {{
contains(name) {{ return classSet.has(name); }},
}},
getAttribute(name) {{
return Object.prototype.hasOwnProperty.call(attrMap, name) ? attrMap[name] : null;
}},
hasAttribute(name) {{
return Object.prototype.hasOwnProperty.call(attrMap, name);
}},
querySelector(selector) {{
return children && children[selector] || null;
}},
}};
}}
const registry = api.createAssistantTurnAnchorRegistry({{
session_id:'sid-renderer',
turn_id:'turn-renderer',
}});
api.applyAssistantTurnAnchorSourceEvents(registry, [
{{event:'reasoning', payload:{{text:'thinking'}}, event_id:'run-renderer:1', seq:1}},
{{event:'tool', payload:{{tool_call_id:'tool-1', name:'terminal', args:{{command:'rg anchor'}}}}, event_id:'run-renderer:2', seq:2}},
{{event:'tool_complete', payload:{{tool_call_id:'tool-1', name:'terminal', result:'ok', is_error:false}}, event_id:'run-renderer:3', seq:3}},
{{event:'done', payload:{{status:'done'}}, event_id:'run-renderer:4', seq:4}},
], {{run_id:'run-renderer', stream_id:'stream-renderer'}});
const scene = api.projectAssistantTurnAnchorActivityScene(registry, {{mode:'transparent_stream'}});
const transparentRows = [
node({{
'data-transparent-event-row':'1',
'data-event-type':'thinking',
'data-event-id':'run-renderer:1',
}}, '', {{
'.thinking-card-body pre': {{textContent:'thinking'}},
'.transparent-event-preview': {{textContent:'thinking'}},
}}, ['transparent-event-row']),
node({{
'data-transparent-event-row':'1',
'data-event-type':'tool',
'data-event-status':'Completed',
'data-event-id':'run-renderer:3',
'data-tool-name':'terminal',
'data-live-tid':'tool-1',
}}, '', {{
'.tool-card-name': {{textContent:'terminal'}},
'.tool-card-preview': {{textContent:'Completed'}},
}}, ['transparent-event-row','tool-card-row']),
];
const transparentRoot = {{
querySelectorAll(selector) {{
if (selector.includes('transparent-event-row')) return transparentRows;
return [];
}},
}};
const transparentSnapshot = api.createAssistantTurnAnchorRendererSnapshot({{
root:transparentRoot,
mode:'transparent_stream',
renderer:'transparent_stream',
}});
const transparentReconciliation = api.reconcileAssistantTurnAnchorRendererSnapshot({{
scene,
renderer_snapshot:transparentSnapshot,
}});
const matchingSnapshot = api.createAssistantTurnAnchorRendererSnapshot({{
mode:'transparent_stream',
rows:scene.activity_rows.map((row) => ({{
row_id:row.row_id,
kind:row.kind,
role:row.role,
source_event_type:row.source_event_type,
status:row.status,
tool_call_id:row.tool_call_id,
tool_name:row.tool && row.tool.name,
tool_done:row.tool && row.tool.done,
tool_is_error:row.tool && row.tool.is_error,
}})),
}});
const matchingReconciliation = api.reconcileAssistantTurnAnchorRendererSnapshot({{
scene,
renderer_snapshot:matchingSnapshot,
}});
const compactRows = [
node({{'data-worklog-reason-source':'reasoning'}}, '', {{
'.thinking-card-body pre': {{textContent:'compact thinking'}},
}}, ['wl-reason']),
node({{'data-tool-name':'terminal','data-tool-done':'false','data-tool-error':'false','data-live-tid':'tool-live'}}, '', {{
'.tool-card-name': {{textContent:'terminal'}},
'.tool-card-preview': {{textContent:'Running'}},
}}, ['tool-card-row']),
];
const compactSnapshot = api.createAssistantTurnAnchorRendererSnapshot({{
mode:'compact_worklog',
rows:compactRows,
}});
const compressionSnapshot = api.createAssistantTurnAnchorRendererSnapshot({{
mode:'compact_worklog',
rows:[
node({{'data-compression-card':''}}, 'Compression updated', {{}}, ['tool-card-row']),
node({{'data-compression-card':'false','data-tool-name':'terminal'}}, '', {{
'.tool-card-name': {{textContent:'terminal'}},
}}, ['tool-card-row']),
],
}});
console.log(JSON.stringify({{
version:api.version,
transparentSnapshot,
transparentReconciliation,
matchingSnapshot,
matchingReconciliation,
compactSnapshot,
compressionSnapshot,
}}));
"""
result = subprocess.run([NODE, "-e", script], text=True, capture_output=True, check=False)
@@ -523,7 +667,7 @@ def test_registry_owns_one_anchor_and_dedupes_live_plus_replay_events():
registry = data["registry"]
anchor = registry["anchor"]
assert data["version"] == "slice7-dual-run-reconciler"
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert [item["reason"] for item in data["results"][:2]] == [None, "duplicate"]
assert registry["event_index"]["dedupe_keys"][:2] == [
'event_id:"run-1:1"',
@@ -623,7 +767,7 @@ def test_registry_does_not_destructively_dedupe_seqless_local_tool_lifecycle():
registry = data["toolRegistry"]
anchor = registry["anchor"]
assert data["version"] == "slice7-dual-run-reconciler"
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert data["toolResults"] == [
{"applied": True, "reason": None},
{"applied": True, "reason": None},
@@ -673,7 +817,7 @@ def test_shadow_snapshot_feeds_current_source_families_into_one_registry_owner()
registry = data["registry"]
anchor = registry["anchor"]
assert data["version"] == "slice7-dual-run-reconciler"
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert data["results"]["live"] == [{"applied": True, "reason": None}]
assert data["results"]["replay"] == [
{"applied": False, "reason": "duplicate"},
@@ -701,7 +845,7 @@ def test_activity_scene_projects_current_activity_events_for_both_render_modes()
compact = data["compact"]
transparent = data["transparent"]
assert data["version"] == "slice7-dual-run-reconciler"
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert compact["version"] == "activity_scene_v1"
assert transparent["version"] == "activity_scene_v1"
assert compact["mode"] == "compact_worklog"
@@ -826,7 +970,7 @@ def test_activity_scene_reconciler_matches_renderer_snapshot_rows():
data = _activity_scene_reconciliation_snapshot()
matched = data["matched"]
assert data["version"] == "slice7-dual-run-reconciler"
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert matched["version"] == "activity_scene_reconciliation_v1"
assert matched["scene_version"] == "activity_scene_v1"
assert matched["mode"] == "transparent_stream"
@@ -901,7 +1045,8 @@ def test_activity_scene_reconciler_reports_duplicate_renderer_row_ids():
assert duplicated["matched"] is False
assert "duplicate_actual_row" in kinds
assert "missing_actual_row" in kinds
assert "missing_actual_row" not in kinds
assert "field_mismatch" in kinds
duplicates = [
item for item in duplicated["mismatches"]
if item["kind"] == "duplicate_actual_row"
@@ -914,11 +1059,108 @@ def test_activity_scene_reconciler_reports_duplicate_renderer_row_ids():
"row": duplicated["actual_rows"][2],
}
]
mismatched_fields = [
item["field"] for item in duplicated["mismatches"]
if item["kind"] == "field_mismatch"
]
assert "kind" in mismatched_fields
assert "tool_done" in mismatched_fields
def test_activity_scene_reconciler_uses_index_matching_for_partial_actual_row_ids():
data = _activity_scene_reconciliation_snapshot()
mixed = data["mixedIds"]
assert mixed["matched"] is True
assert mixed["summary"] == {
"expected_count": 4,
"actual_count": 4,
"mismatch_count": 0,
}
assert mixed["actual_rows"][1]["row_id"] is None
def test_renderer_snapshot_adapter_extracts_current_renderer_rows():
data = _renderer_snapshot_adapter_snapshot()
transparent = data["transparentSnapshot"]
compact = data["compactSnapshot"]
compression = data["compressionSnapshot"]
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert transparent["version"] == "renderer_snapshot_v1"
assert transparent["mode"] == "transparent_stream"
assert transparent["renderer"] == "transparent_stream"
assert transparent["row_count"] == 2
assert transparent["rows"][0] == {
"row_id": "run-renderer:1",
"order_index": 0,
"kind": "reasoning",
"role": "thinking",
"source_event_type": "reasoning",
"status": None,
"text": "thinking",
"tool_call_id": None,
"tool_name": None,
"tool_done": None,
"tool_is_error": None,
}
assert transparent["rows"][1] == {
"row_id": "run-renderer:3",
"order_index": 1,
"kind": "tool_completed",
"role": "tool",
"source_event_type": "tool_complete",
"status": "completed",
"text": "Completed",
"tool_call_id": "tool-1",
"tool_name": "terminal",
"tool_done": True,
"tool_is_error": False,
}
assert compact["mode"] == "compact_worklog"
assert compact["rows"][0]["kind"] == "reasoning"
assert compact["rows"][0]["text"] == "compact thinking"
assert compact["rows"][1]["kind"] == "tool_started"
assert compact["rows"][1]["status"] is None
assert compact["rows"][1]["tool_done"] is False
assert compact["rows"][1]["tool_call_id"] == "tool-live"
assert compression["rows"][0]["kind"] == "lifecycle_status"
assert compression["rows"][0]["source_event_type"] == "compressed"
assert compression["rows"][1]["kind"] == "tool_started"
assert compression["rows"][1]["tool_name"] == "terminal"
def test_renderer_snapshot_reconciliation_produces_yes_or_no_answer():
data = _renderer_snapshot_adapter_snapshot()
matching = data["matchingReconciliation"]
transparent = data["transparentReconciliation"]
assert matching["version"] == "renderer_snapshot_reconciliation_v1"
assert matching["matched"] is True
assert matching["reconciliation"]["summary"] == {
"expected_count": 4,
"actual_count": 4,
"mismatch_count": 0,
}
assert transparent["version"] == "renderer_snapshot_reconciliation_v1"
assert transparent["matched"] is False
assert transparent["snapshot"]["row_count"] == 2
assert transparent["reconciliation"]["summary"]["expected_count"] == 4
assert transparent["reconciliation"]["summary"]["actual_count"] == 2
kinds = [item["kind"] for item in transparent["reconciliation"]["mismatches"]]
assert "row_count" in kinds
assert "missing_actual_row" in kinds
missing = [
item for item in duplicated["mismatches"]
item for item in transparent["reconciliation"]["mismatches"]
if item["kind"] == "missing_actual_row"
]
assert missing[0]["row_id"] == "run-reconcile:3"
assert [item["row_id"] for item in missing] == [
"run-renderer:2",
"run-renderer:4",
]
def test_final_projection_routes_settled_assistant_message_through_anchor_owner():
@@ -927,7 +1169,7 @@ def test_final_projection_routes_settled_assistant_message_through_anchor_owner(
registry = projected["registry"]
anchor = registry["anchor"]
assert data["version"] == "slice7-dual-run-reconciler"
assert data["version"] == "slice8-renderer-snapshot-adapter"
assert projected["applied"] is True
assert projected["reason"] is None
assert projected["final_message_ref"] == "message-final"