soothe-cli 1.0.8__tar.gz → 1.0.9__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 (149) hide show
  1. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/PKG-INFO +4 -4
  2. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/README.md +3 -3
  3. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/daemon.py +52 -0
  4. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/main.py +5 -11
  5. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/command_registry.py +0 -7
  6. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/slash_commands.py +0 -6
  7. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/cli_config.py +3 -3
  8. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/card.py +0 -5
  9. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/preview_limits.py +14 -9
  10. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_execution.py +28 -8
  11. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_messages_mixin.py +3 -3
  12. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_model.py +61 -26
  13. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/composer_mode.py +6 -12
  14. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/file_change_notify.py +5 -0
  15. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/textual_adapter.py +2 -0
  16. soothe_cli-1.0.9/src/soothe_cli/tui/widgets/context_data.py +890 -0
  17. soothe_cli-1.0.9/src/soothe_cli/tui/widgets/context_viewer.py +1182 -0
  18. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/_helpers.py +6 -33
  19. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_step.py +5 -165
  20. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +209 -19
  21. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/status.py +3 -9
  22. soothe_cli-1.0.8/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -1690
  23. soothe_cli-1.0.8/src/soothe_cli/tui/widgets/context_data.py +0 -163
  24. soothe_cli-1.0.8/src/soothe_cli/tui/widgets/context_viewer.py +0 -458
  25. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/.gitignore +0 -0
  26. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/pyproject.toml +0 -0
  27. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/__init__.py +0 -0
  28. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/_cli_context.py +0 -0
  29. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/_env_vars.py +0 -0
  30. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/_version.py +0 -0
  31. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/card_wire.py +0 -0
  32. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/__init__.py +0 -0
  33. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/__init__.py +0 -0
  34. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
  35. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
  36. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
  37. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
  38. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
  39. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/__init__.py +0 -0
  40. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
  41. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/headless.py +0 -0
  42. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
  43. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/launcher.py +0 -0
  44. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/soothecli.py +0 -0
  45. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/__init__.py +0 -0
  46. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/binding.py +0 -0
  47. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/command_router.py +0 -0
  48. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/subagent_routing.py +0 -0
  49. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/__init__.py +0 -0
  50. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/loader.py +0 -0
  51. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/logging_setup.py +0 -0
  52. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/__init__.py +0 -0
  53. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/markdown_theme.py +0 -0
  54. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/media_utils.py +0 -0
  55. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/mermaid_render.py +0 -0
  56. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/path_utils.py +0 -0
  57. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/shell_color.py +0 -0
  58. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/spinner_labels.py +0 -0
  59. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/theme.py +0 -0
  60. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/tips.py +0 -0
  61. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/tool_display.py +0 -0
  62. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/unicode_security.py +0 -0
  63. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/loops/__init__.py +0 -0
  64. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/loops/sessions.py +0 -0
  65. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/model_config.py +0 -0
  66. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/project_utils.py +0 -0
  67. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/__init__.py +0 -0
  68. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/headless/processor.py +0 -0
  69. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
  70. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
  71. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
  72. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
  73. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/engine.py +0 -0
  74. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/id_format.py +0 -0
  75. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
  76. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
  77. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/step_id_format.py +0 -0
  78. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
  79. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
  80. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/session_stats.py +0 -0
  81. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/step_router.py +0 -0
  82. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
  83. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/token_events_debug.py +0 -0
  84. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/token_usage.py +0 -0
  85. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/transport/session.py +0 -0
  86. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/turn/prepare.py +0 -0
  87. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
  88. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/wire/message_text.py +0 -0
  89. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/wire/messages.py +0 -0
  90. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/__init__.py +0 -0
  91. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/_console_impl.py +0 -0
  92. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/bootstrap.py +0 -0
  93. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/core.py +0 -0
  94. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/glyphs.py +0 -0
  95. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/provider.py +0 -0
  96. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/shell_allow.py +0 -0
  97. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/skills_dirs.py +0 -0
  98. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/stream_config.py +0 -0
  99. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/__init__.py +0 -0
  100. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/__init__.py +0 -0
  101. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_app.py +0 -0
  102. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_clarification.py +0 -0
  103. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_entrypoints.py +0 -0
  104. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_history.py +0 -0
  105. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_model_params.py +0 -0
  106. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_startup.py +0 -0
  107. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_terminal.py +0 -0
  108. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_theme_prefs.py +0 -0
  109. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_types.py +0 -0
  110. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_ui.py +0 -0
  111. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/app.tcss +0 -0
  112. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/file_change_renderers.py +0 -0
  113. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/input.py +0 -0
  114. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/skills/__init__.py +0 -0
  115. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/skills/invocation.py +0 -0
  116. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/skills/load.py +0 -0
  117. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/__init__.py +0 -0
  118. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/_links.py +0 -0
  119. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
  120. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
  121. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
  122. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/diff.py +0 -0
  123. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/editor.py +0 -0
  124. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
  125. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
  126. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/history.py +0 -0
  127. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/loading.py +0 -0
  128. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
  129. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
  130. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/message_store.py +0 -0
  131. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
  132. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
  133. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
  134. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +0 -0
  135. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
  136. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
  137. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
  138. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
  139. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/structured_ask_user.py +0 -0
  140. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
  141. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
  142. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
  143. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/pinned_goal_bar.py +0 -0
  144. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +0 -0
  145. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/queued_goals_bar.py +0 -0
  146. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/router_profile_selector.py +0 -0
  147. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
  148. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/welcome.py +0 -0
  149. {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/update_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: soothe-cli
3
- Version: 1.0.8
3
+ Version: 1.0.9
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
@@ -56,10 +56,10 @@ soothe
56
56
  # Headless single-prompt mode
57
57
  soothe -p "Research AI advances"
58
58
 
59
- # Autopilot job from GOAL.md (greenfield-system rail)
59
+ # Autopilot goal via the TUI /autopilot slash command (loop-native rail)
60
60
  cd /path/to/repo
61
- soothe autopilot submit -f GOAL.md --rail greenfield-system
62
- soothe autopilot top
61
+ soothe
62
+ # Inside the TUI: /autopilot greenfield-system Refactor the auth module
63
63
 
64
64
  # Loop management
65
65
  soothe loop list
@@ -19,10 +19,10 @@ soothe
19
19
  # Headless single-prompt mode
20
20
  soothe -p "Research AI advances"
21
21
 
22
- # Autopilot job from GOAL.md (greenfield-system rail)
22
+ # Autopilot goal via the TUI /autopilot slash command (loop-native rail)
23
23
  cd /path/to/repo
24
- soothe autopilot submit -f GOAL.md --rail greenfield-system
25
- soothe autopilot top
24
+ soothe
25
+ # Inside the TUI: /autopilot greenfield-system Refactor the auth module
26
26
 
27
27
  # Loop management
28
28
  soothe loop list
@@ -66,6 +66,39 @@ def _parse_cron_slash_prompt(prompt: str) -> str | None:
66
66
  return rest
67
67
 
68
68
 
69
+ def _parse_autopilot_slash_prompt(prompt: str) -> tuple[str | None, str] | None:
70
+ """Parse a ``/autopilot`` slash command from headless input.
71
+
72
+ Mirrors the TUI ``/autopilot [rail_name] <goal description>`` syntax.
73
+ When the first token after ``/autopilot`` matches a builtin rail id,
74
+ it is used as the rail; otherwise the full text is the goal (no rail).
75
+
76
+ Args:
77
+ prompt: User input (e.g. ``/autopilot hotfix fix the login bug``).
78
+
79
+ Returns:
80
+ ``(rail_id, goal_text)`` tuple, or ``None`` if not an autopilot slash
81
+ command. ``rail_id`` is ``None`` when no rail prefix is present.
82
+ """
83
+ stripped = prompt.strip()
84
+ if not stripped.lower().startswith("/autopilot"):
85
+ return None
86
+ rest = stripped[len("/autopilot") :].strip()
87
+ if not rest:
88
+ return None
89
+ try:
90
+ from soothe.rails.catalog import BUILTIN_RAIL_IDS
91
+
92
+ tokens = rest.split(None, 1)
93
+ if len(tokens) >= 2 and tokens[0] in BUILTIN_RAIL_IDS:
94
+ return tokens[0], tokens[1].strip()
95
+ except Exception:
96
+ pass
97
+ # No rail prefix matched — return "auto" so the StrangeLoop auto-picks
98
+ # a rail via ``resolve_rail_for_job`` instead of running without one.
99
+ return "auto", rest
100
+
101
+
69
102
  async def _run_headless_session_once(
70
103
  cfg: Any,
71
104
  prompt: str,
@@ -98,6 +131,24 @@ async def _run_headless_session_once(
98
131
  typer.echo(f" Next run: {str(job.get('next_run', ''))[:19]}")
99
132
  return 0, False
100
133
 
134
+ # Parse /autopilot slash command (headless equivalent of the TUI handler).
135
+ # When the first token matches a builtin rail id, it is used as the rail;
136
+ # otherwise the full text is the goal (no rail).
137
+ autopilot_parsed = _parse_autopilot_slash_prompt(prompt)
138
+ autopilot_rail_id: str | None = None
139
+ if autopilot_parsed is not None:
140
+ autopilot_rail_id, autopilot_goal = autopilot_parsed
141
+ if not autopilot_goal:
142
+ _emit_headless_error(
143
+ "Usage: /autopilot [rail_name] <task description>\n"
144
+ "Example: /autopilot refactor the auth module\n"
145
+ "Example: /autopilot hotfix fix the login bug"
146
+ )
147
+ return 1, False
148
+ # Replace the prompt with the goal text so subagent parsing and
149
+ # send_turn operate on the clean goal, not the slash command.
150
+ prompt = autopilot_goal
151
+
101
152
  try:
102
153
  cli_ws = resolve_cli_loop_workspace()
103
154
  # Three first-class modes (batch / adaptive / streaming).
@@ -138,6 +189,7 @@ async def _run_headless_session_once(
138
189
  session.send_turn(
139
190
  effective_prompt,
140
191
  preferred_subagent=subagent_name,
192
+ autopilot_rail_id=autopilot_rail_id,
141
193
  ),
142
194
  timeout=_SESSION_BOOTSTRAP_TIMEOUT_S,
143
195
  )
@@ -175,9 +175,8 @@ def main(
175
175
  typer.Option(
176
176
  "--mode",
177
177
  help=(
178
- "Composer mode: 'auto' (veritas auto-answers), "
179
- "'manual' (relay AI questions to you), or 'plan' (read-only "
180
- "plan mode without typing /plan). Default: 'manual'."
178
+ "Composer mode: 'auto' (veritas auto-answers) or 'plan' "
179
+ "(read-only plan mode without typing /plan). Default: 'auto'."
181
180
  ),
182
181
  ),
183
182
  ] = None,
@@ -227,9 +226,9 @@ def main(
227
226
  raise typer.Exit
228
227
 
229
228
  home_path = Path(soothe_home).expanduser() if soothe_home else Path(SOOTHE_HOME)
230
- if mode is not None and mode not in ("manual", "auto", "plan"):
229
+ if mode is not None and mode not in ("auto", "plan"):
231
230
  typer.echo(
232
- f"Invalid --mode {mode!r}; expected 'manual', 'auto', or 'plan'.",
231
+ f"Invalid --mode {mode!r}; expected 'auto' or 'plan'.",
233
232
  err=True,
234
233
  )
235
234
  raise typer.Exit(code=2)
@@ -276,7 +275,6 @@ def main(
276
275
  # Sub-command groups (nested Typer apps)
277
276
  # ---------------------------------------------------------------------------
278
277
 
279
- from soothe_cli.cli.commands.autopilot_cmd import app as _autopilot_app # noqa: E402
280
278
  from soothe_cli.cli.commands.config_cmd import config_app as _config_app # noqa: E402
281
279
  from soothe_cli.cli.commands.cron_cmd import app as _cron_app # noqa: E402
282
280
  from soothe_cli.cli.commands.loop_cmd import loop_app as _loop_app # noqa: E402
@@ -284,16 +282,12 @@ from soothe_cli.cli.commands.status_cmd import status_app as _status_app # noqa
284
282
 
285
283
  for _sub_app, _name in (
286
284
  (_loop_app, "loop"),
287
- (_autopilot_app, "autopilot"),
288
285
  (_cron_app, "cron"),
289
286
  (_config_app, "config"),
290
287
  ):
291
288
  configure_command_group(_sub_app, show_help_on_no_args=True)
292
289
  app.add_typer(_sub_app, name=_name)
293
290
 
294
- # Alias: 'ap' -> 'autopilot'
295
- app.add_typer(_autopilot_app, name="ap")
296
-
297
291
  # status has a custom default action (combined status) — keep it; add help only.
298
292
  configure_command_group(_status_app, show_help_on_no_args=False)
299
293
  app.add_typer(_status_app, name="status")
@@ -309,7 +303,7 @@ def help_command(
309
303
  ctx: typer.Context,
310
304
  commands: Annotated[
311
305
  list[str] | None,
312
- typer.Argument(help="Optional command path (e.g. loop, loop list, autopilot top)."),
306
+ typer.Argument(help="Optional command path (e.g. loop, loop list, cron list)."),
313
307
  ] = None,
314
308
  ) -> None:
315
309
  """Show this message and exit."""
@@ -66,13 +66,6 @@ class SlashCommand:
66
66
 
67
67
 
68
68
  COMMANDS: tuple[SlashCommand, ...] = (
69
- SlashCommand(
70
- name="/autopilot",
71
- description="Submit autopilot job (usage: /autopilot <task>)",
72
- bypass_tier=BypassTier.QUEUED,
73
- hidden_keywords="goals autonomous job submit",
74
- enter_action=EnterAction.COMPLETE,
75
- ),
76
69
  SlashCommand(
77
70
  name="/cron",
78
71
  description="Add scheduled job (usage: /cron <natural language>)",
@@ -252,12 +252,6 @@ COMMANDS: dict[str, dict[str, Any]] = {
252
252
  "description": "Resume a loop by id",
253
253
  "params_schema": {"loop_id": {"type": "string", "required": True}},
254
254
  },
255
- "/autopilot": {
256
- "location": "daemon",
257
- "type": "routing",
258
- "description": "Submit autopilot job (usage: /autopilot <task>)",
259
- "requires_query": True,
260
- },
261
255
  "/cron": {
262
256
  "location": "daemon",
263
257
  "type": "rpc",
@@ -44,10 +44,10 @@ class CLIConfig:
44
44
  output_streaming_mode: str | None = None
45
45
  """Override daemon streaming mode: 'streaming' or 'batch'."""
46
46
 
47
- # Composer mode (Auto / Manual / Plan). Auto/Manual map to RFC-622
48
- # clarification_mode; Plan sets interaction_mode=plan (read-only plan graph).
47
+ # Composer mode (Auto / Plan). Auto maps to RFC-622 clarification_mode=auto;
48
+ # Plan sets interaction_mode=plan (read-only plan graph).
49
49
  clarification_mode: str | None = None
50
- """'manual', 'auto', or 'plan'. None = seed from daemon default_mode."""
50
+ """'auto' or 'plan'. None = seed from daemon default_mode."""
51
51
 
52
52
  # Resume behavior: when the launcher finds an active loop on startup,
53
53
  # auto-resume it (True) or prompt the user (False, default).
@@ -11,14 +11,9 @@ from __future__ import annotations
11
11
  from textual.content import Content
12
12
 
13
13
  from soothe_cli.display import theme
14
- from soothe_cli.display.preview_limits import STEP_TASK_CARD_COLLAPSE_LINE_THRESHOLD
15
14
  from soothe_cli.display.tool_display import display_width
16
15
  from soothe_cli.settings import get_glyphs
17
16
 
18
- # Preview limits imported from preview_limits module
19
- _STEP_TOOL_PREVIEW_ROWS = STEP_TASK_CARD_COLLAPSE_LINE_THRESHOLD
20
- """Collapsed step/task activity preview shows this many rows."""
21
-
22
17
 
23
18
  def _card_dot_tone(
24
19
  status: str,
@@ -5,16 +5,21 @@ from __future__ import annotations
5
5
  from typing import Final
6
6
 
7
7
  # --- Step cognition cards (`CognitionStepMessage`) ---
8
- # When False, step cards show the latest N tool activity lines in the branch tree
9
- # (task + main-agent scopes) plus per-kind counts on the running status line.
10
- # When True, the full nested tool list also renders in `#step-cognition-tools`.
11
- STEP_CARD_SHOW_TOOL_ROW_DETAILS: Final[bool] = False
12
-
13
8
  # Latest per-tool invocation lines on step cards and orphan SubAgent cards.
14
- STEP_CARD_TOOL_ACTIVITY_PREVIEW_COUNT: Final[int] = 2
15
-
16
- # Optional manual full tool-list folding threshold (not auto-collapse).
17
- STEP_TASK_CARD_COLLAPSE_LINE_THRESHOLD: Final[int] = 3
9
+ STEP_CARD_TOOL_ACTIVITY_PREVIEW_COUNT: Final[int] = 5
10
+
11
+ # File-edit branch on step cards: latest N file-write tool rows (write_file,
12
+ # edit_file, edit_lines, insert_lines, delete_lines, apply_diff, delete_file).
13
+ # The File-edit branch is always rendered (no gate); it only shows when there
14
+ # are file-write tool rows for the step.
15
+ STEP_CARD_FILE_EDIT_PREVIEW_COUNT: Final[int] = 5
16
+
17
+ # TUI file-change preview cards (the standalone write/edit/delete diff cards
18
+ # mounted in the chat transcript, NOT the step-card File-edit branch).
19
+ # When False (default), the standalone file-change cards are suppressed so
20
+ # file edits appear only in the step-card File-edit branch. Set True to also
21
+ # mount the dedicated diff/content preview cards.
22
+ TUI_FILE_CHANGE_CARDS_ENABLED: Final[bool] = False
18
23
 
19
24
  # Single-line task-description preview on task markers and orphan SubAgent headers.
20
25
  TASK_DELEGATION_DESC_MAX_CHARS: Final[int] = 80
@@ -347,11 +347,13 @@ class _ExecutionMixin:
347
347
 
348
348
  Reads `agent.clarification.default_mode` from the daemon config via
349
349
  a one-shot WebSocket RPC. Falls back to `"auto"` when the daemon is
350
- unreachable, the section is missing, or the value is not
351
- `auto`/`manual`.
350
+ unreachable, the section is missing, or the value is not `auto`.
351
+ A daemon-configured `manual` default clamps to `auto` (manual is no
352
+ longer a user-selectable composer mode; the auto→manual fallback
353
+ card is driven by the runtime clarification event instead).
352
354
 
353
355
  Returns:
354
- Normalized composer mode (`"auto"` or `"manual"`).
356
+ Normalized composer mode (`"auto"`).
355
357
  """
356
358
  from soothe_cli.tui.composer_mode import normalize_composer_mode
357
359
 
@@ -383,9 +385,11 @@ class _ExecutionMixin:
383
385
  """Seed the composer mode from the daemon's configured default.
384
386
 
385
387
  When the operator did not pass `--mode` explicitly, the TUI badge
386
- must reflect `agent.clarification.default_mode` from the daemon
387
- config (e.g. `manual`) rather than a hard-coded `auto`. This runs
388
- once after the daemon is ready so subsequent turns send the correct
388
+ reflects `agent.clarification.default_mode` from the daemon config
389
+ rather than a hard-coded `auto`. A daemon default of `manual` clamps
390
+ to `auto` (manual is no longer user-selectable; its fallback card is
391
+ driven by the runtime clarification event). This runs once after the
392
+ daemon is ready so subsequent turns send the correct
389
393
  `clarification_mode` wire field.
390
394
 
391
395
  When `--mode` was passed, `CLIConfig.clarification_mode` is set
@@ -735,13 +739,18 @@ class _ExecutionMixin:
735
739
  rewritten = f"/skill:skill-creator {args}" if args else "/skill:skill-creator"
736
740
  await self._handle_skill_command(rewritten)
737
741
  elif cmd == "/autopilot" or cmd.startswith("/autopilot "):
738
- # Submit autopilot job via WebSocket (like CLI `soothe autopilot submit`)
742
+ # Submit autopilot job via the normal loop submission path (not the
743
+ # autopilot_submit RPC) so the goal runs through StrangeLoop directly.
744
+ # When the first token matches a known builtin rail id, it is used as
745
+ # the autopilot rail: `/autopilot [rail_name] <goal description>`.
739
746
  args = command.strip()[len("/autopilot") :].strip()
740
747
  if not args:
741
748
  await self._mount_message(UserMessage(command))
742
749
  await self._mount_message(
743
750
  AppMessage(
744
- "Usage: /autopilot <task description>\nExample: /autopilot refactor the auth module"
751
+ "Usage: /autopilot [rail_name] <task description>\n"
752
+ "Example: /autopilot refactor the auth module\n"
753
+ "Example: /autopilot hotfix fix the login bug"
745
754
  )
746
755
  )
747
756
  return
@@ -909,6 +918,7 @@ class _ExecutionMixin:
909
918
  message: str,
910
919
  *,
911
920
  skip_daemon_send_turn: bool = False,
921
+ autopilot_rail_id: str | None = None,
912
922
  ) -> None:
913
923
  """Send a message to the agent and start execution.
914
924
 
@@ -920,6 +930,10 @@ class _ExecutionMixin:
920
930
  message: The prompt to send to the agent.
921
931
  skip_daemon_send_turn: When using a daemon session, only attach to
922
932
  the in-flight stream (prompt already queued on the daemon).
933
+ autopilot_rail_id: Optional builtin rail id. When set, the daemon
934
+ binds a ``LoopRailInterpreter`` for this goal via
935
+ ``run_with_progress(autopilot_rail_id=…)``. Used by the
936
+ ``/autopilot [rail_name] <goal>`` slash command.
923
937
  """
924
938
  # Anchor to bottom so streaming response stays visible
925
939
  with suppress(NoMatches, ScreenStackError):
@@ -955,6 +969,7 @@ class _ExecutionMixin:
955
969
  self._run_agent_task(
956
970
  message,
957
971
  skip_daemon_send_turn=skip_daemon_send_turn,
972
+ autopilot_rail_id=autopilot_rail_id,
958
973
  ),
959
974
  exclusive=False,
960
975
  )
@@ -1015,6 +1030,7 @@ class _ExecutionMixin:
1015
1030
  message: str,
1016
1031
  *,
1017
1032
  skip_daemon_send_turn: bool = False,
1033
+ autopilot_rail_id: str | None = None,
1018
1034
  ) -> None:
1019
1035
  """Run the agent task in a background worker.
1020
1036
 
@@ -1024,6 +1040,9 @@ class _ExecutionMixin:
1024
1040
  message: The prompt to send to the agent.
1025
1041
  skip_daemon_send_turn: When `True` with a daemon session, only
1026
1042
  consume the daemon stream (prompt already queued server-side).
1043
+ autopilot_rail_id: Optional builtin rail id forwarded to
1044
+ ``execute_task_textual`` → ``send_turn`` → ``loop_input`` so the
1045
+ daemon binds a ``LoopRailInterpreter`` for this goal.
1027
1046
  """
1028
1047
  # Caller ensures _ui_adapter is set (checked in _handle_user_message)
1029
1048
  if self._ui_adapter is None:
@@ -1073,6 +1092,7 @@ class _ExecutionMixin:
1073
1092
  clarification_mode=wire_clar,
1074
1093
  sticky_preferred_subagent=sticky_subagent,
1075
1094
  interaction_mode=wire_interaction,
1095
+ autopilot_rail_id=autopilot_rail_id,
1076
1096
  is_shutting_down=lambda: getattr(self, "_exit", False),
1077
1097
  )
1078
1098
  break
@@ -1147,7 +1147,7 @@ class _MessagesMixin:
1147
1147
  """Shift+Tab: navigate loop selector when active, otherwise cycle mode.
1148
1148
 
1149
1149
  - In the LoopSelectorScreen, defer to its filter navigation.
1150
- - On the main screen, cycle composer mode Auto → Bypass → Manual → Plan → Ask.
1150
+ - On the main screen, cycle composer mode Auto → Bypass → Plan → Ask.
1151
1151
  """
1152
1152
  from soothe_cli.tui.widgets.loop_selector import LoopSelectorScreen
1153
1153
 
@@ -1159,8 +1159,8 @@ class _MessagesMixin:
1159
1159
  def cycle_composer_mode(self) -> None:
1160
1160
  """Advance composer mode and refresh the status-bar badge.
1161
1161
 
1162
- Cycle: Auto → Bypass → Manual → Plan → Ask → Auto. For agent sub-modes
1163
- (auto/bypass/manual) hot-swap the running goal's agent mode via
1162
+ Cycle: Auto → Bypass → Plan → Ask → Auto. For agent sub-modes
1163
+ (auto/bypass) hot-swap the running goal's agent mode via
1164
1164
  `loop_set_clarification_mode`; Plan/Ask take effect on the next turn.
1165
1165
  """
1166
1166
  from soothe_cli.tui.composer_mode import (
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import logging
7
- import os
8
7
  from contextlib import suppress
9
8
  from typing import TYPE_CHECKING, Any
10
9
 
@@ -304,51 +303,87 @@ class _ModelMixin:
304
303
  self.push_screen(HelpScreen(), handle_result)
305
304
 
306
305
  async def _submit_autopilot_job(self, task: str) -> None:
307
- """Submit an autopilot job via WebSocket (like CLI `soothe autopilot submit`).
306
+ """Submit an autopilot goal via the normal loop submission path.
307
+
308
+ Parses an optional leading rail id from ``[rail_name] <goal>``. When the
309
+ first token matches a builtin rail id, it is used as ``autopilot_rail_id``
310
+ and forwarded through the loop_input wire so ``StrangeLoop.run_with_progress``
311
+ binds a ``LoopRailInterpreter`` for this goal. When no rail prefix is
312
+ present, the goal runs as a normal loop turn (no rail).
313
+
314
+ This bypasses ``AutopilotService.submit_task()`` — the goal is submitted
315
+ like any other user message via the daemon's loop_input dispatcher.
308
316
 
309
317
  Args:
310
- task: Task description for autonomous execution.
318
+ task: Task description, optionally prefixed with ``[rail_name]``.
311
319
  """
312
- from soothe_client import (
313
- async_command_client_from_config,
314
- is_daemon_live,
315
- websocket_url_from_config,
316
- )
320
+ from soothe.rails.catalog import BUILTIN_RAIL_IDS
317
321
 
318
322
  from soothe_cli.runtime import load_config
319
- from soothe_cli.tui.widgets.messages import ErrorMessage, UserMessage
323
+ from soothe_cli.tui.widgets.messages import (
324
+ AppMessage,
325
+ ErrorMessage,
326
+ UserMessage,
327
+ )
320
328
 
321
- await self._mount_message(UserMessage(f"/autopilot {task}"))
329
+ # Parse optional rail prefix: ``/autopilot [rail_name] <goal>``.
330
+ tokens = task.split(None, 1)
331
+ rail_id: str | None = None
332
+ goal_text = task
333
+ if len(tokens) >= 2 and tokens[0] in BUILTIN_RAIL_IDS:
334
+ rail_id = tokens[0]
335
+ goal_text = tokens[1].strip()
336
+ else:
337
+ # No rail prefix — send "auto" so the StrangeLoop auto-picks a
338
+ # rail via ``resolve_rail_for_job`` instead of running without one.
339
+ rail_id = "auto"
340
+
341
+ if not goal_text:
342
+ await self._mount_message(UserMessage(f"/autopilot {task}"))
343
+ await self._mount_message(
344
+ AppMessage(
345
+ "Usage: /autopilot [rail_name] <task description>\n"
346
+ "Example: /autopilot hotfix fix the login bug"
347
+ )
348
+ )
349
+ return
350
+
351
+ display = (
352
+ f"/autopilot {task}" if rail_id and rail_id != "auto" else f"/autopilot {goal_text}"
353
+ )
354
+ await self._mount_message(UserMessage(display))
355
+
356
+ # Verify the daemon is live before submitting via the loop path.
357
+ from soothe_client import is_daemon_live, websocket_url_from_config
322
358
 
323
359
  cfg = load_config()
324
360
  ws_url = websocket_url_from_config(cfg)
325
-
326
- # Check daemon is running
327
361
  if not await is_daemon_live(ws_url, timeout=5.0):
328
362
  await self._mount_message(
329
363
  ErrorMessage("Daemon not running. Start with 'soothed start'.")
330
364
  )
331
365
  return
332
366
 
333
- workspace = self._cwd if hasattr(self, "_cwd") else os.getcwd()
334
-
367
+ # Submit via the normal loop submission path (not autopilot_submit RPC).
368
+ # _send_to_agent → execute_task_textual → daemon_session.send_turn →
369
+ # loop_input RPC → run_with_progress(autopilot_rail_id=rail_id).
335
370
  try:
336
- client = async_command_client_from_config(cfg)
337
- result = await client.autopilot_submit(task, workspace=workspace)
338
- except RuntimeError as exc:
339
- await self._mount_message(ErrorMessage(str(exc)))
340
- return
371
+ await self._send_to_agent(goal_text, autopilot_rail_id=rail_id)
341
372
  except Exception as exc: # noqa: BLE001
342
- logger.exception("Autopilot submit failed")
343
- await self._mount_message(ErrorMessage(f"Failed to submit autopilot job: {exc}"))
373
+ logger.exception("Autopilot loop submission failed")
374
+ await self._mount_message(ErrorMessage(f"Failed to submit autopilot goal: {exc}"))
344
375
  return
345
376
 
346
- goal_id = result.get("goal_id", "")
347
- if goal_id:
348
- self.notify(f"Autopilot job submitted: {goal_id[:8]}", timeout=5)
349
- logger.info("Submitted autopilot job %s: %s", goal_id, task[:50])
377
+ if rail_id and rail_id != "auto":
378
+ self.notify(f"Autopilot goal submitted (rail: {rail_id})", timeout=5)
379
+ logger.info(
380
+ "Submitted autopilot goal via loop path (rail=%s): %s",
381
+ rail_id,
382
+ goal_text[:50],
383
+ )
350
384
  else:
351
- await self._mount_message(ErrorMessage("No goal_id returned from daemon"))
385
+ self.notify("Autopilot goal submitted", timeout=5)
386
+ logger.info("Submitted autopilot goal via loop path: %s", goal_text[:50])
352
387
 
353
388
  async def _submit_cron_job(self, text: str, *, slash_input: str | None = None) -> None:
354
389
  """Submit a cron job via WebSocket (like CLI `soothe cron add`).
@@ -1,7 +1,7 @@
1
1
  """TUI composer modes — working-mode hierarchy.
2
2
 
3
- Agent sub-modes (auto, bypass, manual) grouped first, then plan and ask.
4
- Shift+Tab cycle: Auto → Bypass → Manual → Plan → Ask → Auto.
3
+ Agent sub-modes (auto, bypass) grouped first, then plan and ask.
4
+ Shift+Tab cycle: Auto → Bypass → Plan → Ask → Auto.
5
5
  """
6
6
 
7
7
  from __future__ import annotations
@@ -9,7 +9,6 @@ from __future__ import annotations
9
9
  from dataclasses import dataclass
10
10
 
11
11
  COMPOSER_MODE_AUTO = "auto"
12
- COMPOSER_MODE_MANUAL = "manual"
13
12
  COMPOSER_MODE_PLAN = "plan"
14
13
  COMPOSER_MODE_ASK = "ask"
15
14
  COMPOSER_MODE_BYPASS = "bypass"
@@ -18,16 +17,13 @@ COMPOSER_MODE_BYPASS = "bypass"
18
17
  COMPOSER_MODE_ORDER: tuple[str, ...] = (
19
18
  COMPOSER_MODE_AUTO,
20
19
  COMPOSER_MODE_BYPASS,
21
- COMPOSER_MODE_MANUAL,
22
20
  COMPOSER_MODE_PLAN,
23
21
  COMPOSER_MODE_ASK,
24
22
  )
25
23
  VALID_COMPOSER_MODES: frozenset[str] = frozenset(COMPOSER_MODE_ORDER)
26
24
 
27
25
  # Agent working mode sub-modes (full mutating tool surface).
28
- AGENT_SUB_MODES: frozenset[str] = frozenset(
29
- {COMPOSER_MODE_AUTO, COMPOSER_MODE_MANUAL, COMPOSER_MODE_BYPASS}
30
- )
26
+ AGENT_SUB_MODES: frozenset[str] = frozenset({COMPOSER_MODE_AUTO, COMPOSER_MODE_BYPASS})
31
27
  # Standalone working modes (not agent sub-modes).
32
28
  STANDALONE_WORKING_MODES: frozenset[str] = frozenset({COMPOSER_MODE_PLAN, COMPOSER_MODE_ASK})
33
29
 
@@ -59,9 +55,9 @@ def next_composer_mode(current: str) -> str:
59
55
  def resolve_composer_wire_fields(mode: str) -> ComposerWireFields:
60
56
  """Map composer mode to wire fields.
61
57
 
62
- auto → clarification=auto, interaction=None; manual → clarification=manual;
63
- bypass → clarification=auto, interaction=bypass; plan → interaction=plan;
64
- ask → interaction=ask. Slash routing in the message wins over the sticky hint.
58
+ auto → clarification=auto, interaction=None; bypass → clarification=auto,
59
+ interaction=bypass; plan → interaction=plan; ask → interaction=ask.
60
+ Slash routing in the message wins over the sticky hint.
65
61
  """
66
62
  normalized = normalize_composer_mode(mode)
67
63
  if normalized == COMPOSER_MODE_PLAN:
@@ -70,6 +66,4 @@ def resolve_composer_wire_fields(mode: str) -> ComposerWireFields:
70
66
  return ComposerWireFields(COMPOSER_MODE_AUTO, None, COMPOSER_MODE_ASK)
71
67
  if normalized == COMPOSER_MODE_BYPASS:
72
68
  return ComposerWireFields(COMPOSER_MODE_AUTO, None, COMPOSER_MODE_BYPASS)
73
- if normalized == COMPOSER_MODE_MANUAL:
74
- return ComposerWireFields(COMPOSER_MODE_MANUAL, None, None)
75
69
  return ComposerWireFields(COMPOSER_MODE_AUTO, None, None)
@@ -6,6 +6,7 @@ import asyncio
6
6
  import logging
7
7
  from typing import TYPE_CHECKING, Any
8
8
 
9
+ from soothe_cli.display.preview_limits import TUI_FILE_CHANGE_CARDS_ENABLED
9
10
  from soothe_cli.runtime.state.file_tracker import (
10
11
  FILE_CHANGE_TOOLS,
11
12
  file_change_action_label,
@@ -97,6 +98,8 @@ async def mount_file_change_preview(
97
98
  assistant_id: Agent id for path resolution.
98
99
  file_op_tracker: Optional tracker with `recently_completed` lookups.
99
100
  """
101
+ if not TUI_FILE_CHANGE_CARDS_ENABLED:
102
+ return
100
103
  if tool_name not in FILE_CHANGE_TOOLS:
101
104
  return
102
105
  tcid = str(tool_call_id or "").strip()
@@ -208,6 +211,8 @@ async def mount_completed_file_change_preview(
208
211
  Returns:
209
212
  True when a completed preview card was mounted from the tracker record.
210
213
  """
214
+ if not TUI_FILE_CHANGE_CARDS_ENABLED:
215
+ return False
211
216
  tool_name = str(record.tool_name or "").strip()
212
217
  tcid = str(record.tool_call_id or "").strip()
213
218
  if tool_name not in FILE_CHANGE_TOOLS or not tcid:
@@ -3031,6 +3031,7 @@ async def execute_task_textual(
3031
3031
  clarification_mode: str | None = None,
3032
3032
  sticky_preferred_subagent: str | None = None,
3033
3033
  interaction_mode: str | None = None,
3034
+ autopilot_rail_id: str | None = None,
3034
3035
  is_shutting_down: Callable[[], bool] | None = None,
3035
3036
  ) -> SessionStats:
3036
3037
  """Execute a task with output directed to Textual UI."""
@@ -3202,6 +3203,7 @@ async def execute_task_textual(
3202
3203
  interaction_mode=interaction_mode,
3203
3204
  clarification_answer=sending_clarification_answer,
3204
3205
  clarification_answers=pending_clarification_answers,
3206
+ autopilot_rail_id=autopilot_rail_id,
3205
3207
  )
3206
3208
  chunk_source = daemon_session.iter_turn_chunks()
3207
3209