soothe-cli 1.0.5__tar.gz → 1.0.7__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.
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/PKG-INFO +1 -1
- soothe_cli-1.0.7/src/soothe_cli/display/card.py +158 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_clarification.py +5 -8
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_messages_mixin.py +29 -12
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/file_change_preview.py +1 -1
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -10
- soothe_cli-1.0.7/src/soothe_cli/tui/widgets/messages/_helpers.py +155 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/app.py +1 -1
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/assistant.py +2 -4
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +4 -4
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +1 -1
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/cognition_step.py +7 -5
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +29 -29
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/error.py +1 -1
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/skill.py +1 -1
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/structured_ask_user.py +33 -21
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +5 -5
- soothe_cli-1.0.5/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -318
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/.gitignore +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/README.md +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/pyproject.toml +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/_cli_context.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/_env_vars.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/_version.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/card_wire.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/main.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/cli/soothecli.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/commands/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/commands/binding.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/commands/command_registry.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/commands/command_router.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/commands/slash_commands.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/commands/subagent_routing.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/config/cli_config.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/markdown_theme.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/media_utils.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/mermaid_render.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/path_utils.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/preview_limits.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/shell_color.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/spinner_labels.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/theme.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/tips.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/tool_display.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/display/unicode_security.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/loops/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/loops/sessions.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/model_config.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/project_utils.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/headless/processor.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/presentation/id_format.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/presentation/step_id_format.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/state/step_router.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/token_events_debug.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/token_usage.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/transport/session.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/turn/prepare.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/_console_impl.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/bootstrap.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/core.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/glyphs.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/provider.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/shell_allow.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/skills_dirs.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/settings/stream_config.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_app.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_entrypoints.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_execution.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_model.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_model_params.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_startup.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_terminal.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_theme_prefs.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_types.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/_ui.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/composer_mode.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/file_change_notify.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/textual_adapter.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/context_data.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/context_viewer.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/pinned_goal_bar.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/queued_goals_bar.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/router_profile_selector.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/welcome.py +0 -0
- {soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/update_check.py +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Card prefix glyph, header assembly, and body gutter alignment shared by TUI card widgets.
|
|
2
|
+
|
|
3
|
+
These helpers encode card *appearance* (prefix tone, glyph prefix, gutter
|
|
4
|
+
alignment, header assembly). They are intentionally free of Textual widget
|
|
5
|
+
lifecycle concerns; refresh throttling and animation visibility stay in the
|
|
6
|
+
TUI layer (``tui/widgets/messages/_helpers.py``).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from textual.content import Content
|
|
12
|
+
|
|
13
|
+
from soothe_cli.display import theme
|
|
14
|
+
from soothe_cli.display.preview_limits import STEP_TASK_CARD_COLLAPSE_LINE_THRESHOLD
|
|
15
|
+
from soothe_cli.display.tool_display import display_width
|
|
16
|
+
from soothe_cli.settings import get_glyphs
|
|
17
|
+
|
|
18
|
+
# Preview limits imported from preview_limits module
|
|
19
|
+
_STEP_TOOL_PREVIEW_ROWS = STEP_TASK_CARD_COLLAPSE_LINE_THRESHOLD
|
|
20
|
+
"""Collapsed step/task activity preview shows this many rows."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _card_dot_tone(
|
|
24
|
+
status: str,
|
|
25
|
+
colors: theme.ThemeColors,
|
|
26
|
+
*,
|
|
27
|
+
accent: str | None = None,
|
|
28
|
+
spinner_position: int = 0,
|
|
29
|
+
animate_running: bool = False,
|
|
30
|
+
) -> str:
|
|
31
|
+
"""Return a Textual style string for a card prefix dot from lifecycle status."""
|
|
32
|
+
phase = (status or "pending").strip().lower()
|
|
33
|
+
if phase in ("success", "done", "completed"):
|
|
34
|
+
return colors.card_success
|
|
35
|
+
if phase in ("error", "failed", "interrupted"):
|
|
36
|
+
return colors.card_error
|
|
37
|
+
if phase == "running":
|
|
38
|
+
# Yellow flash on every other spinner tick; dim amber in between.
|
|
39
|
+
if animate_running and spinner_position % 2:
|
|
40
|
+
return colors.card_running_muted
|
|
41
|
+
return colors.card_running
|
|
42
|
+
if phase in ("queued", "pending", "continue", "replan"):
|
|
43
|
+
return colors.muted
|
|
44
|
+
if accent:
|
|
45
|
+
return accent
|
|
46
|
+
return colors.muted
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _card_dot_prefix_content(
|
|
50
|
+
widget: object,
|
|
51
|
+
status: str,
|
|
52
|
+
*,
|
|
53
|
+
accent: str | None = None,
|
|
54
|
+
glyph_override: str | None = None,
|
|
55
|
+
spinner_position: int = 0,
|
|
56
|
+
animate_running: bool = False,
|
|
57
|
+
) -> Content:
|
|
58
|
+
"""Build a stateful colored card-prefix glyph."""
|
|
59
|
+
try:
|
|
60
|
+
colors = theme.get_theme_colors(widget)
|
|
61
|
+
except Exception: # noqa: BLE001
|
|
62
|
+
colors = theme.DARK_COLORS
|
|
63
|
+
glyph = glyph_override or get_glyphs().tool_prefix
|
|
64
|
+
tone = _card_dot_tone(
|
|
65
|
+
status,
|
|
66
|
+
colors,
|
|
67
|
+
accent=accent,
|
|
68
|
+
spinner_position=spinner_position,
|
|
69
|
+
animate_running=animate_running,
|
|
70
|
+
)
|
|
71
|
+
return Content.styled(f"{glyph} ", tone)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _card_prefix_width(glyph_override: str | None = None) -> int:
|
|
75
|
+
"""Display width of the card header prefix (the dot/glyph plus one space).
|
|
76
|
+
|
|
77
|
+
Every body line and tree-branch line must left-align at the right side of
|
|
78
|
+
this prefix, so card content never extends left of the header text. In
|
|
79
|
+
ASCII mode the prefix is ``"[*] "`` (4 cols) while the tree gutter glyph
|
|
80
|
+
``"L"`` is only 1 col; the body gutter is padded to close that gap.
|
|
81
|
+
"""
|
|
82
|
+
glyph = glyph_override or get_glyphs().tool_prefix
|
|
83
|
+
return display_width(glyph) + 1 # +1 for the trailing space
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _card_body_gutter(glyph_override: str | None = None) -> str:
|
|
87
|
+
"""Body/tree-branch gutter aligned to the right of the card prefix dot.
|
|
88
|
+
|
|
89
|
+
The tree glyph (``⎿`` / ``L``) stays at the left edge of the prefix column;
|
|
90
|
+
trailing spaces pad the gutter so its display width equals the header
|
|
91
|
+
prefix width (``glyph + " "``). That makes every card line and tree branch
|
|
92
|
+
start at the right side of the dot space.
|
|
93
|
+
"""
|
|
94
|
+
g = get_glyphs()
|
|
95
|
+
prefix_width = _card_prefix_width(glyph_override)
|
|
96
|
+
glyph_w = display_width(g.output_prefix)
|
|
97
|
+
pad = max(0, prefix_width - glyph_w)
|
|
98
|
+
return f"{g.output_prefix}{' ' * pad}"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _card_item_indent(
|
|
102
|
+
extra_indent: int = 0,
|
|
103
|
+
*,
|
|
104
|
+
glyph_override: str | None = None,
|
|
105
|
+
) -> str:
|
|
106
|
+
"""Plain-space indent for activity item rows (no tree glyph).
|
|
107
|
+
|
|
108
|
+
Item rows under a section (tool rows, todo items, notes, "+N more") sit at
|
|
109
|
+
the card header prefix width plus ``extra_indent`` columns — aligned with
|
|
110
|
+
the right side of the dot space so they never extend left of the title
|
|
111
|
+
text, and indented under the section's ``⎿`` label.
|
|
112
|
+
"""
|
|
113
|
+
prefix_width = _card_prefix_width(glyph_override)
|
|
114
|
+
return f"{' ' * max(0, prefix_width + extra_indent)}"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _assemble_card_header(
|
|
118
|
+
widget: object,
|
|
119
|
+
body_part: str,
|
|
120
|
+
*,
|
|
121
|
+
status: str = "running",
|
|
122
|
+
accent: str | None = None,
|
|
123
|
+
glyph_override: str | None = None,
|
|
124
|
+
spinner_position: int = 0,
|
|
125
|
+
animate_running: bool = False,
|
|
126
|
+
) -> Content:
|
|
127
|
+
"""Build a card title: stateful prefix glyph plus foreground body (no bold).
|
|
128
|
+
|
|
129
|
+
Used for Goal, Plan, Step, and tool (including Task) headers. The glyph color
|
|
130
|
+
reflects lifecycle status; body lines below use the tree gutter returned by
|
|
131
|
+
:func:`_card_body_gutter`, which aligns to the right of the card prefix dot.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
widget: Mounted widget (or any object accepted by ``get_theme_colors``).
|
|
135
|
+
body_part: Header text (goal, step description, etc.).
|
|
136
|
+
status: Card lifecycle phase for glyph color/shape.
|
|
137
|
+
accent: Optional override tone for the glyph (e.g. skill/error accent).
|
|
138
|
+
spinner_position: Toggles gray flash while ``animate_running`` is true.
|
|
139
|
+
animate_running: Flash the glyph gray while running (step/task/assistant cards).
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Assembled ``Content`` for a ``Static`` header.
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
colors = theme.get_theme_colors(widget)
|
|
146
|
+
except Exception: # noqa: BLE001
|
|
147
|
+
colors = theme.DARK_COLORS
|
|
148
|
+
return Content.assemble(
|
|
149
|
+
_card_dot_prefix_content(
|
|
150
|
+
widget,
|
|
151
|
+
status,
|
|
152
|
+
accent=accent,
|
|
153
|
+
glyph_override=glyph_override,
|
|
154
|
+
spinner_position=spinner_position,
|
|
155
|
+
animate_running=animate_running,
|
|
156
|
+
),
|
|
157
|
+
Content.styled(body_part, colors.foreground),
|
|
158
|
+
)
|
|
@@ -7,19 +7,16 @@ keep the execution mixin focused on agent execution and message routing.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
_PLAN_REVIEW_ACTIONS = frozenset({"Approve", "Reject", "Refine"})
|
|
10
|
-
_TOOL_APPROVAL_ACTIONS = frozenset({"Approve", "Reject"
|
|
10
|
+
_TOOL_APPROVAL_ACTIONS = frozenset({"Approve", "Reject"})
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def clarification_wire_content(answers: list[str], *, origin_node: str = "") -> str:
|
|
14
14
|
"""Human-readable turn content for a clarification submit (not a new goal).
|
|
15
15
|
|
|
16
|
-
Option-selector actions use a stable
|
|
17
|
-
|
|
18
|
-
Plan review →
|
|
19
|
-
|
|
20
|
-
instructions in `answers[1]`). When `origin_node` is empty (legacy
|
|
21
|
-
callers) the action label alone selects the prefix, preserving the safety
|
|
22
|
-
net for any selector-origin submit.
|
|
16
|
+
Option-selector actions use a stable ``<prefix>: …`` header so a dropped
|
|
17
|
+
``clarification_answer`` flag cannot turn a bare action into Pass1 TASK.
|
|
18
|
+
Plan review → ``Plan review:`` (Refine carries refinement text in
|
|
19
|
+
``answers[1]``). Tool approval → ``Tool approval:``.
|
|
23
20
|
"""
|
|
24
21
|
non_empty = [a for a in answers if str(a).strip()]
|
|
25
22
|
if not non_empty:
|
|
@@ -1159,11 +1159,15 @@ class _MessagesMixin:
|
|
|
1159
1159
|
def cycle_composer_mode(self) -> None:
|
|
1160
1160
|
"""Advance composer mode and refresh the status-bar badge.
|
|
1161
1161
|
|
|
1162
|
-
Cycle: Auto → Bypass → Manual → Plan → Ask → Auto.
|
|
1163
|
-
|
|
1164
|
-
|
|
1162
|
+
Cycle: Auto → Bypass → Manual → Plan → Ask → Auto. For agent sub-modes
|
|
1163
|
+
(auto/bypass/manual) hot-swap the running goal's agent mode via
|
|
1164
|
+
`loop_set_clarification_mode`; Plan/Ask take effect on the next turn.
|
|
1165
1165
|
"""
|
|
1166
|
-
from soothe_cli.tui.composer_mode import
|
|
1166
|
+
from soothe_cli.tui.composer_mode import (
|
|
1167
|
+
AGENT_SUB_MODES,
|
|
1168
|
+
next_composer_mode,
|
|
1169
|
+
resolve_composer_wire_fields,
|
|
1170
|
+
)
|
|
1167
1171
|
|
|
1168
1172
|
current = getattr(self, "_composer_mode", "auto")
|
|
1169
1173
|
new_mode = next_composer_mode(current)
|
|
@@ -1171,13 +1175,21 @@ class _MessagesMixin:
|
|
|
1171
1175
|
if self._status_bar is not None:
|
|
1172
1176
|
self._status_bar.set_clarification_mode(new_mode)
|
|
1173
1177
|
|
|
1174
|
-
#
|
|
1175
|
-
#
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
self._push_clarification_mode_to_running_loop(
|
|
1178
|
+
# Bypass carries an `interaction_mode` wire field; Plan/Ask stay
|
|
1179
|
+
# next-turn-only.
|
|
1180
|
+
if new_mode in AGENT_SUB_MODES:
|
|
1181
|
+
wire = resolve_composer_wire_fields(new_mode)
|
|
1182
|
+
self._push_clarification_mode_to_running_loop(
|
|
1183
|
+
wire.clarification_mode,
|
|
1184
|
+
interaction_mode=wire.interaction_mode,
|
|
1185
|
+
)
|
|
1179
1186
|
|
|
1180
|
-
def _push_clarification_mode_to_running_loop(
|
|
1187
|
+
def _push_clarification_mode_to_running_loop(
|
|
1188
|
+
self,
|
|
1189
|
+
mode: str,
|
|
1190
|
+
*,
|
|
1191
|
+
interaction_mode: str | None = None,
|
|
1192
|
+
) -> None:
|
|
1181
1193
|
"""Best-effort hot-swap RPC; failures are silent (next turn still works)."""
|
|
1182
1194
|
import asyncio
|
|
1183
1195
|
|
|
@@ -1190,11 +1202,16 @@ class _MessagesMixin:
|
|
|
1190
1202
|
|
|
1191
1203
|
async def _push() -> None:
|
|
1192
1204
|
try:
|
|
1193
|
-
await session.set_clarification_mode(
|
|
1205
|
+
await session.set_clarification_mode(
|
|
1206
|
+
mode,
|
|
1207
|
+
interaction_mode=interaction_mode,
|
|
1208
|
+
)
|
|
1194
1209
|
except Exception:
|
|
1195
1210
|
logger.debug(
|
|
1196
|
-
"set_clarification_mode RPC failed (mode=%s);
|
|
1211
|
+
"set_clarification_mode RPC failed (mode=%s, interaction_mode=%s); "
|
|
1212
|
+
"will apply on next turn",
|
|
1197
1213
|
mode,
|
|
1214
|
+
interaction_mode,
|
|
1198
1215
|
exc_info=True,
|
|
1199
1216
|
)
|
|
1200
1217
|
|
|
@@ -11,6 +11,7 @@ from textual.events import Click
|
|
|
11
11
|
from textual.widgets import Static
|
|
12
12
|
|
|
13
13
|
from soothe_cli.display import theme
|
|
14
|
+
from soothe_cli.display.card import _assemble_card_header
|
|
14
15
|
from soothe_cli.display.preview_limits import (
|
|
15
16
|
TOOL_APPROVAL_BODY_MAX_LINES,
|
|
16
17
|
TOOL_APPROVAL_DIFF_WIDGET_MAX_LINES,
|
|
@@ -19,7 +20,6 @@ from soothe_cli.display.preview_limits import (
|
|
|
19
20
|
from soothe_cli.settings import get_glyphs
|
|
20
21
|
from soothe_cli.tui.widgets.clipboard import screen_has_text_selection
|
|
21
22
|
from soothe_cli.tui.widgets.diff import DIFF_CODE_GAP, compose_diff_line_list
|
|
22
|
-
from soothe_cli.tui.widgets.messages._helpers import _assemble_card_header
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
from textual.app import ComposeResult
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
"""Message widgets for Soothe TUI."""
|
|
2
2
|
|
|
3
3
|
from soothe_cli.tui.widgets.messages._helpers import (
|
|
4
|
-
_assemble_card_header,
|
|
5
|
-
_is_widget_animation_visible,
|
|
6
|
-
_mode_color,
|
|
7
|
-
_should_refresh_now,
|
|
8
|
-
_strip_success_exit_line,
|
|
9
4
|
flush_deferred_tools_refreshes,
|
|
10
5
|
request_deferred_tools_refresh,
|
|
11
6
|
reset_turn_tool_refresh_state,
|
|
@@ -39,11 +34,6 @@ from soothe_cli.tui.widgets.messages.user import (
|
|
|
39
34
|
|
|
40
35
|
__all__ = [
|
|
41
36
|
# Helpers
|
|
42
|
-
"_assemble_card_header",
|
|
43
|
-
"_is_widget_animation_visible",
|
|
44
|
-
"_mode_color",
|
|
45
|
-
"_should_refresh_now",
|
|
46
|
-
"_strip_success_exit_line",
|
|
47
37
|
"flush_deferred_tools_refreshes",
|
|
48
38
|
"request_deferred_tools_refresh",
|
|
49
39
|
"reset_turn_tool_refresh_state",
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Shared helpers and constants for message widgets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import weakref
|
|
9
|
+
from time import monotonic
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from soothe_cli.display import theme
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# TUI refresh throttling - minimum interval between widget refreshes
|
|
17
|
+
_DEFAULT_TUI_REFRESH_INTERVAL_MS = 800
|
|
18
|
+
"""Default minimum interval between TUI refreshes in milliseconds."""
|
|
19
|
+
|
|
20
|
+
_global_refresh_interval_ms: int | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_tui_refresh_interval_ms() -> int:
|
|
24
|
+
"""Get the TUI refresh interval from environment or default.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Minimum interval between refreshes in milliseconds.
|
|
28
|
+
"""
|
|
29
|
+
global _global_refresh_interval_ms
|
|
30
|
+
if _global_refresh_interval_ms is not None:
|
|
31
|
+
return _global_refresh_interval_ms
|
|
32
|
+
from soothe_cli._env_vars import TUI_REFRESH_INTERVAL_MS
|
|
33
|
+
|
|
34
|
+
env_val = os.environ.get(TUI_REFRESH_INTERVAL_MS)
|
|
35
|
+
if env_val:
|
|
36
|
+
try:
|
|
37
|
+
parsed = int(env_val.strip())
|
|
38
|
+
if parsed >= 50: # Minimum 50ms to prevent UI lockup
|
|
39
|
+
_global_refresh_interval_ms = parsed
|
|
40
|
+
return parsed
|
|
41
|
+
except ValueError:
|
|
42
|
+
pass
|
|
43
|
+
_global_refresh_interval_ms = _DEFAULT_TUI_REFRESH_INTERVAL_MS
|
|
44
|
+
return _DEFAULT_TUI_REFRESH_INTERVAL_MS
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _should_refresh_now(last_refresh_time: float | None) -> bool:
|
|
48
|
+
"""Check if enough time has passed since last refresh for throttling.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
last_refresh_time: Monotonic time of last refresh, or None if never refreshed.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if refresh should proceed, False if throttled.
|
|
55
|
+
"""
|
|
56
|
+
if last_refresh_time is None:
|
|
57
|
+
return True
|
|
58
|
+
interval_secs = _get_tui_refresh_interval_ms() / 1000.0
|
|
59
|
+
return (monotonic() - last_refresh_time) >= interval_secs
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
_RUNNING_SPINNER_INTERVAL_SECONDS = 0.2
|
|
63
|
+
"""Spinner/status animation cadence for running cards."""
|
|
64
|
+
|
|
65
|
+
# Deferred tool-list refresh (turn-level coalescing + global repaint budget).
|
|
66
|
+
_DEFERRED_TOOL_REFRESH_WIDGETS: weakref.WeakSet[Any] = weakref.WeakSet()
|
|
67
|
+
_global_tools_list_refresh_at: float = 0.0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def reset_turn_tool_refresh_state() -> None:
|
|
71
|
+
"""Clear deferred refresh registry at the start of a new agent turn."""
|
|
72
|
+
global _global_tools_list_refresh_at
|
|
73
|
+
_global_tools_list_refresh_at = 0.0
|
|
74
|
+
_DEFERRED_TOOL_REFRESH_WIDGETS.clear()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def request_deferred_tools_refresh(widget: Any) -> None:
|
|
78
|
+
"""Queue a card for batched tool-list repaint."""
|
|
79
|
+
_DEFERRED_TOOL_REFRESH_WIDGETS.add(widget)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def flush_deferred_tools_refreshes(*, force: bool = False) -> None:
|
|
83
|
+
"""Repaint queued tool cards (global budget unless `force`)."""
|
|
84
|
+
global _global_tools_list_refresh_at
|
|
85
|
+
pending = list(_DEFERRED_TOOL_REFRESH_WIDGETS)
|
|
86
|
+
if not pending:
|
|
87
|
+
return
|
|
88
|
+
now = monotonic()
|
|
89
|
+
if not force:
|
|
90
|
+
interval = _get_tui_refresh_interval_ms() / 1000.0
|
|
91
|
+
if now - _global_tools_list_refresh_at < interval:
|
|
92
|
+
return
|
|
93
|
+
_global_tools_list_refresh_at = now
|
|
94
|
+
_DEFERRED_TOOL_REFRESH_WIDGETS.clear()
|
|
95
|
+
for widget in pending:
|
|
96
|
+
flush_fn = getattr(widget, "_flush_deferred_tools_refresh", None)
|
|
97
|
+
if callable(flush_fn):
|
|
98
|
+
flush_fn()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _is_widget_animation_visible(widget: object) -> bool:
|
|
102
|
+
"""Return whether a widget is currently visible on screen.
|
|
103
|
+
|
|
104
|
+
This is used to skip animation work for off-screen cards.
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
if not getattr(widget, "is_attached", False):
|
|
108
|
+
return False
|
|
109
|
+
if not getattr(widget, "visible", True):
|
|
110
|
+
return False
|
|
111
|
+
is_on_screen = getattr(widget, "is_on_screen", True)
|
|
112
|
+
if callable(is_on_screen):
|
|
113
|
+
return bool(is_on_screen())
|
|
114
|
+
return bool(is_on_screen)
|
|
115
|
+
except Exception:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _mode_color(mode: str | None, widget_or_app: object | None = None) -> str:
|
|
120
|
+
"""Return the hex color string for a mode, falling back to primary.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
mode: Mode name (e.g. `'shell'`, `'command'`) or `None`.
|
|
124
|
+
widget_or_app: Textual widget or `App` for theme-aware lookup.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Color string from the active theme's `ThemeColors`.
|
|
128
|
+
"""
|
|
129
|
+
colors = theme.get_theme_colors(widget_or_app)
|
|
130
|
+
if not mode:
|
|
131
|
+
return colors.primary
|
|
132
|
+
if mode == "shell":
|
|
133
|
+
return colors.mode_bash
|
|
134
|
+
if mode == "command":
|
|
135
|
+
return colors.mode_command
|
|
136
|
+
logger.warning("Missing color for mode '%s'; falling back to primary.", mode)
|
|
137
|
+
return colors.primary
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
_SUCCESS_EXIT_RE = re.compile(r"\n?\[Command succeeded with exit code 0\]\s*$")
|
|
141
|
+
"""Strip the SDK's `[Command succeeded with exit code 0]` trailer from tool output."""
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _strip_success_exit_line(text: str) -> str:
|
|
145
|
+
"""Remove the `[Command succeeded with exit code 0]` trailer.
|
|
146
|
+
|
|
147
|
+
Non-zero exit codes are left intact (they come through `set_error`).
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
text: Raw tool output string.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Text with the success exit-code trailer removed, if present.
|
|
154
|
+
"""
|
|
155
|
+
return _SUCCESS_EXIT_RE.sub("", text)
|
|
@@ -9,9 +9,9 @@ from textual.events import Click
|
|
|
9
9
|
from textual.widgets import Static
|
|
10
10
|
|
|
11
11
|
from soothe_cli.display import theme
|
|
12
|
+
from soothe_cli.display.card import _assemble_card_header
|
|
12
13
|
from soothe_cli.tui.widgets._links import open_style_link
|
|
13
14
|
from soothe_cli.tui.widgets.clipboard import screen_has_text_selection
|
|
14
|
-
from soothe_cli.tui.widgets.messages._helpers import _assemble_card_header
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class AppMessage(Static):
|
|
@@ -10,11 +10,9 @@ from textual.selection import Selection
|
|
|
10
10
|
from textual.strip import Strip
|
|
11
11
|
from textual.widgets import Static
|
|
12
12
|
|
|
13
|
+
from soothe_cli.display.card import _card_dot_prefix_content
|
|
13
14
|
from soothe_cli.display.markdown_theme import ThemedMarkdownRenderer, resolve_markdown_theme_parts
|
|
14
|
-
from soothe_cli.tui.widgets.messages._helpers import
|
|
15
|
-
_RUNNING_SPINNER_INTERVAL_SECONDS,
|
|
16
|
-
_card_dot_prefix_content,
|
|
17
|
-
)
|
|
15
|
+
from soothe_cli.tui.widgets.messages._helpers import _RUNNING_SPINNER_INTERVAL_SECONDS
|
|
18
16
|
|
|
19
17
|
if TYPE_CHECKING:
|
|
20
18
|
from textual.app import ComposeResult
|
{soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py
RENAMED
|
@@ -12,6 +12,10 @@ from textual.content import Content
|
|
|
12
12
|
from textual.widgets import Static
|
|
13
13
|
|
|
14
14
|
from soothe_cli.display import theme
|
|
15
|
+
from soothe_cli.display.card import (
|
|
16
|
+
_card_body_gutter,
|
|
17
|
+
_card_dot_prefix_content,
|
|
18
|
+
)
|
|
15
19
|
from soothe_cli.display.preview_limits import PLAN_QUICK_VIEW_STEP_LINE_MAX_CHARS
|
|
16
20
|
from soothe_cli.runtime.presentation.duration_format import (
|
|
17
21
|
format_duration,
|
|
@@ -23,10 +27,6 @@ from soothe_cli.runtime.presentation.step_id_format import (
|
|
|
23
27
|
numeric_step_prefix,
|
|
24
28
|
)
|
|
25
29
|
from soothe_cli.settings import get_glyphs
|
|
26
|
-
from soothe_cli.tui.widgets.messages._helpers import (
|
|
27
|
-
_card_body_gutter,
|
|
28
|
-
_card_dot_prefix_content,
|
|
29
|
-
)
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from textual.app import ComposeResult
|
{soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/cognition_reason.py
RENAMED
|
@@ -8,7 +8,7 @@ from textual.containers import Vertical
|
|
|
8
8
|
from textual.content import Content
|
|
9
9
|
from textual.widgets import Static
|
|
10
10
|
|
|
11
|
-
from soothe_cli.
|
|
11
|
+
from soothe_cli.display.card import _assemble_card_header
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from textual.app import ComposeResult
|
|
@@ -19,6 +19,11 @@ from textual.events import Click
|
|
|
19
19
|
from textual.widgets import Static
|
|
20
20
|
|
|
21
21
|
from soothe_cli.display import theme
|
|
22
|
+
from soothe_cli.display.card import (
|
|
23
|
+
_STEP_TOOL_PREVIEW_ROWS,
|
|
24
|
+
_assemble_card_header,
|
|
25
|
+
_card_body_gutter,
|
|
26
|
+
)
|
|
22
27
|
from soothe_cli.display.preview_limits import STEP_CARD_SHOW_TOOL_ROW_DETAILS
|
|
23
28
|
from soothe_cli.display.tool_display import (
|
|
24
29
|
display_width,
|
|
@@ -31,9 +36,6 @@ from soothe_cli.tui.widgets.clipboard import (
|
|
|
31
36
|
)
|
|
32
37
|
from soothe_cli.tui.widgets.messages._helpers import (
|
|
33
38
|
_RUNNING_SPINNER_INTERVAL_SECONDS,
|
|
34
|
-
_STEP_TOOL_PREVIEW_ROWS,
|
|
35
|
-
_assemble_card_header,
|
|
36
|
-
_card_body_gutter,
|
|
37
39
|
_is_widget_animation_visible,
|
|
38
40
|
_should_refresh_now,
|
|
39
41
|
_strip_success_exit_line,
|
|
@@ -645,7 +647,7 @@ class CognitionStepMessage(Vertical):
|
|
|
645
647
|
return max(0, width - 2)
|
|
646
648
|
|
|
647
649
|
def _step_task_activity_content(self) -> Content:
|
|
648
|
-
"""
|
|
650
|
+
"""Tool-use then To-do (task markers, tool preview, notes) under the step title."""
|
|
649
651
|
g = get_glyphs()
|
|
650
652
|
try:
|
|
651
653
|
colors = theme.get_theme_colors(self)
|
|
@@ -1643,7 +1645,7 @@ def create_subagent_card(
|
|
|
1643
1645
|
"""Create an intake-only orphan SubAgent card (shared step-card style path).
|
|
1644
1646
|
|
|
1645
1647
|
In-step `task` delegations stay on the parent step card. This factory only
|
|
1646
|
-
sets orphan fields (`_subagent_type`, etc.); header meta,
|
|
1648
|
+
sets orphan fields (`_subagent_type`, etc.); header meta, Tool-use / To-do
|
|
1647
1649
|
activity, and footers use the same `CognitionStepMessage` methods.
|
|
1648
1650
|
|
|
1649
1651
|
Args:
|
{soothe_cli-1.0.5 → soothe_cli-1.0.7}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py
RENAMED
|
@@ -17,6 +17,10 @@ from textual.content import Content
|
|
|
17
17
|
|
|
18
18
|
from soothe_cli.commands.subagent_routing import get_subagent_display_name
|
|
19
19
|
from soothe_cli.display import theme
|
|
20
|
+
from soothe_cli.display.card import (
|
|
21
|
+
_card_body_gutter,
|
|
22
|
+
_card_item_indent,
|
|
23
|
+
)
|
|
20
24
|
from soothe_cli.display.preview_limits import (
|
|
21
25
|
STEP_CARD_TOOL_ACTIVITY_PREVIEW_COUNT,
|
|
22
26
|
TASK_DELEGATION_DESC_MAX_CHARS,
|
|
@@ -27,10 +31,6 @@ from soothe_cli.display.tool_display import (
|
|
|
27
31
|
format_step_tool_activity_line,
|
|
28
32
|
)
|
|
29
33
|
from soothe_cli.runtime.presentation.duration_format import format_running_elapsed_compact
|
|
30
|
-
from soothe_cli.tui.widgets.messages._helpers import (
|
|
31
|
-
_card_body_gutter,
|
|
32
|
-
_card_item_indent,
|
|
33
|
-
)
|
|
34
34
|
|
|
35
35
|
# Todo item status → phase_icon input.
|
|
36
36
|
_TODO_STATUS_TO_PHASE: dict[str, str] = {
|
|
@@ -663,7 +663,7 @@ class StepCardStatusLine:
|
|
|
663
663
|
|
|
664
664
|
|
|
665
665
|
class StepActivityTree:
|
|
666
|
-
"""Pure render:
|
|
666
|
+
"""Pure render: Tool-use + To-do sections under the step title.
|
|
667
667
|
|
|
668
668
|
Task rows are flat markers under Tool-use. While a task is running, the marker
|
|
669
669
|
line shows that task's subgraph tool count. Nested child tool lines are not
|
|
@@ -686,14 +686,14 @@ class StepActivityTree:
|
|
|
686
686
|
max_cols: int | None = None,
|
|
687
687
|
glyph_override: str | None = None,
|
|
688
688
|
) -> Content:
|
|
689
|
-
"""
|
|
689
|
+
"""Tool-use section then To-do section (task markers + main tool preview).
|
|
690
690
|
|
|
691
691
|
`max_cols` bounds each rendered line to the terminal width so tool
|
|
692
692
|
rows never wrap; `None` preserves the prior fixed-cap behavior.
|
|
693
693
|
`glyph_override` is the card header glyph (subagent glyph for orphan
|
|
694
694
|
SubAgent cards) so the activity gutters align to the right of that dot.
|
|
695
695
|
|
|
696
|
-
Layout: the section header (`
|
|
696
|
+
Layout: the section header (`Tool-use` / `To-do`) sits on a `⎿`
|
|
697
697
|
body-gutter line; its items (tool rows, todo markers, notes, `+N
|
|
698
698
|
more`) are indented one column beyond the section label so they nest
|
|
699
699
|
under the header without a tree glyph.
|
|
@@ -715,29 +715,7 @@ class StepActivityTree:
|
|
|
715
715
|
return Content("")
|
|
716
716
|
|
|
717
717
|
first_block = True
|
|
718
|
-
if todo_items:
|
|
719
|
-
first_block = False
|
|
720
|
-
parts.append(Content.styled(f"{section_gutter}To-do", theme.SECONDARY_TEXT_STYLE))
|
|
721
|
-
for item in todo_items:
|
|
722
|
-
content = str(item.get("content") or "").strip()
|
|
723
|
-
if not content:
|
|
724
|
-
continue
|
|
725
|
-
status = str(item.get("status") or "pending")
|
|
726
|
-
phase = todo_status_phase(status)
|
|
727
|
-
animate = phase == "running" and step_status == "running"
|
|
728
|
-
icon = phase_icon(
|
|
729
|
-
phase,
|
|
730
|
-
g,
|
|
731
|
-
spinner_position=spinner_position,
|
|
732
|
-
animate_running=animate,
|
|
733
|
-
)
|
|
734
|
-
tone = task_tool_row_tone_for_phase(phase, colors)
|
|
735
|
-
parts.append("\n")
|
|
736
|
-
parts.append(Content.styled(f"{item_gutter}{icon} {content}", tone))
|
|
737
|
-
|
|
738
718
|
if has_tools:
|
|
739
|
-
if not first_block:
|
|
740
|
-
parts.append("\n")
|
|
741
719
|
first_block = False
|
|
742
720
|
parts.append(Content.styled(f"{section_gutter}Tool-use", theme.SECONDARY_TEXT_STYLE))
|
|
743
721
|
|
|
@@ -806,4 +784,26 @@ class StepActivityTree:
|
|
|
806
784
|
parts.append("\n")
|
|
807
785
|
parts.append(Content.styled(f"{item_gutter}· {t}", theme.SECONDARY_TEXT_STYLE))
|
|
808
786
|
|
|
787
|
+
if todo_items:
|
|
788
|
+
if not first_block:
|
|
789
|
+
parts.append("\n")
|
|
790
|
+
first_block = False
|
|
791
|
+
parts.append(Content.styled(f"{section_gutter}To-do", theme.SECONDARY_TEXT_STYLE))
|
|
792
|
+
for item in todo_items:
|
|
793
|
+
content = str(item.get("content") or "").strip()
|
|
794
|
+
if not content:
|
|
795
|
+
continue
|
|
796
|
+
status = str(item.get("status") or "pending")
|
|
797
|
+
phase = todo_status_phase(status)
|
|
798
|
+
animate = phase == "running" and step_status == "running"
|
|
799
|
+
icon = phase_icon(
|
|
800
|
+
phase,
|
|
801
|
+
g,
|
|
802
|
+
spinner_position=spinner_position,
|
|
803
|
+
animate_running=animate,
|
|
804
|
+
)
|
|
805
|
+
tone = task_tool_row_tone_for_phase(phase, colors)
|
|
806
|
+
parts.append("\n")
|
|
807
|
+
parts.append(Content.styled(f"{item_gutter}{icon} {content}", tone))
|
|
808
|
+
|
|
809
809
|
return Content.assemble(*parts) if parts else Content("")
|
|
@@ -8,7 +8,7 @@ from textual.content import Content
|
|
|
8
8
|
from textual.widgets import Static
|
|
9
9
|
|
|
10
10
|
from soothe_cli.display import theme
|
|
11
|
-
from soothe_cli.
|
|
11
|
+
from soothe_cli.display.card import _assemble_card_header
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ErrorMessage(Static):
|