## Release v0.51.261 — Release IC (stage-r11) Live Todos panel via an explicit `todo_state` SSE contract. ### Fixed | Issue/PR | Author | Fix | |----------|--------|-----| | #3373 follow-up (#3454) | @v2psv | The Todos side panel now tracks `todo` tool state **live during an active run** instead of staying stale until settle / rolling back on a mid-stream reload. A dedicated `todo_state` SSE event sends a full, redacted, idempotent snapshot on todo-tool completion (no more truncated `tool_complete.preview`); the same `api.todo_state` parser feeds live + cold-load; live snapshots persist into INFLIGHT so reload/reattach restores the panel; cold-load vs INFLIGHT reconciled by timestamp (incl. the `coldTs===0` compressed-session edge); legacy reverse-scan kept as fallback for old servers. | ### Gate - Full pytest suite: **7692 passed, 0 failed** - ESLint: CLEAN · ruff: CLEAN · browser-smoke: CLEAN - Codex (regression): **SAFE TO SHIP** — verified the new `todo_state` SSE handler composes with existing dispatch (no double-subscribe), INFLIGHT persistence is cleared on terminal/cancel (composes with discard_session + turn-journal), timestamp reconciliation can't let a stale local snapshot win, redaction holds, the legacy reverse-scan fallback still works with no double-render, and the `models.py` change is todo-scoped (no CLI-classification interaction). Co-authored-by: v2psv <v2psv@users.noreply.github.com>
45 lines
1.3 KiB
Python
45 lines
1.3 KiB
Python
from __future__ import annotations
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
|
|
STREAMING_PY = Path(__file__).parent.parent / "api" / "streaming.py"
|
|
|
|
|
|
def _emit_todo_state_calls() -> list[ast.Call]:
|
|
tree = ast.parse(STREAMING_PY.read_text(encoding="utf-8"))
|
|
calls: list[ast.Call] = []
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "emit_todo_state":
|
|
calls.append(node)
|
|
return calls
|
|
|
|
|
|
def test_streaming_imports_todo_state_emitter():
|
|
tree = ast.parse(STREAMING_PY.read_text(encoding="utf-8"))
|
|
|
|
assert any(
|
|
isinstance(node, ast.ImportFrom)
|
|
and node.module == "api.todo_state"
|
|
and any(alias.name == "emit_todo_state" for alias in node.names)
|
|
for node in ast.walk(tree)
|
|
)
|
|
|
|
|
|
def test_streaming_emits_todo_state_on_both_tool_callback_shapes():
|
|
calls = _emit_todo_state_calls()
|
|
|
|
assert len(calls) >= 2
|
|
for call in calls[:2]:
|
|
kw_names = {kw.arg for kw in call.keywords}
|
|
assert {"name", "function_result", "session_id", "stream_id"}.issubset(kw_names)
|
|
|
|
|
|
def test_streaming_prefers_full_tool_result_when_available():
|
|
src = STREAMING_PY.read_text(encoding="utf-8")
|
|
|
|
assert "cb_kwargs.get('result')" in src
|
|
assert "else preview" in src
|
|
assert "function_result=function_result" in src
|