Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 489cfee1f0 |
@@ -32,9 +32,9 @@ separate dev team rather than an in-tree Worldtree tool.
|
|||||||
|
|
||||||
## Current state / in-flight
|
## Current state / in-flight
|
||||||
|
|
||||||
_As of 2026-05-25 (post-v0.8.1 text streams inline, no overlap):_
|
_As of 2026-05-25 (post-v0.8.2 drop double-print; v0.9.0 live-md next):_
|
||||||
|
|
||||||
**Status: v0.8.1 shipped.** Eleven core features complete (`sse_client`
|
**Status: v0.8.2 shipped.** Eleven core features complete (`sse_client`
|
||||||
#1, `sessions` #2, `cli` #3, `tui` #4, `--end-user-id` #5, TUI
|
#1, `sessions` #2, `cli` #3, `tui` #4, `--end-user-id` #5, TUI
|
||||||
startup error visibility #6, presenter contract semantics amendment
|
startup error visibility #6, presenter contract semantics amendment
|
||||||
#12, startup agent picker #8, §5 layout reshape + Tools pane #13)
|
#12, startup agent picker #8, §5 layout reshape + Tools pane #13)
|
||||||
@@ -51,7 +51,8 @@ Static in the footer (static "Tools" v1; dynamic when more tabs
|
|||||||
land). CLI mode (--send) unaffected by design — INV-018.
|
land). CLI mode (--send) unaffected by design — INV-018.
|
||||||
|
|
||||||
Last commits on `main`:
|
Last commits on `main`:
|
||||||
- v0.8.1 fix(tui): kill current-text Static; Text streams inline via coalesce
|
- v0.8.2 fix(tui): drop post-Done Markdown body re-render (no double-print)
|
||||||
|
- `11ef683` fix(tui,sse): inline Text streaming + empty-id keepalive skip (v0.8.1)
|
||||||
- `9fade55` feat(local_agents): JSON-backed local tier-3 index + picker merge (v0.8.0)
|
- `9fade55` feat(local_agents): JSON-backed local tier-3 index + picker merge (v0.8.0)
|
||||||
- `9918c10` fix(tui): coalesce thinking deltas on `\n` (v0.7.1)
|
- `9918c10` fix(tui): coalesce thinking deltas on `\n` (v0.7.1)
|
||||||
- `c086ae2` feat(tier3): ratatoskr.tier3 module + CLI (v0.7.0)
|
- `c086ae2` feat(tier3): ratatoskr.tier3 module + CLI (v0.7.0)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ratatoskr"
|
name = "ratatoskr"
|
||||||
version = "0.8.1"
|
version = "0.8.2"
|
||||||
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
+7
-12
@@ -310,18 +310,13 @@ class TuiPresenterState:
|
|||||||
f"usage {_format_usage(event.usage, arrow='→')}",
|
f"usage {_format_usage(event.usage, arrow='→')}",
|
||||||
style=_AU_SUCCESS,
|
style=_AU_SUCCESS,
|
||||||
))
|
))
|
||||||
# v0.8.1: in non-raw mode, ALSO write Rule + Markdown body
|
# v0.8.2: post-Done Markdown body re-render dropped. Pre-
|
||||||
# as the canonical rendered version. The streamed lines
|
# v0.8.2 the transcript got BOTH the streamed text AND
|
||||||
# above are plain text; the Markdown body re-renders the
|
# the Markdown(response) re-render — same content twice,
|
||||||
# same content with proper formatting (lists, bold, code
|
# operator-flagged as "double prints". The streamed text
|
||||||
# blocks). Some duplication is acceptable — the streamed
|
# IS the response now; markdown formatting (bold, lists,
|
||||||
# content gave live progress; the Markdown is the final.
|
# code) renders as plain text. Matches thinking pane's
|
||||||
if not raw:
|
# stream-as-content semantics (no post-close re-render).
|
||||||
from rich.markdown import Markdown
|
|
||||||
from rich.rule import Rule
|
|
||||||
|
|
||||||
log.write(Rule(style=_AU_DEMOTED))
|
|
||||||
log.write(Markdown(event.response))
|
|
||||||
elif isinstance(event, Error):
|
elif isinstance(event, Error):
|
||||||
log.write(RichText(
|
log.write(RichText(
|
||||||
f"[error] turn_id={event.sse_id.turn_id} code={event.error_code} "
|
f"[error] turn_id={event.sse_id.turn_id} code={event.error_code} "
|
||||||
|
|||||||
+22
-18
@@ -333,11 +333,12 @@ class TestTuiPresenterState:
|
|||||||
# thinking_log got at least Rule(start) + "partial" delta + Rule(end)
|
# thinking_log got at least Rule(start) + "partial" delta + Rule(end)
|
||||||
assert thinking_log.write.call_count >= 3
|
assert thinking_log.write.call_count >= 3
|
||||||
|
|
||||||
def test_done_renders_markdown_after_label(self) -> None:
|
def test_done_flushes_tail_and_writes_label(self) -> None:
|
||||||
"""done_renders_markdown_after_label [happy, v0.8.1]:
|
"""done_flushes_tail_and_writes_label [happy, v0.8.2]:
|
||||||
Text("hi") buffers in text_chunk_buffer (no `\\n`). Done flushes
|
Text("hi") buffers in text_chunk_buffer (no `\\n`). Done flushes
|
||||||
"hi" as a tail line in transcript, then writes [done] + Rule +
|
"hi" tail to transcript, then writes [done] label. v0.8.2 drops
|
||||||
Markdown body (non-raw).
|
the post-Done Markdown body re-render — streamed text is the
|
||||||
|
canonical content (no double-print).
|
||||||
"""
|
"""
|
||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
from rich.rule import Rule
|
from rich.rule import Rule
|
||||||
@@ -354,7 +355,6 @@ class TestTuiPresenterState:
|
|||||||
thinking_log=MagicMock(),
|
thinking_log=MagicMock(),
|
||||||
raw=False,
|
raw=False,
|
||||||
)
|
)
|
||||||
# v0.8.1: Text "hi" stays buffered (no `\n` yet) — no log write yet.
|
|
||||||
assert not log.write.called
|
assert not log.write.called
|
||||||
assert state.text_chunk_buffer == "hi"
|
assert state.text_chunk_buffer == "hi"
|
||||||
state.render(
|
state.render(
|
||||||
@@ -365,12 +365,13 @@ class TestTuiPresenterState:
|
|||||||
thinking_log=MagicMock(),
|
thinking_log=MagicMock(),
|
||||||
raw=False,
|
raw=False,
|
||||||
)
|
)
|
||||||
# On Done: tail flush + [done] + Rule + Markdown body.
|
# On Done: tail flush "hi" + [done] label. No Markdown, no Rule.
|
||||||
writes = [c[0][0] for c in log.write.call_args_list]
|
writes = [c[0][0] for c in log.write.call_args_list]
|
||||||
assert "hi" in writes
|
assert "hi" in writes
|
||||||
assert any(_text_of(w).startswith("[done]") for w in writes)
|
assert any(_text_of(w).startswith("[done]") for w in writes)
|
||||||
assert any(isinstance(w, Rule) for w in writes)
|
# v0.8.2: no post-Done re-render — no duplicate content.
|
||||||
assert any(isinstance(w, Markdown) for w in writes)
|
assert not any(isinstance(w, Markdown) for w in writes)
|
||||||
|
assert not any(isinstance(w, Rule) for w in writes)
|
||||||
assert state.text_chunk_buffer == ""
|
assert state.text_chunk_buffer == ""
|
||||||
|
|
||||||
def test_raw_flag_skips_markdown(self) -> None:
|
def test_raw_flag_skips_markdown(self) -> None:
|
||||||
@@ -1059,11 +1060,12 @@ async def _submit_and_wait(app: RatatoskrApp, pilot, content: str) -> None:
|
|||||||
|
|
||||||
class TestStreamTurnWorker:
|
class TestStreamTurnWorker:
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_happy_text_done_renders_markdown(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
async def test_happy_text_done_no_double_print(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
"""happy_text_done_renders_markdown [happy,tracer, v0.8.1]:
|
"""happy_text_done_no_double_print [happy,tracer, v0.8.2]:
|
||||||
Text("hello") buffers in text_chunk_buffer (no `\\n`); on Done,
|
Text("hello") buffers; on Done, "hello" flushes as tail to transcript
|
||||||
flushes "hello" tail to transcript, then [done] label, then Rule
|
+ [done] label. v0.8.2 drops the post-Done Markdown body re-render
|
||||||
+ Markdown body.
|
(was double-printing the response — streamed text + Markdown twice).
|
||||||
|
Only the turn-header Rule remains in the transcript.
|
||||||
"""
|
"""
|
||||||
stream = _sse_chunk("42:1", {"type": "text", "content": "hello"}) + _sse_chunk(
|
stream = _sse_chunk("42:1", {"type": "text", "content": "hello"}) + _sse_chunk(
|
||||||
"42:2", _DONE_BODY
|
"42:2", _DONE_BODY
|
||||||
@@ -1080,14 +1082,16 @@ class TestStreamTurnWorker:
|
|||||||
await _submit_and_wait(app, pilot, "hi")
|
await _submit_and_wait(app, pilot, "hi")
|
||||||
assert app.state == "idle"
|
assert app.state == "idle"
|
||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
from rich.rule import Rule
|
|
||||||
|
|
||||||
# v0.8.1: "hello" appears in transcript as a tail-flush on Done.
|
# "hello" appears as a tail-flush; [done] label fires.
|
||||||
assert any(w == "hello" for w in writes)
|
assert any(w == "hello" for w in writes)
|
||||||
assert any("[done]" in str(w) for w in writes)
|
assert any("[done]" in str(w) for w in writes)
|
||||||
# Post-Done: Markdown body + Rule + turn-header Rule all present.
|
# v0.8.2: NO Markdown body re-render (was the duplicate).
|
||||||
assert any(isinstance(w, Markdown) for w in writes)
|
assert not any(isinstance(w, Markdown) for w in writes)
|
||||||
assert any(isinstance(w, Rule) for w in writes)
|
# The turn-header Rule is written to all 4 panes; we still expect
|
||||||
|
# SOME Rules in the spy (one per pane), but NOT the post-Done
|
||||||
|
# separator Rule that pre-v0.8.2 wrote.
|
||||||
|
# We rely on _spy_writes counting turn-header Rules only.
|
||||||
|
|
||||||
@respx.mock
|
@respx.mock
|
||||||
async def test_raw_flag_skips_markdown_render(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
async def test_raw_flag_skips_markdown_render(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user