948 lines
39 KiB
Python
948 lines
39 KiB
Python
"""Anchor fallback ownership guards for the settled activity scene.
|
|
|
|
The Stable Assistant Turn Anchor should own settled activity when a message has
|
|
`_anchor_activity_scene`. Raw `content[]` ordering and legacy settled tool-card
|
|
rebuilds are still required for historical/non-anchor transcripts, but they must
|
|
exit before competing with anchor-owned turns.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
UI_JS_PATH = ROOT / "static" / "ui.js"
|
|
PHASE0_DOC_PATH = (
|
|
ROOT / "docs" / "architecture" / "stable-assistant-turn-anchor-phase0.md"
|
|
)
|
|
|
|
|
|
def _read_required_text(path: Path, label: str) -> str:
|
|
assert path.exists(), f"{label} not found at {path}"
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def _ui_js() -> str:
|
|
return _read_required_text(UI_JS_PATH, "static/ui.js")
|
|
|
|
|
|
def _phase0_doc() -> str:
|
|
return _read_required_text(
|
|
PHASE0_DOC_PATH,
|
|
"Stable Assistant Turn Anchors Phase 0 inventory",
|
|
)
|
|
|
|
|
|
def _run_node_script(script: str) -> str:
|
|
node = shutil.which("node")
|
|
if not node:
|
|
pytest.skip("node executable is required for JavaScript behavior checks")
|
|
try:
|
|
with tempfile.NamedTemporaryFile("w", suffix=".js", encoding="utf-8", delete=False) as handle:
|
|
handle.write(script)
|
|
script_path = handle.name
|
|
try:
|
|
result = subprocess.run(
|
|
[node, script_path],
|
|
cwd=ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
timeout=10,
|
|
)
|
|
finally:
|
|
Path(script_path).unlink(missing_ok=True)
|
|
except subprocess.TimeoutExpired as exc:
|
|
pytest.fail(
|
|
"node behavior check timed out"
|
|
f"\nstdout:\n{exc.stdout or '<empty>'}"
|
|
f"\nstderr:\n{exc.stderr or '<empty>'}",
|
|
)
|
|
if result.returncode:
|
|
pytest.fail(
|
|
"node behavior check failed"
|
|
f"\nexit code: {result.returncode}"
|
|
f"\nstdout:\n{result.stdout or '<empty>'}"
|
|
f"\nstderr:\n{result.stderr or '<empty>'}",
|
|
)
|
|
stdout_lines = [line for line in result.stdout.splitlines() if line.strip()]
|
|
return stdout_lines[-1] if stdout_lines else ""
|
|
|
|
|
|
def _is_js_identifier_char(char: str) -> bool:
|
|
return char.isalnum() or char in {"_", "$"}
|
|
|
|
|
|
def _previous_significant_js_token(src: str, idx: int) -> str:
|
|
idx -= 1
|
|
while idx >= 0 and src[idx].isspace():
|
|
idx -= 1
|
|
if idx < 0:
|
|
return ""
|
|
if _is_js_identifier_char(src[idx]):
|
|
end = idx + 1
|
|
while idx >= 0 and _is_js_identifier_char(src[idx]):
|
|
idx -= 1
|
|
return src[idx + 1 : end]
|
|
return src[idx]
|
|
|
|
|
|
def _looks_like_js_regex_literal_start(src: str, idx: int) -> bool:
|
|
if src[idx] != "/" or src.startswith(("//", "/*"), idx):
|
|
return False
|
|
previous = _previous_significant_js_token(src, idx)
|
|
if previous in {"return", "throw", "case", "delete", "typeof", "void", "yield"}:
|
|
return True
|
|
return previous in {
|
|
"",
|
|
"(",
|
|
"[",
|
|
"{",
|
|
"=",
|
|
":",
|
|
",",
|
|
";",
|
|
"!",
|
|
"?",
|
|
"&",
|
|
"|",
|
|
"+",
|
|
"-",
|
|
"*",
|
|
"%",
|
|
"^",
|
|
"~",
|
|
"<",
|
|
">",
|
|
}
|
|
|
|
|
|
def _skip_js_regex_literal(src: str, idx: int) -> int:
|
|
assert src[idx] == "/", f"expected regex literal at {idx}"
|
|
idx += 1
|
|
in_class = False
|
|
while idx < len(src):
|
|
if src[idx] == "\\":
|
|
idx += 2
|
|
continue
|
|
if src[idx] == "[":
|
|
in_class = True
|
|
elif src[idx] == "]":
|
|
in_class = False
|
|
elif src[idx] == "/" and not in_class:
|
|
idx += 1
|
|
while idx < len(src) and src[idx].isalpha():
|
|
idx += 1
|
|
return idx
|
|
idx += 1
|
|
raise AssertionError("JavaScript regex literal did not close")
|
|
|
|
|
|
def _skip_js_string_or_comment(src: str, idx: int) -> int:
|
|
if src.startswith("//", idx):
|
|
end = src.find("\n", idx + 2)
|
|
return len(src) if end == -1 else end + 1
|
|
if src.startswith("/*", idx):
|
|
end = src.find("*/", idx + 2)
|
|
assert end != -1, "JavaScript block comment did not close"
|
|
return end + 2
|
|
if _looks_like_js_regex_literal_start(src, idx):
|
|
return _skip_js_regex_literal(src, idx)
|
|
quote = src[idx]
|
|
if quote == "`":
|
|
return _skip_js_template_literal(src, idx)
|
|
if quote not in {"'", '"'}:
|
|
return idx
|
|
idx += 1
|
|
while idx < len(src):
|
|
if src[idx] == "\\":
|
|
idx += 2
|
|
continue
|
|
if src[idx] == quote:
|
|
return idx + 1
|
|
idx += 1
|
|
raise AssertionError(f"JavaScript string literal {quote!r} did not close")
|
|
|
|
|
|
def _skip_js_template_literal(src: str, idx: int) -> int:
|
|
assert src[idx] == "`", f"expected template literal at {idx}"
|
|
idx += 1
|
|
while idx < len(src):
|
|
if src[idx] == "\\":
|
|
idx += 2
|
|
continue
|
|
if src[idx] == "`":
|
|
return idx + 1
|
|
if src.startswith("${", idx):
|
|
expression_close = _matching_delimiter(src, idx + 1, "{", "}")
|
|
idx = expression_close + 1
|
|
continue
|
|
idx += 1
|
|
raise AssertionError("JavaScript template literal did not close")
|
|
|
|
|
|
def _matching_delimiter(src: str, open_idx: int, opener: str, closer: str) -> int:
|
|
assert src[open_idx] == opener, f"expected {opener!r} at {open_idx}"
|
|
depth = 0
|
|
idx = open_idx
|
|
while idx < len(src):
|
|
next_idx = _skip_js_string_or_comment(src, idx)
|
|
if next_idx != idx:
|
|
idx = next_idx
|
|
continue
|
|
if src[idx] == opener:
|
|
depth += 1
|
|
elif src[idx] == closer:
|
|
depth -= 1
|
|
if depth == 0:
|
|
return idx
|
|
idx += 1
|
|
raise AssertionError(f"{opener}{closer} delimiter did not close")
|
|
|
|
|
|
def _skip_js_whitespace(src: str, idx: int) -> int:
|
|
while idx < len(src) and src[idx].isspace():
|
|
idx += 1
|
|
return idx
|
|
|
|
|
|
def _find_function_declaration(src: str, name: str) -> tuple[int, int]:
|
|
idx = 0
|
|
while idx < len(src):
|
|
next_idx = _skip_js_string_or_comment(src, idx)
|
|
if next_idx != idx:
|
|
idx = next_idx
|
|
continue
|
|
if not src.startswith("function", idx):
|
|
idx += 1
|
|
continue
|
|
before = src[idx - 1] if idx else ""
|
|
after_keyword = idx + len("function")
|
|
after = src[after_keyword] if after_keyword < len(src) else ""
|
|
if _is_js_identifier_char(before) or _is_js_identifier_char(after):
|
|
idx += 1
|
|
continue
|
|
name_start = _skip_js_whitespace(src, after_keyword)
|
|
name_end = name_start + len(name)
|
|
if src[name_start:name_end] != name:
|
|
idx += 1
|
|
continue
|
|
after_name = src[name_end] if name_end < len(src) else ""
|
|
if _is_js_identifier_char(after_name):
|
|
idx += 1
|
|
continue
|
|
params_open = _skip_js_whitespace(src, name_end)
|
|
if params_open < len(src) and src[params_open] == "(":
|
|
return idx, params_open
|
|
idx += 1
|
|
raise AssertionError(f"{name} not found")
|
|
|
|
|
|
def _function_source(src: str, name: str) -> str:
|
|
start, params_open = _find_function_declaration(src, name)
|
|
params_close = _matching_delimiter(src, params_open, "(", ")")
|
|
brace = src.find("{", params_close)
|
|
assert brace != -1, f"{name} body not found"
|
|
close = _matching_delimiter(src, brace, "{", "}")
|
|
return src[start : close + 1]
|
|
|
|
|
|
def _function_body(src: str, name: str) -> str:
|
|
_, params_open = _find_function_declaration(src, name)
|
|
params_close = _matching_delimiter(src, params_open, "(", ")")
|
|
brace = src.find("{", params_close)
|
|
assert brace != -1, f"{name} body not found"
|
|
close = _matching_delimiter(src, brace, "{", "}")
|
|
return src[brace + 1 : close]
|
|
|
|
|
|
def test_phase0_doc_records_settled_fallback_ownership_matrix():
|
|
doc = _phase0_doc()
|
|
|
|
assert "### Settled Fallback Ownership Matrix" in doc
|
|
assert "_anchor_activity_scene` is the semantic" in doc
|
|
assert "| Settled Compact Worklog activity |" in doc
|
|
assert "| Settled Transparent Stream activity |" in doc
|
|
assert "| Historical / non-anchor transcripts |" in doc
|
|
assert "This matrix is the current settled-render contract" in doc
|
|
assert "compatibility-only rebuilds" in doc
|
|
assert "explicit raw transcript indexes" in doc
|
|
|
|
|
|
def test_function_extractor_handles_nested_template_literal_interpolation():
|
|
source = """
|
|
function sample(){
|
|
const text=`outer ${condition ? `inner ${value}` : { fallback: true }}`;
|
|
if(anchorOwnedAssistantRawIdxs.has(rawIdx)) return;
|
|
}
|
|
function afterSample(){ return false; }
|
|
"""
|
|
|
|
body = _function_body(source, "sample")
|
|
|
|
assert "if(anchorOwnedAssistantRawIdxs.has(rawIdx)) return;" in body
|
|
assert "function afterSample" not in body
|
|
|
|
|
|
def test_function_extractor_matches_exact_declarations_outside_comments():
|
|
source = """
|
|
// function sample(){ return 'comment'; }
|
|
function samplePrefix(){ return 'prefix'; }
|
|
function sample(){
|
|
const hasBrace = /\\{[^}]+\\}/.test(text);
|
|
return hasBrace ? 'target' : 'fallback';
|
|
}
|
|
function afterSample(){ return false; }
|
|
"""
|
|
|
|
body = _function_body(source, "sample")
|
|
|
|
assert "return hasBrace ? 'target' : 'fallback';" in body
|
|
assert "return 'comment';" not in body
|
|
assert "return 'prefix';" not in body
|
|
assert "function afterSample" not in body
|
|
|
|
|
|
def test_function_extractor_ignores_destructured_parameter_braces():
|
|
source = """
|
|
function sample({ fallback = true }){
|
|
if(anchorOwnedAssistantRawIdxs.has(rawIdx)) return;
|
|
}
|
|
"""
|
|
|
|
body = _function_body(source, "sample")
|
|
|
|
assert body.lstrip().startswith("if(anchorOwnedAssistantRawIdxs.has(rawIdx))")
|
|
assert "fallback = true" not in body
|
|
|
|
|
|
def test_function_extractor_skips_regex_after_binary_operator():
|
|
source = """
|
|
function sample(){
|
|
const matcher = fallback || /\\{[^}]+\\}/;
|
|
if(anchorOwnedAssistantRawIdxs.has(rawIdx)) return;
|
|
}
|
|
"""
|
|
|
|
body = _function_body(source, "sample")
|
|
|
|
assert "const matcher = fallback || /\\{[^}]+\\}/;" in body
|
|
assert "if(anchorOwnedAssistantRawIdxs.has(rawIdx)) return;" in body
|
|
|
|
|
|
def test_transparent_raw_content_helper_is_fallback_only_when_anchor_scene_absent():
|
|
helper = _function_body(_ui_js(), "_transparentStreamOrderedParts")
|
|
|
|
transparent_gate = helper.index("!isTransparentStream()) return null;")
|
|
role_gate = helper.index("!message||message.role!=='assistant'||message._live")
|
|
anchor_exit = helper.index("if(message._anchor_activity_scene) return null;")
|
|
content_loop = helper.index("for(const part of message.content)")
|
|
fallback_return = helper.index("return hasText&&hasTool?ordered:null;")
|
|
|
|
assert transparent_gate < role_gate < anchor_exit < content_loop < fallback_return
|
|
assert "part.type==='text'" in helper
|
|
assert "part.type==='tool_use'" in helper
|
|
|
|
|
|
def test_transparent_raw_content_fallback_exits_for_anchor_owned_messages():
|
|
helper_source = _function_source(_ui_js(), "_transparentStreamOrderedParts")
|
|
script = textwrap.dedent(
|
|
f"""
|
|
let transparentStream = true;
|
|
function isTransparentStream() {{
|
|
return transparentStream;
|
|
}}
|
|
|
|
eval({json.dumps(helper_source)});
|
|
|
|
const anchorOwned = {{
|
|
role: 'assistant',
|
|
content: [
|
|
{{ type: 'text', text: 'Checked the repo state.' }},
|
|
{{ type: 'tool_use', id: 'toolu_anchor', name: 'terminal', input: {{ cmd: 'git status' }} }},
|
|
],
|
|
_anchor_activity_scene: {{
|
|
schema_version: 'activity_scene_v1',
|
|
activity_rows: [],
|
|
}},
|
|
}};
|
|
const historical = {{
|
|
role: 'assistant',
|
|
content: [
|
|
{{ type: 'text', text: 'Checked the repo state.' }},
|
|
{{ type: 'tool_use', id: 'toolu_history', name: 'terminal', input: {{ cmd: 'git status' }} }},
|
|
],
|
|
}};
|
|
|
|
const anchorResult = _transparentStreamOrderedParts(anchorOwned);
|
|
const historicalResult = _transparentStreamOrderedParts(historical);
|
|
transparentStream = false;
|
|
const disabledResult = _transparentStreamOrderedParts(historical);
|
|
|
|
console.log(JSON.stringify({{
|
|
anchorResult,
|
|
historicalResult,
|
|
disabledResult,
|
|
}}));
|
|
"""
|
|
)
|
|
|
|
result = json.loads(_run_node_script(script))
|
|
|
|
assert result["anchorResult"] is None
|
|
assert result["disabledResult"] is None
|
|
assert [part["kind"] for part in result["historicalResult"]] == ["text", "tool"]
|
|
assert result["historicalResult"][0]["text"] == "Checked the repo state."
|
|
assert result["historicalResult"][1] == {
|
|
"kind": "tool",
|
|
"toolUseId": "toolu_history",
|
|
"name": "terminal",
|
|
"input": {"cmd": "git status"},
|
|
}
|
|
|
|
|
|
def test_render_messages_keeps_anchor_owned_turn_out_of_legacy_activity_rebuilds():
|
|
"""Drive the real renderMessages() gate, not only source-order assertions."""
|
|
|
|
render_source = _function_source(_ui_js(), "renderMessages")
|
|
transparent_source = _function_source(_ui_js(), "_transparentStreamOrderedParts")
|
|
legacy_metadata_source = _function_source(
|
|
_ui_js(), "_legacySettledFallbackHasToolMetadata"
|
|
)
|
|
# #2051: renderMessages() inserts a message block through this helper, so it is
|
|
# evaluated with it. The shim's createElement() returns no template `content`, so the
|
|
# helper takes its insertAdjacentHTML fallback here, exactly as before.
|
|
insert_block_source = _function_source(_ui_js(), "_insertSegmentBlock")
|
|
script = textwrap.dedent(
|
|
f"""
|
|
class FakeClassList {{
|
|
constructor(el) {{ this.el = el; }}
|
|
_set() {{ return new Set(String(this.el.className || '').split(/\\s+/).filter(Boolean)); }}
|
|
contains(name) {{ return this._set().has(name); }}
|
|
add(...names) {{
|
|
const set = this._set();
|
|
names.forEach((name) => set.add(name));
|
|
this.el.className = Array.from(set).join(' ');
|
|
}}
|
|
remove(...names) {{
|
|
const set = this._set();
|
|
names.forEach((name) => set.delete(name));
|
|
this.el.className = Array.from(set).join(' ');
|
|
}}
|
|
}}
|
|
class FakeElement {{
|
|
constructor(tag = 'div') {{
|
|
this.tagName = tag.toUpperCase();
|
|
this.children = [];
|
|
this.parentElement = null;
|
|
this.dataset = {{}};
|
|
this.attributes = {{}};
|
|
this.className = '';
|
|
this.id = '';
|
|
this.hidden = false;
|
|
this.innerHTML = '';
|
|
this.style = {{}};
|
|
this.classList = new FakeClassList(this);
|
|
}}
|
|
appendChild(child) {{
|
|
child.parentElement = this;
|
|
this.children.push(child);
|
|
return child;
|
|
}}
|
|
insertBefore(child, ref) {{
|
|
child.parentElement = this;
|
|
const idx = this.children.indexOf(ref);
|
|
if (idx < 0) this.children.push(child);
|
|
else this.children.splice(idx, 0, child);
|
|
return child;
|
|
}}
|
|
remove() {{
|
|
if (!this.parentElement) return;
|
|
const idx = this.parentElement.children.indexOf(this);
|
|
if (idx >= 0) this.parentElement.children.splice(idx, 1);
|
|
this.parentElement = null;
|
|
}}
|
|
setAttribute(name, value) {{
|
|
this.attributes[name] = String(value);
|
|
if (name === 'id') this.id = String(value);
|
|
if (name === 'class') this.className = String(value);
|
|
if (name.startsWith('data-')) this.dataset[dataKey(name)] = String(value);
|
|
}}
|
|
getAttribute(name) {{
|
|
if (name === 'id') return this.id || null;
|
|
if (name === 'class') return this.className || null;
|
|
if (name.startsWith('data-')) {{
|
|
const value = this.dataset[dataKey(name)];
|
|
return value === undefined ? null : String(value);
|
|
}}
|
|
return this.attributes[name] === undefined ? null : this.attributes[name];
|
|
}}
|
|
removeAttribute(name) {{
|
|
delete this.attributes[name];
|
|
if (name.startsWith('data-')) delete this.dataset[dataKey(name)];
|
|
}}
|
|
matches(selector) {{ return matchesSelector(this, selector); }}
|
|
closest(selector) {{
|
|
let node = this;
|
|
while (node) {{
|
|
if (matchesSelector(node, selector)) return node;
|
|
node = node.parentElement;
|
|
}}
|
|
return null;
|
|
}}
|
|
querySelectorAll(selector) {{
|
|
const found = [];
|
|
const visit = (node) => {{
|
|
for (const child of node.children) {{
|
|
if (matchesSelector(child, selector)) found.push(child);
|
|
visit(child);
|
|
}}
|
|
}};
|
|
visit(this);
|
|
return found;
|
|
}}
|
|
querySelector(selector) {{
|
|
return this.querySelectorAll(selector)[0] || null;
|
|
}}
|
|
insertAdjacentHTML() {{}}
|
|
}}
|
|
function dataKey(name) {{
|
|
return String(name).slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
}}
|
|
function matchesSelector(el, selector) {{
|
|
return String(selector || '').split(',').some((part) => matchesSimple(el, part.trim()));
|
|
}}
|
|
function matchesSimple(el, selector) {{
|
|
if (!selector) return false;
|
|
const negated = [];
|
|
const baseSelector = selector.replace(/:not\\(([^()]*)\\)/g, (_, inner) => {{
|
|
negated.push(String(inner || '').trim());
|
|
return '';
|
|
}}).trim();
|
|
if (negated.some((inner) => inner && matchesSimple(el, inner))) return false;
|
|
if (!baseSelector) return true;
|
|
const classMatches = [...baseSelector.matchAll(/\\.([A-Za-z0-9_-]+)/g)].map((m) => m[1]);
|
|
if (classMatches.some((name) => !el.classList.contains(name))) return false;
|
|
const attrMatches = [...baseSelector.matchAll(/\\[([^=\\]]+)(?:=["']?([^"'\\]]+)["']?)?\\]/g)];
|
|
for (const match of attrMatches) {{
|
|
const value = el.getAttribute(match[1]);
|
|
if (value === null) return false;
|
|
if (match[2] !== undefined && String(value) !== String(match[2])) return false;
|
|
}}
|
|
const idMatch = baseSelector.match(/#([A-Za-z0-9_-]+)/);
|
|
if (idMatch && el.id !== idMatch[1]) return false;
|
|
const tagMatch = baseSelector.match(/^[A-Za-z][A-Za-z0-9_-]*/);
|
|
if (tagMatch && el.tagName.toLowerCase() !== tagMatch[0].toLowerCase()) return false;
|
|
return true;
|
|
}}
|
|
|
|
const elements = {{
|
|
msgInner: new FakeElement('div'),
|
|
emptyState: new FakeElement('div'),
|
|
}};
|
|
global.window = {{}};
|
|
global.document = {{
|
|
createElement: (tag) => new FakeElement(tag),
|
|
getElementById: (id) => elements[id] || null,
|
|
}};
|
|
global.performance = {{ now: () => 1 }};
|
|
global.requestAnimationFrame = (fn) => fn();
|
|
global.setTimeout = (fn) => fn();
|
|
function $(id) {{ return elements[id] || null; }}
|
|
function isTransparentStream() {{ return false; }}
|
|
function isCompactWorklogMode() {{ return true; }}
|
|
function isSimplifiedToolCalling() {{ return true; }}
|
|
function t(key) {{ return key; }}
|
|
function li() {{ return ''; }}
|
|
function esc(value) {{ return String(value == null ? '' : value); }}
|
|
function msgContent(message) {{
|
|
if (Array.isArray(message.content)) {{
|
|
return message.content.filter((p) => p && p.type === 'text').map((p) => p.text || p.content || '').join('\\n');
|
|
}}
|
|
return String(message.content || '');
|
|
}}
|
|
let S;
|
|
const INFLIGHT = {{}};
|
|
let _loadingSessionId = null;
|
|
let _messageRenderWindowSid = null;
|
|
let _messageUserUnpinned = false;
|
|
let _programmaticScroll = false;
|
|
let _programmaticScrollSetAt = 0;
|
|
let _sessionHtmlCacheSid = null;
|
|
let _messagesTruncated = false;
|
|
let _oldestIdx = 0;
|
|
const _sessionHtmlCache = new Map();
|
|
const _recycleStash = new Map();
|
|
const _msgNodeRecycleEnabled = false;
|
|
const _recycleResetAttrs = [];
|
|
const _ERR_MSG_RE = /__never__/;
|
|
|
|
function _captureMessageScrollSnapshot() {{ return null; }}
|
|
function _resetMessageRenderWindow(sid) {{ _messageRenderWindowSid = sid; }}
|
|
function _latestPreservedCompressionTaskListMessages() {{ return []; }}
|
|
function _getVisibleMessagesWithIdx() {{ return S.messages.map((m, rawIdx) => (m && m.role !== 'tool') ? {{ m, rawIdx }} : null).filter(Boolean); }}
|
|
function _messageVirtualKeepTailCount() {{ return 100; }}
|
|
function _currentMessageVirtualWindow(vis) {{ return {{ virtualized: false, start: 0, end: vis.length, topPad: 0, bottomPad: 0, total: vis.length, tailStart: vis.length }}; }}
|
|
function _messageVirtualWindowKeyFor() {{ return 'all'; }}
|
|
function _messageRenderCacheSignature() {{ return 'sig'; }}
|
|
function _compressionStateForCurrentSession() {{ return null; }}
|
|
function clearCompressionUi() {{}}
|
|
function _handoffStateForCurrentSession() {{ return null; }}
|
|
function _captureWorklogDetailDisclosureState() {{ return null; }}
|
|
function _latestCompressionReferenceMessage() {{ return {{ message: null, rawIdx: -1 }}; }}
|
|
function _shouldShowSettledCompressionReference() {{ return false; }}
|
|
function _applySessionNavigationPrefs() {{}}
|
|
function _messageVirtualSpacer() {{ return new FakeElement('div'); }}
|
|
function _compressionAnchorIndex() {{ return null; }}
|
|
function _assistantTurnFinalVisibleContentMap() {{ return new Map(); }}
|
|
function _assistantTurnVisibleContentMap() {{ return new Map(); }}
|
|
function _isPreservedCompressionTaskListMessage() {{ return false; }}
|
|
function _preservedCompressionTaskListCardsHtml() {{ return ''; }}
|
|
function _isContextCompactionMessage() {{ return false; }}
|
|
function _createAssistantTurn() {{
|
|
const turn = new FakeElement('div');
|
|
turn.className = 'assistant-turn';
|
|
const blocks = new FakeElement('div');
|
|
blocks.className = 'assistant-turn-blocks';
|
|
turn.appendChild(blocks);
|
|
return turn;
|
|
}}
|
|
function _assistantTurnBlocks(turn) {{ return turn ? turn.querySelector('.assistant-turn-blocks') : null; }}
|
|
function _setLatestAssistantTurnLandmark() {{}}
|
|
function _assistantRoleHtml() {{ return ''; }}
|
|
function _userMessageDomId(rawIdx) {{ return `user-${{rawIdx}}`; }}
|
|
function _messageSessionIndexForRawIdx(rawIdx) {{ return rawIdx; }}
|
|
function _messageViewportAnchorKeyForMessage() {{ return 'k'; }}
|
|
function _stripAttachedFilesMarkerForDisplay(value) {{ return String(value || ''); }}
|
|
function _stripWorkspaceDisplayPrefix(value) {{ return String(value || ''); }}
|
|
function _stripLeadingAssistantThinkingMarkup(value) {{ return String(value || ''); }}
|
|
function _getCachedRender(value) {{ return String(value || ''); }}
|
|
function _formatInServerTz() {{ return ''; }}
|
|
function _formatMessageFooterTimestamp() {{ return ''; }}
|
|
function _questionJumpButtonHtml() {{ return ''; }}
|
|
function _formatTurnTps() {{ return ''; }}
|
|
function isTpsDisplayEnabled() {{ return false; }}
|
|
function _renderAttachmentHtml() {{ return ''; }}
|
|
function _isMarkerOnlyAssistantCompressionMessage() {{ return false; }}
|
|
function _isAssistantEmptyPlaceholderContent() {{ return false; }}
|
|
function _assistantTurnAnchorSettledFinalAnswer() {{ return null; }}
|
|
function _worklogReasoningTextFromMessage() {{ return ''; }}
|
|
function _assistantMessageBelongsInWorklog() {{ return false; }}
|
|
function _assistantThinkingBelongsInWorklog() {{ return false; }}
|
|
function _assistantReasoningPayloadText() {{ return ''; }}
|
|
function _statusCardHtml() {{ return ''; }}
|
|
function _collectHandoffSummaryStates() {{ return []; }}
|
|
function _insertCompressionLikeNode() {{}}
|
|
function _handoffCardsNode() {{ return null; }}
|
|
function renderCompressionUi() {{}}
|
|
function _assistantToolAnchorIdxForMessage(messages, rawIdx) {{ return rawIdx; }}
|
|
function _cliToolResultSnippet(value) {{ return String(value || ''); }}
|
|
function _cliPatchSnippetFromArgs() {{ return ''; }}
|
|
function _cliToolCardSnippet(value) {{ return String(value || ''); }}
|
|
function _cliToolCardHasDiffSnippet() {{ return false; }}
|
|
function _toolArgsSnapshot(args) {{ return args || {{}}; }}
|
|
function _worklogReasonHtmlFromAnchor() {{ return ''; }}
|
|
function _normalizeThinkingEchoCompare(value) {{ return String(value || ''); }}
|
|
function _toolWorklogListEl(group) {{ return group; }}
|
|
let legacyCards = [];
|
|
function ensureActivityGroup(parent, opts) {{
|
|
const group = new FakeElement('div');
|
|
group.className = 'tool-worklog-group tool-call-group agent-activity-group';
|
|
group.setAttribute('data-legacy-fallback-owner', '1');
|
|
const anchor = opts && opts.anchor;
|
|
if (parent && anchor && anchor.parentElement === parent) parent.insertBefore(group, anchor);
|
|
else if (parent) parent.appendChild(group);
|
|
return group;
|
|
}}
|
|
function _appendWorklogStep(group, anchor, cards) {{
|
|
for (const card of cards || []) {{
|
|
legacyCards.push({{
|
|
tid: card.tid || card.id || card.tool_call_id || '',
|
|
name: card.name || '',
|
|
snippet: String(card.snippet || ''),
|
|
}});
|
|
const row = new FakeElement('div');
|
|
row.className = 'tool-card-row';
|
|
row.setAttribute('data-tool-id', card.tid || card.id || card.tool_call_id || '');
|
|
group.appendChild(row);
|
|
}}
|
|
}}
|
|
function _syncToolCallGroupSummary() {{}}
|
|
function _restoreWorklogDetailDisclosureState() {{}}
|
|
function _scrollAfterMessageRender() {{}}
|
|
function _maybeRecoverVirtualizedBlankViewport() {{ return false; }}
|
|
function _updateMessageVirtualMeasurements() {{}}
|
|
function _resetMessageVirtualMeasurementBurst() {{}}
|
|
function postProcessRenderedMessages() {{}}
|
|
function _postProcessWithAnchorSuppression() {{}}
|
|
function _formatGatewayModelLabel() {{ return ''; }}
|
|
function _gatewayRoutingFailoverText() {{ return ''; }}
|
|
function _gatewayModelWarningText() {{ return ''; }}
|
|
function _usedModelTurnChipLabel() {{ return ''; }}
|
|
function _formatTurnDuration() {{ return ''; }}
|
|
function _loadedCompactionMarkerRawIdxs() {{ return []; }}
|
|
function _selectCompactionCardPlacements() {{
|
|
return {{ preWindowMarkers: [], inlineMarkers: [], taskOwner: null }};
|
|
}}
|
|
function _insertCompactionCardNodes() {{
|
|
return {{ insertedNodes: [], taskOwnerNode: null }};
|
|
}}
|
|
function _insertPreservedCompressionTaskFallback() {{ return false; }}
|
|
function _pinCompactionCardAtTop() {{ return false; }}
|
|
function _renderSettledAnchorSceneForMessage(message, segment, rawIdx) {{
|
|
const group = new FakeElement('div');
|
|
group.className = 'tool-worklog-group agent-activity-group';
|
|
group.setAttribute('data-anchor-settled-scene-owner', '1');
|
|
const blocks = _assistantTurnBlocks(segment.closest('.assistant-turn'));
|
|
if (blocks) blocks.insertBefore(group, segment);
|
|
return true;
|
|
}}
|
|
|
|
eval({json.dumps(transparent_source)});
|
|
eval({json.dumps(legacy_metadata_source)});
|
|
eval({json.dumps(insert_block_source)});
|
|
eval({json.dumps(render_source)});
|
|
|
|
const toolResult = {{ role: 'tool', tool_call_id: 'toolu_1', content: 'tool result' }};
|
|
const selectorSanityElement = new FakeElement('div');
|
|
selectorSanityElement.className = 'tool-worklog-group anchor-owned';
|
|
selectorSanityElement.setAttribute('data-owner', 'anchor');
|
|
const selectorSanity = {{
|
|
positive: selectorSanityElement.matches('.tool-worklog-group:not(.legacy-owner)[data-owner="anchor"]'),
|
|
negatedClass: selectorSanityElement.matches('.tool-worklog-group:not(.anchor-owned)'),
|
|
negatedAttr: selectorSanityElement.matches('.tool-worklog-group:not([data-owner="anchor"])'),
|
|
}};
|
|
const legacyToolCall = {{
|
|
id: 'toolu_1',
|
|
function: {{ name: 'terminal', arguments: '{{"cmd":"git status"}}' }},
|
|
}};
|
|
const legacyPartial = {{ id: 'partial_1', name: 'terminal', args: {{ cmd: 'pwd' }}, snippet: 'partial result' }};
|
|
const legacyContentTool = {{ type: 'tool_use', id: 'content_1', name: 'terminal', input: {{ cmd: 'ls' }} }};
|
|
const anchorOwned = {{
|
|
role: 'assistant',
|
|
content: [{{ type: 'text', text: 'Anchor final answer' }}, legacyContentTool],
|
|
tool_calls: [legacyToolCall],
|
|
_partial_tool_calls: [legacyPartial],
|
|
_anchor_activity_scene: {{
|
|
version: 'activity_scene_v1',
|
|
activity_rows: [{{ id: 'row1', kind: 'tool', role: 'tool', tool: {{ name: 'terminal' }} }}],
|
|
final_answer: 'Anchor final answer',
|
|
}},
|
|
}};
|
|
S = {{
|
|
session: {{ session_id: 's1', tool_calls: [{{ tid: 'toolu_1', snippet: 'persisted result' }}] }},
|
|
messages: [{{ role: 'user', content: 'run' }}, anchorOwned, toolResult],
|
|
toolCalls: [{{ tid: 'toolu_1', assistant_msg_idx: 1, name: 'terminal', snippet: 'session fallback' }}],
|
|
busy: false,
|
|
}};
|
|
renderMessages();
|
|
const anchorSummary = {{
|
|
anchorGroups: elements.msgInner.querySelectorAll('[data-anchor-settled-scene-owner]').length,
|
|
legacyGroups: elements.msgInner.querySelectorAll('[data-legacy-fallback-owner]').length,
|
|
legacyRows: elements.msgInner.querySelectorAll('.tool-card-row').length,
|
|
legacyCards,
|
|
sToolCalls: S.toolCalls.length,
|
|
}};
|
|
|
|
elements.msgInner = new FakeElement('div');
|
|
legacyCards = [];
|
|
const historical = {{
|
|
role: 'assistant',
|
|
content: 'Historical answer',
|
|
}};
|
|
S = {{
|
|
session: {{ session_id: 's2', tool_calls: [{{ tid: 'toolu_1', snippet: 'persisted result' }}] }},
|
|
messages: [{{ role: 'user', content: 'run' }}, historical, toolResult],
|
|
toolCalls: [{{ tid: 'toolu_1', assistant_msg_idx: 1, name: 'terminal', snippet: 'session fallback' }}],
|
|
busy: false,
|
|
}};
|
|
renderMessages();
|
|
const historicalSummary = {{
|
|
anchorGroups: elements.msgInner.querySelectorAll('[data-anchor-settled-scene-owner]').length,
|
|
legacyGroups: elements.msgInner.querySelectorAll('[data-legacy-fallback-owner]').length,
|
|
legacyRows: elements.msgInner.querySelectorAll('.tool-card-row').length,
|
|
legacyCards,
|
|
sToolCalls: S.toolCalls.length,
|
|
}};
|
|
|
|
elements.msgInner = new FakeElement('div');
|
|
legacyCards = [];
|
|
const rawHistorical = {{
|
|
role: 'assistant',
|
|
content: [{{ type: 'text', text: 'Historical raw answer' }}, legacyContentTool],
|
|
tool_calls: [legacyToolCall],
|
|
_partial_tool_calls: [legacyPartial],
|
|
}};
|
|
S = {{
|
|
session: {{
|
|
session_id: 's3',
|
|
tool_calls: [{{ tid: 'content_1', snippet: 'persisted content result' }}],
|
|
}},
|
|
messages: [{{ role: 'user', content: 'run' }}, rawHistorical, toolResult],
|
|
toolCalls: [],
|
|
busy: false,
|
|
}};
|
|
renderMessages();
|
|
const rawHistoricalSummary = {{
|
|
anchorGroups: elements.msgInner.querySelectorAll('[data-anchor-settled-scene-owner]').length,
|
|
legacyGroups: elements.msgInner.querySelectorAll('[data-legacy-fallback-owner]').length,
|
|
legacyRows: elements.msgInner.querySelectorAll('.tool-card-row').length,
|
|
legacyCards,
|
|
sToolCalls: S.toolCalls.length,
|
|
}};
|
|
|
|
elements.msgInner = new FakeElement('div');
|
|
legacyCards = [];
|
|
const duplicateAnchorTool = {{ type: 'tool_use', id: 'toolu_anchor_dup', name: 'terminal', input: {{ cmd: 'anchor' }} }};
|
|
const duplicateHistoricalTool = {{ type: 'tool_use', id: 'toolu_hist_dup', name: 'terminal', input: {{ cmd: 'history' }} }};
|
|
const duplicateAnchorCall = {{
|
|
id: 'toolu_anchor_dup',
|
|
function: {{ name: 'terminal', arguments: '{{"cmd":"anchor"}}' }},
|
|
}};
|
|
const duplicateAnchorOwned = {{
|
|
role: 'assistant',
|
|
content: [{{ type: 'text', text: 'Duplicate answer' }}, duplicateAnchorTool],
|
|
tool_calls: [duplicateAnchorCall],
|
|
_anchor_activity_scene: {{
|
|
version: 'activity_scene_v1',
|
|
activity_rows: [{{ id: 'row-dup', kind: 'tool', role: 'tool', tool: {{ name: 'terminal' }} }}],
|
|
final_answer: 'Duplicate answer',
|
|
}},
|
|
}};
|
|
const duplicateHistorical = {{
|
|
role: 'assistant',
|
|
content: [{{ type: 'text', text: 'Duplicate answer' }}, duplicateHistoricalTool],
|
|
}};
|
|
S = {{
|
|
session: {{
|
|
session_id: 's4',
|
|
tool_calls: [{{ tid: 'toolu_hist_dup', snippet: 'historical persisted result' }}],
|
|
}},
|
|
messages: [
|
|
{{ role: 'user', content: 'anchor turn' }},
|
|
duplicateAnchorOwned,
|
|
{{ role: 'user', content: 'historical turn' }},
|
|
duplicateHistorical,
|
|
toolResult,
|
|
],
|
|
toolCalls: [{{ tid: 'toolu_anchor_dup', assistant_msg_idx: 1, name: 'terminal', snippet: 'anchor session fallback' }}],
|
|
busy: false,
|
|
}};
|
|
renderMessages();
|
|
const duplicateReferenceSummary = {{
|
|
anchorGroups: elements.msgInner.querySelectorAll('[data-anchor-settled-scene-owner]').length,
|
|
legacyGroups: elements.msgInner.querySelectorAll('[data-legacy-fallback-owner]').length,
|
|
legacyRows: elements.msgInner.querySelectorAll('.tool-card-row').length,
|
|
legacyCards,
|
|
sToolCalls: S.toolCalls.length,
|
|
}};
|
|
|
|
console.log(JSON.stringify({{
|
|
selectorSanity,
|
|
anchorSummary,
|
|
historicalSummary,
|
|
rawHistoricalSummary,
|
|
duplicateReferenceSummary,
|
|
}}));
|
|
"""
|
|
)
|
|
|
|
result = json.loads(_run_node_script(script))
|
|
|
|
assert result["selectorSanity"] == {
|
|
"positive": True,
|
|
"negatedClass": False,
|
|
"negatedAttr": False,
|
|
}
|
|
assert result["anchorSummary"] == {
|
|
"anchorGroups": 1,
|
|
"legacyGroups": 0,
|
|
"legacyRows": 0,
|
|
"legacyCards": [],
|
|
"sToolCalls": 1,
|
|
}
|
|
assert result["historicalSummary"]["anchorGroups"] == 0
|
|
assert result["historicalSummary"]["legacyGroups"] == 1
|
|
assert result["historicalSummary"]["legacyRows"] >= 1
|
|
assert result["historicalSummary"]["sToolCalls"] >= 1
|
|
assert [card["tid"] for card in result["historicalSummary"]["legacyCards"]] == [
|
|
"toolu_1"
|
|
]
|
|
|
|
raw_cards = result["rawHistoricalSummary"]["legacyCards"]
|
|
raw_tids = {card["tid"] for card in raw_cards}
|
|
raw_snippets = {card["tid"]: card["snippet"] for card in raw_cards}
|
|
assert result["rawHistoricalSummary"]["anchorGroups"] == 0
|
|
assert result["rawHistoricalSummary"]["legacyGroups"] == 1
|
|
assert result["rawHistoricalSummary"]["legacyRows"] >= 3
|
|
assert result["rawHistoricalSummary"]["sToolCalls"] >= 3
|
|
assert {"toolu_1", "partial_1", "content_1"}.issubset(raw_tids)
|
|
assert raw_snippets["toolu_1"] == "tool result"
|
|
assert raw_snippets["partial_1"] == "partial result"
|
|
assert raw_snippets["content_1"] == "persisted content result"
|
|
|
|
duplicate_cards = result["duplicateReferenceSummary"]["legacyCards"]
|
|
assert result["duplicateReferenceSummary"]["anchorGroups"] == 1
|
|
assert result["duplicateReferenceSummary"]["legacyGroups"] == 1
|
|
assert result["duplicateReferenceSummary"]["legacyRows"] >= 1
|
|
assert result["duplicateReferenceSummary"]["sToolCalls"] >= 1
|
|
assert {card["tid"] for card in duplicate_cards} == {"toolu_hist_dup"}
|
|
assert "toolu_anchor_dup" not in {card["tid"] for card in duplicate_cards}
|
|
assert duplicate_cards[0]["snippet"] == "historical persisted result"
|
|
|
|
|
|
def test_settled_legacy_tool_rebuild_excludes_anchor_owned_turns():
|
|
render = _function_body(_ui_js(), "renderMessages")
|
|
|
|
set_decl = render.index("const anchorOwnedAssistantRawIdxs=new Set();")
|
|
collect_segments = render.index("turn.querySelectorAll('.assistant-segment[data-msg-idx]')")
|
|
metadata_scan = render.index("const hasMessageToolMetadata=")
|
|
fallback_sources = render.index("const fallbackToolSources=[];")
|
|
source_collect = render.index("fallbackToolSources.push({m,rawIdx});")
|
|
|
|
assert set_decl < collect_segments < metadata_scan < fallback_sources < source_collect
|
|
assert "S.messages.indexOf(m)" not in render
|
|
assert "S.messages.some((m,rawIdx)=>" in render
|
|
assert "!anchorOwnedAssistantRawIdxs.has(rawIdx)&&_legacySettledFallbackHasToolMetadata(m)" in render
|
|
assert "if(anchorOwnedAssistantRawIdxs.has(rawIdx)) return;" in render
|
|
|
|
|
|
def test_settled_legacy_activity_buckets_skip_anchor_owned_turns_before_rendering():
|
|
render = _function_body(_ui_js(), "renderMessages")
|
|
|
|
tool_loop = render.index("for(const tc of (S.toolCalls||[])){")
|
|
tool_skip = render.index("if(anchorOwnedAssistantRawIdxs.has(aIdx)) continue;", tool_loop)
|
|
thinking_loop = render.index("for(const aIdx of assistantThinking.keys()){")
|
|
thinking_skip = render.index("if(anchorOwnedAssistantRawIdxs.has(aIdx)) continue;", thinking_loop)
|
|
worklog_loop = render.index("for(const [aIdx,seg] of assistantSegments){")
|
|
worklog_skip = render.index("if(anchorOwnedAssistantRawIdxs.has(aIdx)) continue;", worklog_loop)
|
|
anchor_render = render.index("_renderSettledAnchorSceneForMessage(msg, seg, rawIdx)")
|
|
|
|
assert tool_loop < tool_skip < thinking_loop < thinking_skip < worklog_loop < worklog_skip
|
|
assert worklog_skip < anchor_render
|
|
|
|
|
|
def test_anchor_settled_renderers_remain_the_primary_scene_path():
|
|
settled = _function_body(_ui_js(), "_renderSettledAnchorSceneForMessage")
|
|
transparent = _function_body(
|
|
_ui_js(),
|
|
"_renderSettledAnchorSceneTransparentForMessage",
|
|
)
|
|
|
|
assert "if(!message||!message._anchor_activity_scene||!segment) return false;" in settled
|
|
assert "return _renderSettledAnchorSceneTransparentForMessage(message,segment,rawIdx);" in settled
|
|
assert "_anchorSceneRowsForRendering(scene,{settled:true})" in settled
|
|
assert "group.setAttribute('data-anchor-settled-scene-owner','1');" in settled
|
|
|
|
assert "if(!message||!message._anchor_activity_scene||!segment) return false;" in transparent
|
|
assert "_anchorSceneRowsForRendering(scene,{settled:true})" in transparent
|
|
assert "const lastNonTerminalWorkRowIndex=_anchorSceneLastNonTerminalWorkRowIndex(rows);" in transparent
|
|
assert "liveTokenFinalPrefixEligible:idx>lastNonTerminalWorkRowIndex" in transparent
|