soothe-cli 0.8.0__tar.gz → 0.8.1__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.
Files changed (134) hide show
  1. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/PKG-INFO +2 -1
  2. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/pyproject.toml +2 -1
  3. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/commands/autopilot_cmd.py +2 -2
  4. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/commands/config_cmd.py +1 -1
  5. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/commands/cron_cmd.py +1 -1
  6. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/commands/loop_cmd.py +4 -2
  7. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/commands/run_cmd.py +9 -62
  8. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/commands/status_cmd.py +1 -1
  9. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/execution/daemon.py +6 -47
  10. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/execution/headless.py +1 -1
  11. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/main.py +1 -1
  12. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/config/cli_config.py +1 -1
  13. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/headless/processor.py +2 -2
  14. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/headless/processor_state.py +1 -1
  15. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/parse/tool_result.py +2 -0
  16. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/presentation/renderer_protocol.py +1 -1
  17. soothe_cli-0.8.1/src/soothe_cli/runtime/transport/session.py +80 -0
  18. soothe_cli-0.8.1/src/soothe_cli/runtime/turn/pipeline.py +35 -0
  19. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/wire/message_text.py +1 -1
  20. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_model.py +3 -2
  21. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_startup.py +2 -2
  22. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/commands/command_router.py +1 -1
  23. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/config.py +1 -1
  24. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/model_config.py +9 -3
  25. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/skills/invocation.py +5 -2
  26. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/theme.py +1 -1
  27. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/tool_display.py +1 -1
  28. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/update_check.py +1 -1
  29. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/chat_input.py +1 -1
  30. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/context_data.py +38 -31
  31. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/context_viewer.py +7 -4
  32. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +1 -1
  33. soothe_cli-0.8.0/src/soothe_cli/runtime/transport/session.py +0 -773
  34. soothe_cli-0.8.0/src/soothe_cli/runtime/turn/pipeline.py +0 -404
  35. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/.gitignore +0 -0
  36. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/README.md +0 -0
  37. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/__init__.py +0 -0
  38. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/__init__.py +0 -0
  39. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/commands/__init__.py +0 -0
  40. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/execution/__init__.py +0 -0
  41. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
  42. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  43. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/cli/execution/launcher.py +0 -0
  44. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/config/__init__.py +0 -0
  45. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/config/loader.py +0 -0
  46. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/config/logging_setup.py +0 -0
  47. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/__init__.py +0 -0
  48. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/parse/message_processing.py +0 -0
  49. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
  50. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/policy/display_policy.py +0 -0
  51. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/policy/essential_events.py +0 -0
  52. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
  53. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
  54. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/presentation/engine.py +0 -0
  55. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/presentation/id_format.py +0 -0
  56. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
  57. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
  58. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
  59. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/state/session_stats.py +0 -0
  60. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/state/step_router.py +0 -0
  61. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
  62. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/state/transcript.py +0 -0
  63. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/token_events_debug.py +0 -0
  64. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/token_usage.py +0 -0
  65. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/turn/prepare.py +0 -0
  66. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
  67. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/runtime/wire/messages.py +0 -0
  68. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/__init__.py +0 -0
  69. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/_cli_context.py +0 -0
  70. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/_env_vars.py +0 -0
  71. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/_version.py +0 -0
  72. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/__init__.py +0 -0
  73. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_app.py +0 -0
  74. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_execution.py +0 -0
  75. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_history.py +0 -0
  76. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_messages_mixin.py +0 -0
  77. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_module_init.py +0 -0
  78. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/_ui.py +0 -0
  79. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/app/app.tcss +0 -0
  80. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/binding.py +0 -0
  81. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/command_registry.py +0 -0
  82. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/commands/__init__.py +0 -0
  83. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/commands/slash_commands.py +0 -0
  84. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/commands/subagent_routing.py +0 -0
  85. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/file_change_notify.py +0 -0
  86. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/file_change_renderers.py +0 -0
  87. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/hooks.py +0 -0
  88. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/input.py +0 -0
  89. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/markdown_theme.py +0 -0
  90. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/media_utils.py +0 -0
  91. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/path_utils.py +0 -0
  92. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/preview_limits.py +0 -0
  93. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/project_utils.py +0 -0
  94. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/sessions.py +0 -0
  95. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/shell_color.py +0 -0
  96. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/skills/__init__.py +0 -0
  97. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/skills/load.py +0 -0
  98. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/spinner_labels.py +0 -0
  99. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/textual_adapter.py +0 -0
  100. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/tips.py +0 -0
  101. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/unicode_security.py +0 -0
  102. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  103. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/_links.py +0 -0
  104. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  105. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  106. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/diff.py +0 -0
  107. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/editor.py +0 -0
  108. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
  109. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
  110. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/history.py +0 -0
  111. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/loading.py +0 -0
  112. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  113. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  114. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  115. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
  116. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/_helpers.py +0 -0
  117. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
  118. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
  119. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/clarification.py +0 -0
  120. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
  121. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/cognition_step.py +0 -0
  122. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +0 -0
  123. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/cognition_subagent.py +0 -0
  124. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
  125. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
  126. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
  127. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
  128. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  129. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  130. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +0 -0
  131. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/router_profile_selector.py +0 -0
  132. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/status.py +0 -0
  133. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  134. {soothe_cli-0.8.0 → soothe_cli-0.8.1}/src/soothe_cli/tui/widgets/welcome.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soothe-cli
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Soothe CLI client - communicates with daemon via WebSocket
5
5
  Project-URL: Homepage, https://github.com/mirasoth/soothe
