test(tui): correct the nudge the internal-turn test asserts (#1016)

* correct the nudge the internal-turn test asserts

The test expected "ended the autonomous Strix run", which strix.core.execution
does not inject; it says "ended the autonomous run". The classifier was right and
the test was not, so the suite failed on main while the behaviour it guards was
fine.

The sentence is written inline in another module and copied by hand into the
classifier and again into the test, which is how it drifted. A second test now
reads it back out of that module's source, joining the adjacent string literals
its line wrapping leaves behind, and fails if either nudge is no longer injected
verbatim. Reworded one and it reports which nudge went missing and what a resumed
scan would do about it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* read the nudges out of what the module can inject, not out of its text

Searching the source accepted the sentence anywhere in the file, so a stale copy
left behind in a comment would have kept the guard passing after the message it
guards had changed - the drift it exists to catch.

Parsing the module instead limits it to strings the code can actually inject.
Comments never reach the tree, docstrings are dropped as description rather than
behaviour, and adjacent literals are joined during parsing, which the line
wrapping needed and the regex was only approximating.

Checked by rewording the message and leaving the old wording in a comment: the
guard fails, where searching the text passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ahmed Allam
2026-08-08 22:34:45 +03:00
committed by GitHub
co-authored by Claude Opus 4.8
parent b260a4ee38
commit 0b9e029a5d
+59 -3
View File
@@ -7,19 +7,26 @@ shows what the user actually typed; resuming has to match that.
from __future__ import annotations
import ast
import json
import sqlite3
from pathlib import Path
from typing import TYPE_CHECKING, Any
import pytest
from strix.core import execution
from strix.core.paths import runtime_state_dir
from strix.interface.tui.backend.live_view import TuiLiveView as GoTuiLiveView
from strix.interface.tui.live_view import TuiLiveView, _is_internal_agent_turn
from strix.interface.tui.live_view import (
_INTERNAL_TURN_PREFIXES,
TuiLiveView,
_is_internal_agent_turn,
)
if TYPE_CHECKING:
from pathlib import Path
from types import ModuleType
def _write_run(run_dir: Path, items: list[dict[str, Any]], agent_id: str = "root") -> None:
@@ -176,11 +183,60 @@ def test_internal_turn_classifier_matches_every_injected_form() -> None:
"[CRITICAL] Turn budget: 480/500 used (96%).",
"== Inherited context from parent (background only) ==",
"Your previous message ended a turn without a tool call.",
"Your previous response ended the autonomous Strix run without a lifecycle tool call.",
"Your previous response ended the autonomous run without a lifecycle tool call.",
):
assert _is_internal_agent_turn(content), content
def _injected_strings(module: ModuleType) -> list[str]:
"""Every string a module can inject, and nothing it merely mentions.
Parsing rather than searching the text keeps comments out of it, so a stale
copy of a message left in a comment cannot pass for the message itself. It
also joins adjacent literals for free, which the line wrapping needs, and
docstrings are dropped because they describe the code rather than run in it.
"""
tree = ast.parse(Path(module.__file__ or "").read_text(encoding="utf-8"))
docstrings = set()
for node in ast.walk(tree):
if not isinstance(node, ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef):
continue
first = node.body[0] if node.body else None
if isinstance(first, ast.Expr) and isinstance(first.value, ast.Constant):
docstrings.add(id(first.value))
literals: list[str] = []
for node in ast.walk(tree):
if isinstance(node, ast.Constant):
if isinstance(node.value, str) and id(node) not in docstrings:
literals.append(node.value)
elif isinstance(node, ast.JoinedStr):
literals.append(
"".join(
part.value
for part in node.values
if isinstance(part, ast.Constant) and isinstance(part.value, str)
)
)
return literals
def test_internal_turn_prefixes_still_match_what_is_injected() -> None:
"""The classifier copies sentences out of another module, so they can drift.
Both nudges are written inline in strix.core.execution, so there is nothing to
import and compare against. Read them back out of what that module can inject.
"""
injected = _injected_strings(execution)
nudges = [prefix for prefix in _INTERNAL_TURN_PREFIXES if prefix.startswith("Your previous")]
assert nudges, "the no-tool-call nudges are no longer in the classifier"
for nudge in nudges:
assert any(nudge in literal for literal in injected), (
f"the classifier expects {nudge!r}, which strix.core.execution no longer "
f"injects. A resumed scan would show that nudge as the user's own message."
)
def test_internal_turn_classifier_keeps_bracketed_user_text() -> None:
"""A leading bracket is not enough: typed text often starts with one."""
for content in (