soothe-cli 0.6.13__tar.gz → 0.6.15__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 (113) hide show
  1. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/PKG-INFO +1 -1
  2. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/textual_adapter.py +30 -2
  3. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/messages.py +79 -23
  4. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/.gitignore +0 -0
  5. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/README.md +0 -0
  6. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/pyproject.toml +0 -0
  7. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/__init__.py +0 -0
  8. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/__init__.py +0 -0
  9. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/commands/__init__.py +0 -0
  10. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
  11. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
  12. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
  13. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
  14. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  15. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/execution/__init__.py +0 -0
  16. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/execution/daemon.py +0 -0
  17. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
  18. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/execution/headless.py +0 -0
  19. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  20. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/execution/launcher.py +0 -0
  21. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/cli/main.py +0 -0
  22. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/config/__init__.py +0 -0
  23. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/config/cli_config.py +0 -0
  24. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/config/loader.py +0 -0
  25. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/config/logging_setup.py +0 -0
  26. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/__init__.py +0 -0
  27. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/headless/processor.py +0 -0
  28. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
  29. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/parse/_utils.py +0 -0
  30. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
  31. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
  32. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/parse/tool_message_format.py +0 -0
  33. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/parse/tool_result.py +0 -0
  34. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
  35. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
  36. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
  37. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
  38. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/presentation/engine.py +0 -0
  39. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/presentation/explore_task_display.py +0 -0
  40. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
  41. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
  42. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
  43. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/state/session_stats.py +0 -0
  44. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/state/step_router.py +0 -0
  45. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
  46. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/state/transcript.py +0 -0
  47. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/transport/session.py +0 -0
  48. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/turn/pipeline.py +0 -0
  49. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/turn/prepare.py +0 -0
  50. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
  51. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/wire/display_text.py +0 -0
  52. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/wire/message_text.py +0 -0
  53. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/runtime/wire/messages.py +0 -0
  54. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/__init__.py +0 -0
  55. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/_cli_context.py +0 -0
  56. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/_env_vars.py +0 -0
  57. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/_version.py +0 -0
  58. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/__init__.py +0 -0
  59. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_app.py +0 -0
  60. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_commands.py +0 -0
  61. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_execution.py +0 -0
  62. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_history.py +0 -0
  63. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
  64. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_model.py +0 -0
  65. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_module_init.py +0 -0
  66. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_startup.py +0 -0
  67. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/_ui.py +0 -0
  68. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/app/app.tcss +0 -0
  69. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/binding.py +0 -0
  70. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/command_registry.py +0 -0
  71. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/commands/__init__.py +0 -0
  72. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/commands/command_router.py +0 -0
  73. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
  74. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
  75. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/config.py +0 -0
  76. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/file_change_notify.py +0 -0
  77. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/file_change_renderers.py +0 -0
  78. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/hooks.py +0 -0
  79. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/input.py +0 -0
  80. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/media_utils.py +0 -0
  81. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/model_config.py +0 -0
  82. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/path_utils.py +0 -0
  83. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/preview_limits.py +0 -0
  84. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/project_utils.py +0 -0
  85. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/sessions.py +0 -0
  86. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/skills/__init__.py +0 -0
  87. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/skills/invocation.py +0 -0
  88. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/skills/load.py +0 -0
  89. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/theme.py +0 -0
  90. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/tips.py +0 -0
  91. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/tool_display.py +0 -0
  92. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/unicode_security.py +0 -0
  93. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/update_check.py +0 -0
  94. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  95. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/_links.py +0 -0
  96. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  97. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
  98. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
  99. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  100. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  101. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/diff.py +0 -0
  102. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/editor.py +0 -0
  103. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
  104. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/history.py +0 -0
  105. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/loading.py +0 -0
  106. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  107. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  108. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  109. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  110. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  111. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/status.py +0 -0
  112. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  113. {soothe_cli-0.6.13 → soothe_cli-0.6.15}/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.6.13