6
6
  Project-URL: Documentation, https://soothe.readthedocs.io
@@ -22,6 +22,7 @@ Requires-Dist: pillow<12.0.0,>=10.0.0
22
22
  Requires-Dist: python-dotenv<2.0.0,>=1.0.0
23
23
  Requires-Dist: pyyaml<7.0.0,>=6.0.0
24
24
  Requires-Dist: rich<15.0.0,>=13.0.0
25
+ Requires-Dist: soothe-client-python<1.0.0,>=0.5.25
25
26
  Requires-Dist: soothe-sdk<1.0.0,>=0.5.25
26
27
  Requires-Dist: textual<10.0.0,>=8.0.0
27
28
  Requires-Dist: typer<2.0.0,>=0.9.0
@@ -23,7 +23,8 @@ classifiers = [
23
23
  "Topic :: Software Development :: Libraries :: Python Modules",
24
24
  ]
25
25
  dependencies = [
26
- "soothe-sdk>=0.5.25,<1.0.0", # Subagent wire helpers (types owned by soothe subagents)
26
+ "soothe-sdk>=0.5.25,<1.0.0", # events/display/wire contracts
27
+ "soothe-client-python>=0.5.25,<1.0.0", # WebSocket transport client
27
28
  "typer>=0.9.0,<2.0.0", # CLI framework
28
29
  "textual>=8.0.0,<10.0.0", # TUI framework
29
30
  "rich>=13.0.0,<15.0.0", # Console output
@@ -14,12 +14,12 @@ from collections import Counter
14
14
  from pathlib import Path
15
15
 
16
16
  import typer
17
- from soothe_sdk.client import (
17
+ from soothe_client import (
18
18
  is_daemon_live,
19
19
  websocket_url_from_config,
20
20
  ws_command_client_from_config,
21
21
  )
22
- from soothe_sdk.client.protocol import preview_first
22
+ from soothe_sdk.wire.protocol import preview_first
23
23
 
24
24
  app = typer.Typer(help="Autopilot mode — long-running autonomous agent control.")
25
25
 
@@ -10,7 +10,7 @@ from typing import Annotated, Any
10
10
  import typer
11
11
  from rich.console import Console
12
12
  from rich.panel import Panel
13
- from soothe_sdk.client import (
13
+ from soothe_client import (
14
14
  WebSocketClient,
15
15
  request_daemon_config_reload,
16
16
  websocket_url_from_config,
@@ -21,7 +21,7 @@ import typer
21
21
  from rich.console import Console
22
22
  from rich.panel import Panel
23
23
  from rich.table import Table
24
- from soothe_sdk.client import (
24
+ from soothe_client import (
25
25
  is_daemon_live,
26
26
  websocket_url_from_config,
27
27
  ws_command_client_from_config,
@@ -17,12 +17,14 @@ from rich.console import Console
17
17
  from rich.panel import Panel
18
18
  from rich.prompt import Prompt
19
19
  from rich.table import Table
20
- from soothe_sdk.client import (
21
- ProtocolError,
20
+ from soothe_client import (
22
21
  WebSocketClient,
23
22
  is_daemon_live,
24
23
  websocket_url_from_config,
25
24
  )
25
+ from soothe_sdk.client import (
26
+ ProtocolError,
27
+ )
26
28
 
27
29
  from soothe_cli.runtime import load_config
28
30
 
@@ -4,10 +4,9 @@ import logging
4
4
  import sys
5
5
  import time
6
6
  from pathlib import Path
7
- from typing import Any
8
7
 
9
8
  import typer
10
- from soothe_sdk.client.config import SOOTHE_HOME
9
+ from soothe_sdk.paths import SOOTHE_HOME
11
10
  from soothe_sdk.utils.logging import resolve_cli_log_level
12
11
 
13
12
  from soothe_cli.cli.execution import run_headless, run_tui
@@ -37,7 +36,8 @@ def run_impl(
37
36
  autonomous: Enable autonomous iteration mode
38
37
  max_iterations: Max iterations for autonomous mode
39
38
  tui_with_prompt: When True with a prompt, open the TUI instead of headless.
40
- mcp_config: Path to additional MCP server config (JSON/YAML) to merge.
39
+ mcp_config: Deprecated. MCP servers must be configured on the daemon;
40
+ passing this flag only emits a warning.
41
41
  """
42
42
  startup_start = time.perf_counter()
43
43
  use_headless: bool | None = None # Track execution mode for exit tip
@@ -45,9 +45,13 @@ def run_impl(
45
45
  try:
46
46
  cfg = load_config()
47
47
 
48
- # Merge --mcp-config file into SootheConfig.mcp_servers
48
+ # MCP servers are owned by the daemon config; CLI cannot merge them post-split.
49
49
  if mcp_config:
50
- _merge_mcp_config(cfg, mcp_config)
50
+ typer.echo(
51
+ "--mcp-config is ignored in the CLI; configure MCP servers on the daemon "
52
+ f"(~/.soothe/config). Requested file: {mcp_config}",
53
+ err=True,
54
+ )
51
55
  log_level = resolve_cli_log_level(logging_level=cfg.logging_level)
52
56
  log_file = Path(SOOTHE_HOME) / "logs" / "cli.log"
53
57
  setup_logging(log_level, log_file=log_file)
@@ -123,60 +127,3 @@ def run_impl(
123
127
 
124
128
  typer.echo(f"Error: {format_cli_error(e)}", err=True)
125
129
  sys.exit(1)
126
-
127
-
128
- def _merge_mcp_config(cfg: Any, mcp_config_path: str) -> None:
129
- """Load MCP server config from a JSON/YAML file and merge into SootheConfig.
130
-
131
- Args:
132
- cfg: SootheConfig instance.
133
- mcp_config_path: Path to MCP config file (JSON or YAML).
134
- """
135
- import json
136
-
137
- path = Path(mcp_config_path).expanduser().resolve()
138
- if not path.is_file():
139
- typer.echo(f"--mcp-config: file not found: {path}", err=True)
140
- return
141
-
142
- try:
143
- raw = path.read_text(encoding="utf-8")
144
- if path.suffix in (".yml", ".yaml"):
145
- try:
146
- import yaml
147
-
148
- data = yaml.safe_load(raw)
149
- except ImportError:
150
- typer.echo("--mcp-config: PyYAML not installed; use JSON format", err=True)
151
- return
152
- else:
153
- data = json.loads(raw)
154
- except Exception as e:
155
- typer.echo(f"--mcp-config: failed to parse {path}: {e}", err=True)
156
- return
157
-
158
- servers = data.get("mcp_servers") or data.get("servers") or []
159
- if not isinstance(servers, list):
160
- typer.echo("--mcp-config: expected 'mcp_servers' or 'servers' list", err=True)
161
- return
162
-
163
- from soothe.config.models import MCPServerConfig
164
-
165
- new_servers: list[MCPServerConfig] = []
166
- for entry in servers:
167
- if isinstance(entry, dict):
168
- try:
169
- new_servers.append(MCPServerConfig(**entry))
170
- except Exception as e:
171
- logger.warning("Skipping invalid MCP server entry: %s", e)
172
-
173
- if new_servers:
174
- existing = list(cfg.mcp_servers) if cfg.mcp_servers else []
175
- existing_names = {s.name for s in existing}
176
- for s in new_servers:
177
- if s.name in existing_names:
178
- logger.warning("--mcp-config: server '%s' already in config, skipping", s.name)
179
- else:
180
- existing.append(s)
181
- cfg.mcp_servers = existing
182
- logger.info("--mcp-config: merged %d MCP server(s)", len(new_servers))
@@ -15,7 +15,7 @@ import typer
15
15
  from rich.console import Console
16
16
  from rich.panel import Panel
17
17
  from rich.table import Table
18
- from soothe_sdk.client import (
18
+ from soothe_client import (
19
19
  WebSocketClient,
20
20
  check_daemon_status,
21
21
  is_daemon_live,
@@ -12,13 +12,14 @@ import signal
12
12
  from typing import Any
13
13
 
14
14
  import typer
15
- from soothe_sdk.client import (
15
+ from soothe_client import (
16
16
  WebSocketClient,
17
17
  async_ws_command_client_from_config,
18
18
  bootstrap_loop_session,
19
19
  connect_websocket_with_retries,
20
20
  websocket_url_from_config,
21
21
  )
22
+ from soothe_client.appkit import is_loop_scoped_event, unwrap_next
22
23
 
23
24
  from soothe_cli.cli.execution.daemon_errors import (
24
25
  friendly_daemon_execution_error,
@@ -39,46 +40,6 @@ _HEADLESS_WORKER_LOST_RETRIES = 1
39
40
  _CANCEL_SEND_TIMEOUT_S = 3.0
40
41
 
41
42
 
42
- def _unwrap_next(event: dict[str, Any] | None) -> dict[str, Any] | None:
43
- """Unwrap a protocol-1 ``next`` envelope to its inner streaming frame.
44
-
45
- Under protocol-1 (RFC-450 §9.3) the daemon wraps free-form streaming
46
- frames (``event``/``command_response``/card replay) in a
47
- ``{proto, type:"next", payload:{namespace, mode, data}}`` envelope. This
48
- helper returns the inner ``data`` dict (the legacy frame) so the headless
49
- loop and EventProcessor can branch on the same fields as before.
50
- ``status``/``error``/``response``/``complete`` are sent raw and pass
51
- through unchanged.
52
- """
53
- if not isinstance(event, dict):
54
- return event
55
- if event.get("type") != "next":
56
- return event
57
- payload = event.get("payload")
58
- if not isinstance(payload, dict):
59
- return event
60
- data = payload.get("data")
61
- return data if isinstance(data, dict) else event
62
-
63
-
64
- def _is_loop_scoped_event(event: dict[str, Any], *, active_loop_id: str) -> bool:
65
- """Return whether a daemon frame belongs to the active StrangeLoop session.
66
-
67
- Unwraps protocol-1 ``next`` envelopes first, then checks ``loop_id`` on the
68
- inner streaming frame. Non-scoped types (``response``, ``error``,
69
- ``complete``, etc.) are always considered in-scope.
70
- """
71
- event_type = event.get("type", "")
72
- if event_type == "next":
73
- inner = _unwrap_next(event)
74
- if isinstance(inner, dict):
75
- event_type = inner.get("type", "")
76
- return event_type not in {"status", "event"} or (inner.get("loop_id") == active_loop_id)
77
- if event_type not in {"status", "event"}:
78
- return True
79
- return event.get("loop_id") == active_loop_id
80
-
81
-
82
43
  def _emit_headless_error(message: str) -> None:
83
44
  """Write a user-facing error line to stderr (headless renderer convention)."""
84
45
  typer.echo(f"ERROR: {message}", err=True)
@@ -120,8 +81,6 @@ async def _run_headless_session_once(
120
81
  max_iterations: int | None = None,
121
82
  ) -> tuple[int, bool]:
122
83
  """Run one headless daemon session; return ``(exit_code, retry_on_worker_loss)``."""
123
- from soothe_sdk.client import WebSocketClient
124
-
125
84
  ws_url = websocket_url_from_config(cfg)
126
85
  client = WebSocketClient(url=ws_url)
127
86
 
@@ -248,13 +207,13 @@ async def _run_headless_session_once(
248
207
  break
249
208
 
250
209
  event_type = event.get("type", "")
251
- if not _is_loop_scoped_event(event, active_loop_id=active_loop_id):
210
+ if not is_loop_scoped_event(event, active_loop_id=active_loop_id):
252
211
  continue
253
212
 
254
213
  # Unwrap protocol-1 ``next`` envelopes to the inner streaming frame
255
214
  # (RFC-450 §9.3). ``status``/``error`` arrive raw and pass through.
256
215
  if event_type == "next":
257
- inner = _unwrap_next(event)
216
+ inner = unwrap_next(event)
258
217
  if isinstance(inner, dict):
259
218
  event = inner
260
219
  event_type = event.get("type", "")
@@ -288,12 +247,12 @@ async def _run_headless_session_once(
288
247
  break
289
248
  if not nxt:
290
249
  break
291
- if not _is_loop_scoped_event(nxt, active_loop_id=active_loop_id):
250
+ if not is_loop_scoped_event(nxt, active_loop_id=active_loop_id):
292
251
  continue
293
252
  # Unwrap ``next`` envelopes before handing to the
294
253
  # processor so it sees the legacy frame shape.
295
254
  if nxt.get("type") == "next":
296
- inner = _unwrap_next(nxt)
255
+ inner = unwrap_next(nxt)
297
256
  if isinstance(inner, dict):
298
257
  nxt = inner
299
258
  processor.process_event(nxt)
@@ -4,7 +4,7 @@ import asyncio
4
4
  import sys
5
5
 
6
6
  import typer
7
- from soothe_sdk.client import (
7
+ from soothe_client import (
8
8
  WebSocketClient,
9
9
  is_daemon_live,
10
10
  request_daemon_shutdown,
@@ -10,7 +10,7 @@ from pathlib import Path # noqa: E402
10
10
  from typing import Annotated # noqa: E402
11
11
 
12
12
  import typer # noqa: E402
13
- from soothe_sdk.client.config import SOOTHE_HOME # noqa: E402
13
+ from soothe_sdk.paths import SOOTHE_HOME # noqa: E402
14
14
 
15
15
  from soothe_cli.config.cli_config import CLIConfig # noqa: E402
16
16
  from soothe_cli.config.loader import set_runtime_config # noqa: E402
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from dataclasses import dataclass, field
6
6
  from pathlib import Path
7
7
 
8
- from soothe_sdk.client.config import SOOTHE_HOME
8
+ from soothe_sdk.paths import SOOTHE_HOME
9
9
 
10
10
 
11
11
  @dataclass
@@ -35,7 +35,7 @@ from soothe_cli.runtime.policy.tui_trace_log import log_tui_trace
35
35
  from soothe_cli.runtime.presentation.engine import PresentationEngine
36
36
 
37
37
  if TYPE_CHECKING:
38
- from soothe_sdk.client.schemas import Plan
38
+ from soothe_client.schemas import Plan
39
39
 
40
40
  from soothe_cli.runtime.presentation.renderer_protocol import RendererProtocol
41
41
 
@@ -962,7 +962,7 @@ class EventProcessor:
962
962
 
963
963
  def _handle_plan_created(self, data: dict[str, Any]) -> None:
964
964
  """Handle plan creation event."""
965
- from soothe_sdk.client.schemas import Plan, PlanStep
965
+ from soothe_client.schemas import Plan, PlanStep
966
966
 
967
967
  steps = [
968
968
  PlanStep(
@@ -11,7 +11,7 @@ from dataclasses import dataclass, field
11
11
  from typing import TYPE_CHECKING, Any
12
12
 
13
13
  if TYPE_CHECKING:
14
- from soothe_sdk.client.schemas import Plan
14
+ from soothe_client.schemas import Plan
15
15
 
16
16
  from soothe_cli.runtime.state.stream_accumulator import StreamingTextAccumulator
17
17
 
@@ -5,9 +5,11 @@ from __future__ import annotations
5
5
  from soothe_sdk.display.tool_result import (
6
6
  extract_tool_result_payload,
7
7
  infer_tool_output_suggests_error,
8
+ is_error_tool_result_text,
8
9
  )
9
10
 
10
11
  __all__ = [
11
12
  "extract_tool_result_payload",
12
13
  "infer_tool_output_suggests_error",
14
+ "is_error_tool_result_text",
13
15
  ]
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
  from typing import TYPE_CHECKING, Any, Protocol
10
10
 
11
11
  if TYPE_CHECKING:
12
- from soothe_sdk.client.schemas import Plan
12
+ from soothe_client.schemas import Plan
13
13
 
14
14
  TaskScope = tuple[str, str]
15
15
 
@@ -0,0 +1,80 @@
1
+ """Daemon-backed session helpers for the Textual TUI.
2
+
3
+ Thin CLI wrapper over ``soothe_client.appkit.DaemonSession`` that derives
4
+ WebSocket URL / stream delivery from CLI config and uses the richer CLI early
5
+ chunk filter (LangChain-aware).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from soothe_client.appkit import DaemonSession
13
+ from soothe_client.helpers import websocket_url_from_config
14
+
15
+ # Re-exported for test monkeypatches / historical import sites.
16
+ from soothe_client.session import ( # noqa: F401
17
+ bootstrap_loop_session,
18
+ connect_websocket_with_retries,
19
+ )
20
+
21
+ from soothe_cli.runtime.state.session_stats import TurnEventStats
22
+ from soothe_cli.runtime.wire.chunk_filter import should_drop_stream_chunk_early
23
+
24
+ # Match headless daemon client post-idle drain window.
25
+ _POST_IDLE_DRAIN_DEADLINE_S = 0.5
26
+
27
+ # Brief close handshake on TUI exit — the daemon cleans up on disconnect anyway.
28
+ TUI_EXIT_HANDSHAKE_TIMEOUT_S = 0.3
29
+
30
+
31
+ class TuiDaemonSession(DaemonSession):
32
+ """Own the daemon websocket session used by the TUI."""
33
+
34
+ def __init__(
35
+ self,
36
+ cfg: Any,
37
+ *,
38
+ workspace: str | None = None,
39
+ post_idle_drain_deadline: float = _POST_IDLE_DRAIN_DEADLINE_S,
40
+ ) -> None:
41
+ self._cfg = cfg
42
+ super().__init__(
43
+ websocket_url_from_config(cfg),
44
+ workspace=workspace,
45
+ stream_delivery=self._resolve_stream_delivery_mode,
46
+ post_idle_drain_deadline=post_idle_drain_deadline,
47
+ early_drop_fn=should_drop_stream_chunk_early,
48
+ stats_factory=TurnEventStats,
49
+ )
50
+
51
+ def _resolve_stream_delivery_mode(self) -> str:
52
+ """Determine stream delivery mode from config (RFC-614).
53
+
54
+ Returns one of ``batch`` | ``adaptive`` | ``streaming``. CLI override
55
+ wins, then config; defaults to ``adaptive``.
56
+ """
57
+ if (
58
+ self._cfg
59
+ and hasattr(self._cfg, "output_streaming_mode")
60
+ and self._cfg.output_streaming_mode
61
+ ):
62
+ return str(self._cfg.output_streaming_mode)
63
+
64
+ if self._cfg and hasattr(self._cfg, "agent"):
65
+ streaming_cfg = self._cfg.agent.loop.output_streaming
66
+ return str(streaming_cfg.mode)
67
+
68
+ return "adaptive"
69
+
70
+
71
+ # Historical alias used by some call sites / docs.
72
+ DaemonSession = TuiDaemonSession # noqa: F811
73
+
74
+ __all__ = [
75
+ "DaemonSession",
76
+ "TUI_EXIT_HANDSHAKE_TIMEOUT_S",
77
+ "TuiDaemonSession",
78
+ "bootstrap_loop_session",
79
+ "connect_websocket_with_retries",
80
+ ]
@@ -0,0 +1,35 @@
1
+ """Decouple daemon stream ingestion from chunk processing and UI application.
2
+
3
+ Canonical implementation lives in ``soothe_client.appkit.turn`` (RFC-629 Layer 1).
4
+ This module re-exports the public API for CLI import stability.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from soothe_client.appkit.turn import (
10
+ _DEFAULT_BATCH_DELAY_MS,
11
+ _DEFAULT_BATCH_SIZE,
12
+ _DEFAULT_INBOUND_MAXSIZE,
13
+ _DEFAULT_OUTBOUND_MAXSIZE,
14
+ PRIORITY_CRITICAL,
15
+ PRIORITY_HIGH,
16
+ PRIORITY_LOW,
17
+ PRIORITY_NORMAL,
18
+ TurnApplyBatcher,
19
+ TurnEventPipeline,
20
+ run_turn_pipeline,
21
+ )
22
+
23
+ __all__ = [
24
+ "PRIORITY_CRITICAL",
25
+ "PRIORITY_HIGH",
26
+ "PRIORITY_LOW",
27
+ "PRIORITY_NORMAL",
28
+ "TurnApplyBatcher",
29
+ "TurnEventPipeline",
30
+ "run_turn_pipeline",
31
+ "_DEFAULT_BATCH_SIZE",
32
+ "_DEFAULT_BATCH_DELAY_MS",
33
+ "_DEFAULT_INBOUND_MAXSIZE",
34
+ "_DEFAULT_OUTBOUND_MAXSIZE",
35
+ ]
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any
6
6
 
7
- from soothe_sdk.client.wire import flatten_enveloped_message_dict
7
+ from soothe_sdk.wire.codec import flatten_enveloped_message_dict
8
8
 
9
9
 
10
10
  def wire_message_body(msg: Any) -> Any:
@@ -287,6 +287,7 @@ class _ModelMixin:
287
287
  initial_snapshot = await self._load_token_usage_snapshot()
288
288
  screen = ContextViewerScreen(
289
289
  loop_id=loop_id,
290
+ daemon_session=self._daemon_session,
290
291
  load_token_snapshot=self._load_token_usage_snapshot,
291
292
  initial_token_snapshot=initial_snapshot,
292
293
  )
@@ -308,7 +309,7 @@ class _ModelMixin:
308
309
  Args:
309
310
  task: Task description for autonomous execution.
310
311
  """
311
- from soothe_sdk.client import (
312
+ from soothe_client import (
312
313
  async_ws_command_client_from_config,
313
314
  is_daemon_live,
314
315
  websocket_url_from_config,
@@ -356,7 +357,7 @@ class _ModelMixin:
356
357
  text: Natural language schedule and task description.
357
358
  slash_input: Original slash command for chat display.
358
359
  """
359
- from soothe_sdk.client import (
360
+ from soothe_client import (
360
361
  async_ws_command_client_from_config,
361
362
  is_daemon_live,
362
363
  websocket_url_from_config,
@@ -473,7 +473,7 @@ class _StartupMixin:
473
473
  ConnectionError: If the daemon is not reachable or ready in time.
474
474
  Exception: If loop bootstrap fails.
475
475
  """
476
- from soothe_sdk.client import (
476
+ from soothe_client import (
477
477
  is_daemon_live,
478
478
  websocket_url_from_config,
479
479
  )
@@ -648,7 +648,7 @@ class _StartupMixin:
648
648
  # TUI from rendering.
649
649
  from langchain_core.messages import AIMessage # noqa: F401
650
650
  from langgraph.types import Command # noqa: F401
651
- from soothe_sdk.client.config import DEFAULT_EXECUTE_TIMEOUT # noqa: F401
651
+ from soothe_sdk.paths import DEFAULT_EXECUTE_TIMEOUT # noqa: F401
652
652
  except Exception:
653
653
  logger.warning("Could not prewarm third-party imports", exc_info=True)
654
654
 
@@ -16,7 +16,7 @@ from soothe_cli.tui.commands.subagent_routing import parse_subagent_from_input
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from rich.console import Console
19
- from soothe_sdk.client import WebSocketClient
19
+ from soothe_client import WebSocketClient
20
20
 
21
21
  logger = logging.getLogger(__name__)
22
22
 
@@ -13,7 +13,7 @@ from enum import StrEnum
13
13
  from pathlib import Path
14
14
  from typing import TYPE_CHECKING, Any
15
15
 
16
- from soothe_sdk.client.config import SOOTHE_HOME
16
+ from soothe_sdk.paths import SOOTHE_HOME
17
17
 
18
18
  from soothe_cli.tui._version import (
19
19
  __version__,
@@ -13,7 +13,7 @@ from dataclasses import dataclass
13
13
  from pathlib import Path
14
14
  from typing import Any
15
15
 
16
- from soothe_sdk.client.config import SOOTHE_HOME
16
+ from soothe_sdk.paths import SOOTHE_HOME
17
17
 
18
18
  logger = logging.getLogger(__name__)
19
19
 
@@ -72,7 +72,10 @@ async def _fetch_provider_config(provider_name: str) -> dict[str, Any] | None:
72
72
  Provider config dict or None if not found.
73
73
  """
74
74
  try:
75
- from soothe_sdk.client import WebSocketClient, fetch_config_section
75
+ from soothe_client import (
76
+ WebSocketClient,
77
+ fetch_config_section,
78
+ )
76
79
 
77
80
  from soothe_cli.config.loader import load_config
78
81
 
@@ -480,7 +483,10 @@ def get_available_models() -> list[ModelProfileEntry]:
480
483
  try:
481
484
  import asyncio
482
485
 
483
- from soothe_sdk.client import WebSocketClient, fetch_config_section
486
+ from soothe_client import (
487
+ WebSocketClient,
488
+ fetch_config_section,
489
+ )
484
490
 
485
491
  # Use CLIConfig to get WebSocket URL
486
492
  from soothe_cli.config.loader import load_config
@@ -6,7 +6,10 @@ import logging
6
6
  from collections import OrderedDict
7
7
  from typing import TYPE_CHECKING
8
8
 
9
- from soothe_sdk.client import fetch_skills_catalog, websocket_url_from_config
9
+ from soothe_client import (
10
+ fetch_skills_catalog,
11
+ websocket_url_from_config,
12
+ )
10
13
 
11
14
  from soothe_cli.tui.skills.load import ExtendedSkillMetadata
12
15
 
@@ -33,7 +36,7 @@ async def discover_skills_async(
33
36
  (built-in first, winning entry last). Empty list if daemon
34
37
  unavailable.
35
38
  """
36
- from soothe_sdk.client import WebSocketClient
39
+ from soothe_client import WebSocketClient
37
40
 
38
41
  if daemon_config is None:
39
42
  logger.warning("No daemon_config provided for skills discovery; returning empty catalog")
@@ -29,7 +29,7 @@ from pathlib import Path
29
29
  from types import MappingProxyType
30
30
  from typing import TYPE_CHECKING, Any, ClassVar
31
31
 
32
- from soothe_sdk.client.config import SOOTHE_HOME
32
+ from soothe_sdk.paths import SOOTHE_HOME
33
33
 
34
34
  if TYPE_CHECKING:
35
35
  from collections.abc import Mapping
@@ -5,10 +5,10 @@ from __future__ import annotations
5
5
  import json
6
6
  from typing import Any
7
7
 
8
- from soothe_sdk.client.protocol import preview_first
9
8
  from soothe_sdk.tools.metadata import get_tool_meta
10
9
  from soothe_sdk.utils import get_tool_display_name
11
10
  from soothe_sdk.utils.formatting import convert_and_abbreviate_path
11
+ from soothe_sdk.wire.protocol import preview_first
12
12
 
13
13
  from soothe_cli.runtime.parse.message_processing import (
14
14
  _normalize_tool_name_for_arg_map,
@@ -21,7 +21,7 @@ from pathlib import Path
21
21
  from typing import Literal
22
22
 
23
23
  from packaging.version import InvalidVersion, Version
24
- from soothe_sdk.client.config import SOOTHE_HOME
24
+ from soothe_sdk.paths import SOOTHE_HOME
25
25
 
26
26
  from soothe_cli.tui._version import PYPI_URL, USER_AGENT, __version__
27
27
 
@@ -9,7 +9,7 @@ import time
9
9
  from pathlib import Path
10
10
  from typing import TYPE_CHECKING, Any, ClassVar
11
11
 
12
- from soothe_sdk.client.config import SOOTHE_DATA_DIR
12
+ from soothe_sdk.paths import SOOTHE_DATA_DIR
13
13
  from textual.binding import Binding
14
14
  from textual.containers import Horizontal, Vertical, VerticalScroll
15
15
  from textual.content import Content