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.
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/PKG-INFO +4 -4
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/README.md +3 -3
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/daemon.py +52 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/main.py +5 -11
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/command_registry.py +0 -7
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/slash_commands.py +0 -6
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/cli_config.py +3 -3
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/card.py +0 -5
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/preview_limits.py +14 -9
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_execution.py +28 -8
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_messages_mixin.py +3 -3
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_model.py +61 -26
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/composer_mode.py +6 -12
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/file_change_notify.py +5 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/textual_adapter.py +2 -0
- soothe_cli-1.0.9/src/soothe_cli/tui/widgets/context_data.py +890 -0
- soothe_cli-1.0.9/src/soothe_cli/tui/widgets/context_viewer.py +1182 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/_helpers.py +6 -33
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_step.py +5 -165
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_step_activity.py +209 -19
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/status.py +3 -9
- soothe_cli-1.0.8/src/soothe_cli/cli/commands/autopilot_cmd.py +0 -1690
- soothe_cli-1.0.8/src/soothe_cli/tui/widgets/context_data.py +0 -163
- soothe_cli-1.0.8/src/soothe_cli/tui/widgets/context_viewer.py +0 -458
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/.gitignore +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/pyproject.toml +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/_cli_context.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/_env_vars.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/_version.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/card_wire.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/config_cmd.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/cron_cmd.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/loop_cmd.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/run_cmd.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/commands/status_cmd.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/daemon_errors.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/headless.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/headless_renderer.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/execution/launcher.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/cli/soothecli.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/binding.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/command_router.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/commands/subagent_routing.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/loader.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/config/logging_setup.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/markdown_theme.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/media_utils.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/mermaid_render.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/path_utils.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/shell_color.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/spinner_labels.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/theme.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/tips.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/tool_display.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/display/unicode_security.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/loops/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/loops/sessions.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/model_config.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/project_utils.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/headless/processor.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/headless/processor_state.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/parse/tool_call_resolution.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/policy/tui_trace_log.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/duration_format.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/engine.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/id_format.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/renderer_base.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/renderer_protocol.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/step_id_format.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/presentation/subagent_task_display.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/file_tracker.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/session_stats.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/step_router.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/state/stream_accumulator.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/token_events_debug.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/token_usage.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/transport/session.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/turn/prepare.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/wire/chunk_filter.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/wire/message_text.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/runtime/wire/messages.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/_console_impl.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/bootstrap.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/core.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/glyphs.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/provider.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/shell_allow.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/skills_dirs.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/settings/stream_config.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_app.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_clarification.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_entrypoints.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_history.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_model_params.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_startup.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_terminal.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_theme_prefs.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_types.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/_ui.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/app/app.tcss +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/file_change_renderers.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/input.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/skills/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/skills/invocation.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/skills/load.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/_links.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/autocomplete.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/chat_input.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/clipboard.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/diff.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/editor.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/file_change_preview.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/help_screen.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/history.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/loading.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/loop_selector.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/mcp_viewer.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/message_store.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/__init__.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/app.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/assistant.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_goal_tree.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/cognition_reason.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/diff_message.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/error.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/skill.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/structured_ask_user.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/messages/user.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/model_selector.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/notification_settings.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/pinned_goal_bar.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/plan_quick_view_overlay.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/queued_goals_bar.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/router_profile_selector.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/theme_selector.py +0 -0
- {soothe_cli-1.0.8 → soothe_cli-1.0.9}/src/soothe_cli/tui/widgets/welcome.py +0 -0
- {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.
|
|
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
|
|
59
|
+
# Autopilot goal via the TUI /autopilot slash command (loop-native rail)
|
|
60
60
|
cd /path/to/repo
|
|
61
|
-
soothe
|
|
62
|
-
|
|
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
|
|
22
|
+
# Autopilot goal via the TUI /autopilot slash command (loop-native rail)
|
|
23
23
|
cd /path/to/repo
|
|
24
|
-
soothe
|
|
25
|
-
|
|
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
|
-
"
|
|
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 ("
|
|
229
|
+
if mode is not None and mode not in ("auto", "plan"):
|
|
231
230
|
typer.echo(
|
|
232
|
-
f"Invalid --mode {mode!r}; expected '
|
|
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,
|
|
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 /
|
|
48
|
-
#
|
|
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
|
-
"""'
|
|
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] =
|
|
15
|
-
|
|
16
|
-
#
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
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>\
|
|
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 →
|
|
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 →
|
|
1163
|
-
(auto/bypass
|
|
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
|
|
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
|
|
318
|
+
task: Task description, optionally prefixed with ``[rail_name]``.
|
|
311
319
|
"""
|
|
312
|
-
from
|
|
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
|
|
323
|
+
from soothe_cli.tui.widgets.messages import (
|
|
324
|
+
AppMessage,
|
|
325
|
+
ErrorMessage,
|
|
326
|
+
UserMessage,
|
|
327
|
+
)
|
|
320
328
|
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
343
|
-
await self._mount_message(ErrorMessage(f"Failed to submit autopilot
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
|
4
|
-
Shift+Tab cycle: Auto → Bypass →
|
|
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;
|
|
63
|
-
bypass →
|
|
64
|
-
|
|
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
|
|