soothe-cli 0.4.1__tar.gz → 0.4.2__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.1 → soothe_cli-0.4.2}/.gitignore +1 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/PKG-INFO +1 -1
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/commands/run_cmd.py +10 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/execution/daemon.py +7 -1
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/execution/headless.py +1 -1
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/main.py +10 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/renderer.py +56 -84
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/stream/formatter.py +3 -46
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/stream/pipeline.py +1 -21
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/config/cli_config.py +25 -4
- soothe_cli-0.4.2/src/soothe_cli/shared/async_renderer_protocol.py +184 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/config_loader.py +1 -1
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/display_policy.py +0 -35
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/event_processor.py +161 -30
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/presentation_engine.py +3 -3
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/processor_state.py +19 -4
- soothe_cli-0.4.2/src/soothe_cli/shared/renderer_base.py +72 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/renderer_protocol.py +25 -0
- soothe_cli-0.4.2/src/soothe_cli/shared/stream_accumulator.py +141 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/fallback.py +1 -1
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/file_ops.py +4 -1
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/app.py +12 -2
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/daemon_session.py +24 -1
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/textual_adapter.py +132 -74
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/message_store.py +0 -5
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/messages.py +0 -3
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/welcome.py +1 -2
- soothe_cli-0.4.1/src/soothe_cli/shared/suppression_state.py +0 -189
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/README.md +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/pyproject.toml +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/commands/subagent_names.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/commands/thread_cmd.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/stream/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/stream/context.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/stream/display_line.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/cli/utils.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/loop_commands.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/plan/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/plan/rich_tree.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/command_router.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/essential_events.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/message_processing.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/rendering.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/slash_commands.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/subagent_routing.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_call_resolution.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_card_payload.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/base.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/execution.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/goal_formatter.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/media.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/structured.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/subagent.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_formatters/web.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_message_format.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tool_output_formatter.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/shared/tui_trace_log.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/_ask_user_types.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/_env_vars.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/_session_stats.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/app.tcss +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/command_registry.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/config.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/file_ops.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/formatting.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/message_display_filter.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/output.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/preview_limits.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/tool_display.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/approval.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/status.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/thread_selector.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
- {soothe_cli-0.4.1 → soothe_cli-0.4.2}/src/soothe_cli/tui/widgets/tools.py +0 -0
|
@@ -23,6 +23,8 @@ def run_impl(
|
|
|
23
23
|
autonomous: bool, # noqa: FBT001
|
|
24
24
|
max_iterations: int | None,
|
|
25
25
|
output_format: str,
|
|
26
|
+
streaming_enabled: bool | None = None,
|
|
27
|
+
streaming_mode: str | None = None,
|
|
26
28
|
) -> None:
|
|
27
29
|
"""Core implementation for running Soothe agent.
|
|
28
30
|
|
|
@@ -35,6 +37,8 @@ def run_impl(
|
|
|
35
37
|
autonomous: Enable autonomous iteration mode
|
|
36
38
|
max_iterations: Max iterations for autonomous mode
|
|
37
39
|
output_format: Output format (text or jsonl)
|
|
40
|
+
streaming_enabled: Override daemon streaming enabled setting (RFC-614)
|
|
41
|
+
streaming_mode: Override daemon streaming mode ('streaming' or 'batch')
|
|
38
42
|
"""
|
|
39
43
|
startup_start = time.perf_counter()
|
|
40
44
|
|
|
@@ -50,6 +54,12 @@ def run_impl(
|
|
|
50
54
|
if checkpointer == "postgresql":
|
|
51
55
|
logger.info("PostgreSQL checkpointer configured; ensure server is running.")
|
|
52
56
|
|
|
57
|
+
# Apply CLI streaming overrides (RFC-614)
|
|
58
|
+
if streaming_enabled is not None:
|
|
59
|
+
cfg.output_streaming_enabled = streaming_enabled
|
|
60
|
+
if streaming_mode is not None:
|
|
61
|
+
cfg.output_streaming_mode = streaming_mode
|
|
62
|
+
|
|
53
63
|
startup_elapsed_ms = (time.perf_counter() - startup_start) * 1000
|
|
54
64
|
logger.info("[Startup] ✓ Ready (%.1fms)", startup_elapsed_ms)
|
|
55
65
|
|
|
@@ -48,6 +48,7 @@ async def run_headless_via_daemon(
|
|
|
48
48
|
ws_url = websocket_url_from_config(cfg)
|
|
49
49
|
client = WebSocketClient(url=ws_url)
|
|
50
50
|
verbosity = cfg.logging.verbosity
|
|
51
|
+
final_output_mode = getattr(cfg, "final_output_mode", "streaming")
|
|
51
52
|
|
|
52
53
|
try:
|
|
53
54
|
await connect_websocket_with_retries(client)
|
|
@@ -84,7 +85,12 @@ async def run_headless_via_daemon(
|
|
|
84
85
|
# for pipeline + message gating (RFC-502).
|
|
85
86
|
presentation = PresentationEngine()
|
|
86
87
|
renderer = CliRenderer(verbosity=verbosity, presentation_engine=presentation)
|
|
87
|
-
processor = EventProcessor(
|
|
88
|
+
processor = EventProcessor(
|
|
89
|
+
renderer,
|
|
90
|
+
verbosity=verbosity,
|
|
91
|
+
final_output_mode=final_output_mode,
|
|
92
|
+
presentation_engine=presentation,
|
|
93
|
+
)
|
|
88
94
|
|
|
89
95
|
has_error = False
|
|
90
96
|
query_started = False # Track if we've seen the query start running
|
|
@@ -29,7 +29,7 @@ def run_headless(
|
|
|
29
29
|
) -> None:
|
|
30
30
|
"""Run a single prompt with streaming output and progress events.
|
|
31
31
|
|
|
32
|
-
Connects to running daemon via WebSocket if available to avoid
|
|
32
|
+
Connects to running daemon via WebSocket if available to avoid database lock conflicts.
|
|
33
33
|
Auto-starts daemon if not running (RFC-0013 daemon lifecycle).
|
|
34
34
|
|
|
35
35
|
Note (RFC-0013): Daemon persists after request completion. Use 'soothed stop'
|
|
@@ -74,6 +74,14 @@ def main(
|
|
|
74
74
|
str,
|
|
75
75
|
typer.Option("--format", "-f", help="Output format for headless mode: text or jsonl."),
|
|
76
76
|
] = "text",
|
|
77
|
+
streaming: Annotated[
|
|
78
|
+
bool | None,
|
|
79
|
+
typer.Option("--streaming/--no-streaming", help="Enable/disable output streaming."),
|
|
80
|
+
] = None,
|
|
81
|
+
streaming_mode: Annotated[
|
|
82
|
+
str | None,
|
|
83
|
+
typer.Option("--streaming-mode", help="Streaming mode: 'streaming' or 'batch'"),
|
|
84
|
+
] = None,
|
|
77
85
|
show_help: Annotated[ # noqa: FBT002
|
|
78
86
|
bool,
|
|
79
87
|
typer.Option("--help", "-h", is_flag=True, help="Show this message and exit."),
|
|
@@ -117,6 +125,8 @@ def main(
|
|
|
117
125
|
autonomous=False,
|
|
118
126
|
max_iterations=None,
|
|
119
127
|
output_format=output_format,
|
|
128
|
+
streaming_enabled=streaming,
|
|
129
|
+
streaming_mode=streaming_mode,
|
|
120
130
|
)
|
|
121
131
|
|
|
122
132
|
|
|
@@ -20,7 +20,7 @@ from soothe_cli.cli.utils import make_tool_block
|
|
|
20
20
|
from soothe_cli.shared.display_policy import VerbosityLevel, normalize_verbosity
|
|
21
21
|
from soothe_cli.shared.message_processing import format_tool_call_args
|
|
22
22
|
from soothe_cli.shared.presentation_engine import PresentationEngine
|
|
23
|
-
from soothe_cli.shared.
|
|
23
|
+
from soothe_cli.shared.renderer_base import RendererBase
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from soothe_sdk.client.schemas import Plan
|
|
@@ -36,8 +36,8 @@ class CliRendererState:
|
|
|
36
36
|
# Track if stderr was just written (to add spacing before next stdout)
|
|
37
37
|
stderr_just_written: bool = False
|
|
38
38
|
|
|
39
|
-
#
|
|
40
|
-
|
|
39
|
+
# Per-turn assistant output accumulation for diagnostics/tests.
|
|
40
|
+
full_response: list[str] = field(default_factory=list)
|
|
41
41
|
|
|
42
42
|
# Track current plan for status display
|
|
43
43
|
current_plan: Plan | None = None
|
|
@@ -45,11 +45,14 @@ class CliRendererState:
|
|
|
45
45
|
# Track tool call start times for duration display (RFC-0020)
|
|
46
46
|
tool_call_start_times: dict[str, float] = field(default_factory=dict)
|
|
47
47
|
|
|
48
|
+
# Buffer tool-call line text until result arrives for single-line rendering.
|
|
49
|
+
pending_tool_call_lines: dict[str, str] = field(default_factory=dict)
|
|
50
|
+
|
|
48
51
|
# After LLM text on stdout, next stderr icon block gets one leading blank line
|
|
49
52
|
stderr_blank_before_next_icon_block: bool = False
|
|
50
53
|
|
|
51
54
|
|
|
52
|
-
class CliRenderer:
|
|
55
|
+
class CliRenderer(RendererBase):
|
|
53
56
|
"""CLI renderer for headless stdout/stderr output.
|
|
54
57
|
|
|
55
58
|
Implements RendererProtocol callbacks for CLI mode:
|
|
@@ -58,6 +61,8 @@ class CliRenderer:
|
|
|
58
61
|
- Progress events -> stderr via StreamDisplayPipeline
|
|
59
62
|
- Errors -> stderr
|
|
60
63
|
|
|
64
|
+
Inherits from RendererBase for unified text repair logic.
|
|
65
|
+
|
|
61
66
|
Spacing: Soothe-originated stderr lines (icons from the pipeline, tools, results,
|
|
62
67
|
errors) call `_stderr_begin_icon_block()`, which inserts one blank stderr line only
|
|
63
68
|
after LLM text was written to stdout, so icon blocks separate from answers without
|
|
@@ -80,6 +85,7 @@ class CliRenderer:
|
|
|
80
85
|
verbosity: Progress visibility level.
|
|
81
86
|
presentation_engine: Shared presentation engine (optional).
|
|
82
87
|
"""
|
|
88
|
+
super().__init__()
|
|
83
89
|
self._verbosity = normalize_verbosity(verbosity)
|
|
84
90
|
self._state = CliRendererState()
|
|
85
91
|
self._presentation = presentation_engine or PresentationEngine()
|
|
@@ -99,12 +105,7 @@ class CliRenderer:
|
|
|
99
105
|
@property
|
|
100
106
|
def full_response(self) -> list[str]:
|
|
101
107
|
"""Get accumulated response text."""
|
|
102
|
-
return self._state.
|
|
103
|
-
|
|
104
|
-
@property
|
|
105
|
-
def multi_step_active(self) -> bool:
|
|
106
|
-
"""Whether multi-step plan is active."""
|
|
107
|
-
return self._state.suppression.multi_step_active
|
|
108
|
+
return self._state.full_response
|
|
108
109
|
|
|
109
110
|
@property
|
|
110
111
|
def presentation_engine(self) -> PresentationEngine:
|
|
@@ -138,38 +139,17 @@ class CliRenderer:
|
|
|
138
139
|
sys.stderr.flush()
|
|
139
140
|
self._state.stderr_just_written = True
|
|
140
141
|
|
|
141
|
-
def _write_stdout_final_report(self, text: str) -> None:
|
|
142
|
-
"""Write aggregated final answer to stdout (multi-step headless mode only)."""
|
|
143
|
-
stripped = text.strip()
|
|
144
|
-
if not stripped:
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
self._state.suppression.full_response.append(stripped)
|
|
148
|
-
|
|
149
|
-
# Add newline before final report if stderr was just written (goal completion)
|
|
150
|
-
if self._state.stderr_just_written:
|
|
151
|
-
sys.stdout.write("\n")
|
|
152
|
-
self._state.stderr_just_written = False
|
|
153
|
-
|
|
154
|
-
sys.stdout.write(stripped)
|
|
155
|
-
if not stripped.endswith("\n"):
|
|
156
|
-
sys.stdout.write("\n")
|
|
157
|
-
sys.stdout.flush()
|
|
158
|
-
self._state.needs_stdout_newline = True
|
|
159
|
-
self._state.stderr_blank_before_next_icon_block = True
|
|
160
|
-
self._presentation.mark_final_answer_locked()
|
|
161
|
-
|
|
162
142
|
def on_assistant_text(
|
|
163
143
|
self,
|
|
164
144
|
text: str,
|
|
165
145
|
*,
|
|
166
146
|
is_main: bool,
|
|
167
|
-
is_streaming: bool,
|
|
147
|
+
is_streaming: bool,
|
|
168
148
|
) -> None:
|
|
169
149
|
"""Write assistant text to stdout.
|
|
170
150
|
|
|
171
|
-
|
|
172
|
-
|
|
151
|
+
Write assistant text directly. Daemon-side output contract decides
|
|
152
|
+
which assistant text reaches clients.
|
|
173
153
|
|
|
174
154
|
Args:
|
|
175
155
|
text: Text content to display.
|
|
@@ -179,25 +159,41 @@ class CliRenderer:
|
|
|
179
159
|
if not is_main:
|
|
180
160
|
return # Subagent text not shown in CLI headless mode
|
|
181
161
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# Accumulate for final report instead
|
|
185
|
-
self._state.suppression.accumulate_text(text)
|
|
186
|
-
return
|
|
187
|
-
|
|
188
|
-
# Emit only on final iteration (after flags cleared)
|
|
189
|
-
self._state.suppression.full_response.append(text)
|
|
162
|
+
payload = text if is_streaming else self.repair_concatenated_output(text)
|
|
163
|
+
self._state.full_response.append(payload)
|
|
190
164
|
|
|
191
165
|
if self._state.stderr_just_written:
|
|
192
166
|
self._state.stderr_just_written = False
|
|
193
167
|
|
|
194
168
|
# LLM stream: do not inject extra blank lines (spacing before icon stderr
|
|
195
169
|
# is handled in _stderr_begin_icon_block when progress resumes).
|
|
196
|
-
sys.stdout.write(
|
|
170
|
+
sys.stdout.write(payload)
|
|
197
171
|
sys.stdout.flush()
|
|
198
172
|
self._state.needs_stdout_newline = True
|
|
199
173
|
self._state.stderr_blank_before_next_icon_block = True
|
|
200
174
|
|
|
175
|
+
def on_streaming_output(
|
|
176
|
+
self,
|
|
177
|
+
event_type: str,
|
|
178
|
+
text: str,
|
|
179
|
+
*,
|
|
180
|
+
is_chunk: bool,
|
|
181
|
+
namespace: tuple[str, ...],
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Handle streaming output from unified framework (RFC-614).
|
|
184
|
+
|
|
185
|
+
Default implementation: delegate to on_assistant_text.
|
|
186
|
+
CLI renderer treats all streaming output as assistant text.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
event_type: Event type string.
|
|
190
|
+
text: Text content (may be chunk or final).
|
|
191
|
+
is_chunk: True if partial chunk, False if final.
|
|
192
|
+
namespace: Namespace tuple for stream context (ignored in CLI headless mode).
|
|
193
|
+
"""
|
|
194
|
+
# Delegate to on_assistant_text for unified display
|
|
195
|
+
self.on_assistant_text(text, is_main=True, is_streaming=is_chunk)
|
|
196
|
+
|
|
201
197
|
def on_tool_call(
|
|
202
198
|
self,
|
|
203
199
|
name: str,
|
|
@@ -217,10 +213,6 @@ class CliRenderer:
|
|
|
217
213
|
if not self._presentation.tier_visible(VerbosityTier.NORMAL, self._verbosity):
|
|
218
214
|
return
|
|
219
215
|
|
|
220
|
-
# Multi-step / agentic suppression applies to assistant stdout only (IG-143).
|
|
221
|
-
# Tool calls and results still stream to stderr at normal+ verbosity so headless
|
|
222
|
-
# runs show the same tool activity as the TUI.
|
|
223
|
-
|
|
224
216
|
self._stderr_begin_icon_block()
|
|
225
217
|
|
|
226
218
|
display_name = get_tool_display_name(name)
|
|
@@ -239,10 +231,12 @@ class CliRenderer:
|
|
|
239
231
|
# Track start time for duration display (RFC-0020)
|
|
240
232
|
if tool_call_id:
|
|
241
233
|
self._state.tool_call_start_times[tool_call_id] = time.time()
|
|
234
|
+
self._state.pending_tool_call_lines[tool_call_id] = tool_block
|
|
235
|
+
return
|
|
242
236
|
|
|
237
|
+
# No stable ID means we cannot join with result later - keep old behavior.
|
|
243
238
|
sys.stderr.write(f"{tool_block}\n")
|
|
244
239
|
sys.stderr.flush()
|
|
245
|
-
# Mark that stderr was just written
|
|
246
240
|
self._state.stderr_just_written = True
|
|
247
241
|
|
|
248
242
|
def on_tool_result(
|
|
@@ -266,8 +260,6 @@ class CliRenderer:
|
|
|
266
260
|
if not self._presentation.tier_visible(VerbosityTier.NORMAL, self._verbosity):
|
|
267
261
|
return
|
|
268
262
|
|
|
269
|
-
# See on_tool_call: do not suppress stderr tool results during multi-step runs.
|
|
270
|
-
|
|
271
263
|
self._stderr_begin_icon_block()
|
|
272
264
|
|
|
273
265
|
# Calculate duration (RFC-0020)
|
|
@@ -287,9 +279,15 @@ class CliRenderer:
|
|
|
287
279
|
if duration_ms > 0:
|
|
288
280
|
result_line += f" ({duration_ms}ms)"
|
|
289
281
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
282
|
+
combined_call_line: str | None = None
|
|
283
|
+
if tool_call_id:
|
|
284
|
+
combined_call_line = self._state.pending_tool_call_lines.pop(tool_call_id, None)
|
|
285
|
+
|
|
286
|
+
if combined_call_line:
|
|
287
|
+
result_line = f"{combined_call_line} -> {result_line}"
|
|
288
|
+
elif self._is_inside_step_context():
|
|
289
|
+
# IG-257: Add indentation when inside step context
|
|
290
|
+
# Unicode U+2514 "└─" (Box Drawings Light Up and Right) for tree branch
|
|
293
291
|
result_line = f" └─ {result_line}"
|
|
294
292
|
|
|
295
293
|
sys.stderr.write(result_line + "\n")
|
|
@@ -323,7 +321,7 @@ class CliRenderer:
|
|
|
323
321
|
event_type: str,
|
|
324
322
|
data: dict[str, Any],
|
|
325
323
|
*,
|
|
326
|
-
namespace: tuple[str, ...],
|
|
324
|
+
namespace: tuple[str, ...],
|
|
327
325
|
) -> None:
|
|
328
326
|
"""Write progress event to stderr using StreamDisplayPipeline.
|
|
329
327
|
|
|
@@ -332,22 +330,11 @@ class CliRenderer:
|
|
|
332
330
|
data: Event payload.
|
|
333
331
|
namespace: Subagent namespace.
|
|
334
332
|
"""
|
|
335
|
-
# Track suppression state from event (IG-143)
|
|
336
|
-
final_stdout = self._state.suppression.track_from_event(event_type, data)
|
|
337
|
-
|
|
338
|
-
payload = dict(data)
|
|
339
|
-
payload.pop("final_stdout_message", None)
|
|
340
|
-
|
|
341
333
|
# Build event dict for pipeline
|
|
342
|
-
event = {"type": event_type, **
|
|
334
|
+
event = {"type": event_type, **data}
|
|
343
335
|
lines = self._pipeline.process(event)
|
|
344
336
|
self.write_lines(lines)
|
|
345
337
|
|
|
346
|
-
# Emit final report on loop completion (IG-143)
|
|
347
|
-
if self._state.suppression.should_emit_final_report(event_type, final_stdout):
|
|
348
|
-
response = self._state.suppression.get_final_response(final_stdout)
|
|
349
|
-
self._write_stdout_final_report(response)
|
|
350
|
-
|
|
351
338
|
def on_plan_created(self, plan: Plan) -> None:
|
|
352
339
|
"""Write plan creation to stderr.
|
|
353
340
|
|
|
@@ -355,7 +342,6 @@ class CliRenderer:
|
|
|
355
342
|
plan: Created plan object.
|
|
356
343
|
"""
|
|
357
344
|
self._state.current_plan = plan
|
|
358
|
-
self._state.suppression.track_from_plan(len(plan.steps))
|
|
359
345
|
|
|
360
346
|
# Use pipeline for consistent formatting
|
|
361
347
|
event = {
|
|
@@ -420,24 +406,10 @@ class CliRenderer:
|
|
|
420
406
|
self.write_lines(lines)
|
|
421
407
|
|
|
422
408
|
def on_turn_end(self) -> None:
|
|
423
|
-
"""Finalize
|
|
424
|
-
|
|
425
|
-
If multi_step_active was suppressing output, flush the accumulated
|
|
426
|
-
response to stdout now that the plan is complete.
|
|
427
|
-
"""
|
|
428
|
-
# Capture state BEFORE resetting
|
|
429
|
-
was_multi_step = self._state.suppression.multi_step_active
|
|
430
|
-
accumulated_response = self._state.suppression.full_response
|
|
431
|
-
|
|
432
|
-
# Reset state for next turn FIRST (before output logic)
|
|
409
|
+
"""Finalize turn-local renderer state."""
|
|
433
410
|
self._state.needs_stdout_newline = False
|
|
434
|
-
self._state.
|
|
435
|
-
|
|
436
|
-
# Multi-step mode intentionally suppresses step body output in headless CLI.
|
|
437
|
-
# For single-step mode, keep existing newline flush behavior.
|
|
438
|
-
if (not was_multi_step) and accumulated_response:
|
|
439
|
-
sys.stdout.write("\n")
|
|
440
|
-
sys.stdout.flush()
|
|
411
|
+
self._state.full_response.clear()
|
|
412
|
+
self._state.pending_tool_call_lines.clear()
|
|
441
413
|
|
|
442
414
|
def _stderr_begin_icon_block(self) -> None:
|
|
443
415
|
"""Prepare stderr for Soothe icon lines (progress, tools, tool results).
|
|
@@ -96,7 +96,7 @@ def format_goal_header(
|
|
|
96
96
|
DisplayLine for goal header.
|
|
97
97
|
"""
|
|
98
98
|
# Add inline symbol for goal marker
|
|
99
|
-
content = f"{goal}"
|
|
99
|
+
content = f"📍 {goal}"
|
|
100
100
|
return DisplayLine(
|
|
101
101
|
level=1,
|
|
102
102
|
content=content,
|
|
@@ -242,7 +242,6 @@ def format_subagent_milestone(
|
|
|
242
242
|
def format_subagent_done(
|
|
243
243
|
summary: str,
|
|
244
244
|
duration_s: float,
|
|
245
|
-
result_preview: str = "",
|
|
246
245
|
*,
|
|
247
246
|
namespace: tuple[str, ...] = (),
|
|
248
247
|
verbosity_tier: VerbosityTier = VerbosityTier.NORMAL,
|
|
@@ -250,12 +249,11 @@ def format_subagent_done(
|
|
|
250
249
|
"""Format a subagent completion line with metrics.
|
|
251
250
|
|
|
252
251
|
IG-256: Restored verbose format with triple success markers and separate result display.
|
|
253
|
-
|
|
252
|
+
Results show via separate tool events.
|
|
254
253
|
|
|
255
254
|
Args:
|
|
256
255
|
summary: Completion summary with subagent-specific metrics (e.g., "success", "$1.23").
|
|
257
256
|
duration_s: Duration in seconds.
|
|
258
|
-
result_preview: Ignored (kept for backward compatibility).
|
|
259
257
|
namespace: Event namespace.
|
|
260
258
|
verbosity_tier: Current verbosity tier.
|
|
261
259
|
|
|
@@ -264,9 +262,7 @@ def format_subagent_done(
|
|
|
264
262
|
"""
|
|
265
263
|
duration_ms = int(duration_s * 1000)
|
|
266
264
|
|
|
267
|
-
# IG-256: Verbose format restored - triple success markers
|
|
268
|
-
# Format: "✓ ✅ ✓ {summary}"
|
|
269
|
-
# result_preview is ignored - let result show via separate tool execution events
|
|
265
|
+
# IG-256: Verbose format restored - triple success markers
|
|
270
266
|
content = f"✓ ✅ ✓ {summary}"
|
|
271
267
|
|
|
272
268
|
return DisplayLine(
|
|
@@ -307,58 +303,21 @@ def format_plan_phase_reasoning(
|
|
|
307
303
|
)
|
|
308
304
|
|
|
309
305
|
|
|
310
|
-
def format_reasoning(
|
|
311
|
-
reasoning: str,
|
|
312
|
-
*,
|
|
313
|
-
namespace: tuple[str, ...] = (),
|
|
314
|
-
verbosity_tier: VerbosityTier = VerbosityTier.NORMAL,
|
|
315
|
-
) -> DisplayLine:
|
|
316
|
-
"""Format a reasoning line for LLM decision internal analysis.
|
|
317
|
-
|
|
318
|
-
IG-XXX: Shows technical reasoning with "Reasoning:" prefix for clarity.
|
|
319
|
-
Uses solid bullet ● (matching goal) to indicate reasoning is active phase.
|
|
320
|
-
|
|
321
|
-
IG-262: Uses level=2 (flat layout) for consistency with judgement lines.
|
|
322
|
-
Reasoning is a sibling to judgement, not a child.
|
|
323
|
-
|
|
324
|
-
Args:
|
|
325
|
-
reasoning: Internal technical analysis text.
|
|
326
|
-
namespace: Event namespace.
|
|
327
|
-
verbosity_tier: Current verbosity tier.
|
|
328
|
-
|
|
329
|
-
Returns:
|
|
330
|
-
DisplayLine for reasoning.
|
|
331
|
-
"""
|
|
332
|
-
# Polish: Add "Reasoning:" prefix to make internal analysis visible
|
|
333
|
-
content = f"💭 {reasoning}"
|
|
334
|
-
|
|
335
|
-
return DisplayLine(
|
|
336
|
-
level=2, # IG-262: Use level 2 for flat layout (sibling to judgement, not child)
|
|
337
|
-
content=content,
|
|
338
|
-
icon="●", # Solid bullet matching goal icon (polish)
|
|
339
|
-
indent=indent_for_level(2),
|
|
340
|
-
source_prefix=_derive_source_prefix(namespace, verbosity_tier),
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
|
|
344
306
|
def format_judgement(
|
|
345
307
|
judgement: str,
|
|
346
308
|
action: str,
|
|
347
309
|
*,
|
|
348
|
-
plan_action: str | None = None,
|
|
349
310
|
namespace: tuple[str, ...] = (),
|
|
350
311
|
verbosity_tier: VerbosityTier = VerbosityTier.NORMAL,
|
|
351
312
|
) -> DisplayLine:
|
|
352
313
|
"""Format a judgement line for LLM decision reasoning.
|
|
353
314
|
|
|
354
315
|
IG-089: Shows meaningful judgement info without raw intermediate data.
|
|
355
|
-
IG-XXX: Prominent reasoning display with "Reason:" prefix for clarity.
|
|
356
316
|
IG-265: Removed [new]/[keep] badge from CLI display (kept in event data for logs).
|
|
357
317
|
|
|
358
318
|
Args:
|
|
359
319
|
judgement: Human-readable summary of the decision.
|
|
360
320
|
action: Action taken ("continue" or "complete").
|
|
361
|
-
plan_action: Ignored (kept for backward compatibility, appears in logs only).
|
|
362
321
|
namespace: Event namespace.
|
|
363
322
|
verbosity_tier: Current verbosity tier.
|
|
364
323
|
|
|
@@ -367,7 +326,6 @@ def format_judgement(
|
|
|
367
326
|
"""
|
|
368
327
|
action_icon = "○" if action == "continue" else "●" # Polish: ○ for continue, ● for complete
|
|
369
328
|
|
|
370
|
-
# IG-265: Remove badge from CLI display (plan_action kept in event data for logs)
|
|
371
329
|
content = f"🌟 {judgement}"
|
|
372
330
|
|
|
373
331
|
return DisplayLine(
|
|
@@ -489,7 +447,6 @@ __all__ = [
|
|
|
489
447
|
"format_goal_header",
|
|
490
448
|
"format_judgement",
|
|
491
449
|
"format_plan_phase_reasoning",
|
|
492
|
-
"format_reasoning",
|
|
493
450
|
"format_step_done",
|
|
494
451
|
"format_step_header",
|
|
495
452
|
"format_subagent_done",
|
|
@@ -16,7 +16,6 @@ from soothe_cli.cli.stream.formatter import (
|
|
|
16
16
|
format_goal_header,
|
|
17
17
|
format_judgement,
|
|
18
18
|
format_plan_phase_reasoning,
|
|
19
|
-
format_reasoning,
|
|
20
19
|
format_step_done,
|
|
21
20
|
format_step_header,
|
|
22
21
|
format_subagent_done,
|
|
@@ -42,9 +41,6 @@ GOAL_COMPLETE_EVENTS = {
|
|
|
42
41
|
"soothe.cognition.agent_loop.completed",
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
# IG-264: Default goal completion message (skip display to avoid redundancy)
|
|
46
|
-
DEFAULT_GOAL_ACHIEVED_MESSAGE = "Goal achieved successfully"
|
|
47
|
-
|
|
48
44
|
# Verbosity tier mapping
|
|
49
45
|
_VERBOSITY_TO_TIER = {
|
|
50
46
|
"quiet": VerbosityTier.QUIET,
|
|
@@ -200,7 +196,7 @@ class StreamDisplayPipeline:
|
|
|
200
196
|
Returns:
|
|
201
197
|
Display lines for goal header.
|
|
202
198
|
"""
|
|
203
|
-
# IG-
|
|
199
|
+
# IG-287: Prefer friendly_message over goal/goal_description
|
|
204
200
|
friendly_message = event.get("friendly_message")
|
|
205
201
|
goal = friendly_message or event.get("goal", event.get("goal_description", ""))
|
|
206
202
|
if not goal:
|
|
@@ -400,7 +396,6 @@ class StreamDisplayPipeline:
|
|
|
400
396
|
format_subagent_done(
|
|
401
397
|
preview_first(summary, 70), # Increased from 50 for richer metrics
|
|
402
398
|
duration_s,
|
|
403
|
-
result_preview=result_preview,
|
|
404
399
|
namespace=self._current_namespace,
|
|
405
400
|
verbosity_tier=self._verbosity_tier,
|
|
406
401
|
)
|
|
@@ -615,14 +610,10 @@ class StreamDisplayPipeline:
|
|
|
615
610
|
# Determine action type
|
|
616
611
|
action = "complete" if status == "done" else "continue"
|
|
617
612
|
|
|
618
|
-
raw_plan_action = event.get("plan_action")
|
|
619
|
-
plan_action_kw: str | None = raw_plan_action if raw_plan_action in ("keep", "new") else None
|
|
620
|
-
|
|
621
613
|
lines = [
|
|
622
614
|
format_judgement(
|
|
623
615
|
action_text,
|
|
624
616
|
action,
|
|
625
|
-
plan_action=plan_action_kw,
|
|
626
617
|
namespace=self._current_namespace,
|
|
627
618
|
verbosity_tier=self._verbosity_tier,
|
|
628
619
|
)
|
|
@@ -640,17 +631,6 @@ class StreamDisplayPipeline:
|
|
|
640
631
|
verbosity_tier=self._verbosity_tier,
|
|
641
632
|
)
|
|
642
633
|
)
|
|
643
|
-
else:
|
|
644
|
-
reasoning = event.get("reasoning", "").strip()
|
|
645
|
-
# IG-264: Skip redundant reasoning when it's the default goal message
|
|
646
|
-
if reasoning and reasoning != DEFAULT_GOAL_ACHIEVED_MESSAGE:
|
|
647
|
-
lines.append(
|
|
648
|
-
format_reasoning(
|
|
649
|
-
reasoning,
|
|
650
|
-
namespace=self._current_namespace,
|
|
651
|
-
verbosity_tier=self._verbosity_tier,
|
|
652
|
-
)
|
|
653
|
-
)
|
|
654
634
|
|
|
655
635
|
return lines
|
|
656
636
|
|
|
@@ -30,6 +30,14 @@ class CLIConfig:
|
|
|
30
30
|
logging_level: str | None = None
|
|
31
31
|
|
|
32
32
|
output_format: str = "text"
|
|
33
|
+
final_output_mode: str = "streaming"
|
|
34
|
+
|
|
35
|
+
# Output streaming overrides (RFC-614)
|
|
36
|
+
output_streaming_enabled: bool | None = None
|
|
37
|
+
"""Override daemon streaming enabled setting."""
|
|
38
|
+
|
|
39
|
+
output_streaming_mode: str | None = None
|
|
40
|
+
"""Override daemon streaming mode: 'streaming' or 'batch'."""
|
|
33
41
|
|
|
34
42
|
# Paths
|
|
35
43
|
soothe_home: Path = field(default_factory=lambda: Path.home() / ".soothe")
|
|
@@ -101,16 +109,28 @@ class CLIConfig:
|
|
|
101
109
|
daemon_section = data.get("daemon", {})
|
|
102
110
|
transports = daemon_section.get("transports", {})
|
|
103
111
|
websocket = transports.get("websocket", {})
|
|
112
|
+
websocket_legacy = data.get("websocket", {})
|
|
113
|
+
ui_section = data.get("ui", {})
|
|
104
114
|
|
|
105
115
|
raw_level = data.get("logging_level")
|
|
106
116
|
if raw_level is not None and not isinstance(raw_level, str):
|
|
107
117
|
raw_level = None
|
|
108
118
|
|
|
119
|
+
raw_final_output_mode = data.get("final_output_mode")
|
|
120
|
+
if raw_final_output_mode is None and isinstance(ui_section, dict):
|
|
121
|
+
raw_final_output_mode = ui_section.get("final_output_mode")
|
|
122
|
+
if not isinstance(raw_final_output_mode, str):
|
|
123
|
+
raw_final_output_mode = "streaming"
|
|
124
|
+
final_output_mode = raw_final_output_mode.strip().lower()
|
|
125
|
+
if final_output_mode not in {"streaming", "batch"}:
|
|
126
|
+
final_output_mode = "streaming"
|
|
127
|
+
|
|
109
128
|
return cls(
|
|
110
|
-
daemon_host=websocket.get("host", "127.0.0.1"),
|
|
111
|
-
daemon_port=websocket.get("port", 8765),
|
|
112
|
-
verbosity=data.get("verbosity", "normal"),
|
|
129
|
+
daemon_host=websocket.get("host", websocket_legacy.get("host", "127.0.0.1")),
|
|
130
|
+
daemon_port=websocket.get("port", websocket_legacy.get("port", 8765)),
|
|
131
|
+
verbosity=data.get("verbosity", ui_section.get("verbosity", "normal")),
|
|
113
132
|
logging_level=raw_level,
|
|
133
|
+
final_output_mode=final_output_mode,
|
|
114
134
|
soothe_home=Path(data.get("home", str(Path.home() / ".soothe"))),
|
|
115
135
|
)
|
|
116
136
|
|
|
@@ -135,8 +155,9 @@ class CLIConfig:
|
|
|
135
155
|
return cls(
|
|
136
156
|
daemon_host=soothe_config.daemon.transports.websocket.host,
|
|
137
157
|
daemon_port=soothe_config.daemon.transports.websocket.port,
|
|
138
|
-
verbosity=soothe_config.
|
|
158
|
+
verbosity=soothe_config.observability.verbosity,
|
|
139
159
|
logging_level=logging_level,
|
|
160
|
+
final_output_mode="streaming",
|
|
140
161
|
soothe_home=Path(soothe_config.home),
|
|
141
162
|
)
|
|
142
163
|
|