soothe-cli 0.4.7__tar.gz → 0.4.8__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-0.4.7 → soothe_cli-0.4.8}/PKG-INFO +1 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/execution/daemon.py +0 -2
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/config/cli_config.py +0 -13
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/core/event_processor.py +2 -8
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/message_processing.py +1 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py +47 -20
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/_session_stats.py +6 -3
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/app.py +15 -11
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/app.tcss +15 -5
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/textual_adapter.py +736 -242
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/tool_display.py +96 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/loading.py +51 -18
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/message_store.py +31 -1
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/messages.py +998 -96
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/.gitignore +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/README.md +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/pyproject.toml +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/commands/thread_cmd.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/main.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/stream/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/stream/context.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/stream/display_line.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/stream/formatter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/stream/pipeline.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/cli/stream/task_scope.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/plan/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/plan/rich_tree.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/commands/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/commands/command_router.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/commands/slash_commands.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/commands/subagent_routing.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/config_loader.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/core/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/core/presentation_engine.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/core/processor_state.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/core/renderer_protocol.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/events/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/events/display_policy.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/events/essential_events.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/events/explore_task_display.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/events/stream_accumulator.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/events/tui_trace_log.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/rendering/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/rendering/async_renderer_protocol.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/rendering/renderer_base.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/_utils.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/rendering.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_call_resolution.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_card_payload.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_card_visibility.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/base.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/execution.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/fallback.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/goal_formatter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/media.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/structured.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/subagent.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/web.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_message_format.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_output_formatter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/_ask_user_types.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/command_registry.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/config.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/daemon_session.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/file_ops.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/formatting.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/message_display_filter.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/output.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/preview_limits.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/approval.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/thread_selector.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/tools.py +0 -0
- {soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/tui/widgets/welcome.py +0 -0
|
@@ -47,7 +47,6 @@ async def run_headless_via_daemon(
|
|
|
47
47
|
|
|
48
48
|
ws_url = websocket_url_from_config(cfg)
|
|
49
49
|
client = WebSocketClient(url=ws_url)
|
|
50
|
-
final_output_mode = getattr(cfg, "final_output_mode", "streaming")
|
|
51
50
|
|
|
52
51
|
try:
|
|
53
52
|
await connect_websocket_with_retries(client)
|
|
@@ -85,7 +84,6 @@ async def run_headless_via_daemon(
|
|
|
85
84
|
renderer = HeadlessCliRenderer()
|
|
86
85
|
processor = EventProcessor(
|
|
87
86
|
renderer,
|
|
88
|
-
final_output_mode=final_output_mode,
|
|
89
87
|
presentation_engine=presentation,
|
|
90
88
|
headless_output=True,
|
|
91
89
|
)
|
|
@@ -27,8 +27,6 @@ class CLIConfig:
|
|
|
27
27
|
# logging_level: DEBUG/INFO/… for ~/.soothe/logs/soothe-cli.log; None = default INFO.
|
|
28
28
|
logging_level: str | None = None
|
|
29
29
|
|
|
30
|
-
final_output_mode: str = "streaming"
|
|
31
|
-
|
|
32
30
|
# Output streaming overrides (RFC-614)
|
|
33
31
|
output_streaming_enabled: bool | None = None
|
|
34
32
|
"""Override daemon streaming enabled setting."""
|
|
@@ -107,26 +105,15 @@ class CLIConfig:
|
|
|
107
105
|
transports = daemon_section.get("transports", {})
|
|
108
106
|
websocket = transports.get("websocket", {})
|
|
109
107
|
websocket_legacy = data.get("websocket", {})
|
|
110
|
-
ui_section = data.get("ui", {})
|
|
111
108
|
|
|
112
109
|
raw_level = data.get("logging_level")
|
|
113
110
|
if raw_level is not None and not isinstance(raw_level, str):
|
|
114
111
|
raw_level = None
|
|
115
112
|
|
|
116
|
-
raw_final_output_mode = data.get("final_output_mode")
|
|
117
|
-
if raw_final_output_mode is None and isinstance(ui_section, dict):
|
|
118
|
-
raw_final_output_mode = ui_section.get("final_output_mode")
|
|
119
|
-
if not isinstance(raw_final_output_mode, str):
|
|
120
|
-
raw_final_output_mode = "streaming"
|
|
121
|
-
final_output_mode = raw_final_output_mode.strip().lower()
|
|
122
|
-
if final_output_mode not in {"streaming", "batch"}:
|
|
123
|
-
final_output_mode = "streaming"
|
|
124
|
-
|
|
125
113
|
return cls(
|
|
126
114
|
daemon_host=websocket.get("host", websocket_legacy.get("host", "127.0.0.1")),
|
|
127
115
|
daemon_port=websocket.get("port", websocket_legacy.get("port", 8765)),
|
|
128
116
|
logging_level=raw_level,
|
|
129
|
-
final_output_mode=final_output_mode,
|
|
130
117
|
soothe_home=Path(data.get("home", str(Path.home() / ".soothe"))),
|
|
131
118
|
)
|
|
132
119
|
|
|
@@ -82,7 +82,6 @@ class EventProcessor:
|
|
|
82
82
|
self,
|
|
83
83
|
renderer: RendererProtocol,
|
|
84
84
|
*,
|
|
85
|
-
final_output_mode: str = "streaming",
|
|
86
85
|
presentation_engine: PresentationEngine | None = None,
|
|
87
86
|
tui_debug: bool = False,
|
|
88
87
|
headless_output: bool = False,
|
|
@@ -98,9 +97,6 @@ class EventProcessor:
|
|
|
98
97
|
"""
|
|
99
98
|
self._renderer = renderer
|
|
100
99
|
self._headless_output = headless_output
|
|
101
|
-
self._final_output_mode = (
|
|
102
|
-
final_output_mode if final_output_mode in {"streaming", "batch"} else "streaming"
|
|
103
|
-
)
|
|
104
100
|
self._tui_debug = tui_debug
|
|
105
101
|
|
|
106
102
|
rebind = getattr(renderer, "_rebind_presentation", None)
|
|
@@ -1103,12 +1099,10 @@ class EventProcessor:
|
|
|
1103
1099
|
Dict with enabled, mode, and synthesis_streaming fields.
|
|
1104
1100
|
"""
|
|
1105
1101
|
# Use defaults - streaming is enabled by default per RFC-614
|
|
1106
|
-
#
|
|
1102
|
+
# Always use streaming mode
|
|
1107
1103
|
config = {
|
|
1108
1104
|
"enabled": True,
|
|
1109
|
-
"mode":
|
|
1110
|
-
if self._final_output_mode in {"streaming", "batch"}
|
|
1111
|
-
else "streaming",
|
|
1105
|
+
"mode": "streaming",
|
|
1112
1106
|
"synthesis_streaming": True,
|
|
1113
1107
|
}
|
|
1114
1108
|
|
|
@@ -392,7 +392,7 @@ def format_tool_call_args(tool_name: str, tool_call: dict[str, Any]) -> str:
|
|
|
392
392
|
def _is_path_arg_name(key: str) -> bool:
|
|
393
393
|
return _PATH_ARG_PATTERN.match(key) is not None
|
|
394
394
|
|
|
395
|
-
max_value_length =
|
|
395
|
+
max_value_length = 50 # Max length for displayed values
|
|
396
396
|
|
|
397
397
|
def _display_path_value(raw: str) -> str:
|
|
398
398
|
out = convert_and_abbreviate_path(raw)
|
{soothe_cli-0.4.7 → soothe_cli-0.4.8}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py
RENAMED
|
@@ -13,8 +13,8 @@ from soothe_cli.shared.tools.tool_output_formatter import ToolBrief
|
|
|
13
13
|
class FileOpsFormatter(BaseFormatter):
|
|
14
14
|
"""Formatter for file operation tools.
|
|
15
15
|
|
|
16
|
-
Handles: read_file, write_file, delete_file, list_files, search_files,
|
|
17
|
-
grep, ls, file_info
|
|
16
|
+
Handles: read_file, write_file, edit_file, delete_file, list_files, search_files,
|
|
17
|
+
glob, grep, ls, file_info, edit_file_lines, insert_lines, delete_lines, apply_diff
|
|
18
18
|
|
|
19
19
|
Provides semantic summaries with size, line count, and item count metrics.
|
|
20
20
|
"""
|
|
@@ -46,6 +46,26 @@ class FileOpsFormatter(BaseFormatter):
|
|
|
46
46
|
return self._format_read_file(result)
|
|
47
47
|
if normalized == "write_file":
|
|
48
48
|
return self._format_write_file(result)
|
|
49
|
+
if normalized == "edit_file":
|
|
50
|
+
return self._format_file_mutation(
|
|
51
|
+
result, success_summary="Edited file", failure_summary="Edit failed"
|
|
52
|
+
)
|
|
53
|
+
if normalized == "edit_file_lines":
|
|
54
|
+
return self._format_file_mutation(
|
|
55
|
+
result, success_summary="Updated file", failure_summary="Line edit failed"
|
|
56
|
+
)
|
|
57
|
+
if normalized == "insert_lines":
|
|
58
|
+
return self._format_file_mutation(
|
|
59
|
+
result, success_summary="Inserted lines", failure_summary="Insert failed"
|
|
60
|
+
)
|
|
61
|
+
if normalized == "delete_lines":
|
|
62
|
+
return self._format_file_mutation(
|
|
63
|
+
result, success_summary="Deleted lines", failure_summary="Delete lines failed"
|
|
64
|
+
)
|
|
65
|
+
if normalized == "apply_diff":
|
|
66
|
+
return self._format_file_mutation(
|
|
67
|
+
result, success_summary="Applied patch", failure_summary="Patch failed"
|
|
68
|
+
)
|
|
49
69
|
if normalized == "delete_file":
|
|
50
70
|
return self._format_delete_file(result)
|
|
51
71
|
if normalized in ("list_files", "ls"):
|
|
@@ -143,6 +163,28 @@ class FileOpsFormatter(BaseFormatter):
|
|
|
143
163
|
metrics={"size_bytes": size_bytes, "lines": lines},
|
|
144
164
|
)
|
|
145
165
|
|
|
166
|
+
def _format_file_mutation(
|
|
167
|
+
self,
|
|
168
|
+
result: str,
|
|
169
|
+
*,
|
|
170
|
+
success_summary: str,
|
|
171
|
+
failure_summary: str,
|
|
172
|
+
) -> ToolBrief:
|
|
173
|
+
"""Format success/error for tools that mutate file content (write, edit, patch, …)."""
|
|
174
|
+
if text_looks_like_error(result):
|
|
175
|
+
return ToolBrief(
|
|
176
|
+
icon="✗",
|
|
177
|
+
summary=failure_summary,
|
|
178
|
+
detail=self._truncate_text(result, 80),
|
|
179
|
+
metrics={"error": True},
|
|
180
|
+
)
|
|
181
|
+
return ToolBrief(
|
|
182
|
+
icon="✓",
|
|
183
|
+
summary=success_summary,
|
|
184
|
+
detail=None,
|
|
185
|
+
metrics={},
|
|
186
|
+
)
|
|
187
|
+
|
|
146
188
|
def _format_write_file(self, result: str) -> ToolBrief:
|
|
147
189
|
"""Format write_file result.
|
|
148
190
|
|
|
@@ -157,25 +199,10 @@ class FileOpsFormatter(BaseFormatter):
|
|
|
157
199
|
Example:
|
|
158
200
|
>>> brief = formatter._format_write_file("Successfully wrote to file")
|
|
159
201
|
>>> brief.summary
|
|
160
|
-
'Wrote
|
|
202
|
+
'Wrote file'
|
|
161
203
|
"""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return ToolBrief(
|
|
165
|
-
icon="✗",
|
|
166
|
-
summary="Write failed",
|
|
167
|
-
detail=self._truncate_text(result, 80),
|
|
168
|
-
metrics={"error": True},
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
# Try to extract size from result (if available)
|
|
172
|
-
# Common patterns: "Wrote X bytes", "Successfully wrote X"
|
|
173
|
-
# For now, show simple success
|
|
174
|
-
return ToolBrief(
|
|
175
|
-
icon="✓",
|
|
176
|
-
summary="Wrote file",
|
|
177
|
-
detail=None,
|
|
178
|
-
metrics={},
|
|
204
|
+
return self._format_file_mutation(
|
|
205
|
+
result, success_summary="Wrote file", failure_summary="Write failed"
|
|
179
206
|
)
|
|
180
207
|
|
|
181
208
|
def _format_delete_file(self, result: str) -> ToolBrief:
|
|
@@ -9,10 +9,13 @@ config, no widget imports) so that `app.py` can import `SessionStats` and
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
|
-
from typing import Literal
|
|
13
12
|
|
|
14
|
-
SpinnerStatus =
|
|
15
|
-
"""
|
|
13
|
+
SpinnerStatus = str | None
|
|
14
|
+
"""Spinner line label, or `None` to hide.
|
|
15
|
+
|
|
16
|
+
Common values include ``Thinking``, ``Offloading``, ``Writing`` (assistant streaming),
|
|
17
|
+
``Tools`` (tool execution), and ``Synthesizing`` (goal-completion stream).
|
|
18
|
+
"""
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
@dataclass
|
|
@@ -21,7 +21,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Literal
|
|
|
21
21
|
|
|
22
22
|
from textual.app import App, ScreenStackError
|
|
23
23
|
from textual.binding import Binding, BindingType
|
|
24
|
-
from textual.containers import Container, VerticalScroll
|
|
24
|
+
from textual.containers import Container, Vertical, VerticalScroll
|
|
25
25
|
from textual.content import Content
|
|
26
26
|
from textual.css.query import NoMatches
|
|
27
27
|
from textual.message import Message
|
|
@@ -780,15 +780,16 @@ class SootheApp(App):
|
|
|
780
780
|
# Main chat area with scrollable messages
|
|
781
781
|
# VerticalScroll tracks user scroll intent for better auto-scroll behavior
|
|
782
782
|
with VerticalScroll(id="chat"):
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
783
|
+
with Vertical(id="chat-body"):
|
|
784
|
+
yield WelcomeBanner(
|
|
785
|
+
thread_id=self._lc_loop_id,
|
|
786
|
+
mcp_tool_count=self._mcp_tool_count,
|
|
787
|
+
connecting=self._connecting,
|
|
788
|
+
resuming=self._resume_thread_intent is not None,
|
|
789
|
+
local_server=self._server_kwargs is not None,
|
|
790
|
+
id="welcome-banner",
|
|
791
|
+
)
|
|
792
|
+
yield Container(id="messages")
|
|
792
793
|
with Container(id="bottom-app-container"):
|
|
793
794
|
yield Container(id="thinking-status")
|
|
794
795
|
yield ChatInput(
|
|
@@ -1965,9 +1966,12 @@ class SootheApp(App):
|
|
|
1965
1966
|
|
|
1966
1967
|
if self._loading_widget is None:
|
|
1967
1968
|
# Create new
|
|
1968
|
-
self.
|
|
1969
|
+
turn_mono = self._inflight_turn_start if self._agent_running else None
|
|
1970
|
+
self._loading_widget = LoadingWidget(status, turn_start_mono=turn_mono)
|
|
1969
1971
|
await thinking_status.mount(self._loading_widget)
|
|
1970
1972
|
else:
|
|
1973
|
+
if self._agent_running:
|
|
1974
|
+
self._loading_widget.set_turn_start_mono(self._inflight_turn_start)
|
|
1971
1975
|
# Update existing
|
|
1972
1976
|
self._loading_widget.set_status(status)
|
|
1973
1977
|
# NOTE: Don't call anchor() here - it would re-anchor and drag user back
|
|
@@ -16,14 +16,24 @@ Screen {
|
|
|
16
16
|
/* Chat area - main scrollable messages area */
|
|
17
17
|
#chat {
|
|
18
18
|
height: 1fr;
|
|
19
|
-
padding:
|
|
19
|
+
padding: 0 2;
|
|
20
20
|
background: $background;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/* Fills at least the scroll viewport; bottom-aligns welcome + messages when the
|
|
24
|
+
thread is short so dead space sits above the banner instead of above the input. */
|
|
25
|
+
#chat-body {
|
|
26
|
+
width: 1fr;
|
|
27
|
+
min-height: 100%;
|
|
28
|
+
height: auto;
|
|
29
|
+
layout: vertical;
|
|
30
|
+
align-vertical: bottom;
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
/* Welcome banner */
|
|
24
34
|
#welcome-banner {
|
|
25
35
|
height: auto;
|
|
26
|
-
margin-bottom:
|
|
36
|
+
margin-bottom: 0;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/* Messages area — uses undocumented "stream" layout (Textual ≥5.2.0) for
|
|
@@ -36,14 +46,14 @@ Screen {
|
|
|
36
46
|
/* Bottom app container - holds ChatInput (now inside scroll) */
|
|
37
47
|
#bottom-app-container {
|
|
38
48
|
height: auto;
|
|
39
|
-
margin-top:
|
|
49
|
+
margin-top: 0;
|
|
40
50
|
padding: 0 1;
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
/* Sticky thinking row directly above input box */
|
|
44
54
|
#thinking-status {
|
|
45
55
|
height: auto;
|
|
46
|
-
min-height:
|
|
56
|
+
min-height: 0;
|
|
47
57
|
margin-bottom: 0;
|
|
48
58
|
}
|
|
49
59
|
|
|
@@ -175,7 +185,7 @@ QueuedUserMessage.-ascii {
|
|
|
175
185
|
}
|
|
176
186
|
|
|
177
187
|
ToolCallMessage.-ascii {
|
|
178
|
-
border-left: ascii $
|
|
188
|
+
border-left: ascii $primary;
|
|
179
189
|
}
|
|
180
190
|
|
|
181
191
|
ToolCallMessage.-ascii:hover {
|