soothe-cli 1.0.3__tar.gz → 1.0.4__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.3 → soothe_cli-1.0.4}/.gitignore +1 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/PKG-INFO +2 -2
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/pyproject.toml +1 -1
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/main.py +2 -2
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/commands/slash_commands.py +1 -1
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/config/cli_config.py +2 -2
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/tips.py +1 -1
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_app.py +17 -15
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_execution.py +9 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_messages_mixin.py +4 -10
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_startup.py +6 -1
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_ui.py +12 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/composer_mode.py +22 -16
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/textual_adapter.py +9 -6
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/help_screen.py +1 -1
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/loading.py +71 -1
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +13 -2
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/cognition_step.py +20 -2
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +2 -2
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/status.py +22 -65
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/README.md +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/_cli_context.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/_env_vars.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/_version.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/card_wire.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/execution/daemon.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/cli/soothecli.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/commands/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/commands/binding.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/commands/command_registry.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/commands/command_router.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/commands/subagent_routing.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/markdown_theme.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/media_utils.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/mermaid_render.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/path_utils.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/preview_limits.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/shell_color.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/spinner_labels.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/theme.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/tool_display.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/display/unicode_security.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/loops/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/loops/sessions.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/model_config.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/project_utils.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/headless/processor.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/presentation/id_format.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/presentation/step_id_format.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/state/step_router.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/token_events_debug.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/token_usage.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/transport/session.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/turn/prepare.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/_console_impl.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/bootstrap.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/core.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/glyphs.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/provider.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/shell_allow.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/skills_dirs.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/settings/stream_config.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_clarification.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_entrypoints.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_model.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_model_params.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_terminal.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_theme_prefs.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/_types.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/file_change_notify.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/context_data.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/context_viewer.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/structured_ask_user.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/pinned_goal_bar.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/router_profile_selector.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/welcome.py +0 -0
- {soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/update_check.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: soothe-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
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
|
|
@@ -21,7 +21,7 @@ Requires-Dist: pillow<12.0.0,>=10.0.0
|
|
|
21
21
|
Requires-Dist: python-dotenv<2.0.0,>=1.0.0
|
|
22
22
|
Requires-Dist: pyyaml<7.0.0,>=6.0.0
|
|
23
23
|
Requires-Dist: rich<15.0.0,>=13.0.0
|
|
24
|
-
Requires-Dist: soothe-client-python<2.0.0,>=1.0.
|
|
24
|
+
Requires-Dist: soothe-client-python<2.0.0,>=1.0.21
|
|
25
25
|
Requires-Dist: soothe-sdk<2.0.0,>=1.0.12
|
|
26
26
|
Requires-Dist: termaid<1.0.0,>=0.7.1
|
|
27
27
|
Requires-Dist: textual<10.0.0,>=8.0.0
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
25
25
|
"soothe-sdk>=1.0.12,<2.0.0", # events/display/wire contracts
|
|
26
|
-
"soothe-client-python>=1.0.
|
|
26
|
+
"soothe-client-python>=1.0.21,<2.0.0", # WebSocket transport client; matched to latest release v1.0.21
|
|
27
27
|
"typer>=0.9.0,<2.0.0", # CLI framework
|
|
28
28
|
"textual>=8.0.0,<10.0.0", # TUI framework
|
|
29
29
|
"rich>=13.0.0,<15.0.0", # Console output
|
|
@@ -198,10 +198,10 @@ def main(
|
|
|
198
198
|
help=(
|
|
199
199
|
"Auto-show the in-flow plan panel while a goal is executing "
|
|
200
200
|
"and hide it when the goal completes. Ctrl+t toggles "
|
|
201
|
-
"thereafter. Default:
|
|
201
|
+
"thereafter. Default: disabled (hidden)."
|
|
202
202
|
),
|
|
203
203
|
),
|
|
204
|
-
] =
|
|
204
|
+
] = False,
|
|
205
205
|
show_version: Annotated[ # noqa: FBT002
|
|
206
206
|
bool,
|
|
207
207
|
typer.Option("--version", help="Show version and exit."),
|
|
@@ -154,7 +154,7 @@ KEYBOARD_SHORTCUTS: dict[str, str] = {
|
|
|
154
154
|
"Ctrl+V": "Paste image from clipboard as [image N] attachment",
|
|
155
155
|
"Ctrl+T": "Toggle plan panel above thinking row",
|
|
156
156
|
"Ctrl+O": "Toggle expand/collapse of the most recent skill or tool card",
|
|
157
|
-
"Shift+Tab": "Cycle composer mode (Auto → Manual → Plan → Ask)",
|
|
157
|
+
"Shift+Tab": "Cycle composer mode (Auto → Bypass → Manual → Plan → Ask)",
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
|
|
@@ -33,9 +33,9 @@ class CLIConfig:
|
|
|
33
33
|
"""Markdown appearance preset (``match-app``, ``langchain``, ``standard``, …)."""
|
|
34
34
|
|
|
35
35
|
# Plan panel visibility preference
|
|
36
|
-
plan_panel_default_visible: bool =
|
|
36
|
+
plan_panel_default_visible: bool = False
|
|
37
37
|
"""When True, auto-show the in-flow plan panel while a goal is executing and
|
|
38
|
-
hide it when the goal completes. Default
|
|
38
|
+
hide it when the goal completes. Default False (hidden; Ctrl+t to open)."""
|
|
39
39
|
|
|
40
40
|
# Output streaming overrides (RFC-614)
|
|
41
41
|
output_streaming_enabled: bool | None = None
|
|
@@ -40,7 +40,7 @@ from soothe_cli.tui.app._types import (
|
|
|
40
40
|
from soothe_cli.tui.app._ui import _UIMixin
|
|
41
41
|
from soothe_cli.tui.composer_mode import normalize_composer_mode
|
|
42
42
|
from soothe_cli.tui.widgets.chat_input import ChatInput
|
|
43
|
-
from soothe_cli.tui.widgets.loading import LoadingWidget
|
|
43
|
+
from soothe_cli.tui.widgets.loading import LoadingWidget, TipRow
|
|
44
44
|
from soothe_cli.tui.widgets.message_store import MessageStore
|
|
45
45
|
from soothe_cli.tui.widgets.messages import (
|
|
46
46
|
AssistantMessage,
|
|
@@ -226,10 +226,11 @@ class SootheApp(
|
|
|
226
226
|
self._mcp_tool_count = sum(len(s.tools) for s in (mcp_server_info or []))
|
|
227
227
|
|
|
228
228
|
self._status_bar: StatusBar | None = None
|
|
229
|
+
self._tip_row: TipRow | None = None
|
|
229
230
|
self._default_session_tip: str = ""
|
|
230
231
|
self._status_notification_timer: Timer | None = None
|
|
231
232
|
self._status_notification_active = False
|
|
232
|
-
# Rotating tip source + interval timer for the
|
|
233
|
+
# Rotating tip source + interval timer for the thinking row.
|
|
233
234
|
self._tip_rotator: TipRotator = TipRotator()
|
|
234
235
|
self._tip_rotation_timer: Timer | None = None
|
|
235
236
|
|
|
@@ -381,11 +382,11 @@ class SootheApp(
|
|
|
381
382
|
"""Persist and render the fallback tip shown when no notification is active."""
|
|
382
383
|
cleaned = (tip or "").strip()
|
|
383
384
|
self._default_session_tip = cleaned
|
|
384
|
-
if self.
|
|
385
|
-
self.
|
|
385
|
+
if self._tip_row is not None and not self._status_notification_active:
|
|
386
|
+
self._tip_row.set_tip(cleaned)
|
|
386
387
|
|
|
387
388
|
def start_tip_rotation(self, interval: float = 12.0) -> None:
|
|
388
|
-
"""Cycle through rotating tips in the
|
|
389
|
+
"""Cycle through rotating tips in the thinking row at a fixed interval.
|
|
389
390
|
|
|
390
391
|
Args:
|
|
391
392
|
interval: Seconds between tip rotations.
|
|
@@ -401,7 +402,7 @@ class SootheApp(
|
|
|
401
402
|
self._tip_rotation_timer = None
|
|
402
403
|
|
|
403
404
|
def _rotate_session_tip(self) -> None:
|
|
404
|
-
"""Advance to the next tip and push it to the
|
|
405
|
+
"""Advance to the next tip and push it to the thinking row.
|
|
405
406
|
|
|
406
407
|
Transient notifications take precedence: rotation is skipped while a
|
|
407
408
|
notification is active so it can run its timeout uninterrupted.
|
|
@@ -411,7 +412,7 @@ class SootheApp(
|
|
|
411
412
|
self.set_default_session_tip(self._tip_rotator.next_tip())
|
|
412
413
|
|
|
413
414
|
def _set_status_notification(self, message: str, *, timeout: float | None = None) -> None:
|
|
414
|
-
"""Render a transient notification in the
|
|
415
|
+
"""Render a transient notification in the thinking-row tip area."""
|
|
415
416
|
text = (message or "").strip()
|
|
416
417
|
if not text:
|
|
417
418
|
return
|
|
@@ -420,8 +421,8 @@ class SootheApp(
|
|
|
420
421
|
self._status_notification_timer.stop()
|
|
421
422
|
self._status_notification_timer = None
|
|
422
423
|
self._status_notification_active = True
|
|
423
|
-
if self.
|
|
424
|
-
self.
|
|
424
|
+
if self._tip_row is not None:
|
|
425
|
+
self._tip_row.set_notification(text)
|
|
425
426
|
|
|
426
427
|
duration = timeout if timeout and timeout > 0 else 3.0
|
|
427
428
|
self._status_notification_timer = self.set_timer(duration, self._clear_status_notification)
|
|
@@ -430,8 +431,8 @@ class SootheApp(
|
|
|
430
431
|
"""Restore the default tip after transient notification timeout."""
|
|
431
432
|
self._status_notification_timer = None
|
|
432
433
|
self._status_notification_active = False
|
|
433
|
-
if self.
|
|
434
|
-
self.
|
|
434
|
+
if self._tip_row is not None:
|
|
435
|
+
self._tip_row.set_tip(self._default_session_tip)
|
|
435
436
|
|
|
436
437
|
def notify( # type: ignore[override]
|
|
437
438
|
self,
|
|
@@ -442,7 +443,7 @@ class SootheApp(
|
|
|
442
443
|
timeout: float | None = None,
|
|
443
444
|
markup: bool = True, # noqa: ARG002
|
|
444
445
|
) -> None:
|
|
445
|
-
"""Display notifications in the
|
|
446
|
+
"""Display notifications in the thinking-row tip area instead of toast bubbles."""
|
|
446
447
|
text = str(message or "").strip()
|
|
447
448
|
if not text:
|
|
448
449
|
return
|
|
@@ -488,14 +489,15 @@ class SootheApp(
|
|
|
488
489
|
# In-flow plan panel: sits above the sticky bottom chrome so Ctrl+t
|
|
489
490
|
# never covers the thinking row or chat input. Auto-shows while a goal
|
|
490
491
|
# is executing when CLIConfig.plan_panel_default_visible is True
|
|
491
|
-
# (
|
|
492
|
-
plan_visible = getattr(self._daemon_config, "plan_panel_default_visible",
|
|
492
|
+
# (--plan-panel); default is False (hidden, Ctrl+t to open).
|
|
493
|
+
plan_visible = getattr(self._daemon_config, "plan_panel_default_visible", False)
|
|
493
494
|
yield PlanQuickViewOverlay(
|
|
494
495
|
id="plan-quick-view-overlay",
|
|
495
496
|
default_visible=plan_visible,
|
|
496
497
|
)
|
|
497
498
|
with Container(id="bottom-app-container"):
|
|
498
|
-
|
|
499
|
+
with Container(id="thinking-status"):
|
|
500
|
+
yield TipRow(id="tip-row")
|
|
499
501
|
yield ChatInput(
|
|
500
502
|
cwd=self._cwd,
|
|
501
503
|
image_tracker=self._image_tracker,
|
|
@@ -941,6 +941,10 @@ class _ExecutionMixin:
|
|
|
941
941
|
)
|
|
942
942
|
)
|
|
943
943
|
return
|
|
944
|
+
except Exception as exc:
|
|
945
|
+
logger.warning("Unexpected error connecting to daemon", exc_info=True)
|
|
946
|
+
await self._mount_message(ErrorMessage(f"Unexpected connection error. {exc}"))
|
|
947
|
+
return
|
|
944
948
|
self._agent_running = True
|
|
945
949
|
|
|
946
950
|
# Stop passive reads so active turn streaming has sole websocket access.
|
|
@@ -1093,6 +1097,11 @@ class _ExecutionMixin:
|
|
|
1093
1097
|
continue
|
|
1094
1098
|
except (ConnectionError, OSError, TimeoutError):
|
|
1095
1099
|
pass
|
|
1100
|
+
except Exception:
|
|
1101
|
+
logger.debug(
|
|
1102
|
+
"Unexpected error during daemon reconnect retry",
|
|
1103
|
+
exc_info=True,
|
|
1104
|
+
)
|
|
1096
1105
|
raise
|
|
1097
1106
|
except Exception as e: # Resilient tool rendering
|
|
1098
1107
|
# Attach-only idle timeout is benign: the prior turn had already
|
|
@@ -983,7 +983,7 @@ class _MessagesMixin:
|
|
|
983
983
|
"""Shift+Tab: navigate loop selector when active, otherwise cycle mode.
|
|
984
984
|
|
|
985
985
|
- In the LoopSelectorScreen, defer to its filter navigation.
|
|
986
|
-
- On the main screen, cycle composer mode Auto → Manual → Plan → Ask.
|
|
986
|
+
- On the main screen, cycle composer mode Auto → Bypass → Manual → Plan → Ask.
|
|
987
987
|
"""
|
|
988
988
|
from soothe_cli.tui.widgets.loop_selector import LoopSelectorScreen
|
|
989
989
|
|
|
@@ -995,15 +995,9 @@ class _MessagesMixin:
|
|
|
995
995
|
def cycle_composer_mode(self) -> None:
|
|
996
996
|
"""Advance composer mode and refresh the status-bar badge.
|
|
997
997
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
immediately; no toast is emitted because the badge is the feedback.
|
|
1002
|
-
|
|
1003
|
-
When a goal is running and the new mode is Auto or Manual, the mode is
|
|
1004
|
-
also hot-swapped on the daemon's live `LoopRuntimeContext` via the
|
|
1005
|
-
`loop_set_clarification_mode` RPC so the next clarification uses the
|
|
1006
|
-
new policy without waiting for the next turn.
|
|
998
|
+
Cycle: Auto → Bypass → Manual → Plan → Ask → Auto. When a goal is
|
|
999
|
+
running and the new mode is Auto or Manual, hot-swaps the daemon's
|
|
1000
|
+
live clarification policy via `loop_set_clarification_mode`.
|
|
1007
1001
|
"""
|
|
1008
1002
|
from soothe_cli.tui.composer_mode import next_composer_mode
|
|
1009
1003
|
|
|
@@ -24,6 +24,7 @@ from soothe_cli.tui.app._types import (
|
|
|
24
24
|
TextualSessionState,
|
|
25
25
|
)
|
|
26
26
|
from soothe_cli.tui.widgets.chat_input import ChatInput
|
|
27
|
+
from soothe_cli.tui.widgets.loading import TipRow
|
|
27
28
|
from soothe_cli.tui.widgets.messages import (
|
|
28
29
|
AppMessage,
|
|
29
30
|
ErrorMessage,
|
|
@@ -129,6 +130,7 @@ class _StartupMixin:
|
|
|
129
130
|
self.watch(chat, "scroll_y", self._on_chat_scroll_y_changed)
|
|
130
131
|
|
|
131
132
|
self._status_bar = self.query_one("#status-bar", StatusBar)
|
|
133
|
+
self._tip_row = self.query_one("#tip-row", TipRow)
|
|
132
134
|
self._chat_input = self.query_one("#input-area", ChatInput)
|
|
133
135
|
with suppress(NoMatches):
|
|
134
136
|
from soothe_cli.tui.widgets.plan_quick_view_overlay import PlanQuickViewOverlay
|
|
@@ -142,8 +144,11 @@ class _StartupMixin:
|
|
|
142
144
|
# hard-coded "auto" fallback before the daemon-default seed runs).
|
|
143
145
|
self._status_bar.set_clarification_mode(self._composer_mode)
|
|
144
146
|
|
|
145
|
-
# Seed the
|
|
147
|
+
# Seed the thinking-row tip with the first rotating tip and start rotation.
|
|
148
|
+
# The tip row is visible while the loop is idle; `_set_spinner` hides it
|
|
149
|
+
# when the agent starts working and restores it when the loop goes idle.
|
|
146
150
|
self.set_default_session_tip(self._tip_rotator.next_tip())
|
|
151
|
+
self._show_tip_row()
|
|
147
152
|
self.start_tip_rotation()
|
|
148
153
|
|
|
149
154
|
# Focus the input immediately so the cursor is visible on first paint
|
|
@@ -421,8 +421,10 @@ class _UIMixin:
|
|
|
421
421
|
await self._loading_widget.remove()
|
|
422
422
|
self._loading_widget = None
|
|
423
423
|
self._connect_spinner_start_mono = None
|
|
424
|
+
self._show_tip_row()
|
|
424
425
|
return
|
|
425
426
|
|
|
427
|
+
self._hide_tip_row()
|
|
426
428
|
thinking_status = self.query_one("#thinking-status", Container)
|
|
427
429
|
|
|
428
430
|
connect_start_mono: float | None = None
|
|
@@ -481,6 +483,16 @@ class _UIMixin:
|
|
|
481
483
|
if self._loading_widget is not None:
|
|
482
484
|
self._loading_widget.resume()
|
|
483
485
|
|
|
486
|
+
def _show_tip_row(self) -> None:
|
|
487
|
+
"""Show the rotating tip in the thinking row when the loop is idle."""
|
|
488
|
+
if self._tip_row is not None:
|
|
489
|
+
self._tip_row.display = True
|
|
490
|
+
|
|
491
|
+
def _hide_tip_row(self) -> None:
|
|
492
|
+
"""Hide the tip row so the thinking spinner gets the full row."""
|
|
493
|
+
if self._tip_row is not None:
|
|
494
|
+
self._tip_row.display = False
|
|
495
|
+
|
|
484
496
|
def action_toggle_plan_quick_view(self) -> None:
|
|
485
497
|
"""Expand or collapse the in-flow plan panel above the thinking row."""
|
|
486
498
|
if self.screen.is_modal:
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""TUI composer modes — working-mode hierarchy.
|
|
2
|
+
|
|
3
|
+
Agent sub-modes (auto, bypass, manual) grouped first, then plan and ask.
|
|
4
|
+
Shift+Tab cycle: Auto → Bypass → Manual → Plan → Ask → Auto.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
@@ -8,15 +12,25 @@ COMPOSER_MODE_AUTO = "auto"
|
|
|
8
12
|
COMPOSER_MODE_MANUAL = "manual"
|
|
9
13
|
COMPOSER_MODE_PLAN = "plan"
|
|
10
14
|
COMPOSER_MODE_ASK = "ask"
|
|
15
|
+
COMPOSER_MODE_BYPASS = "bypass"
|
|
11
16
|
|
|
17
|
+
# Agent sub-modes grouped first, then standalone working modes.
|
|
12
18
|
COMPOSER_MODE_ORDER: tuple[str, ...] = (
|
|
13
19
|
COMPOSER_MODE_AUTO,
|
|
20
|
+
COMPOSER_MODE_BYPASS,
|
|
14
21
|
COMPOSER_MODE_MANUAL,
|
|
15
22
|
COMPOSER_MODE_PLAN,
|
|
16
23
|
COMPOSER_MODE_ASK,
|
|
17
24
|
)
|
|
18
25
|
VALID_COMPOSER_MODES: frozenset[str] = frozenset(COMPOSER_MODE_ORDER)
|
|
19
26
|
|
|
27
|
+
# Agent working mode sub-modes (full mutating tool surface).
|
|
28
|
+
AGENT_SUB_MODES: frozenset[str] = frozenset(
|
|
29
|
+
{COMPOSER_MODE_AUTO, COMPOSER_MODE_MANUAL, COMPOSER_MODE_BYPASS}
|
|
30
|
+
)
|
|
31
|
+
# Standalone working modes (not agent sub-modes).
|
|
32
|
+
STANDALONE_WORKING_MODES: frozenset[str] = frozenset({COMPOSER_MODE_PLAN, COMPOSER_MODE_ASK})
|
|
33
|
+
|
|
20
34
|
|
|
21
35
|
@dataclass(frozen=True)
|
|
22
36
|
class ComposerWireFields:
|
|
@@ -35,11 +49,7 @@ def normalize_composer_mode(mode: str | None) -> str:
|
|
|
35
49
|
|
|
36
50
|
|
|
37
51
|
def next_composer_mode(current: str) -> str:
|
|
38
|
-
"""Advance
|
|
39
|
-
|
|
40
|
-
Unknown values normalize to `auto` (the default, same as a first
|
|
41
|
-
Shift+Tab from a garbage seed), without advancing past Auto in that step.
|
|
42
|
-
"""
|
|
52
|
+
"""Advance to the next mode in the cycle. Unknown values reset to `auto`."""
|
|
43
53
|
if current not in VALID_COMPOSER_MODES:
|
|
44
54
|
return COMPOSER_MODE_AUTO
|
|
45
55
|
idx = COMPOSER_MODE_ORDER.index(current)
|
|
@@ -47,23 +57,19 @@ def next_composer_mode(current: str) -> str:
|
|
|
47
57
|
|
|
48
58
|
|
|
49
59
|
def resolve_composer_wire_fields(mode: str) -> ComposerWireFields:
|
|
50
|
-
"""Map composer mode to wire
|
|
51
|
-
`interaction_mode`.
|
|
52
|
-
|
|
53
|
-
Args:
|
|
54
|
-
mode: Composer mode (`auto`, `manual`, `plan`, or `ask`).
|
|
60
|
+
"""Map composer mode to wire fields.
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`clarification_mode=auto` plus `interaction_mode=ask`. Slash routing
|
|
60
|
-
in the message still wins over the sticky hint at send time.
|
|
62
|
+
auto → clarification=auto, interaction=None; manual → clarification=manual;
|
|
63
|
+
bypass → clarification=auto, interaction=bypass; plan → interaction=plan;
|
|
64
|
+
ask → interaction=ask. Slash routing in the message wins over the sticky hint.
|
|
61
65
|
"""
|
|
62
66
|
normalized = normalize_composer_mode(mode)
|
|
63
67
|
if normalized == COMPOSER_MODE_PLAN:
|
|
64
68
|
return ComposerWireFields(COMPOSER_MODE_AUTO, None, "plan")
|
|
65
69
|
if normalized == COMPOSER_MODE_ASK:
|
|
66
70
|
return ComposerWireFields(COMPOSER_MODE_AUTO, None, COMPOSER_MODE_ASK)
|
|
71
|
+
if normalized == COMPOSER_MODE_BYPASS:
|
|
72
|
+
return ComposerWireFields(COMPOSER_MODE_AUTO, None, COMPOSER_MODE_BYPASS)
|
|
67
73
|
if normalized == COMPOSER_MODE_MANUAL:
|
|
68
74
|
return ComposerWireFields(COMPOSER_MODE_MANUAL, None, None)
|
|
69
75
|
return ComposerWireFields(COMPOSER_MODE_AUTO, None, None)
|
|
@@ -4121,7 +4121,15 @@ async def execute_task_textual(
|
|
|
4121
4121
|
goal_loop_start_monotonic=goal_loop_start_monotonic,
|
|
4122
4122
|
turn_start_monotonic=start_time,
|
|
4123
4123
|
)
|
|
4124
|
-
|
|
4124
|
+
follow_on = (
|
|
4125
|
+
data.get("follow_on_exec")
|
|
4126
|
+
if isinstance(data, dict)
|
|
4127
|
+
else None
|
|
4128
|
+
)
|
|
4129
|
+
# Skip the terminal footer when a follow-on exec
|
|
4130
|
+
# goal is pending: the daemon immediately enqueues
|
|
4131
|
+
# it, so showing "Done" here is misleading.
|
|
4132
|
+
if adapter._goal_tree_message is not None and not follow_on:
|
|
4125
4133
|
goal_elapsed_start = _goal_loop_elapsed_start(
|
|
4126
4134
|
goal_loop_start_monotonic=goal_loop_start_monotonic,
|
|
4127
4135
|
turn_start_monotonic=start_time,
|
|
@@ -4164,11 +4172,6 @@ async def execute_task_textual(
|
|
|
4164
4172
|
# row stays active through the plan→exec transition;
|
|
4165
4173
|
# the exec goal's ``STRANGE_LOOP_STARTED`` will
|
|
4166
4174
|
# re-anchor and replace it.
|
|
4167
|
-
follow_on = (
|
|
4168
|
-
data.get("follow_on_exec")
|
|
4169
|
-
if isinstance(data, dict)
|
|
4170
|
-
else None
|
|
4171
|
-
)
|
|
4172
4175
|
if adapter._set_spinner and not clarification_pending:
|
|
4173
4176
|
if follow_on:
|
|
4174
4177
|
adapter._plan_approve_follow_on_pending = True
|
|
@@ -69,7 +69,7 @@ def build_keyboard_shortcut_rows() -> list[tuple[str, str]]:
|
|
|
69
69
|
"Toggle plan panel (shown by default; --no-plan-panel disables auto-show)",
|
|
70
70
|
),
|
|
71
71
|
("Ctrl+O", "Toggle expand/collapse of the most recent skill or tool card"),
|
|
72
|
-
("Shift+Tab", "Cycle composer mode (Auto → Manual → Plan → Ask)"),
|
|
72
|
+
("Shift+Tab", "Cycle composer mode (Auto → Bypass → Manual → Plan → Ask)"),
|
|
73
73
|
("@filename", "Autocomplete files and inject content"),
|
|
74
74
|
("/command", "Slash commands (e.g. /help, /clear, /quit)"),
|
|
75
75
|
("!command", "Run shell commands directly"),
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from collections.abc import Callable
|
|
6
6
|
from time import monotonic
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
9
|
from textual.content import Content
|
|
10
10
|
from textual.widgets import Static
|
|
@@ -305,3 +305,73 @@ class LoadingWidget(Static):
|
|
|
305
305
|
def stop(self) -> None:
|
|
306
306
|
"""Stop the animation (widget will be removed by caller)."""
|
|
307
307
|
self._stop_timer()
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class TipRow(Static):
|
|
311
|
+
"""Rotating tip / transient notice shown in the thinking row when idle.
|
|
312
|
+
|
|
313
|
+
Sits in the same ``#thinking-status`` container as :class:`LoadingWidget`.
|
|
314
|
+
When the loop is inactive (no spinner) the tip is visible; when the loop
|
|
315
|
+
is active the spinner replaces it and this row is hidden by the caller.
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
DEFAULT_CSS = """
|
|
319
|
+
TipRow {
|
|
320
|
+
height: 1;
|
|
321
|
+
padding: 0 1;
|
|
322
|
+
margin-top: 1;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
TipRow.notification {
|
|
326
|
+
color: $warning;
|
|
327
|
+
text-style: bold;
|
|
328
|
+
}
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
332
|
+
"""Initialize the tip row with no content."""
|
|
333
|
+
super().__init__(*args, **kwargs)
|
|
334
|
+
self._is_notification = False
|
|
335
|
+
self._tip_text: str = ""
|
|
336
|
+
|
|
337
|
+
def _render_tip(self, text: str) -> Content:
|
|
338
|
+
colors = theme.get_theme_colors(self)
|
|
339
|
+
prefix = Content.styled("💡 ", colors.muted)
|
|
340
|
+
body = Content.styled(text, colors.muted)
|
|
341
|
+
return Content.assemble(prefix, body)
|
|
342
|
+
|
|
343
|
+
def _render_notice(self, text: str) -> Content:
|
|
344
|
+
colors = theme.get_theme_colors(self)
|
|
345
|
+
prefix = Content.styled("⚠ ", colors.warning)
|
|
346
|
+
body = Content.styled(text, colors.warning)
|
|
347
|
+
return Content.assemble(prefix, body)
|
|
348
|
+
|
|
349
|
+
def _refresh_content(self) -> None:
|
|
350
|
+
"""Re-render the stored tip/notice text, styled only when mounted."""
|
|
351
|
+
if not self.is_mounted:
|
|
352
|
+
return
|
|
353
|
+
text = self._tip_text
|
|
354
|
+
if not text:
|
|
355
|
+
self.update("")
|
|
356
|
+
elif self._is_notification:
|
|
357
|
+
self.update(self._render_notice(text))
|
|
358
|
+
else:
|
|
359
|
+
self.update(self._render_tip(text))
|
|
360
|
+
|
|
361
|
+
def on_mount(self) -> None:
|
|
362
|
+
"""Render the initial tip content once the widget is in the DOM."""
|
|
363
|
+
self._refresh_content()
|
|
364
|
+
|
|
365
|
+
def set_tip(self, text: str) -> None:
|
|
366
|
+
"""Show a default rotating tip (muted, non-bold)."""
|
|
367
|
+
self._is_notification = False
|
|
368
|
+
self.remove_class("notification")
|
|
369
|
+
self._tip_text = (text or "").strip()
|
|
370
|
+
self._refresh_content()
|
|
371
|
+
|
|
372
|
+
def set_notification(self, text: str) -> None:
|
|
373
|
+
"""Show a transient notice (warning-colored, bold)."""
|
|
374
|
+
self._is_notification = True
|
|
375
|
+
self.add_class("notification")
|
|
376
|
+
self._tip_text = (text or "").strip()
|
|
377
|
+
self._refresh_content()
|
{soothe_cli-1.0.3 → soothe_cli-1.0.4}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py
RENAMED
|
@@ -343,9 +343,20 @@ class CognitionGoalTreeMessage(Vertical):
|
|
|
343
343
|
return f"↑{format_token_count(total_in)} ↓{format_token_count(total_out)}"
|
|
344
344
|
|
|
345
345
|
def mark_loop_started(self, started_at: float | None = None) -> None:
|
|
346
|
-
"""Anchor plan-level elapsed time (matches thinking-row turn start).
|
|
347
|
-
|
|
346
|
+
"""Anchor plan-level elapsed time (matches thinking-row turn start).
|
|
347
|
+
|
|
348
|
+
On reuse after a terminal footer (plan-approve → exec follow-on),
|
|
349
|
+
clear the stale footer + step rows so the panel returns to live state.
|
|
350
|
+
"""
|
|
351
|
+
if self._loop_started_at is None or self._footer_visible:
|
|
348
352
|
self._loop_started_at = started_at if started_at is not None else time()
|
|
353
|
+
if self._footer_visible:
|
|
354
|
+
self._footer_visible = False
|
|
355
|
+
self._footer_plain = ""
|
|
356
|
+
self._footer_tone = "muted"
|
|
357
|
+
self._step_order.clear()
|
|
358
|
+
self._steps.clear()
|
|
359
|
+
self._sync_goal_tree_widgets()
|
|
349
360
|
|
|
350
361
|
def _indent_prefix(self) -> str:
|
|
351
362
|
"""Body gutter aligned to the right of the goal header prefix dot.
|
|
@@ -233,6 +233,20 @@ class CognitionStepMessage(Vertical):
|
|
|
233
233
|
return "[Ask] "
|
|
234
234
|
return ""
|
|
235
235
|
|
|
236
|
+
@property
|
|
237
|
+
def _suppress_execute_prose(self) -> bool:
|
|
238
|
+
"""True when the step's execute prose should not render in the detail panel.
|
|
239
|
+
|
|
240
|
+
In plan mode, the agent's final message IS the plan document. The plan
|
|
241
|
+
review card (StructuredAskUserWidget with origin ``plan_mode_review``)
|
|
242
|
+
already renders the full plan body (formatted, collapsible). Showing the
|
|
243
|
+
raw plan markdown again on the step card is redundant — especially after a
|
|
244
|
+
refinement, where the old step card would display the *original* plan
|
|
245
|
+
alongside the *refined* plan review card. Suppress the prose so only the
|
|
246
|
+
review card shows the plan content.
|
|
247
|
+
"""
|
|
248
|
+
return self._interaction_mode == "plan"
|
|
249
|
+
|
|
236
250
|
def set_description(self, description: str) -> None:
|
|
237
251
|
"""Update the step title (full plan/execute brief, no abbreviation)."""
|
|
238
252
|
text = (description or "").strip() or "(step)"
|
|
@@ -487,7 +501,7 @@ class CognitionStepMessage(Vertical):
|
|
|
487
501
|
suffix=tool_part,
|
|
488
502
|
)
|
|
489
503
|
prose = (self._last_completed_execute_prose or "").strip()
|
|
490
|
-
if prose:
|
|
504
|
+
if prose and not self._suppress_execute_prose:
|
|
491
505
|
self._detail_widget.update(self._step_branched_execute_body(prose, muted=True))
|
|
492
506
|
self._detail_widget.display = True
|
|
493
507
|
elif self._has_clarification_details:
|
|
@@ -1445,8 +1459,12 @@ class CognitionStepMessage(Vertical):
|
|
|
1445
1459
|
suffix=f"{tool_part}{token_suffix}",
|
|
1446
1460
|
)
|
|
1447
1461
|
self._sync_step_card_surface()
|
|
1448
|
-
if prose:
|
|
1462
|
+
if prose and not self._suppress_execute_prose:
|
|
1449
1463
|
self._detail_widget.update(self._step_branched_execute_body(prose, muted=True))
|
|
1464
|
+
elif self._detail_widget is not None:
|
|
1465
|
+
# Clear stale running-display content (e.g. plan markdown in
|
|
1466
|
+
# plan mode) so the detail panel is empty when collapsed.
|
|
1467
|
+
self._detail_widget.update("")
|
|
1450
1468
|
self._auto_collapse_on_terminal()
|
|
1451
1469
|
return
|
|
1452
1470
|
|
|
@@ -135,7 +135,7 @@ class PlanQuickViewOverlay(Vertical):
|
|
|
135
135
|
|
|
136
136
|
Auto-shows while a goal is executing (`_loop_executing()`) when the
|
|
137
137
|
preferred visibility is on (`CLIConfig.plan_panel_default_visible`,
|
|
138
|
-
default
|
|
138
|
+
default False). Auto-hides once the loop reaches a terminal footer
|
|
139
139
|
(`set_loop_finished` / `set_interrupted`). Toggle with `Ctrl+t`.
|
|
140
140
|
Mounted as a Screen sibling between `#chat` and
|
|
141
141
|
`#bottom-app-container` so expanding it shrinks the transcript
|
|
@@ -191,7 +191,7 @@ class PlanQuickViewOverlay(Vertical):
|
|
|
191
191
|
}
|
|
192
192
|
"""
|
|
193
193
|
|
|
194
|
-
def __init__(self, *, default_visible: bool =
|
|
194
|
+
def __init__(self, *, default_visible: bool = False, **kwargs: Any) -> None:
|
|
195
195
|
super().__init__(**kwargs)
|
|
196
196
|
self._preferred_visible: bool = default_visible
|
|
197
197
|
# True when the user explicitly opened the panel (Ctrl+t). Suppresses
|