3
+ Version: 0.6.15
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
@@ -119,6 +119,9 @@ from soothe_cli.tui.widgets.messages import (
119
119
 
120
120
  logger = logging.getLogger(__name__)
121
121
 
122
+ # IG-504: LLM retry event type for step card status display
123
+ LLM_RETRY_ATTEMPT = "soothe.cognition.llm.retry.attempt"
124
+
122
125
  # ---------------------------------------------------------------------------
123
126
  # Adapter core
124
127
  # ---------------------------------------------------------------------------
@@ -2793,12 +2796,37 @@ async def execute_task_textual(
2793
2796
  )
2794
2797
  continue
2795
2798
 
2799
+ # IG-504: Handle LLM retry events for step card status display
2800
+ if event_type == LLM_RETRY_ATTEMPT:
2801
+ # Find the running step card for this thread and update retry status
2802
+ attempt = int(data.get("attempt", 0))
2803
+ max_attempts = int(data.get("max_attempts", 0))
2804
+ error_type = str(data.get("error_type", "timeout"))
2805
+ # Try to find a running step card via namespace or active steps
2806
+ widget = None
2807
+ if ns_key:
2808
+ widget = adapter._step_by_namespace.get(ns_key)
2809
+ if widget is None:
2810
+ # Fallback: find any running step in current step messages
2811
+ for step_widget in adapter._current_step_messages.values():
2812
+ if step_widget._status == "running": # noqa: SLF001
2813
+ widget = step_widget
2814
+ break
2815
+ if widget is not None:
2816
+ widget.set_retry_status(attempt, max_attempts, error_type)
2817
+ continue
2818
+
2796
2819
  if event_type == LOOP_REASON_EVENT_TYPE:
2797
2820
  assessment_reasoning = str(
2798
2821
  data.get("assessment_reasoning", "")
2799
2822
  ).strip()
2800
2823
  plan_reasoning = str(data.get("plan_reasoning", "")).strip()
2801
- if not assessment_reasoning and not plan_reasoning:
2824
+ next_action = str(data.get("next_action", "")).strip()
2825
+ if (
2826
+ not assessment_reasoning
2827
+ and not plan_reasoning
2828
+ and not next_action
2829
+ ):
2802
2830
  continue
2803
2831
  pending_text = pending_text_by_namespace.get(ns_key, "")
2804
2832
  if pending_text:
@@ -2814,7 +2842,7 @@ async def execute_task_textual(
2814
2842
  pa_raw = data.get("plan_action", "")
2815
2843
  plan_action = pa_raw if pa_raw in ("keep", "new") else ""
2816
2844
  plan_widget = CognitionReasonMessage(
2817
- next_action="",
2845
+ next_action=next_action,
2818
2846
  status=str(data.get("status", "")),
2819
2847
  iteration=int(data.get("iteration", 0)),
2820
2848
  plan_action=str(plan_action),
@@ -1303,6 +1303,10 @@ class CognitionStepMessage(Vertical):
1303
1303
  self._last_tool_call_count: int = 0
1304
1304
  self._last_summary: str = ""
1305
1305
  self._input_tokens: int = 0
1306
+ # IG-504: Retry tracking for running status display
1307
+ self._retry_attempt: int = 0
1308
+ self._max_retry_attempts: int = 0
1309
+ self._retry_error_type: str | None = None
1306
1310
  self._output_tokens: int = 0
1307
1311
  self._interrupt_message: str | None = None
1308
1312
  self._deferred_interrupted: str | None = None
@@ -2856,14 +2860,53 @@ class CognitionStepMessage(Vertical):
2856
2860
  self._refresh_tools_display()
2857
2861
 
2858
2862
  def _sync_running_status_line(self) -> None:
2859
- """Refresh status text when tool stats change without repainting tool rows."""
2863
+ """Refresh status text when tool stats change without repainting tool rows.
2864
+
2865
+ This method is called when tool calls arrive during a running step,
2866
+ so it updates the status line immediately (without visibility check)
2867
+ to show the tool count in real-time.
2868
+ """
2860
2869
  if self._status == "running":
2861
- self._update_running_animation()
2870
+ self._sync_running_status_text()
2862
2871
  elif self._status == "queued":
2863
2872
  self._refresh_queued_display()
2864
2873
  elif self._status == "pending":
2865
2874
  self._refresh_pending_display()
2866
2875
 
2876
+ def _sync_running_status_text(self) -> None:
2877
+ """Update running status line text immediately (no visibility check).
2878
+
2879
+ Called when tool stats change during running state to show tool count
2880
+ in real-time, bypassing the animation visibility check that would
2881
+ prevent updates for newly-mounted or off-screen widgets.
2882
+ """
2883
+ if self._status != "running" or self._status_widget is None:
2884
+ return
2885
+ frames = get_glyphs().spinner_frames
2886
+ frame = frames[self._spinner_position]
2887
+ elapsed = ""
2888
+ if self._start_time is not None:
2889
+ elapsed_secs = int(time() - self._start_time)
2890
+ elapsed = f" ({format_duration(float(elapsed_secs))})"
2891
+ try:
2892
+ colors = theme.get_theme_colors(self)
2893
+ except Exception: # noqa: BLE001
2894
+ colors = theme.DARK_COLORS
2895
+ gutter = f"{get_glyphs().output_prefix} "
2896
+ stats_suffix = self._stats_title_suffix()
2897
+ token_suffix = self._token_budget_suffix()
2898
+ # IG-504: Show retry count in running status when retries are happening
2899
+ retry_suffix = ""
2900
+ if self._retry_attempt > 0 and self._max_retry_attempts > 0:
2901
+ retry_suffix = f" ({self._retry_attempt}/{self._max_retry_attempts} attempts)"
2902
+ head = f"{gutter}{frame} Running{retry_suffix}...{elapsed}"
2903
+ tail = f"{stats_suffix}{token_suffix}"
2904
+ clear_widget_text_selection(self._status_widget)
2905
+ parts: list[object] = [Content.styled(head, colors.warning)]
2906
+ if tail:
2907
+ parts.append(Content.styled(tail, colors.cognition))
2908
+ self._status_widget.update(Content.assemble(*parts))
2909
+
2867
2910
  def _refresh_pending_display(self) -> None:
2868
2911
  """Show waiting state for planned steps that are not executing yet."""
2869
2912
  if self._status != "pending" or self._status_widget is None:
@@ -2918,7 +2961,9 @@ class CognitionStepMessage(Vertical):
2918
2961
  self._status_widget.remove_class("pending")
2919
2962
  self._status_widget.remove_class("queued")
2920
2963
  self._status_widget.display = True
2921
- self._update_running_animation()
2964
+ # Immediate status line update (bypasses visibility check) so tool count
2965
+ # appears in real-time when tools arrive before the animation timer fires.
2966
+ self._sync_running_status_text()
2922
2967
  self._refresh_task_activity_display()
2923
2968
  self._animation_timer = self.set_interval(
2924
2969
  _RUNNING_SPINNER_INTERVAL_SECONDS,
@@ -2930,29 +2975,38 @@ class CognitionStepMessage(Vertical):
2930
2975
  self._animation_timer.stop()
2931
2976
  self._animation_timer = None
2932
2977
 
2978
+ def set_retry_status(self, attempt: int, max_attempts: int, error_type: str) -> None:
2979
+ """IG-504: Update retry status for running animation display.
2980
+
2981
+ Args:
2982
+ attempt: Current attempt number (1-indexed).
2983
+ max_attempts: Maximum attempts allowed.
2984
+ error_type: "timeout" or "rate_limit".
2985
+ """
2986
+ self._retry_attempt = attempt
2987
+ self._max_retry_attempts = max_attempts
2988
+ self._retry_error_type = error_type
2989
+ # Trigger immediate refresh of running status (bypass visibility check)
2990
+ if self._status == "running":
2991
+ self._sync_running_status_text()
2992
+
2933
2993
  def _update_running_animation(self) -> None:
2994
+ """Animation timer callback: advance spinner and update status line.
2995
+
2996
+ The visibility check here is for efficiency - skipping expensive
2997
+ re-renders when the widget is off-screen. For immediate tool-stats
2998
+ updates (called when tool calls arrive), use `_sync_running_status_text`
2999
+ which bypasses this check.
3000
+ """
2934
3001
  if self._status != "running" or self._status_widget is None:
2935
3002
  return
2936
3003
  if not _is_widget_animation_visible(self):
2937
3004
  return
3005
+ # Advance spinner position for animation effect
2938
3006
  frames = get_glyphs().spinner_frames
2939
- frame = frames[self._spinner_position]
2940
3007
  self._spinner_position = (self._spinner_position + 1) % len(frames)
2941
- elapsed = ""
2942
- if self._start_time is not None:
2943
- elapsed_secs = int(time() - self._start_time)
2944
- elapsed = f" ({format_duration(float(elapsed_secs))})"
2945
- colors = theme.get_theme_colors(self)
2946
- gutter = f"{get_glyphs().output_prefix} "
2947
- stats_suffix = self._stats_title_suffix()
2948
- token_suffix = self._token_budget_suffix()
2949
- head = f"{gutter}{frame} Running...{elapsed}"
2950
- tail = f"{stats_suffix}{token_suffix}"
2951
- clear_widget_text_selection(self._status_widget)
2952
- parts: list[object] = [Content.styled(head, colors.warning)]
2953
- if tail:
2954
- parts.append(Content.styled(tail, colors.cognition))
2955
- self._status_widget.update(Content.assemble(*parts))
3008
+ # Update status line with current stats
3009
+ self._sync_running_status_text()
2956
3010
  if self._has_active_task_branch_animation():
2957
3011
  self._refresh_task_activity_display()
2958
3012
 
@@ -3187,12 +3241,12 @@ class CognitionReasonMessage(Vertical):
3187
3241
  """Initialize a plan-reason card.
3188
3242
 
3189
3243
  Args:
3190
- next_action: Deprecated; retained for message-store replay only.
3244
+ next_action: User-facing plan-generate line (RFC-604 / IG-329).
3191
3245
  status: Plan status (continue, replan, done).
3192
3246
  iteration: Agent-loop iteration index.
3193
3247
  plan_action: ``keep`` or ``new`` (execution strategy).
3194
- assessment_reasoning: Phase-1 status justification.
3195
- plan_reasoning: Phase-2 plan-strategy text.
3248
+ assessment_reasoning: Phase-1 status justification from plan-assess.
3249
+ plan_reasoning: Legacy phase-2 strategy text (usually empty after IG-329).
3196
3250
  **kwargs: Passed to ``Vertical``.
3197
3251
  """
3198
3252
  super().__init__(**kwargs)
@@ -3209,6 +3263,8 @@ class CognitionReasonMessage(Vertical):
3209
3263
  parts.append(self._assessment_reasoning)
3210
3264
  if self._plan_reasoning:
3211
3265
  parts.append(self._plan_reasoning)
3266
+ elif self._next_action:
3267
+ parts.append(self._next_action)
3212
3268
  if len(parts) == 2:
3213
3269
  first = parts[0]
3214
3270
  if not first.endswith((".", "!", "?")):
@@ -3217,7 +3273,7 @@ class CognitionReasonMessage(Vertical):
3217
3273
  elif parts:
3218
3274
  body = parts[0]
3219
3275
  else:
3220
- body = self._next_action or ""
3276
+ body = ""
3221
3277
  if self._plan_action in ("keep", "new") and body:
3222
3278
  body = f"{body} · {self._plan_action}"
3223
3279
  return _assemble_card_header(self, "", body)
File without changes
File without changes
File without changes