soothe-cli 0.7.13__tar.gz → 0.7.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/PKG-INFO +1 -1
  2. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/commands/run_cmd.py +2 -2
  3. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/token_usage.py +7 -1
  4. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/transport/session.py +16 -1
  5. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_ui.py +1 -1
  6. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/theme.py +1 -1
  7. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/cognition_step.py +17 -38
  8. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +17 -0
  9. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/.gitignore +0 -0
  10. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/README.md +0 -0
  11. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/pyproject.toml +0 -0
  12. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/__init__.py +0 -0
  13. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/__init__.py +0 -0
  14. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/commands/__init__.py +0 -0
  15. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  16. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
  17. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
  18. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
  19. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  20. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/execution/__init__.py +0 -0
  21. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/execution/daemon.py +0 -0
  22. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
  23. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/execution/headless.py +0 -0
  24. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  25. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/execution/launcher.py +0 -0
  26. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/cli/main.py +0 -0
  27. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/config/__init__.py +0 -0
  28. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/config/cli_config.py +0 -0
  29. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/config/loader.py +0 -0
  30. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/config/logging_setup.py +0 -0
  31. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/__init__.py +0 -0
  32. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/headless/processor.py +0 -0
  33. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
  34. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
  35. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
  36. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
  37. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
  38. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
  39. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
  40. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
  41. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/presentation/engine.py +0 -0
  42. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
  43. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
  44. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
  45. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
  46. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/state/session_stats.py +0 -0
  47. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/state/step_router.py +0 -0
  48. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
  49. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/state/transcript.py +0 -0
  50. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/token_events_debug.py +0 -0
  51. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
  52. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/turn/prepare.py +0 -0
  53. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
  54. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/wire/message_text.py +0 -0
  55. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/runtime/wire/messages.py +0 -0
  56. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/__init__.py +0 -0
  57. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/_cli_context.py +0 -0
  58. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/_env_vars.py +0 -0
  59. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/_version.py +0 -0
  60. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/__init__.py +0 -0
  61. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_app.py +0 -0
  62. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_execution.py +0 -0
  63. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_history.py +0 -0
  64. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
  65. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_model.py +0 -0
  66. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_module_init.py +0 -0
  67. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/_startup.py +0 -0
  68. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/app/app.tcss +0 -0
  69. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/binding.py +0 -0
  70. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/command_registry.py +0 -0
  71. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/commands/__init__.py +0 -0
  72. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/commands/command_router.py +0 -0
  73. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
  74. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
  75. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/config.py +0 -0
  76. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/file_change_notify.py +0 -0
  77. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/file_change_renderers.py +0 -0
  78. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/hooks.py +0 -0
  79. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/input.py +0 -0
  80. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/markdown_theme.py +0 -0
  81. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/media_utils.py +0 -0
  82. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/model_config.py +0 -0
  83. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/path_utils.py +0 -0
  84. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/preview_limits.py +0 -0
  85. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/project_utils.py +0 -0
  86. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/sessions.py +0 -0
  87. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/shell_color.py +0 -0
  88. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/skills/__init__.py +0 -0
  89. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/skills/invocation.py +0 -0
  90. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/skills/load.py +0 -0
  91. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/spinner_labels.py +0 -0
  92. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/textual_adapter.py +0 -0
  93. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/tips.py +0 -0
  94. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/tool_display.py +0 -0
  95. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/unicode_security.py +0 -0
  96. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/update_check.py +0 -0
  97. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  98. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/_links.py +0 -0
  99. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  100. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  101. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  102. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/context_data.py +0 -0
  103. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/context_viewer.py +0 -0
  104. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/diff.py +0 -0
  105. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/editor.py +0 -0
  106. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
  107. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
  108. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/history.py +0 -0
  109. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/loading.py +0 -0
  110. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  111. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  112. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  113. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
  114. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -0
  115. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
  116. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
  117. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/clarification.py +0 -0
  118. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +0 -0
  119. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
  120. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +0 -0
  121. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
  122. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
  123. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
  124. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
  125. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  126. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  127. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +0 -0
  128. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/status.py +0 -0
  129. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  130. {soothe_cli-0.7.13 → soothe_cli-0.7.14}/src/soothe_cli/tui/widgets/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.7.13
3
+ Version: 0.7.14
4
4
  Summary: Soothe CLI client - communicates with daemon via WebSocket
5
5
  Project-URL: Homepage, https://github.com/mirasoth/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
