Clears the two ✗ FAIL (missing STEPS) the v2.1 parser surfaced. #3: faithful STEPS for CliPresenterState.render, _format_duration_ms, _format_usage (the two formatters also gain PRE/POST from their real asserts). render STEPS enumerate AffectUpdate + AwaitingLlmFirstToken as demoted telemetry (Worldtree #204/#201), extending POST-005 beyond the issue #12 set. #4: refresh the TUI presenter contract from the abandoned single-RichLog double-display model to the shipped four-pane live-Markdown model (v0.5.0-v0.14.0 + Worldtree #201/#204). Rewrites TuiPresenterState.render and _stream_turn_worker (signature, POSTs, STEPS, TESTS), INV-005, the [performance] constraint, the COMPOSE sketch, the CLASS block (BRIEF/PROPERTIES/INV-WIRE-002), the resolved open_question, and the _cancel_via_sse call site. Verified against src/ratatoskr/tui.py and the real test names in tests/test_tui.py. Both contracts: 0 validation errors (pre-existing multi-tracer warnings on _run_turn / action_interrupt left untouched).
This commit is contained in:
@@ -390,6 +390,30 @@ POST: [POST-004 side_effect] for Done/Error/Cancelled: if text_written_since_new
|
||||
POST: [POST-005 side_effect] for demoted telemetry (WorkerPhase, TextBoundary, ToolStart, ToolResult): write `. <label>: <fields>\n` to stderr
|
||||
ERROR_ROUTING:
|
||||
(none at this level — pure dispatch over the typed union)
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] Validate event is one of the Event union variants per PRE-001.
|
||||
2. [branch, flexibility=prescriptive] IF isinstance(event, Thinking): # POST-001 — coalesce into the open run
|
||||
IF NOT self.thinking_open: WRITE ". thinking: " to stderr; SET self.thinking_open=True
|
||||
WRITE event.content to stderr; FLUSH; APPEND event.content to self.thinking_buffer
|
||||
RETURN
|
||||
3. [branch, flexibility=prescriptive] IF self.thinking_open (current event is non-Thinking): # POST-002 — close the run before rendering
|
||||
WRITE "\n" to stderr; FLUSH; SET self.thinking_open=False; CLEAR self.thinking_buffer
|
||||
4. [branch, flexibility=prescriptive] IF isinstance(event, Text): # POST-003
|
||||
WRITE event.content to stdout; FLUSH
|
||||
SET self.text_written_since_newline = not event.content.endswith("\n") # Volva F4 — only flag a mid-line cursor
|
||||
RETURN
|
||||
5. [branch, flexibility=prescriptive] IF isinstance(event, (Done, Error, Cancelled)) AND self.text_written_since_newline: # POST-004 / INV-005 stdout boundary
|
||||
WRITE "\n" to stdout; FLUSH; SET self.text_written_since_newline=False
|
||||
6. [branch, flexibility=prescriptive] Dispatch the non-Thinking event to exactly one labeled stderr line, then RETURN:
|
||||
Done -> "[done] turn_id={sse_id.turn_id} model={model} duration={_format_duration_ms(duration_ms)} usage {_format_usage(usage, arrow='->')}" # load-bearing, no demotion prefix (POST-004)
|
||||
Error -> "[error] turn_id={sse_id.turn_id} code={error_code} message={message!r}" # load-bearing (POST-004)
|
||||
Cancelled -> "[cancelled] turn_id={turn_id} reason={reason!r} partial_message_id={partial_message_id}" # load-bearing (POST-004)
|
||||
WorkerPhase -> ". worker_phase: phase={phase} turn_id={turn_id}" # demoted (POST-005)
|
||||
ToolStart -> ". tool_start: name={name} args={arguments!r}" # demoted (POST-005)
|
||||
ToolResult -> ". tool_result: name={name} duration_ms={duration_ms} result={result!r:.200}" # demoted, 200-char cap (POST-005)
|
||||
TextBoundary -> ". text_boundary: kind={kind} char_offset={char_offset}" # demoted (POST-005)
|
||||
AffectUpdate -> ". affect_update: status={status} turn_id={turn_id} [dominant_emotion={...}]" # Worldtree #204 demoted telemetry — extends POST-005 beyond the issue #12 set
|
||||
AwaitingLlmFirstToken -> ". awaiting_llm_first_token: turn_id={turn_id} elapsed={secs:.1f}s" # Worldtree #201 demoted telemetry — extends POST-005 beyond the issue #12 set
|
||||
TESTS:
|
||||
thinking_coalesce_single_run [happy,tracer]: Thinking("hello"), Thinking(" world"), Done → stderr has ". thinking: hello world\n" then "[done] ..."; no demotion prefix on [done]
|
||||
thinking_closes_on_first_non_thinking_event [happy]: Thinking, WorkerPhase → ". thinking: ...\n" then ". worker_phase: ..."
|
||||
@@ -413,6 +437,13 @@ TESTS:
|
||||
```contract
|
||||
FN _format_duration_ms(ms: int) -> str # issue #12 INV-006 helper
|
||||
BRIEF: Auto-scale duration formatting. ms<1000 → "{ms}ms"; ms<60_000 → "{s:.1f}s"; else "{m:.1f}m". Locale-blind.
|
||||
PRE: [PRE-001 hard] ms is a non-negative int -- assert isinstance(ms, int) and ms >= 0
|
||||
POST: [POST-001 return_value] returns a unit-suffixed string: "{ms}ms" below 1s, "{s:.1f}s" below 1m, else "{m:.1f}m"
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] Validate input per PRE-001 -- assert isinstance(ms, int) and ms >= 0
|
||||
2. [branch, flexibility=prescriptive] IF ms < 1000: RETURN f"{ms}ms"
|
||||
3. [branch, flexibility=prescriptive] IF ms < 60_000: RETURN f"{ms / 1000:.1f}s"
|
||||
4. [sequential, flexibility=prescriptive] RETURN f"{ms / 60_000:.1f}m" # minutes fallback
|
||||
TESTS:
|
||||
subsecond: 347 → "347ms"
|
||||
exact_one_second: 1000 → "1.0s"
|
||||
@@ -425,6 +456,12 @@ TESTS:
|
||||
```contract
|
||||
FN _format_usage(usage: dict, *, arrow: str) -> str # issue #12 INV-007 helper
|
||||
BRIEF: Natural-language usage formatting. arrow="->" for CLI (ASCII), arrow="→" for TUI (Unicode).
|
||||
PRE: [PRE-001 hard] usage carries the four token keys -- assert all(k in usage for k in ("prompt_tokens", "completion_tokens", "total_tokens", "cached_input_tokens"))
|
||||
POST: [POST-001 return_value] returns "{p} in {arrow} {c} out ({t} total, {ci} cached)" with the four counts substituted and the caller-supplied arrow glyph
|
||||
STEPS:
|
||||
1. [setup, flexibility=prescriptive] Validate input per PRE-001 -- assert all(k in usage for k in ("prompt_tokens", "completion_tokens", "total_tokens", "cached_input_tokens"))
|
||||
2. [sequential, flexibility=prescriptive] Bind p=usage["prompt_tokens"], c=usage["completion_tokens"], t=usage["total_tokens"], ci=usage["cached_input_tokens"]
|
||||
3. [sequential, flexibility=prescriptive] RETURN f"{p} in {arrow} {c} out ({t} total, {ci} cached)"
|
||||
TESTS:
|
||||
ascii_arrow: arrow="->" → "6756 in -> 126 out (6882 total, 0 cached)"
|
||||
unicode_arrow: arrow="→" → "6756 in → 126 out (6882 total, 0 cached)"
|
||||
|
||||
Reference in New Issue
Block a user