soothe-cli 0.5.6__tar.gz → 0.5.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.5.6 → soothe_cli-0.5.8}/PKG-INFO +1 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/execution/daemon.py +69 -28
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/stream/formatter.py +1 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/stream/pipeline.py +4 -8
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/config/cli_config.py +11 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/commands/command_router.py +6 -6
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/commands/slash_commands.py +2 -14
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/commands/subagent_routing.py +13 -12
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/core/event_processor.py +13 -6
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/core/presentation_engine.py +1 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/core/processor_state.py +8 -2
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/core/renderer_protocol.py +1 -1
- soothe_cli-0.5.8/src/soothe_cli/shared/daemon_errors.py +27 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/rendering/async_renderer_protocol.py +1 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/__init__.py +0 -2
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/message_processing.py +224 -8
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/rendering.py +4 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_call_resolution.py +169 -37
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_card_payload.py +8 -4
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/_env_vars.py +7 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_app.py +7 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_commands.py +3 -3
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_execution.py +10 -6
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_messages_mixin.py +28 -17
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_module_init.py +2 -2
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_ui.py +1 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/command_registry.py +2 -13
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/config.py +1 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/message_display_filter.py +4 -7
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/preview_limits.py +3 -4
- soothe_cli-0.5.8/src/soothe_cli/tui/step_task_routing.py +401 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/textual_adapter/__init__.py +0 -8
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/textual_adapter/_adapter.py +6 -10
- soothe_cli-0.5.8/src/soothe_cli/tui/textual_adapter/_stream_formatting.py +1000 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/textual_adapter/_stream_messages.py +8 -42
- soothe_cli-0.5.8/src/soothe_cli/tui/textual_adapter/_stream_tool_wire.py +167 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/textual_adapter/_turn.py +592 -222
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/textual_adapter/_turn_helpers.py +44 -14
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/tool_display.py +42 -3
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/clipboard.py +64 -25
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/message_store.py +1 -1
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/messages.py +356 -175
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/status.py +3 -3
- soothe_cli-0.5.6/src/soothe_cli/tui/textual_adapter/_stream_formatting.py +0 -285
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/.gitignore +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/README.md +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/pyproject.toml +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/main.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/stream/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/stream/context.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/stream/display_line.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/cli/stream/task_scope.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/plan/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/plan/rich_tree.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/commands/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/config_loader.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/core/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/duration_format.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/events/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/events/display_policy.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/events/essential_events.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/events/explore_task_display.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/events/stream_accumulator.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/events/tui_trace_log.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/rendering/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/rendering/renderer_base.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/_utils.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_card_visibility.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/base.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/execution.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/fallback.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/file_ops.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/goal_formatter.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/media.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/structured.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/subagent.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_formatters/web.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_message_format.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/tools/tool_output_formatter.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/_ask_user_types.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/_cli_context.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/_session_stats.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/_version.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_model.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/_startup.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/daemon_session.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/file_ops.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/formatting.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/hooks.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/media_utils.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/model_config.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/output.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/project_utils.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/sessions.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/theme.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/unicode_security.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/update_check.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/approval.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/ask_user.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/autopilot_dashboard.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/autopilot_screen.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/tool_renderers.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/tool_widgets.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/tools.py +0 -0
- {soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/tui/widgets/welcome.py +0 -0
|
@@ -22,12 +22,17 @@ from soothe_cli.cli.execution.headless_renderer import HeadlessCliRenderer
|
|
|
22
22
|
from soothe_cli.shared import EventProcessor
|
|
23
23
|
from soothe_cli.shared.commands.subagent_routing import parse_subagent_from_input
|
|
24
24
|
from soothe_cli.shared.core.presentation_engine import PresentationEngine
|
|
25
|
+
from soothe_cli.shared.daemon_errors import (
|
|
26
|
+
friendly_daemon_execution_error,
|
|
27
|
+
is_daemon_worker_subprocess_lost,
|
|
28
|
+
)
|
|
25
29
|
|
|
26
30
|
logger = logging.getLogger(__name__)
|
|
27
31
|
|
|
28
32
|
_DAEMON_FALLBACK_EXIT_CODE = 42
|
|
29
33
|
_SESSION_BOOTSTRAP_TIMEOUT_S = 30.0
|
|
30
34
|
_QUERY_START_TIMEOUT_S = 20.0
|
|
35
|
+
_HEADLESS_WORKER_LOST_RETRIES = 1
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
def _is_loop_scoped_event(event: dict[str, Any], *, active_loop_id: str) -> bool:
|
|
@@ -38,19 +43,20 @@ def _is_loop_scoped_event(event: dict[str, Any], *, active_loop_id: str) -> bool
|
|
|
38
43
|
return event.get("loop_id") == active_loop_id
|
|
39
44
|
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
def _emit_headless_error(message: str) -> None:
|
|
47
|
+
"""Write a user-facing error line to stderr (headless renderer convention)."""
|
|
48
|
+
typer.echo(f"ERROR: {message}", err=True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def _run_headless_session_once(
|
|
42
52
|
cfg: Any,
|
|
43
53
|
prompt: str,
|
|
44
54
|
*,
|
|
45
55
|
resume_loop_id: str | None = None,
|
|
46
56
|
autonomous: bool = False,
|
|
47
57
|
max_iterations: int | None = None,
|
|
48
|
-
) -> int:
|
|
49
|
-
"""Run
|
|
50
|
-
|
|
51
|
-
Uses WebSocket transport for all connections (RFC-0013).
|
|
52
|
-
Headless output is RFC-614 loop-tagged main-graph assistant text only (IG-343).
|
|
53
|
-
"""
|
|
58
|
+
) -> tuple[int, bool]:
|
|
59
|
+
"""Run one headless daemon session; return ``(exit_code, retry_on_worker_loss)``."""
|
|
54
60
|
from soothe_sdk.client import WebSocketClient
|
|
55
61
|
|
|
56
62
|
ws_url = websocket_url_from_config(cfg)
|
|
@@ -67,20 +73,22 @@ async def run_headless_via_daemon(
|
|
|
67
73
|
subscribe_timeout_s=_SESSION_BOOTSTRAP_TIMEOUT_S,
|
|
68
74
|
)
|
|
69
75
|
if status_event.get("type") == "error":
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
raw = str(status_event.get("message", "unknown"))
|
|
77
|
+
_emit_headless_error(friendly_daemon_execution_error(raw))
|
|
78
|
+
return 1, is_daemon_worker_subprocess_lost(raw)
|
|
72
79
|
|
|
73
80
|
active_loop_id = status_event.get("loop_id")
|
|
74
81
|
if not active_loop_id:
|
|
75
|
-
|
|
76
|
-
return 1
|
|
82
|
+
_emit_headless_error("No loop_id after session bootstrap")
|
|
83
|
+
return 1, False
|
|
77
84
|
|
|
78
85
|
subagent_name, cleaned_prompt = parse_subagent_from_input(prompt)
|
|
86
|
+
effective_prompt = cleaned_prompt if subagent_name else prompt
|
|
79
87
|
|
|
80
88
|
await asyncio.wait_for(
|
|
81
89
|
client.send_input(
|
|
82
90
|
active_loop_id,
|
|
83
|
-
|
|
91
|
+
effective_prompt,
|
|
84
92
|
autonomous=autonomous,
|
|
85
93
|
max_iterations=max_iterations,
|
|
86
94
|
preferred_subagent=subagent_name,
|
|
@@ -107,7 +115,7 @@ async def run_headless_via_daemon(
|
|
|
107
115
|
client.read_event(), timeout=_QUERY_START_TIMEOUT_S
|
|
108
116
|
)
|
|
109
117
|
except TimeoutError:
|
|
110
|
-
return _DAEMON_FALLBACK_EXIT_CODE
|
|
118
|
+
return _DAEMON_FALLBACK_EXIT_CODE, False
|
|
111
119
|
if not event:
|
|
112
120
|
break
|
|
113
121
|
|
|
@@ -116,17 +124,17 @@ async def run_headless_via_daemon(
|
|
|
116
124
|
continue
|
|
117
125
|
|
|
118
126
|
if event_type == "error":
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
raw = str(event.get("message", "unknown"))
|
|
128
|
+
_emit_headless_error(friendly_daemon_execution_error(raw))
|
|
129
|
+
return 1, is_daemon_worker_subprocess_lost(raw)
|
|
121
130
|
|
|
122
131
|
ev_data = event.get("data")
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
and isinstance(ev_data, dict)
|
|
126
|
-
and str(ev_data.get("type", "")).startswith("soothe.error")
|
|
132
|
+
if isinstance(ev_data, dict) and str(ev_data.get("type", "")).startswith(
|
|
133
|
+
"soothe.error"
|
|
127
134
|
):
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
raw = str(ev_data.get("error", "unknown"))
|
|
136
|
+
_emit_headless_error(friendly_daemon_execution_error(raw))
|
|
137
|
+
return 1, is_daemon_worker_subprocess_lost(raw)
|
|
130
138
|
|
|
131
139
|
if event_type == "status":
|
|
132
140
|
state = event.get("state", "")
|
|
@@ -155,15 +163,48 @@ async def run_headless_via_daemon(
|
|
|
155
163
|
logger.exception("Daemon connection failed")
|
|
156
164
|
from soothe_sdk.utils import format_cli_error
|
|
157
165
|
|
|
158
|
-
|
|
159
|
-
return _DAEMON_FALLBACK_EXIT_CODE
|
|
166
|
+
_emit_headless_error(format_cli_error(e))
|
|
167
|
+
return _DAEMON_FALLBACK_EXIT_CODE, False
|
|
160
168
|
except Exception as e:
|
|
161
169
|
logger.exception("Failed to run via daemon")
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return 1
|
|
170
|
+
friendly = friendly_daemon_execution_error(e)
|
|
171
|
+
_emit_headless_error(friendly)
|
|
172
|
+
return 1, is_daemon_worker_subprocess_lost(e)
|
|
166
173
|
else:
|
|
167
|
-
return 0
|
|
174
|
+
return 0, False
|
|
168
175
|
finally:
|
|
169
176
|
await client.close()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def run_headless_via_daemon(
|
|
180
|
+
cfg: Any,
|
|
181
|
+
prompt: str,
|
|
182
|
+
*,
|
|
183
|
+
resume_loop_id: str | None = None,
|
|
184
|
+
autonomous: bool = False,
|
|
185
|
+
max_iterations: int | None = None,
|
|
186
|
+
) -> int:
|
|
187
|
+
"""Run a single prompt by connecting to a running daemon.
|
|
188
|
+
|
|
189
|
+
Uses WebSocket transport for all connections (RFC-0013).
|
|
190
|
+
Headless output is RFC-614 loop-tagged main-graph assistant text only (IG-343).
|
|
191
|
+
|
|
192
|
+
Retries once when the worker pool loses a subprocess mid-query (common idle-timeout
|
|
193
|
+
race or transient worker recycle).
|
|
194
|
+
"""
|
|
195
|
+
last_code = 1
|
|
196
|
+
for attempt in range(_HEADLESS_WORKER_LOST_RETRIES + 1):
|
|
197
|
+
last_code, retryable = await _run_headless_session_once(
|
|
198
|
+
cfg,
|
|
199
|
+
prompt,
|
|
200
|
+
resume_loop_id=resume_loop_id,
|
|
201
|
+
autonomous=autonomous,
|
|
202
|
+
max_iterations=max_iterations,
|
|
203
|
+
)
|
|
204
|
+
if last_code == 0:
|
|
205
|
+
return 0
|
|
206
|
+
if retryable and attempt < _HEADLESS_WORKER_LOST_RETRIES:
|
|
207
|
+
logger.warning("Headless query failed after worker subprocess exit; retrying once")
|
|
208
|
+
continue
|
|
209
|
+
return last_code
|
|
210
|
+
return last_code
|
|
@@ -111,7 +111,7 @@ def format_subagent_done(
|
|
|
111
111
|
duration_s: Duration in seconds.
|
|
112
112
|
task_scope: Optional ``(task_tool_call_id, subagent_type)`` for delegated rows.
|
|
113
113
|
task_description: Original brief (e.g. explore ``search_target``) when available.
|
|
114
|
-
task_done_success: False for delegated failures (e.g.
|
|
114
|
+
task_done_success: False for delegated failures (e.g. optional plugin delegate error).
|
|
115
115
|
answer_summary: Optional one-line answer tail after metrics (IG-344).
|
|
116
116
|
|
|
117
117
|
Returns:
|
|
@@ -301,11 +301,7 @@ class StreamDisplayPipeline:
|
|
|
301
301
|
|
|
302
302
|
answer_tail: str | None = None
|
|
303
303
|
raw_summary = event.get("summary")
|
|
304
|
-
if (
|
|
305
|
-
isinstance(raw_summary, str)
|
|
306
|
-
and raw_summary.strip()
|
|
307
|
-
and subagent_name in ("claude", "browser", "research")
|
|
308
|
-
):
|
|
304
|
+
if isinstance(raw_summary, str) and raw_summary.strip() and subagent_name:
|
|
309
305
|
answer_tail = preview_first(raw_summary.strip(), 120)
|
|
310
306
|
|
|
311
307
|
return [
|
|
@@ -323,7 +319,7 @@ class StreamDisplayPipeline:
|
|
|
323
319
|
|
|
324
320
|
Args:
|
|
325
321
|
event: Event dictionary.
|
|
326
|
-
subagent_name: Subagent
|
|
322
|
+
subagent_name: Subagent id (e.g. explore, plan, research, or a plugin id).
|
|
327
323
|
|
|
328
324
|
Returns:
|
|
329
325
|
Formatted summary string with key metrics.
|
|
@@ -342,7 +338,7 @@ class StreamDisplayPipeline:
|
|
|
342
338
|
return summary
|
|
343
339
|
return "done"
|
|
344
340
|
|
|
345
|
-
#
|
|
341
|
+
# Optional plugin session: cost_usd + opaque session id field
|
|
346
342
|
if subagent_name == "claude":
|
|
347
343
|
cost = event.get("cost_usd", 0.0)
|
|
348
344
|
session_id = event.get("claude_session_id")
|
|
@@ -353,7 +349,7 @@ class StreamDisplayPipeline:
|
|
|
353
349
|
return summary
|
|
354
350
|
return "done"
|
|
355
351
|
|
|
356
|
-
#
|
|
352
|
+
# Optional web automation plugin: boolean success in payload
|
|
357
353
|
if subagent_name == "browser":
|
|
358
354
|
success = event.get("success", True)
|
|
359
355
|
return "✓ success" if success else "✗ failed"
|
|
@@ -27,6 +27,10 @@ class CLIConfig:
|
|
|
27
27
|
# logging_level: DEBUG/INFO/… for ~/.soothe/logs/cli.log; None = default INFO.
|
|
28
28
|
logging_level: str | None = None
|
|
29
29
|
|
|
30
|
+
# TUI rendering options
|
|
31
|
+
render_markdown: bool = True
|
|
32
|
+
"""Render assistant messages as Markdown in TUI (default True)."""
|
|
33
|
+
|
|
30
34
|
# Output streaming overrides (RFC-614)
|
|
31
35
|
output_streaming_enabled: bool | None = None
|
|
32
36
|
"""Override daemon streaming enabled setting."""
|
|
@@ -110,10 +114,17 @@ class CLIConfig:
|
|
|
110
114
|
if raw_level is not None and not isinstance(raw_level, str):
|
|
111
115
|
raw_level = None
|
|
112
116
|
|
|
117
|
+
# TUI options from 'tui' section
|
|
118
|
+
tui_section = data.get("tui", {})
|
|
119
|
+
render_markdown = tui_section.get("render_markdown", True)
|
|
120
|
+
if not isinstance(render_markdown, bool):
|
|
121
|
+
render_markdown = True
|
|
122
|
+
|
|
113
123
|
return cls(
|
|
114
124
|
daemon_host=websocket.get("host", websocket_legacy.get("host", "127.0.0.1")),
|
|
115
125
|
daemon_port=websocket.get("port", websocket_legacy.get("port", 8765)),
|
|
116
126
|
logging_level=raw_level,
|
|
127
|
+
render_markdown=render_markdown,
|
|
117
128
|
soothe_home=Path(data.get("home", str(Path.home() / ".soothe"))),
|
|
118
129
|
)
|
|
119
130
|
|
|
@@ -25,7 +25,7 @@ def parse_slash_command(input_text: str) -> tuple[str, str | None]:
|
|
|
25
25
|
"""Parse slash command and extract command + query.
|
|
26
26
|
|
|
27
27
|
Args:
|
|
28
|
-
input_text: Full user input (e.g., "/
|
|
28
|
+
input_text: Full user input (e.g., "/research topic summary")
|
|
29
29
|
|
|
30
30
|
Returns:
|
|
31
31
|
Tuple of (command, query) where query may be None
|
|
@@ -119,7 +119,7 @@ async def route_slash_command(
|
|
|
119
119
|
"""Route slash command based on registry metadata (RFC-404).
|
|
120
120
|
|
|
121
121
|
Args:
|
|
122
|
-
cmd_input: Full command input (e.g., "/memory", "/
|
|
122
|
+
cmd_input: Full command input (e.g., "/memory", "/research topic")
|
|
123
123
|
console: Rich console for rendering
|
|
124
124
|
client: WebSocket client for daemon communication
|
|
125
125
|
|
|
@@ -243,12 +243,12 @@ async def handle_routing_command(
|
|
|
243
243
|
) -> None:
|
|
244
244
|
"""Handle daemon routing command by sending input with optional subagent (RFC-404).
|
|
245
245
|
|
|
246
|
-
For
|
|
247
|
-
``preferred_subagent`` field so the daemon merges a subagent hint into
|
|
248
|
-
Other routing commands (e.g. ``/plan``) are sent as plain text unchanged.
|
|
246
|
+
For routing commands that map to a configured subagent id (e.g. ``/research``, ``/explore``),
|
|
247
|
+
sets the WebSocket ``preferred_subagent`` field so the daemon merges a subagent hint into
|
|
248
|
+
AgentLoop (IG-349). Other routing commands (e.g. ``/plan``) are sent as plain text unchanged.
|
|
249
249
|
|
|
250
250
|
Args:
|
|
251
|
-
cmd_input: Full command input (e.g., "/
|
|
251
|
+
cmd_input: Full command input (e.g., "/research topic summary")
|
|
252
252
|
console: Rich console
|
|
253
253
|
client: WebSocket client
|
|
254
254
|
loop_id: Subscribed loop to target (required for ``loop_input``)
|
|
@@ -160,7 +160,7 @@ KEYBOARD_SHORTCUTS: dict[str, str] = {
|
|
|
160
160
|
"Ctrl+D": "Detach TUI: Leave the loop running (confirm) and exit client",
|
|
161
161
|
"Ctrl+C": "Cancel running job, press twice within 1s to quit",
|
|
162
162
|
"Ctrl+E": "Focus chat input",
|
|
163
|
-
"Ctrl+Y": "Copy
|
|
163
|
+
"Ctrl+Y": "Copy selected text to clipboard (or show hint if none)",
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
|
|
@@ -266,20 +266,8 @@ COMMANDS: dict[str, dict[str, Any]] = {
|
|
|
266
266
|
"requires_loop": True,
|
|
267
267
|
"handler": show_autopilot_dashboard,
|
|
268
268
|
},
|
|
269
|
-
# Daemon routing commands (
|
|
269
|
+
# Daemon routing commands (3)
|
|
270
270
|
"/plan": {"location": "daemon", "type": "routing", "description": "Trigger plan mode"},
|
|
271
|
-
"/browser": {
|
|
272
|
-
"location": "daemon",
|
|
273
|
-
"type": "routing",
|
|
274
|
-
"description": "Route query to Browser subagent",
|
|
275
|
-
"requires_query": True,
|
|
276
|
-
},
|
|
277
|
-
"/claude": {
|
|
278
|
-
"location": "daemon",
|
|
279
|
-
"type": "routing",
|
|
280
|
-
"description": "Route query to Claude subagent",
|
|
281
|
-
"requires_query": True,
|
|
282
|
-
},
|
|
283
271
|
"/research": {
|
|
284
272
|
"location": "daemon",
|
|
285
273
|
"type": "routing",
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
SUBAGENT_DISPLAY_NAMES: dict[str, str] = {
|
|
6
|
-
"browser": "Browser",
|
|
7
|
-
"claude": "Claude",
|
|
8
6
|
"research": "Research",
|
|
9
7
|
"explore": "Explore",
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
# Lowercase ids matched after ``/`` for preferred_subagent routing (core only).
|
|
11
|
+
SUBAGENT_SLASH_ROUTE_IDS: tuple[str, ...] = ("research", "explore")
|
|
12
|
+
|
|
13
|
+
BUILTIN_SUBAGENT_NAMES: list[str] = list(SUBAGENT_SLASH_ROUTE_IDS)
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def get_subagent_display_name(technical_name: str) -> str:
|
|
@@ -19,18 +20,18 @@ def get_subagent_display_name(technical_name: str) -> str:
|
|
|
19
20
|
technical_name: Internal subagent name.
|
|
20
21
|
|
|
21
22
|
Returns:
|
|
22
|
-
|
|
23
|
+
Title-cased label for first-party ids; otherwise the raw id string.
|
|
23
24
|
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
key = (technical_name or "").strip()
|
|
26
|
+
if key.lower() in SUBAGENT_DISPLAY_NAMES:
|
|
27
|
+
return SUBAGENT_DISPLAY_NAMES[key.lower()]
|
|
28
|
+
return key
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def parse_subagent_from_input(user_input: str) -> tuple[str | None, str]:
|
|
31
32
|
"""Parse subagent subcommand from user input.
|
|
32
33
|
|
|
33
|
-
Detects subagent
|
|
34
|
+
Detects subagent routing commands (e.g. ``/research``, ``/explore``)
|
|
34
35
|
and extracts the subagent name along with the cleaned input text.
|
|
35
36
|
|
|
36
37
|
Args:
|
|
@@ -42,13 +43,13 @@ def parse_subagent_from_input(user_input: str) -> tuple[str | None, str]:
|
|
|
42
43
|
The subcommand is removed from ``cleaned_text``.
|
|
43
44
|
|
|
44
45
|
Examples:
|
|
45
|
-
``"/
|
|
46
|
-
``"
|
|
46
|
+
``"/research check this"`` -> ``("research", "check this")``
|
|
47
|
+
``"/explore map the repo"`` -> ``("explore", "map the repo")``
|
|
47
48
|
``"hello world"`` -> ``(None, "hello world")``
|
|
48
49
|
"""
|
|
49
50
|
first_match: tuple[int, str] | None = None
|
|
50
51
|
|
|
51
|
-
for subagent_name in
|
|
52
|
+
for subagent_name in SUBAGENT_SLASH_ROUTE_IDS:
|
|
52
53
|
subcommand = f"/{subagent_name}"
|
|
53
54
|
idx = user_input.lower().find(subcommand)
|
|
54
55
|
if idx != -1 and (first_match is None or idx < first_match[0]):
|
|
@@ -137,12 +137,17 @@ class EventProcessor:
|
|
|
137
137
|
namespace,
|
|
138
138
|
)
|
|
139
139
|
|
|
140
|
-
def _resolve_task_scope(self, namespace: tuple[str, ...]) -> tuple[str, str] | None:
|
|
141
|
-
"""Return ``(task_tool_call_id, subagent_type)`` for
|
|
140
|
+
def _resolve_task_scope(self, namespace: tuple[str, ...]) -> tuple[str, str, str] | None:
|
|
141
|
+
"""Return task scope ``(task_tool_call_id, subagent_type, step_id)`` for namespace."""
|
|
142
142
|
return resolve_task_scope_for_namespace(self._state.namespace_task_bindings, namespace)
|
|
143
143
|
|
|
144
144
|
def _enqueue_task_spawn_if_needed(
|
|
145
|
-
self,
|
|
145
|
+
self,
|
|
146
|
+
name: str,
|
|
147
|
+
args: dict[str, Any],
|
|
148
|
+
tool_call_id: str,
|
|
149
|
+
*,
|
|
150
|
+
is_main: bool,
|
|
146
151
|
) -> None:
|
|
147
152
|
"""Record main-graph ``task`` tool calls so subgraph streams can resolve labels."""
|
|
148
153
|
enqueue_task_spawn(
|
|
@@ -229,7 +234,7 @@ class EventProcessor:
|
|
|
229
234
|
"""Headless stdout: only RFC-614 loop-tagged finals (IG-343 / IG-345).
|
|
230
235
|
|
|
231
236
|
Suppresses unphased execute-wave narration on the main graph; ``goal_completion``,
|
|
232
|
-
``
|
|
237
|
+
``quiz``, etc. are routed via ``assistant_output_phase`` before this path.
|
|
233
238
|
|
|
234
239
|
Subgraph streams without RFC-614 phases are skipped in ``_handle_ai_message`` before
|
|
235
240
|
this runs; loop-tagged subgraph finals use ``_dispatch_loop_tagged_assistant_text``.
|
|
@@ -536,10 +541,11 @@ class EventProcessor:
|
|
|
536
541
|
|
|
537
542
|
# Accumulate streaming tool args (IG-053)
|
|
538
543
|
tool_call_chunks = getattr(msg, "tool_call_chunks", None) or []
|
|
539
|
-
accumulate_tool_call_chunks(
|
|
544
|
+
self._state.last_active_tool_call_id = accumulate_tool_call_chunks(
|
|
540
545
|
self._state.pending_tool_calls,
|
|
541
546
|
tool_call_chunks,
|
|
542
547
|
is_main=is_main,
|
|
548
|
+
last_active_id=self._state.last_active_tool_call_id,
|
|
543
549
|
)
|
|
544
550
|
|
|
545
551
|
# Emit pending tool calls with complete args
|
|
@@ -777,10 +783,11 @@ class EventProcessor:
|
|
|
777
783
|
# Accumulate streaming tool args from tool_call_chunks (IG-053)
|
|
778
784
|
tool_call_chunks = msg.get("tool_call_chunks", [])
|
|
779
785
|
if isinstance(tool_call_chunks, list) and tool_call_chunks:
|
|
780
|
-
accumulate_tool_call_chunks(
|
|
786
|
+
self._state.last_active_tool_call_id = accumulate_tool_call_chunks(
|
|
781
787
|
self._state.pending_tool_calls,
|
|
782
788
|
tool_call_chunks,
|
|
783
789
|
is_main=is_main,
|
|
790
|
+
last_active_id=self._state.last_active_tool_call_id,
|
|
784
791
|
)
|
|
785
792
|
|
|
786
793
|
self._emit_pending_tool_calls(namespace)
|
|
@@ -49,7 +49,7 @@ class PresentationEngine:
|
|
|
49
49
|
|
|
50
50
|
@property
|
|
51
51
|
def final_answer_locked(self) -> bool:
|
|
52
|
-
"""True after a custom final
|
|
52
|
+
"""True after a custom final or quiz-phase response was emitted for this turn."""
|
|
53
53
|
return self._state.final_answer_locked
|
|
54
54
|
|
|
55
55
|
def mark_final_answer_locked(self) -> None:
|
|
@@ -32,6 +32,9 @@ class ProcessorState:
|
|
|
32
32
|
# Maps tool_call_id -> {'name': str, 'args_str': str, 'emitted': bool, 'is_main': bool}
|
|
33
33
|
pending_tool_calls: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
34
34
|
|
|
35
|
+
# Last active tool_call_id for orphan chunk attachment (chunks without explicit id)
|
|
36
|
+
last_active_tool_call_id: str = ""
|
|
37
|
+
|
|
35
38
|
# Namespace -> display name mapping for subagents
|
|
36
39
|
name_map: dict[str, str] = field(default_factory=dict)
|
|
37
40
|
|
|
@@ -65,8 +68,10 @@ class ProcessorState:
|
|
|
65
68
|
"""(phase, namespace) pairs that already emitted final loop-tagged output this turn."""
|
|
66
69
|
|
|
67
70
|
# Task tool spawn queue → bind first subgraph namespace (FIFO; IG-334)
|
|
68
|
-
task_spawn_queue: deque[tuple[str, str]] = field(default_factory=deque)
|
|
69
|
-
namespace_task_bindings: dict[tuple[str, ...], tuple[str, str]] = field(
|
|
71
|
+
task_spawn_queue: deque[tuple[str, str, str]] = field(default_factory=deque)
|
|
72
|
+
namespace_task_bindings: dict[tuple[str, ...], tuple[str, str, str]] = field(
|
|
73
|
+
default_factory=dict
|
|
74
|
+
)
|
|
70
75
|
|
|
71
76
|
def reset_turn(self) -> None:
|
|
72
77
|
"""Reset per-turn state.
|
|
@@ -75,6 +80,7 @@ class ProcessorState:
|
|
|
75
80
|
Clears streaming buffers but preserves session state.
|
|
76
81
|
"""
|
|
77
82
|
self.pending_tool_calls.clear()
|
|
83
|
+
self.last_active_tool_call_id = ""
|
|
78
84
|
self.tool_call_start_times.clear()
|
|
79
85
|
self.emitted_tool_call_ids.clear()
|
|
80
86
|
self.emitted_tool_result_ids.clear()
|
|
@@ -141,7 +141,7 @@ class RendererProtocol(Protocol):
|
|
|
141
141
|
Catch-all for events not covered by specific callbacks.
|
|
142
142
|
|
|
143
143
|
Args:
|
|
144
|
-
event_type: Full event type string (e.g., ``soothe.subagent.
|
|
144
|
+
event_type: Full event type string (e.g., ``soothe.subagent.explore.started``).
|
|
145
145
|
data: Event payload.
|
|
146
146
|
namespace: Subagent namespace tuple (empty for main agent).
|
|
147
147
|
task_scope: When subgraph streams are bound to a Task tool call (IG-334).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Shared daemon execution error presentation for CLI and TUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
# Partial match for pool_runner RuntimeError when an OS worker exits mid-turn.
|
|
6
|
+
DAEMON_WORKER_SUBPROCESS_LOST = "Worker subprocess exited unexpectedly during query execution"
|
|
7
|
+
|
|
8
|
+
_FRIENDLY_WORKER_SUBPROCESS_LOST = (
|
|
9
|
+
"The daemon execution worker stopped unexpectedly (for example after the pool "
|
|
10
|
+
"recycled an idle subprocess). Send your message again."
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def friendly_daemon_execution_error(exc: BaseException | str) -> str:
|
|
15
|
+
"""Map known daemon failures to concise, actionable copy."""
|
|
16
|
+
if isinstance(exc, RuntimeError) and DAEMON_WORKER_SUBPROCESS_LOST in str(exc):
|
|
17
|
+
return _FRIENDLY_WORKER_SUBPROCESS_LOST
|
|
18
|
+
text = str(exc)
|
|
19
|
+
if DAEMON_WORKER_SUBPROCESS_LOST in text:
|
|
20
|
+
return _FRIENDLY_WORKER_SUBPROCESS_LOST
|
|
21
|
+
return text if isinstance(exc, str) else str(exc)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_daemon_worker_subprocess_lost(exc: BaseException | str) -> bool:
|
|
25
|
+
"""Return whether an error indicates a pool worker process exited mid-query."""
|
|
26
|
+
text = str(exc)
|
|
27
|
+
return DAEMON_WORKER_SUBPROCESS_LOST in text
|
{soothe_cli-0.5.6 → soothe_cli-0.5.8}/src/soothe_cli/shared/rendering/async_renderer_protocol.py
RENAMED
|
@@ -140,7 +140,7 @@ class AsyncRendererProtocol(Protocol):
|
|
|
140
140
|
Catch-all for events not covered by specific callbacks.
|
|
141
141
|
|
|
142
142
|
Args:
|
|
143
|
-
event_type: Full event type string (e.g., ``soothe.subagent.
|
|
143
|
+
event_type: Full event type string (e.g., ``soothe.subagent.explore.started``).
|
|
144
144
|
data: Event payload.
|
|
145
145
|
namespace: Subagent namespace tuple (empty for main agent).
|
|
146
146
|
task_scope: When subgraph streams are bound to a Task tool call (IG-334).
|
|
@@ -15,7 +15,6 @@ from soothe_cli.shared.tools.message_processing import (
|
|
|
15
15
|
from soothe_cli.shared.tools.rendering import update_name_map_from_tool_calls
|
|
16
16
|
from soothe_cli.shared.tools.tool_call_resolution import (
|
|
17
17
|
build_streaming_args_overlay,
|
|
18
|
-
infer_tool_name_from_call_id,
|
|
19
18
|
materialize_ai_blocks_with_resolved_tools,
|
|
20
19
|
tool_args_meaningful,
|
|
21
20
|
)
|
|
@@ -64,7 +63,6 @@ __all__ = [
|
|
|
64
63
|
"update_name_map_from_tool_calls",
|
|
65
64
|
# Tool call resolution
|
|
66
65
|
"build_streaming_args_overlay",
|
|
67
|
-
"infer_tool_name_from_call_id",
|
|
68
66
|
"materialize_ai_blocks_with_resolved_tools",
|
|
69
67
|
"tool_args_meaningful",
|
|
70
68
|
# Tool card payload
|