@@ -100,7 +100,7 @@ def run_impl(
100
100
  result = get_last_app_result()
101
101
  if result is not None and result.loop_id:
102
102
  typer.echo(
103
- f"💡 To continue this loop: soothe loop resume {result.loop_id}",
103
+ f"💡 To continue: soothe loop resume {result.loop_id}",
104
104
  err=True,
105
105
  )
106
106
 
@@ -113,7 +113,7 @@ def run_impl(
113
113
  result = get_last_app_result()
114
114
  if result is not None and result.loop_id:
115
115
  typer.echo(
116
- f"💡 To continue this loop: soothe loop resume {result.loop_id}",
116
+ f"💡 To continue: soothe loop resume {result.loop_id}",
117
117
  err=True,
118
118
  )
119
119
  sys.exit(130)
@@ -8,19 +8,25 @@ from typing import Any
8
8
  logger = logging.getLogger(__name__)
9
9
 
10
10
  __all__ = [
11
+ "coerce_total_tokens_used",
11
12
  "extract_stream_message_token_usage",
12
13
  "fetch_conversation_token_count",
13
14
  "message_has_token_usage_metadata",
14
15
  ]
15
16
 
16
17
 
17
- def _coerce_int(value: Any) -> int:
18
+ def coerce_total_tokens_used(value: Any) -> int:
19
+ """Parse a non-negative ``total_tokens_used`` field from event payloads."""
18
20
  try:
19
21
  return max(0, int(value or 0))
20
22
  except (TypeError, ValueError):
21
23
  return 0
22
24
 
23
25
 
26
+ def _coerce_int(value: Any) -> int:
27
+ return coerce_total_tokens_used(value)
28
+
29
+
24
30
  def _usage_from_mapping(usage: dict[str, Any]) -> tuple[int, int, int]:
25
31
  """Return ``(input_tokens, output_tokens, total_tokens)`` from a usage dict."""
26
32
  input_toks = _coerce_int(usage.get("input_tokens") or usage.get("prompt_tokens"))
@@ -321,6 +321,7 @@ class TuiDaemonSession:
321
321
  inbound_dropped_baseline = getattr(self._client, "inbound_dropped", 0)
322
322
  query_started = False
323
323
  expected_loop_id = self._loop_id
324
+ stream_payload_seen = False
324
325
  self._streaming = True
325
326
  turn_read_started = time.monotonic()
326
327
  first_event_logged = False
@@ -405,7 +406,20 @@ class TuiDaemonSession:
405
406
  if state == "running":
406
407
  query_started = True
407
408
  progress_seen = True
408
- elif query_started and state in {"idle", "stopped"}:
409
+ elif query_started and state == "stopped":
410
+ self.last_turn_end_state = state
411
+ async for chunk in self._drain_stream_events_after_idle(
412
+ expected_loop_id=expected_loop_id,
413
+ ):
414
+ yield chunk
415
+ break
416
+ elif query_started and state == "idle":
417
+ if not stream_payload_seen and not self.last_turn_cancellation_seen:
418
+ logger.debug(
419
+ "Ignoring stale idle before stream payload (loop=%s)",
420
+ (expected_loop_id or "?")[:16],
421
+ )
422
+ continue
409
423
  self.last_turn_end_state = state
410
424
  async for chunk in self._drain_stream_events_after_idle(
411
425
  expected_loop_id=expected_loop_id,
@@ -430,6 +444,7 @@ class TuiDaemonSession:
430
444
  self.turn_event_stats.filtered_early += 1
431
445
  continue
432
446
  progress_seen = True
447
+ stream_payload_seen = True
433
448
  yield (namespace, mode, data)
434
449
  # Graph auto-resumes LangGraph interrupts server-side; keep consuming events.
435
450
  if mode == "updates" and isinstance(data, dict) and "__interrupt__" in data:
@@ -170,7 +170,7 @@ class _UIMixin:
170
170
  source: str = "backend",
171
171
  ) -> None:
172
172
  """Merge backend goal-run token totals into the visible loop counter."""
173
- from soothe.foundation.sloop.utils.token_usage import coerce_total_tokens_used
173
+ from soothe_cli.runtime.token_usage import coerce_total_tokens_used
174
174
 
175
175
  goal_total = coerce_total_tokens_used(goal_run_tokens)
176
176
  self._ensure_loop_token_scope(self._lc_loop_id)
@@ -377,7 +377,7 @@ class ThemeColors:
377
377
  """Tertiary activity text — more subdued than card_activity."""
378
378
 
379
379
  card_success: str
380
- """Success state color — prominent green for visibility."""
380
+ """Card prefix dot color for successful lifecycle — green header indicator only."""
381
381
 
382
382
  card_error: str
383
383
  """Error state color — prominent red for visibility."""
@@ -118,6 +118,11 @@ class CognitionStepMessage(Vertical):
118
118
  height: auto;
119
119
  }
120
120
 
121
+ CognitionStepMessage .step-status {
122
+ height: auto;
123
+ color: $text-muted;
124
+ }
125
+
121
126
  CognitionStepMessage.-collapsed .step-tools,
122
127
  CognitionStepMessage.-collapsed .step-status,
123
128
  CognitionStepMessage.-collapsed .step-subagent-notes,
@@ -542,8 +547,8 @@ class CognitionStepMessage(Vertical):
542
547
  Args:
543
548
  head: The leading status text (e.g. ``"Completed (1.2s)"`` or
544
549
  ``"Failed · 1.2s"``) that gets the prominent success/error tone.
545
- success: Whether this is a successful completion (green) or a
546
- failure (red).
550
+ success: Whether this is a successful completion (dim, like tool
551
+ activity rows) or a failure (red).
547
552
  suffix: Optional stats tail (tool counts, token budget) that keeps
548
553
  the subdued cognition tone so it doesn't drown out the head.
549
554
  """
@@ -556,11 +561,16 @@ class CognitionStepMessage(Vertical):
556
561
  except Exception: # noqa: BLE001
557
562
  colors = theme.DARK_COLORS
558
563
  icon = g.checkmark if success else g.error
559
- head_tone = colors.card_success if success else colors.card_error
560
- parts: list[object] = [Content.styled(f"{gutter}{icon} {head}", head_tone)]
561
- if suffix:
562
- parts.append(Content.styled(suffix, theme.SECONDARY_TEXT_STYLE))
563
- self._status_widget.update(Content.assemble(*parts))
564
+ self._status_widget.update(
565
+ StepCardStatusLine.footer_completed(
566
+ gutter=gutter,
567
+ icon=icon,
568
+ head=head,
569
+ suffix=suffix,
570
+ success=success,
571
+ colors=colors,
572
+ )
573
+ )
564
574
  self._status_widget.display = True
565
575
 
566
576
  def _refresh_header_title(self) -> None:
@@ -611,37 +621,6 @@ class CognitionStepMessage(Vertical):
611
621
  style=style,
612
622
  )
613
623
 
614
- def _step_branched_completion_detail(
615
- self,
616
- *,
617
- success: bool,
618
- status_line_body: str,
619
- prose: str,
620
- ) -> Content:
621
- """Completed step detail: first line ``⎿ ✓|✗ status``; prose lines ``⎿ ○ …``."""
622
- g = get_glyphs()
623
- gutter = self._step_goal_tree_gutter()
624
- try:
625
- colors = theme.get_theme_colors(self)
626
- except Exception: # noqa: BLE001
627
- colors = theme.DARK_COLORS
628
- icon = g.checkmark if success else g.error
629
- # Match running step line and tool activity: cognition accent, not semantic green.
630
- tone = colors.cognition if success else colors.error
631
- parts: list[object] = [
632
- Content.styled(f"{gutter}{icon} {status_line_body}", tone),
633
- ]
634
- prose = (prose or "").strip()
635
- if prose:
636
- sub = f"{g.output_prefix} {g.circle_empty} "
637
- prose_style = colors.muted if success else tone
638
- parts.append("\n")
639
- for i, ln in enumerate(prose.splitlines()):
640
- if i:
641
- parts.append("\n")
642
- parts.append(Content.styled(f"{sub}{ln}", prose_style))
643
- return Content.assemble(*parts)
644
-
645
624
  def _step_branched_error_detail(self, err_text: str) -> Content:
646
625
  """Multiline error body: first line ``⎿ ✗ …``; continuations ``⎿ ○ …``."""
647
626
  g = get_glyphs()
@@ -458,6 +458,23 @@ class StepCardStatusLine:
458
458
  parts.append(Content.styled(tail, theme.SECONDARY_TEXT_STYLE))
459
459
  return Content.assemble(*parts)
460
460
 
461
+ @staticmethod
462
+ def footer_completed(
463
+ *,
464
+ gutter: str,
465
+ icon: str,
466
+ head: str,
467
+ suffix: str = "",
468
+ success: bool,
469
+ colors: Any,
470
+ ) -> Content:
471
+ """Step card footer after successful or failed completion."""
472
+ head_tone = theme.SECONDARY_TEXT_STYLE if success else colors.card_error
473
+ parts: list[object] = [Content.styled(f"{gutter}{icon} {head}", head_tone)]
474
+ if suffix:
475
+ parts.append(Content.styled(suffix, theme.SECONDARY_TEXT_STYLE))
476
+ return Content.assemble(*parts)
477
+
461
478
 
462
479
  # Activity tree renderer
463
480
  # ---------------------------------------------------------------------------
File without changes
File without changes
File without changes